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")
103 const FLASH
char * fat_rctostr(FRESULT rc
)
105 return rc
< ARRAY_SIZE(rc_strings
) ? rc_strings
[rc
] : PSTR(" Unknown Error");
108 static void swirl(void)
110 static const FLASH
char swirlchar
[] = { '-','\\','|','/' };
111 static uint_fast8_t cnt
;
112 static uint32_t tstamp
;
114 if (get_timer(0) > tstamp
) {
115 tstamp
= get_timer(0) + 250;
118 putchar(swirlchar
[cnt
]);
123 char *p_end
; /* pointer to NULL at end of path */
124 char p_path
[PATH_MAX
+ 1]; /* pointer to the start of a path */
128 static char *path_skip_heading(char *p
)
130 if (isdigit(p
[0]) && p
[1] == ':') {
137 if (*q
== '\0' || *q
== '/')
148 static void strip_trailing_slash(PATH_T
*p
)
150 char *beg
= path_skip_heading(p
->p_path
);
151 char *end
= p
->p_end
;
153 while (end
> beg
&& end
[-1] == '/')
160 * Move specified string into path. Convert "" to "." to handle BSD
161 * semantics for a null path. Strip trailing slashes.
163 static PATH_T
*path_setup(char *string
)
165 if (strlen(string
) > PATH_MAX
) {
166 cmd_error(0, 0, PSTR("'%s': Name too long"), string
);
170 PATH_T
*p
= (PATH_T
*) malloc(sizeof *p
);
172 cmd_error(0, ENOMEM
, NULL
);
176 strcpy(p
->p_path
, string
);
177 size_t len
= strlen(string
);
178 if (len
> 1 && p
->p_path
[1] == ':' && p
->p_path
[2] != '/') {
179 if (len
< PATH_MAX
) {
180 memmove(p
->p_path
+3, p
->p_path
+2, len
-1);
184 cmd_error(0, ENOMEM
, NULL
);
189 p
->p_end
= p
->p_path
+ len
;
190 if (p
->p_path
== p
->p_end
) {
195 strip_trailing_slash(p
);
200 * pwd - Print current directory of the current drive.
203 command_ret_t
do_pwd(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc UNUSED
, char * const argv
[] UNUSED
)
208 buf
= (char *) malloc(PATH_MAX
);
210 cmd_error(CMD_RET_FAILURE
, ENOMEM
, NULL
);
212 res
= f_getcwd(buf
, PATH_MAX
); /* Get current directory path */
214 debug_fa("### f_getcwd(): buf: '%s', res: %d\n", buf
, res
);
221 cmd_error(CMD_RET_FAILURE
, res
, NULL
);
223 return CMD_RET_SUCCESS
;
228 * cd - Change the current/working directory.
231 command_ret_t
do_cd(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc
, char * const argv
[])
237 arg
= getenv_str(PSTR(ENV_HOME
));
239 cmd_error(CMD_RET_FAILURE
, 0, PSTR("\"%S\" is not set"), PSTR(ENV_HOME
));
244 PATH_T
*path
= path_setup(arg
);
246 return CMD_RET_FAILURE
;
248 if (strlen(path
->p_path
) > 1 && path
->p_path
[1] == ':') {
249 res
= f_chdrive(path
->p_path
);
250 debug_fa("### f_chdrive(): p_path: '%s', res: %d\n", path
->p_path
, res
);
253 res
= f_chdir(path
->p_path
);
254 debug_fa("### f_chdir(): p_path: '%s', res: %d\n", path
->p_path
, res
);
258 cmd_error(CMD_RET_FAILURE
, res
, NULL
);
260 return CMD_RET_SUCCESS
;
265 * ls path - Directory listing
268 command_ret_t
do_ls(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc
, char * const argv
[])
271 DIR Dir
; /* Directory object */
279 buf
= (char *) malloc(PATH_MAX
);
281 cmd_error(CMD_RET_FAILURE
, ENOMEM
, NULL
);
284 res
= f_getcwd(buf
, PATH_MAX
); /* Get current directory path */
286 strncpy(buf
, argv
[1], PATH_MAX
);
289 res
= f_opendir(&Dir
, buf
);
292 cmd_error(CMD_RET_FAILURE
, res
, NULL
);
297 res
= f_readdir(&Dir
, &Finfo
);
298 if ((res
!= FR_OK
) || !Finfo
.fname
[0])
300 if (Finfo
.fattrib
& AM_DIR
) {
303 s1
++; p1
+= Finfo
.fsize
;
305 printf_P(PSTR("%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu %s\n"),
306 (Finfo
.fattrib
& AM_DIR
) ? 'D' : '-',
307 (Finfo
.fattrib
& AM_RDO
) ? 'R' : '-',
308 (Finfo
.fattrib
& AM_HID
) ? 'H' : '-',
309 (Finfo
.fattrib
& AM_SYS
) ? 'S' : '-',
310 (Finfo
.fattrib
& AM_ARC
) ? 'A' : '-',
311 (Finfo
.fdate
>> 9) + 1980, (Finfo
.fdate
>> 5) & 15, Finfo
.fdate
& 31,
312 (Finfo
.ftime
>> 11), (Finfo
.ftime
>> 5) & 63,
313 Finfo
.fsize
, Finfo
.fname
);
319 printf_P(PSTR("%4u File(s),%10lu bytes total\n%4u Dir(s)"), s1
, p1
, s2
);
320 if (f_getfree(buf
, (DWORD
*)&p1
, &fs
) == FR_OK
)
321 printf_P(PSTR(", %10luK bytes free\n"), p1
* fs
->csize
/ 2);
326 cmd_error(CMD_RET_FAILURE
, res
, NULL
);
328 return CMD_RET_SUCCESS
;
333 FRESULT
mkpath(TCHAR
*path
)
341 res
= f_stat (path
, &fd
)
343 p
= strchr(path
, ':');
344 if (p
== NULL
|| *++p
== '\0' || *p
++ != '/')
347 while ((q
= strchr(p
, '/')) != NULL
) {
351 if (ret
!= FR_OK
&& ret
!= FR_EXIST
)
360 /* Work register for fs command */
363 WORD AccFiles
, AccDirs
;
369 char *path
, /* Pointer to the working buffer with start path */
370 struct stat_dat_s
*statp
378 res
= f_opendir(&dirs
, path
);
382 while (((res
= f_readdir(&dirs
, &statp
->Finfo
)) == FR_OK
) &&
383 statp
->Finfo
.fname
[0]) {
384 if (FF_FS_RPATH
&& statp
->Finfo
.fname
[0] == '.')
386 fn
= statp
->Finfo
.fname
;
387 if (statp
->Finfo
.fattrib
& AM_DIR
) {
390 strcpy(path
+i
+1, fn
);
391 res
= scan_files(path
, statp
);
396 //printf_P(PSTR("%s/%s\n"), path, fn);
398 statp
->AccSize
+= statp
->Finfo
.fsize
;
412 * fatstat path - Show logical drive status
415 command_ret_t
do_stat(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc
, char * const argv
[])
422 struct stat_dat_s statp
;
424 buf
= (char *) malloc(PATH_MAX
);
426 cmd_error(CMD_RET_FAILURE
, ENOMEM
, NULL
);
430 res
= f_getfree(path
, &nfreeclst
, &fs
);
434 "Bytes/Cluster: %lu\n"
435 "Number of FATs: %u\n"
436 "Root DIR entries: %u\n"
438 "Number of clusters: %lu\n"
439 "FAT start (lba): %lu\n"
440 "DIR start (lba,cluster): %lu\n"
441 "Data start (lba): %lu\n"),
442 fs
->fs_type
, (DWORD
)fs
->csize
* 512, fs
->n_fats
,
443 fs
->n_rootdir
, fs
->fsize
, fs
->n_fatent
- 2,
444 fs
->fatbase
, fs
->dirbase
, fs
->database
);
448 res
= f_getlabel(path
, buf
, &serial
);
452 "Volume S/N: %04X-%04X\n"),
453 buf
, (WORD
)(serial
>> 16), (WORD
)(serial
& 0xFFFF));
457 statp
.AccSize
= statp
.AccFiles
= statp
.AccDirs
= 0;
460 my_puts_P(PSTR("\nCounting... "));
461 res
= scan_files(buf
, &statp
);
465 printf_P(PSTR("%u files, %lu bytes.\n%u folders.\n"
466 "%lu KB total disk space.\n%lu KB available.\n"),
467 statp
.AccFiles
, statp
.AccSize
, statp
.AccDirs
,
468 (fs
->n_fatent
- 2) * (fs
->csize
/ 2), nfreeclst
* (fs
->csize
/ 2)
475 cmd_error(CMD_RET_FAILURE
, res
, NULL
);
477 return CMD_RET_SUCCESS
;
481 * fatread/write - load binary file to/from a dos filesystem
482 * read <d:/path/filename> <addr> [bytes [pos]]
483 * write <d:/path/filename> <addr> <bytes>
485 command_ret_t
do_rw(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc
, char * const argv
[])
491 unsigned long bytes_rw
;
493 bool dowrite
= (argv
[0][0] == 'w');
499 if (argc
< (dowrite
? 4 : 3))
500 return CMD_RET_USAGE
;
502 addr
= eval_arg(argv
[2], NULL
);
503 if (addr
>= MAX_MEMORY
)
504 cmd_error(CMD_RET_FAILURE
, 0, PSTR("Address too high: %#lx"), addr
);
507 bytes
= eval_arg(argv
[3], NULL
);
511 pos
= eval_arg(argv
[4], NULL
);
515 if (addr
+ bytes
> MAX_MEMORY
)
516 bytes
= MAX_MEMORY
- addr
;
518 buffer
= (uint8_t *) malloc(BUFFER_SIZE
);
520 cmd_error(CMD_RET_FAILURE
, ENOMEM
, NULL
);
522 res
= f_open(&File
, argv
[1], dowrite
? FA_WRITE
| FA_CREATE_ALWAYS
526 res
= f_lseek(&File
, pos
);
529 timer
= get_timer(0);
531 unsigned int cnt
, br
;
533 if (bytes
>= BUFFER_SIZE
) {
535 bytes
-= BUFFER_SIZE
;
537 cnt
= bytes
; bytes
= 0;
540 if (!(z80_bus_cmd(Request
) & ZST_ACQUIRED
)) {
544 z80_read_block(buffer
, addr
, cnt
);
545 z80_bus_cmd(Release
);
546 res
= f_write(&File
, buffer
, cnt
, &br
);
550 res
= f_read(&File
, buffer
, cnt
, &br
);
553 if (!(z80_bus_cmd(Request
) & ZST_ACQUIRED
)) {
557 z80_write_block(buffer
, addr
, br
);
558 z80_bus_cmd(Release
);
565 printf_P(PSTR("Disk full?\n"));
572 FRESULT fr
= f_close(&File
);
575 timer
= get_timer(timer
);
576 printf_P(PSTR("%lu (%#lx) bytes read/written with %lu bytes/sec.\n"),
577 bytes_rw
, bytes_rw
, timer
? (bytes_rw
* 1000 / timer
) : 0);
584 cmd_error(CMD_RET_FAILURE
, res
, PSTR("'%s'"), argv
[1]);
586 cmd_error(CMD_RET_FAILURE
, EBUSTO
, NULL
);
588 return CMD_RET_SUCCESS
;
592 * command table for fat subcommands
595 cmd_tbl_t cmd_tbl_fat
[] = {
597 status
, 2, CTBL_RPT
, do_stat
,
598 "Show logical drive status",
602 pwd
, 2, CTBL_RPT
|CTBL_SUBCMDAUTO
, do_pwd
,
603 "Print name of current/working directory",
607 cd
, 2, 0|CTBL_SUBCMDAUTO
, do_cd
,
608 "Change the current/working directory.",
612 ls
, 2, CTBL_RPT
|CTBL_SUBCMDAUTO
, do_ls
,
618 "load binary file from a dos filesystem",
619 "<d:/path/filename> <addr> [bytes [pos]]\n"
620 " - Load binary file 'path/filename' on logical drive 'd'\n"
621 " to address 'addr' from dos filesystem.\n"
622 " 'pos' gives the file position to start loading from.\n"
623 " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n"
624 " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n"
625 " the load stops on end of file."
629 "write file into a dos filesystem",
630 "<d:/path/filename> <addr> <bytes>\n"
631 " - Write file to 'path/filename' on logical drive 'd' from RAM\n"
632 " starting at address 'addr'.\n"
636 help
, CONFIG_SYS_MAXARGS
, CTBL_RPT
, do_help
,
637 "Print sub command description/usage",
639 " - print brief description of all sub commands\n"
640 "fat help command ...\n"
641 " - print detailed usage of sub cmd 'command'"
644 /* This does not use the CMD_TBL_ITEM macro as ? can't be used in symbol names */
645 {FSTR("?"), CONFIG_SYS_MAXARGS
, 1, do_help
,
647 #ifdef CONFIG_SYS_LONGHELP
649 #endif /* CONFIG_SYS_LONGHELP */
651 #ifdef CONFIG_AUTO_COMPLETE
655 /* Mark end of table */
656 CMD_TBL_END(cmd_tbl_fat
)
660 command_ret_t
do_fat(cmd_tbl_t
*cmdtp UNUSED
, uint_fast8_t flag UNUSED
, int argc UNUSED
, char * const argv
[] UNUSED
)
662 puts_P(PSTR("Huch?"));
664 return CMD_RET_USAGE
;