blob: 84de648299fd0cbf3102c9a532737f44ebe26c6d [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <ddk/binding.h>
#include <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/hidbus.h>
#include <zircon/device/input.h>
#include <hw/inout.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include <hid/usages.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <unistd.h>
#define xprintf(fmt...) do {} while (0)
typedef struct i8042_device {
mtx_t lock;
hidbus_ifc_t* ifc;
void* cookie;
zx_handle_t irq;
thrd_t irq_thread;
int last_code;
int type;
union {
boot_kbd_report_t kbd;
boot_mouse_report_t mouse;
} report;
} i8042_device_t;
static inline bool is_kbd_modifier(uint8_t usage) {
return (usage >= HID_USAGE_KEY_LEFT_CTRL && usage <= HID_USAGE_KEY_RIGHT_GUI);
}
#define MOD_SET 1
#define MOD_EXISTS 2
#define MOD_ROLLOVER 3
static int i8042_modifier_key(i8042_device_t* dev, uint8_t mod, bool down) {
int bit = mod - HID_USAGE_KEY_LEFT_CTRL;
if (bit < 0 || bit > 7) return MOD_ROLLOVER;
if (down) {
if (dev->report.kbd.modifier & 1 << bit) {
return MOD_EXISTS;
} else {
dev->report.kbd.modifier |= 1 << bit;
}
} else {
dev->report.kbd.modifier &= ~(1 << bit);
}
return MOD_SET;
}
#define KEY_ADDED 1
#define KEY_EXISTS 2
#define KEY_ROLLOVER 3
static int i8042_add_key(i8042_device_t* dev, uint8_t usage) {
for (int i = 0; i < 6; i++) {
if (dev->report.kbd.usage[i] == usage) return KEY_EXISTS;
if (dev->report.kbd.usage[i] == 0) {
dev->report.kbd.usage[i] = usage;
return KEY_ADDED;
}
}
return KEY_ROLLOVER;
}
#define KEY_REMOVED 1
#define KEY_NOT_FOUND 2
static int i8042_rm_key(i8042_device_t* dev, uint8_t usage) {
int idx = -1;
for (int i = 0; i < 6; i++) {
if (dev->report.kbd.usage[i] == usage) {
idx = i;
break;
}
}
if (idx == -1) return KEY_NOT_FOUND;
for (int i = idx; i < 5; i++) {
dev->report.kbd.usage[i] = dev->report.kbd.usage[i+1];
}
dev->report.kbd.usage[5] = 0;
return KEY_REMOVED;
}
#define I8042_COMMAND_REG 0x64
#define I8042_STATUS_REG 0x64
#define I8042_DATA_REG 0x60
#define ISA_IRQ_KEYBOARD 0x1
#define ISA_IRQ_MOUSE 0x0c
static inline int i8042_read_data(void) {
return inp(I8042_DATA_REG);
}
static inline int i8042_read_status(void) {
return inp(I8042_STATUS_REG);
}
static inline void i8042_write_data(int val) {
outp(I8042_DATA_REG, val);
}
static inline void i8042_write_command(int val) {
outp(I8042_COMMAND_REG, val);
}
/*
* timeout in milliseconds
*/
#define I8042_CTL_TIMEOUT 500
/*
* status register bits
*/
#define I8042_STR_PARITY 0x80
#define I8042_STR_TIMEOUT 0x40
#define I8042_STR_AUXDATA 0x20
#define I8042_STR_KEYLOCK 0x10
#define I8042_STR_CMDDAT 0x08
#define I8042_STR_MUXERR 0x04
#define I8042_STR_IBF 0x02
#define I8042_STR_OBF 0x01
/*
* control register bits
*/
#define I8042_CTR_KBDINT 0x01
#define I8042_CTR_AUXINT 0x02
#define I8042_CTR_IGNKEYLK 0x08
#define I8042_CTR_KBDDIS 0x10
#define I8042_CTR_AUXDIS 0x20
#define I8042_CTR_XLATE 0x40
/*
* commands
*/
#define I8042_CMD_CTL_RCTR 0x0120
#define I8042_CMD_CTL_WCTR 0x1060
#define I8042_CMD_CTL_TEST 0x01aa
#define I8042_CMD_CTL_AUX 0x00d4
// Identity response will be ACK + 0, 1, or 2 bytes
#define I8042_CMD_IDENTIFY 0x03f2
#define I8042_CMD_SCAN_DIS 0x01f5
#define I8042_CMD_SCAN_EN 0x01f4
#define I8042_CMD_CTL_KBD_DIS 0x00ad
#define I8042_CMD_CTL_KBD_EN 0x00ae
#define I8042_CMD_CTL_KBD_TEST 0x01ab
#define I8042_CMD_KBD_MODE 0x01f0
#define I8042_CMD_CTL_MOUSE_DIS 0x00a7
#define I8042_CMD_CTL_MOUSE_EN 0x00a8
#define I8042_CMD_CTL_MOUSE_TEST 0x01a9
/*
* used for flushing buffers. the i8042 internal buffer shoudn't exceed this.
*/
#define I8042_BUFFER_LENGTH 32
static const uint8_t kbd_hid_report_desc[] = {
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x06, // Usage (Keyboard)
0xA1, 0x01, // Collection (Application)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0xE0, // Usage Minimum (0xE0)
0x29, 0xE7, // Usage Maximum (0xE7)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x75, 0x01, // Report Size (1)
0x95, 0x08, // Report Count (8)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x08, // Report Size (8)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x05, // Report Count (5)
0x75, 0x01, // Report Size (1)
0x05, 0x08, // Usage Page (LEDs)
0x19, 0x01, // Usage Minimum (Num Lock)
0x29, 0x05, // Usage Maximum (Kana)
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x95, 0x01, // Report Count (1)
0x75, 0x03, // Report Size (3)
0x91, 0x01, // Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x95, 0x06, // Report Count (6)
0x75, 0x08, // Report Size (8)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x65, // Logical Maximum (101)
0x05, 0x07, // Usage Page (Kbrd/Keypad)
0x19, 0x00, // Usage Minimum (0x00)
0x29, 0x65, // Usage Maximum (0x65)
0x81, 0x00, // Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
};
static const uint8_t pc_set1_usage_map[128] = {
/* 0x00 */ 0, HID_USAGE_KEY_ESC, HID_USAGE_KEY_1, HID_USAGE_KEY_2,
/* 0x04 */ HID_USAGE_KEY_3, HID_USAGE_KEY_4, HID_USAGE_KEY_5, HID_USAGE_KEY_6,
/* 0x08 */ HID_USAGE_KEY_7, HID_USAGE_KEY_8, HID_USAGE_KEY_9, HID_USAGE_KEY_0,
/* 0x0c */ HID_USAGE_KEY_MINUS, HID_USAGE_KEY_EQUAL, HID_USAGE_KEY_BACKSPACE, HID_USAGE_KEY_TAB,
/* 0x10 */ HID_USAGE_KEY_Q, HID_USAGE_KEY_W, HID_USAGE_KEY_E, HID_USAGE_KEY_R,
/* 0x14 */ HID_USAGE_KEY_T, HID_USAGE_KEY_Y, HID_USAGE_KEY_U, HID_USAGE_KEY_I,
/* 0x18 */ HID_USAGE_KEY_O, HID_USAGE_KEY_P, HID_USAGE_KEY_LEFTBRACE, HID_USAGE_KEY_RIGHTBRACE,
/* 0x1c */ HID_USAGE_KEY_ENTER, HID_USAGE_KEY_LEFT_CTRL, HID_USAGE_KEY_A, HID_USAGE_KEY_S,
/* 0x20 */ HID_USAGE_KEY_D, HID_USAGE_KEY_F, HID_USAGE_KEY_G, HID_USAGE_KEY_H,
/* 0x24 */ HID_USAGE_KEY_J, HID_USAGE_KEY_K, HID_USAGE_KEY_L, HID_USAGE_KEY_SEMICOLON,
/* 0x28 */ HID_USAGE_KEY_APOSTROPHE, HID_USAGE_KEY_GRAVE, HID_USAGE_KEY_LEFT_SHIFT, HID_USAGE_KEY_BACKSLASH,
/* 0x2c */ HID_USAGE_KEY_Z, HID_USAGE_KEY_X, HID_USAGE_KEY_C, HID_USAGE_KEY_V,
/* 0x30 */ HID_USAGE_KEY_B, HID_USAGE_KEY_N, HID_USAGE_KEY_M, HID_USAGE_KEY_COMMA,
/* 0x34 */ HID_USAGE_KEY_DOT, HID_USAGE_KEY_SLASH, HID_USAGE_KEY_RIGHT_SHIFT, HID_USAGE_KEY_KP_ASTERISK,
/* 0x38 */ HID_USAGE_KEY_LEFT_ALT, HID_USAGE_KEY_SPACE, HID_USAGE_KEY_CAPSLOCK, HID_USAGE_KEY_F1,
/* 0x3c */ HID_USAGE_KEY_F2, HID_USAGE_KEY_F3, HID_USAGE_KEY_F4, HID_USAGE_KEY_F5,
/* 0x40 */ HID_USAGE_KEY_F6, HID_USAGE_KEY_F7, HID_USAGE_KEY_F8, HID_USAGE_KEY_F9,
/* 0x44 */ HID_USAGE_KEY_F10, HID_USAGE_KEY_NUMLOCK, HID_USAGE_KEY_SCROLLLOCK, HID_USAGE_KEY_KP_7,
/* 0x48 */ HID_USAGE_KEY_KP_8, HID_USAGE_KEY_KP_9, HID_USAGE_KEY_KP_MINUS, HID_USAGE_KEY_KP_4,
/* 0x4c */ HID_USAGE_KEY_KP_5, HID_USAGE_KEY_KP_6, HID_USAGE_KEY_KP_PLUS, HID_USAGE_KEY_KP_1,
/* 0x50 */ HID_USAGE_KEY_KP_2, HID_USAGE_KEY_KP_3, HID_USAGE_KEY_KP_0, HID_USAGE_KEY_KP_DOT,
/* 0x54 */ 0, 0, 0, HID_USAGE_KEY_F11,
/* 0x58 */ HID_USAGE_KEY_F12, 0, 0, 0,
};
static const uint8_t pc_set1_usage_map_e0[128] = {
/* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x18 */ 0, 0, 0, 0, HID_USAGE_KEY_KP_ENTER, HID_USAGE_KEY_RIGHT_CTRL, 0, 0,
/* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0,
/* 0x28 */ 0, 0, 0, 0, 0, 0, HID_USAGE_KEY_VOL_DOWN, 0,
/* 0x30 */ HID_USAGE_KEY_VOL_UP, 0, 0, 0, 0, HID_USAGE_KEY_KP_SLASH, 0, HID_USAGE_KEY_PRINTSCREEN,
/* 0x38 */ HID_USAGE_KEY_RIGHT_ALT, 0, 0, 0, 0, 0, 0, 0,
/* 0x40 */ 0, 0, 0, 0, 0, 0, 0, HID_USAGE_KEY_HOME,
/* 0x48 */ HID_USAGE_KEY_UP, HID_USAGE_KEY_PAGEUP, 0, HID_USAGE_KEY_LEFT, 0, HID_USAGE_KEY_RIGHT, 0, HID_USAGE_KEY_END,
/* 0x50 */ HID_USAGE_KEY_DOWN, HID_USAGE_KEY_PAGEDOWN, HID_USAGE_KEY_INSERT, HID_USAGE_KEY_DELETE, 0, 0, 0, 0,
/* 0x58 */ 0, 0, 0, HID_USAGE_KEY_LEFT_GUI, HID_USAGE_KEY_RIGHT_GUI, 0 /* MENU */, 0, 0,
};
static const uint8_t mouse_hid_report_desc[] = {
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x02, // Usage (Mouse)
0xA1, 0x01, // Collection (Application)
0x09, 0x01, // Usage (Pointer)
0xA1, 0x00, // Collection (Physical)
0x05, 0x09, // Usage Page (Button)
0x19, 0x01, // Usage Minimum (0x01)
0x29, 0x03, // Usage Maximum (0x03)
0x15, 0x00, // Logical Minimum (0)
0x25, 0x01, // Logical Maximum (1)
0x95, 0x03, // Report Count (3)
0x75, 0x01, // Report Size (1)
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01, // Report Count (1)
0x75, 0x05, // Report Size (5)
0x81, 0x01, // Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
0x09, 0x30, // Usage (X)
0x09, 0x31, // Usage (Y)
0x15, 0x81, // Logical Minimum (129)
0x25, 0x7F, // Logical Maximum (127)
0x75, 0x08, // Report Size (8)
0x95, 0x02, // Report Count (2)
0x81, 0x06, // Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0, // End Collection
0xC0, // End Collection
};
static const boot_kbd_report_t report_err_rollover = {
.modifier = 1,
.usage = {1, 1, 1, 1, 1, 1 }
};
static int i8042_wait_read(void) {
int i = 0;
while ((~i8042_read_status() & I8042_STR_OBF) && (i < I8042_CTL_TIMEOUT)) {
usleep(10);
i++;
}
return -(i == I8042_CTL_TIMEOUT);
}
static int i8042_wait_write(void) {
int i = 0;
while ((i8042_read_status() & I8042_STR_IBF) && (i < I8042_CTL_TIMEOUT)) {
usleep(10);
i++;
}
return -(i == I8042_CTL_TIMEOUT);
}
static int i8042_flush(void) {
unsigned char data __UNUSED;
int i = 0;
while ((i8042_read_status() & I8042_STR_OBF) && (i++ < I8042_BUFFER_LENGTH)) {
usleep(10);
data = i8042_read_data();
}
return i;
}
static int i8042_command_data(uint8_t* param, int command) {
int retval = 0, i = 0;
for (i = 0; i < ((command >> 12) & 0xf); i++) {
if ((retval = i8042_wait_write())) {
break;
}
i8042_write_data(param[i]);
}
int expected = (command >> 8) & 0xf;
if (!retval) {
for (i = 0; i < expected; i++) {
if ((retval = i8042_wait_read())) {
xprintf("i8042: timeout reading; got %d bytes\n", i);
return i;
}
// TODO: do we need to distinguish keyboard and aux data?
param[i] = i8042_read_data();
}
}
return retval ? retval : expected;
}
static int i8042_command(uint8_t* param, int command) {
xprintf("i8042 ctl command 0x%04x\n", command & 0xffff);
int retval = 0;
retval = i8042_wait_write();
if (!retval) {
i8042_write_command(command & 0xff);
}
if (!retval) {
retval = i8042_command_data(param, command);
}
return retval;
}
static int i8042_selftest(void) {
uint8_t param;
int i = 0;
do {
if (i8042_command(&param, I8042_CMD_CTL_TEST) < 0) {
return -1;
}
if (param == 0x55)
return 0;
usleep(50 * 1000);
} while (i++ < 5);
return -1;
}
static int i8042_dev_command(uint8_t* param, int command) {
xprintf("i8042 dev command 0x%04x\n", command & 0xffff);
int retval = 0;
retval = i8042_wait_write();
if (!retval) {
i8042_write_data(command & 0xff);
}
if (!retval) {
retval = i8042_command_data(param, command);
}
return retval;
}
static int i8042_aux_command(uint8_t* param, int command) {
xprintf("i8042 aux command\n");
int retval = 0;
retval = i8042_wait_write();
if (!retval) {
i8042_write_command(I8042_CMD_CTL_AUX);
}
if (!retval) {
return i8042_dev_command(param, command);
}
return retval;
}
static void i8042_process_scode(i8042_device_t* dev, uint8_t scode, unsigned int flags) {
// is this a multi code sequence?
bool multi = (dev->last_code == 0xe0);
// update the last received code
dev->last_code = scode;
// save the key up event bit
bool key_up = !!(scode & 0x80);
scode &= 0x7f;
// translate the key based on our translation table
uint8_t usage;
if (multi) {
usage = pc_set1_usage_map_e0[scode];
} else {
usage = pc_set1_usage_map[scode];
}
if (!usage) return;
bool rollover = false;
if (is_kbd_modifier(usage)) {
switch (i8042_modifier_key(dev, usage, !key_up)) {
case MOD_EXISTS:
return;
case MOD_ROLLOVER:
rollover = true;
break;
case MOD_SET:
default:
break;
}
} else if (key_up) {
if (i8042_rm_key(dev, usage) != KEY_REMOVED) {
rollover = true;
}
} else {
switch (i8042_add_key(dev, usage)) {
case KEY_EXISTS:
return;
case KEY_ROLLOVER:
rollover = true;
break;
case KEY_ADDED:
default:
break;
}
}
//cprintf("i8042: scancode=0x%x, keyup=%u, multi=%u: usage=0x%x\n", scode, !!key_up, multi, usage);
const boot_kbd_report_t* report = rollover ? &report_err_rollover : &dev->report.kbd;
mtx_lock(&dev->lock);
if (dev->ifc) {
dev->ifc->io_queue(dev->cookie, (const uint8_t*)report, sizeof(*report));
}
mtx_unlock(&dev->lock);
}
static void i8042_process_mouse(i8042_device_t* dev, uint8_t data, unsigned int flags) {
switch (dev->last_code) {
case 0:
if (!(data & 0x08)) {
// The first byte always has bit 3 set, so skip this packet.
return;
}
dev->report.mouse.buttons = data;
break;
case 1: {
int state = dev->report.mouse.buttons;
int d = data;
dev->report.mouse.rel_x = d - ((state << 4) & 0x100);
break;
}
case 2: {
int state = dev->report.mouse.buttons;
int d = data;
// PS/2 maps the y-axis backwards so invert the rel_y value
dev->report.mouse.rel_y = ((state << 3) & 0x100) - d;
dev->report.mouse.buttons &= 0x7;
mtx_lock(&dev->lock);
if (dev->ifc) {
dev->ifc->io_queue(dev->cookie, (const uint8_t*)&dev->report.mouse,
sizeof(dev->report.mouse));
}
mtx_unlock(&dev->lock);
memset(&dev->report.mouse, 0, sizeof(dev->report.mouse));
break;
}
}
dev->last_code = (dev->last_code + 1) % 3;
}
static int i8042_irq_thread(void* arg) {
i8042_device_t* device = (i8042_device_t*)arg;
// enable I/O port access
// TODO
zx_status_t status;
status = zx_mmap_device_io(get_root_resource(), I8042_COMMAND_REG, 1);
if (status)
return 0;
status = zx_mmap_device_io(get_root_resource(), I8042_DATA_REG, 1);
if (status)
return 0;
for (;;) {
#if ENABLE_NEW_IRQ_API
status = zx_irq_wait(device->irq, NULL);
#else
uint64_t slots;
status = zx_interrupt_wait(device->irq, &slots);
#endif
if (status == ZX_OK) {
// keep handling status on the controller until no bits are set we care about
bool retry;
do {
retry = false;
uint8_t str = i8042_read_status();
// check for incoming data from the controller
// TODO: deal with potential race between IRQ1 and IRQ12
if (str & I8042_STR_OBF) {
uint8_t data = i8042_read_data();
// TODO: should we check (str & I8042_STR_AUXDATA) before
// handling this byte?
if (device->type == INPUT_PROTO_KBD) {
i8042_process_scode(device, data,
((str & I8042_STR_PARITY) ? I8042_STR_PARITY : 0) |
((str & I8042_STR_TIMEOUT) ? I8042_STR_TIMEOUT : 0));
} else if (device->type == INPUT_PROTO_MOUSE) {
i8042_process_mouse(device, data, 0);
}
retry = true;
}
// TODO check other status bits here
} while (retry);
}
}
return 0;
}
static zx_status_t i8042_setup(uint8_t* ctr) {
// enable I/O port access
zx_status_t status = zx_mmap_device_io(get_root_resource(), I8042_COMMAND_REG, 1);
if (status)
return status;
status = zx_mmap_device_io(get_root_resource(), I8042_DATA_REG, 1);
if (status)
return status;
// initialize hardware
i8042_command(NULL, I8042_CMD_CTL_KBD_DIS);
i8042_command(NULL, I8042_CMD_CTL_MOUSE_DIS);
i8042_flush();
if (i8042_command(ctr, I8042_CMD_CTL_RCTR) < 0)
return -1;
xprintf("i8042 controller register: 0x%02x\n", *ctr);
bool have_mouse = !!(*ctr & I8042_CTR_AUXDIS);
// disable IRQs and translation
*ctr &= ~(I8042_CTR_KBDINT | I8042_CTR_AUXINT | I8042_CTR_XLATE);
if (i8042_command(ctr, I8042_CMD_CTL_WCTR) < 0)
return -1;
if (i8042_selftest() < 0) {
printf("i8042 self-test failed\n");
return -1;
}
uint8_t resp = 0;
if (i8042_command(&resp, I8042_CMD_CTL_KBD_TEST) < 0)
return -1;
if (resp != 0x00) {
printf("i8042 kbd test failed: 0x%02x\n", resp);
return -1;
}
if (have_mouse) {
resp = 0;
if (i8042_command(&resp, I8042_CMD_CTL_MOUSE_TEST) < 0)
return -1;
if (resp != 0x00) {
printf("i8042 mouse test failed: 0x%02x\n", resp);
return -1;
}
}
return ZX_OK;
}
static void i8042_identify(int (*cmd)(uint8_t* param, int command)) {
uint8_t resp[3];
if (cmd(resp, I8042_CMD_SCAN_DIS) < 0) return;
resp[0] = 0;
int ident_sz = cmd(resp, I8042_CMD_IDENTIFY);
if (ident_sz < 0) return;
printf("i8042 device ");
switch (ident_sz) {
case 1:
printf("(unknown)");
break;
case 2:
printf("0x%02x", resp[1]);
break;
case 3:
printf("0x%02x 0x%02x", resp[1], resp[2]);
break;
default:
printf("failed to respond to IDENTIFY");
}
printf("\n");
cmd(resp, I8042_CMD_SCAN_EN);
}
static zx_status_t i8042_query(void* ctx, uint32_t options, hid_info_t* info) {
i8042_device_t* i8042 = ctx;
info->dev_num = i8042->type; // use the type for the device number for now
info->dev_class = i8042->type;
info->boot_device = true;
return ZX_OK;
}
static zx_status_t i8042_start(void* ctx, hidbus_ifc_t* ifc, void* cookie) {
i8042_device_t* i8042 = ctx;
mtx_lock(&i8042->lock);
if (i8042->ifc != NULL) {
mtx_unlock(&i8042->lock);
return ZX_ERR_ALREADY_BOUND;
}
i8042->ifc = ifc;
i8042->cookie = cookie;
mtx_unlock(&i8042->lock);
return ZX_OK;
}
static void i8042_stop(void* ctx) {
i8042_device_t* i8042 = ctx;
mtx_lock(&i8042->lock);
i8042->ifc = NULL;
i8042->cookie = NULL;
mtx_unlock(&i8042->lock);
}
static zx_status_t i8042_get_descriptor(void* ctx, uint8_t desc_type,
void** data, size_t* len) {
if (data == NULL || len == NULL) {
return ZX_ERR_INVALID_ARGS;
}
if (desc_type != HID_DESC_TYPE_REPORT) {
return ZX_ERR_NOT_FOUND;
}
i8042_device_t* device = ctx;
const uint8_t* buf = NULL;
size_t buflen = 0;
if (device->type == INPUT_PROTO_KBD) {
buf = (void*)&kbd_hid_report_desc;
buflen = sizeof(kbd_hid_report_desc);
} else if (device->type == INPUT_PROTO_MOUSE) {
buf = (void*)&mouse_hid_report_desc;
buflen = sizeof(mouse_hid_report_desc);
} else {
return ZX_ERR_NOT_SUPPORTED;
}
*data = malloc(buflen);
*len = buflen;
memcpy(*data, buf, buflen);
return ZX_OK;
}
static zx_status_t i8042_get_report(void* ctx, uint8_t rpt_type, uint8_t rpt_id,
void* data, size_t len, size_t* out_len) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t i8042_set_report(void* ctx, uint8_t rpt_type, uint8_t rpt_id,
void* data, size_t len) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t i8042_get_idle(void* ctx, uint8_t rpt_type, uint8_t* duration) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t i8042_set_idle(void* ctx, uint8_t rpt_type, uint8_t duration) {
return ZX_OK;
}
static zx_status_t i8042_get_protocol(void* ctx, uint8_t* protocol) {
return ZX_ERR_NOT_SUPPORTED;
}
static zx_status_t i8042_set_protocol(void* ctx, uint8_t protocol) {
return ZX_OK;
}
static hidbus_protocol_ops_t hidbus_ops = {
.query = i8042_query,
.start = i8042_start,
.stop = i8042_stop,
.get_descriptor = i8042_get_descriptor,
.get_report = i8042_get_report,
.set_report = i8042_set_report,
.get_idle = i8042_get_idle,
.set_idle = i8042_set_idle,
.get_protocol = i8042_get_protocol,
.set_protocol = i8042_set_protocol,
};
static void i8042_release(void* ctx) {
i8042_device_t* i8042 = ctx;
free(i8042);
}
static zx_protocol_device_t i8042_dev_proto = {
.version = DEVICE_OPS_VERSION,
.release = i8042_release,
};
static zx_status_t i8042_dev_init(i8042_device_t* dev, const char* name, zx_device_t* parent) {
// enable device port
int cmd = dev->type == INPUT_PROTO_KBD ?
I8042_CMD_CTL_KBD_DIS : I8042_CMD_CTL_MOUSE_DIS;
i8042_command(NULL, cmd);
// TODO: use identity to determine device type, rather than assuming aux ==
// mouse
i8042_identify(dev->type == INPUT_PROTO_KBD ?
i8042_dev_command : i8042_aux_command);
cmd = dev->type == INPUT_PROTO_KBD ?
I8042_CMD_CTL_KBD_EN : I8042_CMD_CTL_MOUSE_EN;
i8042_command(NULL, cmd);
uint32_t interrupt = dev->type == INPUT_PROTO_KBD ?
ISA_IRQ_KEYBOARD : ISA_IRQ_MOUSE;
#if ENABLE_NEW_IRQ_API
zx_status_t status = zx_irq_create(get_root_resource(), interrupt,
ZX_INTERRUPT_REMAP_IRQ, &(dev->irq));
if (status != ZX_OK) {
return status;
}
#else
zx_status_t status = zx_interrupt_create(get_root_resource(), 0, &(dev->irq));
if (status != ZX_OK) {
return status;
}
status = zx_interrupt_bind(dev->irq, 0, get_root_resource(), interrupt, ZX_INTERRUPT_REMAP_IRQ);
if (status != ZX_OK) {
zx_handle_close(dev->irq);
return status;
}
#endif
// create irq thread
const char* tname = dev->type == INPUT_PROTO_KBD ?
"i8042-kbd-irq" : "i8042-mouse-irq";
int ret = thrd_create_with_name(&dev->irq_thread, i8042_irq_thread, dev, tname);
if (ret != thrd_success) {
return ZX_ERR_BAD_STATE;
}
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = name,
.ctx = dev,
.ops = &i8042_dev_proto,
.proto_id = ZX_PROTOCOL_HIDBUS,
.proto_ops = &hidbus_ops,
};
return device_add(parent, &args, NULL);
}
static int i8042_init_thread(void* arg) {
zx_device_t* parent = arg;
uint8_t ctr = 0;
zx_status_t status = i8042_setup(&ctr);
if (status != ZX_OK) {
return status;
}
bool have_mouse = !!(ctr & I8042_CTR_AUXDIS);
// turn on translation
ctr |= I8042_CTR_XLATE;
// enable devices and irqs
ctr &= ~I8042_CTR_KBDDIS;
ctr |= I8042_CTR_KBDINT;
if (have_mouse) {
ctr &= ~I8042_CTR_AUXDIS;
ctr |= I8042_CTR_AUXINT;
}
if (i8042_command(&ctr, I8042_CMD_CTL_WCTR) < 0)
return -1;
// create keyboard device
i8042_device_t* kbd_device = calloc(1, sizeof(i8042_device_t));
if (!kbd_device)
return ZX_ERR_NO_MEMORY;
mtx_init(&kbd_device->lock, mtx_plain);
kbd_device->type = INPUT_PROTO_KBD;
status = i8042_dev_init(kbd_device, "i8042-keyboard", parent);
if (status != ZX_OK) {
free(kbd_device);
}
// Mouse
if (have_mouse) {
i8042_device_t* mouse_device = NULL;
mouse_device = calloc(1, sizeof(i8042_device_t));
if (mouse_device) {
mtx_init(&mouse_device->lock, mtx_plain);
mouse_device->type = INPUT_PROTO_MOUSE;
status = i8042_dev_init(mouse_device, "i8042-mouse", parent);
if (status != ZX_OK) {
free(mouse_device);
}
}
}
xprintf("initialized i8042 driver\n");
return ZX_OK;
}
static zx_status_t i8042_bind(void* ctx, zx_device_t* parent) {
thrd_t t;
int rc = thrd_create_with_name(&t, i8042_init_thread, parent, "i8042-init");
return rc;
}
static zx_driver_ops_t i8042_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = i8042_bind,
};
ZIRCON_DRIVER_BEGIN(i8042, i8042_driver_ops, "zircon", "0.1", 6)
BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_ACPI),
BI_GOTO_IF(NE, BIND_ACPI_HID_0_3, 0x504e5030, 0), // PNP0303\0
BI_MATCH_IF(EQ, BIND_ACPI_HID_4_7, 0x33303300),
BI_LABEL(0),
BI_ABORT_IF(NE, BIND_ACPI_CID_0_3, 0x504e5030), // PNP0303\0
BI_MATCH_IF(EQ, BIND_ACPI_CID_4_7, 0x33303300),
ZIRCON_DRIVER_END(i8042)