summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2014-03-17 18:42:15 +0100
committerPeter Wu <peter@lekensteyn.nl>2014-03-17 18:42:15 +0100
commit22397d6ee5f5fb1e60f28120fcb18c431741f974 (patch)
tree08924bbe7154a5498f8ee35e9ff7abb66290c939
parentdba37f6f3f2b8cbec0cc4be630b63841d0a22d5c (diff)
downloadqemu-22397d6ee5f5fb1e60f28120fcb18c431741f974.tar.gz
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 <peter@lekensteyn.nl>
-rw-r--r--hw/usb/dev-unifying.c165
1 files 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");