From 22397d6ee5f5fb1e60f28120fcb18c431741f974 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Mon, 17 Mar 2014 18:42:15 +0100 Subject: unifying: implement basic HID processing For now, the receiver has always a mouse and keyboard paired. Set_Idle, Get_Idle, Get_Protocol and Set_Protocol support interface-specific devices. Interrupt data for the HID mode are also implemented. Signed-off-by: Peter Wu --- hw/usb/dev-unifying.c | 165 +++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 143 insertions(+), 22 deletions(-) diff --git a/hw/usb/dev-unifying.c b/hw/usb/dev-unifying.c index 68e2928b60..c525ddb29c 100644 --- a/hw/usb/dev-unifying.c +++ b/hw/usb/dev-unifying.c @@ -33,15 +33,33 @@ #include "hw/usb.h" #include "hw/usb/desc.h" #include "dump.h" +#include "hw/input/hid.h" + +/* HID requests ((bmRequestType << 8) | bRequest) */ +#define HID_GET_REPORT 0xa101 +#define HID_GET_IDLE 0xa102 +#define HID_GET_PROTOCOL 0xa103 +/* 0x04-0x08 Reserved */ +#define HID_SET_REPORT 0x2109 +#define HID_SET_IDLE 0x210a +#define HID_SET_PROTOCOL 0x210b + +/* interface numbers (also used for array indices) */ +enum { + IFACE_KBD, /* keyboard */ + IFACE_MSE, /* mouse; multimedia, power, media center buttons */ + IFACE_DJ, /* DJ mode */ +}; typedef struct USBLtunifyState { USBDevice dev; - USBEndpoint *intr; + USBEndpoint *intr[3]; /* interfaces (keyboard, mouse, DJ) */ UsbDumpState *usb_dump_state; enum { LTUNIFY_MODE_HID = 1, LTUNIFY_MODE_DJ = 2 } mode; + HIDState hid[2]; /* HID devices (keyboard, mouse) */ } USBLtunifyState; /* descriptors are retrieved with usbhid-dump (need to unbind interfaces from @@ -348,11 +366,78 @@ static const USBDesc desc_ltunify = { .str = desc_strings, }; +/* Called when the host mouse or keyboard generates an event */ +static void usb_ltunify_hid_event(HIDState *hs) +{ + USBLtunifyState *s; + uint8_t ifnum; + + if (hs->kind == HID_KEYBOARD) { + ifnum = IFACE_KBD; + } else if (hs->kind == HID_MOUSE) { + ifnum = IFACE_MSE; + } else { + return; + } + + s = container_of(hs, USBLtunifyState, hid[ifnum]); + usb_wakeup(s->intr[ifnum], 0); +} + static void usb_ltunify_handle_reset(USBDevice *dev) { USBLtunifyState *s = (USBLtunifyState *) dev; s->mode = LTUNIFY_MODE_HID; + hid_reset(&s->hid[IFACE_KBD]); + hid_reset(&s->hid[IFACE_MSE]); +} + +static void usb_ltunify_handle_control_hid(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + USBLtunifyState *s = (USBLtunifyState *) dev; + HIDState *hs = &s->hid[index]; + + switch (request) { + case HID_GET_REPORT: + // report_type = (uint8_t) (value >> 8); + // report_id = (uint8_t) value; + // length + // data + break; + case HID_SET_REPORT: + // report_type = (uint8_t) (value >> 8); + // report_id = (uint8_t) value; + // length + // data + break; + case HID_GET_IDLE: + /* Idle rates for specific Report IDs should be implemented by checking + * (uint8_t) value (Report ID) */ + data[0] = hs->idle; + p->actual_length = 1; + break; + case HID_SET_IDLE: + /* report_id = (uint8_t) value; // 0 is a wildcard for all reports */ + hs->idle = (uint8_t) (value >> 8); + hid_set_next_idle(hs); + if (hs->kind == HID_MOUSE) { + /* start accepting pointer events if not already */ + hid_pointer_activate(hs); + } + break; + case HID_GET_PROTOCOL: + data[0] = hs->protocol; + p->actual_length = 1; + break; + case HID_SET_PROTOCOL: + hs->protocol = data[0]; + break; + default: + p->status = USB_RET_STALL; + break; + } } static void usb_ltunify_handle_control(USBDevice *dev, USBPacket *p, @@ -400,51 +485,80 @@ static void usb_ltunify_handle_control(USBDevice *dev, USBPacket *p, p->actual_length = desc_len; } } - break; - default: + goto data_ready; + } + + if (index >= 0 && index <= 1) { + /* HID interface for mouse and keyboard */ + usb_ltunify_handle_control_hid(dev, p, request, value, index, length, data); + } else { p->status = USB_RET_STALL; - break; } data_ready: usb_dump_complete(s->usb_dump_state, p); } -/* handle interrupt transfers (this device does not have a bulk/iso endpoint */ -static void usb_ltunify_handle_data(USBDevice *dev, USBPacket *p) +static void usb_ltunify_handle_datain_hid(USBDevice *dev, USBPacket *p) { -#if 0 USBLtunifyState *s = (USBLtunifyState *) dev; + uint8_t ifnum = p->ep->nr == 1 ? IFACE_KBD : IFACE_MSE; + HIDState *hs = &s->hid[ifnum]; uint8_t buf[p->iov.size]; int len = 0; + if (hs->kind == HID_MOUSE) { + /* start accepting pointer events if not already */ + hid_pointer_activate(hs); + } + + if (!hid_has_events(hs)) { + p->status = USB_RET_NAK; + return; + } + + hid_set_next_idle(hs); + + assert(hs->kind == HID_MOUSE || hs->kind == HID_KEYBOARD); + if (hs->kind == HID_MOUSE) { + len = hid_pointer_poll(hs, buf, p->iov.size); + } else if (hs->kind == HID_KEYBOARD) { + len = hid_keyboard_poll(hs, buf, p->iov.size); + } + + usb_dump_complete_data(s->usb_dump_state, p, buf, len); + usb_packet_copy(p, buf, len); + usb_dump_submit(s->usb_dump_state, p); +} + +/* handle interrupt transfers (this device does not have a bulk/iso endpoint */ +static void usb_ltunify_handle_data(USBDevice *dev, USBPacket *p) +{ switch (p->pid) { case USB_TOKEN_IN: - if (p->ep->nr == 1) { - if (!(s->changed || s->idle)) { - p->status = USB_RET_NAK; - return; - } - s->changed = 0; - if (s->mode == LTUNIFY_MODE_HID) - len = usb_mouse_poll(s, buf, p->iov.size); - else if (s->mode == LTUNIFY_MODE_LTUNIFY) - len = usb_ltunify_poll(s, buf, p->iov.size); - usb_packet_copy(p, buf, len); - break; + /* keyboard or mouse */ + if (p->ep->nr == 1 || p->ep->nr == 2) { + usb_ltunify_handle_datain_hid(dev, p); + return; + } else if (p->ep->nr == 3) { + /* TODO: handle DJ mode */ + } else { + abort(); } /* Fall through. */ case USB_TOKEN_OUT: default: p->status = USB_RET_STALL; } -#endif } static void usb_ltunify_handle_destroy(USBDevice *dev) { USBLtunifyState *s = (USBLtunifyState *) dev; + hid_free(&s->hid[IFACE_MSE]); + hid_free(&s->hid[IFACE_KBD]); + if (s->usb_dump_state) { usb_dump_cleanup(s->usb_dump_state); g_free(s->usb_dump_state); @@ -462,8 +576,15 @@ static int usb_ltunify_initfn(USBDevice *dev) // init device descriptors, etc. usb_desc_init(dev); - // TODO: is EP1 really wanted here? What about EP2 or EP3? - s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1); + /* retrieve interrupt endpoints for each interface */ + s->intr[IFACE_KBD] = usb_ep_get(dev, USB_TOKEN_IN, 1); + s->intr[IFACE_MSE] = usb_ep_get(dev, USB_TOKEN_IN, 2); + s->intr[IFACE_DJ] = usb_ep_get(dev, USB_TOKEN_IN, 3); + + /* the receiver supports multiple HID devices. Let's load some even if not + * all of them are paired. */ + hid_init(&s->hid[IFACE_KBD], HID_KEYBOARD, usb_ltunify_hid_event); + hid_init(&s->hid[IFACE_MSE], HID_MOUSE, usb_ltunify_hid_event); // TODO: API sucks... s->usb_dump_state = usb_dump_init_alloc("/tmp/usbdump.pcap"); -- cgit v1.2.1