summaryrefslogtreecommitdiff
path: root/fw-update.c
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2014-12-14 23:09:00 +0100
committerPeter Wu <peter@lekensteyn.nl>2014-12-14 23:09:00 +0100
commitc97ff4308ace592d1a9ced27b4598d09fc969e38 (patch)
tree2f87b1a18b5268a94f4c3aa9403e591582c16903 /fw-update.c
parente922ec016555b743a52312df3ad98e4150b05c79 (diff)
downloadt650-dfu-c97ff4308ace592d1a9ced27b4598d09fc969e38.tar.gz
Fix FW writing
The magic packet should not be treated specially. Added reverse-engineered docs
Diffstat (limited to 'fw-update.c')
-rw-r--r--fw-update.c106
1 files changed, 74 insertions, 32 deletions
diff --git a/fw-update.c b/fw-update.c
index e3e67b4..70f5e10 100644
--- a/fw-update.c
+++ b/fw-update.c
@@ -99,7 +99,6 @@ static bool match_notif(HidppMessage *msg, void *userdata) {
bool fw_update(int fd, uint8_t device_index, uint8_t *fw, size_t fw_len) {
int r;
- int i;
bool b;
uint16_t version;
FeatureInfo feat_dfu;
@@ -202,46 +201,89 @@ bool fw_update(int fd, uint8_t device_index, uint8_t *fw, size_t fw_len) {
#define DFU_WRITE(_msg) \
do { \
r = hidpp10_request(fd, &_msg, NULL, NULL); \
- RETURN_IF_FAIL(r == 0, "Write E2 failed"); \
+ RETURN_IF_FAIL(r == 0, "Write E2 failed, r=%#04x", r); \
} while (0)
- // TODO: [ix 82 E2] send magic packet (+verify)
HidppMessage dfu_req;
- // 0X11 0X2 0X82 0Xe2 0X2 0X9 0X5 0X41 0X1 0X0 0X38 0X0 0X0 0X0 0X0 0X0
- // 11 02 82 e2 HID++ long, device index, SubID (Set_Reg_Long), Addr
- // 02 DFU upgrade signature packet command?
- // 09 05 ???
- // 41 01 Wireless PID (MSB LSB)
- // 00 38 New firmware version (build number)
- // 00 00 00 00 00 (padding)
- wpid = WPID_T650;
- for (i = 3; i >= 0; i--) {
- RETURN_IF_FAIL(i > 1, "Too many DFU magic attempts");
- dfu_req = DFU_INIT(0x02, 0x09, 0x05,
- wpid >> 8, (uint8_t) wpid,
- 0x00, 0x38);
+ /* Format (req): E2 [action] [firmware blob]
+ * Possible action for requests (from sw to dvc; it seems to be dependent on
+ * the responses received from the device):
+ * 02 indicates the begin of firmware (always sent by sw if the DFU
+ * Upgrade Signature bit (rsp.bit1) is set by dvc). ("even toggle
+ * bit" as it is the first fw block at index 0)
+ * 00/01 for even/odd fw block indices.
+ * 04 DFU failed, execute recovery procedure. (params are all 00)
+ * 08 DFU end (verify?). (params are all 00)
+ * If rsp.bit2 is set, then respond with the same result code and firmware
+ * blob in place of the action. Probably for this reason, the device sends
+ * result 12 instead of just 02 which would have an ambiguous meaning.
+ *
+ * Format (rsp): E2 [result]
+ * Sw expects the result to toggle, so if action is 01, then result is
+ * 00 and if action is 00, then result is 01. Bit layout for rsp:
+ * 7-4 f0 ignored (but hw must copy this)
+ * 3 08 set to signal a critical error (sw must abort with action 04)
+ * 2 04 Unidentified packet failure (sw must retry same fw data)
+ * 1 02 DFU Upgrade signature bit (sw must start with begin of fw)
+ * 0 01 toggle bit (dvc flips this bit for every response. If it
+ * doesn't, then Sw should warn and expect the next bit to be the
+ * complement of this one)
+ */
+ unsigned retries = 0;
+ // [ix 82 E2] send magic packet (+verify)
+ uint8_t action = 2;
+ uint8_t dvc_toggle = -1;
+ size_t fw_pos = 0;
+ printf("Starting DFU (%zi blocks)\n", fw_len / 15);
+ while (fw_pos < fw_len) {
+ // [ix 82 E2] send firmware (+check toggle)
+ dfu_req = DFU_INIT(action);
+ memcpy(&dfu_req.params_l[1], fw + fw_pos, 15);
DFU_WRITE(dfu_req);
+
/* verify response */
int code = dfu_req.params[0];
- if (code == 0x12) { /* Retry? */
- } else if (code == 0x01) { /* toggled? */
- /* assume that 0x12 wants us to retry, and 1 means success. */
+ PRWARN_IF_FAIL(dvc_toggle != (uint8_t) -1 && (code & 1) == dvc_toggle,
+ "Device did not toggle properly!");
+ dvc_toggle = (code & 1) ^ 1; /* toggle flag: inverse of previous resp */
+
+ if (code & 8) { /* Critical Error, abort! */
+ warnx("The Device does not accept this firmware code (Wrong Signature)");
break;
- } else {
- RETURN_IF_FAIL(code == 0x12 || code == 0x01,
- "Unexpected DFU resp %#04x", code);
+ } else if (code & 2) { /* DFU Upgrade signature, reset! */
+ if (retries++ < 12) {
+ warnx("DFU error at pos %#zx, retry %i\n", fw_pos, retries);
+ dvc_toggle = -1;
+ fw_pos = 0;
+ action = 2;
+ } else {
+ warnx("DFU error, not retrying anymore!");
+ break;
+ }
+ } else if (code & 4) { /* Unidentified packet failure, resend packet */
+ action = code; /* send this "tag"/result as "tag"/action */
+ /* TODO: maybe check for infinite loop? The official fw updater
+ * doesn't do this either though, stopped counting after 61
+ * retries... */
+ } else { /* fw packet ack. */
+ action = (fw_pos / 15) & 1; /* enable toggle bit for odd blocks */
+ fw_pos += 15; /* go to next fw block */
}
}
- // TODO: [ix 82 E2] send firmware (+check toggle)
- (void)fw, (void)fw_len;
-
- // TODO: [ix 82 E2] send verify (+check toggle)
- dfu_req = DFU_INIT(0x04);
- DFU_WRITE(dfu_req);
- RETURN_IF_FAIL(dfu_req.params[0] == 0x01, "DFU verification failure?");
+ if (fw_pos == fw_len) { /* DFU success */
+ // [ix 82 E2] send verify (+check toggle)
+ dfu_req = DFU_INIT(8); /* verify, write, or whatever */
+ DFU_WRITE(dfu_req);
+ PRWARN_IF_FAIL((dfu_req.params[0] & ~1) == 0x04,
+ "DFU verification failure?");
+ } else { /* DFU failed */
+ warnx("DFU failed, executing recovery procedure");
+ dfu_req = DFU_INIT(4);
+ DFU_WRITE(dfu_req);
+ }
- // TODO: [ff SHORT_REG req] send "XIT
+ // [ff SHORT_REG req] send "XIT
HidppMessage xit_req = {
.report_id = HIDPP_SHORT,
.device_index = 0xFF,
@@ -255,9 +297,9 @@ bool fw_update(int fd, uint8_t device_index, uint8_t *fw, size_t fw_len) {
.notif_id = NOTIF_DEV_CONNECT
};
hidpp10_request(fd, &xit_req, match_notif, &recon_data);
- RETURN_IF_FAIL(r == 0, "XIT failed");
+ RETURN_IF_FAIL(r == 0, "Failed to deactivate DFU mode");
- // TODO: [ix notif] expect re-connection (0x41, proto 5)
+ // [ix notif] expect re-connection (0x41, proto 5)
if (!recon_data.matched) {
b = hidpp_read_msg(fd, 10000, &conn_notif, match_notif, &recon_data);
RETURN_IF_FAIL(b, "Timeout waiting for re-connect");