I'm very interested in how "old key presses" are handled.
Not at all. They are out of scope for CP/M.
The CONST and CONIN are direct mapped to respective BIOS functions of the same name (number 1/2 at offset 3 and 6).
Since the BIOS is machine specific, each and every handling of a keyboard buffer is as well depending on machine and manufacturer. After all, there are many ways that could be used, so CP/M as machine independent OS does not care.
If I start a process
There are no distinct processes on CP/M. Only the OS and whatever is loaded. All without any form of encapsulation or further Handling.
that wants to read keyboard input, and make a CONIN system call, and, if I have made a key press long before that, randomly, it seems like CONIN (by asserting CONST) should read the old key press as a new key press? This seems like the default case.
Exactly.
Except, there is no 'old' or 'new' key press, just a character coming in from the console device. A character that may be buffered in BIOS, some interface, or out in the terminals send buffer.
I can see ways to work around that, such as the processes clearing the status flag in keyboard controller as the first thing it does when it wants to get ready to accept keyboard input.
There is no such function. Not just because it's out of scope, but also as there is not necessarily a keyboard controller at all, or one that features a resetable status. Of for that matter a dedicated keyboard device.
CP/M is build upon the idea of a console device, which more often than not is a real, external terminal, not some build in keyboard and screen hardware. There are zillions of ways such configuration can buffer or not. BIOS can do, Interface can do, modem can do, other side modem can do, terminal send buffer can do terminal keyboard buffer can do - and maybe more I'm not thinking of right now can do.
Important to programmers: none of that matters to CP/M. It offers to check if there is (at least) one char waiting to be processed (CONST) and to read wait for a character to come (CONIN).
At that point it's also important to know that input may come from some file as well. In that case, all future input is ready right before the program starts :))
Or, perhaps (but less likely), having some kind of signal to the keyboard controller that "activates" it.
Not sure what that is supposed to mean.
How were "old characters" in buffer (and status register state) managed, generally?
As said, not at all. Neither by BIOS, BDOS, CCP, nor application.
Howto Clear Waiting Input
Now it seems that your intend is to clear the buffer of any previous/waiting character at start your program. That's rather easy done by reading as long as there is a character waiting:
while (CONST != 0) do CONIN.
Or Better Not
I would strongly suggest against doing so as it creates an unexpected behaviour. Users do rather quickly learn how a system behaves. Number of keypress that can be made while some activity is running - like loading data or starting a program - is one of them.
Sounds strange? Well, it's the way it was back then. Those weren't multi GHz systems with limitless memory and solid state drives transferring data a complete floppy a millisecond. Starting a program took several seconds plus several more for data files. All while the user was eager to continue work. Depending on setup and buffering capability he would already start typing before program load was finished.
This was not only common during program start, but as well while and other operation took it's time. Type ahead was an incredible useful feature for everyday life.
Clearing the buffer at some arbitrary point in the midst of that will stop that workflow and mangle the users mental image of what will happen.
Beside killing manual workflow, 'clearing' console input may destroy redirection. CP/M can redirect console input from other devices or from a file - like to automate a workflow. Since it's not defined if that input is already readable at program start or not, having such a 'clear' loop would possible render your program unusable with redirected input.
Bottom line: DON'T
And Now For Something Completely Different
From from one of your comments I get the impression you're trying to design some FPGA computer made for CP/M to run atop. A computer more like a PC with keyboard as a special device. In that case, I'd say build that keyboard interface a bit like the interface of a UART, using two ports:
Port ONE delivers the a character when a character is available.
If none is available it blocks (inserts wait states or what's appropriate)
Port TWO delivers the character available in port one, unless Port one has been read (and not refilled). In that case it delivers ZERO.
The Hardware version of the BIOS calls is now simply done reading either port:
- CONIN is reading port ONE.
- CONST is reading port TWO.
That's it.
To realise this means creating two components:
- an internal flag holding the value of character present
- a latch to hold a character (width depending on the character definition)
operating like this:
- handling of a a received character depends on that flag
- if cleared, the character gets put into the latch, the flag gets set
- if set the character gets ignored (For buffering see below)
- the latch can be read via port ONE or TWO
- reading via port ONE depends on the flag:
- if cleared, execution gets stopped until a character is received, that is the flag gets set (continue with set step)
- if set, the character gets returned, the flag gets cleared.
- reading via port TWO depends as well on the flag:
- if set, the character is returned
- if cleared 'all bits zero' is returned.
Shouldn't be more than a hand full of lines in VHDL. Adding buffering becomes a modular addition. Now the 'character received' event does not trigger the latch handling, but gets bound to a FIFO, whose 'not empty' output gets routed to the latch's 'character received'
Also, note, any'clearing' attempt done in hardware will be so fast, that it contradicts any purpose.