6

CP/M uses CONIN to read a single character from the keyboard, and it will assert CONST to poll the status register for incoming characters. Let's assume the keyboard controller can buffer one character, has a 1-bit status register, and the I/O is done by polling. Reading a character will clear the status register (set it to 0. )

I assume CONST will always be set to 1 if a key press is made, if the keyboard controller is powered on.

I'm very interested in how "old key presses" are handled.

If I start a process 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.

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. Or, perhaps (but less likely), having some kind of signal to the keyboard controller that "activates" it.

How were "old characters" in buffer (and status register state) managed, generally?

2
  • 1
    This seems similar to the 86-DOS destructive vs nondestructive reads. To make sure a read is new, empty the buffers at one point. That is done by (nondestructively) polling for input and (destructively) reading that input, in a loop, until there is no more input available. Any reads after that will be sure to read new input.
    – ecm
    Commented Jan 29, 2023 at 10:59
  • @ecm Thanks! Good point. Good to know the standard approach to the problem.
    – BipedalJoe
    Commented Jan 29, 2023 at 14:00

2 Answers 2

9

Keep in mind that if you call CONIN and there is no NEW character available, it will block until one is available. That's the purpose of the CONST function, it allows an application to check for a key without blocking.

If there is a key, and you call CONIN, the key is returned and the buffer where it's been held is cleared along with the flag. If you call CONIN again and there is no key, you will not get the old key, the call will block until there is one.

See this for BIOS example: BIOS example

Note the CONIN and CONST functions:

See how conin loops until the char is ready? No timer, no interrupts, just polling the status bit until something happens.

conin:          ;returns console character in register a
in  a,(3)   ;get status
and 002h    ;check RxRDY bit
jp  z,conin ;loop until char ready
in  a,(2)   ;get char
AND 7fh ;strip parity bit
ret 

Note how this just checks the status and never loops:

const:          ;console status, return 0ffh if character ready, 00h if not
in  a,(3)   ;get status
and 002h    ;check RxRDY bit
jp  z,no_char
ld  a,0ffh  ;char ready
ret
no_char:    ld  a,00h   ;no char
ret

Just to be clear, when a BIOS call like CONIN blocks it will NOT return until there is a character available. If you call CONIN in a loop and there are no characters it will block or hang or wait until there is one. It may wait there forever if nobody ever presses a key.

20
  • 2
    Thanks. I understand that repeatedly calling CONIN will return a new key every time. I wonder about how an "old character" that is left in the buffer (and flagged as "new" by the status flag) is treated by a process that initiates some kind of read loop for the first time.
    – BipedalJoe
    Commented Jan 29, 2023 at 11:24
  • The handling of the status flag and the buffer is the responsibility of the BIOS code. Again it should NEVER return the old character but if called block until a new one is available. Do you understand what I mean by "BLOCK" here?
    – jwh20
    Commented Jan 29, 2023 at 11:29
  • If CONIN clears the status flag when it is called, and then waits for the status flag to be set, that could explain it. I don't know exactly what you mean with BLOCK here. Is BLOCK a CP/M term I can read up on? Or is it in a more general term in this context.
    – BipedalJoe
    Commented Jan 29, 2023 at 11:46
  • 1
    I can only say what I think from my perspective. If the keyboard controller will operate somewhat independently. It will receive a char when a key is pressed. The process then wants to read with CONIN. If it does not clear or ignore whatever is there from before, it might read "old keypress" as if it was new. This is not a problem if it somehow clears the register it checks (which does not matter) when it is called, and then waits for new input. Otherwise it uses "typeahead" approach. I think it is possible to see what I wondered about.
    – BipedalJoe
    Commented Jan 29, 2023 at 12:39
  • 1
    I believe you're overthinking this in the context of today's systems. CP/M was very low-level and the machine interface was quite simple. There was no concept of a device driver or the like. See my updated answer for more on how the BIOS worked.
    – jwh20
    Commented Jan 29, 2023 at 13:12
6

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.

10
  • Surely the normal way to start a new program is by running it from the prompt, meaning that the ENTER key will be the last thing read (and status-cleared) before the program runs. The first keystroke after that belongs to the program whether it's ready for it immediately or takes ten minutes before it tries to get it. In which case it would have been buffered for the program. So I've gotta go with Raffzahn here, the concept of old keystrokes doesn't come into play because all keystrokes after the program starts, belong to the program.
    – paxdiablo
    Commented Jan 29, 2023 at 12:57
  • 2
    Thanks for answer. What I can gather from answer is that I was right that type ahead is the default case. I agree it is useful in many cases. And I assume that when it isn't desired then the program running (the process running in the transient program area) would have to clear/reset whatever is used to signal "new character", before reading. And I assume from answer that CONIN does not do that automatically, instead it has to be done manually. It all sounds good to me. Thanks for helping me get a better understanding!
    – BipedalJoe
    Commented Jan 29, 2023 at 13:02
  • 2
    OK. The char register set to 0 is the same as no new character then, I assume. I was assuming it would either use that approach, or have a separate status register. What I wondered about was if type ahead is default, or not. I think there are cases where type ahead isn't desirable. And I understand it can be manually turned off by clearing whatever is used to check if a new char is available. Thank you for your answer.
    – BipedalJoe
    Commented Jan 29, 2023 at 13:20
  • 1
    @paxdiablo good point about the prompt ENTER being the last read (and status-cleared) before program runs. And thank you both again for helping me out with question.
    – BipedalJoe
    Commented Jan 29, 2023 at 13:35
  • 2
    Yes I understood that. And it verifies the assumption I had that type ahead was default. Whether or not programs would want to avoid it, I can't say for sure, but intuitively, if I play a game and it opens a query text field, I wouldn't want some random key press from before to pop up first. But I see how a program can avoid it if they want. The flag internally that is a port, sounds a bit like the status flag I was assuming might be there. Thanks for your answer, it helped me understand things better!
    – BipedalJoe
    Commented Jan 29, 2023 at 14:09

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .