initial version
[flashkit-mdc.git] / flashkit.c
CommitLineData
d9502b8d 1/*
2 * Tool for USB serial communication with Krikzz's FlashKit-MD
3 * Copyright (c) 2017 notaz
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
20 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
21 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
22 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE.
24 */
25
26#include <stdio.h>
27#include <string.h>
28#include <stdlib.h>
29#include <stdint.h>
30#include <assert.h>
31#include <sys/types.h>
32#include <sys/stat.h>
33#include <fcntl.h>
34#include <arpa/inet.h> // hton
35#include <termios.h>
36#include <unistd.h>
37
38#ifndef min
39#define min(x, y) ((x) < (y) ? (x) : (y))
40#define max(x, y) ((x) > (y) ? (x) : (y))
41#endif
42
43enum dev_cmd {
44 CMD_ADDR = 0,
45 CMD_LEN = 1,
46 CMD_RD = 2,
47 CMD_WR = 3,
48 CMD_RY = 4,
49 CMD_DELAY = 5,
50};
51#define PAR_MODE8 (1 << 4)
52#define PAR_DEV_ID (1 << 5)
53#define PAR_SINGE (1 << 6)
54#define PAR_INC (1 << 7)
55
56static int setup(int fd)
57{
58 struct termios tty;
59 int ret;
60
61 memset(&tty, 0, sizeof(tty));
62
63 ret = tcgetattr(fd, &tty);
64 if (ret != 0)
65 {
66 perror("tcgetattr");
67 return 1;
68 }
69
70 tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
71 | INLCR | IGNCR | ICRNL | IXON);
72 tty.c_oflag &= ~OPOST;
73 tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
74 tty.c_cflag &= ~(CSIZE | PARENB);
75 tty.c_cflag |= CS8;
76
77 //tty.c_cc[VMIN] = 1;
78 //tty.c_cc[VTIME] = 5; // 0.5 seconds read timeout
79
80 ret = tcsetattr(fd, TCSANOW, &tty);
81 if (ret != 0) {
82 perror("tcsetattr");
83 return ret;
84 }
85
86 return 0;
87}
88
89static int write_serial(int fd, const void *data, size_t size)
90{
91 int ret;
92
93 ret = write(fd, data, size);
94 if (ret != size) {
95 fprintf(stderr, "write %d/%zd: ", ret, size);
96 perror("");
97 exit(1);
98 }
99
100 return 0;
101}
102
103static int read_serial(int fd, void *data, size_t size)
104{
105 size_t got = 0;
106 int ret;
107
108 while (got < size) {
109 ret = read(fd, (char *)data + got, size - got);
110 if (ret <= 0) {
111 fprintf(stderr, "read %d %zd/%zd: ",
112 ret, got, size);
113 perror("");
114 exit(1);
115 }
116 got += ret;
117 }
118
119 return 0;
120}
121
122static void set_addr(int fd, uint32_t addr)
123{
124 uint8_t cmd[6] = {
125 CMD_ADDR, addr >> 17,
126 CMD_ADDR, addr >> 9,
127 CMD_ADDR, addr >> 1
128 };
129 write_serial(fd, cmd, sizeof(cmd));
130}
131
132static uint16_t read_word(int fd, uint32_t addr)
133{
134 uint8_t cmd[7] = {
135 CMD_ADDR, addr >> 17,
136 CMD_ADDR, addr >> 9,
137 CMD_ADDR, addr >> 1,
138 CMD_RD | PAR_SINGE
139 };
140 uint16_t r;
141
142 write_serial(fd, cmd, sizeof(cmd));
143 read_serial(fd, &r, sizeof(r));
144 return ntohs(r);
145}
146
147static void write_word(int fd, uint32_t addr, uint16_t d)
148{
149 uint8_t cmd[9] = {
150 CMD_ADDR, addr >> 17,
151 CMD_ADDR, addr >> 9,
152 CMD_ADDR, addr >> 1,
153 CMD_WR | PAR_SINGE,
154 d >> 8, d
155 };
156
157 write_serial(fd, cmd, sizeof(cmd));
158}
159
160static void read_block(int fd, void *dst, uint32_t size)
161{
162 uint8_t cmd[5] = {
163 CMD_LEN, size >> 9,
164 CMD_LEN, size >> 1,
165 CMD_RD | PAR_INC
166 };
167
168 assert(size <= 0x10000);
169 write_serial(fd, cmd, sizeof(cmd));
170 read_serial(fd, dst, size);
171}
172
173static uint16_t flash_seq_r(int fd, uint8_t cmd, uint32_t addr)
174{
175 // unlock
176 write_word(fd, 0xaaa, 0xaa);
177 write_word(fd, 0x555, 0x55);
178
179 write_word(fd, 0xaaa, cmd);
180 return read_word(fd, addr);
181}
182
183static void flash_seq_erase(int fd, uint32_t addr)
184{
185 // printf("erase %06x\n", addr);
186 write_word(fd, 0xaaa, 0xaa);
187 write_word(fd, 0x555, 0x55);
188 write_word(fd, 0xaaa, 0x80);
189
190 write_word(fd, 0xaaa, 0xaa);
191 write_word(fd, 0x555, 0x55);
192 write_word(fd, addr, 0x30);
193}
194
195static void flash_seq_write(int fd, uint32_t addr, uint8_t *d)
196{
197 uint8_t cmd[] = {
198 // unlock
199 CMD_ADDR, 0,
200 CMD_ADDR, 0x05,
201 CMD_ADDR, 0x55,
202 CMD_WR | PAR_SINGE | PAR_MODE8, 0xaa,
203 CMD_ADDR, 0,
204 CMD_ADDR, 0x02,
205 CMD_ADDR, 0xaa,
206 CMD_WR | PAR_SINGE | PAR_MODE8, 0x55,
207 // program setup
208 CMD_ADDR, 0,
209 CMD_ADDR, 0x05,
210 CMD_ADDR, 0x55,
211 CMD_WR | PAR_SINGE | PAR_MODE8, 0xa0,
212 // program data
213 CMD_ADDR, addr >> 17,
214 CMD_ADDR, addr >> 9,
215 CMD_ADDR, addr >> 1,
216 CMD_WR | PAR_SINGE, d[0], d[1],
217 CMD_RY
218 };
219
220 write_serial(fd, cmd, sizeof(cmd));
221}
222
223// status wait + dummy read to cause a wait?
224static uint16_t ry_read(int fd)
225{
226 uint8_t cmd[2] = { CMD_RY, CMD_RD | PAR_SINGE };
227 uint16_t rv = 0;
228
229 write_serial(fd, cmd, sizeof(cmd));
230 read_serial(fd, &rv, sizeof(rv));
231 return ntohs(rv);
232}
233
234static void set_delay(int fd, uint8_t delay)
235{
236 uint8_t cmd[2] = { CMD_DELAY, delay };
237
238 write_serial(fd, cmd, sizeof(cmd));
239}
240
241static struct flash_info {
242 uint16_t mid;
243 uint16_t did;
244 uint32_t size;
245 uint16_t region_cnt;
246 struct {
247 uint32_t block_size;
248 uint32_t block_count;
249 uint32_t start;
250 uint32_t size;
251 } region[4];
252} info;
253
254static void read_info(int fd)
255{
256 static const uint16_t qry[3] = { 'Q', 'R', 'Y' };
257 uint32_t total = 0;
258 uint16_t resp[3];
259 uint32_t i, a;
260
261 info.mid = flash_seq_r(fd, 0x90, 0); // autoselect
262 info.did = read_word(fd, 2);
263
264 // could enter CFI directly, but there seems to be a "stack"
265 // of modes, so 2 exits would be needed
266 write_word(fd, 0, 0xf0);
267
268 write_word(fd, 0xaa, 0x98); // CFI Query
269 resp[0] = read_word(fd, 0x20);
270 resp[1] = read_word(fd, 0x22);
271 resp[2] = read_word(fd, 0x24);
272 if (memcmp(resp, qry, sizeof(resp))) {
273 fprintf(stderr, "unexpected CFI response: %04x %04x %04x\n",
274 resp[0], resp[1], resp[2]);
275 exit(1);
276 }
277 info.size = 1u << read_word(fd, 0x4e);
278 info.region_cnt = read_word(fd, 0x58);
279 assert(0 < info.region_cnt && info.region_cnt <= 4);
280 for (i = 0, a = 0x5a; i < info.region_cnt; i++, a += 8) {
281 info.region[i].block_count = read_word(fd, a + 0) + 1;
282 info.region[i].block_count += read_word(fd, a + 2) << 8;
283 info.region[i].block_size = read_word(fd, a + 4) << 8;
284 info.region[i].block_size |= read_word(fd, a + 6) << 16;
285 info.region[i].start = total;
286 info.region[i].size =
287 info.region[i].block_size * info.region[i].block_count;
288 assert(info.region[i].size);
289 total += info.region[i].size;
290 }
291
292 write_word(fd, 0, 0xf0); // flash reset
293
294 printf("Flash info:\n");
295 printf("Manufacturer ID: %04x\n", info.mid);
296 printf("Device ID: %04x\n", info.did);
297 printf("size: %u\n", info.size);
298 printf("Erase Block Regions: %u\n", info.region_cnt);
299 for (i = 0; i < info.region_cnt; i++)
300 printf(" %5u x %u\n", info.region[i].block_size,
301 info.region[i].block_count);
302 if (info.size != total)
303 fprintf(stderr, "warning: total is %u, bad CFI?\n", total);
304}
305
306static uint32_t get_block_addr(uint32_t addr, uint32_t blk_offset)
307{
308 uint32_t i;
309
310 assert(info.region_cnt);
311 for (i = 0; i < info.region_cnt; i++) {
312 if (info.region[i].start <= addr
313 && addr < info.region[i].start + info.region[i].size)
314 {
315 uint32_t blk = (addr - info.region[i].start)
316 / info.region[i].block_size
317 + blk_offset;
318 return info.region[i].start
319 + blk * info.region[i].block_size;
320 }
321 }
322
323 fprintf(stderr, "\naddress out of range: 0x%x\n", addr);
324 exit(1);
325}
326
327static void print_progress(uint32_t done, uint32_t total)
328{
329 int i, step;
330
331 printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b");
332 printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b"); /* 20 */
333 printf("\b\b\b\b\b\b");
334 printf("%06x/%06x |", done, total);
335
336 step = (total + 19) / 20;
337 for (i = step; i <= total; i += step)
338 fputc(done >= i ? '=' : '-', stdout);
339 printf("| %3d%%", done * 100 / total);
340 fflush(stdout);
341 if (done >= total)
342 fputc('\n', stdout);
343}
344
345static void usage(const char *argv0)
346{
347 printf("usage:\n"
348 "%s [options]\n"
349 " -d <ttydevice> (default /dev/ttyUSB0)\n"
350 " -r <file> [size] dump the cart (default 4MB)\n"
351 " -w <file> [size] flash the cart (file size)\n"
352 " -e <size> erase (rounds to block size)\n"
353 " -a <start_address> cart start address (default 0)\n"
354 " -v verify written data\n"
355 " -i get info about the flash chip\n"
356 , argv0);
357 exit(1);
358}
359
360static void invarg(int argc, char *argv[], int arg)
361{
362 if (arg < argc)
363 fprintf(stderr, "invalid arg %d: \"%s\"\n", arg, argv[arg]);
364 else
365 fprintf(stderr, "missing required argument %d\n", arg);
366 exit(1);
367}
368
369static void *getarg(int argc, char *argv[], int arg)
370{
371 if (arg >= argc)
372 invarg(argc, argv, arg);
373 return argv[arg];
374}
375
376static uint8_t g_block[0x10000];
377static uint8_t g_block2[0x10000];
378
379int main(int argc, char *argv[])
380{
381 const char *portname = "/dev/ttyUSB0";
382 const char *fname_w = NULL;
383 const char *fname_r = NULL;
384 long size_w = 0;
385 long size_r = 0;
386 long size_e = 0;
387 long size_v = 0;
388 long len, address_in = 0;
389 long a, a_blk, end;
390 int do_info = 0;
391 int do_verify = 0;
392 FILE *f_w = NULL;
393 FILE *f_r = NULL;
394 uint8_t id[2] = { 0, 0 };
395 uint8_t cmd;
396 uint16_t rv;
397 int arg = 1;
398 int fd;
399
400 if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help"))
401 usage(argv[0]);
402
403 for (arg = 1; arg < argc; arg++) {
404 if (!strcmp(argv[arg], "-d")) {
405 portname = getarg(argc, argv, ++arg);
406 continue;
407 }
408 if (!strcmp(argv[arg], "-r")) {
409 fname_r = getarg(argc, argv, ++arg);
410 if (arg + 1 < argc && argv[arg + 1][0] != '-') {
411 size_r = strtol(argv[++arg], NULL, 0);
412 if (size_r <= 0)
413 invarg(argc, argv, arg);
414 }
415 continue;
416 }
417 if (!strcmp(argv[arg], "-w")) {
418 fname_w = getarg(argc, argv, ++arg);
419 if (arg + 1 < argc && argv[arg + 1][0] != '-') {
420 size_w = strtol(argv[++arg], NULL, 0);
421 if (size_w <= 0)
422 invarg(argc, argv, arg);
423 }
424 continue;
425 }
426 if (!strcmp(argv[arg], "-a")) {
427 address_in = strtol(getarg(argc, argv, ++arg), NULL, 0);
428 if (address_in < 0 || (address_in & 1))
429 invarg(argc, argv, arg);
430 continue;
431 }
432 if (!strcmp(argv[arg], "-e")) {
433 size_e = strtol(getarg(argc, argv, ++arg), NULL, 0);
434 if (size_e <= 0)
435 invarg(argc, argv, arg);
436 continue;
437 }
438 if (!strcmp(argv[arg], "-v")) {
439 do_verify = 1;
440 continue;
441 }
442 if (!strcmp(argv[arg], "-i")) {
443 do_info = 1;
444 continue;
445 }
446 invarg(argc, argv, arg);
447 }
448
449 if (fname_r && size_r == 0)
450 size_r = 0x400000;
451
452 if (fname_w) {
453 f_w = fopen(fname_w, "rb");
454 if (!f_w) {
455 fprintf(stderr, "fopen %s: ", fname_w);
456 perror("");
457 return 1;
458 }
459 if (size_w <= 0) {
460 fseek(f_w, 0, SEEK_END);
461 size_w = ftell(f_w);
462 fseek(f_w, 0, SEEK_SET);
463 }
464 if (size_w <= 0) {
465 fprintf(stderr, "size of %s is %ld\n",
466 fname_w, size_w);
467 return 1;
468 }
469 if (size_e < size_w)
470 size_e = size_w;
471 if (do_verify)
472 size_v = size_w;
473 }
474
475 fd = open(portname, O_RDWR | O_NOCTTY | O_SYNC);
476 if (fd < 0) {
477 fprintf(stderr, "open %s: ", portname);
478 perror("");
479 return 1;
480 }
481
482 setup(fd);
483
484 cmd = CMD_RD | PAR_SINGE | PAR_DEV_ID;
485 write_serial(fd, &cmd, sizeof(cmd));
486 read_serial(fd, id, sizeof(id));
487 if (id[0] != id[1] || id[0] == 0) {
488 fprintf(stderr, "unexpected id: %02x %02x\n", id[0], id[1]);
489 return 1;
490 }
491 printf("flashkit id: %02x\n", id[0]);
492
493 set_delay(fd, 1);
494 write_word(fd, 0, 0xf0); // flash reset
495
496 if (do_info || size_e)
497 read_info(fd);
498
499 if (size_e) {
500 // set_delay(fd, 0); // ?
501 a_blk = get_block_addr(address_in, 0);
502 end = address_in + size_e;
503
504 printf("erasing %ld bytes:\n", size_e);
505 print_progress(0, size_e);
506 for (a = address_in; a < end; ) {
507 flash_seq_erase(fd, a_blk);
508 rv = ry_read(fd);
509 if (rv != 0xffff) {
510 fprintf(stderr, "\nerase error: %lx %04x\n",
511 a_blk, rv);
512 }
513
514 a_blk = get_block_addr(a_blk, 1);
515 a += a_blk - a;
516 print_progress(a - address_in, size_e);
517 }
518 }
519 if (f_w != NULL) {
520 uint8_t b[2];
521 set_delay(fd, 0);
522 printf("writing %ld bytes:\n", size_w);
523 for (a = 0; a < size_w; a += 2) {
524 ssize_t r;
525
526 b[1] = 0xff;
527 len = min(size_w - a, 2);
528 r = fread(b, 1, len, f_w);
529 if (r != len) {
530 perror("\nfread");
531 return 1;
532 }
533 flash_seq_write(fd, address_in + a, b);
534
535 if (!(a & 0x3fe))
536 print_progress(a, size_w);
537 }
538 print_progress(a, size_w);
539 rv = ry_read(fd);
540 if (rv != ((b[0] << 8) | b[1]))
541 fprintf(stderr, "warning: last bytes: %04x %02x%02x\n",
542 rv, b[0], b[1]);
543 rewind(f_w);
544 set_delay(fd, 1);
545 }
546
547 if (fname_r || size_v) {
548 long blks, blks_v, done, verify_diff = 0;
549
550 blks = (size_r + sizeof(g_block) - 1) / sizeof(g_block);
551 blks_v = (size_v + sizeof(g_block) - 1) / sizeof(g_block);
552 blks = max(blks, blks_v);
553 if (fname_r) {
554 f_r = fopen(fname_r, "wb");
555 if (!f_r) {
556 fprintf(stderr, "fopen %s: ", fname_r);
557 perror("");
558 return 1;
559 }
560 }
561
562 printf("reading %ld bytes:\n", max(size_r, size_v));
563 print_progress(0, blks * sizeof(g_block));
564 set_addr(fd, address_in);
565 for (done = 0; done < size_r || done < size_v; ) {
566 read_block(fd, g_block, sizeof(g_block));
567 if (f_r && done < size_r) {
568 len = min(size_r - done, sizeof(g_block));
569 if (fwrite(g_block, 1, len, f_r) != len) {
570 perror("fwrite");
571 return 1;
572 }
573 }
574 if (done < size_v) {
575 len = min(size_v - done, sizeof(g_block));
576 if (fread(g_block2, 1, len, f_w) != len) {
577 perror("fread");
578 return 1;
579 }
580 verify_diff |= memcmp(g_block, g_block2, len);
581 }
582 done += sizeof(g_block);
583 print_progress(done, blks * sizeof(g_block));
584 }
585 if (verify_diff) {
586 fprintf(stderr, "verify FAILED\n");
587 return 1;
588 }
589 }
590 if (f_r)
591 fclose(f_r);
592 if (f_w)
593 fclose(f_w);
594
595 return 0;
596}