initial version
[flashkit-mdc.git] / flashkit.c
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
43 enum 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
56 static 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
89 static 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
103 static 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
122 static 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
132 static 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
147 static 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
160 static 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
173 static 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
183 static 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
195 static 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?
224 static 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
234 static 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
241 static 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
254 static 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
306 static 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
327 static 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
345 static 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
360 static 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
369 static void *getarg(int argc, char *argv[], int arg)
370 {
371         if (arg >= argc)
372                 invarg(argc, argv, arg);
373         return argv[arg];
374 }
375
376 static uint8_t g_block[0x10000];
377 static uint8_t g_block2[0x10000];
378
379 int 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 }