Преглед на файлове

Added BRFS to BDOS, started on new shell which uses BRFS and fixed many problems that came up during this process.

bart преди 7 месеца
родител
ревизия
f88d0c38c1

+ 229 - 249
BCC/BDOS/BDOS.c

@@ -1,79 +1,64 @@
 /* Bart's Drive Operating System(BDOS)
-* A relatively simple OS that allows for some basic features including:
-* - Running a single user program with full hardware control
-* - Some system calls
-* - A basic shell
-* - Network loaders for sending data and controlling the FPGC
-* - HID fifo including USB keyboard driver
-*/
-
+ * A relatively simple OS that allows for some basic features including:
+ * - Running a single user program with full hardware control
+ * - Some system calls
+ * - A basic shell
+ * - Network loaders for sending data and controlling the FPGC
+ * - HID fifo including USB keyboard driver
+ * - BRFS Filesystem
+ */
 
 /* List of reserved stuff:
 - Timer2 is used for USB keyboard polling, even when a user program is running
 - Socket 7 is used for netHID
 */
 
-
 /*
-* Defines (also might be used by included libraries below)
-*/
+ * Defines (also might be used by included libraries below)
+ */
 
 // As of writing, BCC assigns 4 memory addresses to ints, so we should use chars instead
 // However, this is confusing, so we typedef it to word, since that is what it basically is
 #define word char
 
-#define SYSCALL_RETVAL_ADDR 0x200000    // address for system call communication with user program
-#define FS_LDIR_FNAME_ADDR  0x210000    // address for filename list in ldir command
-#define FS_LDIR_FSIZE_ADDR  0x218000    // address for filesize list in ldir command
-#define TEMP_ADDR           0x220000    // address for (potentially) large temporary outputs/buffers
-#define RUN_ADDR            0x400000    // address of loaded user program
+#define SYSCALL_RETVAL_ADDR 0x200000  // Address for system call communication with user program
+#define TEMP_ADDR 0x220000            // Address for (potentially) large temporary outputs/buffers
+#define RUN_ADDR 0x400000             // Address of loaded user program
+
+#define NETWORK_LOCAL_IP 213          // local IP address (last byte)
 
-#define FS_PATH_MAX_LENGHT  256         // max length of a path
+#define MAX_PATH_LENGTH 127           // Max length of a file path
+#define BDOS_DEFAULT_BLOCKS 1024      // Default number of blocks for the BRFS filesystem
+#define BDOS_DEFAULT_BLOCK_SIZE 128   // Default number of words per block for the BRFS filesystem
 
 // Interrupt IDs for interrupt handler
-#define INTID_TIMER1  0x1
-#define INTID_TIMER2  0x2
-#define INTID_UART0   0x3
-#define INTID_GPU     0x4
-#define INTID_TIMER3  0x5
-#define INTID_PS2     0x6
-#define INTID_UART1   0x7
-#define INTID_UART2   0x8
+#define INTID_TIMER1 0x1
+#define INTID_TIMER2 0x2
+#define INTID_UART0 0x3
+#define INTID_GPU 0x4
+#define INTID_TIMER3 0x5
+#define INTID_PS2 0x6
+#define INTID_UART1 0x7
+#define INTID_UART2 0x8
 
 // System call IDs
-#define SYSCALL_FIFO_AVAILABLE  1
-#define SYSCALL_FIFO_READ       2
+#define SYSCALL_FIFO_AVAILABLE 1
+#define SYSCALL_FIFO_READ 2
 #define SYSCALL_PRINT_C_CONSOLE 3
-#define SYSCALL_GET_ARGS        4 // get arguments for executed program
-#define SYSCALL_GET_PATH        5 // get OS path
-#define SYSCALL_GET_USB_KB_BUF  6 // get the 8 bytes of the USB keyboard buffer
-
+#define SYSCALL_GET_ARGS 4       // Get arguments for executed program
+#define SYSCALL_GET_PATH 5       // Get OS path
+#define SYSCALL_GET_USB_KB_BUF 6 // Get the 8 bytes of the USB keyboard buffer
 
 /*
-* Global vars (also might be used by included libraries below)
-*/
+ * Global vars (also might be used by included libraries below)
+ */
 
 // Flag that indicates whether a user program is running
-word BDOS_userprogramRunning = 0;
-
-// Path variable and its backup variable
-char SHELL_path[FS_PATH_MAX_LENGHT];
-char SHELL_pathBackup[FS_PATH_MAX_LENGHT];
-
+word bdos_userprogram_running = 0;
 
 /*
-* Function prototypes, so they can be called in all libraries below
-*/
-
-// These functions are used by some of the other libraries
-void BDOS_Backup();
-void BDOS_Restore();
-void SHELL_clearCommand();
-
-
-/*
-* Included libraries
-*/
+ * Included libraries
+ */
 
 // Data includes
 #include "data/ASCII_BW.c"
@@ -86,256 +71,251 @@ void SHELL_clearCommand();
 #include "lib/gfx.c"
 #include "lib/hidfifo.c"
 #include "lib/ps2.c"
+#include "lib/brfs.c"
+#include "lib/shell.c"
 #include "lib/usbkeyboard.c"
-#include "lib/fs.c"
 #include "lib/wiz5500.c"
 #include "lib/netloader.c"
 #include "lib/nethid.c"
-#include "lib/shell.c"
+#include "lib/spiflash.c"
 
 
 /*
-* Functions
-*/
-
-// Initializes CH376 and mounts drive
-// returns 1 on success
-word BDOS_Init_FS()
+ * Functions
+ */
+
+/**
+ * Initialize the BRFS filesystem
+ * Returns 1 if successful, 0 if not
+ */
+word bdos_init_brfs()
 {
-    if (FS_init() != FS_ANSW_RET_SUCCESS)
-    {
-        GFX_PrintConsole("Error initializing CH376 for FS");
-        return 0;
-    }
-
-    delay(10);
-
-    if (FS_connectDrive() != FS_ANSW_USB_INT_SUCCESS)
-    {
-        GFX_PrintConsole("Could not mount drive");
-        return 0;
-    }
-    return 1;
+  // Initialize the SPI flash
+  spiflash_init();
+
+  // Try to read the filesystem from the SPI flash
+  GFX_PrintConsole("Reading BRFS from SPI flash...\n");
+  if (brfs_read_from_flash())
+  {
+    GFX_PrintConsole("BRFS read from flash\n");
+  }
+  else
+  {
+    GFX_PrintConsole("Could not read BRFS from flash!\n");
+    return 0;
+  }
+
+  return 1;
 }
 
 // Clears all VRAM
 //  and copies the default ASCII pattern table and palette table
 // also resets the cursor
-void BDOS_Reinit_VRAM()
+void bdos_init_vram()
 {
-    GFX_initVram();
-    GFX_copyPaletteTable((word)DATA_PALETTE_DEFAULT);
-    GFX_copyPatternTable((word)DATA_ASCII_DEFAULT);
-    GFX_cursor = 0;
+  GFX_initVram();
+  GFX_copyPaletteTable((word)DATA_PALETTE_DEFAULT);
+  GFX_copyPatternTable((word)DATA_ASCII_DEFAULT);
+  GFX_cursor = 0;
 }
 
 // Initialize the W5500
-void BDOS_initNetwork()
+void bdos_init_network()
 {
-    word ip_addr[4] = {192, 168, 0, 213};
+  word ip_addr[4] = {192, 168, 0, NETWORK_LOCAL_IP};
 
-    word gateway_addr[4] = {192, 168, 0, 1};
+  word gateway_addr[4] = {192, 168, 0, 1};
 
-    word mac_addr[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x24, 0x64};
+  word mac_addr[6] = {0xDE, 0xAD, 0xBE, 0xEF, 0x24, 0x64};
 
-    word sub_mask[4] = {255, 255, 255, 0};
+  word sub_mask[4] = {255, 255, 255, 0};
 
-    wiz_Init(ip_addr, gateway_addr, mac_addr, sub_mask);
+  wiz_Init(ip_addr, gateway_addr, mac_addr, sub_mask);
 }
 
-// Backup important things before running a user program
-void BDOS_Backup()
-{
-    // TODO look into what to backup
-}
 
 // Restores certain things when returning from a user program
-void BDOS_Restore()
+void bdos_restore()
 {
-    // restore graphics (trying to keep text in window plane)
-    GFX_copyPaletteTable((word)DATA_PALETTE_DEFAULT);
-    GFX_copyPatternTable((word)DATA_ASCII_DEFAULT);
-    GFX_clearBGtileTable();
-    GFX_clearBGpaletteTable();
-    GFX_clearWindowpaletteTable();
-    GFX_clearSprites();
-
-    // restore netloader
-    NETLOADER_init(NETLOADER_SOCKET);
+  // restore graphics (trying to keep text in window plane)
+  GFX_copyPaletteTable((word)DATA_PALETTE_DEFAULT);
+  GFX_copyPatternTable((word)DATA_ASCII_DEFAULT);
+  GFX_clearBGtileTable();
+  GFX_clearBGpaletteTable();
+  GFX_clearWindowpaletteTable();
+  GFX_clearSprites();
+
+  // restore netloader
+  NETLOADER_init(NETLOADER_SOCKET);
 }
 
 // Main BDOS code
-int main() 
+int main()
 {
-    // all kinds of initialisations
-    uprintln("BDOS_INIT"); // extra message over UART for debugging
+  uprintln("BDOS_INIT");
 
-    BDOS_userprogramRunning = 0; // indicate that no user program is running
+  bdos_userprogram_running = 0; // Indicate that no user program is running
 
-    BDOS_Reinit_VRAM(); // start with loading ASCII table and set palette
-    
-    GFX_PrintConsole("Starting BDOS\n"); // print welcome message
+  bdos_init_vram();
+  GFX_PrintConsole("VRAM initialized\n"); // Print to console now VRAM is initialized
+  GFX_PrintConsole("Starting BDOS\n");
 
-    GFX_PrintConsole("Init network...");
-    BDOS_initNetwork();
-    GFX_PrintConsole("DONE\n");
+  GFX_PrintConsole("Initalizing network\n");
+  bdos_init_network();
 
-    GFX_PrintConsole("Init netloader...");
-    NETLOADER_init(NETLOADER_SOCKET);
-    GFX_PrintConsole("DONE\n");
+  GFX_PrintConsole("Initalizing netloader\n");
+  NETLOADER_init(NETLOADER_SOCKET);
 
-    GFX_PrintConsole("Init netHID...");
-    NETHID_init(NETHID_SOCKET);
-    GFX_PrintConsole("DONE\n");
+  GFX_PrintConsole("Initalizing netHID\n");
+  NETHID_init(NETHID_SOCKET);
 
-    GFX_PrintConsole("Init USB keyboard...");
-    USBkeyboard_init();
-    GFX_PrintConsole("DONE\n");
+  GFX_PrintConsole("Initalizing USB keyboard\n");
+  USBkeyboard_init();
 
-    GFX_PrintConsole("Init filesystem...");
-    if (!BDOS_Init_FS())
-        return 0;
-    GFX_PrintConsole("DONE\n");
+  GFX_PrintConsole("Initalizing filesystem\n");
+  if (!bdos_init_brfs())
+  {
+    GFX_PrintConsole("Error initializing FS\n");
+    return 0;
+  }
 
-    // init shell
-    SHELL_init();
+  GFX_PrintConsole("Initalizing shell\n");
+  shell_init();
 
-    // main loop
-    while (1)
-    {
-        SHELL_loop();                       // update the shell state
-        NETLOADER_loop(NETLOADER_SOCKET);   // update the netloader state
-    }
+  // Main loop
+  while (1)
+  {
+    shell_loop();                     // Update shell state
+    NETLOADER_loop(NETLOADER_SOCKET); // Update netloader state
+  }
 
-    return 1;
+  return 1;
 }
 
-
 // System call handler
 // Returns at the same address it reads the command from
 void syscall()
 {
-    word* p = (word*) SYSCALL_RETVAL_ADDR;
-    word ID = p[0];
-
-    switch(ID)
-    {
-        case SYSCALL_FIFO_AVAILABLE:
-            p[0] = HID_FifoAvailable();
-            break;
-        case SYSCALL_FIFO_READ:
-            p[0] = HID_FifoRead();
-            break;
-        case SYSCALL_PRINT_C_CONSOLE:
-            GFX_PrintcConsole(p[1]);
-            p[0] = 0;
-            break;
-        case SYSCALL_GET_ARGS:
-            p[0] = SHELL_command;
-            break;
-        case SYSCALL_GET_PATH:
-            p[0] = SHELL_pathBackup;
-            break;
-        case SYSCALL_GET_USB_KB_BUF:
-            p[0] = USBkeyboard_buffer_parsed;
-            break;
-        default:
-            p[0] = 0;
-            break;
-    }
+  word *p = (word *)SYSCALL_RETVAL_ADDR;
+  word ID = p[0];
+
+  switch (ID)
+  {
+  case SYSCALL_FIFO_AVAILABLE:
+    p[0] = HID_FifoAvailable();
+    break;
+  case SYSCALL_FIFO_READ:
+    p[0] = HID_FifoRead();
+    break;
+  case SYSCALL_PRINT_C_CONSOLE:
+    GFX_PrintcConsole(p[1]);
+    p[0] = 0;
+    break;
+  case SYSCALL_GET_ARGS:
+    p[0] = 0; // TODO: implement
+    break;
+  case SYSCALL_GET_PATH:
+    p[0] = 0; // TODO: implement
+    break;
+  case SYSCALL_GET_USB_KB_BUF:
+    p[0] = USBkeyboard_buffer_parsed;
+    break;
+  default:
+    p[0] = 0;
+    break;
+  }
 }
 
 // Interrupt handler
 void interrupt()
 {
-    // handle all interrupts
-    int i = getIntID();
-    switch(i)
-    {
-        case INTID_TIMER1:
-            timer1Value = 1; // notify ending of timer1
-            break;
-
-        case INTID_TIMER2:
-            USBkeyboard_HandleInterrupt(); // handle USB keyboard interrupt
-            break;
-
-        case INTID_UART0:
-            break;
-
-        case INTID_GPU:
-            if (NETHID_isInitialized == 1)
-            {
-                // check using CS if we are not interrupting any critical access to the W5500
-                word* spi3ChipSelect = (word*) 0xC02732;
-                if (*spi3ChipSelect == 1)
-                {
-                    NETHID_loop(NETHID_SOCKET); // look for an input sent to netHID
-                }
-            }
-            break;
-
-        case INTID_TIMER3:
-            break;
-
-        case INTID_PS2:
-            PS2_HandleInterrupt(); // handle PS2 interrupt
-            break;
-
-        case INTID_UART1:
-            break;
-
-        case INTID_UART2:
-            break;
-    }
-
-    // check if a user program is running
-    if (BDOS_userprogramRunning)
-    {
-        // call interrupt() of user 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"
-
-            "savpc r1\n"
-            "push r1\n"
-            "jump 0x400001\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"
-            );
-        return;
-    }
-    else // code to only run when not running a user program
+  // Handle BDOS interrupts
+  int i = getIntID();
+  switch (i)
+  {
+  case INTID_TIMER1:
+    timer1Value = 1; // Notify ending of timer1
+    break;
+
+  case INTID_TIMER2:
+    USBkeyboard_HandleInterrupt(); // Handle USB keyboard interrupt
+    break;
+
+  case INTID_UART0:
+    break;
+
+  case INTID_GPU:
+    if (NETHID_isInitialized == 1)
     {
-        
+      // Check using CS if we are not interrupting any critical access to the W5500
+      word *spi3ChipSelect = (word *)0xC02732; // TODO: use a define for this address
+      if (*spi3ChipSelect == 1)
+      {
+        NETHID_loop(NETHID_SOCKET); // Look for an input sent to netHID
+      }
     }
+    break;
+
+  case INTID_TIMER3:
+    break;
+
+  case INTID_PS2:
+    PS2_HandleInterrupt(); // Handle PS2 interrupt
+    break;
+
+  case INTID_UART1:
+    break;
+
+  case INTID_UART2:
+    break;
+  }
+
+  // Handle user program interrupts
+  if (bdos_userprogram_running)
+  {
+    // Call interrupt() of user 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"
+
+        "savpc r1\n"
+        "push r1\n"
+        "jump 0x400001\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");
+    return;
+  }
+  else
+  {
+    // Code to only run when not running a user program
+  }
 }

+ 1415 - 0
BCC/BDOS/lib/brfs.c

@@ -0,0 +1,1415 @@
+// Bart's RAM File System (BRFS)
+
+/*
+Implementing in BDOS on PCB v3:
+- BDOS only uses ~340 pages of 256 bytes -> 90000 bytes
+- SPI flash has 65536 pages of 256 bytes -> 16777216 bytes
+- With current verilog implementation, only 32MiB of RAM is addressable, so BRFS should be used somewhere in the first 32MiB
+- With current BDOS memory map, BRFS should be placed in the first 8MiB available as BDOS Program Code
+- Lets use the last 4MiB of this space for BRFS (0x100000 - 0x200000)
+*/
+
+#define BRFS_SUPPORTED_VERSION 1
+
+#define BRFS_RAM_STORAGE_ADDR 0x100000 // From 4th MiB
+
+// Addresses in SPI Flash
+// Note that each section should be in a different 4KiB sector in SPI Flash
+#define BRFS_SPIFLASH_SUPERBLOCK_ADDR 0xDF000 // One sector before FAT
+#define BRFS_SPIFLASH_FAT_ADDR 0xE0000 // Can be 32768 words (128KiB) for 32MiB of 256word blocks
+#define BRFS_SPIFLASH_BLOCK_ADDR 0x100000 // From first MiB
+
+//#define MAX_PATH_LENGTH 127 // Set by BDOS
+#define MAX_OPEN_FILES 16 // Can be set higher, but 4 is good for testing
+
+// Length of structs, should not be changed
+#define SUPERBLOCK_SIZE 16
+#define DIR_ENTRY_SIZE 8
+
+#define BRFS_MAX_BLOCKS 65536 // 64KiB
+word brfs_changed_blocks[BRFS_MAX_BLOCKS >> 5]; // Bitmap of changed blocks, each block has 1 bit
+
+// 16 words long
+struct brfs_superblock
+{
+  word total_blocks;
+  word words_per_block;
+  word label[10];       // 1 char per word
+  word brfs_version;
+  word reserved[3];
+};
+
+// 8 words long
+struct brfs_dir_entry
+{
+  word filename[4];       // 4 chars per word
+  word modify_date;       // TBD when RTC added to FPGC
+  word flags;             // 32 flags, from right to left: directory, hidden 
+  word fat_idx;           // idx of first FAT block
+  word filesize;          // file size in words, not bytes
+};
+
+word *brfs_ram_storage = (word*) BRFS_RAM_STORAGE_ADDR; // RAM storage of file system
+
+// Variables for open files
+word brfs_cursors[MAX_OPEN_FILES]; // Cursor position offset from start of file
+word brfs_file_pointers[MAX_OPEN_FILES]; // FAT idx of open file
+struct brfs_dir_entry* brfs_dir_entry_pointers[MAX_OPEN_FILES]; // Pointer to dir entry of open file
+
+/**
+ * Create a hexdump like dump of a section of memory
+ * addr: address of the section
+ * len: length of the section in words
+ * linesize: number of words per line to print
+*/
+void brfs_dump_section(word* addr, word len, word linesize)
+{
+  char buf[16];
+  word i;
+  for (i = 0; i < len; i++)
+  {
+    itoah(addr[i], buf);
+    if (strlen(buf+2) == 1)
+      uprintc('0');
+    uprint(buf+2);
+
+    uprintc(' ');
+
+    // newline every linesize words
+    // also print last linesize words as chars if alphanum
+    if (i != 0 && MATH_modU(i+1, linesize) == 0)
+    {
+      uprint("  ");
+      word j;
+      for (j = i - (linesize-1); j < i+1; j++)
+      {
+        if (isalnum(addr[j]) || addr[j] == ' ')
+          uprintc(addr[j]);
+        else
+          uprintc('.');
+      }
+      uprintc('\n');
+    }
+  }
+}
+
+
+/**
+ * Create a raw filesystem dump over UART
+ * fatsize: size of the FAT table in words
+ * datasize: size of the data section in words
+*/
+void brfs_dump(word fatsize, word datasize)
+{
+  // Superblock dump
+  uprintln("Superblock:");
+  brfs_dump_section(brfs_ram_storage, SUPERBLOCK_SIZE, 16);
+
+  // FAT dump
+  uprintln("\nFAT:");
+  brfs_dump_section(brfs_ram_storage+SUPERBLOCK_SIZE, fatsize, 16);
+
+  // Datablock dump
+  uprintln("\nData:");
+  brfs_dump_section(brfs_ram_storage+SUPERBLOCK_SIZE+fatsize, datasize, 32);
+
+  uprintln("\nOpen files:");
+  word i;
+  for (i = 0; i < MAX_OPEN_FILES; i++)
+  {
+    uprint("FP");
+    uprintDec(i+1);
+    uprint(":");
+    uprint(" FAT idx: ");
+    uprintDec(brfs_file_pointers[i]);
+    uprint(" Cursor: ");
+    uprintDec(brfs_cursors[i]);
+    uprint(" Size: ");
+    uprintDec(brfs_dir_entry_pointers[i] ? brfs_dir_entry_pointers[i]->filesize : 0);
+    uprintc('\n');
+  }
+}
+
+
+/**
+ * Return the FAT index of a directory, or -1 if not found
+ * dir_path: full path of the directory
+*/
+word brfs_get_fat_idx_of_dir(char* dir_path)
+{
+  // Check length of path
+  if (strlen(dir_path) > MAX_PATH_LENGTH)
+  {
+    uprintln("Path too long!");
+    return -1;
+  }
+
+  // Start with root directory
+  word current_dir_fat_idx = 0;
+
+  // Check if root directory is requested
+  if (strcmp(dir_path, "/") == 0)
+  {
+    return current_dir_fat_idx;
+  }
+
+  // Copy dir_path, size + 1 for null terminator
+  // Since strtok modifies the string
+  char dir_path_copy[MAX_PATH_LENGTH+1];
+  strcpy(dir_path_copy, dir_path);
+
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* brfs_data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+  word dir_entries_max = superblock->words_per_block / sizeof(struct brfs_dir_entry);
+
+  // Split path by '/' and traverse directories
+  char* token = strtok(dir_path_copy, "/");
+  while (token != (word*)-1)
+  {
+    // Find token in current directory
+    word* dir_addr = brfs_data_block_addr + (current_dir_fat_idx * superblock->words_per_block);
+    word found_dir = 0; // Keep track if token is found in current directory
+    word i;
+    for (i = 0; i < dir_entries_max; i++)
+    {
+      struct brfs_dir_entry* dir_entry = (struct brfs_dir_entry*) (dir_addr + (i * sizeof(struct brfs_dir_entry)));
+      if (dir_entry->filename[0] != 0)
+      {
+        char decompressed_filename[16];
+        strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
+        // Also check for directory flag
+        if (strcmp(decompressed_filename, token) == 0 && dir_entry->flags == 1)
+        {
+          // Found token in current directory
+          // Set current directory to token's FAT index
+          current_dir_fat_idx = dir_entry->fat_idx;
+          found_dir = 1;
+          break;
+        }
+      }
+    }
+
+    // If token not found in current directory, return -1
+    if (!found_dir)
+    {
+      uprint("Directory ");
+      uprint(dir_path);
+      uprintln(" not found!");
+      return -1;
+    }
+
+    token = strtok((word*)-1, "/");
+  }
+  return current_dir_fat_idx;
+}
+
+/**
+ * Given the address of the FAT table and the number of blocks, find the next free block
+ * Returns -1 if no free block is found
+ * fat_addr: address of the FAT table
+ * blocks: number of blocks in the FAT table
+*/
+word brfs_find_next_free_block(word* fat_addr, word blocks)
+{
+  word i = 0;
+  word* fat_ptr = fat_addr;
+
+  while (i < blocks)
+  {
+    if (*fat_ptr == 0)
+    {
+      return i;
+    }
+
+    fat_ptr++;
+    i++;
+  }
+
+  return -1;
+}
+
+/**
+ * Given the address of a directory data block and the maximum number of entries, find the next free directory entry
+ * Returns -1 if no free entry is found
+ * dir_addr: address of the directory data block (not the FAT idx)
+ * dir_entries_max: maximum number of entries in the directory
+*/
+word brfs_find_next_free_dir_entry(word* dir_addr, word dir_entries_max)
+{
+  word i = 0;
+  word* dir_ptr = dir_addr;
+
+  while (i < dir_entries_max)
+  {
+    if (*dir_ptr == 0)
+    {
+      return i;
+    }
+
+    dir_ptr += sizeof(struct brfs_dir_entry);
+    i++;
+  }
+
+  return -1;
+}
+
+/**
+ * Create a single directory entry
+ * dir_entry: pointer to the directory entry to be created
+ * filename: name of the file, max 16 chars and uncompressed
+ * fat_idx: index of the first FAT block of the file/directory
+ * filesize: size of the file in words
+ * flags: flags of the file/directory
+*/
+void brfs_create_single_dir_entry(struct brfs_dir_entry* dir_entry, char* filename, word fat_idx, word filesize, word flags)
+{
+  // Initialize to 0
+  memset((char*)dir_entry, 0, sizeof(*dir_entry));
+
+  // Set filename
+  char compressed_filename[4] = {0,0,0,0};
+  strcompress(compressed_filename, filename);
+  memcpy((char*)&(dir_entry->filename), compressed_filename, sizeof(compressed_filename));
+
+  // Set other fields
+  dir_entry->fat_idx = fat_idx;
+  dir_entry->flags = flags;
+  dir_entry->filesize = filesize;
+}
+
+/**
+ * Initialize a directory with . and .. entries
+ * dir_addr: address of the directory data block
+ * dir_entries_max: maximum number of entries in the directory
+ * dir_fat_idx: index of the FAT block of the directory
+ * parent_fat_idx: index of the FAT block of the parent directory
+*/
+void brfs_init_directory(word* dir_addr, word dir_entries_max, word dir_fat_idx, word parent_fat_idx)
+{
+  // Create . entry
+  struct brfs_dir_entry dir_entry;
+  brfs_create_single_dir_entry(&dir_entry, ".", dir_fat_idx, dir_entries_max*sizeof(struct brfs_dir_entry), 1);
+  // Copy to first data entry
+  memcpy(dir_addr, (char*)&dir_entry, sizeof(dir_entry));
+
+  // Create .. entry
+  brfs_create_single_dir_entry(&dir_entry, "..", parent_fat_idx, dir_entries_max*sizeof(struct brfs_dir_entry), 1);
+  // Copy to second data entry
+  memcpy(dir_addr+sizeof(dir_entry), (char*)&dir_entry, sizeof(dir_entry));
+
+  // Set FAT table
+  brfs_ram_storage[SUPERBLOCK_SIZE + dir_fat_idx] = -1;
+
+  // Set changed block
+  brfs_changed_blocks[dir_fat_idx >> 5] |= (1 << (dir_fat_idx & 31));
+}
+
+/**
+ * Format the ram storage as a BRFS filesystem
+ * Also writes the superblock to SPI Flash
+ * blocks: number of blocks in the filesystem
+ * words_per_block: number of bytes per block
+ * label: label of the filesystem
+ * full_format: if 1, initialize data section to 0
+*/
+void brfs_format(word blocks, word words_per_block, char* label, word full_format)
+{
+  // Create a superblock
+  struct brfs_superblock superblock;
+
+  // Initialize to 0
+  memset((char*)&superblock, 0, sizeof(superblock));
+
+  // Set values of superblock
+  superblock.total_blocks = blocks;
+  superblock.words_per_block = words_per_block;
+  strcpy((char*)&superblock.label, label);
+  superblock.brfs_version = BRFS_SUPPORTED_VERSION;
+
+  // Copy superblock to head of ram addr
+  memcpy(brfs_ram_storage, (char*)&superblock, sizeof(superblock));
+
+
+  // Create FAT
+  memset(brfs_ram_storage + SUPERBLOCK_SIZE, 0, blocks);
+
+  // Create Data section
+  if (full_format)
+  {
+    memset(brfs_ram_storage + SUPERBLOCK_SIZE + blocks, 0, blocks * words_per_block);
+  }
+  
+  // Initialize root dir
+  word dir_entries_max = words_per_block / sizeof(struct brfs_dir_entry);
+  brfs_init_directory(brfs_ram_storage + SUPERBLOCK_SIZE + blocks, dir_entries_max, 0, 0);
+
+  // Clear open files and cursors
+  memset(brfs_file_pointers, 0, sizeof(brfs_file_pointers));
+  memset(brfs_cursors, 0, sizeof(brfs_cursors));
+  // Set all dir entry pointers to 0
+  word i;
+  for (i = 0; i < MAX_OPEN_FILES; i++)
+  {
+    brfs_dir_entry_pointers[i] = 0;
+  }
+
+  // For all blocks that have just been formatted, set changed block
+  word j;
+  for (j = 0; j < blocks; j++)
+  {
+    brfs_changed_blocks[j >> 5] |= (1 << (j & 31));
+  }
+
+  // Write superblock to SPI Flash
+  spiflash_sector_erase(BRFS_SPIFLASH_SUPERBLOCK_ADDR);
+  spiflash_write_page_in_words((char*)&superblock, BRFS_SPIFLASH_SUPERBLOCK_ADDR, sizeof(superblock));
+}
+
+/**
+ * Create a new directory in the directory of parent_dir_path
+ * Returns 1 on success, 0 on error
+ * parent_dir_path: full path of the parent directory
+ * dirname: name of the new directory
+*/
+word brfs_create_directory(char* parent_dir_path, char* dirname)
+{
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+
+  word* brfs_data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+
+  // Find first free FAT block
+  word next_free_block = brfs_find_next_free_block(brfs_ram_storage + SUPERBLOCK_SIZE, superblock->total_blocks);
+  if (next_free_block == -1)
+  {
+    uprintln("No free blocks left!");
+    return 0;
+  }
+
+  // Find data block address of parent directory path
+  word parent_dir_fat_idx = brfs_get_fat_idx_of_dir(parent_dir_path);
+  if (parent_dir_fat_idx == -1)
+  {
+    uprint("Parent directory ");
+    uprint(parent_dir_path);
+    uprintln(" not found!");
+    return 0;
+  }
+
+  // Check if file or folder already exists
+  word* parent_dir_addr = brfs_data_block_addr + (parent_dir_fat_idx * superblock->words_per_block);
+  word dir_entries_max = superblock->words_per_block / sizeof(struct brfs_dir_entry);
+  word i;
+  for (i = 0; i < dir_entries_max; i++)
+  {
+    struct brfs_dir_entry* dir_entry = (struct brfs_dir_entry*) (parent_dir_addr + (i * sizeof(struct brfs_dir_entry)));
+    if (dir_entry->filename[0] != 0)
+    {
+      char decompressed_filename[16];
+      strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
+      if (strcmp(decompressed_filename, dirname) == 0)
+      {
+        uprint(dirname);
+        uprintln(" already exists!");
+        return 0;
+      }
+    }
+  }
+
+  // Find first free dir entry
+  word next_free_dir_entry = brfs_find_next_free_dir_entry(
+    brfs_data_block_addr + (parent_dir_fat_idx * superblock->words_per_block), 
+    superblock->words_per_block / sizeof(struct brfs_dir_entry)
+  );
+  if (next_free_dir_entry == -1)
+  {
+    uprintln("No free dir entries left!");
+    return 0;
+  }
+
+  // Create dir entry
+  struct brfs_dir_entry new_entry;
+  brfs_create_single_dir_entry(&new_entry, dirname, next_free_block, 0, 1);
+
+  // Copy dir entry to first free dir entry
+  memcpy(
+    brfs_data_block_addr + (parent_dir_fat_idx * superblock->words_per_block) + (next_free_dir_entry * sizeof(struct brfs_dir_entry)),
+    (char*)&new_entry,
+    sizeof(new_entry)
+  );
+
+  // Initialize directory
+  brfs_init_directory(
+    brfs_data_block_addr + (next_free_block * superblock->words_per_block),
+    dir_entries_max,
+    next_free_block,
+    parent_dir_fat_idx
+  );
+
+  // Update changed block
+  brfs_changed_blocks[next_free_block >> 5] |= (1 << (next_free_block & 31));
+
+  return 1;
+}
+
+/**
+ * Create a new file in the directory of parent_dir_path
+ * Returns 1 on success, 0 on error
+ * parent_dir_path: full path of the parent directory
+ * filename: name of the new file
+*/
+word brfs_create_file(char* parent_dir_path, char* filename)
+{
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+
+  word* brfs_data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+
+  // Find first free FAT block
+  word next_free_block = brfs_find_next_free_block(brfs_ram_storage + SUPERBLOCK_SIZE, superblock->total_blocks);
+  if (next_free_block == -1)
+  {
+    uprintln("No free blocks left!");
+    return 0;
+  }
+
+  // Find data block address of parent directory path
+  word parent_dir_fat_idx = brfs_get_fat_idx_of_dir(parent_dir_path);
+  if (parent_dir_fat_idx == -1)
+  {
+    uprint("Parent directory ");
+    uprint(parent_dir_path);
+    uprintln(" not found!");
+    return 0;
+  }
+
+  // Check if file or folder already exists
+  word* parent_dir_addr = brfs_data_block_addr + (parent_dir_fat_idx * superblock->words_per_block);
+  word dir_entries_max = superblock->words_per_block / sizeof(struct brfs_dir_entry);
+  word i;
+  for (i = 0; i < dir_entries_max; i++)
+  {
+    struct brfs_dir_entry* dir_entry = (struct brfs_dir_entry*) (parent_dir_addr + (i * sizeof(struct brfs_dir_entry)));
+    if (dir_entry->filename[0] != 0)
+    {
+      char decompressed_filename[16];
+      strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
+      if (strcmp(decompressed_filename, filename) == 0)
+      {
+        uprint(filename);
+        uprintln(" already exists!");
+        return 0;
+      }
+    }
+  }
+
+  // Find first free dir entry
+  word next_free_dir_entry = brfs_find_next_free_dir_entry(
+    brfs_data_block_addr + (parent_dir_fat_idx * superblock->words_per_block), 
+    superblock->words_per_block / sizeof(struct brfs_dir_entry)
+  );
+  if (next_free_dir_entry == -1)
+  {
+    uprintln("No free dir entries left!");
+    return 0;
+  }
+
+  // Create file entry
+  struct brfs_dir_entry new_entry;
+  brfs_create_single_dir_entry(&new_entry, filename, next_free_block, 0, 0);
+
+  // Copy dir entry to first free dir entry
+  memcpy(
+    brfs_data_block_addr + (parent_dir_fat_idx * superblock->words_per_block) + (next_free_dir_entry * sizeof(struct brfs_dir_entry)),
+    (char*)&new_entry,
+    sizeof(new_entry)
+  );
+
+  // Initialize file by setting data to 0
+  memset(
+    brfs_data_block_addr + (next_free_block * superblock->words_per_block),
+    0,
+    superblock->words_per_block
+  );
+
+  // Update FAT
+  brfs_ram_storage[SUPERBLOCK_SIZE + next_free_block] = -1;
+
+  // Update changed block
+  brfs_changed_blocks[next_free_block >> 5] |= (1 << (next_free_block & 31));
+
+  return 1;
+}
+
+/**
+ * List the contents of a directory over UART
+ * dir_path: full path of the directory
+*/
+void brfs_list_directory(char* dir_path)
+{
+  uprint("Listing directory ");
+  uprintln(dir_path);
+  uprintln("-------------------");
+
+  // Find data block address of parent directory path
+  word dir_fat_idx = brfs_get_fat_idx_of_dir(dir_path);
+  if (dir_fat_idx == -1)
+  {
+    uprint("Parent directory ");
+    uprint(dir_path);
+    uprintln(" not found!");
+    return;
+  }
+
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* dir_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks + (dir_fat_idx * superblock->words_per_block);
+  word dir_entries_max = superblock->words_per_block / sizeof(struct brfs_dir_entry);
+
+  word i;
+  for (i = 0; i < dir_entries_max; i++)
+  {
+    struct brfs_dir_entry* dir_entry = (struct brfs_dir_entry*) (dir_addr + (i * sizeof(struct brfs_dir_entry)));
+    if (dir_entry->filename[0] != 0)
+    {
+      uprint("Filename: ");
+      char decompressed_filename[16];
+      strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
+      uprint(decompressed_filename);
+      uprint(" FAT idx: ");
+      uprintDec((dir_entry->fat_idx));
+      uprint(" Flags: ");
+      uprintDec((dir_entry->flags));
+      uprint(" Filesize: ");
+      uprintDec((dir_entry->filesize));
+      uprintc('\n');
+    }
+  }
+  uprintln("");
+}
+
+/**
+ * Open a file for reading and writing
+ * Returns the file pointer (FAT idx of file), or -1 on error
+ * file_path: full path of the file
+*/
+word brfs_open_file(char* file_path)
+{
+
+  // Split filename from path using basename and dirname
+  char dirname_output[MAX_PATH_LENGTH];
+  char* file_path_basename = basename(file_path);
+  char* file_path_dirname = dirname(dirname_output, file_path);
+
+  // Find data block address of parent directory path
+  word dir_fat_idx = brfs_get_fat_idx_of_dir(file_path_dirname);
+  if (dir_fat_idx == -1)
+  {
+    uprint("Parent directory ");
+    uprint(file_path_dirname);
+    uprintln(" not found!");
+    return -1;
+  }
+
+  // Find file in directory
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* dir_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks + (dir_fat_idx * superblock->words_per_block);
+  word dir_entries_max = superblock->words_per_block / sizeof(struct brfs_dir_entry);
+
+  word i;
+  for (i = 0; i < dir_entries_max; i++)
+  {
+    struct brfs_dir_entry* dir_entry = (struct brfs_dir_entry*) (dir_addr + (i * sizeof(struct brfs_dir_entry)));
+    if (dir_entry->filename[0] != 0)
+    {
+      char decompressed_filename[16];
+      strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
+      // Also check for directory flag to be 0
+      if (strcmp(decompressed_filename, file_path_basename) == 0 && dir_entry->flags == 0)
+      {
+        // Found file
+        // Check if file is already open
+        word j;
+        for (j = 0; j < MAX_OPEN_FILES; j++)
+        {
+          if (brfs_file_pointers[j] == dir_entry->fat_idx)
+          {
+            uprint("File ");
+            uprint(file_path_basename);
+            uprintln(" already open!");
+            return -1;
+          }
+        }
+
+        // Find first free file pointer
+        word next_free_file_pointer = -1;
+        for (j = 0; j < MAX_OPEN_FILES; j++)
+        {
+          if (brfs_file_pointers[j] == 0)
+          {
+            next_free_file_pointer = j;
+            break;
+          }
+        }
+
+        if (next_free_file_pointer == -1)
+        {
+          uprintln("All files already opened!");
+          return -1;
+        }
+
+        // Open file
+        brfs_file_pointers[next_free_file_pointer] = dir_entry->fat_idx;
+        brfs_cursors[next_free_file_pointer] = 0;
+        brfs_dir_entry_pointers[next_free_file_pointer] = dir_entry;
+        return brfs_file_pointers[next_free_file_pointer];
+      }
+    }
+  }
+  uprint("File ");
+  uprint(file_path_basename);
+  uprintln(" not found!");
+  return -1;
+}
+
+/**
+ * Close an opened file
+ * Returns 1 on success, 0 on error
+ * file_pointer: file pointer returned by brfs_open_file
+*/
+word brfs_close_file(word file_pointer)
+{
+  // Find file pointer
+  word i;
+  for (i = 0; i < MAX_OPEN_FILES; i++)
+  {
+    if (brfs_file_pointers[i] == file_pointer)
+    {
+      // Close file
+      brfs_file_pointers[i] = 0;
+      brfs_cursors[i] = 0;
+      brfs_dir_entry_pointers[i] = 0;
+      return 1;
+    }
+  }
+  uprintln("File not found!");
+  return 0;
+}
+
+
+/**
+ * Delete a file by removing all FAT blocks and the directory entry
+ * Returns 1 on success, 0 on error
+ * file_path: full path of the file
+*/
+word brfs_delete_file(char* file_path)
+{
+  // Split filename from path using basename and dirname
+  char dirname_output[MAX_PATH_LENGTH];
+  char* file_path_basename = basename(file_path);
+  char* file_path_dirname = dirname(dirname_output, file_path);
+
+  // Find data block address of parent directory path
+  word dir_fat_idx = brfs_get_fat_idx_of_dir(file_path_dirname);
+  if (dir_fat_idx == -1)
+  {
+    uprint("Parent directory ");
+    uprint(file_path_dirname);
+    uprintln(" not found!");
+    return 0;
+  }
+
+  // Find file in directory
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* dir_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks + (dir_fat_idx * superblock->words_per_block);
+  word dir_entries_max = superblock->words_per_block / sizeof(struct brfs_dir_entry);
+
+  word i;
+  for (i = 0; i < dir_entries_max; i++)
+  {
+    struct brfs_dir_entry* dir_entry = (struct brfs_dir_entry*) (dir_addr + (i * sizeof(struct brfs_dir_entry)));
+    if (dir_entry->filename[0] != 0)
+    {
+      char decompressed_filename[16];
+      strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
+      // Also check for directory flag to be 0
+      if (strcmp(decompressed_filename, file_path_basename) == 0 && dir_entry->flags == 0)
+      {
+        // Found file
+        // Check if file is already open
+        word j;
+        for (j = 0; j < MAX_OPEN_FILES; j++)
+        {
+          if (brfs_file_pointers[j] == dir_entry->fat_idx)
+          {
+            uprint("File ");
+            uprint(file_path_basename);
+            uprintln(" is open!");
+            return 0;
+          }
+        }
+
+        // Delete fat blocks
+        word current_fat_idx = dir_entry->fat_idx;
+        word next_fat_idx;
+        while (current_fat_idx != -1)
+        {
+          next_fat_idx = brfs_ram_storage[SUPERBLOCK_SIZE + current_fat_idx];
+          brfs_ram_storage[SUPERBLOCK_SIZE + current_fat_idx] = 0;
+          brfs_changed_blocks[current_fat_idx >> 5] |= (1 << (current_fat_idx & 31));
+          current_fat_idx = next_fat_idx;
+        }
+
+        // Delete file
+        memset((char*)dir_entry, 0, sizeof(struct brfs_dir_entry));
+
+        // Update changed block
+        brfs_changed_blocks[dir_fat_idx >> 5] |= (1 << (dir_fat_idx & 31));
+        return 1;
+      }
+    }
+  }
+  uprint("File ");
+  uprint(file_path_basename);
+  uprintln(" not found!");
+  return 0;
+}
+
+/**
+ * Set the cursor of an opened file
+ * Returns 1 on success, 0 on error
+ * file_pointer: file pointer returned by brfs_open_file
+ * cursor: new cursor position in words
+*/
+word brfs_set_cursor(word file_pointer, word cursor)
+{
+  if (file_pointer == 0)
+  {
+    uprintln("File not open!");
+    return 0;
+  }
+
+  // Find file pointer
+  word i;
+  for (i = 0; i < MAX_OPEN_FILES; i++)
+  {
+    if (brfs_file_pointers[i] == file_pointer)
+    {
+      // Set cursor
+      if (cursor < 0 || cursor > brfs_dir_entry_pointers[i]->filesize)
+      {
+        cursor = brfs_dir_entry_pointers[i]->filesize;
+      }
+
+      brfs_cursors[i] = cursor;
+      return 1;
+    }
+  }
+  uprintln("File not found!");
+  return 0;
+}
+
+/**
+ * Get the cursor of an opened file
+ * Returns the cursor position in words, or -1 on error
+ * file_pointer: file pointer returned by brfs_open_file
+*/
+word brfs_get_cursor(word file_pointer)
+{
+  if (file_pointer == 0)
+  {
+    uprintln("File not open!");
+    return -1;
+  }
+
+  // Find file pointer
+  word i;
+  for (i = 0; i < MAX_OPEN_FILES; i++)
+  {
+    if (brfs_file_pointers[i] == file_pointer)
+    {
+      // Get cursor
+      return brfs_cursors[i];
+    }
+  }
+  uprintln("File not found!");
+  return -1;
+}
+
+/**
+ * Get the FAT index of a file at the cursor
+ * Returns the FAT index, or 0 on error
+ * file_pointer: file pointer returned by brfs_open_file
+ * cursor: cursor position of opened file
+*/
+word brfs_get_fat_idx_at_cursor(word file_pointer, word cursor)
+{
+  if (file_pointer == 0)
+  {
+    uprintln("File not open!");
+    return 0;
+  }
+  // Get FAT index of file at cursor
+  word current_fat_idx = file_pointer;
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+
+  // Loop through FAT until cursor is reached
+  while (cursor > superblock->words_per_block)
+  {
+    current_fat_idx = brfs_ram_storage[SUPERBLOCK_SIZE + current_fat_idx];
+    if (current_fat_idx == -1)
+    {
+      uprintln("Cursor is out of bounds!");
+      return 0;
+    }
+    cursor -= superblock->words_per_block;
+  }
+
+  return current_fat_idx;
+}
+
+/**
+ * Read a file from the cursor position
+ * Returns 1 on success, or 0 on error
+ * file_pointer: file pointer returned by brfs_open_file
+ * buffer: buffer to read the file into
+ * length: number of words to read
+*/
+word brfs_read(word file_pointer, word* buffer, word length)
+{
+  if (file_pointer == 0)
+  {
+    uprintln("File not open!");
+    return 0;
+  }
+
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+
+  // Find file pointer
+  word i;
+  for (i = 0; i < MAX_OPEN_FILES; i++)
+  {
+    if (brfs_file_pointers[i] == file_pointer)
+    {
+      if (length < 0)
+      {
+        uprintln("Length cannot be negative!");
+        return 0;
+      }
+      // Trunctate length to file size - cursor
+      if (length > brfs_dir_entry_pointers[i]->filesize - brfs_cursors[i])
+      {
+        length = brfs_dir_entry_pointers[i]->filesize - brfs_cursors[i];
+      }
+
+      // Get FAT index of file at cursor
+      word current_fat_idx = brfs_get_fat_idx_at_cursor(file_pointer, brfs_cursors[i]);
+      if (current_fat_idx == 0)
+      {
+        uprintln("Error getting FAT index at cursor!");
+        return 0;
+      }
+
+      // Loop:
+      // - calculate words until end of block (or up to length)
+      // - read words until end of block (or up to length)
+      // - decrease length by words read
+      // - get next block from FAT
+      // - repeat until length is 0
+      while (length > 0)
+      {
+        word words_until_end_of_block = superblock->words_per_block - (MATH_modU(brfs_cursors[i], superblock->words_per_block));
+        word words_to_read = words_until_end_of_block > length ? length : words_until_end_of_block;
+
+        // Copy words to buffer
+        memcpy(buffer, data_block_addr + (current_fat_idx * superblock->words_per_block) + MATH_modU(brfs_cursors[i], superblock->words_per_block), words_to_read);
+
+        // Update cursor and length
+        brfs_cursors[i] += words_to_read;
+        length -= words_to_read;
+        buffer += words_to_read;
+
+        // Get next block from FAT
+        current_fat_idx = brfs_ram_storage[SUPERBLOCK_SIZE + current_fat_idx];
+        if (current_fat_idx == -1 && length > 0)
+        {
+          uprintln("There is no next block in the file!");
+          return 0;
+        }
+
+        
+      }
+
+      return 1;
+    }
+  }
+  uprintln("File not found!");
+  return 0;
+}
+
+/**
+ * Write a file from the cursor position
+ * Returns 1 on success, or 0 on error
+ * file_pointer: file pointer returned by brfs_open_file
+ * buffer: buffer to write to the file
+ * length: number of words to write
+*/
+word brfs_write(word file_pointer, word* buffer, word length)
+{
+  if (file_pointer == 0)
+  {
+    uprintln("File not open!");
+    return 0;
+  }
+
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+
+  // Find file pointer
+  word i;
+  for (i = 0; i < MAX_OPEN_FILES; i++)
+  {
+    if (brfs_file_pointers[i] == file_pointer)
+    {
+      if (length < 0)
+      {
+        uprintln("Length cannot be negative!");
+        return 0;
+      }
+
+      // Get FAT index of file at cursor
+      word current_fat_idx = brfs_get_fat_idx_at_cursor(file_pointer, brfs_cursors[i]);
+      if (current_fat_idx == 0)
+      {
+        uprintln("Error getting FAT index at cursor!");
+        return 0;
+      }
+
+      // Loop:
+      // - calculate words until end of block (or up to length)
+      // - write words until end of block (or up to length)
+      // - decrease length by words written
+      // - get next block from FAT, or find next free block if end of block
+      // - if next block is needed, update FAT
+      // - repeat until length is 0
+      while (length > 0)
+      {
+        word cursor_in_block = MATH_modU(brfs_cursors[i], superblock->words_per_block);
+        word words_until_end_of_block = superblock->words_per_block - cursor_in_block;
+        word words_to_write = words_until_end_of_block > length ? length : words_until_end_of_block;
+
+        // Copy words to buffer
+        memcpy(data_block_addr + (current_fat_idx * superblock->words_per_block) + cursor_in_block, buffer, words_to_write);
+
+        // Update changed block
+        brfs_changed_blocks[current_fat_idx >> 5] |= (1 << (current_fat_idx & 31));
+
+        // Update cursor and length
+        brfs_cursors[i] += words_to_write;
+        length -= words_to_write;
+        buffer += words_to_write;
+
+        // Get next block from FAT, or find next free block if end of block
+        if (words_until_end_of_block == words_to_write && length > 0)
+        {
+          
+          word next_fat_idx = brfs_ram_storage[SUPERBLOCK_SIZE + current_fat_idx];
+          // Check if next block is already allocated
+          if (next_fat_idx != -1)
+          {
+            current_fat_idx = next_fat_idx;
+          }
+          else
+          {
+            // Find next free block
+            word next_free_block = brfs_find_next_free_block(brfs_ram_storage + SUPERBLOCK_SIZE, superblock->total_blocks);
+            if (next_free_block == -1)
+            {
+              uprintln("No free blocks left!");
+              return 0;
+            }
+            // Update FAT
+            brfs_ram_storage[SUPERBLOCK_SIZE + current_fat_idx] = next_free_block;
+            // Go to next block
+            current_fat_idx = next_free_block;
+            // Set next block to -1 to indicate end of file
+            brfs_ram_storage[SUPERBLOCK_SIZE + current_fat_idx] = -1;
+            // Update changed block
+            brfs_changed_blocks[current_fat_idx >> 5] |= (1 << (current_fat_idx & 31));
+          }
+        }
+      }
+
+      // Update file size in dir entry if we wrote past the current size
+      if (brfs_cursors[i] > brfs_dir_entry_pointers[i]->filesize)
+      {
+        brfs_dir_entry_pointers[i]->filesize = brfs_cursors[i];
+      }
+
+      return 1;
+    }
+  }
+  uprintln("File not found!");
+  return 0;
+}
+
+/**
+ * Stat a file or directory
+ * Returns the directory entry, or -1 on error
+*/
+struct brfs_dir_entry* brfs_stat(char* file_path)
+{
+  // Split filename from path using basename and dirname
+  char dirname_output[MAX_PATH_LENGTH];
+  char* file_path_basename = basename(file_path);
+  char* file_path_dirname = dirname(dirname_output, file_path);
+
+  // Find data block address of parent directory path
+  word dir_fat_idx = brfs_get_fat_idx_of_dir(file_path_dirname);
+  if (dir_fat_idx == -1)
+  {
+    uprint("Parent directory ");
+    uprint(file_path_dirname);
+    uprintln(" not found!");
+    return (struct brfs_dir_entry*)-1;
+  }
+
+  // Find file in directory
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* dir_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks + (dir_fat_idx * superblock->words_per_block);
+  word dir_entries_max = superblock->words_per_block / sizeof(struct brfs_dir_entry);
+
+  word i;
+  for (i = 0; i < dir_entries_max; i++)
+  {
+    struct brfs_dir_entry* dir_entry = (struct brfs_dir_entry*) (dir_addr + (i * sizeof(struct brfs_dir_entry)));
+    if (dir_entry->filename[0] != 0)
+    {
+      char decompressed_filename[16];
+      strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
+      // Also check for directory flag to be 0
+      if (strcmp(decompressed_filename, file_path_basename) == 0)
+      {
+        return dir_entry;
+      }
+    }
+  }
+  uprint("File or directory ");
+  uprint(file_path_basename);
+  uprintln(" not found!");
+  return (struct brfs_dir_entry*)-1;
+}
+
+/**
+ * Check if a block has changed by comparing it to the flash, returns 1 if changed and 0 if not
+ * Note: this is slow and should eventually be replaced by a list of changed blocks
+ * block_idx: index of the block
+*/
+word brfs_check_block_changed(word block_idx)
+{
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+
+  word spi_data_buffer[256];
+
+  if (superblock->words_per_block > 256)
+  {
+    uprintln("Error: words_per_block should be <= 256 for this function!");
+    return 0;
+  }
+
+  // Read block from flash, and enable bytes to word
+  spiflash_read_from_address(spi_data_buffer, BRFS_SPIFLASH_BLOCK_ADDR + block_idx * superblock->words_per_block, superblock->words_per_block, 1);
+
+  // Compare block to flash
+  return memcmp(data_block_addr + (block_idx * superblock->words_per_block), spi_data_buffer, superblock->words_per_block);
+}
+
+/**
+ * Write the FAT table to SPI flash by performing three steps:
+ * 1. Check which FAT entries have changed
+ * 2. Erase the 4KiB sectors that contain these FAT entries
+ * 3. Write each changed FAT entry to flash by using 16 page writes per sector
+*/
+void brfs_write_fat_to_flash()
+{
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+
+  // 1 sector = 4KiB = 1024 words = 1024 FAT entries
+  // 1 word contains 32 flags for changed blocks/FAT entries
+  // 1024/32 = 32 words in the changed_blocks array per sector
+
+  uprintln("---Writing FAT to SPI Flash---");
+
+  // Loop over brfs_changed_blocks in 32 word parts
+  // Assumes length of brfs_changed_blocks is a multiple of 32
+  word i;
+  for (i = 0; i < sizeof(brfs_changed_blocks); i+=32)
+  {
+    // Check if any value within brfs_changed_blocks[i:i+32] is not 0
+    word j;
+    word changed = 0;
+    for (j = 0; j < 32; j++)
+    {
+      if (brfs_changed_blocks[i+j] != 0)
+      {
+        changed = 1;
+        break;
+      }
+    }
+
+    if (changed)
+    {
+      // Erase sector
+      word addr = BRFS_SPIFLASH_FAT_ADDR; // Workaround because of large static number
+      addr += (i >> 5) * 4096; // Sector idx * bytes per sector
+      spiflash_sector_erase(addr);
+      uprint("Erased sector ");
+      uprintDec(i >> 5);
+      uprint(" at address ");
+      uprintHex(addr);
+      uprintln("");
+
+
+      // Write sector by writing 16 pages k of 64 words
+      // Does not check for boundaries of actual FAT table size,
+      //  so it can write garbage if block size is not a multiple of 1024
+      word k;
+      for (k = 0; k < 1024; k+=64)
+      {
+        addr = BRFS_SPIFLASH_FAT_ADDR; // Workaround because of large static number
+        addr += (i >> 5) * 4096; // Sector idx * bytes per sector
+        addr += k << 2; // 64 words * 4 bytes per word
+
+        word* fat_addr_ram = brfs_ram_storage + SUPERBLOCK_SIZE + (i << 5) + k;
+        spiflash_write_page_in_words(fat_addr_ram, addr, 64);
+
+        uprint("Wrote FAT entries ");
+        uprintDec((i << 5) + k);
+        uprint(":");
+        uprintDec((i << 5) + k + 63);
+        uprint(" from RAM addr ");
+        uprintHex((word)fat_addr_ram);
+        uprint(" to SPI Flash addr ");
+        uprintHex(addr);
+        uprintln("");
+      }
+    }
+  }
+
+  uprintln("---Finished writing FAT to SPI Flash---");
+}
+
+/**
+ * Write a sector (4KiB) to SPI flash
+ * sector_idx: index of the sector
+*/
+void brfs_write_sector_to_flash(word sector_idx)
+{
+  word spi_addr = BRFS_SPIFLASH_BLOCK_ADDR; // Workaround because of large static number
+  spi_addr += sector_idx * 4096; // Sector idx * bytes per sector
+  
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+  word brfs_sector_addr = data_block_addr + sector_idx * (4096 >> 2); // Divided by 4 because of word size
+
+  // Write sector by writing 16 pages k of 64 words
+  // Does not check for boundaries of actual FAT table size,
+  //  so it can write garbage if block size is not a multiple of 1024
+  word k;
+  for (k = 0; k < 1024; k+=64)
+  {
+    spiflash_write_page_in_words(brfs_sector_addr + k, spi_addr + (k << 2), 64);
+
+    uprint("Wrote sector ");
+    uprintDec(sector_idx);
+    uprint(":");
+    uprintDec(sector_idx + 15);
+    uprint(" from RAM addr ");
+    uprintHex((word)(brfs_sector_addr + k));
+    uprint(" to SPI Flash addr ");
+    uprintHex(spi_addr + (k << 2));
+    uprintln("");
+  }
+}
+
+/**
+ * Write the data blocks to SPI flash by performing three steps:
+ * 1. Check which blocks have changed
+ * 2. Erase the 4KiB sectors that contain these blocks
+ * 3. Write each erased sector with the new block data by using 16 page writes per sector
+*/
+void brfs_write_blocks_to_flash()
+{
+  // Loop over all blocks
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+
+  // Check if block size is <= 4KiB
+  if (superblock->words_per_block > 1024)
+  {
+    uprintln("Error: block size should be <= 4KiB");
+    return;
+  }
+
+  // Check if block size is a multiple of 64
+  if (superblock->words_per_block & 63)
+  {
+    uprintln("Error: block size should be a multiple of 64");
+    return;
+  }
+
+  uprintln("---Writing blocks to SPI Flash---");
+
+  word blocks_per_sector = MATH_divU(4096, superblock->words_per_block * 4);
+  uprint("Blocks per sector: ");
+  uprintDec(blocks_per_sector);
+  uprintln("");
+
+  // Erase 4KiB sectors that contain changed blocks
+  // This code is written such that it only erases each sector once, even if multiple blocks in the sector have changed
+  word i;
+  word sector_to_erase = -1;
+  for (i = 0; i < superblock->total_blocks; i++)
+  {
+    if (brfs_changed_blocks[i >> 5] & (1 << (i & 31)))
+    {
+      if (sector_to_erase == -1)
+      {
+        sector_to_erase = MATH_divU(i, blocks_per_sector);
+      }
+      else if (sector_to_erase != MATH_divU(i, blocks_per_sector))
+      {
+        word addr = BRFS_SPIFLASH_BLOCK_ADDR; // Workaround because of large static number
+        addr += sector_to_erase * 4096;
+        spiflash_sector_erase(addr);
+        uprint("Erased sector ");
+        uprintDec(sector_to_erase);
+        uprint(" at address ");
+        uprintHex(addr);
+        uprintln("");
+
+        brfs_write_sector_to_flash(sector_to_erase);
+
+        sector_to_erase = MATH_divU(i, blocks_per_sector);
+      }
+    }
+  }
+  if (sector_to_erase != -1)
+  {
+    word addr = BRFS_SPIFLASH_BLOCK_ADDR; // Workaround because of large static number
+    addr += sector_to_erase * 4096;
+    spiflash_sector_erase(addr);
+    uprint("Erased sector ");
+    uprintDec(sector_to_erase);
+    uprint(" at address ");
+    uprintHex(addr);
+    uprintln("");
+
+    brfs_write_sector_to_flash(sector_to_erase);
+  }
+
+  // Write each block to flash in parts of 64 words
+  /*
+  for (i = 0; i < superblock->total_blocks; i++)
+  {
+    if (brfs_changed_blocks[i >> 5] & (1 << (i & 31)))
+    {
+      word j;
+      for (j = 0; j < superblock->words_per_block; j+=64)
+      {
+        word spiflash_addr = BRFS_SPIFLASH_BLOCK_ADDR; // Workaround because of large static number
+        spiflash_addr += i * (superblock->words_per_block * 4) + (j * 4);
+
+        word* data_addr = data_block_addr + (i * superblock->words_per_block) + j;
+        spiflash_write_page_in_words(data_addr, spiflash_addr, 64);
+
+        uprint("Wrote block ");
+        uprintDec(i);
+        uprint(" from RAM addr ");
+        uprintHex((word)data_addr);
+        uprint(" to SPI Flash addr ");
+        uprintHex(spiflash_addr);
+        uprintln("");
+      }
+    }
+  }
+  */
+
+  uprintln("---Finished writing blocks to SPI Flash---");
+}
+
+/**
+ * Write the FAT and data blocks to SPI flash
+ * Superblock should already be written to flash during format
+*/
+void brfs_write_to_flash()
+{
+  brfs_write_fat_to_flash();
+  brfs_write_blocks_to_flash();
+}
+
+/**
+ * Checks if given superblock is valid
+ * Returns 1 if valid, 0 if invalid
+*/
+word brfs_superblock_is_valid(struct brfs_superblock* superblock)
+{
+  // Check if brfs version is correct
+  if (superblock->brfs_version != BRFS_SUPPORTED_VERSION)
+  {
+    uprint("BRFS version ");
+    uprintDec(superblock->brfs_version);
+    uprint(" is not supported by this implementation (");
+    uprintDec(BRFS_SUPPORTED_VERSION);
+    uprintln(")!");
+    return 0;
+  }
+  // Check if total blocks is > 0 and a multiple of 64
+  if (superblock->total_blocks == 0 || superblock->total_blocks & 63)
+  {
+    uprintln("Error: total blocks should be > 0 and a multiple of 64");
+    return 0;
+  }
+  // Check if block size is > 0
+  if (superblock->words_per_block == 0)
+  {
+    uprintln("Error: block size should be > 0");
+    return 0;
+  }
+  // Check if words per block is > 0 and <= 2048
+  if (superblock->words_per_block == 0 || superblock->words_per_block > 2048)
+  {
+    uprintln("Error: words per block should be > 0 and <= 2048");
+    return 0;
+  }
+
+  return 1;
+}
+
+/**
+ * Read the superblock, FAT and data blocks from SPI flash
+ * Returns 1 on success, or 0 on error
+*/
+word brfs_read_from_flash()
+{
+  // Read superblock from flash
+  spiflash_read_from_address(brfs_ram_storage, BRFS_SPIFLASH_SUPERBLOCK_ADDR, SUPERBLOCK_SIZE, 1);
+
+  // Perform validity checks on superblock
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  if (!brfs_superblock_is_valid(superblock))
+  {
+    uprintln("Error: superblock is not valid!");
+    return 0;
+  }
+  
+  word* data_block_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks;
+
+  // Read FAT from flash
+  spiflash_read_from_address(brfs_ram_storage + SUPERBLOCK_SIZE, BRFS_SPIFLASH_FAT_ADDR, superblock->total_blocks, 1);
+
+  // Read data blocks from flash
+  spiflash_read_from_address(data_block_addr, BRFS_SPIFLASH_BLOCK_ADDR, superblock->total_blocks * superblock->words_per_block, 1);
+
+  return 1;
+}

+ 3 - 12
BCC/BDOS/lib/fs.c

@@ -89,15 +89,6 @@ char (*fsLdirFnames)[FS_LDIR_FNAME_SIZE] = (char (*)[FS_LDIR_FNAME_SIZE]) FS_LDI
 word *fsLdirFsizes= (word *) FS_LDIR_FSIZE_ADDR;
 word fsLdirEntries = 0; // index for lists
 
-// Workaround for defines in ASM
-void FS_asmDefines()
-{
-    asm(
-        "define FS_SPI1_CS_ADDR = 0xC0272C ; address of SPI1_CS\n"
-        "define FS_SPI1_ADDR = 0xC0272B    ; address of SPI1\n"
-        );
-}
-
 // Sets SPI1_CS low
 void FS_spiBeginTransfer()
 {
@@ -106,7 +97,7 @@ void FS_spiBeginTransfer()
         "push r1\n"
         "push r2\n"
 
-        "load32 FS_SPI1_CS_ADDR r2          ; r2 = FS_SPI1_CS_ADDR\n"
+        "load32 0xC0272C r2                 ; r2 = FS_SPI1_CS_ADDR\n"
 
         "load 0 r1                          ; r1 = 0 (enable)\n"
         "write 0 r2 r1                      ; write to SPI1_CS\n"
@@ -125,7 +116,7 @@ void FS_spiEndTransfer()
         "push r1\n"
         "push r2\n"
 
-        "load32 FS_SPI1_CS_ADDR r2          ; r2 = FS_SPI1_CS_ADDR\n"
+        "load32 0xC0272C r2                 ; r2 = FS_SPI1_CS_ADDR\n"
 
         "load 1 r1                          ; r1 = 1 (disable)\n"
         "write 0 r2 r1                      ; write to SPI1_CS\n"
@@ -142,7 +133,7 @@ word FS_spiTransfer(word dataByte)
 {
     word retval = 0;
     asm(
-        "load32 FS_SPI1_ADDR r2             ; r2 = FS_SPI1_ADDR\n"
+        "load32 0xC0272B r2                 ; r2 = FS_SPI1_ADDR\n"
         "write 0 r2 r4                      ; write r4 over SPI1\n"
         "read 0 r2 r2                       ; read return value\n"
         "write -4 r14 r2                    ; write to stack to return\n"

+ 7 - 0
BCC/BDOS/lib/gfx.c

@@ -616,6 +616,13 @@ void GFX_PrintConsole(char* str)
     }
 }
 
+void GFX_PrintDecConsole(word i)
+{
+    char buffer[20];
+    itoa(i, buffer);
+    GFX_PrintConsole(buffer);
+}
+
 // Just for funny memory dumping
 void GFX_DumpcConsole(char c)
 {

+ 166 - 49
BCC/BDOS/lib/netloader.c

@@ -115,10 +115,9 @@ word NETLOADER_getContentStart(char* rbuf, word rsize)
 
 void NETLOADER_runProgramFromMemory()
 {
-    BDOS_Backup();
 
     // indicate that a user program is running
-    BDOS_userprogramRunning = 1;
+    bdos_userprogram_running = 1;
 
     // jump to the program
     asm(
@@ -163,14 +162,14 @@ void NETLOADER_runProgramFromMemory()
         );
 
     // indicate that no user program is running anymore
-    BDOS_userprogramRunning = 0;
+    bdos_userprogram_running = 0;
 
     // clear the shell
-    SHELL_clearCommand();
+    shell_clear_command();
 
     // setup the shell again
-    BDOS_Restore();
-    SHELL_print_prompt();
+    bdos_restore();
+    shell_print_prompt();
 }
 
 word NETLOADER_percentageDone(word remaining, word full)
@@ -192,6 +191,13 @@ void NETLOADER_handleSession(word s)
     char dbuf[10]; // percentage done for progress indication
     dbuf[0] = 0; // terminate
 
+    word fp = -1;
+
+    word shift = 24; // shift for compressing data, kept the same for the whole session
+
+    word leftOverData[4];
+    word leftOverBytes = 0;
+
     while (wizGetSockReg8(s, WIZNET_SnSR) == WIZNET_SOCK_ESTABLISHED)
     {
         word rsize = wizGetSockReg16(s, WIZNET_SnRX_RSR);
@@ -227,51 +233,40 @@ void NETLOADER_handleSession(word s)
                     NETLOADER_getFileName(rbuf, rsize, fileNameStr);
 
                     word failedToCreateFile = 1;
-                    // sanity check current path
-                    if (FS_sendFullPath(SHELL_path) == FS_ANSW_USB_INT_SUCCESS)
+
+                    // check length of filename
+                    if (strlen(fileNameStr) < 16)
                     {
-                        word retval = FS_open();
-                        // check that we can open the path
-                        if (retval == FS_ANSW_USB_INT_SUCCESS || retval == FS_ANSW_ERR_OPEN_DIR)
+                        char new_file_path[MAX_PATH_LENGTH];
+                        strcpy(new_file_path, shell_path);
+                        strcat(new_file_path, "/");
+                        strcat(new_file_path, fileNameStr);
+
+                        // try to delete file in case it exists
+                        brfs_delete_file(new_file_path);
+                        if (brfs_create_file(shell_path, fileNameStr))
                         {
-                            // check length of filename
-                            if (strlen(fileNameStr) <= 12)
+                            
+                            strcpy(new_file_path, shell_path);
+                            strcat(new_file_path, "/");
+                            strcat(new_file_path, fileNameStr);
+                            fp = brfs_open_file(new_file_path);
+                            if (fp != -1)
                             {
-                                // uppercase filename
-                                strToUpper(fileNameStr);
-                                // send filename
-                                FS_sendSinglePath(fileNameStr);
-
-                                // create the file
-                                if (FS_createFile() == FS_ANSW_USB_INT_SUCCESS)
-                                {
-                                    // open the path again
-                                    FS_sendFullPath(SHELL_path);
-                                    FS_open();
-                                    // send filename again
-                                    FS_sendSinglePath(fileNameStr);
-                                    // open the newly created file
-                                    if (FS_open() == FS_ANSW_USB_INT_SUCCESS)
-                                    {
-                                        // set cursor to start
-                                        if (FS_setCursor(0) == FS_ANSW_USB_INT_SUCCESS)
-                                        {
-                                            failedToCreateFile = 0;
-                                        }
-                                    }
-                                }
+                                brfs_set_cursor(fp, 0);
+                                failedToCreateFile = 0;
                             }
                         }
                     }
+
                     if (failedToCreateFile)
                     {
-                        FS_close();
                         wizWriteDataFromMemory(s, "ERR!", 4);
                         wizCmd(s, WIZNET_CR_DISCON);
                         GFX_PrintConsole("E: Could not create file\n");
                         // clear the shell
-                        SHELL_clearCommand();
-                        SHELL_print_prompt();
+                        shell_clear_command();
+                        shell_print_prompt();
                         return;
                     }
                 }
@@ -283,8 +278,8 @@ void NETLOADER_handleSession(word s)
                     wizCmd(s, WIZNET_CR_DISCON);
                     GFX_PrintConsole("E: Unknown netloader cmd\n");
                     // clear the shell
-                    SHELL_clearCommand();
-                    SHELL_print_prompt();
+                    shell_clear_command();
+                    shell_print_prompt();
                     return;
                 }
 
@@ -296,7 +291,50 @@ void NETLOADER_handleSession(word s)
 
                 if (downloadToFile)
                 {
-                    FS_writeFile(rbuf+dataStart, rsize - dataStart);
+                    // compress each 4 bytes into a word
+                    word compressed_data[WIZNET_MAX_RBUF];
+                    memset(compressed_data, 0, WIZNET_MAX_RBUF);
+                    word i;
+                    for (i = 0; i < rsize - dataStart; i++)
+                    {
+                        compressed_data[i >> 2] |= rbuf[dataStart + i] << shift;
+                        if (shift == 0)
+                        {
+                            shift = 24;
+                        }
+                        else
+                        {
+                            shift -= 8;
+                        }
+                    }
+                    brfs_write(fp, compressed_data, (rsize - dataStart)>>2);
+                    if (shift != 24)
+                    {
+
+                        // save the left over data
+                        leftOverBytes = (rsize - dataStart)&3;
+                        switch (leftOverBytes)
+                        {
+                            case 1:
+                                leftOverData[0] = rbuf[dataStart + (rsize - dataStart) - 1];
+                                break;
+                            case 2:
+                                leftOverData[0] = rbuf[dataStart + (rsize - dataStart) - 2];
+                                leftOverData[1] = rbuf[dataStart + (rsize - dataStart) - 1];
+                                break;
+                            case 3:
+                                leftOverData[0] = rbuf[dataStart + (rsize - dataStart) - 3];
+                                leftOverData[1] = rbuf[dataStart + (rsize - dataStart) - 2];
+                                leftOverData[2] = rbuf[dataStart + (rsize - dataStart) - 1];
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                    else
+                    {
+                        leftOverBytes = 0;
+                    }
                 }
                 else
                 {
@@ -314,10 +352,10 @@ void NETLOADER_handleSession(word s)
 
                     if (downloadToFile)
                     {
-                        FS_close();
+                        brfs_close_file(fp);
                         // clear the shell
-                        SHELL_clearCommand();
-                        SHELL_print_prompt();
+                        shell_clear_command();
+                        shell_print_prompt();
                     }
                     else
                     {
@@ -352,7 +390,86 @@ void NETLOADER_handleSession(word s)
 
                 if (downloadToFile)
                 {
-                    FS_writeFile(rbuf, rsize);
+                    // compress each 4 bytes into a word
+                    word compressed_data[WIZNET_MAX_RBUF];
+                    memset(compressed_data, 0, WIZNET_MAX_RBUF);
+
+                    // add the left over data
+                    switch (leftOverBytes)
+                    {
+                        case 1:
+                            compressed_data[0] = leftOverData[0] << 24;
+                            shift = 16;
+                            break;
+                        case 2:
+                            compressed_data[0] = leftOverData[0] << 24;
+                            compressed_data[0] |= leftOverData[1] << 16;
+                            shift = 8;
+                            break;
+                        case 3:
+                            compressed_data[0] = leftOverData[0] << 24;
+                            compressed_data[0] |= leftOverData[1] << 16;
+                            compressed_data[0] |= leftOverData[2] << 8;
+                            shift = 0;
+                            break;
+                    }
+
+                    word i;
+                    word j = leftOverBytes;
+                    for (i = 0; i < rsize; i++)
+                    {
+                        compressed_data[j >> 2] |= rbuf[i] << shift;
+                        if (shift == 0)
+                        {
+                            shift = 24;
+                        }
+                        else
+                        {
+                            shift -= 8;
+                        }
+                        j++;
+                    }
+                    if (shift != 24)
+                    {
+
+                        // save the left over data
+                        if (shift == 16)
+                        {
+                            leftOverBytes = 1;
+                        }
+                        else if (shift == 8)
+                        {
+                            leftOverBytes = 2;
+                        }
+                        else if (shift == 0)
+                        {
+                            leftOverBytes = 3;
+                        }
+                        //leftOverBytes = (rsize)&3;
+                        switch (leftOverBytes)
+                        {
+                            case 1:
+                                leftOverData[0] = rbuf[rsize - 1];
+                                break;
+                            case 2:
+                                leftOverData[0] = rbuf[rsize - 2];
+                                leftOverData[1] = rbuf[rsize - 1];
+                                break;
+                            case 3:
+                                leftOverData[0] = rbuf[rsize - 3];
+                                leftOverData[1] = rbuf[rsize - 2];
+                                leftOverData[2] = rbuf[rsize - 1];
+                                break;
+                            default:
+                                break;
+                        }
+                    }
+                    else
+                    {
+                        leftOverBytes = 0;
+                    }
+                    brfs_write(fp, compressed_data, rsize >> 2);
+                    //brfs_write(fp, rbuf, rsize);
                 }
                 else
                 {
@@ -376,10 +493,10 @@ void NETLOADER_handleSession(word s)
 
                     if (downloadToFile)
                     {
-                        FS_close();
+                        brfs_close_file(fp);
                         // clear the shell
-                        SHELL_clearCommand();
-                        SHELL_print_prompt();
+                        shell_clear_command();
+                        shell_print_prompt();
                     }
                     else
                     {

+ 922 - 0
BCC/BDOS/lib/oldshell.c

@@ -0,0 +1,922 @@
+/*
+* 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);
+            }
+        }
+    }
+}
+

+ 419 - 858
BCC/BDOS/lib/shell.c

@@ -1,922 +1,483 @@
-/*
-* 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
+#define SHELL_PATH "/bin/"
 
-// 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;
+word shell_cmd[SHELL_CMD_MAX_LENGTH];
+word shell_cmd_idx = 0;
+word* shell_tokens[SHELL_CMD_MAX_LENGTH >> 1]; // Array of tokens in the command
+word shell_num_tokens = 0;
 
-    // no need to update length if the history is already full
-    if (SHELL_historyLength < SHELL_CMD_HISTORY_LENGTH)
-    {
-        SHELL_historyLength++;
-    }
-}
+word shell_path[MAX_PATH_LENGTH];
 
-void SHELL_historyGoBack()
+/**
+ * Print the shell prompt
+*/
+void shell_print_prompt()
 {
-    // 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]);
-    }
+  GFX_PrintConsole(shell_path);
+  GFX_PrintConsole("> ");
 }
 
-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()
+/**
+ * Clear the current command buffer
+*/
+void shell_clear_command()
 {
-    SHELL_command[0] = 0;
-    SHELL_commandIdx = 0;
+  shell_cmd[0] = 0;
+  shell_cmd_idx = 0;
+  shell_tokens[0] = 0;
+  shell_num_tokens = 0;
 }
 
-// Clears current command and replaces it with cmd
-void SHELL_setCommand(char* cmd)
+/**
+ * Append character to command buffer
+*/
+void shell_append_command(char c)
 {
-    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);
+  if (shell_cmd_idx < SHELL_CMD_MAX_LENGTH)
+  {
+    shell_cmd[shell_cmd_idx] = c;
+    shell_cmd_idx++;
+    shell_cmd[shell_cmd_idx] = 0; // Terminate
+  }
 }
 
-// Prints current display prompt
-void SHELL_print_prompt()
+/**
+ * Parse the command buffer into tokens
+*/
+void shell_parse_command()
 {
-    // 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;
+  // Strip trailing spaces
+  word i = shell_cmd_idx - 1;
+  while (i >= 0 && shell_cmd[i] == ' ')
+  {
+    shell_cmd[i] = 0;
+    i--;
+  }
+
+  shell_num_tokens = 0;
+  shell_tokens[shell_num_tokens] = strtok(shell_cmd, " ");
+	while((word)shell_tokens[shell_num_tokens] != -1)
+  {
+    shell_num_tokens++;
+    shell_tokens[shell_num_tokens] = strtok(-1, " ");
+  }
 }
 
-
-// Initialize shell
-void SHELL_init()
+/**
+ * Print help
+*/
+void shell_print_help()
 {
-    // clear current command
-    SHELL_clearCommand();
-
-    // clear screen
-    GFX_clearWindowtileTable();
-    GFX_clearWindowpaletteTable();
-    GFX_cursor = 0;
-
-    SHELL_print_prompt();
+  GFX_PrintConsole("Available commands:\n");
+  GFX_PrintConsole("cd\n");
+  GFX_PrintConsole("clear\n");
+  GFX_PrintConsole("help\n");
 }
 
-
-// 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)
+/**
+ * Attempt to run program from FS
+ * If run_from_path is 1, the program is run from the SHELL_PATH
+ * Returns 1 if program was found, 0 otherwise
+*/
+word shell_run_program(word run_from_path)
 {
-    word similar = 1;
-
-    word i = 0;
-    while (cmd[i] != 0)
-    {
-        if (cmd[i] != p[i])
-        {
-            similar = 0;
-        }
-        i += 1;
+  // Create absolute path to program
+  char absolute_path[MAX_PATH_LENGTH];
+  // Check if program is relative or absolute
+  if (shell_tokens[0][0] == '/')
+  {
+    strcpy(absolute_path, shell_tokens[0]);
+  }
+  else
+  {
+    if (run_from_path)
+    {
+      strcpy(absolute_path, SHELL_PATH);
+      strcat(absolute_path, shell_tokens[0]);
     }
-
-    if (similar)
+    else
     {
-        similar = 0;
-        if (p[i] == 0 || p[i] == ' ')
-            similar = 1;
-    }
-
-    return similar;
+      // Append to current path
+      strcpy(absolute_path, shell_path);
+      if (strlen(absolute_path) + strlen(shell_tokens[0]) < MAX_PATH_LENGTH)
+      {
+        // If not root, append slash
+        if (strcmp(shell_path, "/") != 0)
+        {
+          strcat(absolute_path, "/");
+        }
+        strcat(absolute_path, shell_tokens[0]);
+      }
+      else
+      {
+        GFX_PrintConsole("Path too long\n");
+        return 0;
+      }
+    }
+  }
+
+  // Get filesize of the program
+  struct brfs_dir_entry* dir = brfs_stat(absolute_path);
+  if ((word)dir == -1)
+  {
+    //GFX_PrintConsole("Program not found\n");
+    return 0;
+  }
+  word filesize = dir->filesize;
+
+  // Attempt to open file
+  word fp = brfs_open_file(absolute_path);
+  if (fp == -1)
+  {
+    GFX_PrintConsole("Could not open file\n");
+    return 0;
+  }
+
+  // Set cursor to start of file
+  brfs_set_cursor(fp, 0);
+
+  // Read program into memory
+  word* program = (word*) RUN_ADDR;
+  if (!brfs_read(fp, program, filesize))
+  {
+    GFX_PrintConsole("Could not read file\n");
+    return 0;
+  }
+
+  // Run program
+  // Indicate that a user program is running
+  bdos_userprogram_running = 1;
+
+  uprintln("Running program...");
+
+  // 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();
+
+  // Close file
+  brfs_close_file(fp);
+
+  return 1;
 }
 
-
-// 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)
+/**
+ * List the contents of a directory (TMP function until separate ls program is implemented)
+ * dir_path: full path of the directory
+*/
+void shell_list_directory(char* dir_path)
 {
-    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;
+  // Find data block address of parent directory path
+  word dir_fat_idx = brfs_get_fat_idx_of_dir(dir_path);
+  if (dir_fat_idx == -1)
+  {
+    GFX_PrintConsole("Directory not found\n");
+    return;
+  }
+
+  struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
+  word* dir_addr = brfs_ram_storage + SUPERBLOCK_SIZE + superblock->total_blocks + (dir_fat_idx * superblock->words_per_block);
+  word dir_entries_max = superblock->words_per_block / sizeof(struct brfs_dir_entry);
+
+  word i;
+  for (i = 0; i < dir_entries_max; i++)
+  {
+    struct brfs_dir_entry* dir_entry = (struct brfs_dir_entry*) (dir_addr + (i * sizeof(struct brfs_dir_entry)));
+    if (dir_entry->filename[0] != 0)
+    {
+      char decompressed_filename[16];
+      strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
+      GFX_PrintConsole(decompressed_filename);
+      GFX_PrintConsole(" FAT: ");
+      GFX_PrintDecConsole((dir_entry->fat_idx));
+      GFX_PrintConsole(" Flg: ");
+      GFX_PrintDecConsole((dir_entry->flags));
+      GFX_PrintConsole(" Size: ");
+      GFX_PrintDecConsole((dir_entry->filesize));
+      GFX_PrintConsole("\n");
+    }
+  }
 }
 
 
-// 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)
+/**
+ * Process dots in path so that ".." goes up one directory and "." is skipped
+*/
+void shell_process_dots(char* path)
 {
-    // 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);
+  // Create a copy of the path
+  char path_copy[MAX_PATH_LENGTH];
+  strcpy(path_copy, path);
+
+  // Split path by '/' and traverse directories
+  word num_tokens = 0;
+  char* tokens[MAX_PATH_LENGTH >> 1];
+  tokens[num_tokens] = strtok(path_copy+1, "/");
+  while((word)tokens[num_tokens] != -1)
+  {
+    num_tokens++;
+    tokens[num_tokens] = strtok(-1, "/");
+  }
+
+  if (num_tokens == 0)
+  {
+    return;
+  }
+
+  strcpy(path, "/");
+
+  // Traverse path
+  word i = 0;
+  while (i < num_tokens)
+  {
+    if (strcmp(tokens[i], ".") == 0)
+    {
+      // Skip
+    }
+    else if (strcmp(tokens[i], "..") == 0)
+    {
+      // Remove all characters after last slash
+      word j = strlen(path) - 1;
+      while (j > 0 && path[j] != '/')
+      {
+        path[j] = 0;
+        j--;
+      }
     }
     else
     {
-        GFX_PrintConsole("E: Invalid path\n");
+      // Copy token
+      if (strlen(path) > 1)
+      {
+        strcat(path, "/");
+      }
+      strcat(path, tokens[i]);
     }
+    i++;
+  }
 }
 
-
-// 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)
+/**
+ * Change directory
+*/
+void shell_change_directory()
 {
-    // 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);
+  if (shell_num_tokens != 2)
+  {
+    GFX_PrintConsole("Usage: cd <path>\n");
+    return;
+  }
+
+  // Remove trailing slashes, skipping the first character
+  word i = strlen(shell_tokens[1]) - 1;
+  while (i >= 1 && shell_tokens[1][i] == '/')
+  {
+    shell_tokens[1][i] = 0;
+    i--;
+  }
+
+  // Check if path is root
+  if (strcmp(shell_tokens[1], "/") == 0)
+  {
+    strcpy(shell_path, "/");
+    return;
+  }
+
+  char absolute_path[MAX_PATH_LENGTH];
+
+  // Create absolute path
+  // Check if argument is relative or absolute
+  if (shell_tokens[1][0] == '/')
+  {
+    strcpy(absolute_path, shell_tokens[1]);
+  }
+  else
+  {
+    // Append to current path
+    strcpy(absolute_path, shell_path);
+    if (strlen(absolute_path) + strlen(shell_tokens[1]) < MAX_PATH_LENGTH)
+    {
+      // If not root, append slash
+      if (strcmp(shell_path, "/") != 0)
+      {
+        strcat(absolute_path, "/");
+      }
+      strcat(absolute_path, shell_tokens[1]);
     }
     else
     {
-        // create full path using arg
-        FS_getFullPath(arg);
+      GFX_PrintConsole("Path too long\n");
     }
+  }
 
-    // if the resulting path is correct (can be file or directory)
-    if (FS_sendFullPath(SHELL_path) == FS_ANSW_USB_INT_SUCCESS)
-    {
+  uprintln(absolute_path);
 
-        // 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_userprogramRunning = 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_userprogramRunning = 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");
-    }
+  // Get dir entry of the path
+  struct brfs_dir_entry* dir = brfs_stat(absolute_path);
 
-    // restore path
-    strcpy(SHELL_path, SHELL_pathBackup);
+  // Check if valid and is a directory
+  if ((word)dir != -1 && dir->flags & (1 << 0))
+  {
+    // Set new path
+    strcpy(shell_path, absolute_path);
+    // Process dots in path
+    shell_process_dots(shell_path);
+  }
+  else
+  {
+    GFX_PrintConsole("Directory not found\n");
+  }
 }
 
-
-// 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)
+/**
+ * Handle the command
+*/
+void shell_handle_command()
 {
-    // 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 (strcmp(shell_tokens[0], "cd") == 0)
+  {
+    shell_change_directory();
+  }
+  else if (strcmp(shell_tokens[0], "clear") == 0)
+  {
+    // clear screen by clearing window tables and resetting the cursor
+    GFX_clearWindowtileTable();
+    GFX_clearWindowpaletteTable();
+    GFX_cursor = 0;
+  }
+  else if (strcmp(shell_tokens[0], "ls") == 0)
+  {
+    if (shell_num_tokens > 1)
     {
-
-        // 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");
+      shell_list_directory(shell_tokens[1]);
     }
     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);
+      shell_list_directory(shell_path);
+    }
+  }
+  else if (strcmp(shell_tokens[0], "help") == 0)
+  {
+    shell_print_help();
+  }
+  else if (strcmp(shell_tokens[0], "sync") == 0)
+  {
+    brfs_write_to_flash();
+  }
+  else if (strcmp(shell_tokens[0], "read") == 0)
+  {
+    brfs_read_from_flash();
+  }
+  else if (strcmp(shell_tokens[0], "format") == 0)
+  {
+    word blocks = BDOS_DEFAULT_BLOCKS;
+    word words_per_block = BDOS_DEFAULT_BLOCK_SIZE;
+    word full_format = 1;
+    brfs_format(blocks, words_per_block, "FPGC5", full_format);
+    brfs_write_to_flash();
+  }
+  // Attempt to run program both from local dir and from SHELL_PATH
+  else if (!shell_run_program(0))
+  {
+    if (!shell_run_program(1))
+    {
+      GFX_PrintConsole("Command not found\n");
+    
+    }
+  } 
 }
 
-
-// Creates directory in current directory
-void SHELL_createDir(char* arg)
+/**
+ * Initialize shell
+*/
+void shell_init()
 {
-    // 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");
+  shell_clear_command();
 
-    // restore path
-    strcpy(SHELL_path, SHELL_pathBackup);
-}
+  // Set path to root
+  strcpy(shell_path, "/");
 
-// 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");
+  shell_print_prompt();
 }
 
 
-// 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)
+/**
+ * Main shell loop
+*/
+void shell_loop()
 {
-    // ./ (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
+  // Read command from HID FIFO
+  if (HID_FifoAvailable())
+  {
+    char c = HID_FifoRead();
+    if (c == '\n')
+    {
+      GFX_PrintcConsole('\n');
+      shell_parse_command();
+      if (shell_num_tokens != 0)
+      {
+        shell_handle_command();
+      }
+      shell_clear_command();
+      shell_print_prompt();
+    }
+    else if (c == 0x8) // backspace
+    {
+      if (shell_cmd_idx > 0)
+      {
+        shell_cmd_idx--;
+        shell_cmd[shell_cmd_idx] = 0;
+        GFX_PrintcConsole(0x8);
+      }
     }
-
-    // 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;
+      // Append character to command buffer and print it
+      shell_append_command(c);
+      GFX_PrintcConsole(c);
     }
-}
-
-
-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);
-            }
-        }
-    }
-}
-
+  }
+}

+ 308 - 0
BCC/BDOS/lib/spiflash.c

@@ -0,0 +1,308 @@
+/*
+* SPI Flash library
+* Contains functions to interact with the Winbond W25Q128 SPI Flash chip
+*/
+
+// Sets SPI0_CS low
+void spiflash_begin_transfer()
+{
+  asm(
+      "; Backup regs\n"
+      "push r1\n"
+      "push r2\n"
+
+      "load32 0xC02729 r2   ; r2 = 0xC02729\n"
+
+      "load 0 r1            ; r1 = 0 (enable)\n"
+      "write 0 r2 r1        ; write to SPI0_CS\n"
+
+      "; Restore regs\n"
+      "pop r2\n"
+      "pop r1\n"
+      );
+}
+
+// Sets SPI0_CS high
+void spiflash_end_transfer()
+{
+  asm(
+      "; Backup regs\n"
+      "push r1\n"
+      "push r2\n"
+
+      "load32 0xC02729 r2   ; r2 = 0xC02729\n"
+
+      "load 1 r1            ; r1 = 1 (disable)\n"
+      "write 0 r2 r1        ; write to SPI0_CS\n"
+
+      "; Restore regs\n"
+      "pop r2\n"
+      "pop r1\n"
+      );
+}
+
+// Write dataByte and return read value
+// Write 0x00 for a read
+// Writes byte over SPI0
+word spiflash_transfer(word dataByte)
+{
+  word retval = 0;
+  asm(
+      "load32 0xC02728 r2   ; r2 = 0xC02728\n"
+      "write 0 r2 r4        ; write r4 over SPI0\n"
+      "read 0 r2 r2         ; read return value\n"
+      "write -4 r14 r2      ; write to stack to return\n"
+      );
+  return retval;
+}
+
+// Initialize manual operation of SPI flash by enabling SPI0 and resetting the chip
+// This is needed to get out of continuous read mode and allow manual commands
+void spiflash_init()
+{
+  // Already set CS high before enabling SPI0
+  spiflash_end_transfer();
+
+  // Enable SPI0 (connects simpleSPI.v and disconnects SPIreader.v)
+  word *p = (word *) 0xC0272A; // Set address (SPI0 enable)
+  *p = 1; // Write value
+  delay(10);
+
+  // Reset to get out of continuous read mode
+  spiflash_begin_transfer();
+  spiflash_transfer(0x66);
+  spiflash_end_transfer();
+  delay(1);
+  spiflash_begin_transfer();
+  spiflash_transfer(0x99);
+  spiflash_end_transfer();
+  delay(1);
+  spiflash_begin_transfer();
+  spiflash_transfer(0x66);
+  spiflash_end_transfer();
+  delay(1);
+  spiflash_begin_transfer();
+  spiflash_transfer(0x99);
+  spiflash_end_transfer();
+  delay(1);
+}
+
+// Reads manufacturer and device IDs and prints them over UART
+// Should print 239 as Winbond manufacturer, and 23 for W25Q128 device ID
+void spiflash_read_chip_ids()
+{
+  spiflash_begin_transfer();
+  spiflash_transfer(0x90);
+  spiflash_transfer(0);
+  spiflash_transfer(0);
+  spiflash_transfer(0);
+  word manufacturer = spiflash_transfer(0);
+  word deviceID = spiflash_transfer(0);
+  spiflash_end_transfer();
+
+  uprint("Manufacturer ID: ");
+  uprintDec(manufacturer);
+  uprint("\n");
+
+  uprint("Device ID: ");
+  uprintDec(deviceID);
+  uprint("\n");
+}
+
+// Enables write operation on the chip
+void spiflash_enable_write()
+{
+  spiflash_begin_transfer();
+  spiflash_transfer(0x06);
+  spiflash_end_transfer();
+}
+
+// Reads len bytes from addr and stores them in output
+// If bytes_to_word is 1, then each 4 bytes are shifted into one word in output with len in words
+void spiflash_read_from_address(word* output, word addr, word len, word bytes_to_word)
+{
+  spiflash_begin_transfer();
+  spiflash_transfer(0x03);
+  spiflash_transfer(addr >> 16);
+  spiflash_transfer(addr >> 8);
+  spiflash_transfer(addr);
+
+  if (bytes_to_word)
+  {
+    len = len << 2;
+
+    word i;
+    for (i = 0; i < len; i++)
+    {
+      if ((i & 3) == 0)
+      {
+        output[i >> 2] = 0;
+      }
+      output[i >> 2] |= spiflash_transfer(0) << (24 - ((i & 3) << 3));
+    }
+  }
+  else
+  {
+    word i;
+    for (i = 0; i < len; i++)
+    {
+      output[i] = spiflash_transfer(0);
+    }
+  }
+
+  spiflash_end_transfer();
+}
+
+// Writes up to 64 words as 256 bytes to the given address
+// Address must be aligned to a page boundary
+void spiflash_write_page_in_words(word* input, word addr, word len)
+{
+  // Check if length exceeds page size
+  if (len > 64)
+  {
+    uprintln("Error: cannot write more than a page!");
+    return;
+  }
+
+  // Check if address is aligned to a page boundary
+  if (addr & 0x3F)
+  {
+    uprintln("Error: address must be aligned to a page boundary!");
+    return;
+  }
+
+  spiflash_enable_write();
+
+  word status = spiflash_read_status_reg(1);
+
+  // Check if write is enabled
+  if ((status & 0x2) == 0)
+  {
+    uprintln("WE disabled!");
+    return;
+  }
+
+  spiflash_begin_transfer();
+  spiflash_transfer(0x02);
+  spiflash_transfer(addr >> 16);
+  spiflash_transfer(addr >> 8);
+  spiflash_transfer(addr);
+
+  word i;
+  for (i = 0; i < len; i++)
+  {
+    spiflash_transfer(input[i] >> 24);
+    spiflash_transfer(input[i] >> 16);
+    spiflash_transfer(input[i] >> 8);
+    spiflash_transfer(input[i]);
+  }
+
+  spiflash_end_transfer();
+
+  // Wait for busy bit to be 0
+  status = 1;
+
+  while((status & 0x1) == 1) 
+  {
+    status = spiflash_read_status_reg(1);
+  }
+}
+
+// Reads status register 1, 2, or 3
+word spiflash_read_status_reg(word reg_idx)
+{
+  if (reg_idx < 1 || reg_idx > 3)
+    return 0;
+
+  spiflash_begin_transfer();
+
+  word command;
+  switch (reg_idx)
+  {
+    case 1:
+      command = 0x05;
+      break;
+    case 2:
+      command = 0x35;
+      break;
+    case 3:
+      command = 0x15;
+      break;
+  }
+  spiflash_transfer(command);
+  word status = spiflash_transfer(0);
+  spiflash_end_transfer();
+
+  return status;
+}
+
+// Executes sector erase operation
+// Erases the 4KiB sector of the given address
+// (e.g. addr 4096 erases the second sector)
+// This is the smallest unit that can be erased
+// Returns 1 on success
+word spiflash_sector_erase(word addr)
+{
+  spiflash_enable_write();
+
+  word status = spiflash_read_status_reg(1);
+
+  // Check if write is enabled
+  if ((status & 0x2) == 0)
+  {
+    uprintln("WE disabled!");
+    return 0;
+  }
+
+  // Send command
+  spiflash_begin_transfer();
+  spiflash_transfer(0x20);
+  spiflash_transfer(addr >> 16);
+  spiflash_transfer(addr >> 8);
+  spiflash_transfer(addr);
+  spiflash_end_transfer();
+
+
+  // Wait for busy bit to be 0
+  status = 1;
+
+  while((status & 0x1) == 1) 
+  {
+    status = spiflash_read_status_reg(1);
+  }
+
+  return 1;
+}
+
+// Executes chip erase operation
+// TAKES AT LEAST 20 SECONDS!
+// Returns 1 on success
+word spiflash_chip_erase()
+{
+  spiflash_enable_write();
+
+  word status = spiflash_read_status_reg(1);
+
+  // Check if write is enabled
+  if ((status & 0x2) == 0)
+  {
+    uprintln("WE disabled!");
+    return 0;
+  }
+
+  // Send erase chip command
+  spiflash_begin_transfer();
+  spiflash_transfer(0xC7);
+  spiflash_end_transfer();
+
+  // Wait for busy bit to be 0
+  status = 1;
+
+  while((status & 0x1) == 1) 
+  {
+    delay(100);
+    status = spiflash_read_status_reg(1);
+  }
+
+  return 1;
+}

+ 232 - 2
BCC/BDOS/lib/stdlib.c

@@ -54,6 +54,18 @@ word memcmp(word* a, word* b, word n)
     return 1;
 }
 
+/*
+Sets n words from dest to val
+*/
+void memset(word* dest, word val, word n)
+{
+  word i;
+  for (i = 0; i < n; i++)
+  {
+    dest[i] = val;
+  }
+}
+
 
 // Returns length of string
 word strlen(char* str)
@@ -108,6 +120,27 @@ word strcat(char* dest, char* src)
     return strcpy(dest+endOfDest, src);
 }
 
+/*
+Appends string from src to dest up to n characters
+Returns pointer to resulting dest string
+*/
+char *strncat(char *dest, char *src, word n)
+{
+  word len1 = strlen(dest);
+  word len2 = strlen(src);
+    
+  if (len2 < n) 
+  {
+	  strcpy(&dest[len1], src);
+  }
+  else
+  {
+    strncpy(&dest[len1], src, n);
+    dest[len1 + n] = '\0';
+  }
+  return dest;
+}
+
 
 /*
 Compares two strings a and b
@@ -125,6 +158,179 @@ word strcmp(char* s1, char* s2)
 }
 
 
+/*
+Returns a pointer to the first occurrence of the character c in the string s, or 0 if the character is not found.
+*/
+char* strchr (const char *s, char c)
+{
+  do {
+    if (*s == c)
+      {
+        return (char*)s;
+      }
+  } while (*s++);
+  return 0;
+}
+
+
+/*
+Returns a pointer to the last occurance of a character, or 0 if the character is not found.
+*/
+char* strrchr (const char *s, int c)
+{
+  char *rtnval = 0;
+
+  do {
+    if (*s == c)
+      rtnval = (char*) s;
+  } while (*s++);
+  return (rtnval);
+}
+
+
+char * strtok_old_str;
+/* 
+Parse str into tokens separated by characters in delim.
+If S is NULL, the last string strtok() was called with is used.
+Note that strtok() modifies the input string.
+For example:
+	char s[] = "-abc-=-def";
+	x = strtok(s, "-");		// x = "abc"
+	x = strtok(NULL, "-=");		// x = "def"
+	x = strtok(NULL, "=");		// x = NULL
+		// s = "abc\0=-def\0"
+*/
+char* strtok(char* str, const char* delim)
+{
+  if (str != (word*)-1)
+    strtok_old_str = str;
+
+  if (strtok_old_str == (word*)-1)
+    return (word*)-1;
+
+  // Return reached end of string
+  if (*strtok_old_str == 0)
+  {
+    return (word*)-1;
+  }
+  // Skip leading delimiters
+  while (strchr(delim, *strtok_old_str) != 0)
+    strtok_old_str++;
+
+  // Find end of token
+  char* start = strtok_old_str;
+  while (*strtok_old_str != 0 && strchr(delim, *strtok_old_str) == 0)
+    strtok_old_str++;
+
+  if (*strtok_old_str == 0)
+  {
+    strtok_old_str = (word*)-1;
+    return start;
+  }
+
+  *strtok_old_str = 0;
+  strtok_old_str++;
+  return start;
+}
+
+
+/*
+Compress a string made of one char per word, into a string made of one char per byte.
+*/
+void strcompress(word* dest, char* src)
+{
+  word i_src = 0;
+  word i_dst = 0;
+  word byte_offset = 0;
+  word c = src[i_src];
+
+  while (c != 0)
+  {
+    dest[i_dst] |= (c << byte_offset);
+
+    if (byte_offset == 24)
+    {
+      byte_offset = 0;
+      i_dst++;
+      dest[i_dst] = 0;
+    }
+    else
+    {
+      byte_offset += 8;
+    }
+
+    i_src++;
+    c = src[i_src];
+  }
+}
+
+/*
+Decompress a string made of one char per byte, into a string made of one char per word.
+*/
+void strdecompress(char* dest, word* src)
+{
+  word i_src = 0;
+  word i_dst = 0;
+  word byte_offset = 0;
+
+  while (1)
+  {
+    word c = (src[i_src] >> byte_offset) & 0xFF;
+    if (c == 0)
+      break;
+
+    dest[i_dst++] = c;
+
+    if (byte_offset == 24)
+    {
+      byte_offset = 0;
+      i_src++;
+    }
+    else
+    {
+      byte_offset += 8;
+    }
+  }
+
+  // Terminate
+  dest[i_dst] = 0;
+}
+
+/**
+ * Return the basename of a path
+ * path: full path
+*/
+char* basename(char *path)
+{
+  char *base = strrchr(path, '/');
+  return base ? base + 1 : path;
+}
+
+/**
+ * Return the dirname of a path
+ * output: buffer to store the dirname
+ * path: full path
+*/
+char* dirname(char* output, char *path)
+{
+  strcpy(output, path);
+  char *last_slash = strrchr(output, '/');
+  if (last_slash != 0)
+  {
+    *last_slash = 0;
+    // If the last slash is the first character, return "/"
+    if (last_slash == output)
+    {
+      strcpy(output, "/");
+    }
+  } else
+  {
+    // No slash found, return "."
+    strcpy(output, ".");
+  }
+  return output;
+}
+
 /*
 Recursive helper function for itoa
 Eventually returns the number of digits in n
@@ -215,6 +421,32 @@ void itoah(word n, char *s)
 } 
 
 
+// isalpha
+word isalpha(char c)
+{
+  if (c >= 'A' && c <= 'Z')
+    return 2;
+  if (c >= 'a' && c <= 'z')
+    return 1;
+  return 0;
+}
+
+// isdigit
+word isdigit(char c)
+{
+  if (c >= '0' && c <= '9')
+    return 1;
+  return 0;
+}
+
+// isalnum
+word isalnum(char c)
+{
+  if (isdigit(c) || isalpha(c))
+    return 1;
+  return 0;
+}
+
 /*
 Converts string into int.
 Assumes the string is valid.
@@ -311,7 +543,6 @@ void uprintDec(word i)
     char buffer[20];
     itoa(i, buffer);
     uprint(buffer);
-    uprintc('\n');
 }
 
 /*
@@ -322,7 +553,6 @@ void uprintHex(word i)
     char buffer[16];
     itoah(i, buffer);
     uprint(buffer);
-    uprintc('\n');
 }
 
 

+ 0 - 3
BCC/FPGCbuildTools/bcc/backend.c

@@ -87,9 +87,6 @@ void GenInitFinalize(void)
       
       "Return_BDOS:\n"
       
-      "    load32 0x100300 r1\n"
-      "    write 0 r1 r0\n"
-      
       "    pop r1\n"
       "    jumpr 3 r1\n"
       "    halt\n"

+ 0 - 2
BCC/backend.c

@@ -98,8 +98,6 @@ void GenInitFinalize(void)
       "; Its adrress should be on top of the hardware stack\n"
       "Return_BDOS:\n"
       "    ; notify program has ended\n"
-      "    load32 0x100300 r1\n"
-      "    write 0 r1 r0\n"
       "\n"
       "    pop r1\n"
       "    jumpr 3 r1\n"

+ 7 - 7
BCC/userBDOS/BRFS.C

@@ -229,7 +229,7 @@ word brfs_get_fat_idx_of_dir(char* dir_path)
   word current_dir_fat_idx = 0;
 
   // Check if root directory is requested
-  if (strcmp(dir_path, "/") == 1)
+  if (strcmp(dir_path, "/") == 0)
   {
     return current_dir_fat_idx;
   }
@@ -259,7 +259,7 @@ word brfs_get_fat_idx_of_dir(char* dir_path)
         char decompressed_filename[16];
         strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
         // Also check for directory flag
-        if (strcmp(decompressed_filename, token) == 1 && dir_entry->flags == 1)
+        if (strcmp(decompressed_filename, token) == 0 && dir_entry->flags == 1)
         {
           // Found token in current directory
           // Set current directory to token's FAT index
@@ -487,7 +487,7 @@ word brfs_create_directory(char* parent_dir_path, char* dirname)
     {
       char decompressed_filename[16];
       strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
-      if (strcmp(decompressed_filename, dirname) == 1)
+      if (strcmp(decompressed_filename, dirname) == 0)
       {
         uprint(dirname);
         uprintln(" already exists!");
@@ -573,7 +573,7 @@ word brfs_create_file(char* parent_dir_path, char* filename)
     {
       char decompressed_filename[16];
       strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
-      if (strcmp(decompressed_filename, filename) == 1)
+      if (strcmp(decompressed_filename, filename) == 0)
       {
         uprint(filename);
         uprintln(" already exists!");
@@ -703,7 +703,7 @@ word brfs_open_file(char* file_path)
       char decompressed_filename[16];
       strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
       // Also check for directory flag to be 0
-      if (strcmp(decompressed_filename, file_path_basename) == 1 && dir_entry->flags == 0)
+      if (strcmp(decompressed_filename, file_path_basename) == 0 && dir_entry->flags == 0)
       {
         // Found file
         // Check if file is already open
@@ -811,7 +811,7 @@ word brfs_delete_file(char* file_path)
       char decompressed_filename[16];
       strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
       // Also check for directory flag to be 0
-      if (strcmp(decompressed_filename, file_path_basename) == 1 && dir_entry->flags == 0)
+      if (strcmp(decompressed_filename, file_path_basename) == 0 && dir_entry->flags == 0)
       {
         // Found file
         // Check if file is already open
@@ -1165,7 +1165,7 @@ struct brfs_dir_entry* brfs_stat(char* file_path)
       char decompressed_filename[16];
       strdecompress(decompressed_filename, (char*)&(dir_entry->filename));
       // Also check for directory flag to be 0
-      if (strcmp(decompressed_filename, file_path_basename) == 1)
+      if (strcmp(decompressed_filename, file_path_basename) == 0)
       {
         return dir_entry;
       }

+ 27 - 15
BCC/userBDOS/LIB/STDLIB.C

@@ -154,28 +154,40 @@ word strcat(char* dest, char* src)
   return strcpy(dest+endOfDest, src);
 }
 
+/*
+Appends string from src to dest up to n characters
+Returns pointer to resulting dest string
+*/
+char *strncat(char *dest, char *src, word n)
+{
+  word len1 = strlen(dest);
+  word len2 = strlen(src);
+    
+  if (len2 < n) 
+  {
+	  strcpy(&dest[len1], src);
+  }
+  else
+  {
+    strncpy(&dest[len1], src, n);
+    dest[len1 + n] = '\0';
+  }
+  return dest;
+}
 
 /*
 Compares two strings a and b
-Returns 1 if similar, 0 otherwise
+Returns 0 if similar
+ otherwise returns the difference in the first non-matching character
 */
-word strcmp(char* a, char* b)
+word strcmp(char* s1, char* s2)
 {
-  if (strlen(a) != strlen(b))
-    return 0;
-
-
-  word i = 0;
-  while (a[i] != 0)
+  while(*s1 && (*s1 == *s2))
   {
-    if (a[i] != b[i])
-    {
-      return 0;
-    }
-    i++;
+    s1++;
+    s2++;
   }
-
-  return 1;
+  return *s1 - *s2;
 }
 
 

+ 3 - 1
Documentation/docs/Software/BDOS/BRFS.md

@@ -33,7 +33,7 @@ Given that SPI NOR Flash has very fast reads and very slow writes, and that the
 ```
 
 ``` text
-8 word Dir entries:
+8 word dir entries:
   - (4) filename.ext [4 chars per word -> 16 chars total]
   - (1) modify date [to be implemented when RTC]
   - (1) flags [max 32 flags, lsb = is_directory]
@@ -41,6 +41,8 @@ Given that SPI NOR Flash has very fast reads and very slow writes, and that the
   - (1) file size [in words, not bytes]
 ```
 
+Similar to a linked list, each data block has its own FAT entry (of one word) in the FAT table which points to the next data block of the file, or -1 if it is the last block of the file (or if it is a dir). Each dir entry contains the first FAT index of the file.
+
 ## Implementation Notes:
 
 - Current directory is managed by the application/OS, not the FS. Directory (or file) can be checked to exist using stat()

+ 2 - 0
Programmer/netUpload.py

@@ -21,6 +21,8 @@ if len(sys.argv) >= 3:
 
 with open(filename, "rb") as f:
     binfile = f.read()
+    # Add three bytes of 0 at the end
+    binfile += b'\x00\x00\x00'
 
 downloadToFile = True