/*
* 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 .
*/
#include "ch-flash.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/* 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:
*/