input: add new evdev subsystem

Co-authored-by: Matheus <matheusuky@gmail.com>
This commit is contained in:
2026-04-27 00:09:53 -03:00
committed by Matheus
parent 2f694a63ef
commit a1cfbe32bf
6 changed files with 894 additions and 31 deletions
+2
View File
@@ -2,9 +2,11 @@
#define _CONSOLE_H
#include <stddef.h>
#include <kernel/input.h>
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
+1
View File
@@ -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);
+231
View File
@@ -0,0 +1,231 @@
#ifndef _INPUT_H
#define _INPUT_H
#include <kernel/poll.h>
#include <kernel/timekeeper.h>
#include <kernel/vfs.h>
#include <bitmap.h>
#include <list.h>
#include <ringbuffer.h>
#include <semaphore.h>
#include <spinlock.h>
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
+1
View File
@@ -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
+161 -31
View File
@@ -5,16 +5,31 @@
#include <kernel/term.h>
#include <kernel/devfs.h>
#include <errno.h>
#include <kernel/keyboard.h>
#include <termios.h>
#include <kernel/poll.h>
#include <kernel/tty.h>
#include <kernel/init.h>
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);
+498
View File
@@ -0,0 +1,498 @@
#include <kernel/alloc.h>
#include <kernel/console.h>
#include <kernel/devfs.h>
#include <kernel/init.h>
#include <kernel/input.h>
#include <kernel/usercopy.h>
#include <logging.h>
#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);