summaryrefslogtreecommitdiff
path: root/hw/usb
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2014-08-17 23:12:01 +0200
committerPeter Wu <peter@lekensteyn.nl>2014-08-17 23:12:01 +0200
commitde29db4632a64cc7c20eb626a1b17e41e50adb26 (patch)
treeb09a92c303180556c83db1d64d65c2a1ea5b04b4 /hw/usb
parent505280bd9af458c712ec6d19afacdc192c6e02ab (diff)
downloadqemu-de29db4632a64cc7c20eb626a1b17e41e50adb26.tar.gz
WIP firmware reverse engineeringlogitech-unifying
Not touched since 7 April 2014.
Diffstat (limited to 'hw/usb')
-rw-r--r--hw/usb/core.c8
-rw-r--r--hw/usb/dev-unifying.c29
-rw-r--r--hw/usb/hid-logitech-dj.c193
-rw-r--r--hw/usb/hid-logitech-dj.h11
-rw-r--r--hw/usb/hid-logitech-hidpp20.c7
5 files changed, 240 insertions, 8 deletions
diff --git a/hw/usb/core.c b/hw/usb/core.c
index 67ba7d6018..328f2f926a 100644
--- a/hw/usb/core.c
+++ b/hw/usb/core.c
@@ -115,6 +115,10 @@ static void do_token_setup(USBDevice *s, USBPacket *p)
value = (s->setup_buf[3] << 8) | s->setup_buf[2];
index = (s->setup_buf[5] << 8) | s->setup_buf[4];
+ // TODO: per wiki.osdev.org/USB, if setup_len is 0, then there is no data
+ // stage and the direction must be ignored. It should always go to the
+ // status stage, expecting an IN token. Perhaps fake stuff and set
+ // setup_buf = (setup_buf[0] & ~USB_DIR_IN)? Is this actually a problem?
if (s->setup_buf[0] & USB_DIR_IN) {
usb_device_handle_control(s, p, request, value, index,
s->setup_len, s->data_buf);
@@ -351,6 +355,10 @@ static void usb_process_one(USBPacket *p)
*/
p->status = USB_RET_SUCCESS;
+ /* Default Control Pipe (EP 0) is a message pipe (not stream pipe), so there
+ * must be request (setup) / data / status stages. Flow is bi-directional.
+ * XXX: Can there be other endpoints using the control transfer that can
+ * also use these control data? */
if (p->ep->nr == 0) {
/* control pipe */
if (p->parameter) {
diff --git a/hw/usb/dev-unifying.c b/hw/usb/dev-unifying.c
index 9e3a487e68..2be1714902 100644
--- a/hw/usb/dev-unifying.c
+++ b/hw/usb/dev-unifying.c
@@ -645,9 +645,38 @@ static int usb_ltunify_initfn(USBDevice *dev)
return 0;
}
+#if 0
+static int usb_ltunify_post_load(void *opaque, int version_id)
+{
+ USBLtunifyState *s = opaque;
+
+ if (s->dev.remote_wakeup) {
+ hid_pointer_activate(&s->hid[IFACE_MSE]);
+ }
+
+ // TODO: capture file restart?
+
+ return 0;
+}
+#endif
+
static const VMStateDescription vmstate_usb_ltunify = {
.name = "usb-ltunify",
.unmigratable = 1,
+ /* TODO: find out what fields to migrate. Also, what about the dump
+ * functionality? */
+#if 0
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = usb_ltunify_post_load,
+ .fields = (VMStateField []) {
+ VMSTATE_USB_DEVICE(dev, USBLtunifyState),
+ VMSTATE_HID_KEYBOARD_DEVICE(hid[IFACE_KBD], USBLtunifyState),
+ VMSTATE_HID_POINTER_DEVICE(hid[IFACE_MSE], USBLtunifyState),
+ //VMSTATE_STRUCT(input_queue, USBLtunifyState, 1,
+ VMSTATE_END_OF_LIST()
+ }
+#endif
};
static Property usb_ltunify_properties[] = {
diff --git a/hw/usb/hid-logitech-dj.c b/hw/usb/hid-logitech-dj.c
index f5b736f6f3..ea187f1cb9 100644
--- a/hw/usb/hid-logitech-dj.c
+++ b/hw/usb/hid-logitech-dj.c
@@ -149,6 +149,120 @@ static void hidpp_process_version_request(USBLtunifyState *s, HidppMsgShort *msg
hidpp_queue_output_report(s, (HidppMsg *) msg);
}
+/* not in hidpp20 because it needs to queue an item */
+static void dj_send_notif_conn_status(USBLtunifyState *s, uint8_t device_index,
+ bool connected)
+{
+ DjMsgLong msg;
+
+ msg.report_id = DJ_SHORT;
+ msg.device_index = device_index;
+ msg.report_type = 0x42; /* Connection Status */
+ memset(msg.payload, 0, sizeof(msg.payload));
+ /* Connection Status: 0 (established), 1 (linkloss) */
+ msg.payload[0] = !connected;
+ hidpp_queue_output_report(s, (HidppMsg *) &msg);
+}
+
+static void lt_dfu_enter(USBLtunifyState *s, uint8_t device_index)
+{
+ LHidReceiver *rcvr = &s->receiver;
+ LHidDevice *hd;
+ int i;
+
+ if (rcvr->dfu.active) {
+ /* already in DFU mode, ignore request */
+ return;
+ }
+
+ rcvr->dfu.active = true;
+ rcvr->dfu.device_index = device_index;
+
+ /* TODO: clear queues as the receiver "restarts"? Reset report counters */
+
+ /* disconnect all other devices */
+ for (i = 0; i < MAX_DEVICES; i++) {
+ hd = &s->devices[i];
+ if (hd->powered_on && hd->mode == LTUNIFY_MODE_DJ &&
+ device_index - 1 != i) {
+ // NOTE: this is for the DJ enumerator, the updater doesn't care
+ // whether this is sent or not.
+ /* TODO: power off? */
+ dj_send_notif_conn_status(s, i + 1, false);
+ }
+ }
+}
+
+static bool lt_dfu_process(USBLtunifyState *s, HidppMsg *msg)
+{
+ //LHidDevice *hd = &s->devices[msg->device_index - 1];
+ HidppMsgLong *ml = &msg->hidpp_l;
+ unsigned req = (ml->report_id << 16) | (ml->sub_id << 8) | ml->address;
+ uint8_t action;
+ uint8_t *val = ml->value;
+
+ switch (req) {
+ case SET_LONG_REG(0xE2):
+ action = ml->value[0];
+ msg->report_id = HIDPP_SHORT;
+ memset(ml->value, 0, sizeof(ml->value));
+ switch (action) {
+ case 2: /* DFU upgrade signature packet */
+ *val = 1; // 0x12 12 is error/retry?
+ break;
+ case 1: /* odd fw data packets */
+ *val = 0;
+ break;
+ case 0: /* even fw data packets */
+ *val = 1;
+ break;
+ case 4: /* stop DFU */
+ *val = 0x12;
+ break;
+ case 8: /* Verify firmware result? */
+ // 1 does not trigger "did not toggle" error, but does not end DFU
+ // neither did 0 work.
+ // 2 should work, but it didn't.
+ // 4 should work
+ *val = 4 | 1;
+ // TODO: what to do? what to say?
+ break;
+ }
+ hidpp_queue_output_report(s, msg);
+ return true;
+ default:
+ /* unhandled */
+ return false;
+ }
+}
+
+static void lt_dfu_exit(USBLtunifyState *s)
+{
+ LHidReceiver *rcvr = &s->receiver;
+ HidppMsgShort msg;
+
+ /* disable DFU and notify of result */
+ rcvr->dfu.active = false;
+
+ msg.report_id = HIDPP_SHORT;
+ msg.device_index = rcvr->dfu.device_index;
+ msg.sub_id = 0x41; /* Device Connection Notification */
+ msg.params[0] = 5; /* protocol: DFU (?) */
+ memset(msg.params + 1, 0, 3);
+
+ if (rcvr->dfu.device_index) {
+ LHidDevice *hd = &s->devices[rcvr->dfu.device_index - 1];
+
+ hd->dfu.active = false;
+ msg.params[1] |= hd->info.device_type & 0xF;
+ msg.params[1] |= (!hd->powered_on << 6);
+ msg.params[2] = (uint8_t) hd->info.wireless_pid;
+ msg.params[3] = hd->info.wireless_pid >> 8;
+ }
+
+ hidpp_queue_output_report(s, (HidppMsg *) &msg);
+}
+
static void hidpp_process_receiver_report(USBLtunifyState *s, HidppMsg *msg)
{
LHidDevice *hd;
@@ -315,13 +429,27 @@ static void hidpp_process_receiver_report(USBLtunifyState *s, HidppMsg *msg)
hidpp_queue_error(s, msg, HIDPP_ERR_INVALID_VALUE);
}
break;
- case SET_REG(0xF0): /* Enter DFU mode?? */
- // 4c 54 02: "LT" + device index?
- // (followed by read f0)
- // 58 49 54: "XIT"
- // (followed by GET_LONG_REG(0xB3)(0, 0, 0))
+ case SET_REG(0xF0): /* Change DFU settings? */
+ if (!memcmp(parms, "LT", 2) && parms[2] >= 1 && parms[2] <= 6) {
+ lt_dfu_enter(s, parms[2]);
+ memset(parms, 0, 3);
+ hidpp_queue_output_report(s, msg);
+ } else if (!memcmp(parms, "XIT", 3)) { /* Exit DFU? */
+ lt_dfu_exit(s);
+ memset(parms, 0, 3);
+ hidpp_queue_output_report(s, msg);
+ } else {
+ hidpp_queue_error(s, msg, HIDPP_ERR_INVALID_VALUE);
+ }
break;
- case GET_REG(0xF0): /* Get DFU information? */
+ case GET_REG(0xF0): /* Get DFU status? */
+ memset(parms, 0, 3);
+ if (s->receiver.dfu.active) {
+ parms[0] = 'L';
+ parms[1] = 'T';
+ parms[2] = s->receiver.dfu.device_index;
+ }
+ hidpp_queue_output_report(s, msg);
break;
case GET_REG(0xF1):
hidpp_process_version_request(s, ms, &rcvr->info.version);
@@ -460,6 +588,19 @@ static void hidpp_process_device_hidpp20(USBLtunifyState *s, HidppMsg *msg)
Hidpp20Msg *func = (Hidpp20Msg *) msg;
int r;
+ if (hd->dfu.active) {
+ /* TODO: move this in hidpp_process_input_report or even further away? */
+ if (s->receiver.dfu.active) {
+ assert(msg->report_id == HIDPP_SHORT || msg->report_id == HIDPP_LONG);
+ // assert(hd);
+ if (lt_dfu_process(s, msg)) {
+ return;
+ }
+ }
+ hidpp_queue_error(s, msg, HIDPP_ERR_RESOURCE_ERROR);
+ return;
+ }
+
r = hidpp20_feature_call(hd, func);
/* note: if r == 0, then the function is void and there is no output */
if (r > 0) {
@@ -467,6 +608,10 @@ static void hidpp_process_device_hidpp20(USBLtunifyState *s, HidppMsg *msg)
} else if (r < 0) {
hidpp20_queue_error(s, func, -r);
}
+
+ if (hd->dfu.active) {
+ dj_send_notif_conn_status(s, msg->device_index, false);
+ }
}
@@ -538,6 +683,34 @@ void usb_ltunify_handle_control_hidpp(USBDevice *dev, USBPacket *p,
}
}
+/* TODO: remove this crappy hack */
+static void apply_button_hacks(USBLtunifyState *s)
+{
+ LHidDevice *hd;
+
+ hd = &s->devices[1]; /* hard-coded to T650 */
+ /* trigger toggle of power switch */
+ hd->powered_on = !hd->powered_on;
+ fprintf(stderr, "Powered on becomes: %i\n", hd->powered_on);
+ //hidpp_notif_device_connection(s, 2);
+ //dj_send_notif_conn_status(s, 2, hd->powered_on);
+
+ if (hd->powered_on) {
+ Hidpp20Msg msg = {
+ .report_id = HIDPP_LONG,
+ .device_index = 2, /* T650 hardcoded */
+ .feat_index = 6, /* 1D4B WirelessDeviceStatus */
+ .func = 0,
+ .params = {
+ 1, /* reconnection */
+ 1, /* sw reconf needed */
+ 1, /* power switch activated */
+ }
+ };
+ hidpp_queue_output_report(s, (HidppMsg*) &msg);
+ }
+}
+
static void hidpp_handle_hid(USBDevice *dev, USBPacket *p)
{
USBLtunifyState *s = (USBLtunifyState *) dev;
@@ -579,6 +752,8 @@ static void hidpp_handle_hid(USBDevice *dev, USBPacket *p)
p->status = USB_RET_NAK;
}
+bool HACK_toggle;
+
void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p)
{
USBLtunifyState *s = (USBLtunifyState *) dev;
@@ -589,6 +764,11 @@ void usb_ltunify_handle_datain_hidpp(USBDevice *dev, USBPacket *p)
assert(p->pid == USB_TOKEN_IN);
assert(p->ep->nr == 3);
+ if (HACK_toggle) {
+ apply_button_hacks(s);
+ HACK_toggle = false;
+ }
+
/* process input reports */
if (s->input_queue.n > 0) {
slot = s->input_queue.head;
@@ -730,4 +910,5 @@ void hidpp_init(USBLtunifyState *s)
hidpp_init_device(s, 1, WPID_K800);
}
hidpp_init_device(s, 2, WPID_T650);
+ //hidpp_init_device(s, 2, WPID_T400);
}
diff --git a/hw/usb/hid-logitech-dj.h b/hw/usb/hid-logitech-dj.h
index d0522b2a81..0dbf6c7943 100644
--- a/hw/usb/hid-logitech-dj.h
+++ b/hw/usb/hid-logitech-dj.h
@@ -172,7 +172,11 @@ typedef struct {
uint8_t activity_counter[MAX_DEVICES];
/* TODO: pairing lock open or closed (+ timeout) */
/* TODO: connected devices */
- /* TODO: device firmware upgrade things? */
+
+ struct {
+ bool active;
+ uint8_t device_index;
+ } dfu; /* device firmware upgrade thingeys */
} LHidReceiver;
/* defined in hid-logitech-hidpp20.c */
@@ -243,6 +247,11 @@ typedef struct {
uint8_t status;
} battery;
uint8_t activity_counter;
+
+ struct {
+ bool active;
+ int state;
+ } dfu;
} LHidDevice;
/* helper macros for handling the report and error queue */
diff --git a/hw/usb/hid-logitech-hidpp20.c b/hw/usb/hid-logitech-hidpp20.c
index 51e9596151..e3d451a1ef 100644
--- a/hw/usb/hid-logitech-hidpp20.c
+++ b/hw/usb/hid-logitech-hidpp20.c
@@ -271,8 +271,13 @@ static HIDPP20_FEATURE(feat_featureinfo)
static HIDPP20_FEATURE(feat_dfucontrol)
{
- return -HIDPP20_ERR_CODE_UNSUPPORTED; /* TODO: figure out */
switch (fn) {
+ case 0:
+ if (!memcmp(params, "DFU", 3)) {
+ hd->dfu.active = true;
+ return 0;
+ }
+ return -HIDPP20_ERR_CODE_INVALIDARGUMENT;
default:
return -HIDPP20_ERR_CODE_INVALID_FUNCTION_ID;
}