]> cloudbase.mooo.com Git - z180-stamp.git/blob - avr/cmd_fat.c
new command: boot - boot default, i.e., run 'bootcmd'
[z180-stamp.git] / avr / cmd_fat.c
1 /*
2 * (C) Copyright 2014,2016,2018 Leo C. <erbl259-lmu@yahoo.de>
3 *
4 * SPDX-License-Identifier: GPL-2.0
5 */
6
7 /*
8 * FAT filesystem commands
9 */
10
11 #include "cmd_fat.h"
12 #include <ctype.h>
13
14 #include "ff.h"
15 #include "z80-if.h"
16 #include "eval_arg.h"
17 #include "con-utils.h"
18 #include "print-utils.h"
19 #include "time.h"
20 #include "timer.h"
21 #include "debug.h"
22 #include "env.h"
23
24
25 #define DEBUG_FA 0 /* set to 1 to debug */
26
27 #define debug_fa(fmt, args...) \
28 debug_cond(DEBUG_FA, fmt, ##args)
29
30
31 /* TODO: use memory size test function (detect_ramsize() in cmd_loadihex.c) */
32 /* TODO: detect_ramsize() should be moved to z80-if.c */
33 #define MAX_MEMORY CONFIG_SYS_RAMSIZE_MAX
34 #define BUFFER_SIZE FF_MAX_SS
35 #define PATH_MAX CONFIG_SYS_MAX_PATHLEN
36
37
38 /*
39 * Multible (fat) partitions per physical drive are not supported,
40 * but we have up to 2 sdcard slots.
41 */
42 FATFS FatFs0;
43 FATFS FatFs1;
44
45 command_ret_t command_ret;
46 char *cmdname;
47
48
49 void setup_fatfs(void)
50 {
51 f_mount(&FatFs0, "0:", 0);
52 f_mount(&FatFs1, "1:", 0);
53 }
54
55 DWORD get_fattime (void)
56 {
57 time_t timer;
58 struct tm tm_timer;
59
60 time(&timer);
61 gmtime_r(&timer, &tm_timer);
62
63 return fatfs_time(&tm_timer);
64 }
65
66
67 static bool check_abort(void)
68 {
69 bool ret = ctrlc();
70
71 if (ret)
72 printf_P(PSTR("Abort\n"));
73
74 return ret;
75 }
76
77
78 static const FLASH char * const FLASH rc_strings[] = {
79 FSTR("OK"),
80 FSTR("disk error"),
81 FSTR("internal error"),
82 FSTR("not ready"),
83 FSTR("no file"),
84 FSTR("no path"),
85 FSTR("invalid name"),
86 FSTR("denied"),
87 FSTR("exist"),
88 FSTR("invalid object"),
89 FSTR("write protected"),
90 FSTR("invalid drive"),
91 FSTR("not enabled"),
92 FSTR("no file system"),
93 FSTR("mkfs aborted"),
94 FSTR("timeout"),
95 FSTR("locked"),
96 FSTR("not enough core"),
97 FSTR("too many open files"),
98 FSTR("invalid parameter")
99 };
100
101 static const FLASH char * const FLASH rc_names[] = {
102 FSTR("OK"),
103 FSTR("DISK_ERR"),
104 FSTR("INT_ERR"),
105 FSTR("NOT_READY"),
106 FSTR("NO_FILE"),
107 FSTR("NO_PATH"),
108 FSTR("INVALID_NAME"),
109 FSTR("DENIED"),
110 FSTR("EXIST"),
111 FSTR("INVALID_OBJECT"),
112 FSTR("WRITE_PROTECTED"),
113 FSTR("INVALID_DRIVE"),
114 FSTR("NOT_ENABLED"),
115 FSTR("NO_FILE_SYSTEM"),
116 FSTR("MKFS_ABORTED"),
117 FSTR("TIMEOUT"),
118 FSTR("LOCKED"),
119 FSTR("NOT_ENOUGH_CORE"),
120 FSTR("TOO_MANY_OPEN_FILES"),
121 FSTR("INVALID_PARAMETER")
122 };
123
124 static
125 void put_rc (FRESULT rc)
126 {
127 #if GCC_BUG_61443
128 printf_P(PSTR("rc=%u FR_"), rc);
129 my_puts_P(rc < ARRAY_SIZE(rc_names) ? rc_names[rc] : PSTR(" Unknown Error"));
130 my_puts_P(PSTR("\n"));
131 #else
132 printf_P(PSTR("rc=%u FR_%S\n"), rc,
133 rc < ARRAY_SIZE(rc_names) ? rc_names[rc] : PSTR(" Unknown Error"));
134 #endif
135 }
136
137
138 const FLASH char * rctostr(FRESULT rc)
139 {
140 return rc < ARRAY_SIZE(rc_strings) ? rc_strings[rc] : PSTR(" Unknown Error");
141 }
142
143 static void swirl(void)
144 {
145 static const FLASH char swirlchar[] = { '-','\\','|','/' };
146 static uint_fast8_t cnt;
147 static uint32_t tstamp;
148
149 if (get_timer(0) > tstamp) {
150 tstamp = get_timer(0) + 250;
151 putchar('\b');
152 cnt = (cnt+1) & 3;
153 putchar(swirlchar[cnt]);
154 }
155 }
156
157 typedef struct {
158 char *p_end; /* pointer to NULL at end of path */
159 char p_path[PATH_MAX + 1]; /* pointer to the start of a path */
160 } PATH_T;
161
162
163 static char *path_skip_heading(char *p)
164 {
165 if (isdigit(p[0]) && p[1] == ':') {
166 p += 2;
167 } else {
168 char *q = p;
169 if (*q++ == '.') {
170 if (*q == '.')
171 ++q;
172 if (*q == '\0' || *q == '/')
173 p = q;
174 }
175 return p;
176 }
177 if (*p == '/')
178 ++p;
179
180 return p;
181 }
182
183 static void strip_trailing_slash(PATH_T *p)
184 {
185 char *beg = path_skip_heading(p->p_path);
186 char *end = p->p_end;
187
188 while (end > beg && end[-1] == '/')
189 *--end = '\0';
190
191 p->p_end =end;
192 }
193
194 /*
195 * Move specified string into path. Convert "" to "." to handle BSD
196 * semantics for a null path. Strip trailing slashes.
197 */
198 static PATH_T *path_setup(char *string)
199 {
200 if (strlen(string) > PATH_MAX) {
201 cmd_error(PSTR("'%s': name too long"), string);
202 return NULL;
203 }
204
205 PATH_T *p = (PATH_T *) malloc(sizeof *p);
206 if (p == NULL) {
207 cmd_error(PSTR("'%s': Out of Memory"), string);
208 return NULL;
209 }
210
211 strcpy(p->p_path, string);
212 size_t len = strlen(string);
213 if (len > 1 && p->p_path[1] == ':' && p->p_path[2] != '/') {
214 if (len < PATH_MAX) {
215 memmove(p->p_path+3, p->p_path+2, len-1);
216 p->p_path[2] = '/';
217 len += 1;
218 } else {
219 cmd_error(PSTR("'%s': Out of Memory"), string);
220 return NULL;
221 }
222 }
223
224 p->p_end = p->p_path + len;
225 if (p->p_path == p->p_end) {
226 *p->p_end++ = '.';
227 *p->p_end = '\0';
228 }
229
230 strip_trailing_slash(p);
231 return p;
232 }
233
234 /*
235 * pwd - Print current directory of the current drive.
236 *
237 */
238 command_ret_t do_pwd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED)
239 {
240 FRESULT res;
241 char *buf;
242
243 buf = (char *) malloc(PATH_MAX);
244 if (buf == NULL) {
245 printf_P(PSTR("pwd: Out of Memory!\n"));
246 return CMD_RET_FAILURE;
247 }
248 *buf = '\0';
249 res = f_getcwd(buf, PATH_MAX); /* Get current directory path */
250
251 debug_fa("### f_getcwd(): buf: '%s', res: %d\n", buf, res);
252
253 if (res == FR_OK) {
254 puts(buf);
255 }
256 free(buf);
257 if (res != FR_OK) {
258 put_rc(res);
259 return CMD_RET_FAILURE;
260 }
261 return CMD_RET_SUCCESS;
262 }
263
264
265 /*
266 * cd - Change the current/working directory.
267 *
268 */
269 command_ret_t do_cd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
270 {
271 char *arg;
272 FRESULT res = FR_OK;
273
274 if (argc < 2) {
275 arg = getenv_str(PSTR(ENV_HOME));
276 if (arg == NULL) {
277 printf_P(PSTR("%s: \"%S\" is not set\n"), argv[0], PSTR(ENV_HOME));
278 return CMD_RET_FAILURE;
279 }
280 } else
281 arg = argv[1];
282
283 PATH_T *path = path_setup(arg);
284 if (path == NULL)
285 return CMD_RET_FAILURE;
286
287 if (strlen(path->p_path) > 1 && path->p_path[1] == ':') {
288 res = f_chdrive(path->p_path);
289 debug_fa("### f_chdrive(): p_path: '%s', res: %d\n", path->p_path, res);
290 }
291 if (res == FR_OK) {
292 res = f_chdir(path->p_path);
293 debug_fa("### f_chdir(): p_path: '%s', res: %d\n", path->p_path, res);
294 }
295 free(path);
296 if (res != FR_OK) {
297 put_rc(res);
298 return CMD_RET_FAILURE;
299 }
300 return CMD_RET_SUCCESS;
301 }
302
303
304 /*
305 * ls path - Directory listing
306 *
307 */
308 command_ret_t do_ls(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
309 {
310 FATFS *fs;
311 DIR Dir; /* Directory object */
312 FILINFO Finfo;
313 unsigned long p1;
314 unsigned int s1, s2;
315 FRESULT res = FR_OK;
316 char *buf;
317
318
319 buf = (char *) malloc(PATH_MAX);
320 if (buf == NULL) {
321 printf_P(PSTR("pwd: Out of Memory!\n"));
322 return CMD_RET_FAILURE;
323 }
324
325 if (argc < 2)
326 res = f_getcwd(buf, PATH_MAX); /* Get current directory path */
327 else
328 strncpy(buf, argv[1], PATH_MAX);
329
330 if (res == FR_OK)
331 res = f_opendir(&Dir, buf);
332 if (res != FR_OK) {
333 free(buf);
334 put_rc(res);
335 return CMD_RET_FAILURE;
336 }
337
338 p1 = s1 = s2 = 0;
339 for(;;) {
340 res = f_readdir(&Dir, &Finfo);
341 if ((res != FR_OK) || !Finfo.fname[0])
342 break;
343 if (Finfo.fattrib & AM_DIR) {
344 s2++;
345 } else {
346 s1++; p1 += Finfo.fsize;
347 }
348 printf_P(PSTR("%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu %s\n"),
349 (Finfo.fattrib & AM_DIR) ? 'D' : '-',
350 (Finfo.fattrib & AM_RDO) ? 'R' : '-',
351 (Finfo.fattrib & AM_HID) ? 'H' : '-',
352 (Finfo.fattrib & AM_SYS) ? 'S' : '-',
353 (Finfo.fattrib & AM_ARC) ? 'A' : '-',
354 (Finfo.fdate >> 9) + 1980, (Finfo.fdate >> 5) & 15, Finfo.fdate & 31,
355 (Finfo.ftime >> 11), (Finfo.ftime >> 5) & 63,
356 Finfo.fsize, Finfo.fname);
357 if (check_abort())
358 break;
359 }
360
361 if (res == FR_OK) {
362 printf_P(PSTR("%4u File(s),%10lu bytes total\n%4u Dir(s)"), s1, p1, s2);
363 if (f_getfree(buf, (DWORD*)&p1, &fs) == FR_OK)
364 printf_P(PSTR(", %10luK bytes free\n"), p1 * fs->csize / 2);
365 }
366
367 free(buf);
368 if (res) {
369 put_rc(res);
370 return CMD_RET_FAILURE;
371 }
372
373 return CMD_RET_SUCCESS;
374 }
375
376 #if 0
377 static
378 FRESULT mkpath(TCHAR *path)
379 {
380 /* TODO: */
381 (void) path;
382 FILINFO fd
383 TCHAR *p, *q;
384 FRESULT ret;
385
386 res = f_stat (path, &fd)
387
388 p = strchr(path, ':');
389 if (p == NULL || *++p == '\0' || *p++ != '/')
390 return FR_OK;
391
392 while ((q = strchr(p, '/')) != NULL) {
393 *q = '\0';
394 ret = f_mkdir(path);
395 *q = '/';
396 if (ret != FR_OK && ret != FR_EXIST)
397 return ret;
398 p = q + 1;
399 }
400
401 return FR_OK;
402 }
403 #endif
404
405 /* Work register for fs command */
406 struct stat_dat_s {
407 DWORD AccSize;
408 WORD AccFiles, AccDirs;
409 FILINFO Finfo;
410 };
411
412 static
413 FRESULT scan_files (
414 char *path, /* Pointer to the working buffer with start path */
415 struct stat_dat_s *statp
416 )
417 {
418 DIR dirs;
419 FRESULT res;
420 int i;
421 char *fn;
422
423 res = f_opendir(&dirs, path);
424 swirl();
425 if (res == FR_OK) {
426 i = strlen(path);
427 while (((res = f_readdir(&dirs, &statp->Finfo)) == FR_OK) &&
428 statp->Finfo.fname[0]) {
429 if (FF_FS_RPATH && statp->Finfo.fname[0] == '.')
430 continue;
431 fn = statp->Finfo.fname;
432 if (statp->Finfo.fattrib & AM_DIR) {
433 statp->AccDirs++;
434 path[i] = '/';
435 strcpy(path+i+1, fn);
436 res = scan_files(path, statp);
437 path[i] = '\0';
438 if (res != FR_OK)
439 break;
440 } else {
441 //printf_P(PSTR("%s/%s\n"), path, fn);
442 statp->AccFiles++;
443 statp->AccSize += statp->Finfo.fsize;
444 }
445 if (check_abort()) {
446 res = 255;
447 break;
448 }
449 }
450 }
451
452 return res;
453 }
454
455
456 /*
457 * fatstat path - Show logical drive status
458 *
459 */
460 command_ret_t do_stat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
461 {
462 FATFS *fs;
463 DWORD nfreeclst;
464 FRESULT res;
465 char *buf;
466 char *path = "";
467 struct stat_dat_s statp;
468
469 buf = (char *) malloc(PATH_MAX);
470 if (buf == NULL) {
471 printf_P(PSTR("fat stat: Out of Memory!\n"));
472 return CMD_RET_FAILURE;
473 }
474
475 if (argc > 1)
476 path = argv[1];
477 res = f_getfree(path, &nfreeclst, &fs);
478 if (!res) {
479 printf_P(PSTR(
480 "FAT type: %u\n"
481 "Bytes/Cluster: %lu\n"
482 "Number of FATs: %u\n"
483 "Root DIR entries: %u\n"
484 "Sectors/FAT: %lu\n"
485 "Number of clusters: %lu\n"
486 "FAT start (lba): %lu\n"
487 "DIR start (lba,cluster): %lu\n"
488 "Data start (lba): %lu\n"),
489 fs->fs_type, (DWORD)fs->csize * 512, fs->n_fats,
490 fs->n_rootdir, fs->fsize, fs->n_fatent - 2,
491 fs->fatbase, fs->dirbase, fs->database);
492
493 #if FF_USE_LABEL
494 DWORD serial;
495 res = f_getlabel(path, buf, &serial);
496 if (!res) {
497 printf_P(PSTR(
498 "Volume name: %s\n"
499 "Volume S/N: %04X-%04X\n"),
500 buf, (WORD)(serial >> 16), (WORD)(serial & 0xFFFF));
501 }
502 #endif
503 if (!res) {
504 statp.AccSize = statp.AccFiles = statp.AccDirs = 0;
505 strcpy(buf, path);
506
507 my_puts_P(PSTR("\nCounting... "));
508 res = scan_files(buf, &statp);
509 putchar('\r');
510 }
511 if (!res) {
512 printf_P(PSTR("%u files, %lu bytes.\n%u folders.\n"
513 "%lu KB total disk space.\n%lu KB available.\n"),
514 statp.AccFiles, statp.AccSize, statp.AccDirs,
515 (fs->n_fatent - 2) * (fs->csize / 2), nfreeclst * (fs->csize / 2)
516 );
517 }
518 }
519
520 free(buf);
521 if (res) {
522 put_rc(res);
523 return CMD_RET_FAILURE;
524 }
525 return CMD_RET_SUCCESS;
526 }
527
528 /*
529 * fatread/write - load binary file to/from a dos filesystem
530 * read <d:/path/filename> <addr> [bytes [pos]]
531 * write <d:/path/filename> <addr> <bytes>
532 */
533 command_ret_t do_rw(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[])
534 {
535 FIL File;
536 uint32_t addr;
537 unsigned long bytes;
538 unsigned long pos;
539 unsigned long bytes_rw;
540
541 bool dowrite = (argv[0][0] == 'w');
542 FRESULT res = FR_OK;
543 bool buserr = 0;
544 uint32_t timer;
545 uint8_t *buffer;
546
547 if (argc < (dowrite ? 4 : 3))
548 return CMD_RET_USAGE;
549
550 addr = eval_arg(argv[2], NULL);
551 if (addr >= MAX_MEMORY) {
552 printf_P(PSTR("address too high: 0x%0lx\n"), addr);
553 return CMD_RET_FAILURE;
554 }
555 if (argc > 3)
556 bytes = eval_arg(argv[3], NULL);
557 else
558 bytes = MAX_MEMORY;
559 if (argc > 4)
560 pos = eval_arg(argv[4], NULL);
561 else
562 pos = 0;
563
564 if (addr + bytes > MAX_MEMORY)
565 bytes = MAX_MEMORY - addr;
566
567 buffer = (uint8_t *) malloc(BUFFER_SIZE);
568 if (buffer == NULL) {
569 printf_P(PSTR("fatstat: Out of Memory!\n"));
570 return CMD_RET_FAILURE;
571 }
572
573 if (!res) {
574 res = f_open(&File, argv[1], dowrite ? FA_WRITE | FA_CREATE_ALWAYS
575 : FA_READ );
576
577 if (!res) {
578 res = f_lseek(&File, pos);
579 if (!res) {
580 bytes_rw = 0;
581 timer = get_timer(0);
582 while (bytes) {
583 unsigned int cnt, br;
584
585 if (bytes >= BUFFER_SIZE) {
586 cnt = BUFFER_SIZE;
587 bytes -= BUFFER_SIZE;
588 } else {
589 cnt = bytes; bytes = 0;
590 }
591 if (dowrite) {
592 if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) {
593 buserr = 1;
594 break;
595 }
596 z80_read_block(buffer, addr, cnt);
597 z80_bus_cmd(Release);
598 res = f_write(&File, buffer, cnt, &br);
599 if (res != FR_OK)
600 break;
601 } else {
602 res = f_read(&File, buffer, cnt, &br);
603 if (res != FR_OK)
604 break;
605 if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) {
606 buserr = 1;
607 break;
608 }
609 z80_write_block(buffer, addr, br);
610 z80_bus_cmd(Release);
611 }
612 addr += br;
613
614 bytes_rw += br;
615 if (cnt != br) {
616 if (dowrite)
617 printf_P(PSTR("Disk full?\n"));
618 break;
619 }
620 if (check_abort())
621 break;
622 }
623
624 FRESULT fr = f_close(&File);
625 if (!res)
626 res = fr;
627 timer = get_timer(timer);
628 printf_P(PSTR("%lu (0x%lx) bytes read/written with %lu bytes/sec.\n"),
629 bytes_rw, bytes_rw, timer ? (bytes_rw * 1000 / timer) : 0);
630 }
631 }
632 }
633
634 free(buffer);
635
636 if (buserr)
637 my_puts_P(PSTR("Bus timeout\n"));
638 if (res)
639 put_rc(res);
640 if (buserr || res)
641 return CMD_RET_FAILURE;
642
643 return CMD_RET_SUCCESS;
644 }
645
646 /*
647 * command table for fat subcommands
648 */
649
650 cmd_tbl_t cmd_tbl_fat[] = {
651 CMD_TBL_ITEM(
652 status, 2, CTBL_RPT, do_stat,
653 "Show logical drive status",
654 "dev"
655 ),
656 CMD_TBL_ITEM(
657 pwd, 2, CTBL_RPT|CTBL_SUBCMDAUTO, do_pwd,
658 "Print name of current/working directory",
659 ""
660 ),
661 CMD_TBL_ITEM(
662 cd, 2, 0|CTBL_SUBCMDAUTO, do_cd,
663 "Change the current/working directory.",
664 "path"
665 ),
666 CMD_TBL_ITEM(
667 ls, 2, CTBL_RPT|CTBL_SUBCMDAUTO, do_ls,
668 "Directory listing",
669 "path"
670 ),
671 CMD_TBL_ITEM(
672 load, 5, 0, do_rw,
673 "load binary file from a dos filesystem",
674 "<d:/path/filename> <addr> [bytes [pos]]\n"
675 " - Load binary file 'path/filename' on logical drive 'd'\n"
676 " to address 'addr' from dos filesystem.\n"
677 " 'pos' gives the file position to start loading from.\n"
678 " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n"
679 " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n"
680 " the load stops on end of file."
681 ),
682 CMD_TBL_ITEM(
683 write, 4, 0, do_rw,
684 "write file into a dos filesystem",
685 "<d:/path/filename> <addr> <bytes>\n"
686 " - Write file to 'path/filename' on logical drive 'd' from RAM\n"
687 " starting at address 'addr'.\n"
688 ),
689
690 CMD_TBL_ITEM(
691 help, CONFIG_SYS_MAXARGS, CTBL_RPT, do_help,
692 "Print sub command description/usage",
693 "\n"
694 " - print brief description of all sub commands\n"
695 "fat help command ...\n"
696 " - print detailed usage of sub cmd 'command'"
697 ),
698
699 /* This does not use the CMD_TBL_ITEM macro as ? can't be used in symbol names */
700 {FSTR("?"), CONFIG_SYS_MAXARGS, 1, do_help,
701 NULL,
702 #ifdef CONFIG_SYS_LONGHELP
703 FSTR(""),
704 #endif /* CONFIG_SYS_LONGHELP */
705 NULL,
706 #ifdef CONFIG_AUTO_COMPLETE
707 NULL,
708 #endif
709 },
710 /* Mark end of table */
711 CMD_TBL_END(cmd_tbl_fat)
712 };
713
714
715 command_ret_t do_fat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED)
716 {
717 puts_P(PSTR("Huch?"));
718
719 return CMD_RET_USAGE;
720 }