Design of the keyboard handler

Discussions on more advanced topics such as monolithic vs micro-kernels, transactional memory models, and paging vs segmentation should go here. Use this forum to expand and improve the wiki!
Post Reply
kutkloon7
Member
Member
Posts: 98
Joined: Fri Jan 04, 2013 6:56 pm

Design of the keyboard handler

Post by kutkloon7 »

After a long while of doing other stuff, I got a lot of time again and I decided to start working on my kernel again. The last thing I implemented is keyboard handling (using Brokenthorns tutorial, part of his series).

I have a interrupt handler written in ASM. It's more like a boilerplate really, it just calls the C interrupt handler. My initial approach was using a 1-character buffer (just for testing purposes) in addition to a buffer that hold a byte that is either 0 (not down) or 1 (down) for each key (but this last buffer is not really relevant to the rest of the story). I read some things on the scancodes and decided I would just go with trial-and-error conversion of the scancodes to ascii-codes, an approach that worked reasonably well (with the obvious downside that I don't really understand what's going on) when I just want to print characters on the screen. When I want to adjust the position of the cursor using the arrow keys, for example, it gets more complicated as the arrow keys cannot be passed as ascii-codes. I could just pass scancodes but then I would need to check for all special keys in the library (or wherever I want to do this) and this just feels wrong. A unified approach, where both the scancode and the ascii-code, if applicable, seems most beneficial to me.

However, I just checked and some special keys (especially the block printscreen, pause/break, insert, home, ...) have multiple-byte scancodes or other weird behaviour (printscreen seems to fire only when released, pause/break only when pressed). Also, handling and checking the various scancode sets concerns me, as I have no idea how to do that... The handling of keypresses seems like a fairly standard problem in OS development and I'm a little ashamed that I can't find a good solution on my own. I was hoping that there is a standard approach to do this. In any case I'd be open to suggestions or commentary. For the record: I read the wiki and googled, but it's possible I missed some things (and to be honest, I don't understand too much of the wiki, though I checked and Bochs seems to use scancode set 1).

Anyway, I have good experiences with this forum, so I'm hoping for some intelligent replies again :)
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Design of the keyboard handler

Post by Brendan »

Hi,

The first thing to do is to realise that there's 2 completely different/separate pieces of hardware involved. The first piece of hardware is the PS/2 controller, which is mostly just a thing that handles 2 serial ports. Note that these serial ports aren't RS232 but are PS/2. For this piece of hardware you mostly need some code (e.g. a device driver) to send and receives bytes to/from either serial port.

The second piece of hardware is whatever happens to be plugged into a serial port (e.g. the keyboard). The code for the keyboard driver just talks to the device driver for the PS/2 controller and has no need use an IO ports or IRQs itself. Of course there's no real reason why it has to talk to the device driver for the PS/2 controller - for testing purposes it could talk to a dummy thing that just replays hard-coded bytes (and in this way you could write a functional keyboard driver before you even bother writing any code for the PS/2 controller).

The keyboard driver typically ends up being 4 stages. The first stage is mostly a state machine; where receiving various bytes makes it change from one state (e.g. "waiting for second byte of multi-byte sequence") to a different state (e.g. "got last byte of multi-byte sequence"). The second stage is converting scan-codes into something less stupid (e.g. fixed size "key identifiers"). The third stage is to use "keyboard layout data" (e.g. lookup tables loaded from a file) to translate the "key identifiers" into some sort of "keypress packet" (where a "keypress packet" might include the states of important keys, a pressed/released flag, the key identifier itself, and possibly an optional unicode codepoint or ASCII value). The last stage is to send the resulting keypress packet to something else (GUI?).


Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
kutkloon7
Member
Member
Posts: 98
Joined: Fri Jan 04, 2013 6:56 pm

Re: Design of the keyboard handler

Post by kutkloon7 »

Brendan wrote:Hi,

The first thing to do is to realise that there's 2 completely different/separate pieces of hardware involved. The first piece of hardware is the PS/2 controller, which is mostly just a thing that handles 2 serial ports. Note that these serial ports aren't RS232 but are PS/2. For this piece of hardware you mostly need some code (e.g. a device driver) to send and receives bytes to/from either serial port.

The second piece of hardware is whatever happens to be plugged into a serial port (e.g. the keyboard). The code for the keyboard driver just talks to the device driver for the PS/2 controller and has no need use an IO ports or IRQs itself. Of course there's no real reason why it has to talk to the device driver for the PS/2 controller - for testing purposes it could talk to a dummy thing that just replays hard-coded bytes (and in this way you could write a functional keyboard driver before you even bother writing any code for the PS/2 controller).
Good point, I didn't consider this. I probably understood this before, but the last time I looked at the kernel was more than half a year ago. Thanks for mention :)
The keyboard driver typically ends up being 4 stages. The first stage is mostly a state machine; where receiving various bytes makes it change from one state (e.g. "waiting for second byte of multi-byte sequence") to a different state (e.g. "got last byte of multi-byte sequence"). The second stage is converting scan-codes into something less stupid (e.g. fixed size "key identifiers"). The third stage is to use "keyboard layout data" (e.g. lookup tables loaded from a file) to translate the "key identifiers" into some sort of "keypress packet" (where a "keypress packet" might include the states of important keys, a pressed/released flag, the key identifier itself, and possibly an optional unicode codepoint or ASCII value). The last stage is to send the resulting keypress packet to something else (GUI?).


Cheers,

Brendan
Thanks for the information! I'm currently in the pre-first stage, as I only look at single bytes :P
Again, excellent suggestion and information!
kutkloon7
Member
Member
Posts: 98
Joined: Fri Jan 04, 2013 6:56 pm

Re: Design of the keyboard handler

Post by kutkloon7 »

Hm. After a night of sleep I realize that I'm still not in the clear about this. A major thing I'm not sure about is the way I handle interrupts. Currently, the interrupts are being handled by the OS, and the application needs to poll for keypresses. This is of course not ideal: a better system would be one where programs gets notified of any keypresses via a mechanism that gets started in an interrupt handler. So I would have to make some kind of list of handlers that need to be called in case of an interrupt (and a possibility for programs to add a handler on the list). Currently, I have not implemented any multithreading support or distinction between kernel and user space programs whatsoever, so the equivalent to window's "pause" would be to simple make sure the interrupt handler notifies your program on a keypress, then run a HLT instruction? (I'm not sure about this, as this would mean that the interrupt would never be returned from...)
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Design of the keyboard handler

Post by Brendan »

Hi,
kutkloon7 wrote:Hm. After a night of sleep I realize that I'm still not in the clear about this. A major thing I'm not sure about is the way I handle interrupts. Currently, the interrupts are being handled by the OS, and the application needs to poll for keypresses. This is of course not ideal: a better system would be one where programs gets notified of any keypresses via a mechanism that gets started in an interrupt handler. So I would have to make some kind of list of handlers that need to be called in case of an interrupt (and a possibility for programs to add a handler on the list). Currently, I have not implemented any multithreading support or distinction between kernel and user space programs whatsoever, so the equivalent to window's "pause" would be to simple make sure the interrupt handler notifies your program on a keypress, then run a HLT instruction? (I'm not sure about this, as this would mean that the interrupt would never be returned from...)
What you're looking for is some form of inter-process communication (IPC). Note: This isn't a good name for it - it's not necessarily limited to processes only, and may be used for communication between kernel and applications, kernel IRQ handlers and kernel threads, etc.

The basic idea is to have a way of sending data from one place to another; with a way of receiving the data that includes blocking/waiting until data is available is there currently isn't any to receive (where the task gets no CPU time until something sends data to it).

Also don't forget that there's typically several layers. For an example; when an IRQ occurs:
  • the kernel's IRQ handler might send an "IRQ occurred" message to the PS/2 driver
  • the PS/2 driver might unblock and receive the message; and handle it by getting a byte from the first PS/2 port and sending a "byte received" message to the keyboard driver
  • the keyboard driver might unblock and receive the message; and handle it by building a "keypress packet" message and sending it to some sort of virtual terminal layer
  • the virtual terminal layer might unblock and receive the message; and handle it by forwarding the message to a GUI
  • the GUI might unblock and receive the message; and handle it by forwarding the message to an application
  • the application might unblock and receive the message

Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
kutkloon7
Member
Member
Posts: 98
Joined: Fri Jan 04, 2013 6:56 pm

Re: Design of the keyboard handler

Post by kutkloon7 »

Brendan wrote:Hi,
kutkloon7 wrote:Hm. After a night of sleep I realize that I'm still not in the clear about this. A major thing I'm not sure about is the way I handle interrupts. Currently, the interrupts are being handled by the OS, and the application needs to poll for keypresses. This is of course not ideal: a better system would be one where programs gets notified of any keypresses via a mechanism that gets started in an interrupt handler. So I would have to make some kind of list of handlers that need to be called in case of an interrupt (and a possibility for programs to add a handler on the list). Currently, I have not implemented any multithreading support or distinction between kernel and user space programs whatsoever, so the equivalent to window's "pause" would be to simple make sure the interrupt handler notifies your program on a keypress, then run a HLT instruction? (I'm not sure about this, as this would mean that the interrupt would never be returned from...)
What you're looking for is some form of inter-process communication (IPC). Note: This isn't a good name for it - it's not necessarily limited to processes only, and may be used for communication between kernel and applications, kernel IRQ handlers and kernel threads, etc.

The basic idea is to have a way of sending data from one place to another; with a way of receiving the data that includes blocking/waiting until data is available is there currently isn't any to receive (where the task gets no CPU time until something sends data to it).

Also don't forget that there's typically several layers. For an example; when an IRQ occurs:
  • the kernel's IRQ handler might send an "IRQ occurred" message to the PS/2 driver
  • the PS/2 driver might unblock and receive the message; and handle it by getting a byte from the first PS/2 port and sending a "byte received" message to the keyboard driver
  • the keyboard driver might unblock and receive the message; and handle it by building a "keypress packet" message and sending it to some sort of virtual terminal layer
  • the virtual terminal layer might unblock and receive the message; and handle it by forwarding the message to a GUI
  • the GUI might unblock and receive the message; and handle it by forwarding the message to an application
  • the application might unblock and receive the message

Cheers,

Brendan
Thanks for the help! Currently I'm working on a much lower level though. The actual problem is in the sending of the keypacket and the unblocking of the process (I'm not sure if I should call it a process if I only have 1 process: maybe usercode or something is a better word).

Hm, I wrote something and deleted it for three times now :P I think things are starting to make sense. I guess I could do something like:

Code: Select all

void waitForKeyPress(void)
{
    wait = true;
    while (wait)
    {
        halt();
    }
}
where halt just does the asm command HLT, and then in the key handler I set wait to false. I will read your commands again when I'm ready for a better key driver and proper IPC :).
User avatar
Brendan
Member
Member
Posts: 8561
Joined: Sat Jan 15, 2005 12:00 am
Location: At his keyboard!
Contact:

Re: Design of the keyboard handler

Post by Brendan »

Hi,
kutkloon7 wrote:Thanks for the help! Currently I'm working on a much lower level though. The actual problem is in the sending of the keypacket and the unblocking of the process (I'm not sure if I should call it a process if I only have 1 process: maybe usercode or something is a better word).

Hm, I wrote something and deleted it for three times now :P I think things are starting to make sense. I guess I could do something like:

Code: Select all

void waitForKeyPress(void)
{
    wait = true;
    while (wait)
    {
        halt();
    }
}
where halt just does the asm command HLT, and then in the key handler I set wait to false. I will read your commands again when I'm ready for a better key driver and proper IPC :).
If you've only got one single-threaded process; then you've got 2 cases:
  • One thread is running
  • No threads are running
The following code is really bad, but should give you a better idea of what you're missing:

Code: Select all

void reschedule(void) {
    while (number_of_threads_running == 0) {
        // Scheduler's idle loop!
        halt();
    }
    // Determine which thread should get CPU time here (later)
    // Switch to selected thread here (later)
}

void changeThreadState(thread *thread, int newState) {
    if(thread->state == newState) {
        return;
    }
    if(thread->state == RUNNING) {
       thread->state = newState;
       number_of_threads_running--;
       if(thread == currentlyRunningThread) {
           reschedule();                          // Currently running thread stopped running
        }
    } else {
       thread->state = newState;
       if(newState == RUNNING) {
           number_of_threads_running--;
           reschedule();                            // The thread was unblocked
        }
    }
}

void sendMessage(thread *receiver, void *message) {
    // Put the message on a queue or something here
    if(receiver->messageQueueBottom == NULL) {
       receiver->messageQueueTop = message; 
    } else {
       receiver->messageQueueBottom->next = message; 
    }
    receiver->messageQueueBottom = message; 

    // If the receiving thread was blocked waiting for a message, tell scheduler the thread is running now
    if(receiver->state == WAITING_FOR_MESSAGE) {
        changeThreadState(receiver, RUNNING);
    }
}

void *getMessage(void) {
    thread *thisThread = the_data_for_the_thread_currently_running_on_this_CPU;
    void *message;

    while(thisThread->messageQueueTop == NULL) {
        // Tell scheduler this thread is waiting for a message now
        changeThreadState(receiver, WAITING_FOR_MESSAGE);
    }

    // Get the message

    message = thisThread->messageQueueTop;
    thisThread->messageQueueTop = message->next;
    return message;
}

Cheers,

Brendan
For all things; perfection is, and will always remain, impossible to achieve in practice. However; by striving for perfection we create things that are as perfect as practically possible. Let the pursuit of perfection be our guide.
Post Reply