Page 1 of 1

Keyboard input.

Posted: Sat Feb 13, 2016 12:39 pm
by Najimac
Hello, I have searched everywhere but have not found a single good implementation of a keyboard input driver.
I don't care about keyboard leds, control keys.
I have everthing setup, irq, io, I just need help.

Code: Select all

/* KBDUS means US Keyboard Layout. This is a scancode table
*  used to layout a standard US keyboard. I have left some
*  comments in to give you an idea of what key is what, even
*  though I set it's array index to 0. You can change that to
*  whatever you want using a macro, if you wish! */
#include "common.h"
#include "isr.h"
#include <stdbool.h>
#include "kb.h"

unsigned char kbdus[] =
{
    0,  27, '1', '2', '3', '4', '5', '6', '7', '8',   /* 9 */
  '9', '0', '-', '=', '\b',   /* Backspace */
  '\t',         /* Tab */
  'q', 'w', 'e', 'r',   /* 19 */
  't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\n',      /* Enter key */
    0,         /* 29   - Control */
  'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';',   /* 39 */
'\'', '`',   0,      /* Left shift */
'\\', 'z', 'x', 'c', 'v', 'b', 'n',         /* 49 */
  'm', ',', '.', '/',   0,               /* Right shift */
..............................
};
char c;
char* buff;
void keyboard_handler()
{
  asm volatile("cli");
  u8int in = inb(0x60);
  c = kbdus[in];
  asm volatile("sti");
}
char* getbuff() {
}
void keyboard_init()
{
  register_interrupt_handler(IRQ1,keyboard_handler);
}


Thanks in advance.

Re: Keyboard input.

Posted: Sun Feb 14, 2016 2:35 am
by Techel
Whats your question?

Re: Keyboard input.

Posted: Sun Feb 14, 2016 3:09 am
by Brendan
Hi,

I don't know what you're actually having trouble with; but looking at the source code I see 2 areas that need work.

The first is that you will need a kind of state machine in the IRQ handler. For example, "a" is a bit different to "shift + a" and "control+a" is very different to both, so you need to know about the current state to determine what to do when you get the scan code for "a". In the same way, some scancodes are 2 or more bytes - you'll get an escape code then a second byte, and if you don't remember that you previously got the escape code you won't know what to do with the second byte.

The second thing is that you need some way to transfer "key presses" (not characters, as there are no character to represent "F1 key" or "pause key" or "insert key") to something else (e.g. from the IRQ handler to the other half of the keyboard driver, or from the keyboard driver to a GUI or virtual terminal layer or something). This is typically a 2 part problem - transferring the data itself, plus some way to arrange for the receiver to get CPU time. There are multiple different ways to meet both requirements (deferred procedure call with fixed buffer, message passing, pipes, sockets, ....). Note that in my opinion it's a mistake to begin writing any device driver before "communication between things" (IPC) is implemented and tested.


Cheers,

Brendan

Re: Keyboard input.

Posted: Sun Feb 14, 2016 5:30 am
by Najimac
Brendan, I won't have a problem with implementing control keys, I am looking for a simple working implementation of an irq based keyboard driver,
I was asking about that.
That code is just a template for driver, I have tried using a variable that is set true (I have booleans imported.) when a key is pressed then a while loop waits for it, I had no luck with that.

Re: Keyboard input.

Posted: Sun Feb 14, 2016 6:40 am
by Brendan
Hi,
Najimac wrote:Brendan, I won't have a problem with implementing control keys, I am looking for a simple working implementation of an irq based keyboard driver,
I was asking about that.
I don't think it works like that. You can have a simple implementation that doesn't work or only works under very specific conditions; and you can have a complex implementation that actually works; but you can't have a simple working implementation because any definition of "working" implies a certain amount of unavoidable complexity.

Instead; my advice is to split it into 4 logical layers:
  • The first layer is is IRQ handling (e.g. PIC chips) and "sends" some kind of notification that attention is needed.
  • The second layer gets a byte from the PS/2 port and (using a small state machine) converts it into an "initial key press packet" consisting of a fixed size key code and a "pressed/released" flag. If and only if it was the last byte of a "one or more byte" sequence and you do end up with a valid "initial key press packet", the packet is "sent" to the next layer
  • The third layer keeps track of the current state of each key (whether it's currently being pressed or not) with a "one bit per key code" table. It updates this table, then uses the table to add more information to the "key press packet", including flags that indicate if meta-keys (shift, control, alt) where being held down at the time, including flags that indicate the capslock/numlock/scrolllock state at the time, and including determining if the key is "pressed" or "repeated". When done, the packet is "sent" to the next layer.
  • The fourth layer uses the information in the packet (combined with a keyboard layout lookup table) to determine a "character code" of some sort (UTF-32LE?) and (if it makes sense) adds that character code to the packet. After this you've got a "key press packet" that contains complete information (e.g. pressed/released/repeated, 2 control key states, 2 shift key states, 2 alt key states, the 3 capslock/numlock/scrolllock states, the key code, the character code). This final packet is "sent" to whatever (GUI, virtual terminal layer).
Note that throughout this I've deliberately written "sent" in quotes for a reason. Each occurrence of "sent" may be as simple as a function call or may be something much more complex (pipes, messages, etc). It depends on where each layer is implemented. For example; you might implement all the layers within the IRQ handler (which is probably a bad idea - you should do the least work possible in an IRQ handler) and in that case almost all of the places I've said "sent" (all except the last) could be a function call. For another example; you could implement each layer as a completely separate process in user space (which is a bad idea - the task switching overhead alone would be insane) and in that case all of the places I've said "sent" could involve message passing.

Typically, for a monolithic kernel, the first layer "sends" to the second layer using a function call, the second layer "sends" to the third layer using some kind of "deferred procedure call" (Windows) or "kernel tasklet" (Linux) or equivalent (it's all the same idea with different names), then third layer "sends" with a function call, and the fourth layer "sends" with either messaging (e.g. dbus) or pipes.

Typically, for a micro-kernel, the first layer "sends" to the second layer using message passing, the last layer "sends" with message passing, and everything in between "sends" with function calls.

Now...

If you think about it, each of these layers is very simple. You should be able to implement each layer just using documentation for whichever scan-code set you're using and a table showing the character codes for whichever character set you're using; without relying on some half-baked/broken example. The hard part is figuring out what "sent" means for your OS, and (for anything that isn't a function call) implementing the mechanism to send/receive.

TL;DR: You think you want a simple working implementation; but you don't want a simple working implementation. ;)


Cheers,

Brendan

Re: Keyboard input.

Posted: Sun Feb 14, 2016 7:06 am
by Najimac
I have the first layer ready, I am not worried about that (It is not that hard).
So I can implement the message as a struct with a character and the key state, And then
I listen for it, But wouldn't checking every possible key be slow.
But translating the character once it's received be simpler?
I mean like stacked based communication would be a bit faster but I am not sure how well it will turn out.
But how would I know if this is the last byte, That depends on the current usage of the keyboard input (Like wait until newline is pressed?).
I think a microkernel will be a bit harder to implement, I am not focusing on anything but keyboard input, I cannot implement ipc right now.

Re: Keyboard input.

Posted: Sun Feb 14, 2016 7:57 am
by Brendan
Hi,

Today I am blessed with the gift of time travel.. :)
Najimac wrote:I have the first layer ready, I am not worried about that (It is not that hard).
So I can implement the message as a struct with a character and the key state, And then
I listen for it, But wouldn't checking every possible key be slow.
Brendan wrote:The second thing is that you need some way to transfer "key presses" (not characters, as there are no character to represent "F1 key" or "pause key" or "insert key") to something else (e.g. from the IRQ handler to the other half of the keyboard driver, or from the keyboard driver to a GUI or virtual terminal layer or something). This is typically a 2 part problem - transferring the data itself, plus some way to arrange for the receiver to get CPU time.
Transferring the data itself can be (e.g.) pushing the parameters for a function call on the stack, or storing a message in memory (in a queue?) somewhere. Arranging for the receiver to get CPU time can be actually calling the function after the parameters are stored (the "call" instruction), or telling the scheduler to unblock the task that received the message.

If you only do half (transfer the data but don't arrange for the receiver to get CPU time) then it ends up broken. A polling loop (e.g. constantly checking if a message was received) is one of the consequences of failing to do things properly (e.g. not being able to tell the scheduler, "don't give me any CPU time until a message is received").
Najimac wrote:But translating the character once it's received be simpler?
I mean like stacked based communication would be a bit faster but I am not sure how well it will turn out.
Brendan wrote:For example; you might implement all the layers within the IRQ handler (which is probably a bad idea - you should do the least work possible in an IRQ handler) and in that case almost all of the places I've said "sent" (all except the last) could be a function call.
The reason it's bad is other more important IRQs can be waiting while you're doing less important work; which results in poor IRQ latency (time between IRQ occurring and being serviced) and worse performance. Note the "(all except the last)" - a keyboard driver that directly calls a function within (e.g.) a GUI would be completely insane. This implies that you must implement something and can't just use function calls for everything.
Najimac wrote:But how would I know if this is the last byte, That depends on the current usage of the keyboard input (Like wait until newline is pressed?).
If you look at the scan code table (for whichever scan code set you're using) you'll see that most multi-byte scan codes begin with a special "escape code" byte and are followed by a second byte. You only need a small state machine to keep track of which special escape code bytes have been received.

For example:

Code: Select all

    byte = in(0x60);

    keycode = 0;
    switch(state) {
        case 0:
            if(byte == ESCAPE_CODE1) {
                state = 1;
            } else {
                // last byte of a 1-byte scancode!
                keycode = byte;
            }
            break;
        case 1:
                // last byte of a 2-byte scancode!
                keycode = byte + 0x100;
            break;
    }
    if(keycode != 0) {
        // Got a full keycode!
        send_to_next_layer(keycode);
    }
For some keys (pause/break) the multi-byte sequence is larger, but you just add more states to the state machine to cover that.
Najimac wrote:I think a microkernel will be a bit harder to implement, I am not focusing on anything but keyboard input, I cannot implement ipc right now.
Brendan wrote:Note that in my opinion it's a mistake to begin writing any device driver before "communication between things" (IPC) is implemented and tested.
This has nothing to do with micro-kernels. The "deferred procedure calls" and the "kernel tasklets" are both intended for monolithic kernels. Things like messages and pipes are also implemented in virtually all monolithic kernels.

Mostly; this is a bit like waking up and going shopping. You might get to the shops, and you might choose the items you want to purchase; but you still won't be able to buy anything because you're naked. If you actually want to do shopping successfully; you have to wake up, get dressed, and then go shopping. You can't repeatedly say "I don't want to get dressed" (or "I don't want to implement IPC") and the get all confused when you fail. ;)


Cheers,

Brendan

Re: Keyboard input.

Posted: Sun Feb 14, 2016 8:41 am
by Najimac
Thanks Branden.
But how do I check if it was pressed, I get messages twice, I think it's sending one for press and one for release.

Re: Keyboard input.

Posted: Sun Feb 14, 2016 9:32 am
by Brendan
Hi,
Najimac wrote:Thanks Branden.
But how do I check if it was pressed, I get messages twice, I think it's sending one for press and one for release.
Cool!

To tell them apart, check if the "pressed or released" flag is set (that's what it's for)... :)


Cheers,

Brendan