diff options
Diffstat (limited to 'ch-flash/ch-flash.c')
-rw-r--r-- | ch-flash/ch-flash.c | 776 |
1 files changed, 0 insertions, 776 deletions
diff --git a/ch-flash/ch-flash.c b/ch-flash/ch-flash.c deleted file mode 100644 index 548487d..0000000 --- a/ch-flash/ch-flash.c +++ /dev/null @@ -1,776 +0,0 @@ -/* - * 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: - */ |