From 3a5922cc11f73269bfcd762997efc2c029a3b585 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Tue, 18 Mar 2014 21:21:51 +0100 Subject: unifying: WIP for HID++ support Error queue is implemented, possible receiver and device properties are filled in (in the header). Signed-off-by: Peter Wu --- hw/usb/Makefile.objs | 2 +- hw/usb/dev-unifying.c | 17 ++--- hw/usb/hid-logitech-dj.c | 145 ++++++++++++++++++++++++++++++++++++++ hw/usb/hid-logitech-dj.h | 176 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 326 insertions(+), 14 deletions(-) create mode 100644 hw/usb/hid-logitech-dj.c diff --git a/hw/usb/Makefile.objs b/hw/usb/Makefile.objs index c4edcf3d35..f735867bd1 100644 --- a/hw/usb/Makefile.objs +++ b/hw/usb/Makefile.objs @@ -14,7 +14,7 @@ common-obj-y += dev-hub.o common-obj-y += dev-hid.o common-obj-$(CONFIG_USB_TABLET_WACOM) += dev-wacom.o common-obj-$(CONFIG_USB_LG_UNIFYING) += dump.o -common-obj-$(CONFIG_USB_LG_UNIFYING) += dev-unifying.o +common-obj-$(CONFIG_USB_LG_UNIFYING) += dev-unifying.o hid-logitech-dj.o common-obj-$(CONFIG_USB_STORAGE_BOT) += dev-storage.o common-obj-$(CONFIG_USB_STORAGE_UAS) += dev-uas.o common-obj-$(CONFIG_USB_AUDIO) += dev-audio.o diff --git a/hw/usb/dev-unifying.c b/hw/usb/dev-unifying.c index 25f1fe920e..e60a472ba7 100644 --- a/hw/usb/dev-unifying.c +++ b/hw/usb/dev-unifying.c @@ -36,15 +36,6 @@ #include "hw/input/hid.h" #include "hw/usb/hid-logitech-dj.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 - /* descriptors are retrieved with usbhid-dump (need to unbind interfaces from * usbhid) and prettified with hidrd 0.2 */ @@ -479,6 +470,9 @@ static void usb_ltunify_handle_control(USBDevice *dev, USBPacket *p, if (index == IFACE_KBD || index == IFACE_KBD) { /* HID interface for mouse and keyboard */ usb_ltunify_handle_control_hid(dev, p, request, value, index, length, data); + } else if (index == IFACE_HIDPP) { + /* Logitech Proprietary HID++ interface */ + usb_ltunify_handle_control_hidpp(dev, p, request, value, index, length, data); } else { p->status = USB_RET_STALL; } @@ -529,7 +523,8 @@ static void usb_ltunify_handle_data(USBDevice *dev, USBPacket *p) usb_ltunify_handle_datain_hid(dev, p); return; } else if (p->ep->nr == 3) { - /* TODO: handle DJ mode */ + usb_ltunify_handle_datain_hidpp(dev, p); + return; } else { abort(); } @@ -567,7 +562,7 @@ static int usb_ltunify_initfn(USBDevice *dev) /* 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); + s->intr[IFACE_HIDPP] = 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. */ diff --git a/hw/usb/hid-logitech-dj.c b/hw/usb/hid-logitech-dj.c new file mode 100644 index 0000000000..ab7db1be8a --- /dev/null +++ b/hw/usb/hid-logitech-dj.c @@ -0,0 +1,145 @@ +/* + * Logitech Unifying receiver emulation (HID++ protocol). + * + * Copyright (c) 2014 Peter Wu + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "hw/hw.h" +#include "ui/console.h" +#include "hw/usb.h" +#include "dump.h" +#include "hw/input/hid.h" +#include "hw/usb/hid-logitech-dj.h" + +/* Report IDs */ +enum { + HIDPP_SHORT = 0x10, /* 7 bytes */ + HIDPP_LONG = 0x11, /* 20 bytes */ + DJ_SHORT = 0x20, /* 15 bytes */ + DJ_LONG = 0x21, /* 32 bytes */ +}; + +/* returns the expected length of the report or 0 if invalid */ +static unsigned msg_report_length(HidppMsg *msg) +{ + switch (msg->report_id) { + case HIDPP_SHORT: return 7; + case HIDPP_LONG: return 20; + case DJ_SHORT: return 15; + case DJ_LONG: return 32; + default: return 0; + } +} + +static void hidpp_queue_error(USBLtunifyState *s, HidppMsg *msg, uint8_t err) +{ + HidppMsgShort *report; + unsigned slot; + + if (s->error_queue.n < LQUEUE_SIZE(s->error_queue)) { + slot = LQUEUE_WRAP(s->error_queue, s->queue.head + s->queue.n); + s->error_queue.n++; + report = &s->error_queue.reports[slot]; + report->device_index = msg->device_index; + report->sub_id = msg->hidpp_s.sub_id; + report->address = msg->hidpp_s.address; + report->value[0] = err; + report->value[1] = report->value[2] = 0; + } +} + +/* process a received report */ +static void hidpp_set_report(USBDevice *dev, USBPacket *p, uint8_t *data, size_t len) +{ + USBLtunifyState *s = DO_UPCAST(USBLtunifyState, dev, dev); + HidppMsg *msg = (HidppMsg *) data; + int report_len; + + if (len < sizeof(HidppMsgShort)) { + goto fail; + } + + report_len = msg_report_length(msg); + if (report_len > 0 && len >= report_len) { + if (s->queue.n < LQUEUE_SIZE(s->queue)) { + unsigned slot = LQUEUE_WRAP(s->queue, s->queue.head + s->queue.n); + s->queue.n++; + memcpy((uint8_t *) &s->queue.reports[slot], data, report_len); + } else { + /* queue full, cannot accept this report */ + hidpp_queue_error(s, msg, HIDPP_ERR_BUSY); + } + } else { +fail: + p->status = USB_RET_STALL; + } +} + +void usb_ltunify_handle_control_hidpp(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data) +{ + //USBLtunifyState *s = (USBLtunifyState *) dev; + + switch (request) { + case HID_GET_REPORT: + /* FIXME */ + break; + case HID_SET_REPORT: + hidpp_set_report(dev, p, data, length); + break; + default: + p->status = USB_RET_STALL; + break; + } +} + +void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p) +{ + USBLtunifyState *s = (USBLtunifyState *) dev; + int slot; + int len = 0; + HidppMsg *msg = NULL; + + + assert(p->pid == USB_TOKEN_IN); + assert(p->ep->nr == 3); + + /* try to send a reply to a previously sent request if possible. */ + if (s->error_queue.n > 0) { + slot = s->error_queue.head; + LQUEUE_INCR(s->error_queue, s->error_queue.head); + s->error_queue.n--; + msg = (HidppMsg *) &s->error_queue.reports[slot]; + } else if (s->queue.n > 0) { + slot = s->queue.head; + LQUEUE_INCR(s->queue, s->queue.head); + s->queue.n--; + msg = &s->queue.reports[slot]; + /* TODO: really process message instead of sending it back... */ + } + + if (msg != NULL) { + len = msg_report_length(msg); + assert(len > 0); + usb_dump_complete_data(s->usb_dump_state, p, (uint8_t *) msg, len); + usb_packet_copy(p, (uint8_t *) msg, len); + usb_dump_submit(s->usb_dump_state, p); + } +} diff --git a/hw/usb/hid-logitech-dj.h b/hw/usb/hid-logitech-dj.h index 4269457354..36b2c31e2e 100644 --- a/hw/usb/hid-logitech-dj.h +++ b/hw/usb/hid-logitech-dj.h @@ -1,5 +1,5 @@ /* - * Logitech Unifying recever emulation (DJ mode). + * Logitech Unifying receiver emulation (HID++ protocol). * * Copyright (c) 2014 Peter Wu * @@ -24,13 +24,163 @@ #ifndef HID_LOGITECH_DJ_H #define HID_LOGITECH_DJ_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 */ + IFACE_HIDPP, /* HID++, DJ */ +}; + +#define MAX_DEVICES 6 + + +/* report formats */ +typedef struct { + uint8_t report_id; + uint8_t device_index; + uint8_t sub_id; + uint8_t address; + uint8_t value[3]; +} HidppMsgShort; + +typedef struct { + uint8_t report_id; + uint8_t device_index; + uint8_t sub_id; + uint8_t address; + uint8_t value[16]; +} HidppMsgLong; + +typedef struct { + uint8_t report_id; + uint8_t device_index; + uint8_t report_type; + uint8_t payload[12]; +} DjMsgShort; + +typedef struct { + uint8_t report_id; + uint8_t device_index; + uint8_t report_type; + uint8_t payload[29]; +} DjMsgLong; + +/* generic HID++ or DJ report */ +typedef struct { + union { + struct { + uint8_t report_id; + uint8_t device_index; + }; + HidppMsgShort hidpp_s; + HidppMsgLong hidpp_l; + DjMsgShort dj_s; + DjMsgLong dj_l; + }; +} HidppMsg; + +/* information to generate an error output report */ +typedef struct { + uint8_t device_index; + uint8_t sub_id; + uint8_t address; +#define HIDPP_ERR_SUCCESS 0x00 +#define HIDPP_ERR_INVALID_SUBID 0x01 +#define HIDPP_ERR_INVALID_ADDRESS 0x02 +#define HIDPP_ERR_INVALID_VALUE 0x03 +#define HIDPP_ERR_CONNECT_FAIL 0x04 +#define HIDPP_ERR_TOO_MANY_DEVICES 0x05 +#define HIDPP_ERR_ALREADY_EXISTS 0x06 +#define HIDPP_ERR_BUSY 0x07 +#define HIDPP_ERR_UNKNOWN_DEVICE 0x08 +#define HIDPP_ERR_RESOURCE_ERROR 0x09 +#define HIDPP_ERR_REQUEST_UNAVAILABLE 0x0A +#define HIDPP_ERR_INVALID_PARAM_VALUE 0x0B +#define HIDPP_ERR_WRONG_PIN_CODE 0x0C + uint8_t error; +} HidppError; + + +/* device and receiver info */ +struct firmware_version { + uint8_t fw_major; + uint8_t fw_minor; + uint16_t fw_build; + /* boot loader */ + uint8_t bl_major; + uint8_t bl_minor; }; +typedef struct { + const struct { + uint32_t serial; + struct firmware_version version; + } info; /* static information */ + +#define REPORTING_FLAG_WIRELESS_NOTIFS 0 +#define REPORTING_FLAG_SOFTWARE_PRESENT (1 << 3) + int reporting_flags; + uint8_t activity_counter[MAX_DEVICES]; + /* TODO: pairing lock open or closed (+ timeout) */ + /* TODO: connected devices */ + /* TODO: device firmware upgrade things? */ +} LHidReceiver; + +typedef struct { + const struct { + enum { + DEVTYPE_KEYBOARD = 1, + DEVTYPE_MOUSE, + DEVTYPE_NUMPAD, + DEVTYPE_PRESENTER, + /* 0x05..0x07 Reserved for future */ + DEVTYPE_TRACKBALL = 8, + DEVTYPE_TOUCHPAD, + /* 0x0A..0x0F Reserved */ + } device_type; + enum { + PROTO_UNIFYING = 4, + } protocol_type; + struct firmware_version version; + uint16_t protocol_version; /* HID++ protocol version */ + uint16_t wireless_pid; + uint32_t serial; + const char name[15]; + uint8_t usability_info; /* bits 0..3 power switch location */ + /* TODO: feature set */ + /* TODO: special mouse and key button mappings */ + } info; /* static information */ + + bool powered_on; + uint8_t report_interval; +#define REPORTING_FLAG_BATTERY_STATUS (1 << 4) + int reporting_flags; + + /* TODO: status (device seen or not, encrypted link) */ + struct { + uint8_t level; + enum { + BAT_STS_CHARGING, + /* TODO: charging, discharging, etc. */ + } status; + } battery; +} LHidDevice; + +/* helper macros for handling the report and error queue */ +#define LQUEUE_SIZE(q) ARRAY_SIZE((q).reports) +#define LQUEUE_WRAP(q, val) ((val) & (LQUEUE_SIZE((q)) - 1u)) +#define LQUEUE_INCR(q, var) ((var) = LQUEUE_WRAP((q), (var) + 1)) + typedef struct USBLtunifyState { USBDevice dev; USBEndpoint *intr[3]; /* interfaces (keyboard, mouse, DJ) */ @@ -40,6 +190,28 @@ typedef struct USBLtunifyState { LTUNIFY_MODE_DJ = 2 } mode; HIDState hid[2]; /* HID devices (keyboard, mouse) */ + + /* queue for HID++ requests and responses */ + struct { + HidppMsg reports[16]; + unsigned head; + unsigned n; + } queue; + /* receiver error queue (to be send): drop if full */ + struct { + HidppMsgShort reports[2]; + unsigned head; + unsigned n; + } error_queue; + LHidReceiver receiver; + LHidDevice devices[MAX_DEVICES]; } USBLtunifyState; + +/* handle control packets for interface 3 (HID++ / DJ) */ +void usb_ltunify_handle_control_hidpp(USBDevice *dev, USBPacket *p, + int request, int value, int index, int length, uint8_t *data); + +/* handle control packets for interface 3 (HID++ / DJ) */ +void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p); #endif -- cgit v1.2.1