aboutsummaryrefslogtreecommitdiff
path: root/ch-flash/ch-flash.c
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2023-01-18 22:29:32 -0700
committerJosh Rahm <joshuarahm@gmail.com>2023-01-18 22:29:32 -0700
commitd93ca841f64cfe6a5ddcb45510becadd83358d84 (patch)
tree8741945dc2069bde9aa6473f4c529d55e7f1da4c /ch-flash/ch-flash.c
parentadb7d9e96ed55c7be5bf2da435c1e568dc11ab8f (diff)
downloadch573-d93ca841f64cfe6a5ddcb45510becadd83358d84.tar.gz
ch573-d93ca841f64cfe6a5ddcb45510becadd83358d84.tar.bz2
ch573-d93ca841f64cfe6a5ddcb45510becadd83358d84.zip
Start implementing ch-flash.
This is a program forked from isp55e0, but with the ability to wait for the device to be plugged in before flashing. I will perhaps add other fetaures to the program which will work better with my workflow, but this is the big change for now.
Diffstat (limited to 'ch-flash/ch-flash.c')
-rw-r--r--ch-flash/ch-flash.c776
1 files changed, 776 insertions, 0 deletions
diff --git a/ch-flash/ch-flash.c b/ch-flash/ch-flash.c
new file mode 100644
index 0000000..548487d
--- /dev/null
+++ b/ch-flash/ch-flash.c
@@ -0,0 +1,776 @@
+/*
+ * ISP-55E0 - an ISP programmer for some WinChipHead MCU families
+ * Copyright 2021 Frank Zago
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "ch-flash.h"
+
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <libusb-1.0/libusb.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+/* Profile of supported chips */
+static const struct ch_profile profiles[] = {
+ {
+ .name = "CH549",
+ .family = 0x12,
+ .type = 0x49,
+ .code_flash_size = 61440,
+ .data_flash_size = 1024,
+ .mcu_id_len = 8,
+ .need_last_write = true,
+ },
+ {
+ .name = "CH551",
+ .family = 0x11,
+ .type = 0x51,
+ .code_flash_size = 10240,
+ .data_flash_size = 128,
+ .mcu_id_len = 4,
+ },
+ {
+ .name = "CH552",
+ .family = 0x11,
+ .type = 0x52,
+ .code_flash_size = 14336,
+ .data_flash_size = 128,
+ .mcu_id_len = 4,
+ },
+ {
+ .name = "CH554",
+ .family = 0x11,
+ .type = 0x54,
+ .code_flash_size = 14336,
+ .data_flash_size = 128,
+ .mcu_id_len = 4,
+ },
+ {
+ .name = "CH559",
+ .family = 0x11,
+ .type = 0x59,
+ .code_flash_size = 61440,
+ .data_flash_size = 1024,
+ .mcu_id_len = 4,
+ },
+ {
+ .name = "CH573",
+ .family = 0x13,
+ .type = 0x73,
+ .code_flash_size = 458752,
+ .data_flash_size = 32768,
+ .mcu_id_len = 8,
+ },
+ {
+ .name = "CH579",
+ .family = 0x13,
+ .type = 0x79,
+ .code_flash_size = 256000,
+ .data_flash_size = 2048,
+ .mcu_id_len = 8,
+ .clear_cfg_rom_read = true,
+ },
+ {
+ .name = "CH32F103",
+ .family = 0x14,
+ .type = 0x3f,
+ .code_flash_size = 65536,
+ .mcu_id_len = 8,
+ .need_remove_wp = true,
+ .need_last_write = true,
+ },
+ {
+ .name = "CH32V103",
+ .family = 0x15,
+ .type = 0x3f,
+ .code_flash_size = 65536,
+ .mcu_id_len = 8,
+ .need_remove_wp = true,
+ .need_last_write = true,
+ },
+ {
+ .name = "CH582",
+ .family = 0x16,
+ .type = 0x82,
+ .code_flash_size = 458752,
+ .data_flash_size = 32768,
+ .mcu_id_len = 8,
+ },
+ {}};
+
+static const struct option long_options[] = {
+ {"code-verify", required_argument, 0, 'c'},
+ {"debug", no_argument, 0, 'd'},
+ {"code-flash", required_argument, 0, 'f'},
+ {"help", no_argument, 0, 'h'},
+ {"data-flash", required_argument, 0, 'k'},
+ {"data-verify", required_argument, 0, 'l'},
+ {"data-dump", required_argument, 0, 'm'},
+ {0, 0, 0, 0}};
+
+static void usage(void)
+{
+ printf("ISP programmer for some WinChipHead MCUs\n");
+ printf("Options:\n");
+ printf(" --code-flash, -f firmware to flash\n");
+ printf(" --code-verify, -c verify existing firwmare\n");
+ printf(" --data-flash, -k data to flash\n");
+ printf(" --data-verify, -l verify existing data\n");
+ printf(" --data-dump, -m dump the data flash to a file\n");
+ printf(" --debug, -d turn debug traces on\n");
+ printf(" --help, -h this help\n");
+}
+
+static void hexdump(const char *name, const void *data, int len)
+{
+ const uint8_t *p = data;
+ int i;
+
+ printf("Dump - %s\n", name);
+ for (i = 0; i < len; i++) {
+ if (i && (i % 16) == 0) printf("\n");
+
+ printf("%02x ", *p++);
+ }
+ printf("\n");
+}
+
+/* Open and claim the USB device */
+static void open_usb_device(struct device *dev)
+{
+ int ret;
+
+ libusb_context *ctx;
+ ret = libusb_init(&ctx);
+ if (ret) errx(EXIT_FAILURE, "Can't initialize USB");
+
+ printf("Opening device with vid and pid\n");
+
+ do {
+ dev->usb_h = libusb_open_device_with_vid_pid(NULL, 0x4348, 0x55e0);
+ if (dev->usb_h != NULL) {
+ break;
+ }
+ fprintf(stderr, "Waiting for CH5xx device in ISP Mode ...\n");
+ libusb_handle_events(ctx);
+ } while (1);
+
+ printf("Detaching from kernel\n");
+ ret = libusb_set_auto_detach_kernel_driver(dev->usb_h, 1);
+ if (ret) errx(EXIT_FAILURE, "Can't detach the device from the kernel");
+
+ ret = libusb_claim_interface(dev->usb_h, 0);
+ if (ret) errx(EXIT_FAILURE, "Can't claim the USB device\n");
+}
+
+/* Send a request, get a reply */
+static int transfer(
+ struct device *dev, void *req, int req_len, void *resp, int resp_len)
+{
+ int len;
+ int ret;
+
+ ret =
+ libusb_bulk_transfer(dev->usb_h, EP_OUT, req, req_len, &len, USB_TIMEOUT);
+ if (ret) return -EIO;
+
+ if (dev->debug) hexdump("request", req, len);
+
+ ret = libusb_bulk_transfer(
+ dev->usb_h, EP_IN, resp, resp_len, &len, USB_TIMEOUT);
+ if (ret) return -EIO;
+
+ if (dev->debug) hexdump("response", resp, len);
+
+ return 0;
+}
+
+static void set_chip_profile(struct device *dev, uint8_t family, uint8_t type)
+{
+ const struct ch_profile *profile = profiles;
+
+ while (profile->name) {
+ if (profile->family == family && profile->type == type) {
+ dev->profile = profile;
+ dev->fw.max_flash_size = profile->code_flash_size;
+ dev->data.max_flash_size = profile->data_flash_size;
+ dev->data_dump.max_flash_size = profile->data_flash_size;
+ return;
+ }
+
+ profile++;
+ }
+
+ errx(
+ EXIT_FAILURE,
+ "Device family 0x%02x type 0x%02x is not supported\n",
+ family,
+ type);
+}
+
+static void read_chip_type(struct device *dev)
+{
+ struct req_get_chip_type req = {
+ .hdr.command = CMD_CHIP_TYPE,
+ .hdr.data_len = sizeof(req) - sizeof(req.hdr),
+ .string = "MCU ISP & WCH.CN",
+ };
+ struct resp_chip_type resp;
+ int ret;
+
+ ret = transfer(dev, &req, sizeof(req), &resp, sizeof(resp));
+ if (ret) errx(EXIT_FAILURE, "Can't get the device type");
+
+ if (resp.family == 0)
+ errx(EXIT_FAILURE, "Chip is hosed. Reset or power cycle it.");
+
+ set_chip_profile(dev, resp.family, resp.type);
+}
+
+static void read_config(struct device *dev)
+{
+ struct req_read_config req = {
+ .hdr.command = CMD_READ_CONFIG,
+ .hdr.data_len = sizeof(req) - sizeof(req.hdr),
+ .what = 0x1f,
+ };
+ struct resp_read_config resp;
+ int ret;
+
+ ret = transfer(dev, &req, sizeof(req), &resp, sizeof(resp));
+ if (ret) errx(EXIT_FAILURE, "Can't get the device configuration");
+
+ dev->bv = be32toh(resp.bootloader_version);
+ memcpy(dev->id, resp.id, dev->profile->mcu_id_len);
+ memcpy(dev->config_data, resp.config_data, sizeof(dev->config_data));
+}
+
+/* Write some configuration. Hardcoded for now. */
+static void write_config(struct device *dev)
+{
+ struct req_write_config req = {
+ .hdr.command = CMD_WRITE_CONFIG,
+ .hdr.data_len = sizeof(req) - sizeof(req.hdr),
+ .what = 0x07,
+ };
+ struct resp_write_config resp;
+ int ret;
+
+ memcpy(req.config_data, dev->config_data, sizeof(req.config_data));
+
+ if (dev->profile->need_remove_wp && req.config_data[0] == 0xff)
+ req.config_data[0] = 0xa5;
+
+ if (dev->profile->clear_cfg_rom_read) {
+ /* CH579 - the CFG_ROM_READ must be cleared, otherwise
+ * flashing will fail.
+ */
+ req.config_data[8] &= ~0x80;
+ }
+
+ ret = transfer(dev, &req, sizeof(req), &resp, sizeof(resp));
+ if (ret) errx(EXIT_FAILURE, "Can't write the new configuration");
+}
+
+/* Erase the flash */
+static void erase_code_flash(struct device *dev)
+{
+ struct req_erase_flash req = {
+ .hdr.command = CMD_ERASE_CODE_FLASH,
+ .hdr.data_len = sizeof(req) - sizeof(req.hdr),
+ };
+ struct resp_erase_flash resp;
+ int length;
+ int ret;
+
+ /* Erase length is in KiB blocks, with a minimum of 8KiB */
+ length = ((dev->fw.len + 1023) & ~1023) / 1024;
+ if (length < 8) length = 8;
+
+ req.length = length;
+
+ ret = transfer(dev, &req, sizeof(req), &resp, sizeof(resp));
+ if (ret) errx(EXIT_FAILURE, "Can't erase the code flash");
+
+ if (resp.return_code != 0x00)
+ errx(EXIT_FAILURE, "The device refused to erase the code flash");
+}
+
+static void load_file(struct device *dev, struct content *info)
+{
+ struct stat statbuf;
+ int ret;
+ int fd;
+
+ fd = open(info->filename, O_RDONLY);
+ if (fd == -1) err(EXIT_FAILURE, "Can't open the firmware file");
+
+ ret = fstat(fd, &statbuf);
+ if (ret == -1) err(EXIT_FAILURE, "Can't get firmware file size");
+
+ /* Round up to 8 bytes boundary as upload protocol requires
+ * it. Extra bytes are zeroes. */
+ info->len = (statbuf.st_size + 7) & ~7;
+
+ if (info->len > info->max_flash_size)
+ errx(EXIT_FAILURE, "Firmware cannot fit in flash");
+
+ info->buf = malloc(info->len);
+ if (info->buf == NULL)
+ errx(EXIT_FAILURE, "Can't allocate %zd bytes for the firmware", info->len);
+
+ memset(info->buf, 0xff, info->len);
+
+ /* TODO: loop until all read, or use mmap instead. */
+ ret = read(fd, info->buf, statbuf.st_size);
+ if (ret != statbuf.st_size) err(EXIT_FAILURE, "Can't read firmware file");
+
+ close(fd);
+}
+
+/* Encrypt (or decrypt) some data */
+static void encrypt(const struct device *dev, struct content *info)
+{
+ uint8_t *p;
+ int i;
+
+ p = info->buf;
+ for (i = 0; i < info->len; i++) {
+ *p++ ^= dev->xor_key[i % XOR_KEY_LEN];
+ }
+
+ info->encrypted = !info->encrypted;
+}
+
+/* Create the local key to encrypt the data to send */
+static void create_key(struct device *dev)
+{
+ uint8_t sum;
+ int i;
+
+ sum = 0;
+ for (i = 0; i < dev->profile->mcu_id_len; i++) sum += dev->id[i];
+
+ for (i = 0; i < XOR_KEY_LEN; i++) dev->xor_key[i] = sum;
+ dev->xor_key[7] += dev->profile->type;
+}
+
+/* Send the encryption key */
+static void send_key(struct device *dev)
+{
+ static struct req_set_key req = {
+ .hdr.command = CMD_SET_KEY,
+ .hdr.data_len = 0x1e,
+ };
+ struct resp_set_key resp;
+ int ret;
+ uint8_t sum;
+ int i;
+
+ sum = 0;
+ for (i = 0; i < XOR_KEY_LEN; i++) sum += dev->xor_key[i];
+
+ ret = transfer(
+ dev,
+ &req,
+ sizeof(struct req_hdr) + req.hdr.data_len,
+ &resp,
+ sizeof(resp));
+ if (ret) errx(EXIT_FAILURE, "Can't set the key");
+
+ if (resp.key_checksum != sum)
+ errx(EXIT_FAILURE, "The device refused the key");
+}
+
+/* read or write code flash, or write data flash */
+static int flash_rw(
+ struct device *dev, int cmd, struct content *info, int *offset_out)
+{
+ struct req_flash_rw req = {
+ .hdr.command = cmd,
+ };
+ struct resp_flash_rw resp;
+ int offset;
+ int to_send;
+ int len;
+ int ret;
+
+ /* Send the firmware in 56 bytes chunks */
+ offset = 0;
+ to_send = info->len;
+ while (to_send) {
+ req.offset = offset;
+
+ len = sizeof(req.data);
+ if (len > to_send) len = to_send;
+
+ req.hdr.data_len = len + 5;
+
+ memcpy(&req.data, &info->buf[offset], len);
+
+ ret = transfer(
+ dev,
+ &req,
+ sizeof(struct req_hdr) + req.hdr.data_len,
+ &resp,
+ sizeof(resp));
+ if (ret) errx(EXIT_FAILURE, "Write failure at offset %d", offset);
+
+ if (resp.return_code != 0) {
+ *offset_out = offset;
+ return resp.return_code;
+ }
+
+ to_send -= len;
+ offset += len;
+ }
+
+ if (cmd == CMD_WRITE_CODE_FLASH && dev->profile->need_last_write) {
+ /* The CH32Fx need a last empty write. */
+ req.offset = info->len;
+ req.hdr.data_len = 5;
+
+ ret = transfer(
+ dev,
+ &req,
+ sizeof(struct req_hdr) + req.hdr.data_len,
+ &resp,
+ sizeof(resp));
+ if (ret) errx(EXIT_FAILURE, "Write failure at offset %d", offset);
+
+ if (resp.return_code != 0) {
+ *offset_out = offset;
+ return resp.return_code;
+ }
+ }
+
+ return 0;
+}
+
+static void write_code_flash(struct device *dev)
+{
+ int offset;
+ int ret;
+
+ ret = flash_rw(dev, CMD_WRITE_CODE_FLASH, &dev->fw, &offset);
+ if (ret) errx(EXIT_FAILURE, "Write code flash failure at offset %d", offset);
+}
+
+static void verify_code_flash(struct device *dev)
+{
+ int offset;
+ int ret;
+
+ ret = flash_rw(dev, CMD_CMP_CODE_FLASH, &dev->fw, &offset);
+ if (ret) errx(EXIT_FAILURE, "Check code flash failure at offset %d", offset);
+}
+
+static void erase_data_flash(struct device *dev)
+{
+ struct req_erase_data_flash req = {
+ .hdr.command = CMD_ERASE_DATA_FLASH,
+ };
+ struct resp_erase_data_flash resp;
+ size_t length;
+ int ret;
+
+ /* Erase length is in KiB blocks, with a minimum of 1KiB */
+ length = ((dev->profile->data_flash_size + 1023) & ~1023) / 1024;
+ if (length < 1) length = 1;
+
+ req.len = length;
+
+ ret = transfer(dev, &req, sizeof(req), &resp, sizeof(resp));
+ if (ret) errx(EXIT_FAILURE, "Can't erase the data flash");
+
+ if (resp.return_code != 0x00)
+ errx(EXIT_FAILURE, "The device refused to erase the data flash");
+}
+
+static void write_data_flash(struct device *dev)
+{
+ int ret;
+ int offset;
+
+ ret = flash_rw(dev, CMD_WRITE_DATA_FLASH, &dev->data, &offset);
+ if (ret) errx(EXIT_FAILURE, "Write data flash failure at offset %d", offset);
+}
+
+static void read_data_flash(struct device *dev)
+{
+ struct req_read_data_flash req = {
+ .hdr.command = CMD_READ_DATA_FLASH,
+ };
+ struct resp_read_data_flash resp;
+ int to_read;
+ int offset;
+ int len;
+ int ret;
+
+ to_read = dev->data_dump.max_flash_size;
+
+ dev->data_dump.len = to_read;
+ dev->data_dump.buf = calloc(1, to_read);
+ if (!dev->data_dump.buf)
+ errx(EXIT_FAILURE, "Can't allocate %u bytes for the data flash", to_read);
+
+ offset = 0;
+
+ while (to_read) {
+ req.offset = offset;
+
+ len = sizeof(resp.data);
+ if (len > to_read) len = to_read;
+
+ req.len = len;
+
+ ret = transfer(dev, &req, sizeof(req), &resp, sizeof(resp));
+ if (ret) errx(EXIT_FAILURE, "Data read failure at offset %d", offset);
+
+ if (resp.return_code != 0)
+ errx(EXIT_FAILURE, "Data read failure at offset %d", offset);
+
+ memcpy(&dev->data_dump.buf[offset], resp.data, len);
+
+ to_read -= len;
+ offset += len;
+ }
+}
+
+static void verify_data_flash(struct device *dev)
+{
+ if (dev->data.encrypted) {
+ /* The data was just written previously to the flash,
+ * and encrypted. It needs to been decrypted now so
+ * the comparison can happen. */
+ encrypt(dev, &dev->data);
+ }
+
+ if (memcmp(dev->data.buf, dev->data_dump.buf, dev->data.len) != 0)
+ errx(EXIT_FAILURE, "Data flash doesn't match");
+}
+
+static void dump_data_flash(struct device *dev)
+{
+ int fd;
+ int ret;
+
+ fd = creat(dev->data_dump.filename, 0600);
+ if (fd == -1)
+ err(EXIT_FAILURE, "Can't create the file to dump the data flash");
+
+ ret = write(fd, dev->data_dump.buf, dev->data.max_flash_size);
+ if (ret == -1) err(EXIT_FAILURE, "Can't dump the data flash");
+
+ if (ret != dev->profile->data_flash_size)
+ err(EXIT_FAILURE, "Can't dump all the data flash to file");
+
+ close(fd);
+}
+
+/* Reboot the device */
+static void reboot_device(struct device *dev)
+{
+ struct req_reboot req = {
+ .hdr.command = CMD_REBOOT,
+ .hdr.data_len = sizeof(req) - sizeof(req.hdr),
+ .option = 0x01,
+ };
+ struct resp_reboot resp;
+ int ret;
+
+ ret = transfer(dev, &req, sizeof(req), &resp, sizeof(resp));
+
+ /* 2.4.0 bootloaders do not respond. 2.8.0 does. */
+ if (!dev->wait_reboot_resp) return;
+
+ if (ret) errx(EXIT_FAILURE, "Can't reboot the device");
+
+ if (resp.return_code != 0x00)
+ errx(EXIT_FAILURE, "The device refused to reboot");
+}
+
+int main(int argc, char *argv[])
+{
+ struct device dev = {};
+ bool do_code_flash = false;
+ bool do_code_verify = false;
+ bool do_data_flash = false;
+ bool do_data_verify = false;
+ bool do_data_dump = false;
+ int c;
+ int i;
+
+ while (1) {
+ int option_index = 0;
+
+ c = getopt_long(argc, argv, "c:df:hk:l:m:", long_options, &option_index);
+ if (c == -1) break;
+
+ switch (c) {
+ case 0:
+ printf("option %s", long_options[option_index].name);
+ if (optarg) printf(" with arg %s", optarg);
+ printf("\n");
+ break;
+ case 'c':
+ dev.fw.filename = optarg;
+ do_code_verify = true;
+ break;
+ case 'd':
+ dev.debug = true;
+ break;
+ case 'f':
+ dev.fw.filename = optarg;
+ do_code_flash = true;
+ do_code_verify = true; /* always verify after flashing */
+ break;
+ case 'k':
+ dev.data.filename = optarg;
+ do_data_flash = true;
+ do_data_verify = true;
+ break;
+ case 'l':
+ dev.data.filename = optarg;
+ do_data_verify = true;
+ break;
+ case 'm':
+ dev.data_dump.filename = optarg;
+ do_data_dump = true;
+ break;
+ case 'h':
+ usage();
+ return EXIT_SUCCESS;
+ default:
+ return EXIT_FAILURE;
+ }
+ }
+
+ if (optind < argc) errx(EXIT_FAILURE, "Extra argument: %s", argv[optind]);
+
+ printf("Opening USB Device\n");
+ open_usb_device(&dev);
+ printf("Reading device.\n");
+ read_chip_type(&dev);
+ printf("Found device %s\n", dev.profile->name);
+
+ read_config(&dev);
+
+ printf(
+ "Bootloader version %d.%d.%d\n",
+ (dev.bv >> 16) & 0xff,
+ (dev.bv >> 8) & 0xff,
+ dev.bv & 0xff);
+
+ printf("Unique chip ID ");
+ for (i = 0; i < dev.profile->mcu_id_len; i++) {
+ if (i > 0) printf("-");
+ printf("%02x", dev.id[i]);
+ }
+ printf("\n");
+
+ /* check bootloader version */
+ switch (dev.bv) {
+ case 0x020301:
+ case 0x020400:
+ dev.wait_reboot_resp = false;
+ break;
+
+ case 0x020500:
+ case 0x020600:
+ case 0x020800:
+ case 0x020900:
+ dev.wait_reboot_resp = true;
+ break;
+
+ default:
+ errx(EXIT_FAILURE, "This bootloader version is not supported");
+ }
+
+ create_key(&dev);
+
+ if (do_code_flash || do_code_verify) {
+ load_file(&dev, &dev.fw);
+ encrypt(&dev, &dev.fw);
+ }
+
+ if (do_data_flash || do_data_verify) load_file(&dev, &dev.data);
+
+ if (do_code_flash || do_code_verify || do_data_flash) send_key(&dev);
+
+ /* Code flash */
+
+ if (do_code_flash) {
+ write_config(&dev);
+
+ erase_code_flash(&dev);
+ write_code_flash(&dev);
+
+ printf("Code flashing successful\n");
+ }
+
+ if (do_code_verify) {
+ verify_code_flash(&dev);
+
+ printf("Firmware is good\n");
+ }
+
+ /* Data flash */
+
+ if (do_data_flash) {
+ encrypt(&dev, &dev.data);
+ erase_data_flash(&dev);
+ write_data_flash(&dev);
+
+ printf("Data flashing successful\n");
+ }
+
+ if (do_data_verify || do_data_dump) read_data_flash(&dev);
+
+ if (do_data_verify) {
+ verify_data_flash(&dev);
+
+ printf("Data flash is good\n");
+ }
+
+ if (do_data_dump) {
+ dump_data_flash(&dev);
+
+ printf("Dumped data flash to file\n");
+ }
+
+ if (do_code_flash) reboot_device(&dev);
+
+ return 0;
+}
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-file-style: "linux"
+ * indent-tabs-mode: t
+ * tab-width: 8
+ * End:
+ */