From de29db4632a64cc7c20eb626a1b17e41e50adb26 Mon Sep 17 00:00:00 2001 From: Peter Wu Date: Sun, 17 Aug 2014 23:12:01 +0200 Subject: WIP firmware reverse engineering Not touched since 7 April 2014. --- hw/usb/core.c | 8 ++ hw/usb/dev-unifying.c | 29 +++++++ hw/usb/hid-logitech-dj.c | 193 ++++++++++++++++++++++++++++++++++++++++-- hw/usb/hid-logitech-dj.h | 11 ++- hw/usb/hid-logitech-hidpp20.c | 7 +- 5 files changed, 240 insertions(+), 8 deletions(-) (limited to 'hw') 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; } -- cgit v1.2.1