blob: da2f0f4691c4f7a8bcebf4ee14277619a1072c3d [file] [log] [blame]
/*
* Copyright 2017, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
/**
* @brief USB Keyboard Driver
* @see USB HID spec, Appendix B
*/
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "../services.h"
#include "hid.h"
#include "usbkbd.h"
#define KBD_ENABLE_IRQS
#define KBD_REPEAT_RATE_MS 200
#define KBD_REPEAT_DELAY_MS 1000
/*
* Ring buffer for key logging
*/
struct ringbuf {
char b[100];
int head;
int tail;
int size;
};
static void rb_init(struct ringbuf *rb)
{
rb->head = 0;
rb->tail = 0;
rb->size = 0;
}
static int rb_produce(struct ringbuf *rb, const char *str, int size)
{
if (sizeof(rb->b) / sizeof(*rb->b) - rb->size >= size) {
while (size--) {
rb->b[rb->tail++] = *str++;
if (rb->tail == sizeof(rb->b) / sizeof(*rb->b)) {
rb->tail = 0;
}
rb->size++;
}
return 0;
} else {
return 1;
}
}
static int rb_consume(struct ringbuf *rb)
{
char c;
if (rb->size) {
c = rb->b[rb->head++];
if (rb->head == sizeof(rb->b) / sizeof(*rb->b)) {
rb->head = 0;
}
rb->size--;
return ((int)c) & 0xff;
}
return -1;
}
/*
* Keyboard driver
*/
#define KBDRPT_RATE ((KBD_REPEAT_RATE_MS + 3) / 4)
#define KBDRPT_DELAY ((KBD_REPEAT_DELAY_MS + 3) / 4)
#define KBD_PROTOCOL 1
#define KBD_KEYS_SIZE 8
#define KBDIND_CAPS 0x2
#define KBDIND_NUM 0x1
#define KBDIND_SCRL 0x4
struct usb_kbd_device {
struct usb_dev *udev;
struct usb_hid_device *hid;
struct endpoint *ep_int;
struct xact int_xact;
/// Indicator state. This is a pointer to our universal buffer at index 1
uint8_t ind;
/// Store old keys for repeat detection
uint8_t old_keys[KBD_KEYS_SIZE];
/// new keys is a pointer to our interrupt buffer
uint8_t *new_keys;
/// Cache the current repeat rate to avoid USB transfers
int repeat_rate;
/// ring buffer for characters waiting to be read by the application.
struct ringbuf rb;
};
#define KBDFN_CTRL 0x01
#define KBDFN_SHIFT 0x02
#define KBDFN_ALT 0x04
#define KBDFN_WIN 0x08
#define KBDFN_LEFT(FN) ((KBDFN_##FN) << 0)
#define KBDFN_RIGHT(FN) ((KBDFN_##FN) << 4)
#define KBDFN_TEST(FN, x) !!((x) & (KBDFN_LEFT(FN) | KBDFN_RIGHT(FN)))
static const char std_kcodes[] = {
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
/*0x00 */ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l',
/*0x10 */ 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1', '2',
/*0x20 */ '3', '4', '5', '6', '7', '8', '9', '0', 10, 27, 8, 9, ' ', '-', '=', '[',
/*0x30 */ ']', 92, 0, ';', 39, '`', ',', '.', '/'
};
static const char stdshift_kcodes[] = {
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
/*0x00 */ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L',
/*0x10 */ 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '!', '@',
/*0x20 */ '#', '$', '%', '^', '&', '*', '(', ')', 10, 27, 8, 9, ' ', '_', '+', '{',
/*0x30 */ '}', '|', 0, ':', 39, '~', '<', '>', '?'
};
static const char num_kcodes[] = {
/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
/*0x50 */ '/', '*', '-', '+', 10, '1', '2', '3', '4', '5', '6', '7',
/*0x60 */ '8', '9', '0', '.'
};
#define KBDKEY_NONE 0x00
#define KBDKEY_MASH 0x01
#define KBDKEY_NUMLOCK 0x53
#define KBDKEY_CAPSLOCK 0x39
#define KBDKEY_SCROLLLOCK 0x47
static int kbd_update_ind(struct usb_kbd_device *kbd)
{
int err;
err = usb_hid_set_report(kbd->hid, REPORT_OUTPUT, &kbd->ind, 1);
return err;
}
static int kbd_update_repeat_rate(struct usb_kbd_device *kbd)
{
ZF_LOGD("Changing rate to %dms\n", kbd->repeat_rate * 4);
return usb_hid_set_idle(kbd->hid, kbd->repeat_rate);
}
static int
kbd_irq_handler(void *token, enum usb_xact_status stat, int bytes_remaining)
{
struct usb_kbd_device *kbd = (struct usb_kbd_device *)token;
uint8_t afn;
uint8_t key;
int new_rate = -1;
char c;
int len;
/* Check the status */
if (stat != XACTSTAT_SUCCESS) {
ZF_LOGD("Received unsuccessful IRQ\n");
return 1;
}
len = kbd->int_xact.len - bytes_remaining;
if (len < 4) {
ZF_LOGD("Short read on INT packet (%d)\n", len);
return 1;
}
#if defined(KBDIRQ_DEBUG)
{
int i;
for (i = 0; i < len; i++) {
printf("[0x%02x]", kbd->new_keys[i]);
}
printf("\n");
}
#endif
/* Multiple key press. Ignore input */
if (kbd->new_keys[3] != KBDKEY_NONE) {
kbd->new_keys[2] = kbd->old_keys[2] = KBDKEY_NONE;
}
/* Read in key parameters */
afn = kbd->new_keys[0];
key = kbd->new_keys[2];
/* Handle repeat delay */
if (key == KBDKEY_NONE) {
/* No key pressed or someone is being a jerk - idle */
new_rate = 0;
} else if (kbd->old_keys[2] == key) {
/* Someone is holding down a key */
new_rate = KBDRPT_RATE;
} else {
/* Someone pressed a new key! Start repeat delay */
new_rate = KBDRPT_DELAY;
}
/* Adjust the idle delay if required */
if (new_rate != kbd->repeat_rate) {
kbd->repeat_rate = new_rate;
kbd_update_repeat_rate(kbd);
}
/* Process the key */
memcpy(kbd->old_keys, kbd->new_keys, KBD_KEYS_SIZE);
if (key == KBDKEY_NONE || key == KBDKEY_MASH) {
/* Ignore it */
} else if (key < 0x04) {
printf("<!0x%x>", key);
} else if (key < 0x39) {
char cl;
/* Decode the key */
c = std_kcodes[key - 0x04];
if (KBDFN_TEST(CTRL, afn) && c >= '@' && c <= '_') {
c -= '@';
} else if (KBDFN_TEST(SHIFT, afn)) {
c = stdshift_kcodes[key - 0x04];
}
/* Check and update for capslock */
cl = c | 0x20;
if (cl >= 'a' && cl <= 'z' && (kbd->ind & KBDIND_CAPS)) {
c ^= 0x20;
}
/* Register the character */
if (KBDFN_TEST(ALT, afn)) {
char str[2] = { 0x1B, c };
rb_produce(&kbd->rb, str, 2);
} else {
rb_produce(&kbd->rb, &c, 1);
}
} else if (key == KBDKEY_NUMLOCK) {
kbd->ind ^= KBDIND_NUM;
kbd_update_ind(kbd);
} else if (key == KBDKEY_CAPSLOCK) {
kbd->ind ^= KBDIND_CAPS;
kbd_update_ind(kbd);
} else if (key == KBDKEY_SCROLLLOCK) {
kbd->ind ^= KBDIND_SCRL;
kbd_update_ind(kbd);
} else if (key < 0x54) {
/* TODO handle these codes (see below) */
printf("<!0x%x>", key);
} else if (key < 0x64 && (kbd->ind & KBDIND_NUM)) {
c = num_kcodes[key - 0x54];
rb_produce(&kbd->rb, &c, 1);
} else if (key < 0x66) {
/* TODO find scan codes for these keys and keypad
* with no numlock */
#if 0
F1 - F12 3 a - 45
prntscrn / sysrq 46
pause / break 48
insert 49 home 4 a pgup 4 b delete 4 c end 4 d pgdwn 4e
right 4f left 50 down 51 up 52 macro 64 dropdown 65
#endif
printf("<!0x%x>", key);
} else {
printf("<!!0x%x>", key);
}
usbdev_schedule_xact(kbd->udev, kbd->ep_int, &kbd->int_xact, 1,
&kbd_irq_handler, kbd);
return 1;
}
static ssize_t
kbd_read(ps_chardevice_t *d, void *vdata, size_t bytes,
chardev_callback_t cb, void *token)
{
struct usb_kbd_device *kbd;
char *data;
int i;
kbd = (struct usb_kbd_device *)d->vaddr;
data = (char *)vdata;
for (i = 0; i < bytes; i++) {
int c;
c = rb_consume(&kbd->rb);
if (c >= 0) {
*data++ = c;
} else {
break;
}
}
return i;
}
int usb_kbd_driver_bind(usb_dev_t *usb_dev, struct ps_chardevice *cdev)
{
struct usb_kbd_device *kbd;
int err;
kbd = (struct usb_kbd_device*)usb_malloc(sizeof(struct usb_kbd_device));
if (!kbd) {
ZF_LOGF("Out of memory\n");
}
usb_dev->dev_data = (struct udev_priv*)kbd;
kbd->udev = usb_dev;
kbd->repeat_rate = 0;
rb_init(&kbd->rb);
kbd->hid = usb_hid_alloc(usb_dev);
if (kbd->hid->protocol != 1) {
ZF_LOGF("Not a keyboard: %d\n", kbd->hid->protocol);
}
/* Find endpoint */
kbd->ep_int = usb_dev->ep[kbd->hid->iface];
if (kbd->ep_int == NULL || kbd->ep_int->type != EP_INTERRUPT) {
ZF_LOGF("Endpoint not found\n");
}
ZF_LOGD("Configuring keyboard\n");
err = usb_hid_set_idle(kbd->hid, 0);
if (err < 0) {
ZF_LOGF("Keyboard initialisation error\n");
}
cdev->vaddr = kbd;
cdev->read = kbd_read;
/* Initialise LEDS */
kbd_update_ind(kbd);
/* Initialise IRQs */
if (kbd->ep_int->dir == EP_DIR_IN) {
kbd->int_xact.type = PID_IN;
} else {
kbd->int_xact.type = PID_OUT;
}
kbd->int_xact.len = kbd->ep_int->max_pkt;
err = usb_alloc_xact(usb_dev->dman, &kbd->int_xact, 1);
if (err) {
ZF_LOGF("Out of DMA memory\n");
}
kbd->new_keys = xact_get_vaddr(&kbd->int_xact);
#if defined(KBD_ENABLE_IRQS)
ZF_LOGD("Scheduling IRQS\n");
usbdev_schedule_xact(usb_dev, kbd->ep_int, &kbd->int_xact, 1,
&kbd_irq_handler, kbd);
#else
(void)kbd_irq_handler;
#endif
ZF_LOGD("Successfully initialised\n");
return 0;
}