From a1cfbe32bfc5cc28a16741e3adff4b1756422d95 Mon Sep 17 00:00:00 2001 From: iretq Date: Mon, 27 Apr 2026 00:09:53 -0300 Subject: [PATCH] input: add new evdev subsystem Co-authored-by: Matheus --- kernel-src/include/kernel/console.h | 2 + kernel-src/include/kernel/devfs.h | 1 + kernel-src/include/kernel/input.h | 231 ++++++++++++ kernel-src/include/kernel/interrupt.h | 1 + kernel-src/io/console.c | 192 ++++++++-- kernel-src/io/input.c | 498 ++++++++++++++++++++++++++ 6 files changed, 894 insertions(+), 31 deletions(-) create mode 100644 kernel-src/include/kernel/input.h create mode 100644 kernel-src/io/input.c diff --git a/kernel-src/include/kernel/console.h b/kernel-src/include/kernel/console.h index 276b2c6a..ddf43d6e 100644 --- a/kernel-src/include/kernel/console.h +++ b/kernel-src/include/kernel/console.h @@ -2,9 +2,11 @@ #define _CONSOLE_H #include +#include void console_init(); void console_putc(char c); size_t console_write(char *str, size_t size); +void console_process_events(input_event_t *events, int count); #endif diff --git a/kernel-src/include/kernel/devfs.h b/kernel-src/include/kernel/devfs.h index 23cab493..125ca6e4 100644 --- a/kernel-src/include/kernel/devfs.h +++ b/kernel-src/include/kernel/devfs.h @@ -21,6 +21,7 @@ #define DEV_MAJOR_PTY 12 #define DEV_MAJOR_ACPI 13 #define DEV_MAJOR_PROF 14 +#define DEV_MAJOR_INPUT 15 typedef struct { int (*open)(int minor, vnode_t **vnode, int flags); diff --git a/kernel-src/include/kernel/input.h b/kernel-src/include/kernel/input.h new file mode 100644 index 00000000..4cf69cde --- /dev/null +++ b/kernel-src/include/kernel/input.h @@ -0,0 +1,231 @@ +#ifndef _INPUT_H +#define _INPUT_H + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct { + timeval_t time; + uint16_t type; + uint16_t code; + int32_t value; +} input_event_t; + +typedef struct { + int32_t value; + int32_t min; + int32_t max; + int32_t fuzz; + int32_t flat; + int32_t res; +} input_absinfo_t; + +#define INPUT_DEVICE_BUS_PCI 0x1 +#define INPUT_DEVICE_BUS_USB 0x3 +#define INPUT_DEVICE_BUS_VIRTUAL 0x6 +#define INPUT_DEVICE_BUS_I8042 0x11 + +#define INPUT_PROP_POINTER 0x0 +#define INPUT_PROP_DIRECT 0x1 +#define INPUT_PROP_MAX 0x1f +#define INPUT_PROP_CNT (INPUT_PROP_MAX + 1) + +#define INPUT_EV_SYN 0x0 +#define INPUT_EV_KEY 0x1 +#define INPUT_EV_REL 0x2 +#define INPUT_EV_ABS 0x3 +#define INPUT_EV_MSC 0x4 +#define INPUT_EV_MAX 0x1f +#define INPUT_EV_CNT (INPUT_EV_MAX + 1) + +#define INPUT_SYN_REPORT 0x0 +#define INPUT_SYN_DROPPED 0x3 +#define INPUT_SYN_MAX 0xf +#define INPUT_SYN_CNT (INPUT_SYN_MAX + 1) + +#define INPUT_KEY_RESERVED 0x0 +#define INPUT_KEY_ESC 0x1 +#define INPUT_KEY_1 0x2 +#define INPUT_KEY_2 0x3 +#define INPUT_KEY_3 0x4 +#define INPUT_KEY_4 0x5 +#define INPUT_KEY_5 0x6 +#define INPUT_KEY_6 0x7 +#define INPUT_KEY_7 0x8 +#define INPUT_KEY_8 0x9 +#define INPUT_KEY_9 0xa +#define INPUT_KEY_0 0xb +#define INPUT_KEY_MINUS 0xc +#define INPUT_KEY_EQUAL 0xd +#define INPUT_KEY_BACKSPACE 0xe +#define INPUT_KEY_TAB 0xf +#define INPUT_KEY_Q 0x10 +#define INPUT_KEY_W 0x11 +#define INPUT_KEY_E 0x12 +#define INPUT_KEY_R 0x13 +#define INPUT_KEY_T 0x14 +#define INPUT_KEY_Y 0x15 +#define INPUT_KEY_U 0x16 +#define INPUT_KEY_I 0x17 +#define INPUT_KEY_O 0x18 +#define INPUT_KEY_P 0x19 +#define INPUT_KEY_LEFTBRACE 0x1a +#define INPUT_KEY_RIGHTBRACE 0x1b +#define INPUT_KEY_ENTER 0x1c +#define INPUT_KEY_LEFTCTRL 0x1d +#define INPUT_KEY_A 0x1e +#define INPUT_KEY_S 0x1f +#define INPUT_KEY_D 0x20 +#define INPUT_KEY_F 0x21 +#define INPUT_KEY_G 0x22 +#define INPUT_KEY_H 0x23 +#define INPUT_KEY_J 0x24 +#define INPUT_KEY_K 0x25 +#define INPUT_KEY_L 0x26 +#define INPUT_KEY_SEMICOLON 0x27 +#define INPUT_KEY_APOSTROPHE 0x28 +#define INPUT_KEY_GRAVE 0x29 +#define INPUT_KEY_LEFTSHIFT 0x2a +#define INPUT_KEY_BACKSLASH 0x2b +#define INPUT_KEY_Z 0x2c +#define INPUT_KEY_X 0x2d +#define INPUT_KEY_C 0x2e +#define INPUT_KEY_V 0x2f +#define INPUT_KEY_B 0x30 +#define INPUT_KEY_N 0x31 +#define INPUT_KEY_M 0x32 +#define INPUT_KEY_COMMA 0x33 +#define INPUT_KEY_DOT 0x34 +#define INPUT_KEY_SLASH 0x35 +#define INPUT_KEY_RIGHTSHIFT 0x36 +#define INPUT_KEY_KPASTERISK 0x37 +#define INPUT_KEY_LEFTALT 0x38 +#define INPUT_KEY_SPACE 0x39 +#define INPUT_KEY_CAPSLOCK 0x3a +#define INPUT_KEY_F1 0x3b +#define INPUT_KEY_F2 0x3c +#define INPUT_KEY_F3 0x3d +#define INPUT_KEY_F4 0x3e +#define INPUT_KEY_F5 0x3f +#define INPUT_KEY_F6 0x40 +#define INPUT_KEY_F7 0x41 +#define INPUT_KEY_F8 0x42 +#define INPUT_KEY_F9 0x43 +#define INPUT_KEY_F10 0x44 +#define INPUT_KEY_NUMLOCK 0x45 +#define INPUT_KEY_SCROLLLOCK 0x46 +#define INPUT_KEY_KP7 0x47 +#define INPUT_KEY_KP8 0x48 +#define INPUT_KEY_KP9 0x49 +#define INPUT_KEY_KPMINUS 0x4a +#define INPUT_KEY_KP4 0x4b +#define INPUT_KEY_KP5 0x4c +#define INPUT_KEY_KP6 0x4d +#define INPUT_KEY_KPPLUS 0x4e +#define INPUT_KEY_KP1 0x4f +#define INPUT_KEY_KP2 0x50 +#define INPUT_KEY_KP3 0x51 +#define INPUT_KEY_KP0 0x52 +#define INPUT_KEY_KPDOT 0x53 +#define INPUT_KEY_F11 0x57 +#define INPUT_KEY_F12 0x58 +#define INPUT_KEY_KPENTER 0x60 +#define INPUT_KEY_RIGHTCTRL 0x61 +#define INPUT_KEY_KPSLASH 0x62 +#define INPUT_KEY_RIGHTALT 0x64 +#define INPUT_KEY_HOME 0x66 +#define INPUT_KEY_UP 0x67 +#define INPUT_KEY_PAGEUP 0x68 +#define INPUT_KEY_LEFT 0x69 +#define INPUT_KEY_RIGHT 0x6a +#define INPUT_KEY_END 0x6b +#define INPUT_KEY_DOWN 0x6c +#define INPUT_KEY_PAGEDOWN 0x6d +#define INPUT_KEY_INSERT 0x6e +#define INPUT_KEY_DELETE 0x6f +#define INPUT_KEY_LEFTMETA 0x7d +#define INPUT_KEY_RIGHTMETA 0x7e +#define INPUT_KEY_F13 0xb7 +#define INPUT_KEY_F14 0xb8 +#define INPUT_KEY_F15 0xb9 +#define INPUT_KEY_F16 0xba +#define INPUT_KEY_F17 0xbb +#define INPUT_KEY_F18 0xbc +#define INPUT_KEY_F19 0xbd +#define INPUT_KEY_F20 0xbe +#define INPUT_KEY_F21 0xbf +#define INPUT_KEY_F22 0xc0 +#define INPUT_KEY_F23 0xc1 +#define INPUT_KEY_F24 0xc2 +#define INPUT_KEY_BTN_LEFT 0x110 +#define INPUT_KEY_BTN_RIGHT 0x111 +#define INPUT_KEY_BTN_MIDDLE 0x112 +#define INPUT_KEY_BTN_SIDE 0x113 +#define INPUT_KEY_BTN_EXTRA 0x114 +#define INPUT_KEY_MAX 0x2ff +#define INPUT_KEY_CNT (INPUT_KEY_MAX + 1) + +#define INPUT_REL_X 0x0 +#define INPUT_REL_Y 0x1 +#define INPUT_REL_WHEEL 0x8 +#define INPUT_REL_MAX 0xf +#define INPUT_REL_CNT (INPUT_REL_MAX + 1) + +#define INPUT_ABS_X 0x0 +#define INPUT_ABS_Y 0x1 +#define INPUT_ABS_MAX 0x3f +#define INPUT_ABS_CNT (INPUT_ABS_MAX + 1) + +typedef struct input_device input_device_t; + +typedef struct { + list_node_t node; + + spinlock_t lock; + pollheader_t poll_header; + mutex_t mutex; // for userspace to not race + ringbuffer_t buffer; + + input_device_t *device; + vnode_t *vnode; +} input_listener_t; + +struct input_device { + spinlock_t lock; + list_t listeners; + + int clock_id; + + char name[128]; + char phys[64]; + char uniq[64]; + + bitmap_t prop_bits; + bitmap_t ev_bits; + bitmap_t syn_bits; + bitmap_t key_bits; + bitmap_t rel_bits; + bitmap_t abs_bits; + + uint16_t id_bus; + uint16_t id_vendor; + uint16_t id_product; + uint16_t id_version; + + uint16_t ver_major; + uint8_t ver_minor; + uint8_t ver_patch; +}; + +input_device_t *input_new(); + +// Posts the given input events to the device's event queue followed by a SYN_REPORT event. +void input_queue_packet(input_device_t *dev, input_event_t *events, int count); + +#endif diff --git a/kernel-src/include/kernel/interrupt.h b/kernel-src/include/kernel/interrupt.h index d6de8ec9..6762627b 100644 --- a/kernel-src/include/kernel/interrupt.h +++ b/kernel-src/include/kernel/interrupt.h @@ -18,6 +18,7 @@ #define IPL_NET 400 #define IPL_DISK 500 #define IPL_SERIAL 550 +#define IPL_INPUT 600 #define IPL_KEYBOARD 600 #define IPL_MOUSE 650 #define IPL_ACPI 700 diff --git a/kernel-src/io/console.c b/kernel-src/io/console.c index b9e27d79..bc1eda7a 100644 --- a/kernel-src/io/console.c +++ b/kernel-src/io/console.c @@ -5,16 +5,31 @@ #include #include #include -#include #include #include #include #include +static spinlock_t input_buffer_lock = SPINLOCK_INIT_VALUE; +static ringbuffer_t input_buffer; +static semaphore_t input_sem; + static mutex_t writemutex; static thread_t *thread; static tty_t *tty; +static size_t console_ttywrite(void *internal, char *str, size_t count) { + return console_write(str, count); +} + +void console_putc(char c) { + if (c == '\n') { + console_write("\r\n", 2); + } else { + console_write(&c, 1); + } +} + size_t console_write(char *str, size_t count) { MUTEX_ACQUIRE(&writemutex); @@ -24,15 +39,17 @@ size_t console_write(char *str, size_t count) { return count; } -size_t console_ttywrite(void *internal, char *str, size_t count) { - return console_write(str, count); -} +void console_process_events(input_event_t *events, int count) { + long ipl = spinlock_acquire_raise_ipl(&input_buffer_lock, IPL_INPUT); + for (int i = 0; i < count; ++i) { + if (events[i].type != INPUT_EV_KEY || events[i].code >= INPUT_KEY_BTN_LEFT) + continue; -void console_putc(char c) { - if (c == '\n') - console_write("\n\r", 2); - else - console_write(&c, 1); + size_t written = ringbuffer_write(&input_buffer, &events[i], sizeof(input_event_t)); + if (written == sizeof(input_event_t)) + semaphore_signal(&input_sem); + } + spinlock_release_lower_ipl(&input_buffer_lock, ipl); } // taken from https://en.wikipedia.org/wiki/ANSI_escape_code#Terminal_input_sequences @@ -49,55 +66,165 @@ void console_putc(char c) { #define RIGHT_STR "\e[C" #define LEFT_STR "\e[D" +static const char key_to_ascii_normal[] = { + [INPUT_KEY_ESC] = '\e', [INPUT_KEY_1] = '1', + [INPUT_KEY_2] = '2', [INPUT_KEY_3] = '3', + [INPUT_KEY_4] = '4', [INPUT_KEY_5] = '5', + [INPUT_KEY_6] = '6', [INPUT_KEY_7] = '7', + [INPUT_KEY_8] = '8', [INPUT_KEY_9] = '9', + [INPUT_KEY_0] = '0', [INPUT_KEY_MINUS] = '-', + [INPUT_KEY_EQUAL] = '=', [INPUT_KEY_BACKSPACE] = '\b', + [INPUT_KEY_TAB] = '\t', [INPUT_KEY_Q] = 'q', + [INPUT_KEY_W] = 'w', [INPUT_KEY_E] = 'e', + [INPUT_KEY_R] = 'r', [INPUT_KEY_T] = 't', + [INPUT_KEY_Y] = 'y', [INPUT_KEY_U] = 'u', + [INPUT_KEY_I] = 'i', [INPUT_KEY_O] = 'o', + [INPUT_KEY_P] = 'p', [INPUT_KEY_LEFTBRACE] = '[', + [INPUT_KEY_RIGHTBRACE] = ']', [INPUT_KEY_ENTER] = '\r', + [INPUT_KEY_A] = 'a', [INPUT_KEY_S] = 's', + [INPUT_KEY_D] = 'd', [INPUT_KEY_F] = 'f', + [INPUT_KEY_G] = 'g', [INPUT_KEY_H] = 'h', + [INPUT_KEY_J] = 'j', [INPUT_KEY_K] = 'k', + [INPUT_KEY_L] = 'l', [INPUT_KEY_SEMICOLON] = ';', + [INPUT_KEY_APOSTROPHE] = '\'', [INPUT_KEY_GRAVE] = '`', + [INPUT_KEY_BACKSLASH] = '\\', [INPUT_KEY_Z] = 'z', + [INPUT_KEY_X] = 'x', [INPUT_KEY_C] = 'c', + [INPUT_KEY_V] = 'v', [INPUT_KEY_B] = 'b', + [INPUT_KEY_N] = 'n', [INPUT_KEY_M] = 'm', + [INPUT_KEY_COMMA] = ',', [INPUT_KEY_DOT] = '.', + [INPUT_KEY_SLASH] = '/', [INPUT_KEY_KPASTERISK] = '*', + [INPUT_KEY_SPACE] = ' ', [INPUT_KEY_KP7] = '7', + [INPUT_KEY_KP8] = '8', [INPUT_KEY_KP9] = '9', + [INPUT_KEY_KPMINUS] = '-', [INPUT_KEY_KP4] = '4', + [INPUT_KEY_KP5] = '5', [INPUT_KEY_KP6] = '6', + [INPUT_KEY_KPPLUS] = '+', [INPUT_KEY_KP1] = '1', + [INPUT_KEY_KP2] = '2', [INPUT_KEY_KP3] = '3', + [INPUT_KEY_KP0] = '0', [INPUT_KEY_KPDOT] = '.', + [INPUT_KEY_KPENTER] = '\r', [INPUT_KEY_KPSLASH] = '/', +}; + +static const char key_to_ascii_shift[] = { + [INPUT_KEY_ESC] = '\e', [INPUT_KEY_1] = '!', + [INPUT_KEY_2] = '@', [INPUT_KEY_3] = '#', + [INPUT_KEY_4] = '$', [INPUT_KEY_5] = '%', + [INPUT_KEY_6] = '^', [INPUT_KEY_7] = '&', + [INPUT_KEY_8] = '*', [INPUT_KEY_9] = '(', + [INPUT_KEY_0] = ')', [INPUT_KEY_MINUS] = '_', + [INPUT_KEY_EQUAL] = '+', [INPUT_KEY_BACKSPACE] = '\b', + [INPUT_KEY_TAB] = '\t', [INPUT_KEY_Q] = 'Q', + [INPUT_KEY_W] = 'W', [INPUT_KEY_E] = 'E', + [INPUT_KEY_R] = 'R', [INPUT_KEY_T] = 'T', + [INPUT_KEY_Y] = 'Y', [INPUT_KEY_U] = 'U', + [INPUT_KEY_I] = 'I', [INPUT_KEY_O] = 'O', + [INPUT_KEY_P] = 'P', [INPUT_KEY_LEFTBRACE] = '{', + [INPUT_KEY_RIGHTBRACE] = '}', [INPUT_KEY_ENTER] = '\r', + [INPUT_KEY_A] = 'A', [INPUT_KEY_S] = 'S', + [INPUT_KEY_D] = 'D', [INPUT_KEY_F] = 'F', + [INPUT_KEY_G] = 'G', [INPUT_KEY_H] = 'H', + [INPUT_KEY_J] = 'J', [INPUT_KEY_K] = 'K', + [INPUT_KEY_L] = 'L', [INPUT_KEY_SEMICOLON] = ':', + [INPUT_KEY_APOSTROPHE] = '"', [INPUT_KEY_GRAVE] = '~', + [INPUT_KEY_BACKSLASH] = '|', [INPUT_KEY_Z] = 'Z', + [INPUT_KEY_X] = 'X', [INPUT_KEY_C] = 'C', + [INPUT_KEY_V] = 'V', [INPUT_KEY_B] = 'B', + [INPUT_KEY_N] = 'N', [INPUT_KEY_M] = 'M', + [INPUT_KEY_COMMA] = '<', [INPUT_KEY_DOT] = '>', + [INPUT_KEY_SLASH] = '?', [INPUT_KEY_KPASTERISK] = '*', + [INPUT_KEY_SPACE] = ' ', [INPUT_KEY_KP7] = '7', + [INPUT_KEY_KP8] = '8', [INPUT_KEY_KP9] = '9', + [INPUT_KEY_KPMINUS] = '-', [INPUT_KEY_KP4] = '4', + [INPUT_KEY_KP5] = '5', [INPUT_KEY_KP6] = '6', + [INPUT_KEY_KPPLUS] = '+', [INPUT_KEY_KP1] = '1', + [INPUT_KEY_KP2] = '2', [INPUT_KEY_KP3] = '3', + [INPUT_KEY_KP0] = '0', [INPUT_KEY_KPDOT] = '.', + [INPUT_KEY_KPENTER] = '\r', [INPUT_KEY_KPSLASH] = '/', +}; + static void consolethread() { + bool left_ctrl = false; + bool right_ctrl = false; + bool left_shift = false; + bool right_shift = false; + for (;;) { - kbpacket_t packet; - keyboard_wait(keyboard_console, &packet); - if (packet.flags & KBPACKET_FLAGS_RELEASED) + semaphore_wait(&input_sem, false); + __assert(RINGBUFFER_DATACOUNT(&input_buffer) >= sizeof(input_event_t)); + + input_event_t ev; + __assert(ringbuffer_read(&input_buffer, &ev, sizeof(input_event_t)) == sizeof(input_event_t)); + + if (ev.code == INPUT_KEY_LEFTCTRL) { + left_ctrl = ev.value != 0; + continue; + } else if (ev.code == INPUT_KEY_RIGHTCTRL) { + right_ctrl = ev.value != 0; + continue; + } else if (ev.code == INPUT_KEY_LEFTSHIFT) { + left_shift = ev.value != 0; + continue; + } else if (ev.code == INPUT_KEY_RIGHTSHIFT) { + right_shift = ev.value != 0; + continue; + } + + bool ctrl = left_ctrl || right_ctrl; + bool shift = left_shift || right_shift; + + // we only care about key presses, not releases + if (ev.value == 0) continue; - if (packet.ascii) { - // control characters - if (packet.flags & (KBPACKET_FLAGS_LEFTCTRL | KBPACKET_FLAGS_RIGHTCTRL)) { - if (!((packet.ascii >= 'a' && packet.ascii <= 'z') || (packet.ascii >= 'A' && packet.ascii <= '\\'))) + char ascii = 0; + if (shift) { + if (ev.code < sizeof(key_to_ascii_shift)) + ascii = key_to_ascii_shift[ev.code]; + } else { + if (ev.code < sizeof(key_to_ascii_normal)) + ascii = key_to_ascii_normal[ev.code]; + } + + if (ascii != 0) { + if (ctrl) { + // control characters + if (!((ascii >= 'a' && ascii <= 'z') || (ascii >= 'A' && ascii <= '\\'))) continue; - packet.ascii = packet.ascii >= 'a' ? packet.ascii - 0x60 : packet.ascii - 0x40; + ascii = ascii >= 'a' ? ascii - 0x60 : ascii - 0x40; } - tty_process(tty, packet.ascii); + tty_process(tty, ascii); } else { // no ascii for key but we can send in a escape sequence for it char *target = NULL; - switch (packet.keycode) { - case KEYCODE_HOME: + switch (ev.code) { + case INPUT_KEY_HOME: target = HOME_STR; break; - case KEYCODE_INSERT: + case INPUT_KEY_INSERT: target = INSERT_STR; break; - case KEYCODE_DELETE: + case INPUT_KEY_DELETE: target = DELETE_STR; break; - case KEYCODE_END: + case INPUT_KEY_END: target = END_STR; break; - case KEYCODE_PAGEUP: + case INPUT_KEY_PAGEUP: target = PGUP_STR; break; - case KEYCODE_PAGEDOWN: + case INPUT_KEY_PAGEDOWN: target = PGDN_STR; break; - case KEYCODE_UP: + case INPUT_KEY_UP: target = UP_STR; break; - case KEYCODE_DOWN: + case INPUT_KEY_DOWN: target = DOWN_STR; break; - case KEYCODE_RIGHT: + case INPUT_KEY_RIGHT: target = RIGHT_STR; break; - case KEYCODE_LEFT: + case INPUT_KEY_LEFT: target = LEFT_STR; break; default: @@ -112,6 +239,9 @@ static void consolethread() { } void console_init() { + SEMAPHORE_INIT(&input_sem, 0); + ringbuffer_init(&input_buffer, 128 * sizeof(input_event_t)); + MUTEX_INIT(&writemutex); thread = sched_newthread(consolethread, PAGE_SIZE * 16, 0, NULL, NULL); @@ -127,8 +257,8 @@ void console_init() { tty->winsize.ws_row = y; tty->winsize.ws_xpixel = fbx; tty->winsize.ws_ypixel = fby; - + logging_sethook(console_putc); } -INIT_ROUTINE_DEFINE(console, INIT_ROUTINE_FLAGS_NONE, console_init, scheduler, keyboard); +INIT_ROUTINE_DEFINE(console, INIT_ROUTINE_FLAGS_NONE, console_init, scheduler); diff --git a/kernel-src/io/input.c b/kernel-src/io/input.c new file mode 100644 index 00000000..4cb1f3e0 --- /dev/null +++ b/kernel-src/io/input.c @@ -0,0 +1,498 @@ +#include +#include +#include +#include +#include +#include +#include + +#define MAX_DEVICES 256 + +#define INPUT_DEV_BUFFER_SIZE (128 * sizeof(input_event_t)) + +static int input_dev_count = 0; + +BITMAP_DEFINE_STATIC(listener_minor_bitmap, MAX_DEVICES); +static MUTEX_DEFINE(minor_bitmap_mutex); +static MUTEX_DEFINE(input_dev_table_lock); +static hashtable_t input_dev_table; +static hashtable_t input_listener_table; + +static devops_t input_dev_ops; +static devops_t input_listener_ops; + +static void *get(int minor) { + hashtable_t *table; + if (minor >= MAX_DEVICES) { + table = &input_listener_table; + minor -= MAX_DEVICES; + } else { + if (minor >= input_dev_count) + return NULL; + table = &input_dev_table; + } + + void *result; + + MUTEX_ACQUIRE(&input_dev_table_lock); + int err = hashtable_get(table, &result, &minor, sizeof(minor)); + MUTEX_RELEASE(&input_dev_table_lock); + + return err ? NULL : result; +} + +static int listener_minor_allocate(void) { + MUTEX_ACQUIRE(&minor_bitmap_mutex); + + int output = bitmap_find_first_set(&listener_minor_bitmap); + if (output >= 0) + bitmap_set(&listener_minor_bitmap, output, 0); + + MUTEX_RELEASE(&minor_bitmap_mutex); + return output; +} + +static void listener_minor_free(int minor) { + MUTEX_ACQUIRE(&minor_bitmap_mutex); + bitmap_set(&listener_minor_bitmap, minor, 1); + MUTEX_RELEASE(&minor_bitmap_mutex); +} + +static int input_dev_open(int minor, vnode_t **vnode, int flags) { + input_device_t *dev = get(minor); + if (dev == NULL) + return ENODEV; + + int res; + int listener_minor = listener_minor_allocate(); + if (listener_minor == -1) + return ENOSPC; + + char tmp_name[20]; + snprintf(tmp_name, 20, "input/.listener%d", listener_minor); + + // TODO implement a way to atomically do this + res = devfs_register(&input_listener_ops, tmp_name, V_TYPE_CHDEV, DEV_MAJOR_INPUT, listener_minor + MAX_DEVICES, 0, NULL); + if (res != 0) { + listener_minor_free(listener_minor); + return res; + } + + vnode_t *listener_vnode; + __assert(devfs_getbyname(tmp_name, &listener_vnode) == 0); + devfs_remove(tmp_name, DEV_MAJOR_INPUT, listener_minor + MAX_DEVICES); + + input_listener_t *listener = alloc(sizeof(input_listener_t)); + if (listener == NULL) { + VOP_RELEASE(listener_vnode); + listener_minor_free(listener_minor); + return ENOMEM; + } + + res = ringbuffer_init(&listener->buffer, INPUT_DEV_BUFFER_SIZE); + if (res != 0) { + VOP_RELEASE(listener_vnode); + free(listener); + listener_minor_free(listener_minor); + return res; + } + + MUTEX_ACQUIRE(&input_dev_table_lock); + res = hashtable_set(&input_listener_table, listener, &listener_minor, sizeof(listener_minor), true); + MUTEX_RELEASE(&input_dev_table_lock); + + if (res != 0) { + ringbuffer_destroy(&listener->buffer); + VOP_RELEASE(listener_vnode); + free(listener); + listener_minor_free(listener_minor); + return res; + } + + listener->device = dev; + listener->vnode = listener_vnode; + + SPINLOCK_INIT(listener->lock); + MUTEX_INIT(&listener->mutex); + POLL_INITHEADER(&listener->poll_header); + + long ipl = spinlock_acquire_raise_ipl(&dev->lock, IPL_INPUT); + list_push_back(&dev->listeners, &listener->node); + spinlock_release_lower_ipl(&dev->lock, ipl); + + VOP_RELEASE(*vnode); + *vnode = listener_vnode; + + return 0; +} + +static devops_t input_dev_ops = { + .open = input_dev_open +}; + +static int input_listener_close(int minor, int flags) { + input_listener_t *lis = get(minor); + if (lis == NULL) + return ENODEV; + + int listener_minor = minor - MAX_DEVICES; + + MUTEX_ACQUIRE(&input_dev_table_lock); + hashtable_remove(&input_listener_table, &listener_minor, sizeof(listener_minor)); + MUTEX_RELEASE(&input_dev_table_lock); + + long ipl = spinlock_acquire_raise_ipl(&lis->device->lock, IPL_INPUT); + list_remove(&lis->device->listeners, &lis->node); + spinlock_release_lower_ipl(&lis->device->lock, ipl); + + ringbuffer_destroy(&lis->buffer); + free(lis); + + listener_minor_free(listener_minor); + + return 0; +} + +static int input_listener_internal_poll(input_listener_t *lis, polldata_t *data, int events) { + int revents = 0; + + if ((events & POLLIN) && RINGBUFFER_DATACOUNT(&lis->buffer) > 0) + revents |= POLLIN; + + if (revents == 0 && data) + poll_add(&lis->poll_header, data, events); + + return revents; +} + +static int input_listener_read(int minor, iovec_iterator_t *iovec_iterator, size_t size, uintmax_t offset, int flags, size_t *readc) { + input_listener_t *lis = get(minor); + if (lis == NULL) + return ENODEV; + + if (size % sizeof(input_event_t) != 0) + return EINVAL; + + int err = 0; + for (;;) { + polldesc_t desc = {0}; + err = poll_initdesc(&desc, 1); + if (err) + return err; + + MUTEX_ACQUIRE(&lis->mutex); + long ipl = spinlock_acquire_raise_ipl(&lis->lock, IPL_INPUT); + int revents = input_listener_internal_poll(lis, &desc.data[0], POLLIN); + spinlock_release_lower_ipl(&lis->lock, ipl); + + if (revents != 0) { + *readc = iovec_iterator_read_from_ringbuffer(iovec_iterator, &lis->buffer, size); + if (*readc == RINGBUFFER_USER_COPY_FAILED) + err = EFAULT; + + MUTEX_RELEASE(&lis->mutex); + + poll_leave(&desc); + poll_destroydesc(&desc); + break; + } + + MUTEX_RELEASE(&lis->mutex); + + if (flags & V_FFLAGS_NONBLOCKING) { + poll_leave(&desc); + poll_destroydesc(&desc); + return EAGAIN; + } + + err = poll_dowait(&desc, 0); + + poll_leave(&desc); + poll_destroydesc(&desc); + + if (err) + return err; + } + + return err; +} + +static int input_listener_poll(int minor, polldata_t *data, int events) { + input_listener_t *lis = get(minor); + if (lis == NULL) + return POLLERR; + + long ipl = spinlock_acquire_raise_ipl(&lis->lock, IPL_INPUT); + + int revents = input_listener_internal_poll(lis, data, events); + + spinlock_release_lower_ipl(&lis->lock, ipl); + + return revents; +} + +static int copy_bitmap_to_user(bitmap_t *bitmap, void *arg, size_t size) { + int err = 0; + + long nlong = 0; + if (bitmap != NULL) { + nlong = ROUND_UP(bitmap->size, sizeof(long) * 8) / (sizeof(long) * 8); + err = USERCOPY_POSSIBLY_TO_USER(arg, bitmap->data, min(size, nlong * sizeof(long))); + if (err != 0) + return err; + } + + if (nlong * sizeof(long) < size) + err = USERCOPY_POSSIBLY_MEMSET_TO_USER((void *)arg + nlong * sizeof(long), 0, size - nlong * sizeof(long)); + + return err; +} + +#define EVIOCGVERSION 0x1 +#define EVIOCGID 0x2 +#define EVIOCGNAME 0x6 +#define EVIOCGPHYS 0x7 +#define EVIOCGUNIQ 0x8 +#define EVIOCGPROP 0x9 +#define EVIOCGKEY 0x18 +#define EVIOCGLED 0x19 +#define EVIOCGSND 0x1a +#define EVIOCGSW 0x1b +#define EVIOCGBIT 0x20 +#define EVIOCGABS 0x40 +#define EVIOVSABS 0xc0 + +#define ID_BUS 0 +#define ID_VENDOR 1 +#define ID_PRODUCT 2 +#define ID_VERSION 3 + + +static int input_listener_ioctl(int minor, unsigned long request, void *arg, int *result, cred_t *cred) { + input_listener_t *lis = get(minor); + if (lis == NULL) + return ENODEV; + + int number = request & 0xff; + int type = (request >> 8) & 0xff; + int size = (request >> 16) & 0x3fff; + + if (type != 'E') + return ENOTTY; + + if (number == EVIOCGVERSION) { + uint32_t version = 0; + version |= lis->device->ver_major << 16; + version |= lis->device->ver_minor << 8; + version |= lis->device->ver_patch << 0; + + return USERCOPY_POSSIBLY_TO_USER(arg, &version, sizeof(version)); + } else if (number == EVIOCGID) { + uint16_t id[4]; + id[ID_BUS] = lis->device->id_bus; + id[ID_VENDOR] = lis->device->id_vendor; + id[ID_PRODUCT] = lis->device->id_product; + id[ID_VERSION] = lis->device->id_version; + + return USERCOPY_POSSIBLY_TO_USER(arg, &id, sizeof(id)); + } else if (number == EVIOCGNAME) { + return USERCOPY_POSSIBLY_TO_USER(arg, &lis->device->name, min(size, sizeof(lis->device->name))); + } else if (number == EVIOCGPHYS) { + return USERCOPY_POSSIBLY_TO_USER(arg, &lis->device->phys, min(size, sizeof(lis->device->phys))); + } else if (number == EVIOCGUNIQ) { + return USERCOPY_POSSIBLY_TO_USER(arg, &lis->device->uniq, min(size, sizeof(lis->device->uniq))); + } else if (number == EVIOCGPROP) { + return copy_bitmap_to_user(&lis->device->prop_bits, arg, size); + } else if (number == EVIOCGKEY || number == EVIOCGLED || number == EVIOCGSND || number == EVIOCGSW) { + // TODO: maintain current device state so SYN_DROPPED recovery can + // report pressed keys/buttons and active LEDs/sounds/switches. + return copy_bitmap_to_user(NULL, arg, size); + } else if (number == EVIOCGBIT) { + return copy_bitmap_to_user(&lis->device->ev_bits, arg, size); + } else if (number > EVIOCGBIT && number < EVIOCGBIT + INPUT_EV_CNT) { + bitmap_t *bitmap = NULL; + if (bitmap_get(&lis->device->ev_bits, number - EVIOCGBIT)) { + switch (number - EVIOCGBIT) { + case INPUT_EV_KEY: + bitmap = &lis->device->key_bits; + break; + case INPUT_EV_REL: + bitmap = &lis->device->rel_bits; + break; + case INPUT_EV_ABS: + bitmap = &lis->device->abs_bits; + break; + } + } + + return copy_bitmap_to_user(bitmap, arg, size); + } else if (number >= EVIOCGABS && number < EVIOCGABS + INPUT_ABS_CNT) { + if (!bitmap_get(&lis->device->ev_bits, INPUT_EV_ABS)) + return EINVAL; + + int abs_code = number - EVIOCGABS; + if (!bitmap_get(&lis->device->abs_bits, abs_code)) + return EINVAL; + + input_absinfo_t absinfo; + absinfo.value = -1; + absinfo.min = -1; + absinfo.max = -1; + absinfo.fuzz = 0; + absinfo.flat = 0; + absinfo.res = 0; + return USERCOPY_POSSIBLY_TO_USER(arg, &absinfo, sizeof(input_absinfo_t)); + } else { + printf("input_ioctl: unhandled ioctl %lu (%u, %u, %u)\n", request, number, type, size); + return ENOTTY; + } +} + +static devops_t input_listener_ops = { + .close = input_listener_close, + .read = input_listener_read, + .poll = input_listener_poll, + .ioctl = input_listener_ioctl, +}; + +input_device_t *input_new() { + // TODO proper allocator for hotplug support + int minor = __atomic_fetch_add(&input_dev_count, 1, __ATOMIC_RELAXED); + if (minor >= MAX_DEVICES) + return NULL; + + char name_buffer[32]; + snprintf(name_buffer, sizeof(name_buffer), "input/event%d", minor); + + input_device_t *dev = alloc(sizeof(input_device_t)); + if (dev == NULL) + return NULL; + + list_init(&dev->listeners); + + int res; + res = bitmap_init(&dev->prop_bits, INPUT_PROP_CNT); + if (res != 0) + goto cleanup; + + res = bitmap_init(&dev->ev_bits, INPUT_EV_CNT); + if (res != 0) + goto cleanup; + + res = bitmap_init(&dev->syn_bits, INPUT_SYN_CNT); + if (res != 0) + goto cleanup; + + res = bitmap_init(&dev->key_bits, INPUT_KEY_CNT); + if (res != 0) + goto cleanup; + + res = bitmap_init(&dev->rel_bits, INPUT_REL_CNT); + if (res != 0) + goto cleanup; + + res = bitmap_init(&dev->abs_bits, INPUT_ABS_CNT); + if (res != 0) + goto cleanup; + + MUTEX_ACQUIRE(&input_dev_table_lock); + res = hashtable_set(&input_dev_table, dev, &minor, sizeof(minor), true); + MUTEX_RELEASE(&input_dev_table_lock); + if (res != 0) + goto cleanup; + + // TODO proper handling here for hotplug support + res = devfs_register(&input_dev_ops, name_buffer, V_TYPE_CHDEV, DEV_MAJOR_INPUT, minor, 0666, NULL); + __assert(res == 0); + + bitmap_set(&dev->ev_bits, INPUT_EV_SYN, 1); + bitmap_set(&dev->syn_bits, INPUT_SYN_REPORT, 1); + bitmap_set(&dev->syn_bits, INPUT_SYN_DROPPED, 1); + + return dev; + +cleanup: + if (dev->ev_bits.data != NULL) + bitmap_destroy(&dev->ev_bits); + if (dev->syn_bits.data != NULL) + bitmap_destroy(&dev->syn_bits); + if (dev->key_bits.data != NULL) + bitmap_destroy(&dev->key_bits); + if (dev->rel_bits.data != NULL) + bitmap_destroy(&dev->rel_bits); + if (dev->abs_bits.data != NULL) + bitmap_destroy(&dev->abs_bits); + + free(dev); + return NULL; +} + +static void input_queue_internal(input_listener_t *lis, input_event_t *event, bool signal) { + timespec_t ts; + if (lis->device->clock_id == CLOCK_REALTIME || lis->device->clock_id == CLOCK_MONOTONIC) { + ts = timekeeper_time(); + } else if (lis->device->clock_id == CLOCK_BOOTTIME) { + ts = timekeeper_timefromboot(); + } else { + ts.s = 0; + ts.ns = 0; + } + + event->time = timespec_to_timeval(ts); + + size_t written = ringbuffer_write(&lis->buffer, event, sizeof(input_event_t)); + if (written == sizeof(input_event_t) && signal) + poll_event(&lis->poll_header, POLLIN); +} + +static void input_queue_packet_internal(input_listener_t *lis, input_event_t *events, int count) { + size_t needed_space = (count + 1) * sizeof(input_event_t); + spinlock_acquire(&lis->lock); + if (RINGBUFFER_FREESPACE(&lis->buffer) < needed_space) { + // Not enough space, write a SYN_DROPPED event. + input_event_t event; + event.type = INPUT_EV_SYN; + event.code = INPUT_SYN_DROPPED; + event.value = 0; + input_queue_internal(lis, &event, true); + } else { + // Queue all the events. + for (int i = 0; i < count; ++i) { + input_queue_internal(lis, &events[i], false); + } + + input_event_t event; + event.type = INPUT_EV_SYN; + event.code = INPUT_SYN_REPORT; + event.value = 0; + input_queue_internal(lis, &event, true); + } + spinlock_release(&lis->lock); +} + +void input_queue_packet(input_device_t *dev, input_event_t *events, int count) { + if (count < 1) + return; + + console_process_events(events, count); + + long ipl = spinlock_acquire_raise_ipl(&dev->lock, IPL_INPUT); + + list_for_each(&dev->listeners, node) { + input_listener_t *lis = container_of(node, input_listener_t, node); + input_queue_packet_internal(lis, events, count); + } + + spinlock_release_lower_ipl(&dev->lock, ipl); +} + +static void input_init(void) { + bitmap_set_range(&listener_minor_bitmap, 0, MAX_DEVICES, 1); + __assert(devfs_createdir("input") == 0); + + // TODO define this statically + __assert(hashtable_init(&input_dev_table, 32) == 0); + __assert(hashtable_init(&input_listener_table, 128) == 0); +} + +INIT_ROUTINE_DEFINE(input, INIT_ROUTINE_FLAGS_NONE, input_init, devfs, console);