Browse Source

Create SPI flash library to interact with SPI flash, Added write updated sector functionality to BRFS, Some small fixes in other code.

bart 7 months ago
parent
commit
9cb7744c82

+ 5 - 5
BCC/BareMetal/flashProgrammer.c

@@ -60,8 +60,8 @@ void initVram()
 void SpiFlash_asmDefines()
 {
     asm(
-        "define W5500_SPI0_CS_ADDR = 0xC02729 ; address of SPI0_CS\n"
-        "define W5500_SPI0_ADDR = 0xC02728    ; address of SPI0\n"
+        "define SPI0_CS_ADDR = 0xC02729 ; address of SPI0_CS\n"
+        "define SPI0_ADDR = 0xC02728    ; address of SPI0\n"
         );
 }
 
@@ -73,7 +73,7 @@ void SpiBeginTransfer()
         "push r1\n"
         "push r2\n"
 
-        "load32 W5500_SPI0_CS_ADDR r2       ; r2 = W5500_SPI0_CS_ADDR\n"
+        "load32 SPI0_CS_ADDR r2       ; r2 = SPI0_CS_ADDR\n"
 
         "load 0 r1                          ; r1 = 0 (enable)\n"
         "write 0 r2 r1                      ; write to SPI0_CS\n"
@@ -92,7 +92,7 @@ void SpiEndTransfer()
         "push r1\n"
         "push r2\n"
 
-        "load32 W5500_SPI0_CS_ADDR r2       ; r2 = W5500_SPI0_CS_ADDR\n"
+        "load32 SPI0_CS_ADDR r2       ; r2 = SPI0_CS_ADDR\n"
 
         "load 1 r1                          ; r1 = 1 (disable)\n"
         "write 0 r2 r1                      ; write to SPI0_CS\n"
@@ -110,7 +110,7 @@ word SpiTransfer(word dataByte)
 {
     word retval = 0;
     asm(
-        "load32 W5500_SPI0_ADDR r2          ; r2 = W5500_SPI0_ADDR\n"
+        "load32 SPI0_ADDR r2          ; r2 = SPI0_ADDR\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"

+ 340 - 45
BCC/userBDOS/BRFS.C

@@ -63,26 +63,56 @@ Required operations:
 - [x] List directory
 */
 
+/*
+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)
+*/
+
+/*
+Integrate with SPI Flash:
+- [] Function to read from SPI Flash (to be used on startup of BDOS)
+  - [] Check for valid BRFS superblock
+  - [] Load BRFS into RAM (read total blocks and words per block from superblock)
+- [x] Function to write BRFS to SPI Flash (to be used by applications/OS via System Call)
+  - [x] Check which blocks have changed using a bitmap
+  - [x] Erase changed blocks (by sector) in SPI Flash
+  - [x] Write changed blocks to SPI Flash (by page)
+*/
+
 #define word char
 
 #include "LIB/MATH.C"
 #include "LIB/SYS.C"
 #include "LIB/STDLIB.C"
+#include "LIB/SPIFLASH.C"
+
+#define BRFS_RAM_STORAGE_ADDR 0x100000 // From 4th MiB
 
-#define BRFS_RAM_STORAGE_ADDR 0x600000
+// 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
-#define MAX_OPEN_FILES 4 // Can be set higher, but 4 is good for testing
+#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 bytes_per_block;
+  word words_per_block;
   word label[10];       // 1 char per word
   word brfs_version;
   word reserved[3];
@@ -209,14 +239,14 @@ word brfs_get_fat_idx_of_dir(char* 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->bytes_per_block / sizeof(struct brfs_dir_entry);
+  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->bytes_per_block);
+    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++)
@@ -348,16 +378,20 @@ void brfs_init_directory(word* dir_addr, word dir_entries_max, word dir_fat_idx,
 
   // 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
- * bytes_per_block: number of bytes per block
+ * 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 bytes_per_block, char* label, word full_format)
+void brfs_format(word blocks, word words_per_block, char* label, word full_format)
 {
   // Create a superblock
   struct brfs_superblock superblock;
@@ -367,7 +401,7 @@ void brfs_format(word blocks, word bytes_per_block, char* label, word full_forma
 
   // Set values of superblock
   superblock.total_blocks = blocks;
-  superblock.bytes_per_block = bytes_per_block;
+  superblock.words_per_block = words_per_block;
   strcpy((char*)&superblock.label, label);
   superblock.brfs_version = 1;
 
@@ -381,11 +415,11 @@ void brfs_format(word blocks, word bytes_per_block, char* label, word full_forma
   // Create Data section
   if (full_format)
   {
-    memset(brfs_ram_storage + SUPERBLOCK_SIZE + blocks, 0, blocks * bytes_per_block);
+    memset(brfs_ram_storage + SUPERBLOCK_SIZE + blocks, 0, blocks * words_per_block);
   }
   
   // Initialize root dir
-  word dir_entries_max = bytes_per_block / sizeof(struct brfs_dir_entry);
+  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
@@ -397,6 +431,17 @@ void brfs_format(word blocks, word bytes_per_block, char* label, word full_forma
   {
     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));
 }
 
 /**
@@ -430,8 +475,8 @@ word brfs_create_directory(char* parent_dir_path, char* dirname)
   }
 
   // Check if file or folder already exists
-  word* parent_dir_addr = brfs_data_block_addr + (parent_dir_fat_idx * superblock->bytes_per_block);
-  word dir_entries_max = superblock->bytes_per_block / sizeof(struct brfs_dir_entry);
+  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++)
   {
@@ -451,8 +496,8 @@ word brfs_create_directory(char* parent_dir_path, char* dirname)
 
   // 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->bytes_per_block), 
-    superblock->bytes_per_block / sizeof(struct brfs_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)
   {
@@ -466,19 +511,22 @@ word brfs_create_directory(char* parent_dir_path, char* dirname)
 
   // Copy dir entry to first free dir entry
   memcpy(
-    brfs_data_block_addr + (parent_dir_fat_idx * superblock->bytes_per_block) + (next_free_dir_entry * sizeof(struct brfs_dir_entry)),
+    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->bytes_per_block),
+    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;
 }
 
@@ -513,8 +561,8 @@ word brfs_create_file(char* parent_dir_path, char* filename)
   }
 
   // Check if file or folder already exists
-  word* parent_dir_addr = brfs_data_block_addr + (parent_dir_fat_idx * superblock->bytes_per_block);
-  word dir_entries_max = superblock->bytes_per_block / sizeof(struct brfs_dir_entry);
+  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++)
   {
@@ -534,8 +582,8 @@ word brfs_create_file(char* parent_dir_path, char* filename)
 
   // 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->bytes_per_block), 
-    superblock->bytes_per_block / sizeof(struct brfs_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)
   {
@@ -549,21 +597,24 @@ word brfs_create_file(char* parent_dir_path, char* filename)
 
   // Copy dir entry to first free dir entry
   memcpy(
-    brfs_data_block_addr + (parent_dir_fat_idx * superblock->bytes_per_block) + (next_free_dir_entry * sizeof(struct brfs_dir_entry)),
+    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->bytes_per_block),
+    brfs_data_block_addr + (next_free_block * superblock->words_per_block),
     0,
-    superblock->bytes_per_block
+    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;
 }
 
@@ -588,8 +639,8 @@ void brfs_list_directory(char* dir_path)
   }
 
   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->bytes_per_block);
-  word dir_entries_max = superblock->bytes_per_block / sizeof(struct brfs_dir_entry);
+  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++)
@@ -638,8 +689,8 @@ word brfs_open_file(char* file_path)
 
   // 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->bytes_per_block);
-  word dir_entries_max = superblock->bytes_per_block / sizeof(struct brfs_dir_entry);
+  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++)
@@ -746,8 +797,8 @@ word brfs_delete_file(char* file_path)
 
   // 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->bytes_per_block);
-  word dir_entries_max = superblock->bytes_per_block / sizeof(struct brfs_dir_entry);
+  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++)
@@ -781,11 +832,15 @@ word brfs_delete_file(char* file_path)
         {
           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;
       }
     }
@@ -875,7 +930,7 @@ word brfs_get_fat_idx_at_cursor(word file_pointer, word cursor)
   struct brfs_superblock* superblock = (struct brfs_superblock*) brfs_ram_storage;
 
   // Loop through FAT until cursor is reached
-  while (cursor > superblock->bytes_per_block)
+  while (cursor > superblock->words_per_block)
   {
     current_fat_idx = brfs_ram_storage[SUPERBLOCK_SIZE + current_fat_idx];
     if (current_fat_idx == -1)
@@ -883,7 +938,7 @@ word brfs_get_fat_idx_at_cursor(word file_pointer, word cursor)
       uprintln("Cursor is out of bounds!");
       return 0;
     }
-    cursor -= superblock->bytes_per_block;
+    cursor -= superblock->words_per_block;
   }
 
   return current_fat_idx;
@@ -940,11 +995,11 @@ word brfs_read(word file_pointer, word* buffer, word length)
       // - repeat until length is 0
       while (length > 0)
       {
-        word words_until_end_of_block = superblock->bytes_per_block - (MATH_modU(brfs_cursors[i], superblock->bytes_per_block));
+        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->bytes_per_block) + brfs_cursors[i], words_to_read);
+        memcpy(buffer, data_block_addr + (current_fat_idx * superblock->words_per_block) + brfs_cursors[i], words_to_read);
 
         // Update cursor and length
         brfs_cursors[i] += words_to_read;
@@ -1014,12 +1069,15 @@ word brfs_write(word file_pointer, word* buffer, word length)
       // - repeat until length is 0
       while (length > 0)
       {
-        word cursor_in_block = MATH_modU(brfs_cursors[i], superblock->bytes_per_block);
-        word words_until_end_of_block = superblock->bytes_per_block - cursor_in_block;
+        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->bytes_per_block) + cursor_in_block, buffer, words_to_write);
+        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;
@@ -1051,6 +1109,8 @@ word brfs_write(word file_pointer, word* buffer, word length)
             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));
           }
         }
       }
@@ -1091,8 +1151,8 @@ struct brfs_dir_entry* brfs_stat(char* file_path)
 
   // 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->bytes_per_block);
-  word dir_entries_max = superblock->bytes_per_block / sizeof(struct brfs_dir_entry);
+  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++)
@@ -1115,6 +1175,225 @@ struct brfs_dir_entry* brfs_stat(char* file_path)
   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 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("");
+
+        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("");
+  }
+
+  // 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 + j;
+
+        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---");
+}
+
+
+void read_test()
+{
+  word read_buffer[1024];
+  memset(read_buffer, 0, 1024);
+  spiflash_read_from_address(&read_buffer[0], 0, 1024, 1);
+
+  word i;
+  for(i = 0; i < 1024; i+=128)
+  {
+    uprintHex(read_buffer[i]);
+    uprintc(' ');
+  }
+  uprintln("");
+}
+
 
 int main() 
 {
@@ -1127,14 +1406,28 @@ int main()
   uprintln("BRFS test implementation");
   uprintln("------------------------");
 
-  word blocks = 16;
-  word bytes_per_block = 32;
+  spiflash_init();
+  
+  // Small scale test values
+  //word blocks = 16;
+  //word words_per_block = 64; // 256 bytes per block
+  //word full_format = 1;
+
+  // Large scale test values for 4MiB
+  word blocks = 16; //4096; // 1KiB per block * 4096 = 4MiB
+  word words_per_block = 256; // 1KiB per block
   word full_format = 1;
 
-  brfs_format(blocks, bytes_per_block, "Label", full_format);
-  //brfs_list_directory("/");
-  //brfs_dump(blocks, blocks*bytes_per_block);
-  //uprintln("");
+  uprintln("Formatting BRFS filesystem...");
+  brfs_format(blocks, words_per_block, "SystemBRFS", full_format);
+  uprintln("BRFS filesystem formatted!");
+
+  brfs_dump(blocks, blocks*words_per_block);
+  brfs_write_fat_to_flash();
+  brfs_write_blocks_to_flash();
+  
+
+  /* TEST CODE FOR SMALL SCALE TEST
 
   // Create directories
   if (!brfs_create_directory("/", "dir1"))
@@ -1215,7 +1508,9 @@ int main()
   
   brfs_list_directory("/");
   brfs_list_directory("/dir1");
-  brfs_dump(blocks, blocks*bytes_per_block);
+  brfs_dump(blocks, blocks*words_per_block);
+
+  */
 
   return 'q';
 }

+ 308 - 0
BCC/userBDOS/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
+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;
+}

+ 3 - 3
BCC/userBDOS/LIB/STDLIB.C

@@ -699,14 +699,14 @@ For debugging
 Prints a hex dump of size 'len' for each word starting from 'addr'
 Values are printed over UART
 */
-void hexdump(char* addr, word len)
+void hexdump(char* addr, word len, word words_per_line)
 {
   char buf[16];
   word i;
   for (i = 0; i < len; i++)
   {
-    // newline every 8 words
-    if (i != 0 && MATH_modU(i, 8) == 0)
+    // newline every words_per_line words
+    if (i != 0 && MATH_modU(i, words_per_line) == 0)
       uprintc('\n');
     itoah(addr[i], buf);
     uprint(buf);

+ 7 - 7
BCC/userBDOS/MBROT.C

@@ -13,9 +13,9 @@
 // Framebuffer. fb[Y][X] (bottom right is [239][319])
 char (*fb)[SCREEN_WIDTH] = (char (*)[SCREEN_WIDTH]) FB_ADDR;
 
-char MBROT_r[] = {7,7,7,7,7,7,7,7,6,5,4,3,2,1,0,0,0,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,0};
-char MBROT_g[] = {0,1,2,3,4,5,6,7,7,7,7,7,7,7,7,7,7,7,6,5,4,3,2,1,0,0,0,0,0,0,0,0,0};
-char MBROT_b[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,0};
+char MBROT_r[] = {224, 224, 224, 224, 224, 224, 224, 224, 192, 160, 128, 96, 64, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 64, 96, 128, 160, 192, 224, 0};
+char MBROT_g[] = {0, 32, 64, 96, 128, 160, 192, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 224, 192, 160, 128, 96, 64, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+char MBROT_b[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 64, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 96, 0};
 
 
 word MBROT_xmin = -8601;
@@ -128,16 +128,16 @@ void MBROT_render(word dx, word dy)
       "addr2reg MBROT_r r15\n"
       "add r15 r10 r15             ; r15 = MBROT_r[iter]\n"
       "read 0 r15 r15\n"
-      "shiftl r15 5 r14            ; r14 = r << 5\n"
+      "shiftl r15 16 r14           ; r14 = r << 16\n"
 
       "addr2reg MBROT_g r15\n"
-      "add r15 r10 r15             ; r15 = MBROT_r[iter]\n"
+      "add r15 r10 r15             ; r15 = MBROT_g[iter]\n"
       "read 0 r15 r15\n"
-      "shiftl r15 2 r15            ; r15 = g << 2\n"
+      "shiftl r15 8 r15            ; r15 = g << 8\n"
       "add r15 r14 r14             ; r14 += g (shifted)\n"
 
       "addr2reg MBROT_b r15\n"
-      "add r15 r10 r15             ; r15 = MBROT_r[iter]\n"
+      "add r15 r10 r15             ; r15 = MBROT_b[iter]\n"
       "read 0 r15 r15\n"
       "add r15 r14 r14             ; r14 += b\n"
 

+ 266 - 0
BCC/userBDOS/SPIFLASH.C

@@ -0,0 +1,266 @@
+/*
+Test program for SPI flash memory
+*/
+
+#define word char
+
+#include "LIB/MATH.C"
+#include "LIB/STDLIB.C"
+#include "LIB/SYS.C"
+
+// Sets SPI0_CS low
+void SpiBeginTransfer()
+{
+  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 SpiEndTransfer()
+{
+  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 SpiTransfer(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;
+}
+
+// Inits SPI by enabling SPI0 and resetting the chip
+void initSPI()
+{
+    // Already set CS high before enabling SPI0
+    SpiEndTransfer();
+
+    // Enable SPI0
+    word *p = (word *) 0xC0272A; // Set address (SPI0 enable)
+    *p = 1; // Write value
+    delay(10);
+
+    // Reset to get out of continuous read mode
+    SpiBeginTransfer();
+    SpiTransfer(0x66);
+    SpiEndTransfer();
+    delay(1);
+    SpiBeginTransfer();
+    SpiTransfer(0x99);
+    SpiEndTransfer();
+    delay(1);
+    SpiBeginTransfer();
+    SpiTransfer(0x66);
+    SpiEndTransfer();
+    delay(1);
+    SpiBeginTransfer();
+    SpiTransfer(0x99);
+    SpiEndTransfer();
+    delay(1);
+}
+
+// Should print 239 as Winbond manufacturer, and 23 for W25Q128 device ID
+void readDeviceID()
+{
+    SpiBeginTransfer();
+    SpiTransfer(0x90);
+    SpiTransfer(0);
+    SpiTransfer(0);
+    SpiTransfer(0);
+    word manufacturer = SpiTransfer(0);
+    word deviceID = SpiTransfer(0);
+    SpiEndTransfer();
+
+    BDOS_PrintConsole("Manufacturer ID: ");
+    BDOS_PrintDecConsole(manufacturer);
+    BDOS_PrintConsole("\n");
+
+    BDOS_PrintConsole("Device ID: ");
+    BDOS_PrintDecConsole(deviceID);
+    BDOS_PrintConsole("\n");
+}
+
+void enableWrite()
+{
+    SpiBeginTransfer();
+    SpiTransfer(0x06);
+    SpiEndTransfer();
+}
+
+
+void read_from_address(word addr, word len)
+{
+    SpiBeginTransfer();
+    SpiTransfer(0x03);
+    SpiTransfer(addr >> 16);
+    SpiTransfer(addr >> 8);
+    SpiTransfer(addr);
+
+
+    word i;
+    for (i = 0; i < len; i++)
+    {
+        word x = SpiTransfer(0x00);
+        // delay a bit here
+        uprintDec(x);
+        uprint(" ");
+    }
+    uprint("\n");
+
+    SpiEndTransfer();
+}
+
+word readStatusRegister(word reg)
+{
+    SpiBeginTransfer();
+
+    if (reg == 2)
+        SpiTransfer(0x35);
+    else if (reg == 3)
+        SpiTransfer(0x15);
+    else
+        SpiTransfer(0x05);
+
+    word status = SpiTransfer(0);
+    SpiEndTransfer();
+
+    return status;
+}
+
+// Executes chip erase operation
+// TAKES AT LEAST 20 SECONDS!
+// Returns 1 on success
+word chipErase()
+{
+    enableWrite();
+
+    word status = readStatusRegister(1);
+
+    // Check if write is enabled
+    if ((status & 0x2) == 0)
+    {
+      BDOS_PrintlnConsole("WE disabled!");
+        return 0;
+    }
+
+    // Send command
+    SpiBeginTransfer();
+    SpiTransfer(0xC7);
+    SpiEndTransfer();
+
+
+    // Wait for busy bit to be 0
+    status = 1;
+
+    while((status & 0x1) == 1) 
+    {
+      delay(100);
+        status = readStatusRegister(1);
+    }
+
+    BDOS_PrintlnConsole("Chip erase complete!");
+
+    return 1;
+}
+
+void dump_memory()
+{
+  word page_max = 9000;//65536;
+  word page;
+  for (page = 0; page < page_max; page++)
+  {
+    uprintDec(page);
+    uprint(": ");
+    word addr = page * 256;
+    SpiBeginTransfer();
+    SpiTransfer(0x03);
+    SpiTransfer(addr >> 16);
+    SpiTransfer(addr >> 8);
+    SpiTransfer(addr);
+
+    word i;
+    for (i = 0; i < 16; i++)
+    {
+        word x = SpiTransfer(0x00);
+        
+        uprintDec(x);
+        uprint(" ");
+    }
+    uprint("\n");
+
+    SpiEndTransfer();
+  }
+
+}
+
+int main() 
+{
+  // Clear UART screen:
+  uprintc(0x1B);
+  uprintc(0x5B);
+  uprintc(0x32);
+  uprintc(0x4A);
+
+  BDOS_PrintConsole("SPI Flash test\n");
+
+  initSPI();
+  readDeviceID();
+
+  //chipErase();
+
+  word addr = 2000000;
+
+  read_from_address(addr, 512);
+
+  //dump_memory();
+
+  return 'q';
+}
+
+void interrupt()
+{
+  // Handle all interrupts
+  word i = getIntID();
+  switch(i)
+  {
+    case INTID_TIMER1:
+      timer1Value = 1; // Notify ending of timer1
+      break;
+
+    default:
+      break;
+  }
+}