summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2014-03-18 21:21:51 +0100
committerPeter Wu <peter@lekensteyn.nl>2014-03-18 21:21:51 +0100
commit3a5922cc11f73269bfcd762997efc2c029a3b585 (patch)
tree04f129f72b655e1976b0a9456a17b4f93fe9fe7c
parent0bb8d924cbcd02069eb64893a2c2d48a20631866 (diff)
downloadqemu-3a5922cc11f73269bfcd762997efc2c029a3b585.tar.gz
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 <peter@lekensteyn.nl>
-rw-r--r--hw/usb/Makefile.objs2
-rw-r--r--hw/usb/dev-unifying.c17
-rw-r--r--hw/usb/hid-logitech-dj.c145
-rw-r--r--hw/usb/hid-logitech-dj.h176
4 files changed, 326 insertions, 14 deletions
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 <peter@lekensteyn.nl>
+ *
+ * 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 <peter@lekensteyn.nl>
*
@@ -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