/* * Shell library * Contains shell functions */ // uses fs.c // uses hidfifo.c // uses gfx.c // uses stdlib.c // Max length of a single command #define SHELL_CMD_MAX_LENGTH 128 // The number of commands to remember #define SHELL_CMD_HISTORY_LENGTH 32 // Address of user program (already defined in BDOS.c) //#define RUN_ADDR 0x400000 // Chunk size for reading files and programs // NOTE: must be dividable by 4 #define SHELL_FILE_READ_CHUNK_SIZE 512 #define SHELL_PROGRAM_READ_CHUNK_SIZE 32768 // The current command that is being typed char SHELL_command[SHELL_CMD_MAX_LENGTH]; word SHELL_commandIdx = 0; // index in current command word SHELL_promptCursorPos = 0; // History of commands char SHELL_history[SHELL_CMD_HISTORY_LENGTH][SHELL_CMD_MAX_LENGTH]; word SHELL_historyPtr = 0; // index of next entry in history word SHELL_historySelectIdx = 0; // index of selected entry in history word SHELL_historyMovedBackwards = 0; // number of times that the user moved backwards in history word SHELL_historyLength = 0; // number of filled entries in history // Appends current CMD to history, ignores empty commands void SHELL_historyAppend() { // ignore empty command if (SHELL_command[0] == 0) { return; } // wrap around if full, overwriting the oldest entries if (SHELL_historyPtr == SHELL_CMD_HISTORY_LENGTH) { SHELL_historyPtr = 0; } strcpy(SHELL_history[SHELL_historyPtr], SHELL_command); SHELL_historyPtr++; // sync currently selected with the latest command SHELL_historySelectIdx = SHELL_historyPtr; // no need to update length if the history is already full if (SHELL_historyLength < SHELL_CMD_HISTORY_LENGTH) { SHELL_historyLength++; } } void SHELL_historyGoBack() { // ignore if we have gone fully back in time if (SHELL_historyMovedBackwards < SHELL_historyLength) { SHELL_historyMovedBackwards++; // go back in history, wrap around select idx if (SHELL_historySelectIdx == 0) { SHELL_historySelectIdx = SHELL_CMD_HISTORY_LENGTH; } else { SHELL_historySelectIdx--; } SHELL_setCommand(SHELL_history[SHELL_historySelectIdx]); } } void SHELL_historyGoForwards() { // only if we are in the past if (SHELL_historyMovedBackwards > 0) { SHELL_historyMovedBackwards--; // go forward in history, wrap around select idx if (SHELL_historySelectIdx == SHELL_CMD_HISTORY_LENGTH) { SHELL_historySelectIdx = 0; } else { SHELL_historySelectIdx++; } SHELL_setCommand(SHELL_history[SHELL_historySelectIdx]); // clear command if back in present if (SHELL_historyMovedBackwards == 0) { // use backspaces to clear the text on screen while (SHELL_commandIdx > 0) { GFX_PrintcConsole(0x8); SHELL_commandIdx--; } SHELL_clearCommand(); } } } // Clears the current command in memory void SHELL_clearCommand() { SHELL_command[0] = 0; SHELL_commandIdx = 0; } // Clears current command and replaces it with cmd void SHELL_setCommand(char* cmd) { if (cmd[0] == 0) { return; } // clear current command on screen by doing backspaces while (SHELL_commandIdx > 0) { GFX_PrintcConsole(0x8); SHELL_commandIdx--; } strcpy(SHELL_command, cmd); // set new shell cmd index SHELL_commandIdx = strlen(SHELL_command); // print new command GFX_PrintConsole(SHELL_command); } // Prints current display prompt void SHELL_print_prompt() { // TODO: if path > X chars, only show last X-1 chars with some character in front GFX_PrintConsole(SHELL_path); GFX_PrintConsole("> "); SHELL_promptCursorPos = GFX_cursor; } // Initialize shell void SHELL_init() { // clear current command SHELL_clearCommand(); // clear screen GFX_clearWindowtileTable(); GFX_clearWindowpaletteTable(); GFX_cursor = 0; SHELL_print_prompt(); } // Checks if p starts with cmd, followed by a space or a \0 // Returns 1 if true, 0 otherwise word SHELL_commandCompare(char* p, char* cmd) { word similar = 1; word i = 0; while (cmd[i] != 0) { if (cmd[i] != p[i]) { similar = 0; } i += 1; } if (similar) { similar = 0; if (p[i] == 0 || p[i] == ' ') similar = 1; } return similar; } // Returns the number of arguments of a command line // Does this by counting the number times a character is placed // directly after a space word SHELL_numberOfArguments(char* p) { word args = 0; word foundSpace = 0; word i = 0; while (p[i] != 0) { if (p[i] == ' ') { foundSpace = 1; } else { if (foundSpace) { // if character after space if (p[i] != 0) { args += 1; } foundSpace = 0; } } i += 1; } return args; } // Implementation of ldir command // Lists directory given in arg // Argument is passed in arg and should end with a \0 (not space) void SHELL_ldir(char* arg) { // backup current path strcpy(SHELL_pathBackup, SHELL_path); // add arg to path if (FS_changePath(arg) == FS_ANSW_USB_INT_SUCCESS) { // do listdir char *b = (char *) TEMP_ADDR; if (FS_listDir(SHELL_path, b) == FS_ANSW_USB_INT_SUCCESS) { GFX_PrintConsole(b); } // restore path strcpy(SHELL_path, SHELL_pathBackup); } else { GFX_PrintConsole("E: Invalid path\n"); } } // Implementation of run command // Loads file to memory at RUN_ADDR and jumps to it // Argument is passed in arg and should end with a \0 or space // If useBin is set, it will look for the file in the /BIN folder void SHELL_runFile(char* arg, word useBin) { // backup current path strcpy(SHELL_pathBackup, SHELL_path); // replace space with \0 word i = 0; word putBackSpaceIndex = 0; // and put back later for args while(*(arg+i) != ' ' && *(arg+i) != 0) { i++; } if (*(arg+i) == ' ') { putBackSpaceIndex = i; } *(arg+i) = 0; if (useBin) { strcpy(SHELL_path, "/BIN/"); strcat(SHELL_path, arg); } else { // create full path using arg FS_getFullPath(arg); } // if the resulting path is correct (can be file or directory) if (FS_sendFullPath(SHELL_path) == FS_ANSW_USB_INT_SUCCESS) { // if we can successfully open the file (not directory) if (FS_open() == FS_ANSW_USB_INT_SUCCESS) { word fileSize = FS_getFileSize(); if ((unsigned int) fileSize <= (unsigned int) 0x300000) { if (FS_setCursor(0) == FS_ANSW_USB_INT_SUCCESS) { // read the file in chunks char *b = (char *) RUN_ADDR; word bytesSent = 0; GFX_PrintConsole("Loading"); word loopCount = 0; // counter for animation // loop until all bytes are sent while (bytesSent != fileSize) { word partToSend = fileSize - bytesSent; // send in parts of SHELL_PROGRAM_READ_CHUNK_SIZE if ((unsigned int) partToSend > (unsigned int) SHELL_PROGRAM_READ_CHUNK_SIZE) partToSend = SHELL_PROGRAM_READ_CHUNK_SIZE; // read from usb to memory in word mode if (FS_readFile(b, partToSend, 1) != FS_ANSW_USB_INT_SUCCESS) uprintln("W: Error reading file\n"); // indicate progress if (loopCount == 3) { GFX_PrintcConsole(0x8); // backspace GFX_PrintcConsole(0x8); // backspace GFX_PrintcConsole(0x8); // backspace loopCount = 0; } else { GFX_PrintcConsole('.'); loopCount++; } // Update the amount of bytes sent bytesSent += partToSend; b += ((unsigned)partToSend>>2); // divide by 4 because one address is 4 bytes } // remove the dots for (loopCount; loopCount > 0; loopCount--) { GFX_PrintcConsole(0x8); // backspace } // close file after done FS_close(); // remove the loading text word i; for (i = 0; i < 7; i++) { GFX_PrintcConsole(0x8); // backspace } // put back the space in the command if (putBackSpaceIndex != 0) { *(arg+putBackSpaceIndex) = ' '; } BDOS_Backup(); // Indicate that a user program is running bdos_userprogram_running = 1; // jump to the program asm( "; backup registers\n" "push r1\n" "push r2\n" "push r3\n" "push r4\n" "push r5\n" "push r6\n" "push r7\n" "push r8\n" "push r9\n" "push r10\n" "push r11\n" "push r12\n" "push r13\n" "push r14\n" "push r15\n" //"ccache\n" "savpc r1\n" "push r1\n" "jump 0x400000\n" "; restore registers\n" "pop r15\n" "pop r14\n" "pop r13\n" "pop r12\n" "pop r11\n" "pop r10\n" "pop r9\n" "pop r8\n" "pop r7\n" "pop r6\n" "pop r5\n" "pop r4\n" "pop r3\n" "pop r2\n" "pop r1\n" ); // Indicate that no user program is running anymore bdos_userprogram_running = 0; bdos_restore(); } else GFX_PrintConsole("E: Could not move to start of file\n"); } else GFX_PrintConsole("E: Program is too large\n"); } else { if (useBin) GFX_PrintConsole("E: Unknown command\n"); else GFX_PrintConsole("E: Could not open file\n"); } } else { if (useBin) GFX_PrintConsole("E: Unknown command\n"); else GFX_PrintConsole("E: Invalid path\n"); } // restore path strcpy(SHELL_path, SHELL_pathBackup); } // Implementation of print command // Prints file to screen // Argument is passed in arg and should end with a \0 (not space) void SHELL_printFile(char* arg) { // backup current path strcpy(SHELL_pathBackup, SHELL_path); // create full path using arg FS_getFullPath(arg); // if the resulting path is correct (can be file or directory) if (FS_sendFullPath(SHELL_path) == FS_ANSW_USB_INT_SUCCESS) { // if we can successfully open the file (not directory) if (FS_open() == FS_ANSW_USB_INT_SUCCESS) { word fileSize = FS_getFileSize(); if (FS_setCursor(0) == FS_ANSW_USB_INT_SUCCESS) { // read the file in chunks char *b = (char *) TEMP_ADDR; word bytesSent = 0; // loop until all bytes are sent while (bytesSent != fileSize) { word partToSend = fileSize - bytesSent; // send in parts of SHELL_FILE_READ_CHUNK_SIZE if ((unsigned int) partToSend > (unsigned int) SHELL_FILE_READ_CHUNK_SIZE) partToSend = SHELL_FILE_READ_CHUNK_SIZE; // read from usb to buffer in byte mode if (FS_readFile(b, partToSend, 0) != FS_ANSW_USB_INT_SUCCESS) uprintln("W: Error reading file\n"); // append buffer with terminator *(b+partToSend) = 0; // print buffer to console GFX_PrintConsole(b); // Update the amount of bytes sent bytesSent += partToSend; } // close file after done FS_close(); // end with a newline for the next shell prompt GFX_PrintcConsole('\n'); } else GFX_PrintConsole("E: Could not move to start of file\n"); } else GFX_PrintConsole("E: Could not open file\n"); } else GFX_PrintConsole("E: Invalid path\n"); // restore path strcpy(SHELL_path, SHELL_pathBackup); } // Removes file/dir void SHELL_remove(char* arg) { // backup current path strcpy(SHELL_pathBackup, SHELL_path); // create full path using arg FS_getFullPath(arg); // if the resulting path is correct (can be file or directory) if (FS_sendFullPath(SHELL_path) == FS_ANSW_USB_INT_SUCCESS) { // if we can successfully open the file (not directory) word retval = FS_open(); if (retval == FS_ANSW_USB_INT_SUCCESS) { if (FS_delete() == FS_ANSW_USB_INT_SUCCESS) { GFX_PrintConsole("File removed\n"); } else GFX_PrintConsole("E: Could not delete file\n"); } else if (retval == FS_ANSW_ERR_OPEN_DIR) { if (FS_delete() == FS_ANSW_USB_INT_SUCCESS) { GFX_PrintConsole("Dir removed\n"); } else GFX_PrintConsole("E: Could not delete dir\n"); } else GFX_PrintConsole("E: Could not find file or dir\n"); } else GFX_PrintConsole("E: Invalid path\n"); // restore path strcpy(SHELL_path, SHELL_pathBackup); } // Creates file in current directory void SHELL_createFile(char* arg) { // backup current path strcpy(SHELL_pathBackup, SHELL_path); // if current path is correct (can be file or directory) if (FS_sendFullPath(SHELL_path) == FS_ANSW_USB_INT_SUCCESS) { word retval = FS_open(); // check that we can open the path if (retval == FS_ANSW_USB_INT_SUCCESS || retval == FS_ANSW_ERR_OPEN_DIR) { // check length of filename if (strlen(arg) <= 12) { // uppercase filename strToUpper(arg); // send filename FS_sendSinglePath(arg); // create the file if (FS_createFile() == FS_ANSW_USB_INT_SUCCESS) { GFX_PrintConsole("File created\n"); } else GFX_PrintConsole("E: Could not create file\n"); } else GFX_PrintConsole("E: Filename too long\n"); } else GFX_PrintConsole("E: Invalid path\n"); } else GFX_PrintConsole("E: Invalid path\n"); // restore path strcpy(SHELL_path, SHELL_pathBackup); } // Creates directory in current directory void SHELL_createDir(char* arg) { // backup current path strcpy(SHELL_pathBackup, SHELL_path); // if current path is correct (can be file or directory) if (FS_sendFullPath(SHELL_path) == FS_ANSW_USB_INT_SUCCESS) { word retval = FS_open(); // check that we can open the path if (retval == FS_ANSW_USB_INT_SUCCESS || retval == FS_ANSW_ERR_OPEN_DIR) { // check length of directory, must be 8 if (strlen(arg) <= 8) { // uppercase filename strToUpper(arg); // send filename FS_sendSinglePath(arg); // create the directory if (FS_createDir() == FS_ANSW_USB_INT_SUCCESS) { GFX_PrintConsole("Dir created\n"); } else GFX_PrintConsole("E: Could not create dir\n"); } else GFX_PrintConsole("E: Filename too long\n"); } else GFX_PrintConsole("E: Invalid path\n"); } else GFX_PrintConsole("E: Invalid path\n"); // restore path strcpy(SHELL_path, SHELL_pathBackup); } // Print help text void SHELL_printHelp() { GFX_PrintConsole("BDOS for FPGC\n"); GFX_PrintConsole("Supported OS commands:\n"); GFX_PrintConsole("- ./file [args]\n"); GFX_PrintConsole("- CD [arg1]\n"); GFX_PrintConsole("- LS [arg1]\n"); GFX_PrintConsole("- PRINT [arg1]\n"); GFX_PrintConsole("- MKDIR [arg1]\n"); GFX_PrintConsole("- MKFILE [arg1]\n"); GFX_PrintConsole("- RM [arg1]\n"); GFX_PrintConsole("- CLEAR\n"); GFX_PrintConsole("- HELP\n"); GFX_PrintConsole("\nExtra info:\n"); GFX_PrintConsole("- Programs are executed form /BIN\n"); GFX_PrintConsole("- Run LS /BIN to list all programs\n"); GFX_PrintConsole("- Paths can be relative or absolute\n"); } // Parses command line buffer and executes command if found // Commands to parse: // [x] ./ (RUN) // [x] CD // [x] LS // [x] CLEAR // [x] PRINT // [x] MKDIR // [x] MKFILE // [x] RM // [x] HELP // [] RENAME void SHELL_parseCommand(char* p) { // ./ (RUN) // No commandCompare, because special case with no space if (p[0] == '.' && p[1] == '/' && p[2] != 0) { SHELL_runFile(p+2, 0); // pointer to start of filename, which ends with \0 or space } // LS else if (SHELL_commandCompare(p, "ls")) { word args = SHELL_numberOfArguments(p); // if incorrect number of arguments if (args > 1) { GFX_PrintConsole("E: Too many arguments\n"); return; } else if (args == 1) { SHELL_ldir(p+3); // pointer to start of first arg, which ends with \0 } else // if no args { SHELL_ldir(""); } } // CD else if (SHELL_commandCompare(p, "cd")) { word args = SHELL_numberOfArguments(p); // if incorrect number of arguments if (args > 1) { GFX_PrintConsole("E: Too many arguments\n"); return; } else if (args == 1) { // pointer to start of first arg, which ends with \0 if (FS_changePath(p+3) != FS_ANSW_USB_INT_SUCCESS) { GFX_PrintConsole("E: Invalid path\n"); } } else // if no args, go to root { // reset path variable to / SHELL_path[0] = '/'; SHELL_path[1] = 0; // terminate string } // update path backup strcpy(SHELL_pathBackup, SHELL_path); } // CLEAR else if (SHELL_commandCompare(p, "clear")) { // clear screen by clearing window tables and resetting the cursor GFX_clearWindowtileTable(); GFX_clearWindowpaletteTable(); GFX_cursor = 0; } // PRINT else if (SHELL_commandCompare(p, "print")) { word args = SHELL_numberOfArguments(p); // if incorrect number of arguments if (args != 1) { GFX_PrintConsole("E: Expected 1 argument\n"); return; } else { SHELL_printFile(p+6); // pointer to start of first arg, which ends with \0 } } // RM else if (SHELL_commandCompare(p, "rm")) { word args = SHELL_numberOfArguments(p); // if incorrect number of arguments if (args != 1) { GFX_PrintConsole("E: Expected 1 argument\n"); return; } else { SHELL_remove(p+3); // pointer to start of first arg, which ends with \0 } } // MKFILE else if (SHELL_commandCompare(p, "mkfile")) { word args = SHELL_numberOfArguments(p); // if incorrect number of arguments if (args != 1) { GFX_PrintConsole("E: Expected 1 argument\n"); return; } else { SHELL_createFile(p+7); // pointer to start of first arg, which ends with \0 } } // MKDIR else if (SHELL_commandCompare(p, "mkdir")) { word args = SHELL_numberOfArguments(p); // if incorrect number of arguments if (args != 1) { GFX_PrintConsole("E: Expected 1 argument\n"); return; } else { SHELL_createDir(p+6); // pointer to start of first arg, which ends with \0 } } // HELP else if (SHELL_commandCompare(p, "help")) { SHELL_printHelp(); } // No command else if (p[0] == 0) { // do nothing on enter } // Check if the command is in /BIN as a file else { // copy p to commandBuf but without the arguments char commandBuf[16]; // filenames cannot be larger than 12 characters anyways, so this should be enough word i = 0; commandBuf[0] = 0; while (p[i] != 0 && p[i] != ' ' && i < 15) { commandBuf[i] = p[i]; i++; } commandBuf[i] = 0; // terminate buffer SHELL_runFile(commandBuf, 1); return; } } void SHELL_loop() { if (HID_FifoAvailable()) { word c = HID_FifoRead(); // special keys, like arrow keys if (c > 255) { if (c == 258) // arrow up { SHELL_historyGoBack(); } else if (c == 259) // arrow down { SHELL_historyGoForwards(); } } else if (c == 0x8) // backspace { // replace last char in buffer by 0 (if not at start) if (SHELL_commandIdx != 0) { SHELL_commandIdx--; SHELL_command[SHELL_commandIdx] = 0; } // prevent removing characters from the shell prompt if (GFX_cursor > SHELL_promptCursorPos) { // print backspace to console to remove last typed character GFX_PrintcConsole(c); } } else if (c == 0x9) // tab { // do nothing for now when tab } else if (c == 0x1b) // escape { // do nothing for now when escape } else if (c == 0xa) // newline/enter { // reset history counter SHELL_historyMovedBackwards = 0; // append command to history SHELL_historyAppend(); // start on new line GFX_PrintcConsole(c); // parse/execute command SHELL_parseCommand(SHELL_command); // clear buffer SHELL_clearCommand(); // print shell prompt SHELL_print_prompt(); } else { if (SHELL_commandIdx < SHELL_CMD_MAX_LENGTH) { // add to command buffer and print character SHELL_command[SHELL_commandIdx] = c; SHELL_commandIdx++; SHELL_command[SHELL_commandIdx] = 0; // terminate GFX_PrintcConsole(c); } } } }