From 198529586fff48da10e4c5d56c9a44db2b6a0a1d Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Wed, 19 Mar 2014 15:18:50 +0100 Subject: unifying: move mode to LHIdDevice, init/reset functionality WIP (partially untested). Handle input reports, respond with HID events if there are no queued output reports. Implemented input report processing for receiver: 0x80 (Switch and Keep-Alive), 0x81 (Get Paired Devices). Signed-off-by: Peter Wu --- hw/usb/dev-unifying.c | 11 +++- hw/usb/hid-logitech-dj.c | 157 +++++++++++++++++++++++++++++++++++++++++++++++ hw/usb/hid-logitech-dj.h | 16 +++-- 3 files changed, 177 insertions(+), 7 deletions(-) diff --git a/hw/usb/dev-unifying.c b/hw/usb/dev-unifying.c index e60a472ba7..e7e2bd600b 100644 --- a/hw/usb/dev-unifying.c +++ b/hw/usb/dev-unifying.c @@ -355,16 +355,21 @@ static void usb_ltunify_hid_event(HIDState *hs) } s = container_of(hs, USBLtunifyState, hid[ifnum]); - usb_wakeup(s->intr[ifnum], 0); + /* TODO: devices can get unpaired. Move HIDState to LHidDevice! */ + if (s->devices[ifnum].mode == LTUNIFY_MODE_HID) { + usb_wakeup(s->intr[ifnum], 0); + } else { + usb_wakeup(s->intr[IFACE_HIDPP], 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]); + hidpp_reset(s); } static void usb_ltunify_handle_control_hid(USBDevice *dev, USBPacket *p, @@ -569,6 +574,8 @@ static int usb_ltunify_initfn(USBDevice *dev) hid_init(&s->hid[IFACE_KBD], HID_KEYBOARD, usb_ltunify_hid_event); hid_init(&s->hid[IFACE_MSE], HID_MOUSE, usb_ltunify_hid_event); + hidpp_init(s); + // TODO: API sucks... s->usb_dump_state = usb_dump_init_alloc("/tmp/usbdump.pcap"); return 0; diff --git a/hw/usb/hid-logitech-dj.c b/hw/usb/hid-logitech-dj.c index 6b49e816c0..c421b6639f 100644 --- a/hw/usb/hid-logitech-dj.c +++ b/hw/usb/hid-logitech-dj.c @@ -79,6 +79,70 @@ static void hidpp_queue_output_report(USBLtunifyState *s, HidppMsg *msg) } } +static bool hidpp_device_available(USBLtunifyState *s, uint8_t device_index) +{ + /* TODO: consider unpaired / powered off */ + return device_index >= 1 && device_index <= MAX_DEVICES && + s->devices[device_index - 1].powered_on; +} + +static void hidpp_process_input_report(USBLtunifyState *s, HidppMsg msg) +{ + LHidDevice *hd; + int i; + uint8_t *parms = msg.dj_s.payload; + + /* reports for receiver */ + if (msg.device_index == 0xFF) { + switch (msg.dj_s.report_type) { + case 0x80: /* Switch and Keep-Alive */ + for (i = 0; i < MAX_DEVICES; i++) { + hd = &s->devices[i]; + if ((parms[0] >> i) & 1) { + hd->mode = LTUNIFY_MODE_DJ; + } else { + hd->mode = LTUNIFY_MODE_HID; + } + } + // not implemented: keep-alive timeout params[1]; + break; + case 0x81: /* Get Paired Devices */ + memset(&msg, 0, sizeof(msg)); + msg.report_id = DJ_SHORT; + msg.dj_s.report_type = 0x41; /* Device Paired notif */ + parms[0] = 1; /* more notifications will follow bit. */ + for (i = 0; i < MAX_DEVICES; i++) { + hd = &s->devices[i]; + if (!hidpp_device_available(s, i)) { + continue; + } + + msg.device_index = i + 1; + parms[1] = (uint8_t) hd->info.wireless_pid; + parms[2] = (uint8_t) (hd->info.wireless_pid >> 8); + parms[3] = (uint8_t) hd->info.report_types; + parms[4] = (uint8_t) (hd->info.report_types >> 8); + parms[5] = (uint8_t) (hd->info.report_types >> 16); + parms[6] = (uint8_t) (hd->info.report_types >> 24); + hidpp_queue_output_report(s, &msg); + } + /* end of notifications list */ + msg.device_index = 0x00; + parms[0] = 2; + memset(parms + 1, 0, 6); + hidpp_queue_output_report(s, &msg); + break; + default: + hidpp_queue_error(s, &msg, HIDPP_ERR_INVALID_SUBID); + } + } else if (hidpp_device_available(s, msg.device_index)) { + /* TODO: implement for devices */ + hidpp_queue_error(s, &msg, HIDPP_ERR_REQUEST_UNAVAILABLE); + } else { + hidpp_queue_error(s, &msg, HIDPP_ERR_RESOURCE_ERROR); + } +} + /* process a received report */ static void hidpp_set_report(USBDevice *dev, USBPacket *p, uint8_t *data, size_t len) { @@ -124,6 +188,54 @@ void usb_ltunify_handle_control_hidpp(USBDevice *dev, USBPacket *p, } } +static void hidpp_handle_hid(USBDevice *dev, USBPacket *p) +{ + USBLtunifyState *s = (USBLtunifyState *) dev; + DjMsgShort msg = { 0 }; + uint8_t *data = (uint8_t *) &msg; + uint8_t *hid_data = msg.payload; + int i; + + msg.report_id = DJ_SHORT; + + /* TODO: be more fair, right now the first device always takes all events, + * causing delays for other devices. */ + for (i = 0; i < MAX_DEVICES; i++) { + LHidDevice *hd = &s->devices[i]; + + if (!hd->info.device_type) { + continue; + } + + if (hd->hid->kind == HID_MOUSE) { + /* start accepting pointer events if not already */ + hid_pointer_activate(hd->hid); + } + + if (!hid_has_events(hd->hid)) { + continue; + } + + hid_set_next_idle(hd->hid); + + msg.device_index = i + 1; + if (hd->hid->kind == HID_MOUSE) { + msg.report_type = 0x02; + hid_pointer_poll(hd->hid, hid_data, sizeof(msg.payload)); + } else if (hd->hid->kind == HID_KEYBOARD) { + msg.report_type = 0x01; + hid_keyboard_poll(hd->hid, hid_data, sizeof(msg.payload)); + } + + usb_dump_complete_data(s->usb_dump_state, p, data, sizeof(msg)); + usb_packet_copy(p, data, sizeof(msg)); + usb_dump_submit(s->usb_dump_state, p); + return; + } + + p->status = USB_RET_NAK; +} + void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p) { USBLtunifyState *s = (USBLtunifyState *) dev; @@ -139,6 +251,7 @@ void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p) slot = s->input_queue.head; LQUEUE_INCR(s->input_queue, s->input_queue.head); s->input_queue.n--; + hidpp_process_input_report(s, s->input_queue.reports[slot]); } /* try to send a reply to a previously sent request if possible. */ @@ -154,6 +267,11 @@ void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p) msg = &s->output_queue.reports[slot]; } + if (msg == NULL) { + hidpp_handle_hid(dev, p); + return; + } + if (msg != NULL) { len = msg_report_length(msg); assert(len > 0); @@ -164,3 +282,42 @@ void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p) p->status = USB_RET_NAK; } } + +static void hidpp_init_device(USBLtunifyState *s, int device_index, int devtype) +{ + LHidDevice *hd = &s->devices[device_index]; + + memset(hd, 0, sizeof(*hd)); + + hd->info.device_type = devtype; + switch (devtype) { + case DEVTYPE_KEYBOARD: + hd->hid = &s->hid[IFACE_KBD]; + hd->info.report_types = 0x1a; /* Keyboard, Consumer Control, System Control */ + break; + case DEVTYPE_MOUSE: + hd->hid = &s->hid[IFACE_MSE]; + hd->info.report_types = 4; /* HID Collection: Mouse */ + break; + } + hd->mode = LTUNIFY_MODE_HID; + hd->powered_on = true; +} + +void hidpp_reset(USBLtunifyState *s) +{ + int i; + + for (i = 0; i < MAX_DEVICES; i++) { + LHidDevice *hd = &s->devices[i]; + if (hd->info.device_type) { + hidpp_init_device(s, i, hd->info.device_type); + } + } +} + +void hidpp_init(USBLtunifyState *s) +{ + hidpp_init_device(s, 1, DEVTYPE_KEYBOARD); + hidpp_init_device(s, 2, DEVTYPE_MOUSE); +} diff --git a/hw/usb/hid-logitech-dj.h b/hw/usb/hid-logitech-dj.h index bc4a044382..bcfe17cc7e 100644 --- a/hw/usb/hid-logitech-dj.h +++ b/hw/usb/hid-logitech-dj.h @@ -137,7 +137,7 @@ typedef struct { } LHidReceiver; typedef struct { - const struct { + struct { enum { DEVTYPE_KEYBOARD = 1, DEVTYPE_MOUSE, @@ -157,10 +157,17 @@ typedef struct { uint32_t serial; const char name[15]; uint8_t usability_info; /* bits 0..3 power switch location */ + uint32_t report_types; /* supported report types (e.g. mouse) */ /* TODO: feature set */ /* TODO: special mouse and key button mappings */ } info; /* static information */ + HIDState *hid; + + enum { + LTUNIFY_MODE_HID = 1, + LTUNIFY_MODE_DJ = 2 + } mode; bool powered_on; uint8_t report_interval; #define REPORTING_FLAG_BATTERY_STATUS (1 << 4) @@ -185,10 +192,6 @@ typedef struct USBLtunifyState { USBDevice dev; 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) */ /* queue for HID++ requests and responses */ @@ -219,4 +222,7 @@ void usb_ltunify_handle_control_hidpp(USBDevice *dev, USBPacket *p, /* handle control packets for interface 3 (HID++ / DJ) */ void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p); + +void hidpp_reset(USBLtunifyState *s); +void hidpp_init(USBLtunifyState *s); #endif -- cgit v1.2.1