altos: Add SDCARD and FAT16 filesystem support
authorKeith Packard <keithp@keithp.com>
Wed, 27 Mar 2013 08:12:33 +0000 (01:12 -0700)
committerKeith Packard <keithp@keithp.com>
Wed, 27 Mar 2013 08:12:33 +0000 (01:12 -0700)
This adds a fairly primitive FAT16 file system implementation
along with support for SD cards.

Signed-off-by: Keith Packard <keithp@keithp.com>
src/drivers/ao_bufio.c [new file with mode: 0644]
src/drivers/ao_bufio.h [new file with mode: 0644]
src/drivers/ao_fat.c [new file with mode: 0644]
src/drivers/ao_fat.h [new file with mode: 0644]
src/drivers/ao_sdcard.c [new file with mode: 0644]
src/drivers/ao_sdcard.h [new file with mode: 0644]
src/test/Makefile
src/test/ao_fat_test.c [new file with mode: 0644]

diff --git a/src/drivers/ao_bufio.c b/src/drivers/ao_bufio.c
new file mode 100644 (file)
index 0000000..9a5e801
--- /dev/null
@@ -0,0 +1,298 @@
+/*
+ * Copyright © 2013 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef AO_FAT_TEST
+#include "ao.h"
+#endif
+
+#include "ao_sdcard.h"
+#include "ao_bufio.h"
+
+#define AO_NUM_BUF             4
+#define AO_BUFSIZ              512
+
+struct ao_bufio {
+       uint32_t        block;
+       int16_t         seqno;
+       uint8_t         busy;   
+       uint8_t         dirty;
+};
+
+static struct ao_bufio ao_bufio[AO_NUM_BUF];
+static uint8_t         ao_buffer[AO_NUM_BUF][AO_BUFSIZ];
+static int16_t         ao_seqno;
+static uint8_t         ao_bufio_mutex;
+
+#if 0
+#define DBG(...) printf(__VA_ARGS__)
+#else
+#define DBG(...)
+#endif
+
+static inline void
+ao_bufio_lock(void)
+{
+       ao_mutex_get(&ao_bufio_mutex);
+}
+
+static inline void
+ao_bufio_unlock(void)
+{
+       ao_mutex_put(&ao_bufio_mutex);
+}
+
+static inline int16_t
+ao_seqno_age(int16_t s)
+{
+       return ao_seqno - s;
+}
+
+static inline int16_t
+ao_seqno_next(void)
+{
+       return ++ao_seqno;
+}
+
+static inline int
+ao_seqno_older(int16_t a, int16_t b)
+{
+       return ao_seqno_age(a) > ao_seqno_age(b);
+}
+
+static inline void
+ao_validate_bufno(int b)
+{
+       if (b < 0 || AO_NUM_BUF <= b)
+               ao_panic(AO_PANIC_BUFIO);
+}
+
+static inline int
+ao_buf_to_num(uint8_t *buf)
+{
+       int b = (buf - &ao_buffer[0][0]) / AO_BUFSIZ;
+
+       ao_validate_bufno(b);
+       return b;
+}
+
+static inline int
+ao_bufio_to_num(struct ao_bufio *bufio)
+{
+       int b = (bufio - ao_bufio);
+
+       ao_validate_bufno(b);
+       return b;
+}
+
+static inline struct ao_bufio *
+ao_buf_to_bufio(uint8_t *buf)
+{
+       int b = ao_buf_to_num(buf);
+       struct ao_bufio *bufio;
+
+       bufio = &ao_bufio[b];
+       DBG ("buf %08x is %d bufio %08x\n", buf, b, bufio);
+       return bufio;
+}
+
+static inline uint8_t *
+ao_bufio_to_buf(struct ao_bufio *bufio)
+{
+       int b = ao_bufio_to_num(bufio);
+       uint8_t *buf;
+
+       buf = &ao_buffer[b][0];
+       DBG ("bufio %08x is %d buf %08x\n", bufio, b, buf);
+       return buf;
+}
+
+/*
+ * Write a buffer to storage if it is dirty
+ */
+static void
+ao_bufio_write(struct ao_bufio *bufio)
+{
+       if (bufio->dirty) {
+               ao_sdcard_write_block(bufio->block, ao_bufio_to_buf(bufio));
+               bufio->dirty = 0;
+       }
+}
+
+/*
+ * Read a buffer from storage
+ */
+static uint8_t
+ao_bufio_read(struct ao_bufio *bufio)
+{
+       uint8_t *buf = ao_bufio_to_buf(bufio);
+
+       return ao_sdcard_read_block(bufio->block, buf);
+}
+
+/*
+ * Find a buffer containing the specified block
+ */
+static struct ao_bufio *
+ao_bufio_find_block(uint32_t block)
+{
+       int b;
+
+       for (b = 0; b < AO_NUM_BUF; b++) {
+               struct ao_bufio *bufio = &ao_bufio[b];
+               if (bufio->block == block) {
+                       DBG ("Found existing buffer %d (seqno %d)\n",
+                               ao_bufio_to_num(bufio), bufio->seqno);
+                       return bufio;
+               }
+       }
+       return NULL;
+}
+
+/*
+ * Find the least recently used idle buffer
+ */
+static struct ao_bufio *
+ao_bufio_find_idle(void)
+{
+       int b;
+       struct ao_bufio *oldest = NULL;
+
+       for (b = 0; b < AO_NUM_BUF; b++) {
+               struct ao_bufio *bufio = &ao_bufio[b];
+               if (!bufio->busy)
+                       if (!oldest || ao_seqno_older(bufio->seqno, oldest->seqno))
+                               oldest = bufio;
+       }
+       if (oldest)
+               DBG ("Using idle buffer %d (seqno %d)\n",
+                       ao_bufio_to_num(oldest), oldest->seqno);
+       return oldest;
+}
+
+/*
+ * Return a pointer to a buffer containing
+ * the contents of the specified block
+ */
+uint8_t *
+ao_bufio_get(uint32_t block)
+{
+       struct ao_bufio *bufio;
+       uint8_t *buf = NULL;
+
+       ao_bufio_lock();
+       bufio = ao_bufio_find_block(block);
+       if (!bufio) {
+               bufio = ao_bufio_find_idle();
+               if (bufio) {
+                       ao_bufio_write(bufio);
+                       bufio->block = block;
+                       DBG ("read buffer\n");
+                       if (!ao_bufio_read(bufio)) {
+                               bufio->block = 0xffffffff;
+                               bufio = NULL;
+                       }
+               }
+       }
+       if (bufio) {
+               bufio->busy++;
+               buf = ao_bufio_to_buf(bufio);
+       }
+       ao_bufio_unlock();
+       return buf;
+}
+
+/*
+ * Release a buffer, marking it dirty
+ * if it has been written to
+ */
+void
+ao_bufio_put(uint8_t *buf, uint8_t write)
+{
+       struct ao_bufio *bufio;
+
+       ao_bufio_lock();
+       bufio = ao_buf_to_bufio(buf);
+       
+       DBG ("idle buffer %d write %d\n", ao_bufio_to_num(bufio), write);
+       bufio->dirty |= write;
+       if (!--bufio->busy) {
+               bufio->seqno = ao_seqno_next();
+               DBG ("not busy, seqno %d\n", bufio->seqno);
+       }
+       ao_bufio_unlock();
+}
+
+/*
+ * Flush a single buffer immediately. Useful
+ * if write order is important
+ */
+void
+ao_bufio_flush_one(uint8_t *buf)
+{
+       ao_bufio_lock();
+       ao_bufio_write(ao_buf_to_bufio(buf));
+       ao_bufio_unlock();
+}
+
+/*
+ * Flush all buffers to storage
+ */
+void
+ao_bufio_flush(void)
+{
+       int     b;
+
+       ao_bufio_lock();
+       for (b = 0; b < AO_NUM_BUF; b++)
+               ao_bufio_write(&ao_bufio[b]);
+       ao_bufio_unlock();
+}
+
+static void
+ao_bufio_test_read(void)
+{
+       uint8_t *buf;
+       ao_cmd_decimal();
+       if (ao_cmd_status != ao_cmd_success)
+               return;
+       if ((buf = ao_bufio_get(ao_cmd_lex_u32))) {
+               int i;
+               for (i = 0; i < 512; i++) {
+                       printf (" %02x", buf[i]);
+                       if ((i & 0xf) == 0xf)
+                               printf("\n");
+               }
+               ao_bufio_put(buf, 0);
+       }
+}
+
+static const struct ao_cmds ao_bufio_cmds[] = {
+       { ao_bufio_test_read,   "q\0Test bufio read" },
+       { 0, NULL },
+};
+
+void
+ao_bufio_init(void)
+{
+       int b;
+
+       for (b = 0; b < AO_NUM_BUF; b++)
+               ao_bufio[b].block = 0xffffffff;
+       ao_sdcard_init();
+
+       ao_cmd_register(&ao_bufio_cmds[0]);
+}
diff --git a/src/drivers/ao_bufio.h b/src/drivers/ao_bufio.h
new file mode 100644 (file)
index 0000000..c3bee90
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2013 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_BUFIO_H_
+#define _AO_BUFIO_H_
+
+uint8_t *
+ao_bufio_get(uint32_t block);
+
+void
+ao_bufio_put(uint8_t *buf, uint8_t write);
+
+void
+ao_bufio_flush_one(uint8_t *buf);
+
+void
+ao_bufio_flush(void);
+
+void
+ao_bufio_init(void);
+
+#endif /* _AO_BUFIO_H_ */
diff --git a/src/drivers/ao_fat.c b/src/drivers/ao_fat.c
new file mode 100644 (file)
index 0000000..a147616
--- /dev/null
@@ -0,0 +1,674 @@
+/*
+ * Copyright © 2013 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef AO_FAT_TEST
+#include "ao.h"
+#endif
+
+#include "ao_fat.h"
+#include "ao_bufio.h"
+
+/* Partition information, block numbers */
+
+static uint8_t partition_type;
+static uint32_t        partition_start, partition_end;
+
+/* 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 uint32_t        fat_start;
+static uint32_t root_start;
+static uint32_t data_start;
+
+static uint32_t
+get_u32(uint8_t *base)
+{
+       return ((uint32_t) base[0] |
+               ((uint32_t) base[1] << 8) |
+               ((uint32_t) base[2] << 16) |
+               ((uint32_t) base[3] << 24));
+}
+
+static void
+put_u32(uint8_t *base, uint32_t value)
+{
+       base[0] = value;
+       base[1] = value >> 8;
+       base[2] = value >> 16;
+       base[3] = value >> 24;
+}
+
+static uint16_t
+get_u16(uint8_t *base)
+{
+       return ((uint16_t) base[0] | ((uint16_t) base[1] << 8));
+}
+
+static void
+put_u16(uint8_t *base, uint16_t value)
+{
+       base[0] = value;
+       base[1] = value >> 8;
+}
+
+static uint8_t
+ao_fat_cluster_valid(uint16_t cluster)
+{
+       return (2 <= cluster && cluster < 0xfff0);
+}
+
+/* Start using a block */
+static uint8_t *
+ao_fat_block_get(uint32_t block)
+{
+       block += partition_start;
+       if (block >= partition_end)
+               return NULL;
+       return ao_bufio_get(block);
+}
+
+/* Finish using a block, 'w' is 1 if modified */
+#define ao_fat_block_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 * 0x20;
+       uint32_t        sector = byte >> 9;
+       uint16_t        offset = byte & 0x1ff;
+       uint8_t         *buf;
+
+       buf = ao_fat_block_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 * 0x20) & 0x1ff);
+       uint8_t         *buf = root - offset;
+
+       ao_fat_block_put(buf, write);
+}
+
+/* Get the next cluster entry in the chain */
+static uint16_t
+ao_fat_entry_read(uint16_t cluster)
+{
+       uint32_t        sector;
+       uint16_t        offset;
+       uint8_t         *buf;
+       uint16_t        ret;
+
+       if (!ao_fat_cluster_valid(cluster))
+               return 0xfff7;
+
+       cluster -= 2;
+       sector = cluster >> 8;
+       offset = (cluster << 1) & 0x1ff;
+       buf = ao_fat_block_get(fat_start + sector);
+       if (!buf)
+               return 0;
+       ret = buf[offset] | (buf[offset+1] << 8);
+       ao_fat_block_put(buf, 0);
+       return ret;
+}
+
+static uint16_t
+ao_fat_entry_replace(uint16_t  cluster, uint16_t new_value)
+{
+       uint32_t        sector;
+       uint16_t        offset;
+       uint8_t         *buf;
+       uint16_t        ret;
+       uint8_t         other_fats;
+
+       if (!ao_fat_cluster_valid(cluster))
+               return 0;
+
+       cluster -= 2;
+       sector = cluster >> 8;
+       offset = (cluster << 1) & 0x1ff;
+       buf = ao_fat_block_get(fat_start + sector);
+       if (!buf)
+               return 0;
+       ret = get_u16(buf + offset);
+       put_u16(buf + offset, new_value);
+       ao_fat_block_put(buf, 1);
+       for (other_fats = 1; other_fats < number_fat; other_fats++) {
+               buf = ao_fat_block_get(fat_start + other_fats * sectors_per_fat);
+               if (buf) {
+                       put_u16(buf + offset, new_value);
+                       ao_fat_block_put(buf, 1);
+               }
+       }
+       return ret;
+       
+}
+
+static void
+ao_fat_clear_cluster_chain(uint16_t cluster)
+{
+       while (ao_fat_cluster_valid(cluster))
+               cluster = ao_fat_entry_replace(cluster, 0x0000);
+}
+
+static uint16_t
+ao_fat_cluster_seek(uint16_t cluster, uint16_t distance)
+{
+       while (distance) {
+               cluster = ao_fat_entry_read(cluster);
+               if (!ao_fat_cluster_valid(cluster))
+                       break;
+               distance--;
+       }
+       return cluster;
+}
+
+static uint32_t
+ao_fat_sector_seek(uint16_t cluster, uint32_t sector)
+{
+       cluster = ao_fat_cluster_seek(cluster, sector / sectors_per_cluster);
+       if (!ao_fat_cluster_valid(cluster))
+               return 0xffffffff;
+       return data_start + (cluster-2) * sectors_per_cluster + sector % sectors_per_cluster;
+}
+
+/* Load the boot block and find the first partition */
+static uint8_t
+ao_fat_setup_partition(void)
+{
+       uint8_t *mbr;
+       uint8_t *partition;
+       uint32_t partition_size;
+
+       mbr = ao_bufio_get(0);
+       if (!mbr)
+               return 0;
+
+       /* Check the signature */
+       if (mbr[0x1fe] != 0x55 || mbr[0x1ff] != 0xaa) {
+               printf ("Invalid MBR signature %02x %02x\n",
+                       mbr[0x1fe], mbr[0x1ff]);
+               ao_bufio_put(mbr, 0);
+               return 0;
+       }
+
+       /* Just use the first partition */
+       partition = &mbr[0x1be];
+       
+       partition_type = partition[4];
+       switch (partition_type) {
+       case 4:         /* FAT16 up to 32M */
+       case 6:         /* FAT16 over 32M */
+               break;
+       case 0x0b:      /* FAT32 up to 2047GB */
+       case 0x0c:      /* FAT32 LBA */
+               break;
+       default:
+               printf ("Invalid partition type %02x\n", partition_type);
+               ao_bufio_put(mbr, 0);
+               return 0;
+       }
+
+       partition_start = get_u32(partition+8);
+       partition_size = get_u32(partition+12);
+       if (partition_size == 0) {
+               printf ("Zero-sized partition\n");
+               ao_bufio_put(mbr, 0);
+               return 0;
+       }
+       partition_end = partition_start + partition_size;
+       printf ("Partition type %02x start %08x end %08x\n",
+               partition_type, partition_start, partition_end);
+       ao_bufio_put(mbr, 0);
+       return 1;
+}
+       
+static uint8_t
+ao_fat_setup_fs(void)
+{
+       uint8_t *boot = ao_fat_block_get(0);
+
+       if (!boot)
+               return 0;
+
+       /* Check the signature */
+       if (boot[0x1fe] != 0x55 || boot[0x1ff] != 0xaa) {
+               printf ("Invalid BOOT signature %02x %02x\n",
+                       boot[0x1fe], boot[0x1ff]);
+               ao_bufio_put(boot, 0);
+               return 0;
+       }
+
+       /* Check the sector size */
+       if (get_u16(boot + 0xb) != 0x200) {
+               printf ("Invalid sector size %d\n",
+                       get_u16(boot + 0xb));
+               ao_bufio_put(boot, 0);
+               return 0;
+       }
+
+       sectors_per_cluster = boot[0xd];
+       bytes_per_cluster = sectors_per_cluster << 9;
+       reserved_sector_count = get_u16(boot+0xe);
+       number_fat = boot[0x10];
+       root_entries = get_u16(boot + 0x11);
+       sectors_per_fat = get_u16(boot+0x16);
+
+       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);
+       printf ("root entries %d\n", root_entries);
+       printf ("sectors per fat %d\n", sectors_per_fat);
+
+       fat_start = reserved_sector_count;;
+       root_start = fat_start + number_fat * sectors_per_fat;
+       data_start = root_start + ((root_entries * 0x20 + 0x1ff) >> 9);
+
+       printf ("fat  start %d\n", fat_start);
+       printf ("root start %d\n", root_start);
+       printf ("data start %d\n", data_start);
+
+       return 1;
+}
+
+static uint8_t
+ao_fat_setup(void)
+{
+       if (!ao_fat_setup_partition())
+               return 0;
+       if (!ao_fat_setup_fs())
+               return 0;
+       return 1;
+}
+
+/*
+ * Low-level directory operations
+ */
+
+/*
+ * Basic file operations
+ */
+
+static struct ao_fat_dirent    ao_file_dirent;
+static uint32_t                ao_file_offset;
+
+static uint32_t
+ao_file_offset_to_sector(uint32_t offset)
+{
+       if (offset > ao_file_dirent.size)
+               return 0xffffffff;
+       return ao_fat_sector_seek(ao_file_dirent.cluster, offset >> 9);
+}
+
+uint8_t
+ao_fat_open(char name[11])
+{
+       uint16_t                entry = 0;
+       struct ao_fat_dirent    dirent;
+
+       while (ao_fat_readdir(&entry, &dirent)) {
+               if (!memcmp(name, dirent.name, 11)) {
+                       ao_file_dirent = dirent;
+                       ao_file_offset = 0;
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+
+
+static uint8_t
+ao_fat_set_size(uint32_t size)
+{
+       uint16_t        clear_cluster = 0;
+       uint8_t         *dent;
+       uint16_t        first_cluster;
+
+       first_cluster = ao_file_dirent.cluster;
+       printf ("set size to %d\n", size);
+       if (size == ao_file_dirent.size)
+               return 1;
+       if (size == 0) {
+               printf ("erase file\n");
+               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;
+
+                       printf("Remove %d clusters\n", old_num - new_num);
+                       /* 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);
+
+                       printf ("Last cluster is now %04x\n", last_cluster);
+                       /* 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;
+
+                       if (old_num)
+                               last_cluster = ao_fat_cluster_seek(ao_file_dirent.cluster, old_num - 1);
+                       else
+                               last_cluster = 0;
+
+                       need = new_num - old_num;
+                       printf ("Need %d clusters\n", need);
+                       /* See if there are enough free clusters in the file system */
+                       for (free = 2; need > 0 && (free - 2) < sectors_per_fat * 256; free++) {
+                               if (!ao_fat_entry_read(free)) {
+                                       printf ("\tCluster %04x available\n", free);
+                                       need--;
+                               }
+                       }
+                       /* Still need some, tell the user that we've failed */
+                       if (need) {
+                               printf ("File system full\n");
+                               return 0;
+                       }
+
+                       need = new_num - old_num;
+                       /* Now go allocate those clusters */
+                       for (free = 2; need > 0 && (free - 2) < sectors_per_fat * 256; free++) {
+                               if (!ao_fat_entry_read(free)) {
+                                       printf ("\tAllocate %04x\n", free);
+                                       if (last_cluster)
+                                               ao_fat_entry_replace(last_cluster, free);
+                                       else
+                                               first_cluster = free;
+                                       last_cluster = free;
+                                       need--;
+                               }
+                       }
+                       /* Mark the new end of the chain */
+                       ao_fat_entry_replace(last_cluster, 0xffff);
+               }
+       }
+
+       /* Deallocate clusters off the end of the file */
+       if (ao_fat_cluster_valid(clear_cluster)) {
+               printf ("Clear clusters starting with %04x\n", clear_cluster);
+               ao_fat_clear_cluster_chain(clear_cluster);
+       }
+
+       dent = ao_fat_root_get(ao_file_dirent.entry);
+       if (!dent)
+               return 0;
+       put_u32(dent + 0x1c, size);
+       put_u16(dent + 0x1a, first_cluster);
+       ao_fat_root_put(dent, ao_file_dirent.entry, 1);
+       ao_file_dirent.size = size;
+       ao_file_dirent.cluster = first_cluster;
+       return 1;
+}
+
+uint8_t
+ao_fat_creat(char name[11])
+{
+       uint16_t        entry;
+
+       if (ao_fat_open(name))
+               return ao_fat_set_size(0);
+
+       for (entry = 0; entry < root_entries; entry++) {
+               uint8_t *dent = ao_fat_root_get(entry);
+
+               if (dent[0] == AO_FAT_DENT_EMPTY ||
+                   dent[0] == AO_FAT_DENT_END) {
+                       memmove(dent, name, 11);
+                       dent[0x0b] = 0x00;
+                       dent[0x0c] = 0x00;
+                       dent[0x0d] = 0x00;
+                       /* XXX fix time */
+                       put_u16(dent + 0x0e, 0);
+                       /* XXX fix date */
+                       put_u16(dent + 0x10, 0);
+                       /* XXX fix date */
+                       put_u16(dent + 0x12, 0);
+                       /* XXX FAT32 high cluster bytes */
+                       put_u16(dent + 0x14, 0);
+                       /* XXX fix time */
+                       put_u16(dent + 0x16, 0);
+                       /* XXX fix date */
+                       put_u16(dent + 0x18, 0);
+                       /* cluster number */
+                       put_u16(dent + 0x1a, 0);
+                       /* size */
+                       put_u32(dent + 0x1c, 0);
+                       ao_fat_root_put(dent, entry, 1);
+                       return ao_fat_open(name);
+               }
+       }
+       return 0;
+}
+
+void
+ao_fat_close(void)
+{
+       memset(&ao_file_dirent, '\0', sizeof (struct ao_fat_dirent));
+       ao_bufio_flush();
+}
+
+int
+ao_fat_read(uint8_t *dest, int len)
+{
+       uint32_t        sector;
+       uint16_t        this_time;
+       uint16_t        offset;
+       uint8_t         *buf;
+       int             ret = 0;
+
+       if (ao_file_offset + len > ao_file_dirent.size)
+               len = ao_file_dirent.size - ao_file_offset;
+
+       while (len) {
+               offset = ao_file_offset & 0x1ff;
+               if (offset + len < 512)
+                       this_time = len;
+               else
+                       this_time = 512 - offset;
+
+               sector = ao_file_offset_to_sector(ao_file_offset);
+               if (sector == 0xffffffff)
+                       break;
+               buf = ao_fat_block_get(sector);
+               if (!buf)
+                       break;
+               memcpy(dest, buf + offset, this_time);
+               ao_fat_block_put(buf, 0);
+
+               ret += this_time;
+               len -= this_time;
+               dest += this_time;
+               ao_file_offset += this_time;
+       }
+       return ret;
+}
+
+int
+ao_fat_write(uint8_t *src, int len)
+{
+       uint32_t        sector;
+       uint16_t        this_time;
+       uint16_t        offset;
+       uint8_t         *buf;
+       int             ret = 0;
+
+       if (ao_file_offset + len > ao_file_dirent.size) {
+               if (!ao_fat_set_size(ao_file_offset + len))
+                       return 0;
+       }
+
+       while (len) {
+               offset = ao_file_offset & 0x1ff;
+               if (offset + len < 512)
+                       this_time = len;
+               else
+                       this_time = 512 - offset;
+
+               sector = ao_file_offset_to_sector(ao_file_offset);
+               if (sector == 0xffffffff)
+                       break;
+               buf = ao_fat_block_get(sector);
+               if (!buf)
+                       break;
+               memcpy(buf + offset, src, this_time);
+               ao_fat_block_put(buf, 1);
+
+               ret += this_time;
+               len -= this_time;
+               src += this_time;
+               ao_file_offset += this_time;
+       }
+       return 0;
+}
+
+uint32_t
+ao_fat_seek(int32_t pos, uint8_t whence)
+{
+       switch (whence) {
+       case AO_FAT_SEEK_SET:
+               ao_file_offset = pos;
+               break;
+       case AO_FAT_SEEK_CUR:
+               ao_file_offset += pos;
+               break;
+       case AO_FAT_SEEK_END:
+               ao_file_offset = ao_file_dirent.size + pos;
+               break;
+       }
+       if (ao_file_offset > ao_file_dirent.size)
+               ao_fat_set_size(ao_file_offset);
+       return ao_file_offset;
+}
+
+uint8_t
+ao_fat_unlink(char name[11])
+{
+       uint16_t                entry = 0;
+       struct ao_fat_dirent    dirent;
+
+       while (ao_fat_readdir(&entry, &dirent)) {
+               if (memcmp(name, dirent.name, 11) == 0) {
+                       uint8_t *next;
+                       uint8_t *ent;
+                       uint8_t delete;
+                       ao_fat_clear_cluster_chain(dirent.cluster);
+                       next = ao_fat_root_get(dirent.entry + 1);
+                       if (next && next[0] != AO_FAT_DENT_END)
+                               delete = AO_FAT_DENT_EMPTY;
+                       else
+                               delete = AO_FAT_DENT_END;
+                       if (next)
+                               ao_fat_root_put(next, dirent.entry + 1, 0);
+                       ent = ao_fat_root_get(dirent.entry);
+                       if (ent) {
+                               memset(ent, '\0', 0x20);
+                               *ent = delete;
+                               ao_fat_root_put(ent, dirent.entry, 1);
+                       }
+                       ao_bufio_flush();
+                       return 1;
+               }
+       }
+       return 0;
+}
+
+uint8_t
+ao_fat_rename(char old[11], char new[11])
+{
+       return 0;
+}
+
+uint8_t
+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[0] == AO_FAT_DENT_END) {
+                       ao_fat_root_put(dent, *entry, 0);
+                       return 0;
+               }
+               if (dent[0] != AO_FAT_DENT_EMPTY &&
+                   (dent[0x0b] & (AO_FAT_FILE_DIRECTORY|AO_FAT_FILE_VOLUME_LABEL)) == 0)
+                       break;
+               ao_fat_root_put(dent, *entry, 0);
+               (*entry)++;
+       }
+       memcpy(dirent->name, dent, 11);
+       dirent->attr = dent[0xb];
+       dirent->size = get_u32(dent+0x1c);
+       dirent->cluster = get_u16(dent+0x1a);
+       dirent->entry = *entry;
+       ao_fat_root_put(dent, *entry, 0);
+       (*entry)++;
+       return 1;
+}
+
+static void
+ao_fat_list(void)
+{
+       uint16_t                entry = 0;
+       struct ao_fat_dirent    dirent;
+
+       while (ao_fat_readdir(&entry, &dirent)) {
+               printf ("%-8.8s.%-3.3s %02x %d\n",
+                       dirent.name, dirent.name + 8, dirent.attr, dirent.size);
+       }
+}
+
+static void
+ao_fat_test(void)
+{
+       ao_fat_setup();
+       ao_fat_list();
+}
+
+static const struct ao_cmds ao_fat_cmds[] = {
+       { ao_fat_test,  "F\0Test FAT" },
+       { 0, NULL },
+};
+
+void
+ao_fat_init(void)
+{
+       ao_bufio_init();
+       ao_cmd_register(&ao_fat_cmds[0]);
+}
+
diff --git a/src/drivers/ao_fat.h b/src/drivers/ao_fat.h
new file mode 100644 (file)
index 0000000..2bf6222
--- /dev/null
@@ -0,0 +1,73 @@
+/*
+ * Copyright © 2013 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_FAT_H_
+#define _AO_FAT_H_
+
+void
+ao_fat_init(void);
+
+#define AO_FAT_FILE_READ_ONLY          0x01
+#define AO_FAT_FILE_HIDDEN             0x02
+#define AO_FAT_FILE_SYSTEM             0x04
+#define AO_FAT_FILE_VOLUME_LABEL       0x08
+#define AO_FAT_FILE_DIRECTORY          0x10
+#define AO_FAT_FILE_ARCHIVE            0x20
+
+#define AO_FAT_DENT_EMPTY              0xe5
+#define AO_FAT_DENT_END                        0x00
+
+uint8_t
+ao_fat_open(char name[11]);
+
+uint8_t
+ao_fat_creat(char name[11]);
+
+void
+ao_fat_close(void);
+
+int
+ao_fat_read(uint8_t *dest, int len);
+
+int
+ao_fat_write(uint8_t *buf, int len);
+
+#define AO_FAT_SEEK_SET        0
+#define AO_FAT_SEEK_CUR        1
+#define AO_FAT_SEEK_END        2
+
+uint32_t
+bao_fat_seek(int32_t pos, uint8_t whence);
+
+uint8_t
+ao_fat_unlink(char name[11]);
+
+uint8_t
+ao_fat_rename(char old[11], char new[11]);
+
+struct ao_fat_dirent {
+       char            name[11];
+       uint8_t         attr;
+       uint32_t        size;
+       uint16_t        cluster;
+       uint16_t        entry;
+};
+
+uint8_t
+ao_fat_readdir(uint16_t *entry, struct ao_fat_dirent *dirent);
+
+#endif /* _AO_FAT_H_ */
diff --git a/src/drivers/ao_sdcard.c b/src/drivers/ao_sdcard.c
new file mode 100644 (file)
index 0000000..2174af1
--- /dev/null
@@ -0,0 +1,398 @@
+/*
+ * Copyright © 2013 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include "ao.h"
+#include "ao_sdcard.h"
+
+#define ao_sdcard_get_slow()           ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_250kHz)
+#define ao_sdcard_get()                        ao_spi_get(AO_SDCARD_SPI_BUS, AO_SPI_SPEED_FAST)
+#define ao_sdcard_put()                        ao_spi_put(AO_SDCARD_SPI_BUS)
+#define ao_sdcard_send_fixed(d,l)      ao_spi_send_fixed((d), (l), AO_SDCARD_SPI_BUS)
+#define ao_sdcard_send(d,l)            ao_spi_send((d), (l), AO_SDCARD_SPI_BUS)
+#define ao_sdcard_recv(d,l)            ao_spi_recv((d), (l), AO_SDCARD_SPI_BUS)
+#define ao_sdcard_select()             ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,0)
+#define ao_sdcard_deselect()           ao_gpio_set(AO_SDCARD_SPI_CS_PORT,AO_SDCARD_SPI_CS_PIN,AO_SDCARD_SPI_CS,1)
+
+
+static uint8_t initialized;
+static uint8_t present;
+static uint8_t mutex;
+static enum ao_sdtype sdtype;
+
+#define ao_sdcard_lock()       ao_mutex_get(&mutex)
+#define ao_sdcard_unlock()     ao_mutex_put(&mutex)
+
+#if 0
+#define DBG(...) printf(__VA_ARGS__)
+#else
+#define DBG(...)
+#endif
+
+/*
+ * Send an SD command and await the status reply
+ */
+
+static uint8_t
+ao_sdcard_send_cmd(uint8_t cmd, uint32_t arg)
+{
+       uint8_t data[6];
+       uint8_t reply;
+       int i;
+
+       DBG ("\tsend_cmd %d arg %08x\n", cmd, arg);
+       if (cmd != SDCARD_GO_IDLE_STATE) {
+               for (i = 0; i < SDCARD_CMD_TIMEOUT; i++) {
+                       ao_sdcard_recv(&reply, 1);
+                       if (reply == 0xff)
+                               break;
+               }
+               if (i == SDCARD_CMD_TIMEOUT)
+                       return SDCARD_STATUS_TIMEOUT;
+       }
+       
+       data[0] = cmd & 0x3f | 0x40;
+       data[1] = arg >> 24;
+       data[2] = arg >> 16;
+       data[3] = arg >> 8;
+       data[4] = arg;
+       if (cmd == SDCARD_GO_IDLE_STATE)
+               data[5] = 0x95; /* Valid for 0 arg */
+       else if (cmd == SDCARD_SEND_IF_COND)
+               data[5] = 0x87; /* Valid for 0x1aa arg */
+       else
+               data[5] = 0xff; /* no CRC */
+       ao_sdcard_send(data, 6);
+
+       /* The first reply byte will be the status,
+        * which must have the high bit clear
+        */
+       for (i = 0; i < SDCARD_CMD_TIMEOUT; i++) {
+               ao_sdcard_recv(&reply, 1);
+               DBG ("\t\tgot byte %02x\n", reply);
+               if ((reply & 0x80) == 0)
+                       return reply;
+       }
+       return SDCARD_STATUS_TIMEOUT;
+}
+
+/*
+ * Retrieve any reply, discarding the trailing CRC byte
+ */
+static void
+ao_sdcard_recv_reply(uint8_t *reply, int len)
+{
+       uint8_t discard;
+
+       if (len)
+               ao_sdcard_recv(reply, len);
+       /* trailing byte */
+       ao_sdcard_recv(&discard, 1);
+}
+
+/*
+ * Wait while the card is busy. The
+ * card will return a stream of 0xff
+ * until it isn't busy anymore
+ */
+static void
+ao_sdcard_wait_busy(void)
+{
+       uint8_t v;
+
+       do {
+               ao_sdcard_recv(&v, 1);
+       } while (v != 0xff);
+       ao_sdcard_send_fixed(0xff, 1);
+}
+
+static uint8_t
+ao_sdcard_go_idle_state(void)
+{
+       uint8_t ret;
+
+       DBG ("go_idle_state\n");
+       ao_sdcard_select();
+       ret = ao_sdcard_send_cmd(SDCARD_GO_IDLE_STATE, 0);
+       ao_sdcard_recv_reply(NULL, 0);
+       ao_sdcard_deselect();
+       DBG ("\tgo_idle_state status %02x\n", ret);
+       return ret;
+}
+
+static uint8_t
+ao_sdcard_send_op_cond(void)
+{
+       uint8_t ret;
+
+       DBG ("send_op_cond\n");
+       ao_sdcard_select();
+       ret = ao_sdcard_send_cmd(SDCARD_SEND_OP_COND, 0);
+       ao_sdcard_recv_reply(NULL, 0);
+       ao_sdcard_deselect();
+       DBG ("\tsend_op_cond %02x\n", ret);
+       return ret;
+}
+
+static uint8_t
+ao_sdcard_send_if_cond(uint32_t arg, uint8_t send_if_cond_response[4])
+{
+       uint8_t ret;
+
+       DBG ("send_if_cond\n");
+       ao_sdcard_select();
+       ret = ao_sdcard_send_cmd(SDCARD_SEND_IF_COND, arg);
+       if (ret != SDCARD_STATUS_IDLE_STATE) {
+               DBG ("\tsend_if_cond failed %02x\n", ret);
+               return ret;
+       }
+       ao_sdcard_recv_reply(send_if_cond_response, 4);
+       DBG ("send_if_cond status %02x response %02x %02x %02x %02x\n",
+               ret,
+               send_if_cond_response[0],
+               send_if_cond_response[1],
+               send_if_cond_response[2],
+               send_if_cond_response[3]);
+       ao_sdcard_deselect();
+       return ret;
+}
+
+static uint8_t
+ao_sdcard_set_blocklen(uint32_t blocklen)
+{
+       uint8_t ret;
+
+       DBG ("set_blocklen %d\n", blocklen);
+       ao_sdcard_select();
+       ret = ao_sdcard_send_cmd(SDCARD_SET_BLOCKLEN, blocklen);
+       ao_sdcard_recv_reply(NULL, 0);
+       if (ret != SDCARD_STATUS_READY_STATE)
+               DBG ("\tsend_if_cond failed %02x\n", ret);
+       return ret;
+       
+}
+
+static uint8_t
+ao_sdcard_app_cmd(void)
+{
+       uint8_t ret;
+
+       DBG ("app_cmd\n");
+       ao_sdcard_select();
+       ret = ao_sdcard_send_cmd(SDCARD_APP_CMD, 0);
+       ao_sdcard_recv_reply(NULL, 0);
+       ao_sdcard_deselect();
+       DBG ("\tapp_cmd status %02x\n");
+       return ret;
+}
+
+static uint8_t
+ao_sdcard_app_send_op_cond(uint32_t arg)
+{
+       uint8_t ret;
+
+       ret = ao_sdcard_app_cmd();
+       if (ret != SDCARD_STATUS_IDLE_STATE)
+               return ret;
+       DBG("send_op_comd\n");
+       ao_sdcard_select();
+       ret = ao_sdcard_send_cmd(SDCARD_APP_SEND_OP_COMD, arg);
+       ao_sdcard_recv_reply(NULL, 0);
+       ao_sdcard_deselect();
+       DBG ("\tapp_send_op_cond status %02x\n", ret);
+       return ret;
+}
+
+static uint8_t
+ao_sdcard_read_ocr(uint8_t read_ocr_response[4])
+{
+       uint8_t ret;
+
+       DBG ("read_ocr\n");
+       ao_sdcard_select();
+       ret = ao_sdcard_send_cmd(SDCARD_READ_OCR, 0);
+       if (ret != SDCARD_STATUS_READY_STATE)
+               DBG ("\tread_ocr failed %02x\n", ret);
+       else {
+               ao_sdcard_recv_reply(read_ocr_response, 4);
+               DBG ("\tread_ocr status %02x response %02x %02x %02x %02x\n", ret,
+                       read_ocr_response[0], read_ocr_response[1],
+                       read_ocr_response[2], read_ocr_response[3]);
+       }
+       ao_sdcard_deselect();
+       return ret;
+}
+
+static void
+ao_sdcard_setup(void)
+{
+       int     i;
+       uint8_t ret;
+       uint8_t response[10];
+
+       DBG ("Testing sdcard\n");
+
+       ao_sdcard_get_slow();
+       /*
+        * min 74 clocks with CS high
+        */
+       ao_sdcard_send_fixed(0xff, 10);
+
+       ao_delay(AO_MS_TO_TICKS(10));
+
+       /* Reset the card and get it into SPI mode */
+
+       for (i = 0; i < SDCARD_IDLE_WAIT; i++) {
+               if (ao_sdcard_go_idle_state() == SDCARD_STATUS_IDLE_STATE)
+                       break;
+       }
+       if (i == SDCARD_IDLE_WAIT)
+               goto bail;
+
+       /* Figure out what kind of card we have */
+
+       sdtype = ao_sdtype_unknown;
+
+       if (ao_sdcard_send_if_cond(0x1aa, response) == SDCARD_STATUS_IDLE_STATE) {
+               uint32_t        arg = 0;
+               uint8_t         sdver2 = 0;
+
+               /* Check for SD version 2 */
+               if ((response[2] & 0xf) == 1 && response[3] == 0xaa) {
+                       arg = 0x40000000;
+                       sdver2 = 1;
+               }
+
+               for (i = 0; i < SDCARD_IDLE_WAIT; i++) {
+                       ret = ao_sdcard_app_send_op_cond(arg);
+                       if (ret != SDCARD_STATUS_IDLE_STATE)
+                               break;
+               }
+               if (ret != SDCARD_STATUS_READY_STATE) {
+                       /* MMC */
+                       for (i = 0; i < SDCARD_IDLE_WAIT; i++) {
+                               ret = ao_sdcard_send_op_cond();
+                               if (ret != SDCARD_STATUS_IDLE_STATE)
+                                       break;
+                       }
+                       if (ret != SDCARD_STATUS_READY_STATE)
+                               goto bail;
+                       sdtype = ao_sdtype_mmc3;
+               } else {
+                       /* SD */
+                       if (sdver2 != 0) {
+                               ret = ao_sdcard_read_ocr(response);
+                               if (ret != SDCARD_STATUS_READY_STATE)
+                                       goto bail;
+                               if ((response[0] & 0xc0) == 0xc0)
+                                       sdtype = ao_sdtype_sd2block;
+                               else
+                                       sdtype = ao_sdtype_sd2byte;
+                       } else {
+                               sdtype = ao_sdtype_sd1;
+                       }
+               }
+
+               /* For everything but SDHC cards, set the block length */
+               if (sdtype != ao_sdtype_sd2block) {
+                       ret = ao_sdcard_set_blocklen(512);
+                       if (ret != SDCARD_STATUS_READY_STATE)
+                               DBG ("set_blocklen failed, ignoring\n");
+               }
+       }
+
+       DBG ("SD card detected, type %d\n", sdtype);
+bail:
+       ao_sdcard_put();
+}
+
+static uint8_t
+ao_sdcard_wait_block_start(void)
+{
+       int     i;
+       uint8_t v;
+
+       DBG ("\twait_block_start\n");
+       for (i = 0; i < SDCARD_BLOCK_TIMEOUT; i++) {
+               ao_sdcard_recv(&v, 1);
+               DBG("\t\trecv %02x\n", v);
+               if (v != 0xff)
+                       break;
+       }
+       return v;
+}
+
+/*
+ * Read a block of 512 bytes from the card
+ */
+uint8_t
+ao_sdcard_read_block(uint32_t block, uint8_t *data)
+{
+       uint8_t ret;
+       uint8_t crc[2];
+
+       ao_sdcard_lock();
+       if (!initialized) {
+               ao_sdcard_setup();
+               initialized = 1;
+               if (sdtype != ao_sdtype_unknown)
+                       present = 1;
+       }
+       if (!present) {
+               ao_sdcard_unlock();
+               return 0;
+       }
+       if (sdtype != ao_sdtype_sd2block)
+               block <<= 9;
+       ao_sdcard_get();
+       ao_sdcard_select();
+       ret = ao_sdcard_send_cmd(SDCARD_READ_BLOCK, block);
+       ao_sdcard_recv_reply(NULL, 0);
+       if (ret != SDCARD_STATUS_READY_STATE)
+               goto bail;
+
+       if (ao_sdcard_wait_block_start() != 0xfe) {
+               ret = 0x3f;
+               goto bail;
+       }
+
+       ao_sdcard_recv(data, 512);
+       ao_sdcard_recv(crc, 2);
+bail:
+       ao_sdcard_deselect();
+       ao_sdcard_put();
+       ao_sdcard_unlock();
+       return ret == SDCARD_STATUS_READY_STATE;
+}
+
+/*
+ * Write a block of 512 bytes to the card
+ */
+uint8_t
+ao_sdcard_write_block(uint32_t block, uint8_t *data)
+{
+       /* Not doing anything until the file system code seems reasonable
+        */
+       return 1;
+}
+
+void
+ao_sdcard_init(void)
+{
+       ao_spi_init_cs(AO_SDCARD_SPI_CS_PORT, (1 << AO_SDCARD_SPI_CS_PIN));
+}
+
+
diff --git a/src/drivers/ao_sdcard.h b/src/drivers/ao_sdcard.h
new file mode 100644 (file)
index 0000000..b9f737c
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright © 2013 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#ifndef _AO_SDCARD_H_
+#define _AO_SDCARD_H_
+
+uint8_t
+ao_sdcard_read_block(uint32_t block, uint8_t *data);
+
+uint8_t
+ao_sdcard_write_block(uint32_t block, uint8_t *data);
+
+void
+ao_sdcard_init(void);
+
+/* Commands */
+#define SDCARD_GO_IDLE_STATE           0
+#define SDCARD_SEND_OP_COND            1
+#define SDCARD_SEND_IF_COND            8
+#define SDCARD_SEND_CSD                        9
+#define SDCARD_SEND_CID                        10
+#define SDCARD_SEND_STATUS             13
+#define SDCARD_SET_BLOCKLEN            16
+#define SDCARD_READ_BLOCK              17
+#define SDCARD_WRITE_BLOCK             24
+#define SDCARD_WRITE_MULTIPLE_BLOCK    25
+#define SDCARD_ERASE_WR_BLK_START      32
+#define SDCARD_ERASE_WR_BLK_END                33
+#define SDCARD_ERASE                   38
+#define SDCARD_APP_CMD                 55
+#define SDCARD_READ_OCR                        58
+
+/* App commands */
+#define SDCARD_APP_SET_WR_BLK_ERASE_COUNT      23
+#define SDCARD_APP_SEND_OP_COMD                        41
+
+/* Status */
+#define SDCARD_STATUS_READY_STATE      0
+#define SDCARD_STATUS_IDLE_STATE       1
+#define SDCARD_STATUS_ILLEGAL_COMMAND  4
+#define SDCARD_STATUS_TIMEOUT          0xff
+
+#define SDCARD_DATA_START_BLOCK                0xfe
+#define SDCARD_STOP_TRAN_TOKEN         0xfd
+#define SDCARD_WRITE_MULTIPLE_TOKEN    0xfc
+#define SDCARD_DATA_RES_MASK           0x1f
+#define SDCARD_DATA_RES_ACCEPTED       0x05
+
+#define SDCARD_CMD_TIMEOUT             100
+#define SDCARD_IDLE_WAIT               100
+#define SDCARD_BLOCK_TIMEOUT           100
+
+enum ao_sdtype {
+       ao_sdtype_unknown,
+       ao_sdtype_mmc3,
+       ao_sdtype_sd1,
+       ao_sdtype_sd2byte,
+       ao_sdtype_sd2block,
+};
+
+#endif /* _AO_SDCARD_H_ */
index fccc793787da3bcfe92fb3ee1fd5aee64ede4015..96170cc196dadbb00d0e3b3849e231e9da3c681a 100644 (file)
@@ -2,7 +2,7 @@ vpath % ..:../core:../drivers:../util:../micropeak
 
 PROGS=ao_flight_test ao_flight_test_baro ao_flight_test_accel ao_flight_test_noisy_accel ao_flight_test_mm \
        ao_gps_test ao_gps_test_skytraq ao_convert_test ao_convert_pa_test ao_fec_test \
-       ao_aprs_test ao_micropeak_test
+       ao_aprs_test ao_micropeak_test ao_fat_test
 
 INCS=ao_kalman.h ao_ms5607.h ao_log.h ao_data.h altitude-pa.h altitude.h
 
@@ -64,3 +64,6 @@ check: ao_fec_test ao_flight_test ao_flight_test_baro run-tests
 
 ao_micropeak_test: ao_micropeak_test.c ao_microflight.c ao_kalman.h
        cc $(CFLAGS) -o $@ ao_micropeak_test.c -lm
+
+ao_fat_test: ao_fat_test.c ao_fat.c ao_bufio.c
+       cc $(CFLAGS) -o $@ ao_fat_test.c
diff --git a/src/test/ao_fat_test.c b/src/test/ao_fat_test.c
new file mode 100644 (file)
index 0000000..22ac03a
--- /dev/null
@@ -0,0 +1,118 @@
+/*
+ * Copyright © 2013 Keith Packard <keithp@keithp.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <math.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#define AO_FAT_TEST
+
+void
+ao_mutex_get(uint8_t *mutex)
+{
+}
+
+void
+ao_mutex_put(uint8_t *mutex)
+{
+}
+
+void
+ao_panic(uint8_t panic)
+{
+       printf ("panic %d\n", panic);
+       exit(1);
+}
+
+#define AO_PANIC_BUFIO 15
+
+#define ao_cmd_success 0
+
+uint8_t ao_cmd_status;
+uint32_t ao_cmd_lex_u32;
+
+void
+ao_cmd_decimal()
+{
+}
+
+#define ao_cmd_register(x)
+
+struct ao_cmds {
+       void            (*func)(void);
+       const char      *help;
+};
+
+int fs_fd;
+
+uint8_t
+ao_sdcard_read_block(uint32_t block, uint8_t *data)
+{
+       lseek(fs_fd, block * 512, 0);
+       return read(fs_fd, data, 512) == 512;
+}
+
+uint8_t
+ao_sdcard_write_block(uint32_t block, uint8_t *data)
+{
+       lseek(fs_fd, block * 512, 0);
+       return write(fs_fd, data, 512) == 512;
+}
+
+void
+ao_sdcard_init(void)
+{
+       fs_fd = open("fat.fs", 2);
+}
+
+#include "ao_bufio.c"
+#include "ao_fat.c"
+
+int
+main(int argc, char **argv)
+{
+       uint8_t data[15];
+       int     len;
+       ao_fat_init();
+       ao_fat_test();
+       if (ao_fat_open("DATALOG TXT")) {
+               printf ("DATALOG.TXT\n");
+               while ((len = ao_fat_read(data, sizeof (data))) > 0) {
+                       write(1, data, len);
+               }
+               ao_fat_close();
+//             ao_fat_unlink("DATALOG TXT");
+       }
+       if (ao_fat_open("NEWFILE TXT")) {
+               printf ("NEWFILE.TXT\n");
+               while ((len = ao_fat_read(data, sizeof (data))) > 0) {
+                       write(1, data, len);
+               }
+               ao_fat_close();
+       }
+       if (ao_fat_creat ("NEWFILE TXT")) {
+               for (len = 0; len < 4095; len++)
+                       ao_fat_write((uint8_t *) "hello, world!\n", 14);
+               ao_fat_close();
+       }
+       return 0;
+}