Questions about the behaviour of PS/2 Keyboard

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
atomtables
Posts: 5
Joined: Fri Nov 08, 2024 5:08 pm
Libera.chat IRC: atomtables

Questions about the behaviour of PS/2 Keyboard

Post by atomtables »

I've been trying my best to understand how a PS/2 (AT) keyboard works, but it's been confusing. In most places, it says that you have to wait for the keyboard to respond after sending a command. But from my testing, if I try to wait for the keyboard to set the input bit, the code infinitely hangs because the bit is never set.

In my keyboard_init() function:

Code: Select all

u8 bytein = 0, retries = 0;

    // Disable ps/2 port 1
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_CMD_PORT, 0xAD);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to respond to reset command");
    }
    printf("disabled: %x\n", bytein);
    retries = 0;

    // Disable ps/2 port 2 (superfluous but just in case)
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_CMD_PORT, 0xA7);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to respond to reset command");
    }
    retries = 0;
Adding a

Code: Select all

wait_for(verify_keyboard_ready_read());
before reading causes the code to hang.

Headers/macros

Code: Select all

#define KEYBOARD_CMD_PORT   0x64
#define KEYBOARD_DATA_PORT  0x60

#define BIT_GET(_v, _n) \
    __extension__({ \
        __typeof__(_v) __v = (_v); \
        ((__v >> (_n)) & 1); \
    })

bool verify_keyboard_ready_read() {
    return BIT_GET(inportb(KEYBOARD_CMD_PORT), 0) == 1;
}

bool verify_keyboard_ready_write() {
    return BIT_GET(inportb(KEYBOARD_CMD_PORT), 1) == 0;
}
Also, while my code works fine in qEMU, it completely fails in bochs, and the bochs keyboard buffer fills up without the IRQ launching.

Code: Select all

00013811542i[BIOS  ] Booting from 0000:7c00
00015325433i[KBD   ] keyboard: scan convert turned off
00015337854i[KBD   ] keyboard: scan convert turned off
00015337971i[KBD   ] Switched to scancode set 1
00015344951i[KBD   ] keyboard: scan convert turned off
00157628000i[KBD   ] internal keyboard buffer full, ignoring scancode 0x13
00161964000i[KBD   ] internal keyboard buffer full, ignoring scancode 0x93
My entire keyboard.c:

Code: Select all

//
// Created by Adithiya Venkatakrishnan on 14/12/2024.
//

#include "keyboard.h"

#include <display/display.h>
#include <exception/exception.h>
#include <modules/modules.h>
#include <idt/interrupt.h>
#include <timer/PIT.h>

#define wait_for(_cond) while (!(_cond)) { NOP(); }

const bool DEBUG = false;

static u8 keyboard_layout_us[2][128] = {
    {
        KEY_NULL,
        KEY_ESC, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', KEY_BS, KEY_TAB,
        'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', KEY_LF, 0,
        'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`', 0, '\\',
        'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/', 0, 0, 0, ' ', 0,
        KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5,
        KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, 0, 0, KEY_HOME, KEY_UP,
        KEY_PG_UP, '-', KEY_LEFT, '5', KEY_RIGHT, '+', KEY_END, KEY_DOWN,
        KEY_PG_DN, KEY_INSERT, KEY_DELETE, 0, 0, 0, KEY_F11, KEY_F12
    },
    {
        KEY_NULL,
        KEY_ESC, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', KEY_BS,
        KEY_TAB, 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', KEY_LF,
        0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '\"', '~', 0, '|', 'Z', 'X', 'C', 'V', 'B', 'N',
        'M', '<', '>', '?', 0, 0, 0, ' ', 0, KEY_F1, KEY_F2, KEY_F3, KEY_F4,
        KEY_F5, KEY_F6, KEY_F7, KEY_F8, KEY_F9, KEY_F10, 0, 0, KEY_HOME, KEY_UP,
        KEY_PG_UP, '-', KEY_LEFT, '5', KEY_RIGHT, '+', KEY_END, KEY_DOWN,
        KEY_PG_DN, KEY_INSERT, KEY_DELETE, 0, 0, 0, KEY_F11, KEY_F12
    }
};

bool verify_keyboard_ready_read() {
    return BIT_GET(inportb(KEYBOARD_CMD_PORT), 0) == 1;
}

bool verify_keyboard_ready_write() {
    return BIT_GET(inportb(KEYBOARD_CMD_PORT), 1) == 0;
}

bool verify_keyboard_response(u8 response) {
    if (response == 0xFE || response == 0x00 || response == 0xFF) {
        return false;
    }
    return true;
}

struct keyboard_state {
    bool caps_lock : 1;
    bool num_lock : 1;
    bool scroll_lock : 1;
    bool shift : 1;
    bool ctrl : 1;
    bool alt : 1;
    bool meta : 1;
    u8 current_key;
    u8 current_char;
    bool pressed : 4;
    bool tick : 4; // on every keypress, this will be toggled to indicate a new keypress
} state;

u8 wait_for_keypress() {
    bool tick = state.tick;
    while (state.tick == tick) { NOP(); }
    return state.current_char;
}

char* input(char* buffer, u32 size) {
    u32 i = 0;
    while (i < size) {
        buffer[i] = (char)wait_for_keypress();
        if (buffer[i] == KEY_LF) {
            buffer[i] = 0;
            break;
        }
        if (buffer[i] == KEY_BS) {
            if (i > 0) {
                printf("%c", buffer[i]);
                buffer[i] = 0;
                i--;
                buffer[i] = 0;
            }
            continue;
        }
        printf("%c", buffer[i]);
        i++;
    }
    printf("\n");
    return buffer;
}

void keyboard_handler(struct registers* regs) {
    // get the character from the keyboard
    // first 8 bits are the scancode, last 8 bits is the pressed/released bit
    wait_for(verify_keyboard_ready_read());
    u16 scancode = inportb(KEYBOARD_DATA_PORT);
    u8 hscancode = (u8)(scancode & 0xFF);
    u8 lscancode = scancode; // (u8)(scancode >> 8);

    // printf("Key was detected: %x", scancode);

    if (hscancode == KEYBOARD_EXTENDED) {
        // I don't wanna deal with this right now
        return;
    }

    if (KEY_PRESSED(lscancode)) {
        switch (KEY_SCANCODE(lscancode)) {
        case KEY_LCTRL:
            state.ctrl = true;
            if (DEBUG) printf("ctrl was pressed\n");
            break;
        case KEY_LSHIFT:
        case KEY_RSHIFT:
            state.shift = true;
            if (DEBUG) printf("shift was pressed\n");
            break;
        case KEY_LALT:
            state.alt = true;
            if (DEBUG) printf("alt was pressed\n");
            break;
        case KEY_CAPS_LOCK:
            state.caps_lock = !state.caps_lock;
            if (DEBUG) printf("caps lock was pressed\n");
            break;
        case KEY_SCROLL_LOCK:
            state.scroll_lock = !state.scroll_lock;
            if (DEBUG) printf("scroll lock was pressed\n");
            break;
        case KEY_NUM_LOCK:
            state.num_lock = !state.num_lock;
            if (DEBUG) printf("num lock was pressed\n");
            break;
        default:
            // get the character from the keyboard
            u8 character = keyboard_layout_us[
                state.caps_lock ^ state.shift ? 1 : 0
            ][KEY_SCANCODE(lscancode)];
            if (character != 0) {
                // print the character
                // printf("character %c was pressed\n", character);
                state.current_char = character;
                state.current_key = KEY_SCANCODE(lscancode);
                state.pressed = true;

                state.tick = !state.tick;
            }
        }
    }
    else {
        switch (KEY_SCANCODE(lscancode)) {
        case KEY_LCTRL:
            state.ctrl = false;
            if (DEBUG) printf("ctrl was released\n");
            break;
        case KEY_LSHIFT:
        case KEY_RSHIFT:
            state.shift = false;
            if (DEBUG) printf("shift was released\n");
            break;
        case KEY_LALT:
            state.alt = false;
            if (DEBUG) printf("alt was released\n");
            break;
        default:
            state.current_char = 0;
            state.current_key = 0;
            state.pressed = false;
        }

        // this doesn't matter as much unless we're making a game where its important to know if a key is held down
    }

    // printf("Scancode: %x, Pressed: \n", scancode);
}

u8 read_configuration_byte() {
    wait_for(verify_keyboard_ready_write());
    outportb(KEYBOARD_CMD_PORT, 0x20);
    wait_for(verify_keyboard_ready_read());
    u8 config = inportb(KEYBOARD_DATA_PORT);
    return config;
}

void write_configuration_byte(u8 config) {
    u8 bytein, retries = 0;
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_CMD_PORT, 0x60);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to set configuration byte");
    }
    retries = 0;
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_DATA_PORT, config);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to set configuration byte");
    }
}

void keyboard_init() {
    u8 bytein = 0, retries = 0;

    // Disable ps/2 port 1
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_CMD_PORT, 0xAD);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to respond to reset command");
    }
    printf("disabled: %x\n", bytein);
    retries = 0;

    // Disable ps/2 port 2 (superfluous but just in case)
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_CMD_PORT, 0xA7);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to respond to reset command");
    }
    retries = 0;

    // flush the output buffer
    while (verify_keyboard_ready_read()) {
        inportb(KEYBOARD_DATA_PORT);
    }

    // read configuration byte
    u8 config = read_configuration_byte();
    printf("keyboard configuration: 0x%x\n", config);

    // set specific bits
    config = BIT_SET(config, 0, 0); // disable keyboard irq
    config = BIT_SET(config, 6, 0); // disable keyboard translation

    // write configuration byte
    write_configuration_byte(config);
    printf("written configuration byte: 0x%x\n", read_configuration_byte());

    // perform controller self-test
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_CMD_PORT, 0xAA);
        wait_for(verify_keyboard_ready_read());
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed self-test");
    }
    if (bytein != 0x55) {
        panic("keyboard failed self-test");
    }

    printf("passed self-test\n");

    // restore controller configuration byte
    // write configuration byte
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_CMD_PORT, 0x60);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to set configuration byte");
    }
    retries = 0;
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_DATA_PORT, config);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to set configuration byte");
    }

    // enable ps/2 port 1
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_CMD_PORT, 0xAE);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to respond to reset command");
    }

    // set scan code set to 2
    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_DATA_PORT, 0xF0);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to respond to reset command");
    }
    retries = 0;

    do {
        wait_for(verify_keyboard_ready_write());
        outportb(KEYBOARD_DATA_PORT, 0x01);
        bytein = inportb(KEYBOARD_DATA_PORT);
    } while (bytein == 0xFE && retries++ < 3);
    if (!verify_keyboard_response(bytein)) {
        panic("keyboard failed to respond to reset command");
    }
    retries = 0;

    // read configuration byte
    config = read_configuration_byte();
    printf("keyboard configuration: 0x%x\n", config);

    // set specific bits
    config = BIT_SET(config, 0, 1); // enable keyboard irq

    // write configuration byte
    write_configuration_byte(config);
    printf("written configuration byte: 0x%x\n", read_configuration_byte());

    PIC_install(1, keyboard_handler);
}
My entire keyboard.h:

Code: Select all


//
// Created by Adithiya Venkatakrishnan on 14/12/2024.
//

#ifndef KEYBOARD_H
#define KEYBOARD_H

#include <modules/modules.h>

#define KEY_NULL    0x00
#define KEY_ESC     0x1B
#define KEY_BS      0x08
#define KEY_TAB     0x09
#define KEY_LF      0x0A
#define KEY_CR      0x0D

// these are wrong i'm pretty sure
#define KEY_INSERT  0x90
#define KEY_DELETE  0x91
#define KEY_HOME    0x92
#define KEY_END     0x93
#define KEY_PG_UP   0x94
#define KEY_PG_DN   0x95

//
#define KEY_UP      0x48
#define KEY_LEFT    0x4B
#define KEY_RIGHT   0x4D
#define KEY_DOWN    0x50

#define KEY_F1      0x80
#define KEY_F2      (KEY_F1 + 1)
#define KEY_F3      (KEY_F1 + 2)
#define KEY_F4      (KEY_F1 + 3)
#define KEY_F5      (KEY_F1 + 4)
#define KEY_F6      (KEY_F1 + 5)
#define KEY_F7      (KEY_F1 + 6)
#define KEY_F8      (KEY_F1 + 7)
#define KEY_F9      (KEY_F1 + 8)
#define KEY_F10     (KEY_F1 + 9)
#define KEY_F11     (KEY_F1 + 10)
#define KEY_F12     (KEY_F1 + 11)
#define KEY_F13     (KEY_F1 + 12)
#define KEY_F14     (KEY_F1 + 13)
#define KEY_F15     (KEY_F1 + 14)
#define KEY_F16     (KEY_F1 + 15)

#define KEY_LCTRL   0x1D
#define KEY_RCTRL   0x1D

#define KEY_LALT    0x38
#define KEY_RALT    0x38

#define KEY_LSHIFT  0x2A
#define KEY_RSHIFT  0x36

#define KEY_LMETA   0x5B

#define KEY_CAPS_LOCK   0x3A
#define KEY_SCROLL_LOCK 0x46
#define KEY_NUM_LOCK    0x45

#define KEY_MOD_ALT         0x0200
#define KEY_MOD_CTRL        0x0400
#define KEY_MOD_SHIFT       0x0800
#define KEY_MOD_CAPS_LOCK   0x1000
#define KEY_MOD_NUM_LOCK    0x2000
#define KEY_MOD_SCROLL_LOCK 0x4000

#define KEYBOARD_CMD_PORT   0x64
#define KEYBOARD_DATA_PORT  0x60
#define KEYBOARD_RELEASE    0x80
#define KEYBOARD_EXTENDED   0xE0

#define KEY_PRESSED(_s) (!((_s) & KEYBOARD_RELEASE))
#define KEY_RELEASED(_s) (!!((_s) & KEYBOARD_RELEASE))
#define KEY_SCANCODE(_s) ((_s) & 0x7F)

#define CHAR_PRINTABLE(_c) ((_c) >= 0x20 && (_c) <= 0x7E)
#define CHAR_NONPRINTABLE(_c) ((_c) < 0x20 || (_c) == 0x7F)
#define CHAR_SPECIAL(_c) ((_c) > 0x7F)

void    keyboard_init();

u8      wait_for_keypress();
char*  input(char* buffer, u32 size);

#endif //KEYBOARD_H
I'm extremely confused on why I'm getting this really weird behaviour. I am a beginner, so this code might not be the best code. I put it together to try and just test out what works and doesn't so I can put together a much better keyboard driver. Why does Bochs fail to run the IRQ, and why does waiting for the read buffer to fill not work?
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Questions about the behaviour of PS/2 Keyboard

Post by Octocontrabass »

atomtables wrote: Wed Dec 18, 2024 2:21 pmI've been trying my best to understand how a PS/2 (AT) keyboard works, but it's been confusing.
The keyboard and the PS/2 controller are two separate devices. I think part of the confusion is that you're writing a single driver to handle both of them when ideally you'd have two separate drivers.
atomtables wrote: Wed Dec 18, 2024 2:21 pm

Code: Select all

    config = BIT_SET(config, 6, 0); // disable keyboard translation
Unfortunately, disabling the PS/2 controller's scan code translation breaks the "Fn" key on some laptops. It also doesn't work correctly on computers that are emulating a PS/2 controller in SMM.
atomtables wrote: Wed Dec 18, 2024 2:21 pm

Code: Select all

        outportb(KEYBOARD_CMD_PORT, 0xAA);
You shouldn't use the PS/2 controller's self-test command. It can have side effects that you can't fix by setting the configuration byte. It also doesn't work correctly on computers that are emulating a PS/2 controller in SMM.
atomtables wrote: Wed Dec 18, 2024 2:21 pmWhy does Bochs fail to run the IRQ,
I would guess the interrupt controller is waiting for you to acknowledge an interrupt that got lost somewhere, but it's hard to tell without doing any debugging.
atomtables wrote: Wed Dec 18, 2024 2:21 pmand why does waiting for the read buffer to fill not work?
When you send commands to the keyboard, the keyboard responds. When you send commands to the PS/2 controller, the keyboard does not respond.
User avatar
GoingNuts
Posts: 3
Joined: Fri Jun 17, 2022 7:36 pm
Libera.chat IRC: GoingNuts

Re: Questions about the behaviour of PS/2 Keyboard

Post by GoingNuts »

So, Bochs is your emulator to test your own o/s ?
What is the host o/s ?
atomtables
Posts: 5
Joined: Fri Nov 08, 2024 5:08 pm
Libera.chat IRC: atomtables

Re: Questions about the behaviour of PS/2 Keyboard

Post by atomtables »

Thanks for the help!
Octocontrabass wrote: Wed Dec 18, 2024 11:10 pm When you send commands to the keyboard, the keyboard responds. When you send commands to the PS/2 controller, the keyboard does not respond.
This still confuses me because the wiki says
To send a command to the controller, write the command byte to IO port 0x64. If the command is 2 bytes long, then the next byte needs to be written to IO Port 0x60 after making sure that the controller is ready for it (by making sure bit 1 of the Status Register is clear). If there is a response byte, then the response byte needs to be read from IO Port 0x60 after making sure it has arrived (by making sure bit 0 of the Status Register is set).
And the wiki also includes a lot of information like self-testing should be done as a part of the initialisation process.

I am now writing two separate drivers for the controller and the port, but am unclear on the exact process to properly send a command and receive an acknowledgement back. Are there any other sources you would recommend?
GoingNuts wrote: Thu Dec 19, 2024 4:19 am So, Bochs is your emulator to test your own o/s ?
What is the host o/s ?
I am using MacOS.
atomtables
Posts: 5
Joined: Fri Nov 08, 2024 5:08 pm
Libera.chat IRC: atomtables

Re: Questions about the behaviour of PS/2 Keyboard

Post by atomtables »

Funnily enough, Google Search's Gemini gave me the answer to the Bochs problem. Clearing the input buffer and disabling the keyboard can be done with the command 0xF5 and the keyboard can be reenabled with 0xF4. I'm still unclear on the wiki's sayings however.
Octocontrabass
Member
Member
Posts: 5568
Joined: Mon Mar 25, 2013 7:01 pm

Re: Questions about the behaviour of PS/2 Keyboard

Post by Octocontrabass »

atomtables wrote: Thu Dec 19, 2024 8:51 amThis still confuses me because the wiki says
Both of those ports belong to the PS/2 controller. The PS/2 controller has complete control over what happens when you read or write either of those ports.
atomtables wrote: Thu Dec 19, 2024 8:51 amAnd the wiki also includes a lot of information like self-testing should be done as a part of the initialisation process.
I really do need to change that...
atomtables wrote: Thu Dec 19, 2024 8:51 amI am now writing two separate drivers for the controller and the port,
The PS/2 controller is the port? Or did you mean separate drivers for the PS/2 controller and the keyboard?
atomtables wrote: Thu Dec 19, 2024 8:51 ambut am unclear on the exact process to properly send a command and receive an acknowledgement back.
It depends on whether you're sending a command to the PS/2 controller or to the keyboard. The PS/2 controller doesn't acknowledge commands.
atomtables wrote: Thu Dec 19, 2024 12:17 pmFunnily enough, Google Search's Gemini gave me the answer to the Bochs problem. Clearing the input buffer and disabling the keyboard can be done with the command 0xF5 and the keyboard can be reenabled with 0xF4.
That's not really an answer. Sure, it's a workaround, but you didn't fix the original problem. The "AI" certainly can't help you with that - it doesn't understand anything.
Post Reply