From: notaz Date: Sun, 29 Oct 2017 22:16:50 +0000 (+0200) Subject: initial version X-Git-Url: https://notaz.gp2x.de/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d9502b8d6241a5886e08c32797f917e9d475bef0;p=flashkit-mdc.git initial version --- d9502b8d6241a5886e08c32797f917e9d475bef0 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..589fe24 --- /dev/null +++ b/Makefile @@ -0,0 +1,10 @@ +CFLAGS += -Wall -O2 + +TARGET = flashkit + +all: $(TARGET) + +clean: + $(RM) $(TARGET) + +.PHONY: clean all diff --git a/flashkit.c b/flashkit.c new file mode 100644 index 0000000..3e687fc --- /dev/null +++ b/flashkit.c @@ -0,0 +1,596 @@ +/* + * Tool for USB serial communication with Krikzz's FlashKit-MD + * Copyright (c) 2017 notaz + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include // hton +#include +#include + +#ifndef min +#define min(x, y) ((x) < (y) ? (x) : (y)) +#define max(x, y) ((x) > (y) ? (x) : (y)) +#endif + +enum dev_cmd { + CMD_ADDR = 0, + CMD_LEN = 1, + CMD_RD = 2, + CMD_WR = 3, + CMD_RY = 4, + CMD_DELAY = 5, +}; +#define PAR_MODE8 (1 << 4) +#define PAR_DEV_ID (1 << 5) +#define PAR_SINGE (1 << 6) +#define PAR_INC (1 << 7) + +static int setup(int fd) +{ + struct termios tty; + int ret; + + memset(&tty, 0, sizeof(tty)); + + ret = tcgetattr(fd, &tty); + if (ret != 0) + { + perror("tcgetattr"); + return 1; + } + + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP + | INLCR | IGNCR | ICRNL | IXON); + tty.c_oflag &= ~OPOST; + tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + tty.c_cflag &= ~(CSIZE | PARENB); + tty.c_cflag |= CS8; + + //tty.c_cc[VMIN] = 1; + //tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout + + ret = tcsetattr(fd, TCSANOW, &tty); + if (ret != 0) { + perror("tcsetattr"); + return ret; + } + + return 0; +} + +static int write_serial(int fd, const void *data, size_t size) +{ + int ret; + + ret = write(fd, data, size); + if (ret != size) { + fprintf(stderr, "write %d/%zd: ", ret, size); + perror(""); + exit(1); + } + + return 0; +} + +static int read_serial(int fd, void *data, size_t size) +{ + size_t got = 0; + int ret; + + while (got < size) { + ret = read(fd, (char *)data + got, size - got); + if (ret <= 0) { + fprintf(stderr, "read %d %zd/%zd: ", + ret, got, size); + perror(""); + exit(1); + } + got += ret; + } + + return 0; +} + +static void set_addr(int fd, uint32_t addr) +{ + uint8_t cmd[6] = { + CMD_ADDR, addr >> 17, + CMD_ADDR, addr >> 9, + CMD_ADDR, addr >> 1 + }; + write_serial(fd, cmd, sizeof(cmd)); +} + +static uint16_t read_word(int fd, uint32_t addr) +{ + uint8_t cmd[7] = { + CMD_ADDR, addr >> 17, + CMD_ADDR, addr >> 9, + CMD_ADDR, addr >> 1, + CMD_RD | PAR_SINGE + }; + uint16_t r; + + write_serial(fd, cmd, sizeof(cmd)); + read_serial(fd, &r, sizeof(r)); + return ntohs(r); +} + +static void write_word(int fd, uint32_t addr, uint16_t d) +{ + uint8_t cmd[9] = { + CMD_ADDR, addr >> 17, + CMD_ADDR, addr >> 9, + CMD_ADDR, addr >> 1, + CMD_WR | PAR_SINGE, + d >> 8, d + }; + + write_serial(fd, cmd, sizeof(cmd)); +} + +static void read_block(int fd, void *dst, uint32_t size) +{ + uint8_t cmd[5] = { + CMD_LEN, size >> 9, + CMD_LEN, size >> 1, + CMD_RD | PAR_INC + }; + + assert(size <= 0x10000); + write_serial(fd, cmd, sizeof(cmd)); + read_serial(fd, dst, size); +} + +static uint16_t flash_seq_r(int fd, uint8_t cmd, uint32_t addr) +{ + // unlock + write_word(fd, 0xaaa, 0xaa); + write_word(fd, 0x555, 0x55); + + write_word(fd, 0xaaa, cmd); + return read_word(fd, addr); +} + +static void flash_seq_erase(int fd, uint32_t addr) +{ + // printf("erase %06x\n", addr); + write_word(fd, 0xaaa, 0xaa); + write_word(fd, 0x555, 0x55); + write_word(fd, 0xaaa, 0x80); + + write_word(fd, 0xaaa, 0xaa); + write_word(fd, 0x555, 0x55); + write_word(fd, addr, 0x30); +} + +static void flash_seq_write(int fd, uint32_t addr, uint8_t *d) +{ + uint8_t cmd[] = { + // unlock + CMD_ADDR, 0, + CMD_ADDR, 0x05, + CMD_ADDR, 0x55, + CMD_WR | PAR_SINGE | PAR_MODE8, 0xaa, + CMD_ADDR, 0, + CMD_ADDR, 0x02, + CMD_ADDR, 0xaa, + CMD_WR | PAR_SINGE | PAR_MODE8, 0x55, + // program setup + CMD_ADDR, 0, + CMD_ADDR, 0x05, + CMD_ADDR, 0x55, + CMD_WR | PAR_SINGE | PAR_MODE8, 0xa0, + // program data + CMD_ADDR, addr >> 17, + CMD_ADDR, addr >> 9, + CMD_ADDR, addr >> 1, + CMD_WR | PAR_SINGE, d[0], d[1], + CMD_RY + }; + + write_serial(fd, cmd, sizeof(cmd)); +} + +// status wait + dummy read to cause a wait? +static uint16_t ry_read(int fd) +{ + uint8_t cmd[2] = { CMD_RY, CMD_RD | PAR_SINGE }; + uint16_t rv = 0; + + write_serial(fd, cmd, sizeof(cmd)); + read_serial(fd, &rv, sizeof(rv)); + return ntohs(rv); +} + +static void set_delay(int fd, uint8_t delay) +{ + uint8_t cmd[2] = { CMD_DELAY, delay }; + + write_serial(fd, cmd, sizeof(cmd)); +} + +static struct flash_info { + uint16_t mid; + uint16_t did; + uint32_t size; + uint16_t region_cnt; + struct { + uint32_t block_size; + uint32_t block_count; + uint32_t start; + uint32_t size; + } region[4]; +} info; + +static void read_info(int fd) +{ + static const uint16_t qry[3] = { 'Q', 'R', 'Y' }; + uint32_t total = 0; + uint16_t resp[3]; + uint32_t i, a; + + info.mid = flash_seq_r(fd, 0x90, 0); // autoselect + info.did = read_word(fd, 2); + + // could enter CFI directly, but there seems to be a "stack" + // of modes, so 2 exits would be needed + write_word(fd, 0, 0xf0); + + write_word(fd, 0xaa, 0x98); // CFI Query + resp[0] = read_word(fd, 0x20); + resp[1] = read_word(fd, 0x22); + resp[2] = read_word(fd, 0x24); + if (memcmp(resp, qry, sizeof(resp))) { + fprintf(stderr, "unexpected CFI response: %04x %04x %04x\n", + resp[0], resp[1], resp[2]); + exit(1); + } + info.size = 1u << read_word(fd, 0x4e); + info.region_cnt = read_word(fd, 0x58); + assert(0 < info.region_cnt && info.region_cnt <= 4); + for (i = 0, a = 0x5a; i < info.region_cnt; i++, a += 8) { + info.region[i].block_count = read_word(fd, a + 0) + 1; + info.region[i].block_count += read_word(fd, a + 2) << 8; + info.region[i].block_size = read_word(fd, a + 4) << 8; + info.region[i].block_size |= read_word(fd, a + 6) << 16; + info.region[i].start = total; + info.region[i].size = + info.region[i].block_size * info.region[i].block_count; + assert(info.region[i].size); + total += info.region[i].size; + } + + write_word(fd, 0, 0xf0); // flash reset + + printf("Flash info:\n"); + printf("Manufacturer ID: %04x\n", info.mid); + printf("Device ID: %04x\n", info.did); + printf("size: %u\n", info.size); + printf("Erase Block Regions: %u\n", info.region_cnt); + for (i = 0; i < info.region_cnt; i++) + printf(" %5u x %u\n", info.region[i].block_size, + info.region[i].block_count); + if (info.size != total) + fprintf(stderr, "warning: total is %u, bad CFI?\n", total); +} + +static uint32_t get_block_addr(uint32_t addr, uint32_t blk_offset) +{ + uint32_t i; + + assert(info.region_cnt); + for (i = 0; i < info.region_cnt; i++) { + if (info.region[i].start <= addr + && addr < info.region[i].start + info.region[i].size) + { + uint32_t blk = (addr - info.region[i].start) + / info.region[i].block_size + + blk_offset; + return info.region[i].start + + blk * info.region[i].block_size; + } + } + + fprintf(stderr, "\naddress out of range: 0x%x\n", addr); + exit(1); +} + +static void print_progress(uint32_t done, uint32_t total) +{ + int i, step; + + printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); + printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); /* 20 */ + printf("\b\b\b\b\b\b"); + printf("%06x/%06x |", done, total); + + step = (total + 19) / 20; + for (i = step; i <= total; i += step) + fputc(done >= i ? '=' : '-', stdout); + printf("| %3d%%", done * 100 / total); + fflush(stdout); + if (done >= total) + fputc('\n', stdout); +} + +static void usage(const char *argv0) +{ + printf("usage:\n" + "%s [options]\n" + " -d (default /dev/ttyUSB0)\n" + " -r [size] dump the cart (default 4MB)\n" + " -w [size] flash the cart (file size)\n" + " -e erase (rounds to block size)\n" + " -a cart start address (default 0)\n" + " -v verify written data\n" + " -i get info about the flash chip\n" + , argv0); + exit(1); +} + +static void invarg(int argc, char *argv[], int arg) +{ + if (arg < argc) + fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]); + else + fprintf(stderr, "missing required argument %d\n", arg); + exit(1); +} + +static void *getarg(int argc, char *argv[], int arg) +{ + if (arg >= argc) + invarg(argc, argv, arg); + return argv[arg]; +} + +static uint8_t g_block[0x10000]; +static uint8_t g_block2[0x10000]; + +int main(int argc, char *argv[]) +{ + const char *portname = "/dev/ttyUSB0"; + const char *fname_w = NULL; + const char *fname_r = NULL; + long size_w = 0; + long size_r = 0; + long size_e = 0; + long size_v = 0; + long len, address_in = 0; + long a, a_blk, end; + int do_info = 0; + int do_verify = 0; + FILE *f_w = NULL; + FILE *f_r = NULL; + uint8_t id[2] = { 0, 0 }; + uint8_t cmd; + uint16_t rv; + int arg = 1; + int fd; + + if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) + usage(argv[0]); + + for (arg = 1; arg < argc; arg++) { + if (!strcmp(argv[arg], "-d")) { + portname = getarg(argc, argv, ++arg); + continue; + } + if (!strcmp(argv[arg], "-r")) { + fname_r = getarg(argc, argv, ++arg); + if (arg + 1 < argc && argv[arg + 1][0] != '-') { + size_r = strtol(argv[++arg], NULL, 0); + if (size_r <= 0) + invarg(argc, argv, arg); + } + continue; + } + if (!strcmp(argv[arg], "-w")) { + fname_w = getarg(argc, argv, ++arg); + if (arg + 1 < argc && argv[arg + 1][0] != '-') { + size_w = strtol(argv[++arg], NULL, 0); + if (size_w <= 0) + invarg(argc, argv, arg); + } + continue; + } + if (!strcmp(argv[arg], "-a")) { + address_in = strtol(getarg(argc, argv, ++arg), NULL, 0); + if (address_in < 0 || (address_in & 1)) + invarg(argc, argv, arg); + continue; + } + if (!strcmp(argv[arg], "-e")) { + size_e = strtol(getarg(argc, argv, ++arg), NULL, 0); + if (size_e <= 0) + invarg(argc, argv, arg); + continue; + } + if (!strcmp(argv[arg], "-v")) { + do_verify = 1; + continue; + } + if (!strcmp(argv[arg], "-i")) { + do_info = 1; + continue; + } + invarg(argc, argv, arg); + } + + if (fname_r && size_r == 0) + size_r = 0x400000; + + if (fname_w) { + f_w = fopen(fname_w, "rb"); + if (!f_w) { + fprintf(stderr, "fopen %s: ", fname_w); + perror(""); + return 1; + } + if (size_w <= 0) { + fseek(f_w, 0, SEEK_END); + size_w = ftell(f_w); + fseek(f_w, 0, SEEK_SET); + } + if (size_w <= 0) { + fprintf(stderr, "size of %s is %ld\n", + fname_w, size_w); + return 1; + } + if (size_e < size_w) + size_e = size_w; + if (do_verify) + size_v = size_w; + } + + fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC); + if (fd < 0) { + fprintf(stderr, "open %s: ", portname); + perror(""); + return 1; + } + + setup(fd); + + cmd = CMD_RD | PAR_SINGE | PAR_DEV_ID; + write_serial(fd, &cmd, sizeof(cmd)); + read_serial(fd, id, sizeof(id)); + if (id[0] != id[1] || id[0] == 0) { + fprintf(stderr, "unexpected id: %02x %02x\n", id[0], id[1]); + return 1; + } + printf("flashkit id: %02x\n", id[0]); + + set_delay(fd, 1); + write_word(fd, 0, 0xf0); // flash reset + + if (do_info || size_e) + read_info(fd); + + if (size_e) { + // set_delay(fd, 0); // ? + a_blk = get_block_addr(address_in, 0); + end = address_in + size_e; + + printf("erasing %ld bytes:\n", size_e); + print_progress(0, size_e); + for (a = address_in; a < end; ) { + flash_seq_erase(fd, a_blk); + rv = ry_read(fd); + if (rv != 0xffff) { + fprintf(stderr, "\nerase error: %lx %04x\n", + a_blk, rv); + } + + a_blk = get_block_addr(a_blk, 1); + a += a_blk - a; + print_progress(a - address_in, size_e); + } + } + if (f_w != NULL) { + uint8_t b[2]; + set_delay(fd, 0); + printf("writing %ld bytes:\n", size_w); + for (a = 0; a < size_w; a += 2) { + ssize_t r; + + b[1] = 0xff; + len = min(size_w - a, 2); + r = fread(b, 1, len, f_w); + if (r != len) { + perror("\nfread"); + return 1; + } + flash_seq_write(fd, address_in + a, b); + + if (!(a & 0x3fe)) + print_progress(a, size_w); + } + print_progress(a, size_w); + rv = ry_read(fd); + if (rv != ((b[0] << 8) | b[1])) + fprintf(stderr, "warning: last bytes: %04x %02x%02x\n", + rv, b[0], b[1]); + rewind(f_w); + set_delay(fd, 1); + } + + if (fname_r || size_v) { + long blks, blks_v, done, verify_diff = 0; + + blks = (size_r + sizeof(g_block) - 1) / sizeof(g_block); + blks_v = (size_v + sizeof(g_block) - 1) / sizeof(g_block); + blks = max(blks, blks_v); + if (fname_r) { + f_r = fopen(fname_r, "wb"); + if (!f_r) { + fprintf(stderr, "fopen %s: ", fname_r); + perror(""); + return 1; + } + } + + printf("reading %ld bytes:\n", max(size_r, size_v)); + print_progress(0, blks * sizeof(g_block)); + set_addr(fd, address_in); + for (done = 0; done < size_r || done < size_v; ) { + read_block(fd, g_block, sizeof(g_block)); + if (f_r && done < size_r) { + len = min(size_r - done, sizeof(g_block)); + if (fwrite(g_block, 1, len, f_r) != len) { + perror("fwrite"); + return 1; + } + } + if (done < size_v) { + len = min(size_v - done, sizeof(g_block)); + if (fread(g_block2, 1, len, f_w) != len) { + perror("fread"); + return 1; + } + verify_diff |= memcmp(g_block, g_block2, len); + } + done += sizeof(g_block); + print_progress(done, blks * sizeof(g_block)); + } + if (verify_diff) { + fprintf(stderr, "verify FAILED\n"); + return 1; + } + } + if (f_r) + fclose(f_r); + if (f_w) + fclose(f_w); + + return 0; +}