2 * (C) Copyright 2014,2016,2018 Leo C. <erbl259-lmu@yahoo.de>
4 * SPDX-License-Identifier: GPL-2.0
8 * FAT filesystem commands
17 #include "con-utils.h"
18 #include "print-utils.h"
25 #define DEBUG_FA 0 /* set to 1 to debug */
27 #define debug_fa(fmt, args...) \
28 debug_cond(DEBUG_FA, fmt, ##args)
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
39 * Multible (fat) partitions per physical drive are not supported,
40 * but we have up to 2 sdcard slots.
45 command_ret_t command_ret
;
49 void setup_fatfs(void)
51 f_mount(&FatFs0
, "0:", 0);
52 f_mount(&FatFs1
, "1:", 0);
55 DWORD
get_fattime (void)
61 gmtime_r(&timer
, &tm_timer
);
63 return fatfs_time(&tm_timer
);
67 static bool check_abort(void)
72 printf_P(PSTR("Abort\n"));
78 static const FLASH
char * const FLASH rc_strings
[] = {
81 FSTR("internal error"),
88 FSTR("invalid object"),
89 FSTR("write protected"),
90 FSTR("invalid drive"),
92 FSTR("no file system"),
96 FSTR("not enough core"),
97 FSTR("too many open files"),
98 FSTR("invalid parameter")
101 static const FLASH
char * const FLASH rc_names
[] = {
108 FSTR("INVALID_NAME"),
111 FSTR("INVALID_OBJECT"),
112 FSTR("WRITE_PROTECTED"),
113 FSTR("INVALID_DRIVE"),
115 FSTR("NO_FILE_SYSTEM"),
116 FSTR("MKFS_ABORTED"),
119 FSTR("NOT_ENOUGH_CORE"),
120 FSTR("TOO_MANY_OPEN_FILES"),
121 FSTR("INVALID_PARAMETER")
125 void put_rc (FRESULT rc
)
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"));
132 printf_P(PSTR("rc=%u FR_%S\n"), rc
,
133 rc
< ARRAY_SIZE(rc_names
) ? rc_names
[rc
] : PSTR(" Unknown Error"));
138 const FLASH
char * rctostr(FRESULT rc
)
140 return rc
< ARRAY_SIZE(rc_strings
) ? rc_strings
[rc
] : PSTR(" Unknown Error");
143 static void swirl(void)
145 static const FLASH
char swirlchar
[] = { '-','\\','|','/' };
146 static uint_fast8_t cnt
;
147 static uint32_t tstamp
;
149 if (get_timer(0) > tstamp
) {
150 tstamp
= get_timer(0) + 250;
153 putchar(swirlchar
[cnt
]);
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 */
163 static char *path_skip_heading(char *p
)
165 if (isdigit(p
[0]) && p
[1] == ':') {
172 if (*q
== '\0' || *q
== '/')
183 static void strip_trailing_slash(PATH_T
*p
)
185 char *beg
= path_skip_heading(p
->p_path
);
186 char *end
= p
->p_end
;
188 while (end
> beg
&& end
[-1] == '/')
195 * Move specified string into path. Convert "" to "." to handle BSD
196 * semantics for a null path. Strip trailing slashes.
198 static PATH_T
*path_setup(char *string
)
200 if (strlen(string
) > PATH_MAX
) {
201 cmd_error(PSTR("'%s': name too long"), string
);
205 PATH_T
*p
= (PATH_T
*) malloc(sizeof *p
);
207 cmd_error(PSTR("'%s': Out of Memory"), string
);
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);
219 cmd_error(PSTR("'%s': Out of Memory"), string
);
224 p
->p_end
= p
->p_path
+ len
;
225 if (p
->p_path
== p
->p_end
) {
230 strip_trailing_slash(p
);
235 * pwd - Print current directory of the current drive.
238 command_ret_t
do_pwd(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc UNUSED
, char * const argv
[] UNUSED
)
243 buf
= (char *) malloc(PATH_MAX
);
245 printf_P(PSTR("pwd: Out of Memory!\n"));
246 return CMD_RET_FAILURE
;
249 res
= f_getcwd(buf
, PATH_MAX
); /* Get current directory path */
251 debug_fa("### f_getcwd(): buf: '%s', res: %d\n", buf
, res
);
259 return CMD_RET_FAILURE
;
261 return CMD_RET_SUCCESS
;
266 * cd - Change the current/working directory.
269 command_ret_t
do_cd(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc
, char * const argv
[])
275 arg
= getenv_str(PSTR(ENV_HOME
));
277 printf_P(PSTR("%s: \"%S\" is not set\n"), argv
[0], PSTR(ENV_HOME
));
278 return CMD_RET_FAILURE
;
283 PATH_T
*path
= path_setup(arg
);
285 return CMD_RET_FAILURE
;
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
);
292 res
= f_chdir(path
->p_path
);
293 debug_fa("### f_chdir(): p_path: '%s', res: %d\n", path
->p_path
, res
);
298 return CMD_RET_FAILURE
;
300 return CMD_RET_SUCCESS
;
305 * ls path - Directory listing
308 command_ret_t
do_ls(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc
, char * const argv
[])
311 DIR Dir
; /* Directory object */
319 buf
= (char *) malloc(PATH_MAX
);
321 printf_P(PSTR("pwd: Out of Memory!\n"));
322 return CMD_RET_FAILURE
;
326 res
= f_getcwd(buf
, PATH_MAX
); /* Get current directory path */
328 strncpy(buf
, argv
[1], PATH_MAX
);
331 res
= f_opendir(&Dir
, buf
);
335 return CMD_RET_FAILURE
;
340 res
= f_readdir(&Dir
, &Finfo
);
341 if ((res
!= FR_OK
) || !Finfo
.fname
[0])
343 if (Finfo
.fattrib
& AM_DIR
) {
346 s1
++; p1
+= Finfo
.fsize
;
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
);
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);
370 return CMD_RET_FAILURE
;
373 return CMD_RET_SUCCESS
;
378 FRESULT
mkpath(TCHAR
*path
)
386 res
= f_stat (path
, &fd
)
388 p
= strchr(path
, ':');
389 if (p
== NULL
|| *++p
== '\0' || *p
++ != '/')
392 while ((q
= strchr(p
, '/')) != NULL
) {
396 if (ret
!= FR_OK
&& ret
!= FR_EXIST
)
405 /* Work register for fs command */
408 WORD AccFiles
, AccDirs
;
414 char *path
, /* Pointer to the working buffer with start path */
415 struct stat_dat_s
*statp
423 res
= f_opendir(&dirs
, 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] == '.')
431 fn
= statp
->Finfo
.fname
;
432 if (statp
->Finfo
.fattrib
& AM_DIR
) {
435 strcpy(path
+i
+1, fn
);
436 res
= scan_files(path
, statp
);
441 //printf_P(PSTR("%s/%s\n"), path, fn);
443 statp
->AccSize
+= statp
->Finfo
.fsize
;
457 * fatstat path - Show logical drive status
460 command_ret_t
do_stat(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc
, char * const argv
[])
467 struct stat_dat_s statp
;
469 buf
= (char *) malloc(PATH_MAX
);
471 printf_P(PSTR("fat stat: Out of Memory!\n"));
472 return CMD_RET_FAILURE
;
477 res
= f_getfree(path
, &nfreeclst
, &fs
);
481 "Bytes/Cluster: %lu\n"
482 "Number of FATs: %u\n"
483 "Root DIR entries: %u\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
);
495 res
= f_getlabel(path
, buf
, &serial
);
499 "Volume S/N: %04X-%04X\n"),
500 buf
, (WORD
)(serial
>> 16), (WORD
)(serial
& 0xFFFF));
504 statp
.AccSize
= statp
.AccFiles
= statp
.AccDirs
= 0;
507 my_puts_P(PSTR("\nCounting... "));
508 res
= scan_files(buf
, &statp
);
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)
523 return CMD_RET_FAILURE
;
525 return CMD_RET_SUCCESS
;
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>
533 command_ret_t
do_rw(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc
, char * const argv
[])
539 unsigned long bytes_rw
;
541 bool dowrite
= (argv
[0][0] == 'w');
547 if (argc
< (dowrite
? 4 : 3))
548 return CMD_RET_USAGE
;
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
;
556 bytes
= eval_arg(argv
[3], NULL
);
560 pos
= eval_arg(argv
[4], NULL
);
564 if (addr
+ bytes
> MAX_MEMORY
)
565 bytes
= MAX_MEMORY
- addr
;
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
;
574 res
= f_open(&File
, argv
[1], dowrite
? FA_WRITE
| FA_CREATE_ALWAYS
578 res
= f_lseek(&File
, pos
);
581 timer
= get_timer(0);
583 unsigned int cnt
, br
;
585 if (bytes
>= BUFFER_SIZE
) {
587 bytes
-= BUFFER_SIZE
;
589 cnt
= bytes
; bytes
= 0;
592 if (!(z80_bus_cmd(Request
) & ZST_ACQUIRED
)) {
596 z80_read_block(buffer
, addr
, cnt
);
597 z80_bus_cmd(Release
);
598 res
= f_write(&File
, buffer
, cnt
, &br
);
602 res
= f_read(&File
, buffer
, cnt
, &br
);
605 if (!(z80_bus_cmd(Request
) & ZST_ACQUIRED
)) {
609 z80_write_block(buffer
, addr
, br
);
610 z80_bus_cmd(Release
);
617 printf_P(PSTR("Disk full?\n"));
624 FRESULT fr
= f_close(&File
);
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);
637 my_puts_P(PSTR("Bus timeout\n"));
641 return CMD_RET_FAILURE
;
643 return CMD_RET_SUCCESS
;
647 * command table for fat subcommands
650 cmd_tbl_t cmd_tbl_fat
[] = {
652 status
, 2, CTBL_RPT
, do_stat
,
653 "Show logical drive status",
657 pwd
, 2, CTBL_RPT
|CTBL_SUBCMDAUTO
, do_pwd
,
658 "Print name of current/working directory",
662 cd
, 2, 0|CTBL_SUBCMDAUTO
, do_cd
,
663 "Change the current/working directory.",
667 ls
, 2, CTBL_RPT
|CTBL_SUBCMDAUTO
, do_ls
,
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."
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"
691 help
, CONFIG_SYS_MAXARGS
, CTBL_RPT
, do_help
,
692 "Print sub command description/usage",
694 " - print brief description of all sub commands\n"
695 "fat help command ...\n"
696 " - print detailed usage of sub cmd 'command'"
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
,
702 #ifdef CONFIG_SYS_LONGHELP
704 #endif /* CONFIG_SYS_LONGHELP */
706 #ifdef CONFIG_AUTO_COMPLETE
710 /* Mark end of table */
711 CMD_TBL_END(cmd_tbl_fat
)
715 command_ret_t
do_fat(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc UNUSED
, char * const argv
[] UNUSED
)
717 puts_P(PSTR("Huch?"));
719 return CMD_RET_USAGE
;