1// Copyright 2016 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include <ddk/binding.h>
6#include <ddk/device.h>
7#include <ddk/driver.h>
8#include <ddk/protocol/hidbus.h>
9#include <zircon/device/input.h>
10#include <hw/inout.h>
11
12#include <zircon/syscalls.h>
13#include <zircon/types.h>
14
15#include <hid/usages.h>
16
17#include <stdio.h>
18#include <stdlib.h>
19#include <string.h>
20#include <threads.h>
21#include <unistd.h>
22
23#define xprintf(fmt...) do {} while (0)
24
25typedef struct i8042_device {
26    mtx_t lock;
27    hidbus_ifc_t* ifc;
28    void* cookie;
29
30    zx_handle_t irq;
31    thrd_t irq_thread;
32
33    int last_code;
34
35    int type;
36    union {
37        boot_kbd_report_t kbd;
38        boot_mouse_report_t mouse;
39    } report;
40} i8042_device_t;
41
42static inline bool is_kbd_modifier(uint8_t usage) {
43    return (usage >= HID_USAGE_KEY_LEFT_CTRL && usage <= HID_USAGE_KEY_RIGHT_GUI);
44}
45
46#define MOD_SET 1
47#define MOD_EXISTS 2
48#define MOD_ROLLOVER 3
49static int i8042_modifier_key(i8042_device_t* dev, uint8_t mod, bool down) {
50    int bit = mod - HID_USAGE_KEY_LEFT_CTRL;
51    if (bit < 0 || bit > 7) return MOD_ROLLOVER;
52    if (down) {
53        if (dev->report.kbd.modifier & 1 << bit) {
54            return MOD_EXISTS;
55        } else {
56            dev->report.kbd.modifier |= 1 << bit;
57        }
58    } else {
59        dev->report.kbd.modifier &= ~(1 << bit);
60    }
61    return MOD_SET;
62}
63
64#define KEY_ADDED 1
65#define KEY_EXISTS 2
66#define KEY_ROLLOVER 3
67static int i8042_add_key(i8042_device_t* dev, uint8_t usage) {
68    for (int i = 0; i < 6; i++) {
69        if (dev->report.kbd.usage[i] == usage) return KEY_EXISTS;
70        if (dev->report.kbd.usage[i] == 0) {
71            dev->report.kbd.usage[i] = usage;
72            return KEY_ADDED;
73        }
74    }
75    return KEY_ROLLOVER;
76}
77
78#define KEY_REMOVED 1
79#define KEY_NOT_FOUND 2
80static int i8042_rm_key(i8042_device_t* dev, uint8_t usage) {
81    int idx = -1;
82    for (int i = 0; i < 6; i++) {
83        if (dev->report.kbd.usage[i] == usage) {
84            idx = i;
85            break;
86        }
87    }
88    if (idx == -1) return KEY_NOT_FOUND;
89    for (int i = idx; i < 5; i++) {
90        dev->report.kbd.usage[i] = dev->report.kbd.usage[i+1];
91    }
92    dev->report.kbd.usage[5] = 0;
93    return KEY_REMOVED;
94}
95
96#define I8042_COMMAND_REG 0x64
97#define I8042_STATUS_REG 0x64
98#define I8042_DATA_REG 0x60
99
100#define ISA_IRQ_KEYBOARD 0x1
101#define ISA_IRQ_MOUSE 0x0c
102
103static inline int i8042_read_data(void) {
104    return inp(I8042_DATA_REG);
105}
106
107static inline int i8042_read_status(void) {
108    return inp(I8042_STATUS_REG);
109}
110
111static inline void i8042_write_data(int val) {
112    outp(I8042_DATA_REG, val);
113}
114
115static inline void i8042_write_command(int val) {
116    outp(I8042_COMMAND_REG, val);
117}
118
119/*
120 * timeout in milliseconds
121 */
122#define I8042_CTL_TIMEOUT 500
123
124/*
125 * status register bits
126 */
127#define I8042_STR_PARITY 0x80
128#define I8042_STR_TIMEOUT 0x40
129#define I8042_STR_AUXDATA 0x20
130#define I8042_STR_KEYLOCK 0x10
131#define I8042_STR_CMDDAT 0x08
132#define I8042_STR_MUXERR 0x04
133#define I8042_STR_IBF 0x02
134#define I8042_STR_OBF 0x01
135
136/*
137 * control register bits
138 */
139#define I8042_CTR_KBDINT 0x01
140#define I8042_CTR_AUXINT 0x02
141#define I8042_CTR_IGNKEYLK 0x08
142#define I8042_CTR_KBDDIS 0x10
143#define I8042_CTR_AUXDIS 0x20
144#define I8042_CTR_XLATE 0x40
145
146/*
147 * commands
148 */
149#define I8042_CMD_CTL_RCTR 0x0120
150#define I8042_CMD_CTL_WCTR 0x1060
151#define I8042_CMD_CTL_TEST 0x01aa
152#define I8042_CMD_CTL_AUX  0x00d4
153
154// Identity response will be ACK + 0, 1, or 2 bytes
155#define I8042_CMD_IDENTIFY 0x03f2
156#define I8042_CMD_SCAN_DIS 0x01f5
157#define I8042_CMD_SCAN_EN 0x01f4
158
159#define I8042_CMD_CTL_KBD_DIS 0x00ad
160#define I8042_CMD_CTL_KBD_EN 0x00ae
161#define I8042_CMD_CTL_KBD_TEST 0x01ab
162#define I8042_CMD_KBD_MODE 0x01f0
163
164#define I8042_CMD_CTL_MOUSE_DIS 0x00a7
165#define I8042_CMD_CTL_MOUSE_EN 0x00a8
166#define I8042_CMD_CTL_MOUSE_TEST 0x01a9
167
168/*
169 * used for flushing buffers. the i8042 internal buffer shoudn't exceed this.
170 */
171#define I8042_BUFFER_LENGTH 32
172
173static const uint8_t kbd_hid_report_desc[] = {
174    0x05, 0x01,  // Usage Page (Generic Desktop Ctrls)
175    0x09, 0x06,  // Usage (Keyboard)
176    0xA1, 0x01,  // Collection (Application)
177    0x05, 0x07,  //   Usage Page (Kbrd/Keypad)
178    0x19, 0xE0,  //   Usage Minimum (0xE0)
179    0x29, 0xE7,  //   Usage Maximum (0xE7)
180    0x15, 0x00,  //   Logical Minimum (0)
181    0x25, 0x01,  //   Logical Maximum (1)
182    0x75, 0x01,  //   Report Size (1)
183    0x95, 0x08,  //   Report Count (8)
184    0x81, 0x02,  //   Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
185    0x95, 0x01,  //   Report Count (1)
186    0x75, 0x08,  //   Report Size (8)
187    0x81, 0x01,  //   Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
188    0x95, 0x05,  //   Report Count (5)
189    0x75, 0x01,  //   Report Size (1)
190    0x05, 0x08,  //   Usage Page (LEDs)
191    0x19, 0x01,  //   Usage Minimum (Num Lock)
192    0x29, 0x05,  //   Usage Maximum (Kana)
193    0x91, 0x02,  //   Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
194    0x95, 0x01,  //   Report Count (1)
195    0x75, 0x03,  //   Report Size (3)
196    0x91, 0x01,  //   Output (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
197    0x95, 0x06,  //   Report Count (6)
198    0x75, 0x08,  //   Report Size (8)
199    0x15, 0x00,  //   Logical Minimum (0)
200    0x25, 0x65,  //   Logical Maximum (101)
201    0x05, 0x07,  //   Usage Page (Kbrd/Keypad)
202    0x19, 0x00,  //   Usage Minimum (0x00)
203    0x29, 0x65,  //   Usage Maximum (0x65)
204    0x81, 0x00,  //   Input (Data,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
205    0xC0,        // End Collection
206};
207
208static const uint8_t pc_set1_usage_map[128] = {
209    /* 0x00 */ 0, HID_USAGE_KEY_ESC, HID_USAGE_KEY_1, HID_USAGE_KEY_2,
210    /* 0x04 */ HID_USAGE_KEY_3, HID_USAGE_KEY_4, HID_USAGE_KEY_5, HID_USAGE_KEY_6,
211    /* 0x08 */ HID_USAGE_KEY_7, HID_USAGE_KEY_8, HID_USAGE_KEY_9, HID_USAGE_KEY_0,
212    /* 0x0c */ HID_USAGE_KEY_MINUS, HID_USAGE_KEY_EQUAL, HID_USAGE_KEY_BACKSPACE, HID_USAGE_KEY_TAB,
213    /* 0x10 */ HID_USAGE_KEY_Q, HID_USAGE_KEY_W, HID_USAGE_KEY_E, HID_USAGE_KEY_R,
214    /* 0x14 */ HID_USAGE_KEY_T, HID_USAGE_KEY_Y, HID_USAGE_KEY_U, HID_USAGE_KEY_I,
215    /* 0x18 */ HID_USAGE_KEY_O, HID_USAGE_KEY_P, HID_USAGE_KEY_LEFTBRACE, HID_USAGE_KEY_RIGHTBRACE,
216    /* 0x1c */ HID_USAGE_KEY_ENTER, HID_USAGE_KEY_LEFT_CTRL, HID_USAGE_KEY_A, HID_USAGE_KEY_S,
217    /* 0x20 */ HID_USAGE_KEY_D, HID_USAGE_KEY_F, HID_USAGE_KEY_G, HID_USAGE_KEY_H,
218    /* 0x24 */ HID_USAGE_KEY_J, HID_USAGE_KEY_K, HID_USAGE_KEY_L, HID_USAGE_KEY_SEMICOLON,
219    /* 0x28 */ HID_USAGE_KEY_APOSTROPHE, HID_USAGE_KEY_GRAVE, HID_USAGE_KEY_LEFT_SHIFT, HID_USAGE_KEY_BACKSLASH,
220    /* 0x2c */ HID_USAGE_KEY_Z, HID_USAGE_KEY_X, HID_USAGE_KEY_C, HID_USAGE_KEY_V,
221    /* 0x30 */ HID_USAGE_KEY_B, HID_USAGE_KEY_N, HID_USAGE_KEY_M, HID_USAGE_KEY_COMMA,
222    /* 0x34 */ HID_USAGE_KEY_DOT, HID_USAGE_KEY_SLASH, HID_USAGE_KEY_RIGHT_SHIFT, HID_USAGE_KEY_KP_ASTERISK,
223    /* 0x38 */ HID_USAGE_KEY_LEFT_ALT, HID_USAGE_KEY_SPACE, HID_USAGE_KEY_CAPSLOCK, HID_USAGE_KEY_F1,
224    /* 0x3c */ HID_USAGE_KEY_F2, HID_USAGE_KEY_F3, HID_USAGE_KEY_F4, HID_USAGE_KEY_F5,
225    /* 0x40 */ HID_USAGE_KEY_F6, HID_USAGE_KEY_F7, HID_USAGE_KEY_F8, HID_USAGE_KEY_F9,
226    /* 0x44 */ HID_USAGE_KEY_F10, HID_USAGE_KEY_NUMLOCK, HID_USAGE_KEY_SCROLLLOCK, HID_USAGE_KEY_KP_7,
227    /* 0x48 */ HID_USAGE_KEY_KP_8, HID_USAGE_KEY_KP_9, HID_USAGE_KEY_KP_MINUS, HID_USAGE_KEY_KP_4,
228    /* 0x4c */ HID_USAGE_KEY_KP_5, HID_USAGE_KEY_KP_6, HID_USAGE_KEY_KP_PLUS, HID_USAGE_KEY_KP_1,
229    /* 0x50 */ HID_USAGE_KEY_KP_2, HID_USAGE_KEY_KP_3, HID_USAGE_KEY_KP_0, HID_USAGE_KEY_KP_DOT,
230    /* 0x54 */ 0, 0, 0, HID_USAGE_KEY_F11,
231    /* 0x58 */ HID_USAGE_KEY_F12, 0, 0, 0,
232};
233
234static const uint8_t pc_set1_usage_map_e0[128] = {
235    /* 0x00 */ 0, 0, 0, 0, 0, 0, 0, 0,
236    /* 0x08 */ 0, 0, 0, 0, 0, 0, 0, 0,
237    /* 0x10 */ 0, 0, 0, 0, 0, 0, 0, 0,
238    /* 0x18 */ 0, 0, 0, 0, HID_USAGE_KEY_KP_ENTER, HID_USAGE_KEY_RIGHT_CTRL, 0, 0,
239    /* 0x20 */ 0, 0, 0, 0, 0, 0, 0, 0,
240    /* 0x28 */ 0, 0, 0, 0, 0, 0, HID_USAGE_KEY_VOL_DOWN, 0,
241    /* 0x30 */ HID_USAGE_KEY_VOL_UP, 0, 0, 0, 0, HID_USAGE_KEY_KP_SLASH, 0, HID_USAGE_KEY_PRINTSCREEN,
242    /* 0x38 */ HID_USAGE_KEY_RIGHT_ALT, 0, 0, 0, 0, 0, 0, 0,
243    /* 0x40 */ 0, 0, 0, 0, 0, 0, 0, HID_USAGE_KEY_HOME,
244    /* 0x48 */ HID_USAGE_KEY_UP, HID_USAGE_KEY_PAGEUP, 0, HID_USAGE_KEY_LEFT, 0, HID_USAGE_KEY_RIGHT, 0, HID_USAGE_KEY_END,
245    /* 0x50 */ HID_USAGE_KEY_DOWN, HID_USAGE_KEY_PAGEDOWN, HID_USAGE_KEY_INSERT, HID_USAGE_KEY_DELETE, 0, 0, 0, 0,
246    /* 0x58 */ 0, 0, 0, HID_USAGE_KEY_LEFT_GUI, HID_USAGE_KEY_RIGHT_GUI, 0 /* MENU */, 0, 0,
247};
248
249static const uint8_t mouse_hid_report_desc[] = {
250    0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
251    0x09, 0x02,        // Usage (Mouse)
252    0xA1, 0x01,        // Collection (Application)
253    0x09, 0x01,        //   Usage (Pointer)
254    0xA1, 0x00,        //   Collection (Physical)
255    0x05, 0x09,        //     Usage Page (Button)
256    0x19, 0x01,        //     Usage Minimum (0x01)
257    0x29, 0x03,        //     Usage Maximum (0x03)
258    0x15, 0x00,        //     Logical Minimum (0)
259    0x25, 0x01,        //     Logical Maximum (1)
260    0x95, 0x03,        //     Report Count (3)
261    0x75, 0x01,        //     Report Size (1)
262    0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
263    0x95, 0x01,        //     Report Count (1)
264    0x75, 0x05,        //     Report Size (5)
265    0x81, 0x01,        //     Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
266    0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
267    0x09, 0x30,        //     Usage (X)
268    0x09, 0x31,        //     Usage (Y)
269    0x15, 0x81,        //     Logical Minimum (129)
270    0x25, 0x7F,        //     Logical Maximum (127)
271    0x75, 0x08,        //     Report Size (8)
272    0x95, 0x02,        //     Report Count (2)
273    0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
274    0xC0,              //   End Collection
275    0xC0,              // End Collection
276};
277
278static const boot_kbd_report_t report_err_rollover = {
279    .modifier = 1,
280    .usage = {1, 1, 1, 1, 1, 1 }
281};
282
283static int i8042_wait_read(void) {
284    int i = 0;
285    while ((~i8042_read_status() & I8042_STR_OBF) && (i < I8042_CTL_TIMEOUT)) {
286        usleep(10);
287        i++;
288    }
289    return -(i == I8042_CTL_TIMEOUT);
290}
291
292static int i8042_wait_write(void) {
293    int i = 0;
294    while ((i8042_read_status() & I8042_STR_IBF) && (i < I8042_CTL_TIMEOUT)) {
295        usleep(10);
296        i++;
297    }
298    return -(i == I8042_CTL_TIMEOUT);
299}
300
301static int i8042_flush(void) {
302    unsigned char data __UNUSED;
303    int i = 0;
304
305    while ((i8042_read_status() & I8042_STR_OBF) && (i++ < I8042_BUFFER_LENGTH)) {
306        usleep(10);
307        data = i8042_read_data();
308    }
309
310    return i;
311}
312
313static int i8042_command_data(uint8_t* param, int command) {
314    int retval = 0, i = 0;
315
316    for (i = 0; i < ((command >> 12) & 0xf); i++) {
317        if ((retval = i8042_wait_write())) {
318            break;
319        }
320
321        i8042_write_data(param[i]);
322    }
323
324    int expected = (command >> 8) & 0xf;
325    if (!retval) {
326        for (i = 0; i < expected; i++) {
327            if ((retval = i8042_wait_read())) {
328                xprintf("i8042: timeout reading; got %d bytes\n", i);
329                return i;
330            }
331
332            // TODO: do we need to distinguish keyboard and aux data?
333            param[i] = i8042_read_data();
334        }
335    }
336
337    return retval ? retval : expected;
338}
339
340static int i8042_command(uint8_t* param, int command) {
341    xprintf("i8042 ctl command 0x%04x\n", command & 0xffff);
342    int retval = 0;
343
344    retval = i8042_wait_write();
345    if (!retval) {
346        i8042_write_command(command & 0xff);
347    }
348
349    if (!retval) {
350        retval = i8042_command_data(param, command);
351    }
352
353    return retval;
354}
355
356static int i8042_selftest(void) {
357    uint8_t param;
358    int i = 0;
359    do {
360        if (i8042_command(&param, I8042_CMD_CTL_TEST) < 0) {
361            return -1;
362        }
363        if (param == 0x55)
364            return 0;
365        usleep(50 * 1000);
366    } while (i++ < 5);
367    return -1;
368}
369
370static int i8042_dev_command(uint8_t* param, int command) {
371    xprintf("i8042 dev command 0x%04x\n", command & 0xffff);
372    int retval = 0;
373
374    retval = i8042_wait_write();
375    if (!retval) {
376        i8042_write_data(command & 0xff);
377    }
378
379    if (!retval) {
380        retval = i8042_command_data(param, command);
381    }
382
383    return retval;
384}
385
386static int i8042_aux_command(uint8_t* param, int command) {
387    xprintf("i8042 aux command\n");
388    int retval = 0;
389
390    retval = i8042_wait_write();
391    if (!retval) {
392        i8042_write_command(I8042_CMD_CTL_AUX);
393    }
394
395    if (!retval) {
396        return i8042_dev_command(param, command);
397    }
398
399    return retval;
400}
401
402static void i8042_process_scode(i8042_device_t* dev, uint8_t scode, unsigned int flags) {
403    // is this a multi code sequence?
404    bool multi = (dev->last_code == 0xe0);
405
406    // update the last received code
407    dev->last_code = scode;
408
409    // save the key up event bit
410    bool key_up = !!(scode & 0x80);
411    scode &= 0x7f;
412
413    // translate the key based on our translation table
414    uint8_t usage;
415    if (multi) {
416        usage = pc_set1_usage_map_e0[scode];
417    } else {
418        usage = pc_set1_usage_map[scode];
419    }
420    if (!usage) return;
421
422    bool rollover = false;
423    if (is_kbd_modifier(usage)) {
424        switch (i8042_modifier_key(dev, usage, !key_up)) {
425        case MOD_EXISTS:
426            return;
427        case MOD_ROLLOVER:
428            rollover = true;
429            break;
430        case MOD_SET:
431        default:
432            break;
433        }
434    } else if (key_up) {
435        if (i8042_rm_key(dev, usage) != KEY_REMOVED) {
436            rollover = true;
437        }
438    } else {
439        switch (i8042_add_key(dev, usage)) {
440        case KEY_EXISTS:
441            return;
442        case KEY_ROLLOVER:
443            rollover = true;
444            break;
445        case KEY_ADDED:
446        default:
447            break;
448        }
449    }
450
451    //cprintf("i8042: scancode=0x%x, keyup=%u, multi=%u: usage=0x%x\n", scode, !!key_up, multi, usage);
452
453    const boot_kbd_report_t* report = rollover ? &report_err_rollover : &dev->report.kbd;
454    mtx_lock(&dev->lock);
455    if (dev->ifc) {
456        dev->ifc->io_queue(dev->cookie, (const uint8_t*)report, sizeof(*report));
457    }
458    mtx_unlock(&dev->lock);
459}
460
461static void i8042_process_mouse(i8042_device_t* dev, uint8_t data, unsigned int flags) {
462    switch (dev->last_code) {
463    case 0:
464        if (!(data & 0x08)) {
465            // The first byte always has bit 3 set, so skip this packet.
466            return;
467        }
468        dev->report.mouse.buttons = data;
469        break;
470    case 1: {
471        int state = dev->report.mouse.buttons;
472        int d = data;
473        dev->report.mouse.rel_x = d - ((state << 4) & 0x100);
474        break;
475        }
476    case 2: {
477        int state = dev->report.mouse.buttons;
478        int d = data;
479        // PS/2 maps the y-axis backwards so invert the rel_y value
480        dev->report.mouse.rel_y = ((state << 3) & 0x100) - d;
481        dev->report.mouse.buttons &= 0x7;
482
483        mtx_lock(&dev->lock);
484        if (dev->ifc) {
485            dev->ifc->io_queue(dev->cookie, (const uint8_t*)&dev->report.mouse,
486                               sizeof(dev->report.mouse));
487        }
488        mtx_unlock(&dev->lock);
489        memset(&dev->report.mouse, 0, sizeof(dev->report.mouse));
490        break;
491        }
492    }
493    dev->last_code = (dev->last_code + 1) % 3;
494}
495
496static int i8042_irq_thread(void* arg) {
497    i8042_device_t* device = (i8042_device_t*)arg;
498
499    // enable I/O port access
500    // TODO
501    zx_status_t status;
502    status = zx_ioports_request(get_root_resource(), I8042_COMMAND_REG, 1);
503    if (status)
504        return 0;
505    status = zx_ioports_request(get_root_resource(), I8042_DATA_REG, 1);
506    if (status)
507        return 0;
508
509    for (;;) {
510        status = zx_interrupt_wait(device->irq, NULL);
511        if (status != ZX_OK) {
512            break;
513        }
514        // keep handling status on the controller until no bits are set we care about
515        bool retry;
516        do {
517            retry = false;
518
519            uint8_t str = i8042_read_status();
520
521            // check for incoming data from the controller
522            // TODO: deal with potential race between IRQ1 and IRQ12
523            if (str & I8042_STR_OBF) {
524                uint8_t data = i8042_read_data();
525                // TODO: should we check (str & I8042_STR_AUXDATA) before
526                // handling this byte?
527                if (device->type == INPUT_PROTO_KBD) {
528                    i8042_process_scode(device, data,
529                                        ((str & I8042_STR_PARITY) ? I8042_STR_PARITY : 0) |
530                                        ((str & I8042_STR_TIMEOUT) ? I8042_STR_TIMEOUT : 0));
531                } else if (device->type == INPUT_PROTO_MOUSE) {
532                    i8042_process_mouse(device, data, 0);
533                }
534                retry = true;
535            }
536            // TODO check other status bits here
537        } while (retry);
538    }
539    return 0;
540}
541
542static zx_status_t i8042_setup(uint8_t* ctr) {
543    // enable I/O port access
544    zx_status_t status = zx_ioports_request(get_root_resource(), I8042_COMMAND_REG, 1);
545    if (status)
546        return status;
547    status = zx_ioports_request(get_root_resource(), I8042_DATA_REG, 1);
548    if (status)
549        return status;
550
551    // initialize hardware
552    i8042_command(NULL, I8042_CMD_CTL_KBD_DIS);
553    i8042_command(NULL, I8042_CMD_CTL_MOUSE_DIS);
554    i8042_flush();
555
556    if (i8042_command(ctr, I8042_CMD_CTL_RCTR) < 0)
557        return -1;
558
559    xprintf("i8042 controller register: 0x%02x\n", *ctr);
560    bool have_mouse = !!(*ctr & I8042_CTR_AUXDIS);
561    // disable IRQs and translation
562    *ctr &= ~(I8042_CTR_KBDINT | I8042_CTR_AUXINT | I8042_CTR_XLATE);
563    if (i8042_command(ctr, I8042_CMD_CTL_WCTR) < 0)
564        return -1;
565
566    if (i8042_selftest() < 0) {
567        printf("i8042 self-test failed\n");
568        return -1;
569    }
570
571    uint8_t resp = 0;
572    if (i8042_command(&resp, I8042_CMD_CTL_KBD_TEST) < 0)
573        return -1;
574    if (resp != 0x00) {
575        printf("i8042 kbd test failed: 0x%02x\n", resp);
576        return -1;
577    }
578    if (have_mouse) {
579        resp = 0;
580        if (i8042_command(&resp, I8042_CMD_CTL_MOUSE_TEST) < 0)
581            return -1;
582        if (resp != 0x00) {
583            printf("i8042 mouse test failed: 0x%02x\n", resp);
584            return -1;
585        }
586    }
587    return ZX_OK;
588}
589
590static void i8042_identify(int (*cmd)(uint8_t* param, int command)) {
591    uint8_t resp[3];
592    if (cmd(resp, I8042_CMD_SCAN_DIS) < 0) return;
593    resp[0] = 0;
594    int ident_sz = cmd(resp, I8042_CMD_IDENTIFY);
595    if (ident_sz < 0) return;
596    printf("i8042 device ");
597    switch (ident_sz) {
598    case 1:
599        printf("(unknown)");
600        break;
601    case 2:
602        printf("0x%02x", resp[1]);
603        break;
604    case 3:
605        printf("0x%02x 0x%02x", resp[1], resp[2]);
606        break;
607    default:
608        printf("failed to respond to IDENTIFY");
609    }
610    printf("\n");
611    cmd(resp, I8042_CMD_SCAN_EN);
612}
613
614static zx_status_t i8042_query(void* ctx, uint32_t options, hid_info_t* info) {
615    i8042_device_t* i8042 = ctx;
616    info->dev_num = i8042->type;  // use the type for the device number for now
617    info->dev_class = i8042->type;
618    info->boot_device = true;
619    return ZX_OK;
620}
621
622static zx_status_t i8042_start(void* ctx, hidbus_ifc_t* ifc, void* cookie) {
623    i8042_device_t* i8042 = ctx;
624    mtx_lock(&i8042->lock);
625    if (i8042->ifc != NULL) {
626        mtx_unlock(&i8042->lock);
627        return ZX_ERR_ALREADY_BOUND;
628    }
629    i8042->ifc = ifc;
630    i8042->cookie = cookie;
631    mtx_unlock(&i8042->lock);
632    return ZX_OK;
633}
634
635static void i8042_stop(void* ctx) {
636    i8042_device_t* i8042 = ctx;
637    mtx_lock(&i8042->lock);
638    i8042->ifc = NULL;
639    i8042->cookie = NULL;
640    mtx_unlock(&i8042->lock);
641}
642
643static zx_status_t i8042_get_descriptor(void* ctx, uint8_t desc_type,
644        void** data, size_t* len) {
645    if (data == NULL || len == NULL) {
646        return ZX_ERR_INVALID_ARGS;
647    }
648
649    if (desc_type != HID_DESC_TYPE_REPORT) {
650        return ZX_ERR_NOT_FOUND;
651    }
652
653    i8042_device_t* device = ctx;
654    const uint8_t* buf = NULL;
655    size_t buflen = 0;
656    if (device->type == INPUT_PROTO_KBD) {
657        buf = (void*)&kbd_hid_report_desc;
658        buflen = sizeof(kbd_hid_report_desc);
659    } else if (device->type == INPUT_PROTO_MOUSE) {
660        buf = (void*)&mouse_hid_report_desc;
661        buflen = sizeof(mouse_hid_report_desc);
662    } else {
663        return ZX_ERR_NOT_SUPPORTED;
664    }
665
666    *data = malloc(buflen);
667    *len = buflen;
668    memcpy(*data, buf, buflen);
669    return ZX_OK;
670}
671
672static zx_status_t i8042_get_report(void* ctx, uint8_t rpt_type, uint8_t rpt_id,
673        void* data, size_t len, size_t* out_len) {
674    return ZX_ERR_NOT_SUPPORTED;
675}
676
677static zx_status_t i8042_set_report(void* ctx, uint8_t rpt_type, uint8_t rpt_id,
678        void* data, size_t len) {
679    return ZX_ERR_NOT_SUPPORTED;
680}
681
682static zx_status_t i8042_get_idle(void* ctx, uint8_t rpt_type, uint8_t* duration) {
683    return ZX_ERR_NOT_SUPPORTED;
684}
685
686static zx_status_t i8042_set_idle(void* ctx, uint8_t rpt_type, uint8_t duration) {
687    return ZX_OK;
688}
689
690static zx_status_t i8042_get_protocol(void* ctx, uint8_t* protocol) {
691    return ZX_ERR_NOT_SUPPORTED;
692}
693
694static zx_status_t i8042_set_protocol(void* ctx, uint8_t protocol) {
695    return ZX_OK;
696}
697
698static hidbus_protocol_ops_t hidbus_ops = {
699    .query = i8042_query,
700    .start = i8042_start,
701    .stop = i8042_stop,
702    .get_descriptor = i8042_get_descriptor,
703    .get_report = i8042_get_report,
704    .set_report = i8042_set_report,
705    .get_idle = i8042_get_idle,
706    .set_idle = i8042_set_idle,
707    .get_protocol = i8042_get_protocol,
708    .set_protocol = i8042_set_protocol,
709};
710
711static void i8042_cleanup_irq_thread(i8042_device_t* dev) {
712    zx_interrupt_destroy(dev->irq);
713    thrd_join(dev->irq_thread, NULL);
714    zx_handle_close(dev->irq);
715}
716
717static void i8042_release(void* ctx) {
718    i8042_device_t* i8042 = ctx;
719    i8042_cleanup_irq_thread(i8042);
720    free(i8042);
721}
722
723static zx_protocol_device_t i8042_dev_proto = {
724    .version = DEVICE_OPS_VERSION,
725    .release = i8042_release,
726};
727
728static zx_status_t i8042_dev_init(i8042_device_t* dev, const char* name, zx_device_t* parent) {
729    // enable device port
730    int cmd = dev->type == INPUT_PROTO_KBD ?
731        I8042_CMD_CTL_KBD_DIS : I8042_CMD_CTL_MOUSE_DIS;
732    i8042_command(NULL, cmd);
733
734    // TODO: use identity to determine device type, rather than assuming aux ==
735    // mouse
736    i8042_identify(dev->type == INPUT_PROTO_KBD ?
737            i8042_dev_command : i8042_aux_command);
738
739    cmd = dev->type == INPUT_PROTO_KBD ?
740        I8042_CMD_CTL_KBD_EN : I8042_CMD_CTL_MOUSE_EN;
741    i8042_command(NULL, cmd);
742
743    uint32_t interrupt = dev->type == INPUT_PROTO_KBD ?
744        ISA_IRQ_KEYBOARD : ISA_IRQ_MOUSE;
745
746    zx_status_t status = zx_interrupt_create(get_root_resource(), interrupt,
747                        ZX_INTERRUPT_REMAP_IRQ, &dev->irq);
748    if (status != ZX_OK) {
749        return status;
750    }
751
752        // create irq thread
753    const char* tname = dev->type == INPUT_PROTO_KBD ?
754        "i8042-kbd-irq" : "i8042-mouse-irq";
755    int ret = thrd_create_with_name(&dev->irq_thread, i8042_irq_thread, dev, tname);
756    if (ret != thrd_success) {
757        zx_handle_close(dev->irq);
758        return ZX_ERR_BAD_STATE;
759    }
760
761    device_add_args_t args = {
762        .version = DEVICE_ADD_ARGS_VERSION,
763        .name = name,
764        .ctx = dev,
765        .ops = &i8042_dev_proto,
766        .proto_id = ZX_PROTOCOL_HIDBUS,
767        .proto_ops = &hidbus_ops,
768    };
769
770    status = device_add(parent, &args, NULL);
771    if (status != ZX_OK) {
772        i8042_cleanup_irq_thread(dev);
773    }
774    return status;
775}
776
777static int i8042_init_thread(void* arg) {
778    zx_device_t* parent = arg;
779    uint8_t ctr = 0;
780    zx_status_t status = i8042_setup(&ctr);
781    if (status != ZX_OK) {
782        return status;
783    }
784
785    bool have_mouse = !!(ctr & I8042_CTR_AUXDIS);
786
787    // turn on translation
788    ctr |= I8042_CTR_XLATE;
789
790    // enable devices and irqs
791    ctr &= ~I8042_CTR_KBDDIS;
792    ctr |= I8042_CTR_KBDINT;
793    if (have_mouse) {
794        ctr &= ~I8042_CTR_AUXDIS;
795        ctr |= I8042_CTR_AUXINT;
796    }
797
798    if (i8042_command(&ctr, I8042_CMD_CTL_WCTR) < 0)
799        return -1;
800
801    // create keyboard device
802    i8042_device_t* kbd_device = calloc(1, sizeof(i8042_device_t));
803    if (!kbd_device)
804        return ZX_ERR_NO_MEMORY;
805
806    mtx_init(&kbd_device->lock, mtx_plain);
807    kbd_device->type = INPUT_PROTO_KBD;
808    status = i8042_dev_init(kbd_device, "i8042-keyboard", parent);
809    if (status != ZX_OK) {
810        free(kbd_device);
811    }
812
813    // Mouse
814    if (have_mouse) {
815        i8042_device_t* mouse_device = NULL;
816        mouse_device = calloc(1, sizeof(i8042_device_t));
817        if (mouse_device) {
818            mtx_init(&mouse_device->lock, mtx_plain);
819            mouse_device->type = INPUT_PROTO_MOUSE;
820            status = i8042_dev_init(mouse_device, "i8042-mouse", parent);
821            if (status != ZX_OK) {
822                free(mouse_device);
823            }
824        }
825    }
826
827    xprintf("initialized i8042 driver\n");
828
829    return ZX_OK;
830}
831
832static zx_status_t i8042_bind(void* ctx, zx_device_t* parent) {
833    thrd_t t;
834    int rc = thrd_create_with_name(&t, i8042_init_thread, parent, "i8042-init");
835    return rc;
836}
837
838static zx_driver_ops_t i8042_driver_ops = {
839    .version = DRIVER_OPS_VERSION,
840    .bind = i8042_bind,
841};
842
843ZIRCON_DRIVER_BEGIN(i8042, i8042_driver_ops, "zircon", "0.1", 6)
844    BI_ABORT_IF(NE, BIND_PROTOCOL, ZX_PROTOCOL_ACPI),
845    BI_GOTO_IF(NE, BIND_ACPI_HID_0_3, 0x504e5030, 0), // PNP0303\0
846    BI_MATCH_IF(EQ, BIND_ACPI_HID_4_7, 0x33303300),
847    BI_LABEL(0),
848    BI_ABORT_IF(NE, BIND_ACPI_CID_0_3, 0x504e5030), // PNP0303\0
849    BI_MATCH_IF(EQ, BIND_ACPI_CID_4_7, 0x33303300),
850ZIRCON_DRIVER_END(i8042)
851