altos: Add FAT32 support. And lots more testing.
authorKeith Packard <keithp@keithp.com>
Fri, 29 Mar 2013 07:32:23 +0000 (00:32 -0700)
committerKeith Packard <keithp@keithp.com>
Fri, 29 Mar 2013 07:32:23 +0000 (00:32 -0700)
Generalizes the FAT code to deal with either 16-bit or 32-bit
versions. The testing code now runs over a variety of disk images to
check for compatibility on all of them.

Signed-off-by: Keith Packard <keithp@keithp.com>
src/drivers/ao_bufio.c
src/drivers/ao_bufio.h
src/drivers/ao_fat.c
src/drivers/ao_fat.h
src/test/ao_fat_test.c

index 10b32ceb41a9fba365826e31cadadc0d11721cb6..87de457f96f88539caff1efdc9ef8f00775ed090 100644 (file)
@@ -22,7 +22,7 @@
 #include "ao_sdcard.h"
 #include "ao_bufio.h"
 
-#define AO_NUM_BUF             4
+#define AO_NUM_BUF             16
 #define AO_BUFSIZ              512
 
 struct ao_bufio {
@@ -292,13 +292,21 @@ static const struct ao_cmds ao_bufio_cmds[] = {
 };
 
 void
-ao_bufio_init(void)
+ao_bufio_setup(void)
 {
        int b;
 
-       for (b = 0; b < AO_NUM_BUF; b++)
+       for (b = 0; b < AO_NUM_BUF; b++) {
+               ao_bufio[b].dirty = 0;
+               ao_bufio[b].busy = 0;
                ao_bufio[b].block = 0xffffffff;
-       ao_sdcard_init();
+       }
+}
 
+void
+ao_bufio_init(void)
+{
+       ao_bufio_setup();
+       ao_sdcard_init();
        ao_cmd_register(&ao_bufio_cmds[0]);
 }
index c3bee90681f9bb48ea279128171635142492bbe1..6629f1431fa03ddf89ecdc7bc2c080a4e93e800f 100644 (file)
@@ -30,6 +30,9 @@ ao_bufio_flush_one(uint8_t *buf);
 void
 ao_bufio_flush(void);
 
+void
+ao_bufio_setup(void);
+
 void
 ao_bufio_init(void);
 
index 98f57d674c6c3e3cd647422b65972d6db9db8885..a19eff701564c04dc30fecbb2b656a920351cf20 100644 (file)
 #include "ao_fat.h"
 #include "ao_bufio.h"
 
+/*
+ * Basic file system types
+ */
+
+typedef ao_fat_offset_t                offset_t;
+typedef ao_fat_sector_t                sector_t;
+typedef ao_fat_cluster_t       cluster_t;
+typedef ao_fat_dirent_t                dirent_t;
+typedef ao_fat_cluster_offset_t        cluster_offset_t;
+
 /* Partition information, sector numbers */
 
-static uint8_t partition_type;
-static uint32_t        partition_start, partition_end;
+static uint8_t partition_type;
+static sector_t        partition_start, partition_end;
+
+#define AO_FAT_BAD_CLUSTER             0xffffff7
+#define AO_FAT_LAST_CLUSTER            0xfffffff
+#define AO_FAT_IS_LAST_CLUSTER(c)              (((c) & 0xffffff8) == 0xffffff8)
+#define AO_FAT_IS_LAST_CLUSTER16(c)    (((c) & 0xfff8) == 0xfff8)
+
 
 #define SECTOR_SIZE    512
 #define SECTOR_MASK    (SECTOR_SIZE - 1)
@@ -34,17 +50,25 @@ static uint32_t     partition_start, partition_end;
 #define DIRENT_SIZE    32
 
 /* File system parameters */
-static uint8_t sectors_per_cluster;
-static uint32_t        bytes_per_cluster;
-static uint16_t        reserved_sector_count;
-static uint8_t number_fat;
-static uint16_t        root_entries;
-static uint16_t sectors_per_fat;
-static uint16_t        number_cluster;
-static uint32_t        fat_start;
-static uint32_t root_start;
-static uint32_t data_start;
-static uint16_t        first_free_cluster;
+static uint8_t         sectors_per_cluster;
+static uint32_t                bytes_per_cluster;
+static sector_t                reserved_sector_count;
+static uint8_t         number_fat;
+static dirent_t                root_entries;
+static sector_t        sectors_per_fat;
+static cluster_t       number_cluster;
+static sector_t                fat_start;
+static sector_t        root_start;
+static sector_t        data_start;
+static cluster_t       next_free;
+static uint8_t         filesystem_full;
+
+/* FAT32 extra data */
+static uint8_t         fat32;
+static uint8_t         fsinfo_dirty;
+static cluster_t       root_cluster;
+static sector_t                fsinfo_sector;
+static cluster_t       free_count;
 
 /*
  * Deal with LSB FAT data structures
@@ -81,14 +105,14 @@ put_u16(uint8_t *base, uint16_t value)
 }
 
 static uint8_t
-ao_fat_cluster_valid(uint16_t cluster)
+ao_fat_cluster_valid(cluster_t cluster)
 {
        return (2 <= cluster && cluster < number_cluster);
 }
 
 /* Start using a sector */
 static uint8_t *
-ao_fat_sector_get(uint32_t sector)
+ao_fat_sector_get(sector_t sector)
 {
        sector += partition_start;
        if (sector >= partition_end)
@@ -99,49 +123,36 @@ ao_fat_sector_get(uint32_t sector)
 /* Finish using a sector, 'w' is 1 if modified */
 #define ao_fat_sector_put(b,w) ao_bufio_put(b,w)
 
-/* Start using a root directory entry */
-static uint8_t *
-ao_fat_root_get(uint16_t e)
-{
-       uint32_t        byte = e * DIRENT_SIZE;
-       uint32_t        sector = byte >> SECTOR_SHIFT;
-       uint16_t        offset = byte & SECTOR_MASK;
-       uint8_t         *buf;
-
-       buf = ao_fat_sector_get(root_start + sector);
-       if (!buf)
-               return NULL;
-       return buf + offset;
-}
-
-/* Finish using a root directory entry, 'w' is 1 if modified */
-static void
-ao_fat_root_put(uint8_t *root, uint16_t e, uint8_t write)
-{
-       uint16_t        offset = ((e * DIRENT_SIZE) & SECTOR_MASK);
-       uint8_t         *buf = root - offset;
-
-       ao_fat_sector_put(buf, write);
-}
-
 /* Get the next cluster entry in the chain */
-static uint16_t
-ao_fat_entry_read(uint16_t cluster)
+static cluster_t
+ao_fat_entry_read(cluster_t cluster)
 {
-       uint32_t        sector;
-       uint16_t        offset;
+       sector_t        sector;
+       cluster_t       offset;
        uint8_t         *buf;
-       uint16_t        ret;
+       cluster_t       ret;
 
        if (!ao_fat_cluster_valid(cluster))
-               return 0xfff7;
-
-       sector = cluster >> (SECTOR_SHIFT - 1);
-       offset = (cluster << 1) & SECTOR_MASK;
+               return 0xfffffff7;
+
+       if (fat32)
+               cluster <<= 2;
+       else
+               cluster <<= 1;
+       sector = cluster >> (SECTOR_SHIFT);
+       offset = cluster & SECTOR_MASK;
        buf = ao_fat_sector_get(fat_start + sector);
        if (!buf)
                return 0;
-       ret = get_u16(buf + offset);
+
+       if (fat32) {
+               ret = get_u32(buf + offset);
+               ret &= 0xfffffff;
+       } else {
+               ret = get_u16(buf + offset);
+               if (AO_FAT_IS_LAST_CLUSTER16(ret))
+                       ret |= 0xfff0000;
+       }
        ao_fat_sector_put(buf, 0);
        return ret;
 }
@@ -149,36 +160,60 @@ ao_fat_entry_read(uint16_t cluster)
 /* Replace the referenced cluster entry in the chain with
  * 'new_value'. Return the previous value.
  */
-static uint16_t
-ao_fat_entry_replace(uint16_t  cluster, uint16_t new_value)
+static cluster_t
+ao_fat_entry_replace(cluster_t  cluster, cluster_t new_value)
 {
-       uint32_t        sector;
-       uint16_t        offset;
-       uint8_t         *buf;
-       uint16_t        ret;
-       uint8_t         other_fats;
+       sector_t                sector;
+       cluster_offset_t        offset;
+       uint8_t                 *buf;
+       cluster_t               ret;
+       cluster_t               old_value;
+       uint8_t                 fat;
 
        if (!ao_fat_cluster_valid(cluster))
-               return 0;
-
-       sector = cluster >> (SECTOR_SHIFT - 1);
-       offset = (cluster << 1) & SECTOR_MASK;
-       buf = ao_fat_sector_get(fat_start + sector);
-       if (!buf)
-               return 0;
-       ret = get_u16(buf + offset);
-       put_u16(buf + offset, new_value);
-       ao_fat_sector_put(buf, 1);
-
-       /*
-        * Keep the other FATs in sync
-        */
-       for (other_fats = 1; other_fats < number_fat; other_fats++) {
-               buf = ao_fat_sector_get(fat_start + other_fats * sectors_per_fat + sector);
-               if (buf) {
+               return 0xfffffff7;
+
+       /* Convert from cluster index to byte index */
+       if (fat32)
+               cluster <<= 2;
+       else
+               cluster <<= 1;
+       sector = cluster >> SECTOR_SHIFT;
+       offset = cluster & SECTOR_MASK;
+
+       new_value &= 0xfffffff;
+       for (fat = 0; fat < number_fat; fat++) {
+               buf = ao_fat_sector_get(fat_start + fat * sectors_per_fat + sector);
+               if (!buf)
+                       return 0;
+               if (fat32) {
+                       old_value = get_u32(buf + offset);
+                       put_u32(buf + offset, new_value | (old_value & 0xf0000000));
+                       if (fat == 0) {
+                               ret = old_value & 0xfffffff;
+
+                               /* Track the free count if it wasn't marked
+                                * invalid when we mounted the file system
+                                */
+                               if (free_count != 0xffffffff) {
+                                       if (new_value && !ret) {
+                                               --free_count;
+                                               fsinfo_dirty = 1;
+                                       } else if (!new_value && ret) {
+                                               ++free_count;
+                                               fsinfo_dirty = 1;
+                                       }
+                               }
+                       }
+               } else {
+                       if (fat == 0) {
+                               ret = get_u16(buf + offset);
+                               if (AO_FAT_IS_LAST_CLUSTER16(ret))
+                                       ret |= 0xfff0000;
+                       }
                        put_u16(buf + offset, new_value);
-                       ao_fat_sector_put(buf, 1);
                }
+               ao_fat_sector_put(buf, 1);
        }
        return ret;
        
@@ -189,12 +224,14 @@ ao_fat_entry_replace(uint16_t  cluster, uint16_t new_value)
  * all of them as free
  */
 static void
-ao_fat_free_cluster_chain(uint16_t cluster)
+ao_fat_free_cluster_chain(cluster_t cluster)
 {
        while (ao_fat_cluster_valid(cluster)) {
-               if (cluster < first_free_cluster)
-                       first_free_cluster = cluster;
-               cluster = ao_fat_entry_replace(cluster, 0x0000);
+               if (cluster < next_free) {
+                       next_free = cluster;
+                       fsinfo_dirty = 1;
+               }
+               cluster = ao_fat_entry_replace(cluster, 0x00000000);
        }
 }
 
@@ -207,8 +244,8 @@ ao_fat_free_cluster_chain(uint16_t cluster)
  * 0xffff if we walk off the end of the file or the cluster chain
  * is damaged somehow
  */
-static uint16_t
-ao_fat_cluster_seek(uint16_t cluster, uint16_t distance)
+static cluster_t
+ao_fat_cluster_seek(cluster_t cluster, cluster_t distance)
 {
        while (distance) {
                cluster = ao_fat_entry_read(cluster);
@@ -219,6 +256,176 @@ ao_fat_cluster_seek(uint16_t cluster, uint16_t distance)
        return cluster;
 }
 
+/*
+ * ao_fat_cluster_set_size
+ *
+ * Set the number of clusters in the specified chain,
+ * freeing extra ones or alocating new ones as needed
+ *
+ * Returns AO_FAT_BAD_CLUSTER on allocation failure
+ */
+
+static cluster_t
+ao_fat_cluster_set_size(cluster_t first_cluster, cluster_t size)
+{
+       cluster_t       clear_cluster = 0;
+
+       if (size == 0) {
+               clear_cluster = first_cluster;
+               first_cluster = 0;
+       } else {
+               cluster_t       have;
+               cluster_t       last_cluster = 0;
+               cluster_t       next_cluster;
+
+               /* Walk the cluster chain to the
+                * spot where it needs to change. That
+                * will either be the end of the chain (in case it needs to grow),
+                * or after the desired number of clusters, in which case it needs to shrink
+                */
+               next_cluster = first_cluster;
+               for (have = 0; have < size; have++) {
+                       last_cluster = next_cluster;
+                       next_cluster = ao_fat_entry_read(last_cluster);
+                       if (!ao_fat_cluster_valid(next_cluster))
+                               break;
+               }
+
+               if (have == size) {
+                       /* The file is large enough, truncate as needed */
+                       if (ao_fat_cluster_valid(next_cluster)) {
+                               /* Rewrite that cluster entry with 0xffff to mark the end of the chain */
+                               clear_cluster = ao_fat_entry_replace(last_cluster, AO_FAT_LAST_CLUSTER);
+                               filesystem_full = 0;
+                       } else {
+                               /* The chain is already the right length, don't mess with it */
+                               ;
+                       }
+               } else {
+                       cluster_t       need;
+                       cluster_t       free;
+
+                       if (filesystem_full)
+                               return AO_FAT_BAD_CLUSTER;
+
+                       if (next_free < 2 || number_cluster <= next_free) {
+                               next_free = 2;
+                               fsinfo_dirty = 1;
+                       }
+
+                       /* See if there are enough free clusters in the file system */
+                       need = size - have;
+
+#define loop_cluster   for (free = next_free; need > 0;)
+#define next_cluster                                   \
+                       if (++free == number_cluster)   \
+                               free = 2;               \
+                       if (free == next_free) \
+                               break;                  \
+
+                       loop_cluster {
+                               if (!ao_fat_entry_read(free))
+                                       need--;
+                               next_cluster;
+                       }
+                       /* Still need some, tell the user that we've failed */
+                       if (need) {
+                               filesystem_full = 1;
+                               return AO_FAT_BAD_CLUSTER;
+                       }
+
+                       /* Now go allocate those clusters and
+                        * thread them onto the chain
+                        */
+                       need = size - have;
+                       loop_cluster {
+                               if (!ao_fat_entry_read(free)) {
+                                       next_free = free + 1;
+                                       if (next_free >= number_cluster)
+                                               next_free = 2;
+                                       fsinfo_dirty = 1;
+                                       if (last_cluster)
+                                               ao_fat_entry_replace(last_cluster, free);
+                                       else
+                                               first_cluster = free;
+                                       last_cluster = free;
+                                       need--;
+                               }
+                               next_cluster;
+                       }
+#undef loop_cluster
+#undef next_cluster
+                       /* Mark the new end of the chain */
+                       ao_fat_entry_replace(last_cluster, AO_FAT_LAST_CLUSTER);
+               }
+       }
+
+       /* Deallocate clusters off the end of the file */
+       if (ao_fat_cluster_valid(clear_cluster))
+               ao_fat_free_cluster_chain(clear_cluster);
+       return first_cluster;
+}
+
+/* Start using a root directory entry */
+static uint8_t *
+ao_fat_root_get(dirent_t e)
+{
+       offset_t                byte = e * DIRENT_SIZE;
+       sector_t                sector = byte >> SECTOR_SHIFT;
+       cluster_offset_t        offset = byte & SECTOR_MASK;
+       uint8_t                 *buf;
+
+       if (fat32) {
+               cluster_t       cluster_distance = sector / sectors_per_cluster;
+               sector_t        sector_index = sector % sectors_per_cluster;
+               cluster_t       cluster = ao_fat_cluster_seek(root_cluster, cluster_distance);
+
+               if (ao_fat_cluster_valid(cluster))
+                       sector = data_start + (cluster-2) * sectors_per_cluster + sector_index;
+               else
+                       return NULL;
+       } else {
+               if (e >= root_entries)
+                       return NULL;
+               sector = root_start + sector;
+       }
+
+       buf = ao_fat_sector_get(sector);
+       if (!buf)
+               return NULL;
+       return buf + offset;
+}
+
+/* Finish using a root directory entry, 'w' is 1 if modified */
+static void
+ao_fat_root_put(uint8_t *root, dirent_t e, uint8_t write)
+{
+       cluster_offset_t        offset = ((e * DIRENT_SIZE) & SECTOR_MASK);
+       uint8_t                 *buf = root - offset;
+
+       ao_fat_sector_put(buf, write);
+}
+
+/*
+ * ao_fat_root_extend
+ *
+ * On FAT32, make the 
+ */
+static int8_t
+ao_fat_root_extend(dirent_t ents)
+{
+       offset_t        byte_size;
+       cluster_t       cluster_size;
+       if (!fat32)
+               return 0;
+       
+       byte_size = ents * 0x20;
+       cluster_size = byte_size / bytes_per_cluster;
+       if (ao_fat_cluster_set_size(root_cluster, cluster_size) != AO_FAT_BAD_CLUSTER)
+               return 1;
+       return 0;
+}
+               
 /*
  * ao_fat_setup_partition
  * 
@@ -316,6 +523,26 @@ ao_fat_setup_fs(void)
        number_fat = boot[0x10];
        root_entries = get_u16(boot + 0x11);
        sectors_per_fat = get_u16(boot+0x16);
+       fat32 = 0;
+       if (sectors_per_fat == 0) {
+               fat32 = 1;
+               sectors_per_fat = get_u32(boot+0x24);
+               root_cluster = get_u32(boot+0x2c);
+               fsinfo_sector = get_u16(boot + 0x30);
+       }
+       ao_fat_sector_put(boot, 0);
+
+       free_count = 0xffffffff;
+       next_free = 0;
+       if (fat32 && fsinfo_sector) {
+               uint8_t *fsinfo = ao_fat_sector_get(fsinfo_sector);
+
+               if (fsinfo) {
+                       free_count = get_u32(fsinfo + 0x1e8);
+                       next_free = get_u32(fsinfo + 0x1ec);
+                       ao_fat_sector_put(fsinfo, 0);
+               }
+       }
 
        fat_start = reserved_sector_count;;
        root_start = fat_start + number_fat * sectors_per_fat;
@@ -325,6 +552,7 @@ ao_fat_setup_fs(void)
 
        number_cluster = data_sectors / sectors_per_cluster;
 
+       printf ("fat32: %d\n", fat32);
        printf ("sectors per cluster %d\n", sectors_per_cluster);
        printf ("reserved sectors %d\n", reserved_sector_count);
        printf ("number of FATs %d\n", number_fat);
@@ -335,20 +563,35 @@ ao_fat_setup_fs(void)
        printf ("root start %d\n", root_start);
        printf ("data start %d\n", data_start);
 
-       ao_fat_sector_put(boot, 0);
-
        return 1;
 }
 
+/*
+ * State for the current opened file
+ */
+static struct ao_fat_dirent    ao_file_dirent;
+static uint32_t                ao_file_offset;
+static uint32_t                        ao_file_cluster_offset;
+static cluster_t               ao_file_cluster;
+static uint8_t                 ao_file_opened;
+
 static uint8_t
 ao_fat_setup(void)
 {
+       ao_bufio_setup();
+       
+       partition_type = partition_start = partition_end = 0;
+       sectors_per_cluster = bytes_per_cluster = reserved_sector_count = 0;
+       number_fat = root_entries = sectors_per_fat = 0;
+       number_cluster = fat_start = root_start = data_start = 0;
+       next_free = filesystem_full = 0;
+       fat32 = fsinfo_dirty = root_cluster = fsinfo_sector = free_count = 0;
+       memset(&ao_file_dirent, '\0', sizeof (ao_file_dirent));
+       ao_file_offset = ao_file_cluster_offset = ao_file_cluster = ao_file_opened = 0;
        if (!ao_fat_setup_partition())
                return 0;
-       check_bufio("partition setup");
        if (!ao_fat_setup_fs())
                return 0;
-       check_bufio("fs setup");
        return 1;
 }
 
@@ -356,19 +599,13 @@ ao_fat_setup(void)
  * Basic file operations
  */
 
-static struct ao_fat_dirent    ao_file_dirent;
-static uint32_t                ao_file_offset;
-static uint32_t                        ao_file_cluster_offset;
-static uint16_t                        ao_file_cluster;
-static uint8_t                 ao_file_opened;
-
 static uint32_t
 ao_fat_current_sector(void)
 {
-       uint16_t        cluster_offset;
+       cluster_t       cluster_offset;
        uint32_t        sector_offset;
        uint16_t        sector_index;
-       uint16_t        cluster;
+       cluster_t       cluster;
 
        if (ao_file_offset > ao_file_dirent.size)
                return 0xffffffff;
@@ -381,7 +618,7 @@ ao_fat_current_sector(void)
        }
 
        if (ao_file_cluster_offset + bytes_per_cluster <= ao_file_offset) {
-               uint16_t        cluster_distance;
+               cluster_t       cluster_distance;
 
                cluster_offset = sector_offset / sectors_per_cluster;
 
@@ -414,98 +651,44 @@ ao_fat_set_offset(uint32_t offset)
 static int8_t
 ao_fat_set_size(uint32_t size)
 {
-       uint16_t        clear_cluster = 0;
        uint8_t         *dent;
-       uint16_t        first_cluster;
+       cluster_t       first_cluster;
+       cluster_t       have_clusters, need_clusters;
 
-       first_cluster = ao_file_dirent.cluster;
        if (size == ao_file_dirent.size)
                return AO_FAT_SUCCESS;
-       if (size == 0) {
-               clear_cluster = ao_file_dirent.cluster;
-               first_cluster = 0;
-       } else {
-               uint16_t        new_num;
-               uint16_t        old_num;
-
-               new_num = (size + bytes_per_cluster - 1) / bytes_per_cluster;
-               old_num = (ao_file_dirent.size + bytes_per_cluster - 1) / bytes_per_cluster;
-               if (new_num < old_num) {
-                       uint16_t last_cluster;
-
-                       /* Go find the last cluster we want to preserve in the file */
-                       last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, new_num - 1);
-
-                       /* Rewrite that cluster entry with 0xffff to mark the end of the chain */
-                       clear_cluster = ao_fat_entry_replace(last_cluster, 0xffff);
-               } else if (new_num > old_num) {
-                       uint16_t        need;
-                       uint16_t        free;
-                       uint16_t        last_cluster;
-                       uint16_t        highest_allocated = 0;
-
-                       if (old_num)
-                               last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, old_num - 1);
-                       else
-                               last_cluster = 0;
 
-                       if (first_free_cluster < 2 || number_cluster <= first_free_cluster)
-                               first_free_cluster = 2;
+       first_cluster = ao_file_dirent.cluster;
+       have_clusters = (ao_file_dirent.size + bytes_per_cluster - 1) / bytes_per_cluster;
+       need_clusters = (size + bytes_per_cluster - 1) / bytes_per_cluster;
 
-                       /* See if there are enough free clusters in the file system */
-                       need = new_num - old_num;
+       if (have_clusters != need_clusters) {
+               if (ao_file_cluster && size >= ao_file_cluster_offset) {
+                       cluster_t       offset_clusters = (ao_file_cluster_offset + bytes_per_cluster) / bytes_per_cluster;
+                       cluster_t       extra_clusters = need_clusters - offset_clusters;
+                       cluster_t       next_cluster;
 
-#define loop_cluster   for (free = first_free_cluster; need > 0;)
-#define next_cluster                                   \
-                       if (++free == number_cluster)   \
-                               free = 2;               \
-                       if (free == first_free_cluster) \
-                               break;                  \
-
-                       loop_cluster {
-                               if (!ao_fat_entry_read(free))
-                                       need--;
-                               next_cluster;
-                       }
-                       /* Still need some, tell the user that we've failed */
-                       if (need)
+                       next_cluster = ao_fat_cluster_set_size(ao_file_cluster, extra_clusters);
+                       if (next_cluster == AO_FAT_BAD_CLUSTER)
                                return -AO_FAT_ENOSPC;
+               } else {
+                       first_cluster = ao_fat_cluster_set_size(first_cluster, need_clusters);
 
-                       /* Now go allocate those clusters */
-                       need = new_num - old_num;
-                       loop_cluster {
-                               if (!ao_fat_entry_read(free)) {
-                                       if (free > highest_allocated)
-                                               highest_allocated = free;
-                                       if (last_cluster)
-                                               ao_fat_entry_replace(last_cluster, free);
-                                       else
-                                               first_cluster = free;
-                                       last_cluster = free;
-                                       need--;
-                               }
-                               next_cluster;
-                       }
-                       first_free_cluster = highest_allocated + 1;
-                       if (first_free_cluster >= number_cluster)
-                               first_free_cluster = 2;
-
-                       /* Mark the new end of the chain */
-                       ao_fat_entry_replace(last_cluster, 0xffff);
+                       if (first_cluster == AO_FAT_BAD_CLUSTER)
+                               return -AO_FAT_ENOSPC;
                }
        }
 
-       /* Deallocate clusters off the end of the file */
-       if (ao_fat_cluster_valid(clear_cluster))
-               ao_fat_free_cluster_chain(clear_cluster);
-
        /* Update the directory entry */
        dent = ao_fat_root_get(ao_file_dirent.entry);
        if (!dent)
                return -AO_FAT_EIO;
        put_u32(dent + 0x1c, size);
        put_u16(dent + 0x1a, first_cluster);
+       if (fat32)
+               put_u16(dent + 0x14, first_cluster >> 16);
        ao_fat_root_put(dent, ao_file_dirent.entry, 1);
+
        ao_file_dirent.size = size;
        ao_file_dirent.cluster = first_cluster;
        return AO_FAT_SUCCESS;
@@ -556,9 +739,39 @@ ao_fat_dirent_init(uint8_t *dent, uint16_t entry, struct ao_fat_dirent *dirent)
        dirent->attr = dent[0x0b];
        dirent->size = get_u32(dent+0x1c);
        dirent->cluster = get_u16(dent+0x1a);
+       if (fat32)
+               dirent->cluster |= (cluster_t) get_u16(dent + 0x14) << 16;
        dirent->entry = entry;
 }
 
+/*
+ * ao_fat_flush_fsinfo
+ *
+ * Write out any fsinfo changes to disk
+ */
+
+void
+ao_fat_flush_fsinfo(void)
+{
+       uint8_t *fsinfo;
+
+       if (!fat32)
+               return;
+
+       if (!fsinfo_dirty)
+               return;
+       fsinfo_dirty = 0;
+       if (!fsinfo_sector)
+               return;
+
+       fsinfo = ao_fat_sector_get(fsinfo_sector);
+       if (fsinfo) {
+               put_u32(fsinfo + 0x1e8, free_count);
+               put_u32(fsinfo + 0x1ec, next_free);
+               ao_fat_sector_put(fsinfo, 1);
+       }
+}
+
 /*
  * Public API
  */
@@ -605,6 +818,7 @@ ao_fat_creat(char name[11])
 {
        uint16_t        entry;
        int8_t          status;
+       uint8_t         *dent;
 
        if (ao_file_opened)
                return -AO_FAT_EMFILE;
@@ -616,12 +830,14 @@ ao_fat_creat(char name[11])
                status = ao_fat_set_size(0);
                break;
        case -AO_FAT_ENOENT:
-               for (entry = 0; entry < root_entries; entry++) {
-                       uint8_t *dent = ao_fat_root_get(entry);
-
+               entry = 0;
+               for (;;) {
+                       dent = ao_fat_root_get(entry);
                        if (!dent) {
-                               status = -AO_FAT_EIO;
-                               ao_fat_root_put(dent, entry, 0);
+                               
+                               if (ao_fat_root_extend(entry))
+                                       continue;
+                               status = -AO_FAT_ENOSPC;
                                break;
                        }
                                
@@ -636,9 +852,8 @@ ao_fat_creat(char name[11])
                        } else {
                                ao_fat_root_put(dent, entry, 0);
                        }
+                       entry++;
                }
-               if (entry == root_entries)
-                       status = -AO_FAT_ENOSPC;
        }
        return status;
 }
@@ -658,6 +873,8 @@ ao_fat_close(void)
        ao_file_offset = 0;
        ao_file_cluster = 0;
        ao_file_opened = 0;
+
+       ao_fat_flush_fsinfo();
        ao_bufio_flush();
        return AO_FAT_SUCCESS;
 }
@@ -849,10 +1066,10 @@ ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent)
 {
        uint8_t *dent;
 
-       if (*entry >= root_entries)
-               return 0;
        for (;;) {
                dent = ao_fat_root_get(*entry);
+               if (!dent)
+                       return 0;
 
                if (dent[0] == AO_FAT_DENT_END) {
                        ao_fat_root_put(dent, *entry, 0);
index 5b9b300f51d53ebf402658f1e2ac4bb685e0ac35..cfe98a766b59bff9a324b72dfd670acc5882611b 100644 (file)
@@ -32,8 +32,8 @@ ao_fat_init(void);
 #define AO_FAT_DENT_EMPTY              0xe5
 #define AO_FAT_DENT_END                        0x00
 
-#define AO_FAT_IS_FILE(attr)   (((attr) & (AO_FAT_FILE_VOLUME_LABEL|AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_ARCHIVE)) == 0)
-#define AO_FAT_IS_DIR(attr)    (((attr) & (AO_FAT_FILE_DIRECTORY)) == AO_FAT_FILE_DIRECTORY)
+#define AO_FAT_IS_FILE(attr)   (((attr) & (AO_FAT_FILE_VOLUME_LABEL|AO_FAT_FILE_DIRECTORY)) == 0)
+#define AO_FAT_IS_DIR(attr)    (((attr) & (AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_VOLUME_LABEL)) == AO_FAT_FILE_DIRECTORY)
 
 #define AO_FAT_SUCCESS                 0
 #define AO_FAT_EPERM                   1
@@ -80,12 +80,37 @@ ao_fat_unlink(char name[11]);
 int8_t
 ao_fat_rename(char old[11], char new[11]);
 
+/*
+ * Byte offset within a file. Supports files up to 2GB in size
+ */
+typedef int32_t                ao_fat_offset_t;
+
+/*
+ * Cluster index in partition data space
+ */
+typedef uint32_t       ao_fat_cluster_t;
+
+/*
+ * Sector offset within partition
+ */
+typedef uint32_t       ao_fat_sector_t;
+
+/*
+ * Index within the root directory
+ */
+typedef uint16_t       ao_fat_dirent_t;
+
+/*
+ * Offset within a cluster (or sector)
+ */
+typedef uint16_t       ao_fat_cluster_offset_t;
+
 struct ao_fat_dirent {
-       char            name[11];
-       uint8_t         attr;
-       uint32_t        size;
-       uint16_t        cluster;
-       uint16_t        entry;
+       char                    name[11];
+       uint8_t                 attr;
+       uint32_t                size;
+       ao_fat_cluster_t        cluster;
+       uint16_t                entry;
 };
 
 int8_t
index fffd5af48af38314ba6fc2c4cd0647130511181c..48d5d8a43401100e7871e7991de9d7b387857df5 100644 (file)
@@ -84,14 +84,33 @@ ao_sdcard_write_block(uint32_t block, uint8_t *data)
        return write(fs_fd, data, 512) == 512;
 }
 
-char   *fs = "fs.fat";
+struct fs_param {
+       int     fat;
+       int     blocks;
+} fs_params[] = {
+       { .fat = 16, .blocks = 16384 },
+       { .fat = 32, .blocks = 16384 },
+       { .fat = 16, .blocks = 65536 },
+       { .fat = 32, .blocks = 65536 },
+       { .fat = 16, .blocks = 1048576 },
+       { .fat = 32, .blocks = 1048576 },
+       { .fat = 0, .blocks = 0 },
+};
+
+char           *fs = "fs.fat";
+struct fs_param        *param;
 
 void
 ao_sdcard_init(void)
 {
        char    cmd[1024];
 
-       snprintf(cmd, sizeof(cmd), "rm -f %s && mkfs.vfat -C %s 16384", fs, fs);
+       if (fs_fd) {
+               close(fs_fd);
+               fs_fd = 0;
+       }
+       snprintf(cmd, sizeof(cmd), "rm -f %s && mkfs.vfat -F %d -C %s %d",
+                fs, param->fat, fs, param->blocks);
        if (system (cmd) != 0) {
                fprintf(stderr, "'%s' failed\n", cmd);
                exit(1);
@@ -125,21 +144,27 @@ check_fat(void);
 #include "ao_fat.c"
 
 /* Get the next cluster entry in the chain */
-static uint16_t
-ao_fat_entry_raw_read(uint16_t cluster, uint8_t fat)
+static cluster_t
+ao_fat_entry_raw_read(cluster_t cluster, uint8_t fat)
 {
-       uint32_t        sector;
-       uint16_t        offset;
-       uint8_t         *buf;
-       uint16_t        ret;
-
-//     cluster -= 2;
-       sector = cluster >> (SECTOR_SHIFT - 1);
-       offset = (cluster << 1) & SECTOR_MASK;
+       sector_t                sector;
+       cluster_offset_t        offset;
+       uint8_t                 *buf;
+       cluster_t               ret;
+
+       if (fat32)
+               cluster <<= 2;
+       else
+               cluster <<= 1;
+       sector = cluster >> SECTOR_SHIFT;
+       offset = cluster & SECTOR_MASK;
        buf = ao_fat_sector_get(fat_start + fat * sectors_per_fat + sector);
        if (!buf)
                return 0;
-       ret = get_u16(buf + offset);
+       if (fat32)
+               ret = get_u32(buf + offset);
+       else
+               ret = get_u16(buf + offset);
        ao_fat_sector_put(buf, 0);
        return ret;
 }
@@ -153,16 +178,21 @@ dump_fat(void)
        for (e = 0; e < number_cluster; e++) {
                if ((e & 0xf) == 0x0)
                        printf ("%04x: ", e);
-               printf (" %04x", ao_fat_entry_raw_read(e, 0));
+               if (fat32)
+                       printf (" %08x", ao_fat_entry_raw_read(e, 0));
+               else
+                       printf (" %04x", ao_fat_entry_raw_read(e, 0));
                if ((e & 0xf) == 0xf)
                        putchar ('\n');
        }
+       if (e & 0xf)
+               putchar('\n');
 }
 
 void
 fat_list(void)
 {
-       uint16_t                entry = 0;
+       dirent_t                entry = 0;
        struct ao_fat_dirent    dirent;
 
        printf ("  **** Root directory ****\n");
@@ -182,8 +212,8 @@ fat_list(void)
 void
 fatal(char *msg, ...)
 {
-       dump_fat();
-       fat_list();
+//     dump_fat();
+//     fat_list();
 
        va_list l;
        va_start(l, msg);
@@ -200,7 +230,7 @@ check_fat(void)
        int     f;
 
        for (e = 0; e < number_cluster; e++) {
-               uint16_t        v = ao_fat_entry_raw_read(e, 0);
+               cluster_t       v = ao_fat_entry_raw_read(e, 0);
                for (f = 1; f < number_fat; f++) {
                        if (ao_fat_entry_raw_read(e, f) != v)
                                fatal ("fats differ at %d\n", e);
@@ -208,24 +238,24 @@ check_fat(void)
        }
 }
 
-uint16_t
-check_file(uint16_t dent, uint16_t first_cluster, uint8_t *used)
+cluster_t
+check_file(dirent_t dent, cluster_t first_cluster, dirent_t *used)
 {
-       uint16_t        clusters = 0;
-       uint16_t        cluster;
+       cluster_t       clusters = 0;
+       cluster_t       cluster;
 
        if (!first_cluster)
                return 0;
        
        for (cluster = first_cluster;
-            (cluster & 0xfff8) != 0xfff8;
+            fat32 ? !AO_FAT_IS_LAST_CLUSTER(cluster) : !AO_FAT_IS_LAST_CLUSTER16(cluster);
             cluster = ao_fat_entry_raw_read(cluster, 0))
        {
                if (!ao_fat_cluster_valid(cluster))
-                       fatal("file %d: invalid cluster %04x\n", dent, cluster);
+                       fatal("file %d: invalid cluster %08x\n", dent, cluster);
                if (used[cluster])
-                       fatal("file %d: duplicate cluster %04x\n", dent, cluster);
-               used[cluster] = 1;
+                       fatal("file %d: duplicate cluster %08x also in file %d\n", dent, cluster, used[cluster]-1);
+               used[cluster] = dent;
                clusters++;
        }
        return clusters;
@@ -234,25 +264,27 @@ check_file(uint16_t dent, uint16_t first_cluster, uint8_t *used)
 void
 check_fs(void)
 {
-       uint16_t        r;
-       uint16_t        cluster, chain;
-       uint8_t         *used;
+       dirent_t        r;
+       cluster_t       cluster, chain;
+       dirent_t        *used;
+       uint8_t         *dent;
 
        check_fat();
 
-       used = calloc(1, number_cluster);
+       used = calloc(sizeof (dirent_t), number_cluster);
 
-       for (r = 0; r < root_entries; r++) {
-               uint8_t         *dent = ao_fat_root_get(r);
-               uint16_t        clusters;
-               uint32_t        size;
-               uint16_t        first_cluster;
-               uint8_t         name[11];
+       for (r = 0; (dent = ao_fat_root_get(r)); r++) {
+               cluster_t       clusters;
+               offset_t        size;
+               cluster_t       first_cluster;
+               char            name[11];
 
                if (!dent)
                        fatal("cannot map dent %d\n", r);
                memcpy(name, dent+0, 11);
                first_cluster = get_u16(dent + 0x1a);
+               if (fat32)
+                       first_cluster |= (cluster_t) get_u16(dent + 0x14) << 16;
                size = get_u32(dent + 0x1c);
                ao_fat_root_put(dent, r, 0);
 
@@ -260,7 +292,7 @@ check_fs(void)
                        break;
                }
 
-               clusters = check_file(r, first_cluster, used);
+               clusters = check_file(r + 1, first_cluster, used);
                if (size == 0) {
                        if (clusters != 0)
                                fatal("file %d: zero sized, but %d clusters\n", clusters);
@@ -273,20 +305,29 @@ check_fs(void)
                                      r, size, clusters, clusters * bytes_per_cluster);
                }
        }
-       for (; r < root_entries; r++) {
-               uint8_t *dent = ao_fat_root_get(r);
-               if (!dent)
-                       fatal("cannot map dent %d\n", r);
-               if (dent[0] != AO_FAT_DENT_END)
-                       fatal("found non-zero dent past end %d\n", r);
-               ao_fat_root_put(dent, r, 0);
+       if (!fat32) {
+               for (; r < root_entries; r++) {
+                       uint8_t *dent = ao_fat_root_get(r);
+                       if (!dent)
+                               fatal("cannot map dent %d\n", r);
+                       if (dent[0] != AO_FAT_DENT_END)
+                               fatal("found non-zero dent past end %d\n", r);
+                       ao_fat_root_put(dent, r, 0);
+               }
+       } else {
+               check_file((dirent_t) -1, root_cluster, used);
        }
 
        for (cluster = 0; cluster < 2; cluster++) {
                chain = ao_fat_entry_raw_read(cluster, 0);
 
-               if ((chain & 0xfff8) != 0xfff8)
-                       fatal("cluster %d: not marked busy\n", cluster);
+               if (fat32) {
+                       if ((chain & 0xffffff8) != 0xffffff8)
+                               fatal("cluster %d: not marked busy\n", cluster);
+               } else {
+                       if ((chain & 0xfff8) != 0xfff8)
+                               fatal("cluster %d: not marked busy\n", cluster);
+               }
        }
        for (; cluster < number_cluster; cluster++) {
                chain = ao_fat_entry_raw_read(cluster, 0);
@@ -296,40 +337,66 @@ check_fs(void)
                                fatal("cluster %d: marked busy, but not in any file\n", cluster);
                } else {
                        if (used[cluster] != 0)
-                               fatal("cluster %d: marked free, but foudn in file\n", cluster);
+                               fatal("cluster %d: marked free, but found in file %d\n", cluster, used[cluster]-1);
                }
        }
 }
 
-#define NUM_FILES      10
-#define LINES_FILE     80000
+#define NUM_FILES      100
+#define LINES_FILE     500000
 
 uint32_t               sizes[NUM_FILES];
 
 unsigned char          md5[NUM_FILES][MD5_DIGEST_LENGTH];
 
-int
-main(int argc, char **argv)
+void
+short_test_fs(void)
+{
+       int     len;
+       char    buf[345];
+
+       if (ao_fat_open("HELLO   TXT",AO_FAT_OPEN_READ) == AO_FAT_SUCCESS) {
+               printf ("File contents for HELLO.TXT\n");
+               while ((len = ao_fat_read(buf, sizeof(buf))))
+                       write(1, buf, len);
+               ao_fat_close();
+       }
+       
+       if (ao_fat_creat("NEWFILE TXT") == AO_FAT_SUCCESS) {
+               printf ("Create new file\n");
+               for (len = 0; len < 2; len++)
+                       ao_fat_write("hello, world!\n", 14);
+               ao_fat_seek(0, AO_FAT_SEEK_SET);
+               printf ("read new file\n");
+               while ((len = ao_fat_read(buf, sizeof (buf))))
+                       write (1, buf, len);
+               ao_fat_close();
+       }
+
+       check_fs();
+}
+
+void
+long_test_fs(void)
 {
        char    name[12];
        int     id;
        MD5_CTX ctx;
        unsigned char   md5_check[MD5_DIGEST_LENGTH];
+       char buf[337];
+       int     len;
+       uint64_t        total_file_size = 0;
 
-       if (argv[1])
-               fs = argv[1];
-
-       ao_fat_init();
-
-       check_bufio("top");
-       ao_fat_setup();
+       total_reads = total_writes = 0;
 
-       check_fs();
-       check_bufio("after setup");
        printf ("   **** Creating %d files\n", NUM_FILES);
 
+       memset(sizes, '\0', sizeof (sizes));
        for (id = 0; id < NUM_FILES; id++) {
                sprintf(name, "D%07dTXT", id);
+               if ((id % (NUM_FILES/50)) == 0) {
+                       printf ("."); fflush(stdout);
+               }
                if (ao_fat_creat(name) == AO_FAT_SUCCESS) {
                        int j;
                        char    line[64];
@@ -342,6 +409,7 @@ main(int argc, char **argv)
                                ret = ao_fat_write((uint8_t *) line, len);
                                if (ret <= 0)
                                        break;
+                               total_file_size += ret;
                                MD5_Update(&ctx, line, ret);
                                sizes[id] += ret;
                                if (ret != len)
@@ -353,20 +421,24 @@ main(int argc, char **argv)
                }
        }
 
+       printf ("\n   **** Write IO: read %llu write %llu data sectors %llu\n", total_reads, total_writes, (total_file_size + 511) / 512);
+
        check_bufio("all files created");
        printf ("   **** All done creating files\n");
        check_fs();
 
+       total_reads = total_writes = 0;
+
        printf ("   **** Comparing %d files\n", NUM_FILES);
 
        for (id = 0; id < NUM_FILES; id++) {
-               char buf[337];
                uint32_t size;
                sprintf(name, "D%07dTXT", id);
                size = 0;
+               if ((id % (NUM_FILES/50)) == 0) {
+                       printf ("."); fflush(stdout);
+               }
                if (ao_fat_open(name, AO_FAT_OPEN_READ) == AO_FAT_SUCCESS) {
-                       int     len;
-
                        MD5_Init(&ctx);
                        while ((len = ao_fat_read((uint8_t *) buf, sizeof(buf))) > 0) {
                                MD5_Update(&ctx, buf, len);
@@ -382,7 +454,43 @@ main(int argc, char **argv)
                        check_bufio("file shown");
                }
        }
+       printf ("\n  **** Read IO: read %llu write %llu\n", total_reads, total_writes);
+}
+
+char *params[] = {
+       "-F 16 -C %s 16384",
+       "-F 32 -C %s 16384",
+       "-F 16 -C %s 65536",
+       "-F 32 -C %s 65536",
+       "-F 16 -C %s 1048576",
+       "-F 32 -C %s 1048576",
+       NULL
+};
+
+int
+main(int argc, char **argv)
+{
+       int     p;
+
+       if (argv[1])
+               fs = argv[1];
+
+       for (p = 0; fs_params[p].fat; p++) {
+               param = &fs_params[p];
+               ao_fat_init();
+
+               check_bufio("top");
+               ao_fat_setup();
+
+               check_fs();
+               check_bufio("after setup");
+
+#ifdef SIMPLE_TEST
+               short_test_fs();
+#else
+               long_test_fs();
+#endif
+       }
 
-       printf ("\n    **** Total IO: read %llu write %llu\n", total_reads, total_writes);
        return 0;
 }