Files
Espresso/drivers/ps2_keyboard.c

557 lines
9.9 KiB
C
Raw Normal View History

2025-05-28 14:41:02 -05:00
#include <stdio.h>
#include <port_io.h>
2025-06-13 19:53:54 -05:00
#include <tty.h>
2025-06-17 15:50:07 -05:00
#include <stdlib.h>
2025-10-20 21:57:30 -05:00
2025-05-28 14:41:02 -05:00
#include <drivers/ps2_keyboard.h>
/*
2025-06-13 18:03:39 -05:00
PS/2 Controller IO Ports
2025-05-28 14:41:02 -05:00
2025-06-13 18:03:39 -05:00
The PS/2 Controller itself uses 2 IO ports (IO ports 0x60 and 0x64). Like many IO ports, reads and writes may access different internal registers.
2025-05-28 14:41:02 -05:00
2025-06-13 18:03:39 -05:00
Historical note: The PC-XT PPI had used port 0x61 to reset the keyboard interrupt request signal (among other unrelated functions). Port 0x61 has no keyboard related functions on AT and PS/2 compatibles.
IO Port Access Type Purpose
0x60 Read/Write Data Port
0x64 Read Status Register
0x64 Write Command Register
2025-05-28 14:41:02 -05:00
*/
2025-06-13 19:53:54 -05:00
/*
Note:
The interrupt number for keyboard input in 1.
*/
#define PS2_DATA_PORT 0x60
#define PS2_STATUS_PORT 0x64
#define KEYBOARD_IRQ 1
2025-10-20 21:57:30 -05:00
#define RETURN_STI() do { _sti_asm(); return; } while (0)
2025-06-17 15:50:07 -05:00
bool ps2keyboard_initialized = false;
2025-06-13 19:53:54 -05:00
/* State for shift key */
static bool shift_pressed = false;
2025-06-17 15:50:07 -05:00
/* State for caps-lock key */
static bool capslock_pressed = false;
2025-07-03 20:30:21 -05:00
/* Used for arrow keys among others */
static bool extended = false;
static bool is_new_char = false;
2025-10-20 21:57:30 -05:00
static bool is_new_key = false;
static bool gets_called = false;
static volatile bool gets_finished = false;
2025-07-03 20:30:21 -05:00
volatile unsigned char current_char;
2025-10-20 21:57:30 -05:00
volatile char* current_string = NULL;
volatile char* gets_string = NULL;
2025-06-17 15:50:07 -05:00
volatile int32_t current_length = 0;
2025-10-20 21:57:30 -05:00
volatile int32_t gets_length = 0;
volatile int32_t capacity = 0;
volatile int32_t gets_capacity = 0;
2025-07-03 20:30:21 -05:00
volatile uint16_t current_key;
2025-06-17 15:50:07 -05:00
2025-10-20 21:57:30 -05:00
volatile ps2_hook_t* hooks = NULL;
volatile int hook_count = 0;
extern void _cli_asm(void);
extern void _sti_asm(void);
2025-06-17 15:50:07 -05:00
2025-06-13 19:53:54 -05:00
static const char scancode_map[128] = {
0, 27, '1','2','3','4','5','6','7','8','9','0','-','=','\b', /* Backspace */
2025-07-03 20:30:21 -05:00
'\t', /* Tab */
2025-06-13 19:53:54 -05:00
'q','w','e','r','t','y','u','i','o','p','[',']','\n', /* Enter */
0, /* Control */
'a','s','d','f','g','h','j','k','l',';','\'','`',
0, /* Left shift */
'\\','z','x','c','v','b','n','m',',','.','/',
0, /* Right shift */
'*',
0, /* Alt */
' ', /* Spacebar */
0, /* Caps lock */
/* The rest are function and control keys */
};
2025-05-28 14:41:02 -05:00
void keyboard_init(void)
{
2025-10-20 21:57:30 -05:00
#ifdef _DEBUG
printf("[ PS/2 KBD ] Initializing the PS/2 keyboard...\n");
#endif
2025-06-13 19:53:54 -05:00
outb(0x64, 0xAE); /* Enable keyboard interface (often optional, but here for safety) */
2025-10-20 21:57:30 -05:00
hooks = (ps2_hook_t*) malloc(sizeof(ps2_hook_t) * hook_count);
2025-06-17 15:50:07 -05:00
ps2keyboard_initialized = true;
2025-10-20 21:57:30 -05:00
#ifdef _DEBUG
printf("[ PS/2 KBD ] PS/2 Keyboard initialized\n");
#endif
}
bool setup_hook(ps2_hook_t func)
{
_cli_asm();
ps2_hook_t* copy = malloc(sizeof(ps2_hook_t) * hook_count);
if (hook_count > 0 && copy == NULL)
{
return false;
}
if (hook_count > 0)
{
memcpy(copy, hooks, sizeof(ps2_hook_t) * hook_count);
}
free((void*) hooks);
hooks = malloc(sizeof(ps2_hook_t) * (hook_count + 1));
if (hooks == NULL)
{
return false;
}
if (hook_count > 0)
{
memcpy((void*) hooks, copy, sizeof(ps2_hook_t) * hook_count);
}
free(copy);
hooks[hook_count] = func;
hook_count++;
_sti_asm();
return true;
}
bool remove_hook(ps2_hook_t func)
{
_cli_asm();
int index = -1;
for (int i = 0; i < hook_count; i++)
{
if (hooks[i] == func)
{
index = i;
break;
}
}
if (index == -1)
{
return false;
}
for (int i = index; i < hook_count - 1; i++)
{
hooks[i] = hooks[i + 1];
}
hook_count--;
if (hook_count == 0)
{
free((void*) hooks);
hooks = NULL;
return false;
}
ps2_hook_t* smaller = malloc(sizeof(ps2_hook_t) * hook_count);
if (smaller == NULL)
{
return false;
}
memcpy(smaller, (void*) hooks, sizeof(ps2_hook_t) * hook_count);
free((void*) hooks);
hooks = smaller;
_sti_asm();
return true;
}
void call_hooks(char c)
{
_cli_asm();
if (hook_count == 0)
{
return;
}
for (int i = 0; i < hook_count; i++)
{
hooks[i](c);
}
_sti_asm();
2025-06-17 15:50:07 -05:00
}
char get_char(void)
{
2025-07-03 20:30:21 -05:00
/*
ASCII code 5 (Enquiry) is/was used in legacy systems meant for requesting a response from a remote terminal or device.
2025-10-20 21:57:30 -05:00
For example, one system might send ENQ (ASCII 5), and the receiver could reply with ACK (ASCII 6) to indicate it's ready or still online.
2025-07-03 20:30:21 -05:00
In modern computing, ENQ is largely obsolete:
- Terminal emulators, shells, operating systems, and network protocols generally do not use ENQ.
- It's not used in text files, programming, or standard communications.
- It may still appear in legacy systems, embedded devices, or proprietary serial protocols, but that's niche.
*/
2025-10-20 21:57:30 -05:00
if (!is_new_char)
2025-07-03 20:30:21 -05:00
{
2025-10-20 21:57:30 -05:00
return 5;
2025-07-03 20:30:21 -05:00
}
2025-10-20 21:57:30 -05:00
2025-07-03 20:30:21 -05:00
is_new_char = false;
2025-10-20 21:57:30 -05:00
return current_char;
2025-07-03 20:30:21 -05:00
}
uint16_t get_key(void)
{
uint16_t temp = 0xFFFA;
if (is_new_key)
{
temp = current_key;
is_new_key = false;
}
else if (is_new_char)
{
temp = (uint16_t)current_char;
is_new_char = false;
}
return temp;
2025-06-17 15:50:07 -05:00
}
char* get_string(void)
{
return current_string;
}
2025-10-20 21:57:30 -05:00
char* kbd_gets(void)
{
gets_called = true;
gets_finished = false;
while (gets_finished != true)
{
sleep(1);
}
char* result = strdup(gets_string); /* Could be NULL, that is checked below */
free(gets_string);
gets_string = NULL;
gets_capacity = 0;
gets_length = 0;
gets_called = false;
return result == NULL ? "" : result;
}
void gets_append_char(unsigned char c)
{
_cli_asm();
if (!gets_string && gets_capacity > 0)
{
printf("[keyboard] ERROR: gets_string is NULL but capacity > 0!\n");
RETURN_STI();
}
if (c == KEY_ARROW_UP || c == KEY_ARROW_DOWN || c == KEY_ARROW_RIGHT || c == KEY_ARROW_LEFT)
{
gets_string[1] = '\0';
char tc = '\0';
switch (c)
{
case KEY_ARROW_UP:
tc = KEY_UP;
break;
case KEY_ARROW_DOWN:
tc = KEY_DOWN;
break;
case KEY_ARROW_RIGHT:
tc = KEY_RIGHT;
break;
case KEY_ARROW_LEFT:
tc = KEY_LEFT;
break;
default:
tc = '\0';
break;
}
gets_string[0] = tc;
gets_finished = true;
RETURN_STI();
}
else if (c == '\n')
{
/* The returned string must/will not have newlines in it, so we return here after setting gets_finished to 'true'. */
gets_finished = true;
printf("\n");
RETURN_STI();
}
else if (c == 27) /* ASCII escape, here it's used to cancel input. */
{
gets_finished = true;
gets_string[0] = '\0';
RETURN_STI();
}
else if (c == '\b')
{
if (gets_length < 1)
{
RETURN_STI();
}
gets_length--;
gets_string[gets_length] = '\0';
printf("\b \b");
RETURN_STI();
}
if ((gets_length) >= gets_capacity)
{
int new_capacity = (gets_capacity == 0) ? 64 : gets_capacity * 2;
char* new_str = (char*) malloc(new_capacity);
if (!new_str)
{
RETURN_STI();
}
if (gets_string)
{
for (int i = 0; i < gets_length; i++)
{
new_str[i] = gets_string[i];
}
new_str[gets_length] = '\0';
free(gets_string);
}
gets_string = new_str;
gets_capacity = new_capacity;
}
if (!gets_string)
{
RETURN_STI();
}
gets_string[gets_length] = (char) c;
gets_length++;
gets_string[gets_length] = '\0';
printf("%c", c);
RETURN_STI();
}
void append_char(char c)
2025-06-17 15:50:07 -05:00
{
2025-10-20 21:57:30 -05:00
_cli_asm();
2025-06-17 15:50:07 -05:00
if (current_length + 1 >= capacity)
{
int new_capacity = (capacity == 0) ? 16 : capacity * 2;
2025-10-20 21:57:30 -05:00
char* new_str = (char*) malloc(new_capacity);
if (!new_str)
{
printf("[keyboard] ERROR: malloc failed for new input buffer\n");
2025-06-17 15:50:07 -05:00
return;
}
2025-10-20 21:57:30 -05:00
2025-06-17 15:50:07 -05:00
if (current_string)
{
memcpy(new_str, current_string, current_length);
free(current_string);
}
current_string = new_str;
capacity = new_capacity;
}
2025-10-20 21:57:30 -05:00
if (!current_string)
{
return;
}
2025-06-17 15:50:07 -05:00
current_string[current_length] = c;
current_length++;
2025-10-20 21:57:30 -05:00
current_string[current_length] = '\0';
_sti_asm();
2025-06-17 15:50:07 -05:00
}
2025-10-20 21:57:30 -05:00
void free_current_string(void)
2025-06-17 15:50:07 -05:00
{
if (current_string)
{
free(current_string);
current_string = NULL;
current_length = 0;
capacity = 0;
}
2025-06-13 19:53:54 -05:00
}
2025-07-03 20:30:21 -05:00
void keyboard_handler(void)
{
2025-06-13 19:53:54 -05:00
uint8_t scancode = inb(PS2_DATA_PORT);
/* Handle shift press/release */
if (scancode == 0x2A || scancode == 0x36)
{
shift_pressed = true;
return;
}
else if (scancode == 0xAA || scancode == 0xB6)
{
shift_pressed = false;
return;
}
2025-06-17 15:50:07 -05:00
else if (scancode == 0x3A)
{
capslock_pressed = !capslock_pressed;
return;
}
2025-07-03 20:30:21 -05:00
else if (scancode == 0xE0)
{
extended = true;
return;
}
2025-06-17 15:50:07 -05:00
2025-06-13 19:53:54 -05:00
if (scancode & 0x80)
{
2025-07-03 20:30:21 -05:00
extended = false;
}
else
2025-06-13 19:53:54 -05:00
{
2025-10-20 21:57:30 -05:00
uint16_t c = (uint16_t) scancode_map[scancode];
if ((shift_pressed ^ capslock_pressed) && ((char) c >= 'a') && ((char) c <= 'z'))
2025-07-03 20:30:21 -05:00
{
2025-06-13 19:53:54 -05:00
c -= 32; /* Convert to uppercase */
}
else if (shift_pressed)
{
2025-07-03 20:30:21 -05:00
c = (uint16_t) terminal_get_shifted((unsigned char) c);
2025-06-13 19:53:54 -05:00
}
2025-06-27 14:48:06 -05:00
2025-10-20 21:57:30 -05:00
if (hook_count > 0)
{
call_hooks(c);
}
2025-06-13 19:53:54 -05:00
2025-07-03 20:30:21 -05:00
if (extended)
{
switch (scancode)
{
case 0x48:
c = KEY_ARROW_UP;
break;
case 0x50:
c = KEY_ARROW_DOWN;
break;
case 0x4B:
c = KEY_ARROW_LEFT;
break;
case 0x4D:
c = KEY_ARROW_RIGHT;
break;
default:
c = KEY_NONE;
break;
}
2025-10-20 21:57:30 -05:00
if (gets_called)
{
gets_append_char(c);
return;
}
2025-07-03 20:30:21 -05:00
current_key = c;
is_new_key = true;
extended = false;
return;
}
2025-10-20 21:57:30 -05:00
if (gets_called)
{
gets_append_char(c);
return;
}
2025-07-03 20:30:21 -05:00
2025-06-13 19:53:54 -05:00
if (c)
{
2025-10-20 21:57:30 -05:00
if (c != '\n')
{
append_char(c);
}
2025-06-17 15:50:07 -05:00
current_char = c;
2025-07-03 20:30:21 -05:00
is_new_char = true;
2025-10-20 21:57:30 -05:00
2025-06-17 15:50:07 -05:00
if (c == '\n')
{
2025-10-20 21:57:30 -05:00
/*append_char(c);
current_char = c;
is_new_char = true;*/
2025-06-17 15:50:07 -05:00
free_current_string();
}
2025-06-13 19:53:54 -05:00
}
2025-10-20 21:57:30 -05:00
2025-06-13 19:53:54 -05:00
}
2025-05-28 14:41:02 -05:00
}
2025-06-27 14:48:06 -05:00
2025-10-20 21:57:30 -05:00