From e14834817f78a04b4d9b44a8373119dffd42c966 Mon Sep 17 00:00:00 2001 From: Keith Packard Date: Wed, 27 Mar 2013 01:12:33 -0700 Subject: [PATCH] altos: Add SDCARD and FAT16 filesystem support This adds a fairly primitive FAT16 file system implementation along with support for SD cards. Signed-off-by: Keith Packard --- src/drivers/ao_bufio.c | 298 ++++++++++++++++++ src/drivers/ao_bufio.h | 36 +++ src/drivers/ao_fat.c | 674 ++++++++++++++++++++++++++++++++++++++++ src/drivers/ao_fat.h | 73 +++++ src/drivers/ao_sdcard.c | 398 ++++++++++++++++++++++++ src/drivers/ao_sdcard.h | 75 +++++ src/test/Makefile | 5 +- src/test/ao_fat_test.c | 118 +++++++ 8 files changed, 1676 insertions(+), 1 deletion(-) create mode 100644 src/drivers/ao_bufio.c create mode 100644 src/drivers/ao_bufio.h create mode 100644 src/drivers/ao_fat.c create mode 100644 src/drivers/ao_fat.h create mode 100644 src/drivers/ao_sdcard.c create mode 100644 src/drivers/ao_sdcard.h create mode 100644 src/test/ao_fat_test.c diff --git a/src/drivers/ao_bufio.c b/src/drivers/ao_bufio.c new file mode 100644 index 00000000..9a5e801b --- /dev/null +++ b/src/drivers/ao_bufio.c @@ -0,0 +1,298 @@ +/* + * Copyright © 2013 Keith Packard + * + * 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 index 00000000..c3bee906 --- /dev/null +++ b/src/drivers/ao_bufio.h @@ -0,0 +1,36 @@ +/* + * Copyright © 2013 Keith Packard + * + * 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 index 00000000..a1476168 --- /dev/null +++ b/src/drivers/ao_fat.c @@ -0,0 +1,674 @@ +/* + * Copyright © 2013 Keith Packard + * + * 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 index 00000000..2bf6222e --- /dev/null +++ b/src/drivers/ao_fat.h @@ -0,0 +1,73 @@ +/* + * Copyright © 2013 Keith Packard + * + * 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 index 00000000..2174af1e --- /dev/null +++ b/src/drivers/ao_sdcard.c @@ -0,0 +1,398 @@ +/* + * Copyright © 2013 Keith Packard + * + * 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 index 00000000..b9f737c5 --- /dev/null +++ b/src/drivers/ao_sdcard.h @@ -0,0 +1,75 @@ +/* + * Copyright © 2013 Keith Packard + * + * 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_ */ diff --git a/src/test/Makefile b/src/test/Makefile index fccc7937..96170cc1 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -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 index 00000000..22ac03ad --- /dev/null +++ b/src/test/ao_fat_test.c @@ -0,0 +1,118 @@ +/* + * Copyright © 2013 Keith Packard + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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; +} -- 2.30.2