@@ -314,10 +317,243 @@ static ssize_t type_show(struct device *dev,
}
static DEVICE_ATTR_RO(type);
+static void exc3000_generate_unlock_response(u8 *challenge, u8 *response)
+{
+ u8 op, rot, sum;
+ int i;
+
+ op = challenge[0] + challenge[3];
+ rot = challenge[1] + challenge[2];
+ sum = challenge[0] + challenge[1] + challenge[2] + challenge[3];
+
+ for (i = 0; i < 4; i++) {
+ if ((op >> i) & 0x1) {
+ response[i] = sum + challenge[(rot + i) & 0x3];
+ } else {
+ response[i] = sum - challenge[(rot + i) & 0x3];
+ }
+ }
+}
+
+static int exc3000_firmware_update(struct exc3000_data *data,
+ const struct firmware *fw)
+{
+ struct device *dev = &data->client->dev;
+ u8 resp[EXC3000_LEN_FRAME];
+ int ret, i;
+
+ dev_info(dev, "starting firmware update\n");
+
+ /* 1: check device state */
+ ret = exc3000_vendor_data_request(data, (u8[]){0x39, 0x02}, 2, resp, 1);
+ if (ret < 0)
+ goto out;
+
+ /* 2: switch state from app to bootloader mode if necessary */
+ if (!resp[2] && !resp[3]) {
+ u8 unlock_req[6] = { 0x3a, 0xfc };
+
+ dev_dbg(dev, "device in app mode, switching to bootloader\n");
+
+ /* 2.1 request unlock challenge */
+ ret = exc3000_vendor_data_request(data,
+ (u8[]){0x3a, 0xfb}, 2,
+ resp, 1);
+ if (ret < 0)
+ goto out;
+
+ /* 2.2 generate and send response */
+ exc3000_generate_unlock_response(&resp[2], &unlock_req[2]);
+ ret = exc3000_vendor_data_request(data, unlock_req, 6, resp, 1);
+ if (ret < 0)
+ goto out;
+
+ if (resp[2] != 0x01) {
+ dev_err(dev, "device unlock failed, aborting\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* 2.3 unknown, but required and invariant data */
+ ret = exc3000_vendor_data_request(data,
+ (u8[]){0x3a, 0xfe, 0x34,
+ 0x43, 0xcc}, 5,
+ resp, 1);
+ if (ret < 0)
+ goto out;
+
+ /* 2.4 reset controller */
+ ret = exc3000_vendor_data_request(data, (u8[]){0x3a, 0xff},
+ 2, NULL, 1);
+ if (ret < 0)
+ goto out;
+
+ /* wait for controller init after reset */
+ msleep(500);
+
+ /* 2.5: check communication after reset */
+ ret = exc3000_vendor_data_request(data, (u8[]){0x39, 0x01},
+ 2, resp, 1);
+ if (ret < 0)
+ goto out;
+
+ if (resp[1] != 0x02) {
+ dev_err(dev, "device ping request NACK, aborting\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* 2.6: check device mode again */
+ ret = exc3000_vendor_data_request(data, (u8[]){0x39, 0x02},
+ 2, resp, 1);
+ if (ret < 0)
+ goto out;
+
+ if (!resp[2] && !resp[3]) {
+ dev_err(dev, "device still app mode, aborting\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ /* 3: start firmware upload (erases internal app firmware flash) */
+ dev_dbg(dev, "start firmware upload\n");
+ ret = exc3000_vendor_data_request(data,
+ (u8[]){0x3a, 0x04}, 2, resp, 20);
+ if (ret < 0)
+ goto out;
+
+ if (resp[2] != 0x01) {
+ dev_err(dev, "firmware update start NACK, aborting\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* 4: upload firmware */
+ for (i = 0x56; i < fw->size; i += 36) {
+ u8 fw_chunk[37] = { 0x3a, 0x01, fw->data[i],
+ fw->data[i+1],fw->data[i+34] };
+
+ memcpy(&fw_chunk[5], &fw->data[i+2], 32);
+ ret = exc3000_vendor_data_request(data, fw_chunk, 37, resp, 1);
+ if (ret < 0)
+ goto out;
+
+ if (resp[2] != fw->data[i] || resp[3] != fw->data[i+1] ||
+ resp[4] != fw->data[i+34]) {
+ dev_err(dev,
+ "firmware update readback wrong, aborting\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ data->update_status = DIV_ROUND_UP(i * 100, fw->size);
+ }
+
+ /* 5: end firmware upload */
+ ret = exc3000_vendor_data_request(data,
+ (u8[]){0x3a, 0x05, fw->data[0x37],
+ fw->data[0x38], fw->data[0x39],
+ fw->data[0x1f], fw->data[0x20]},
+ 7, resp, 1);
+ if (ret < 0)
+ goto out;
+
+ if (resp[2] != 0x01) {
+ dev_err(dev, "firmware update end NACK, aborting\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* 6: switch back to app mode */
+ ret = exc3000_vendor_data_request(data, (u8[]){0x3a, 0xff}, 2, NULL, 1);
+ if (ret < 0)
+ goto out;
+
+ /* wait for controller init after reset */
+ msleep(500);
+
+ /* 7: check communication */
+ ret = exc3000_vendor_data_request(data, (u8[]){0x39, 0x01}, 2, resp, 1);
+ if (ret < 0)
+ goto out;
+
+ if (resp[1] != 0x02) {
+ dev_err(dev, "device ping request NACK, aborting\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* 8: check if we are in app mode again */
+ ret = exc3000_vendor_data_request(data, (u8[]){0x39, 0x02}, 2, resp, 1);
+ if (ret < 0)
+ goto out;
+
+ if (resp[2] || resp[3]) {
+ dev_err(dev, "device still bootloader mode, aborting\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ dev_info(dev, "firmware update complete\n");
+
+ data->update_status = 0;
+
+ return 0;
+
+out:
+ data->update_status = ret;
+ return ret;
+}
+
+static ssize_t update_fw_store(struct device *dev,
+ struct device_attribute *dattr,
+ const char *buf, size_t count)
+{
+ struct exc3000_data *data = dev_get_drvdata(dev);
+ char fw_name[NAME_MAX];
+ const struct firmware *fw;
+ size_t copy_count = count;
+ int ret;
+
+ if (count == 0 || count >= NAME_MAX)
+ return -EINVAL;
+
+ if (buf[count - 1] == '\0' || buf[count - 1] == '\n')
+ copy_count -= 1;
+
+ strncpy(fw_name, buf, copy_count);
+ fw_name[copy_count] = '\0';
+
+ ret = request_firmware(&fw, fw_name, dev);
+ if (ret)
+ return ret;
+
+ dev_info(dev, "Flashing %s\n", fw_name);
+
+ ret = exc3000_firmware_update(data, fw);
+
+ release_firmware(fw);
+
+ return ret ?: count;
+}
+static DEVICE_ATTR_WO(update_fw);
+
+static ssize_t update_fw_status_show(struct device *dev,
+ struct device_attribute *dattr, char *buf)
+{
+ struct exc3000_data *data = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", data->update_status);
+}
+static DEVICE_ATTR_RO(update_fw_status);
+
static struct attribute *sysfs_attrs[] = {
&dev_attr_fw_version.attr,
&dev_attr_model.attr,
&dev_attr_type.attr,
+ &dev_attr_update_fw.attr,
+ &dev_attr_update_fw_status.attr,
NULL
};