/* * (C) Copyright 2014,2016,2018 Leo C. * * SPDX-License-Identifier: GPL-2.0 */ /* * FAT filesystem commands */ #include "cmd_fat.h" #include #include "ff.h" #include "z80-if.h" #include "eval_arg.h" #include "con-utils.h" #include "print-utils.h" #include "time.h" #include "timer.h" #include "debug.h" #include "env.h" #define DEBUG_FA 0 /* set to 1 to debug */ #define debug_fa(fmt, args...) \ debug_cond(DEBUG_FA, fmt, ##args) /* TODO: use memory size test function (detect_ramsize() in cmd_loadihex.c) */ /* TODO: detect_ramsize() should be moved to z80-if.c */ #define MAX_MEMORY CONFIG_SYS_RAMSIZE_MAX #define BUFFER_SIZE FF_MAX_SS #define PATH_MAX CONFIG_SYS_MAX_PATHLEN /* * Multible (fat) partitions per physical drive are not supported, * but we have up to 2 sdcard slots. */ FATFS FatFs0; FATFS FatFs1; command_ret_t command_ret; char *cmdname; void setup_fatfs(void) { f_mount(&FatFs0, "0:", 0); f_mount(&FatFs1, "1:", 0); } DWORD get_fattime (void) { time_t timer; struct tm tm_timer; time(&timer); gmtime_r(&timer, &tm_timer); return fatfs_time(&tm_timer); } static bool check_abort(void) { bool ret = ctrlc(); if (ret) printf_P(PSTR("Abort\n")); return ret; } static const FLASH char * const FLASH rc_strings[] = { FSTR("Success"), FSTR("Disk error"), FSTR("Internal error"), FSTR("Not ready"), FSTR("No file"), FSTR("No path"), FSTR("Invalid name"), FSTR("Denied"), FSTR("Exist"), FSTR("Invalid object"), FSTR("Write protected"), FSTR("Invalid drive"), FSTR("Not enabled"), FSTR("No file system"), FSTR("Mkfs aborted"), FSTR("Timeout"), FSTR("Locked"), FSTR("Not enough core"), FSTR("Too many open files"), FSTR("Invalid parameter") }; static const FLASH char * const FLASH rc_names[] = { FSTR("OK"), FSTR("DISK_ERR"), FSTR("INT_ERR"), FSTR("NOT_READY"), FSTR("NO_FILE"), FSTR("NO_PATH"), FSTR("INVALID_NAME"), FSTR("DENIED"), FSTR("EXIST"), FSTR("INVALID_OBJECT"), FSTR("WRITE_PROTECTED"), FSTR("INVALID_DRIVE"), FSTR("NOT_ENABLED"), FSTR("NO_FILE_SYSTEM"), FSTR("MKFS_ABORTED"), FSTR("TIMEOUT"), FSTR("LOCKED"), FSTR("NOT_ENOUGH_CORE"), FSTR("TOO_MANY_OPEN_FILES"), FSTR("INVALID_PARAMETER") }; static void put_rc (FRESULT rc) { #if GCC_BUG_61443 printf_P(PSTR("rc=%u FR_"), rc); my_puts_P(rc < ARRAY_SIZE(rc_names) ? rc_names[rc] : PSTR(" Unknown Error")); my_puts_P(PSTR("\n")); #else printf_P(PSTR("rc=%u FR_%S\n"), rc, rc < ARRAY_SIZE(rc_names) ? rc_names[rc] : PSTR(" Unknown Error")); #endif } const FLASH char * fat_rctostr(FRESULT rc) { return rc < ARRAY_SIZE(rc_strings) ? rc_strings[rc] : PSTR(" Unknown Error"); } static void swirl(void) { static const FLASH char swirlchar[] = { '-','\\','|','/' }; static uint_fast8_t cnt; static uint32_t tstamp; if (get_timer(0) > tstamp) { tstamp = get_timer(0) + 250; putchar('\b'); cnt = (cnt+1) & 3; putchar(swirlchar[cnt]); } } typedef struct { char *p_end; /* pointer to NULL at end of path */ char p_path[PATH_MAX + 1]; /* pointer to the start of a path */ } PATH_T; static char *path_skip_heading(char *p) { if (isdigit(p[0]) && p[1] == ':') { p += 2; } else { char *q = p; if (*q++ == '.') { if (*q == '.') ++q; if (*q == '\0' || *q == '/') p = q; } return p; } if (*p == '/') ++p; return p; } static void strip_trailing_slash(PATH_T *p) { char *beg = path_skip_heading(p->p_path); char *end = p->p_end; while (end > beg && end[-1] == '/') *--end = '\0'; p->p_end =end; } /* * Move specified string into path. Convert "" to "." to handle BSD * semantics for a null path. Strip trailing slashes. */ static PATH_T *path_setup(char *string) { if (strlen(string) > PATH_MAX) { cmd_error(0, 0, PSTR("'%s': Name too long"), string); return NULL; } PATH_T *p = (PATH_T *) malloc(sizeof *p); if (p == NULL) { cmd_error(0, 0, PSTR("'%s': Out of Memory"), string); return NULL; } strcpy(p->p_path, string); size_t len = strlen(string); if (len > 1 && p->p_path[1] == ':' && p->p_path[2] != '/') { if (len < PATH_MAX) { memmove(p->p_path+3, p->p_path+2, len-1); p->p_path[2] = '/'; len += 1; } else { cmd_error(0, 0, PSTR("'%s': Out of Memory"), string); return NULL; } } p->p_end = p->p_path + len; if (p->p_path == p->p_end) { *p->p_end++ = '.'; *p->p_end = '\0'; } strip_trailing_slash(p); return p; } /* * pwd - Print current directory of the current drive. * */ command_ret_t do_pwd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED) { FRESULT res; char *buf; buf = (char *) malloc(PATH_MAX); if (buf == NULL) { printf_P(PSTR("pwd: Out of Memory!\n")); return CMD_RET_FAILURE; } *buf = '\0'; res = f_getcwd(buf, PATH_MAX); /* Get current directory path */ debug_fa("### f_getcwd(): buf: '%s', res: %d\n", buf, res); if (res == FR_OK) { puts(buf); } free(buf); if (res != FR_OK) { cmd_error(CMD_RET_FAILURE, res, NULL); //put_rc(res); //return CMD_RET_FAILURE; } return CMD_RET_SUCCESS; } /* * cd - Change the current/working directory. * */ command_ret_t do_cd(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { char *arg; FRESULT res = FR_OK; if (argc < 2) { arg = getenv_str(PSTR(ENV_HOME)); if (arg == NULL) { printf_P(PSTR("%s: \"%S\" is not set\n"), argv[0], PSTR(ENV_HOME)); return CMD_RET_FAILURE; } } else arg = argv[1]; PATH_T *path = path_setup(arg); if (path == NULL) return CMD_RET_FAILURE; if (strlen(path->p_path) > 1 && path->p_path[1] == ':') { res = f_chdrive(path->p_path); debug_fa("### f_chdrive(): p_path: '%s', res: %d\n", path->p_path, res); } if (res == FR_OK) { res = f_chdir(path->p_path); debug_fa("### f_chdir(): p_path: '%s', res: %d\n", path->p_path, res); } free(path); if (res != FR_OK) { put_rc(res); return CMD_RET_FAILURE; } return CMD_RET_SUCCESS; } /* * ls path - Directory listing * */ command_ret_t do_ls(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { FATFS *fs; DIR Dir; /* Directory object */ FILINFO Finfo; unsigned long p1; unsigned int s1, s2; FRESULT res = FR_OK; char *buf; buf = (char *) malloc(PATH_MAX); if (buf == NULL) { printf_P(PSTR("pwd: Out of Memory!\n")); return CMD_RET_FAILURE; } if (argc < 2) res = f_getcwd(buf, PATH_MAX); /* Get current directory path */ else strncpy(buf, argv[1], PATH_MAX); if (res == FR_OK) res = f_opendir(&Dir, buf); if (res != FR_OK) { free(buf); put_rc(res); return CMD_RET_FAILURE; } p1 = s1 = s2 = 0; for(;;) { res = f_readdir(&Dir, &Finfo); if ((res != FR_OK) || !Finfo.fname[0]) break; if (Finfo.fattrib & AM_DIR) { s2++; } else { s1++; p1 += Finfo.fsize; } printf_P(PSTR("%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu %s\n"), (Finfo.fattrib & AM_DIR) ? 'D' : '-', (Finfo.fattrib & AM_RDO) ? 'R' : '-', (Finfo.fattrib & AM_HID) ? 'H' : '-', (Finfo.fattrib & AM_SYS) ? 'S' : '-', (Finfo.fattrib & AM_ARC) ? 'A' : '-', (Finfo.fdate >> 9) + 1980, (Finfo.fdate >> 5) & 15, Finfo.fdate & 31, (Finfo.ftime >> 11), (Finfo.ftime >> 5) & 63, Finfo.fsize, Finfo.fname); if (check_abort()) break; } if (res == FR_OK) { printf_P(PSTR("%4u File(s),%10lu bytes total\n%4u Dir(s)"), s1, p1, s2); if (f_getfree(buf, (DWORD*)&p1, &fs) == FR_OK) printf_P(PSTR(", %10luK bytes free\n"), p1 * fs->csize / 2); } free(buf); if (res) { put_rc(res); return CMD_RET_FAILURE; } return CMD_RET_SUCCESS; } #if 0 static FRESULT mkpath(TCHAR *path) { /* TODO: */ (void) path; FILINFO fd TCHAR *p, *q; FRESULT ret; res = f_stat (path, &fd) p = strchr(path, ':'); if (p == NULL || *++p == '\0' || *p++ != '/') return FR_OK; while ((q = strchr(p, '/')) != NULL) { *q = '\0'; ret = f_mkdir(path); *q = '/'; if (ret != FR_OK && ret != FR_EXIST) return ret; p = q + 1; } return FR_OK; } #endif /* Work register for fs command */ struct stat_dat_s { DWORD AccSize; WORD AccFiles, AccDirs; FILINFO Finfo; }; static FRESULT scan_files ( char *path, /* Pointer to the working buffer with start path */ struct stat_dat_s *statp ) { DIR dirs; FRESULT res; int i; char *fn; res = f_opendir(&dirs, path); swirl(); if (res == FR_OK) { i = strlen(path); while (((res = f_readdir(&dirs, &statp->Finfo)) == FR_OK) && statp->Finfo.fname[0]) { if (FF_FS_RPATH && statp->Finfo.fname[0] == '.') continue; fn = statp->Finfo.fname; if (statp->Finfo.fattrib & AM_DIR) { statp->AccDirs++; path[i] = '/'; strcpy(path+i+1, fn); res = scan_files(path, statp); path[i] = '\0'; if (res != FR_OK) break; } else { //printf_P(PSTR("%s/%s\n"), path, fn); statp->AccFiles++; statp->AccSize += statp->Finfo.fsize; } if (check_abort()) { res = 255; break; } } } return res; } /* * fatstat path - Show logical drive status * */ command_ret_t do_stat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { FATFS *fs; DWORD nfreeclst; FRESULT res; char *buf; char *path = ""; struct stat_dat_s statp; buf = (char *) malloc(PATH_MAX); if (buf == NULL) { printf_P(PSTR("fat stat: Out of Memory!\n")); return CMD_RET_FAILURE; } if (argc > 1) path = argv[1]; res = f_getfree(path, &nfreeclst, &fs); if (!res) { printf_P(PSTR( "FAT type: %u\n" "Bytes/Cluster: %lu\n" "Number of FATs: %u\n" "Root DIR entries: %u\n" "Sectors/FAT: %lu\n" "Number of clusters: %lu\n" "FAT start (lba): %lu\n" "DIR start (lba,cluster): %lu\n" "Data start (lba): %lu\n"), fs->fs_type, (DWORD)fs->csize * 512, fs->n_fats, fs->n_rootdir, fs->fsize, fs->n_fatent - 2, fs->fatbase, fs->dirbase, fs->database); #if FF_USE_LABEL DWORD serial; res = f_getlabel(path, buf, &serial); if (!res) { printf_P(PSTR( "Volume name: %s\n" "Volume S/N: %04X-%04X\n"), buf, (WORD)(serial >> 16), (WORD)(serial & 0xFFFF)); } #endif if (!res) { statp.AccSize = statp.AccFiles = statp.AccDirs = 0; strcpy(buf, path); my_puts_P(PSTR("\nCounting... ")); res = scan_files(buf, &statp); putchar('\r'); } if (!res) { printf_P(PSTR("%u files, %lu bytes.\n%u folders.\n" "%lu KB total disk space.\n%lu KB available.\n"), statp.AccFiles, statp.AccSize, statp.AccDirs, (fs->n_fatent - 2) * (fs->csize / 2), nfreeclst * (fs->csize / 2) ); } } free(buf); if (res) { put_rc(res); return CMD_RET_FAILURE; } return CMD_RET_SUCCESS; } /* * fatread/write - load binary file to/from a dos filesystem * read [bytes [pos]] * write */ command_ret_t do_rw(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc, char * const argv[]) { FIL File; uint32_t addr; unsigned long bytes; unsigned long pos; unsigned long bytes_rw; bool dowrite = (argv[0][0] == 'w'); FRESULT res = FR_OK; bool buserr = 0; uint32_t timer; uint8_t *buffer; if (argc < (dowrite ? 4 : 3)) return CMD_RET_USAGE; addr = eval_arg(argv[2], NULL); if (addr >= MAX_MEMORY) { printf_P(PSTR("address too high: 0x%0lx\n"), addr); return CMD_RET_FAILURE; } if (argc > 3) bytes = eval_arg(argv[3], NULL); else bytes = MAX_MEMORY; if (argc > 4) pos = eval_arg(argv[4], NULL); else pos = 0; if (addr + bytes > MAX_MEMORY) bytes = MAX_MEMORY - addr; buffer = (uint8_t *) malloc(BUFFER_SIZE); if (buffer == NULL) { printf_P(PSTR("fatstat: Out of Memory!\n")); return CMD_RET_FAILURE; } if (!res) { res = f_open(&File, argv[1], dowrite ? FA_WRITE | FA_CREATE_ALWAYS : FA_READ ); if (!res) { res = f_lseek(&File, pos); if (!res) { bytes_rw = 0; timer = get_timer(0); while (bytes) { unsigned int cnt, br; if (bytes >= BUFFER_SIZE) { cnt = BUFFER_SIZE; bytes -= BUFFER_SIZE; } else { cnt = bytes; bytes = 0; } if (dowrite) { if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) { buserr = 1; break; } z80_read_block(buffer, addr, cnt); z80_bus_cmd(Release); res = f_write(&File, buffer, cnt, &br); if (res != FR_OK) break; } else { res = f_read(&File, buffer, cnt, &br); if (res != FR_OK) break; if (!(z80_bus_cmd(Request) & ZST_ACQUIRED)) { buserr = 1; break; } z80_write_block(buffer, addr, br); z80_bus_cmd(Release); } addr += br; bytes_rw += br; if (cnt != br) { if (dowrite) printf_P(PSTR("Disk full?\n")); break; } if (check_abort()) break; } FRESULT fr = f_close(&File); if (!res) res = fr; timer = get_timer(timer); printf_P(PSTR("%lu (0x%lx) bytes read/written with %lu bytes/sec.\n"), bytes_rw, bytes_rw, timer ? (bytes_rw * 1000 / timer) : 0); } } } free(buffer); if (buserr) my_puts_P(PSTR("Bus timeout\n")); if (res) put_rc(res); if (buserr || res) return CMD_RET_FAILURE; return CMD_RET_SUCCESS; } /* * command table for fat subcommands */ cmd_tbl_t cmd_tbl_fat[] = { CMD_TBL_ITEM( status, 2, CTBL_RPT, do_stat, "Show logical drive status", "dev" ), CMD_TBL_ITEM( pwd, 2, CTBL_RPT|CTBL_SUBCMDAUTO, do_pwd, "Print name of current/working directory", "" ), CMD_TBL_ITEM( cd, 2, 0|CTBL_SUBCMDAUTO, do_cd, "Change the current/working directory.", "path" ), CMD_TBL_ITEM( ls, 2, CTBL_RPT|CTBL_SUBCMDAUTO, do_ls, "Directory listing", "path" ), CMD_TBL_ITEM( load, 5, 0, do_rw, "load binary file from a dos filesystem", " [bytes [pos]]\n" " - Load binary file 'path/filename' on logical drive 'd'\n" " to address 'addr' from dos filesystem.\n" " 'pos' gives the file position to start loading from.\n" " If 'pos' is omitted, 0 is used. 'pos' requires 'bytes'.\n" " 'bytes' gives the size to load. If 'bytes' is 0 or omitted,\n" " the load stops on end of file." ), CMD_TBL_ITEM( write, 4, 0, do_rw, "write file into a dos filesystem", " \n" " - Write file to 'path/filename' on logical drive 'd' from RAM\n" " starting at address 'addr'.\n" ), CMD_TBL_ITEM( help, CONFIG_SYS_MAXARGS, CTBL_RPT, do_help, "Print sub command description/usage", "\n" " - print brief description of all sub commands\n" "fat help command ...\n" " - print detailed usage of sub cmd 'command'" ), /* This does not use the CMD_TBL_ITEM macro as ? can't be used in symbol names */ {FSTR("?"), CONFIG_SYS_MAXARGS, 1, do_help, NULL, #ifdef CONFIG_SYS_LONGHELP FSTR(""), #endif /* CONFIG_SYS_LONGHELP */ NULL, #ifdef CONFIG_AUTO_COMPLETE NULL, #endif }, /* Mark end of table */ CMD_TBL_END(cmd_tbl_fat) }; command_ret_t do_fat(cmd_tbl_t *cmdtp UNUSED, uint_fast8_t flag UNUSED, int argc UNUSED, char * const argv[] UNUSED) { puts_P(PSTR("Huch?")); return CMD_RET_USAGE; }