From: Olivier Matz Date: Thu, 26 Jun 2014 18:14:33 +0000 (+0200) Subject: import sd-reader_source_20120612 X-Git-Url: http://git.droids-corp.org/?a=commitdiff_plain;h=b1352af6f340a1b878d46ed15b51d30eb3d3c36a;p=protos%2Fimu.git import sd-reader_source_20120612 --- diff --git a/byteordering.c b/byteordering.c new file mode 100644 index 0000000..d1c67cd --- /dev/null +++ b/byteordering.c @@ -0,0 +1,110 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include "byteordering.h" + +/** + * \addtogroup byteordering + * + * Architecture-dependent handling of byte-ordering. + * + * @{ + */ +/** + * \file + * Byte-order handling implementation (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +#if DOXYGEN || SWAP_NEEDED + +/** + * \internal + * Swaps the bytes of a 16-bit integer. + * + * \param[in] i A 16-bit integer which to swap. + * \returns The swapped 16-bit integer. + */ +uint16_t swap16(uint16_t i) +{ + return SWAP16(i); +} + +/** + * \internal + * Swaps the bytes of a 32-bit integer. + * + * \param[in] i A 32-bit integer which to swap. + * \returns The swapped 32-bit integer. + */ +uint32_t swap32(uint32_t i) +{ + return SWAP32(i); +} + +#endif + +/** + * Reads a 16-bit integer from memory in little-endian byte order. + * + * \param[in] p Pointer from where to read the integer. + * \returns The 16-bit integer read from memory. + */ +uint16_t read16(const uint8_t* p) +{ + return (((uint16_t) p[1]) << 8) | + (((uint16_t) p[0]) << 0); +} + +/** + * Reads a 32-bit integer from memory in little-endian byte order. + * + * \param[in] p Pointer from where to read the integer. + * \returns The 32-bit integer read from memory. + */ +uint32_t read32(const uint8_t* p) +{ + return (((uint32_t) p[3]) << 24) | + (((uint32_t) p[2]) << 16) | + (((uint32_t) p[1]) << 8) | + (((uint32_t) p[0]) << 0); +} + +/** + * Writes a 16-bit integer into memory in little-endian byte order. + * + * \param[in] p Pointer where to write the integer to. + * \param[in] i The 16-bit integer to write. + */ +void write16(uint8_t* p, uint16_t i) +{ + p[1] = (uint8_t) ((i & 0xff00) >> 8); + p[0] = (uint8_t) ((i & 0x00ff) >> 0); +} + +/** + * Writes a 32-bit integer into memory in little-endian byte order. + * + * \param[in] p Pointer where to write the integer to. + * \param[in] i The 32-bit integer to write. + */ +void write32(uint8_t* p, uint32_t i) +{ + p[3] = (uint8_t) ((i & 0xff000000) >> 24); + p[2] = (uint8_t) ((i & 0x00ff0000) >> 16); + p[1] = (uint8_t) ((i & 0x0000ff00) >> 8); + p[0] = (uint8_t) ((i & 0x000000ff) >> 0); +} + +/** + * @} + */ + diff --git a/byteordering.h b/byteordering.h new file mode 100644 index 0000000..12fa571 --- /dev/null +++ b/byteordering.h @@ -0,0 +1,188 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef BYTEORDERING_H +#define BYTEORDERING_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup byteordering + * + * @{ + */ +/** + * \file + * Byte-order handling header (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +#define SWAP16(val) ((((uint16_t) (val)) << 8) | \ + (((uint16_t) (val)) >> 8) \ + ) +#define SWAP32(val) (((((uint32_t) (val)) & 0x000000ff) << 24) | \ + ((((uint32_t) (val)) & 0x0000ff00) << 8) | \ + ((((uint32_t) (val)) & 0x00ff0000) >> 8) | \ + ((((uint32_t) (val)) & 0xff000000) >> 24) \ + ) + +#if LITTLE_ENDIAN || __AVR__ +#define SWAP_NEEDED 0 +#elif BIG_ENDIAN +#define SWAP_NEEDED 1 +#else +#error "Endianess undefined! Please define LITTLE_ENDIAN=1 or BIG_ENDIAN=1." +#endif + +/** + * \def HTOL16(val) + * + * Converts a 16-bit integer from host byte order to little-endian byte order. + * + * Use this macro for compile time constants only. For variable values + * use the function htol16() instead. This saves code size. + * + * \param[in] val A 16-bit integer in host byte order. + * \returns The given 16-bit integer converted to little-endian byte order. + */ +/** + * \def HTOL32(val) + * + * Converts a 32-bit integer from host byte order to little-endian byte order. + * + * Use this macro for compile time constants only. For variable values + * use the function htol32() instead. This saves code size. + * + * \param[in] val A 32-bit integer in host byte order. + * \returns The given 32-bit integer converted to little-endian byte order. + */ +/** + * \def LTOH16(val) + * + * Converts a 16-bit integer from little-endian byte order to host byte order. + * + * Use this macro for compile time constants only. For variable values + * use the function ltoh16() instead. This saves code size. + * + * \param[in] val A 16-bit integer in little-endian byte order. + * \returns The given 16-bit integer converted to host byte order. + */ +/** + * \def LTOH32(val) + * + * Converts a 32-bit integer from little-endian byte order to host byte order. + * + * Use this macro for compile time constants only. For variable values + * use the function ltoh32() instead. This saves code size. + * + * \param[in] val A 32-bit integer in little-endian byte order. + * \returns The given 32-bit integer converted to host byte order. + */ + +#if SWAP_NEEDED +#define HTOL16(val) SWAP16(val) +#define HTOL32(val) SWAP32(val) +#define LTOH16(val) SWAP16(val) +#define LTOH32(val) SWAP32(val) +#else +#define HTOL16(val) (val) +#define HTOL32(val) (val) +#define LTOH16(val) (val) +#define LTOH32(val) (val) +#endif + +#if DOXYGEN + +/** + * Converts a 16-bit integer from host byte order to little-endian byte order. + * + * Use this function on variable values instead of the + * macro HTOL16(). This saves code size. + * + * \param[in] h A 16-bit integer in host byte order. + * \returns The given 16-bit integer converted to little-endian byte order. + */ +uint16_t htol16(uint16_t h); + +/** + * Converts a 32-bit integer from host byte order to little-endian byte order. + * + * Use this function on variable values instead of the + * macro HTOL32(). This saves code size. + * + * \param[in] h A 32-bit integer in host byte order. + * \returns The given 32-bit integer converted to little-endian byte order. + */ +uint32_t htol32(uint32_t h); + +/** + * Converts a 16-bit integer from little-endian byte order to host byte order. + * + * Use this function on variable values instead of the + * macro LTOH16(). This saves code size. + * + * \param[in] l A 16-bit integer in little-endian byte order. + * \returns The given 16-bit integer converted to host byte order. + */ +uint16_t ltoh16(uint16_t l); + +/** + * Converts a 32-bit integer from little-endian byte order to host byte order. + * + * Use this function on variable values instead of the + * macro LTOH32(). This saves code size. + * + * \param[in] l A 32-bit integer in little-endian byte order. + * \returns The given 32-bit integer converted to host byte order. + */ +uint32_t ltoh32(uint32_t l); + +#elif SWAP_NEEDED + +#define htol16(h) swap16(h) +#define htol32(h) swap32(h) +#define ltoh16(l) swap16(l) +#define ltoh32(l) swap32(l) + +#else + +#define htol16(h) (h) +#define htol32(h) (h) +#define ltoh16(l) (l) +#define ltoh32(l) (l) + +#endif + +uint16_t read16(const uint8_t* p); +uint32_t read32(const uint8_t* p); +void write16(uint8_t* p, uint16_t i); +void write32(uint8_t* p, uint32_t i); + +/** + * @} + */ + +#if SWAP_NEEDED +uint16_t swap16(uint16_t i); +uint32_t swap32(uint32_t i); +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fat.c b/fat.c new file mode 100644 index 0000000..e62f4cf --- /dev/null +++ b/fat.c @@ -0,0 +1,2551 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include "byteordering.h" +#include "partition.h" +#include "fat.h" +#include "fat_config.h" +#include "sd-reader_config.h" + +#include + +#if USE_DYNAMIC_MEMORY + #include +#endif + +/** + * \addtogroup fat FAT support + * + * This module implements FAT16/FAT32 read and write access. + * + * The following features are supported: + * - File names up to 31 characters long. + * - Unlimited depth of subdirectories. + * - Short 8.3 and long filenames. + * - Creating and deleting files. + * - Reading and writing from and to files. + * - File resizing. + * - File sizes of up to 4 gigabytes. + * + * @{ + */ +/** + * \file + * FAT implementation (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * \addtogroup fat_config FAT configuration + * Preprocessor defines to configure the FAT implementation. + */ + +/** + * \addtogroup fat_fs FAT access + * Basic functions for handling a FAT filesystem. + */ + +/** + * \addtogroup fat_file FAT file functions + * Functions for managing files. + */ + +/** + * \addtogroup fat_dir FAT directory functions + * Functions for managing directories. + */ + +/** + * @} + */ + +#define FAT16_CLUSTER_FREE 0x0000 +#define FAT16_CLUSTER_RESERVED_MIN 0xfff0 +#define FAT16_CLUSTER_RESERVED_MAX 0xfff6 +#define FAT16_CLUSTER_BAD 0xfff7 +#define FAT16_CLUSTER_LAST_MIN 0xfff8 +#define FAT16_CLUSTER_LAST_MAX 0xffff + +#define FAT32_CLUSTER_FREE 0x00000000 +#define FAT32_CLUSTER_RESERVED_MIN 0x0ffffff0 +#define FAT32_CLUSTER_RESERVED_MAX 0x0ffffff6 +#define FAT32_CLUSTER_BAD 0x0ffffff7 +#define FAT32_CLUSTER_LAST_MIN 0x0ffffff8 +#define FAT32_CLUSTER_LAST_MAX 0x0fffffff + +#define FAT_DIRENTRY_DELETED 0xe5 +#define FAT_DIRENTRY_LFNLAST (1 << 6) +#define FAT_DIRENTRY_LFNSEQMASK ((1 << 6) - 1) + +/* Each entry within the directory table has a size of 32 bytes + * and either contains a 8.3 DOS-style file name or a part of a + * long file name, which may consist of several directory table + * entries at once. + * + * multi-byte integer values are stored little-endian! + * + * 8.3 file name entry: + * ==================== + * offset length description + * 0 8 name (space padded) + * 8 3 extension (space padded) + * 11 1 attributes (FAT_ATTRIB_*) + * + * long file name (lfn) entry ordering for a single file name: + * =========================================================== + * LFN entry n + * ... + * LFN entry 2 + * LFN entry 1 + * 8.3 entry (see above) + * + * lfn entry: + * ========== + * offset length description + * 0 1 ordinal field + * 1 2 unicode character 1 + * 3 3 unicode character 2 + * 5 3 unicode character 3 + * 7 3 unicode character 4 + * 9 3 unicode character 5 + * 11 1 attribute (always 0x0f) + * 12 1 type (reserved, always 0) + * 13 1 checksum + * 14 2 unicode character 6 + * 16 2 unicode character 7 + * 18 2 unicode character 8 + * 20 2 unicode character 9 + * 22 2 unicode character 10 + * 24 2 unicode character 11 + * 26 2 cluster (unused, always 0) + * 28 2 unicode character 12 + * 30 2 unicode character 13 + * + * The ordinal field contains a descending number, from n to 1. + * For the n'th lfn entry the ordinal field is or'ed with 0x40. + * For deleted lfn entries, the ordinal field is set to 0xe5. + */ + +struct fat_header_struct +{ + offset_t size; + + offset_t fat_offset; + uint32_t fat_size; + + uint16_t sector_size; + uint16_t cluster_size; + + offset_t cluster_zero_offset; + + offset_t root_dir_offset; +#if FAT_FAT32_SUPPORT + cluster_t root_dir_cluster; +#endif +}; + +struct fat_fs_struct +{ + struct partition_struct* partition; + struct fat_header_struct header; + cluster_t cluster_free; +}; + +struct fat_file_struct +{ + struct fat_fs_struct* fs; + struct fat_dir_entry_struct dir_entry; + offset_t pos; + cluster_t pos_cluster; +}; + +struct fat_dir_struct +{ + struct fat_fs_struct* fs; + struct fat_dir_entry_struct dir_entry; + cluster_t entry_cluster; + uint16_t entry_offset; +}; + +struct fat_read_dir_callback_arg +{ + struct fat_dir_entry_struct* dir_entry; + uintptr_t bytes_read; +#if FAT_LFN_SUPPORT + uint8_t checksum; +#endif + uint8_t finished; +}; + +struct fat_usage_count_callback_arg +{ + cluster_t cluster_count; + uintptr_t buffer_size; +}; + +#if !USE_DYNAMIC_MEMORY +static struct fat_fs_struct fat_fs_handles[FAT_FS_COUNT]; +static struct fat_file_struct fat_file_handles[FAT_FILE_COUNT]; +static struct fat_dir_struct fat_dir_handles[FAT_DIR_COUNT]; +#endif + +static uint8_t fat_read_header(struct fat_fs_struct* fs); +static cluster_t fat_get_next_cluster(const struct fat_fs_struct* fs, cluster_t cluster_num); +static offset_t fat_cluster_offset(const struct fat_fs_struct* fs, cluster_t cluster_num); +static uint8_t fat_dir_entry_read_callback(uint8_t* buffer, offset_t offset, void* p); +#if FAT_LFN_SUPPORT +static uint8_t fat_calc_83_checksum(const uint8_t* file_name_83); +#endif + +static uint8_t fat_get_fs_free_16_callback(uint8_t* buffer, offset_t offset, void* p); +#if FAT_FAT32_SUPPORT +static uint8_t fat_get_fs_free_32_callback(uint8_t* buffer, offset_t offset, void* p); +#endif + +#if FAT_WRITE_SUPPORT +static cluster_t fat_append_clusters(struct fat_fs_struct* fs, cluster_t cluster_num, cluster_t count); +static uint8_t fat_free_clusters(struct fat_fs_struct* fs, cluster_t cluster_num); +static uint8_t fat_terminate_clusters(struct fat_fs_struct* fs, cluster_t cluster_num); +static uint8_t fat_clear_cluster(const struct fat_fs_struct* fs, cluster_t cluster_num); +static uintptr_t fat_clear_cluster_callback(uint8_t* buffer, offset_t offset, void* p); +static offset_t fat_find_offset_for_dir_entry(struct fat_fs_struct* fs, const struct fat_dir_struct* parent, const struct fat_dir_entry_struct* dir_entry); +static uint8_t fat_write_dir_entry(const struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry); +#if FAT_DATETIME_SUPPORT +static void fat_set_file_modification_date(struct fat_dir_entry_struct* dir_entry, uint16_t year, uint8_t month, uint8_t day); +static void fat_set_file_modification_time(struct fat_dir_entry_struct* dir_entry, uint8_t hour, uint8_t min, uint8_t sec); +#endif +#endif + +/** + * \ingroup fat_fs + * Opens a FAT filesystem. + * + * \param[in] partition Discriptor of partition on which the filesystem resides. + * \returns 0 on error, a FAT filesystem descriptor on success. + * \see fat_close + */ +struct fat_fs_struct* fat_open(struct partition_struct* partition) +{ + if(!partition || +#if FAT_WRITE_SUPPORT + !partition->device_write || + !partition->device_write_interval +#else + 0 +#endif + ) + return 0; + +#if USE_DYNAMIC_MEMORY + struct fat_fs_struct* fs = malloc(sizeof(*fs)); + if(!fs) + return 0; +#else + struct fat_fs_struct* fs = fat_fs_handles; + uint8_t i; + for(i = 0; i < FAT_FS_COUNT; ++i) + { + if(!fs->partition) + break; + + ++fs; + } + if(i >= FAT_FS_COUNT) + return 0; +#endif + + memset(fs, 0, sizeof(*fs)); + + fs->partition = partition; + if(!fat_read_header(fs)) + { +#if USE_DYNAMIC_MEMORY + free(fs); +#else + fs->partition = 0; +#endif + return 0; + } + + return fs; +} + +/** + * \ingroup fat_fs + * Closes a FAT filesystem. + * + * When this function returns, the given filesystem descriptor + * will be invalid. + * + * \param[in] fs The filesystem to close. + * \see fat_open + */ +void fat_close(struct fat_fs_struct* fs) +{ + if(!fs) + return; + +#if USE_DYNAMIC_MEMORY + free(fs); +#else + fs->partition = 0; +#endif +} + +/** + * \ingroup fat_fs + * Reads and parses the header of a FAT filesystem. + * + * \param[in,out] fs The filesystem for which to parse the header. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_read_header(struct fat_fs_struct* fs) +{ + if(!fs) + return 0; + + struct partition_struct* partition = fs->partition; + if(!partition) + return 0; + + /* read fat parameters */ +#if FAT_FAT32_SUPPORT + uint8_t buffer[37]; +#else + uint8_t buffer[25]; +#endif + offset_t partition_offset = (offset_t) partition->offset * 512; + if(!partition->device_read(partition_offset + 0x0b, buffer, sizeof(buffer))) + return 0; + + uint16_t bytes_per_sector = read16(&buffer[0x00]); + uint16_t reserved_sectors = read16(&buffer[0x03]); + uint8_t sectors_per_cluster = buffer[0x02]; + uint8_t fat_copies = buffer[0x05]; + uint16_t max_root_entries = read16(&buffer[0x06]); + uint16_t sector_count_16 = read16(&buffer[0x08]); + uint16_t sectors_per_fat = read16(&buffer[0x0b]); + uint32_t sector_count = read32(&buffer[0x15]); +#if FAT_FAT32_SUPPORT + uint32_t sectors_per_fat32 = read32(&buffer[0x19]); + uint32_t cluster_root_dir = read32(&buffer[0x21]); +#endif + + if(sector_count == 0) + { + if(sector_count_16 == 0) + /* illegal volume size */ + return 0; + else + sector_count = sector_count_16; + } +#if FAT_FAT32_SUPPORT + if(sectors_per_fat != 0) + sectors_per_fat32 = sectors_per_fat; + else if(sectors_per_fat32 == 0) + /* this is neither FAT16 nor FAT32 */ + return 0; +#else + if(sectors_per_fat == 0) + /* this is not a FAT16 */ + return 0; +#endif + + /* determine the type of FAT we have here */ + uint32_t data_sector_count = sector_count + - reserved_sectors +#if FAT_FAT32_SUPPORT + - sectors_per_fat32 * fat_copies +#else + - (uint32_t) sectors_per_fat * fat_copies +#endif + - ((max_root_entries * 32 + bytes_per_sector - 1) / bytes_per_sector); + uint32_t data_cluster_count = data_sector_count / sectors_per_cluster; + if(data_cluster_count < 4085) + /* this is a FAT12, not supported */ + return 0; + else if(data_cluster_count < 65525) + /* this is a FAT16 */ + partition->type = PARTITION_TYPE_FAT16; + else + /* this is a FAT32 */ + partition->type = PARTITION_TYPE_FAT32; + + /* fill header information */ + struct fat_header_struct* header = &fs->header; + memset(header, 0, sizeof(*header)); + + header->size = (offset_t) sector_count * bytes_per_sector; + + header->fat_offset = /* jump to partition */ + partition_offset + + /* jump to fat */ + (offset_t) reserved_sectors * bytes_per_sector; + header->fat_size = (data_cluster_count + 2) * (partition->type == PARTITION_TYPE_FAT16 ? 2 : 4); + + header->sector_size = bytes_per_sector; + header->cluster_size = (uint16_t) bytes_per_sector * sectors_per_cluster; + +#if FAT_FAT32_SUPPORT + if(partition->type == PARTITION_TYPE_FAT16) +#endif + { + header->root_dir_offset = /* jump to fats */ + header->fat_offset + + /* jump to root directory entries */ + (offset_t) fat_copies * sectors_per_fat * bytes_per_sector; + + header->cluster_zero_offset = /* jump to root directory entries */ + header->root_dir_offset + + /* skip root directory entries */ + (offset_t) max_root_entries * 32; + } +#if FAT_FAT32_SUPPORT + else + { + header->cluster_zero_offset = /* jump to fats */ + header->fat_offset + + /* skip fats */ + (offset_t) fat_copies * sectors_per_fat32 * bytes_per_sector; + + header->root_dir_cluster = cluster_root_dir; + } +#endif + + return 1; +} + +/** + * \ingroup fat_fs + * Retrieves the next following cluster of a given cluster. + * + * Using the filesystem file allocation table, this function returns + * the number of the cluster containing the data directly following + * the data within the cluster with the given number. + * + * \param[in] fs The filesystem for which to determine the next cluster. + * \param[in] cluster_num The number of the cluster for which to determine its successor. + * \returns The wanted cluster number, or 0 on error. + */ +cluster_t fat_get_next_cluster(const struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(!fs || cluster_num < 2) + return 0; + +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + { + /* read appropriate fat entry */ + uint32_t fat_entry; + if(!fs->partition->device_read(fs->header.fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + + /* determine next cluster from fat */ + cluster_num = ltoh32(fat_entry); + + if(cluster_num == FAT32_CLUSTER_FREE || + cluster_num == FAT32_CLUSTER_BAD || + (cluster_num >= FAT32_CLUSTER_RESERVED_MIN && cluster_num <= FAT32_CLUSTER_RESERVED_MAX) || + (cluster_num >= FAT32_CLUSTER_LAST_MIN && cluster_num <= FAT32_CLUSTER_LAST_MAX)) + return 0; + } + else +#endif + { + /* read appropriate fat entry */ + uint16_t fat_entry; + if(!fs->partition->device_read(fs->header.fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + + /* determine next cluster from fat */ + cluster_num = ltoh16(fat_entry); + + if(cluster_num == FAT16_CLUSTER_FREE || + cluster_num == FAT16_CLUSTER_BAD || + (cluster_num >= FAT16_CLUSTER_RESERVED_MIN && cluster_num <= FAT16_CLUSTER_RESERVED_MAX) || + (cluster_num >= FAT16_CLUSTER_LAST_MIN && cluster_num <= FAT16_CLUSTER_LAST_MAX)) + return 0; + } + + return cluster_num; +} + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Appends a new cluster chain to an existing one. + * + * Set cluster_num to zero to create a completely new one. + * + * \param[in] fs The file system on which to operate. + * \param[in] cluster_num The cluster to which to append the new chain. + * \param[in] count The number of clusters to allocate. + * \returns 0 on failure, the number of the first new cluster on success. + */ +cluster_t fat_append_clusters(struct fat_fs_struct* fs, cluster_t cluster_num, cluster_t count) +{ + if(!fs) + return 0; + + device_read_t device_read = fs->partition->device_read; + device_write_t device_write = fs->partition->device_write; + offset_t fat_offset = fs->header.fat_offset; + cluster_t count_left = count; + cluster_t cluster_current = fs->cluster_free; + cluster_t cluster_next = 0; + cluster_t cluster_count; + uint16_t fat_entry16; +#if FAT_FAT32_SUPPORT + uint32_t fat_entry32; + uint8_t is_fat32 = (fs->partition->type == PARTITION_TYPE_FAT32); + + if(is_fat32) + cluster_count = fs->header.fat_size / sizeof(fat_entry32); + else +#endif + cluster_count = fs->header.fat_size / sizeof(fat_entry16); + + fs->cluster_free = 0; + for(cluster_t cluster_left = cluster_count; cluster_left > 0; --cluster_left, ++cluster_current) + { + if(cluster_current < 2 || cluster_current >= cluster_count) + cluster_current = 2; + +#if FAT_FAT32_SUPPORT + if(is_fat32) + { + if(!device_read(fat_offset + (offset_t) cluster_current * sizeof(fat_entry32), (uint8_t*) &fat_entry32, sizeof(fat_entry32))) + return 0; + } + else +#endif + { + if(!device_read(fat_offset + (offset_t) cluster_current * sizeof(fat_entry16), (uint8_t*) &fat_entry16, sizeof(fat_entry16))) + return 0; + } + +#if FAT_FAT32_SUPPORT + if(is_fat32) + { + /* check if this is a free cluster */ + if(fat_entry32 != HTOL32(FAT32_CLUSTER_FREE)) + continue; + + /* If we don't need this free cluster for the + * current allocation, we keep it in mind for + * the next time. + */ + if(count_left == 0) + { + fs->cluster_free = cluster_current; + break; + } + + /* allocate cluster */ + if(cluster_next == 0) + fat_entry32 = HTOL32(FAT32_CLUSTER_LAST_MAX); + else + fat_entry32 = htol32(cluster_next); + + if(!device_write(fat_offset + (offset_t) cluster_current * sizeof(fat_entry32), (uint8_t*) &fat_entry32, sizeof(fat_entry32))) + break; + } + else +#endif + { + /* check if this is a free cluster */ + if(fat_entry16 != HTOL16(FAT16_CLUSTER_FREE)) + continue; + + /* If we don't need this free cluster for the + * current allocation, we keep it in mind for + * the next time. + */ + if(count_left == 0) + { + fs->cluster_free = cluster_current; + break; + } + + /* allocate cluster */ + if(cluster_next == 0) + fat_entry16 = HTOL16(FAT16_CLUSTER_LAST_MAX); + else + fat_entry16 = htol16((uint16_t) cluster_next); + + if(!device_write(fat_offset + (offset_t) cluster_current * sizeof(fat_entry16), (uint8_t*) &fat_entry16, sizeof(fat_entry16))) + break; + } + + cluster_next = cluster_current; + --count_left; + } + + do + { + if(count_left > 0) + break; + + /* We allocated a new cluster chain. Now join + * it with the existing one (if any). + */ + if(cluster_num >= 2) + { +#if FAT_FAT32_SUPPORT + if(is_fat32) + { + fat_entry32 = htol32(cluster_next); + + if(!device_write(fat_offset + (offset_t) cluster_num * sizeof(fat_entry32), (uint8_t*) &fat_entry32, sizeof(fat_entry32))) + break; + } + else +#endif + { + fat_entry16 = htol16((uint16_t) cluster_next); + + if(!device_write(fat_offset + (offset_t) cluster_num * sizeof(fat_entry16), (uint8_t*) &fat_entry16, sizeof(fat_entry16))) + break; + } + } + + return cluster_next; + + } while(0); + + /* No space left on device or writing error. + * Free up all clusters already allocated. + */ + fat_free_clusters(fs, cluster_next); + + return 0; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Frees a cluster chain, or a part thereof. + * + * Marks the specified cluster and all clusters which are sequentially + * referenced by it as free. They may then be used again for future + * file allocations. + * + * \note If this function is used for freeing just a part of a cluster + * chain, the new end of the chain is not correctly terminated + * within the FAT. Use fat_terminate_clusters() instead. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] cluster_num The starting cluster of the chain which to free. + * \returns 0 on failure, 1 on success. + * \see fat_terminate_clusters + */ +uint8_t fat_free_clusters(struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(!fs || cluster_num < 2) + return 0; + + offset_t fat_offset = fs->header.fat_offset; +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + { + uint32_t fat_entry; + while(cluster_num) + { + if(!fs->partition->device_read(fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + + /* get next cluster of current cluster before freeing current cluster */ + uint32_t cluster_num_next = ltoh32(fat_entry); + + if(cluster_num_next == FAT32_CLUSTER_FREE) + return 1; + if(cluster_num_next == FAT32_CLUSTER_BAD || + (cluster_num_next >= FAT32_CLUSTER_RESERVED_MIN && + cluster_num_next <= FAT32_CLUSTER_RESERVED_MAX + ) + ) + return 0; + if(cluster_num_next >= FAT32_CLUSTER_LAST_MIN && cluster_num_next <= FAT32_CLUSTER_LAST_MAX) + cluster_num_next = 0; + + /* We know we will free the cluster, so remember it as + * free for the next allocation. + */ + if(!fs->cluster_free) + fs->cluster_free = cluster_num; + + /* free cluster */ + fat_entry = HTOL32(FAT32_CLUSTER_FREE); + fs->partition->device_write(fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry)); + + /* We continue in any case here, even if freeing the cluster failed. + * The cluster is lost, but maybe we can still free up some later ones. + */ + + cluster_num = cluster_num_next; + } + } + else +#endif + { + uint16_t fat_entry; + while(cluster_num) + { + if(!fs->partition->device_read(fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + + /* get next cluster of current cluster before freeing current cluster */ + uint16_t cluster_num_next = ltoh16(fat_entry); + + if(cluster_num_next == FAT16_CLUSTER_FREE) + return 1; + if(cluster_num_next == FAT16_CLUSTER_BAD || + (cluster_num_next >= FAT16_CLUSTER_RESERVED_MIN && + cluster_num_next <= FAT16_CLUSTER_RESERVED_MAX + ) + ) + return 0; + if(cluster_num_next >= FAT16_CLUSTER_LAST_MIN && cluster_num_next <= FAT16_CLUSTER_LAST_MAX) + cluster_num_next = 0; + + /* free cluster */ + fat_entry = HTOL16(FAT16_CLUSTER_FREE); + fs->partition->device_write(fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry)); + + /* We continue in any case here, even if freeing the cluster failed. + * The cluster is lost, but maybe we can still free up some later ones. + */ + + cluster_num = cluster_num_next; + } + } + + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Frees a part of a cluster chain and correctly terminates the rest. + * + * Marks the specified cluster as the new end of a cluster chain and + * frees all following clusters. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] cluster_num The new end of the cluster chain. + * \returns 0 on failure, 1 on success. + * \see fat_free_clusters + */ +uint8_t fat_terminate_clusters(struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(!fs || cluster_num < 2) + return 0; + + /* fetch next cluster before overwriting the cluster entry */ + cluster_t cluster_num_next = fat_get_next_cluster(fs, cluster_num); + + /* mark cluster as the last one */ +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + { + uint32_t fat_entry = HTOL32(FAT32_CLUSTER_LAST_MAX); + if(!fs->partition->device_write(fs->header.fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + } + else +#endif + { + uint16_t fat_entry = HTOL16(FAT16_CLUSTER_LAST_MAX); + if(!fs->partition->device_write(fs->header.fat_offset + (offset_t) cluster_num * sizeof(fat_entry), (uint8_t*) &fat_entry, sizeof(fat_entry))) + return 0; + } + + /* free remaining clusters */ + if(cluster_num_next) + return fat_free_clusters(fs, cluster_num_next); + else + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Clears a single cluster. + * + * The complete cluster is filled with zeros. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] cluster_num The cluster to clear. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_clear_cluster(const struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(cluster_num < 2) + return 0; + + offset_t cluster_offset = fat_cluster_offset(fs, cluster_num); + + uint8_t zero[16]; + memset(zero, 0, sizeof(zero)); + return fs->partition->device_write_interval(cluster_offset, + zero, + fs->header.cluster_size, + fat_clear_cluster_callback, + 0 + ); +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Callback function for clearing a cluster. + */ +uintptr_t fat_clear_cluster_callback(uint8_t* buffer, offset_t offset, void* p) +{ + return 16; +} +#endif + +/** + * \ingroup fat_fs + * Calculates the offset of the specified cluster. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] cluster_num The cluster whose offset to calculate. + * \returns The cluster offset. + */ +offset_t fat_cluster_offset(const struct fat_fs_struct* fs, cluster_t cluster_num) +{ + if(!fs || cluster_num < 2) + return 0; + + return fs->header.cluster_zero_offset + (offset_t) (cluster_num - 2) * fs->header.cluster_size; +} + +/** + * \ingroup fat_file + * Retrieves the directory entry of a path. + * + * The given path may both describe a file or a directory. + * + * \param[in] fs The FAT filesystem on which to search. + * \param[in] path The path of which to read the directory entry. + * \param[out] dir_entry The directory entry to fill. + * \returns 0 on failure, 1 on success. + * \see fat_read_dir + */ +uint8_t fat_get_dir_entry_of_path(struct fat_fs_struct* fs, const char* path, struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !path || path[0] == '\0' || !dir_entry) + return 0; + + if(path[0] == '/') + ++path; + + /* begin with the root directory */ + memset(dir_entry, 0, sizeof(*dir_entry)); + dir_entry->attributes = FAT_ATTRIB_DIR; + + while(1) + { + if(path[0] == '\0') + return 1; + + struct fat_dir_struct* dd = fat_open_dir(fs, dir_entry); + if(!dd) + break; + + /* extract the next hierarchy we will search for */ + const char* sub_path = strchr(path, '/'); + uint8_t length_to_sep; + if(sub_path) + { + length_to_sep = sub_path - path; + ++sub_path; + } + else + { + length_to_sep = strlen(path); + sub_path = path + length_to_sep; + } + + /* read directory entries */ + while(fat_read_dir(dd, dir_entry)) + { + /* check if we have found the next hierarchy */ + if((strlen(dir_entry->long_name) != length_to_sep || + strncmp(path, dir_entry->long_name, length_to_sep) != 0)) + continue; + + fat_close_dir(dd); + dd = 0; + + if(path[length_to_sep] == '\0') + /* we iterated through the whole path and have found the file */ + return 1; + + if(dir_entry->attributes & FAT_ATTRIB_DIR) + { + /* we found a parent directory of the file we are searching for */ + path = sub_path; + break; + } + + /* a parent of the file exists, but not the file itself */ + return 0; + } + + fat_close_dir(dd); + } + + return 0; +} + +/** + * \ingroup fat_file + * Opens a file on a FAT filesystem. + * + * \param[in] fs The filesystem on which the file to open lies. + * \param[in] dir_entry The directory entry of the file to open. + * \returns The file handle, or 0 on failure. + * \see fat_close_file + */ +struct fat_file_struct* fat_open_file(struct fat_fs_struct* fs, const struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry || (dir_entry->attributes & FAT_ATTRIB_DIR)) + return 0; + +#if USE_DYNAMIC_MEMORY + struct fat_file_struct* fd = malloc(sizeof(*fd)); + if(!fd) + return 0; +#else + struct fat_file_struct* fd = fat_file_handles; + uint8_t i; + for(i = 0; i < FAT_FILE_COUNT; ++i) + { + if(!fd->fs) + break; + + ++fd; + } + if(i >= FAT_FILE_COUNT) + return 0; +#endif + + memcpy(&fd->dir_entry, dir_entry, sizeof(*dir_entry)); + fd->fs = fs; + fd->pos = 0; + fd->pos_cluster = dir_entry->cluster; + + return fd; +} + +/** + * \ingroup fat_file + * Closes a file. + * + * \param[in] fd The file handle of the file to close. + * \see fat_open_file + */ +void fat_close_file(struct fat_file_struct* fd) +{ + if(fd) + { +#if FAT_DELAY_DIRENTRY_UPDATE + /* write directory entry */ + fat_write_dir_entry(fd->fs, &fd->dir_entry); +#endif + +#if USE_DYNAMIC_MEMORY + free(fd); +#else + fd->fs = 0; +#endif + } +} + +/** + * \ingroup fat_file + * Reads data from a file. + * + * The data requested is read from the current file location. + * + * \param[in] fd The file handle of the file from which to read. + * \param[out] buffer The buffer into which to write. + * \param[in] buffer_len The amount of data to read. + * \returns The number of bytes read, 0 on end of file, or -1 on failure. + * \see fat_write_file + */ +intptr_t fat_read_file(struct fat_file_struct* fd, uint8_t* buffer, uintptr_t buffer_len) +{ + /* check arguments */ + if(!fd || !buffer || buffer_len < 1) + return -1; + + /* determine number of bytes to read */ + if(fd->pos + buffer_len > fd->dir_entry.file_size) + buffer_len = fd->dir_entry.file_size - fd->pos; + if(buffer_len == 0) + return 0; + + uint16_t cluster_size = fd->fs->header.cluster_size; + cluster_t cluster_num = fd->pos_cluster; + uintptr_t buffer_left = buffer_len; + uint16_t first_cluster_offset = (uint16_t) (fd->pos & (cluster_size - 1)); + + /* find cluster in which to start reading */ + if(!cluster_num) + { + cluster_num = fd->dir_entry.cluster; + + if(!cluster_num) + { + if(!fd->pos) + return 0; + else + return -1; + } + + if(fd->pos) + { + uint32_t pos = fd->pos; + while(pos >= cluster_size) + { + pos -= cluster_size; + cluster_num = fat_get_next_cluster(fd->fs, cluster_num); + if(!cluster_num) + return -1; + } + } + } + + /* read data */ + do + { + /* calculate data size to copy from cluster */ + offset_t cluster_offset = fat_cluster_offset(fd->fs, cluster_num) + first_cluster_offset; + uint16_t copy_length = cluster_size - first_cluster_offset; + if(copy_length > buffer_left) + copy_length = buffer_left; + + /* read data */ + if(!fd->fs->partition->device_read(cluster_offset, buffer, copy_length)) + return buffer_len - buffer_left; + + /* calculate new file position */ + buffer += copy_length; + buffer_left -= copy_length; + fd->pos += copy_length; + + if(first_cluster_offset + copy_length >= cluster_size) + { + /* we are on a cluster boundary, so get the next cluster */ + if((cluster_num = fat_get_next_cluster(fd->fs, cluster_num))) + { + first_cluster_offset = 0; + } + else + { + fd->pos_cluster = 0; + return buffer_len - buffer_left; + } + } + + fd->pos_cluster = cluster_num; + + } while(buffer_left > 0); /* check if we are done */ + + return buffer_len; +} + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Writes data to a file. + * + * The data is written to the current file location. + * + * \param[in] fd The file handle of the file to which to write. + * \param[in] buffer The buffer from which to read the data to be written. + * \param[in] buffer_len The amount of data to write. + * \returns The number of bytes written (0 or something less than \c buffer_len on disk full) or -1 on failure. + * \see fat_read_file + */ +intptr_t fat_write_file(struct fat_file_struct* fd, const uint8_t* buffer, uintptr_t buffer_len) +{ + /* check arguments */ + if(!fd || !buffer || buffer_len < 1) + return -1; + if(fd->pos > fd->dir_entry.file_size) + return -1; + + uint16_t cluster_size = fd->fs->header.cluster_size; + cluster_t cluster_num = fd->pos_cluster; + uintptr_t buffer_left = buffer_len; + uint16_t first_cluster_offset = (uint16_t) (fd->pos & (cluster_size - 1)); + + /* find cluster in which to start writing */ + if(!cluster_num) + { + cluster_num = fd->dir_entry.cluster; + + if(!cluster_num) + { + if(!fd->pos) + { + /* empty file */ + fd->dir_entry.cluster = cluster_num = fat_append_clusters(fd->fs, 0, 1); + if(!cluster_num) + return 0; + } + else + { + return -1; + } + } + + if(fd->pos) + { + uint32_t pos = fd->pos; + cluster_t cluster_num_next; + while(pos >= cluster_size) + { + pos -= cluster_size; + cluster_num_next = fat_get_next_cluster(fd->fs, cluster_num); + if(!cluster_num_next) + { + if(pos != 0) + return -1; /* current file position points beyond end of file */ + + /* the file exactly ends on a cluster boundary, and we append to it */ + cluster_num_next = fat_append_clusters(fd->fs, cluster_num, 1); + if(!cluster_num_next) + return 0; + } + + cluster_num = cluster_num_next; + } + } + } + + /* write data */ + do + { + /* calculate data size to write to cluster */ + offset_t cluster_offset = fat_cluster_offset(fd->fs, cluster_num) + first_cluster_offset; + uint16_t write_length = cluster_size - first_cluster_offset; + if(write_length > buffer_left) + write_length = buffer_left; + + /* write data which fits into the current cluster */ + if(!fd->fs->partition->device_write(cluster_offset, buffer, write_length)) + break; + + /* calculate new file position */ + buffer += write_length; + buffer_left -= write_length; + fd->pos += write_length; + + if(first_cluster_offset + write_length >= cluster_size) + { + /* we are on a cluster boundary, so get the next cluster */ + cluster_t cluster_num_next = fat_get_next_cluster(fd->fs, cluster_num); + if(!cluster_num_next && buffer_left > 0) + /* we reached the last cluster, append a new one */ + cluster_num_next = fat_append_clusters(fd->fs, cluster_num, 1); + if(!cluster_num_next) + { + fd->pos_cluster = 0; + break; + } + + cluster_num = cluster_num_next; + first_cluster_offset = 0; + } + + fd->pos_cluster = cluster_num; + + } while(buffer_left > 0); /* check if we are done */ + + /* update directory entry */ + if(fd->pos > fd->dir_entry.file_size) + { +#if !FAT_DELAY_DIRENTRY_UPDATE + uint32_t size_old = fd->dir_entry.file_size; +#endif + + /* update file size */ + fd->dir_entry.file_size = fd->pos; + +#if !FAT_DELAY_DIRENTRY_UPDATE + /* write directory entry */ + if(!fat_write_dir_entry(fd->fs, &fd->dir_entry)) + { + /* We do not return an error here since we actually wrote + * some data to disk. So we calculate the amount of data + * we wrote to disk and which lies within the old file size. + */ + buffer_left = fd->pos - size_old; + fd->pos = size_old; + } +#endif + } + + return buffer_len - buffer_left; +} +#endif + +/** + * \ingroup fat_file + * Repositions the read/write file offset. + * + * Changes the file offset where the next call to fat_read_file() + * or fat_write_file() starts reading/writing. + * + * If the new offset is beyond the end of the file, fat_resize_file() + * is implicitly called, i.e. the file is expanded. + * + * The new offset can be given in different ways determined by + * the \c whence parameter: + * - \b FAT_SEEK_SET: \c *offset is relative to the beginning of the file. + * - \b FAT_SEEK_CUR: \c *offset is relative to the current file position. + * - \b FAT_SEEK_END: \c *offset is relative to the end of the file. + * + * The resulting absolute offset is written to the location the \c offset + * parameter points to. + * + * Calling this function can also be used to retrieve the current file position: + \code + int32_t file_pos = 0; + if(!fat_seek_file(fd, &file_pos, FAT_SEEK_CUR)) + { + // error + } + // file_pos now contains the absolute file position + \endcode + * + * \param[in] fd The file decriptor of the file on which to seek. + * \param[in,out] offset A pointer to the new offset, as affected by the \c whence + * parameter. The function writes the new absolute offset + * to this location before it returns. + * \param[in] whence Affects the way \c offset is interpreted, see above. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_seek_file(struct fat_file_struct* fd, int32_t* offset, uint8_t whence) +{ + if(!fd || !offset) + return 0; + + uint32_t new_pos = fd->pos; + switch(whence) + { + case FAT_SEEK_SET: + new_pos = *offset; + break; + case FAT_SEEK_CUR: + new_pos += *offset; + break; + case FAT_SEEK_END: + new_pos = fd->dir_entry.file_size + *offset; + break; + default: + return 0; + } + + if(new_pos > fd->dir_entry.file_size +#if FAT_WRITE_SUPPORT + && !fat_resize_file(fd, new_pos) +#endif + ) + return 0; + + fd->pos = new_pos; + fd->pos_cluster = 0; + + *offset = (int32_t) new_pos; + return 1; +} + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Resizes a file to have a specific size. + * + * Enlarges or shrinks the file pointed to by the file descriptor to have + * exactly the specified size. + * + * If the file is truncated, all bytes having an equal or larger offset + * than the given size are lost. If the file is expanded, the additional + * bytes are allocated. + * + * \note Please be aware that this function just allocates or deallocates disk + * space, it does not explicitely clear it. To avoid data leakage, this + * must be done manually. + * + * \param[in] fd The file decriptor of the file which to resize. + * \param[in] size The new size of the file. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_resize_file(struct fat_file_struct* fd, uint32_t size) +{ + if(!fd) + return 0; + + cluster_t cluster_num = fd->dir_entry.cluster; + uint16_t cluster_size = fd->fs->header.cluster_size; + uint32_t size_new = size; + + do + { + if(cluster_num == 0 && size_new == 0) + /* the file stays empty */ + break; + + /* seek to the next cluster as long as we need the space */ + while(size_new > cluster_size) + { + /* get next cluster of file */ + cluster_t cluster_num_next = fat_get_next_cluster(fd->fs, cluster_num); + if(cluster_num_next) + { + cluster_num = cluster_num_next; + size_new -= cluster_size; + } + else + { + break; + } + } + + if(size_new > cluster_size || cluster_num == 0) + { + /* Allocate new cluster chain and append + * it to the existing one, if available. + */ + cluster_t cluster_count = (size_new + cluster_size - 1) / cluster_size; + cluster_t cluster_new_chain = fat_append_clusters(fd->fs, cluster_num, cluster_count); + if(!cluster_new_chain) + return 0; + + if(!cluster_num) + { + cluster_num = cluster_new_chain; + fd->dir_entry.cluster = cluster_num; + } + } + + /* write new directory entry */ + fd->dir_entry.file_size = size; + if(size == 0) + fd->dir_entry.cluster = 0; + if(!fat_write_dir_entry(fd->fs, &fd->dir_entry)) + return 0; + + if(size == 0) + { + /* free all clusters of file */ + fat_free_clusters(fd->fs, cluster_num); + } + else if(size_new <= cluster_size) + { + /* free all clusters no longer needed */ + fat_terminate_clusters(fd->fs, cluster_num); + } + + } while(0); + + /* correct file position */ + if(size < fd->pos) + { + fd->pos = size; + fd->pos_cluster = 0; + } + + return 1; +} +#endif + +/** + * \ingroup fat_dir + * Opens a directory. + * + * \param[in] fs The filesystem on which the directory to open resides. + * \param[in] dir_entry The directory entry which stands for the directory to open. + * \returns An opaque directory descriptor on success, 0 on failure. + * \see fat_close_dir + */ +struct fat_dir_struct* fat_open_dir(struct fat_fs_struct* fs, const struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry || !(dir_entry->attributes & FAT_ATTRIB_DIR)) + return 0; + +#if USE_DYNAMIC_MEMORY + struct fat_dir_struct* dd = malloc(sizeof(*dd)); + if(!dd) + return 0; +#else + struct fat_dir_struct* dd = fat_dir_handles; + uint8_t i; + for(i = 0; i < FAT_DIR_COUNT; ++i) + { + if(!dd->fs) + break; + + ++dd; + } + if(i >= FAT_DIR_COUNT) + return 0; +#endif + + memcpy(&dd->dir_entry, dir_entry, sizeof(*dir_entry)); + dd->fs = fs; + dd->entry_cluster = dir_entry->cluster; + dd->entry_offset = 0; + + return dd; +} + +/** + * \ingroup fat_dir + * Closes a directory descriptor. + * + * This function destroys a directory descriptor which was + * previously obtained by calling fat_open_dir(). When this + * function returns, the given descriptor will be invalid. + * + * \param[in] dd The directory descriptor to close. + * \see fat_open_dir + */ +void fat_close_dir(struct fat_dir_struct* dd) +{ + if(dd) +#if USE_DYNAMIC_MEMORY + free(dd); +#else + dd->fs = 0; +#endif +} + +/** + * \ingroup fat_dir + * Reads the next directory entry contained within a parent directory. + * + * \param[in] dd The descriptor of the parent directory from which to read the entry. + * \param[out] dir_entry Pointer to a buffer into which to write the directory entry information. + * \returns 0 on failure, 1 on success. + * \see fat_reset_dir + */ +uint8_t fat_read_dir(struct fat_dir_struct* dd, struct fat_dir_entry_struct* dir_entry) +{ + if(!dd || !dir_entry) + return 0; + + /* get current position of directory handle */ + struct fat_fs_struct* fs = dd->fs; + const struct fat_header_struct* header = &fs->header; + uint16_t cluster_size = header->cluster_size; + cluster_t cluster_num = dd->entry_cluster; + uint16_t cluster_offset = dd->entry_offset; + struct fat_read_dir_callback_arg arg; + + if(cluster_offset >= cluster_size) + { + /* The latest call hit the border of the last cluster in + * the chain, but it still returned a directory entry. + * So we now reset the handle and signal the caller the + * end of the listing. + */ + fat_reset_dir(dd); + return 0; + } + + /* reset callback arguments */ + memset(&arg, 0, sizeof(arg)); + memset(dir_entry, 0, sizeof(*dir_entry)); + arg.dir_entry = dir_entry; + + /* check if we read from the root directory */ + if(cluster_num == 0) + { +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + cluster_num = header->root_dir_cluster; + else +#endif + cluster_size = header->cluster_zero_offset - header->root_dir_offset; + } + + /* read entries */ + uint8_t buffer[32]; + while(!arg.finished) + { + /* read directory entries up to the cluster border */ + uint16_t cluster_left = cluster_size - cluster_offset; + offset_t pos = cluster_offset; + if(cluster_num == 0) + pos += header->root_dir_offset; + else + pos += fat_cluster_offset(fs, cluster_num); + + arg.bytes_read = 0; + if(!fs->partition->device_read_interval(pos, + buffer, + sizeof(buffer), + cluster_left, + fat_dir_entry_read_callback, + &arg) + ) + return 0; + + cluster_offset += arg.bytes_read; + + if(cluster_offset >= cluster_size) + { + /* we reached the cluster border and switch to the next cluster */ + + /* get number of next cluster */ + if((cluster_num = fat_get_next_cluster(fs, cluster_num)) != 0) + { + cluster_offset = 0; + continue; + } + + /* we are at the end of the cluster chain */ + if(!arg.finished) + { + /* directory entry not found, reset directory handle */ + fat_reset_dir(dd); + return 0; + } + else + { + /* The current execution of the function has been successful, + * so we can not signal an end of the directory listing to + * the caller, but must wait for the next call. So we keep an + * invalid cluster offset to mark this directory handle's + * traversal as finished. + */ + } + + break; + } + } + + dd->entry_cluster = cluster_num; + dd->entry_offset = cluster_offset; + + return arg.finished; +} + +/** + * \ingroup fat_dir + * Resets a directory handle. + * + * Resets the directory handle such that reading restarts + * with the first directory entry. + * + * \param[in] dd The directory handle to reset. + * \returns 0 on failure, 1 on success. + * \see fat_read_dir + */ +uint8_t fat_reset_dir(struct fat_dir_struct* dd) +{ + if(!dd) + return 0; + + dd->entry_cluster = dd->dir_entry.cluster; + dd->entry_offset = 0; + return 1; +} + +/** + * \ingroup fat_fs + * Callback function for reading a directory entry. + * + * Interprets a raw directory entry and puts the contained + * information into a fat_dir_entry_struct structure. + * + * For a single file there may exist multiple directory + * entries. All except the last one are lfn entries, which + * contain parts of the long filename. The last directory + * entry is a traditional 8.3 style one. It contains all + * other information like size, cluster, date and time. + * + * \param[in] buffer A pointer to 32 bytes of raw data. + * \param[in] offset The absolute offset of the raw data. + * \param[in,out] p An argument structure controlling operation. + * \returns 0 on failure or completion, 1 if reading has + * to be continued + */ +uint8_t fat_dir_entry_read_callback(uint8_t* buffer, offset_t offset, void* p) +{ + struct fat_read_dir_callback_arg* arg = p; + struct fat_dir_entry_struct* dir_entry = arg->dir_entry; + + arg->bytes_read += 32; + + /* skip deleted or empty entries */ + if(buffer[0] == FAT_DIRENTRY_DELETED || !buffer[0]) + { +#if FAT_LFN_SUPPORT + arg->checksum = 0; +#endif + return 1; + } + +#if !FAT_LFN_SUPPORT + /* skip lfn entries */ + if(buffer[11] == 0x0f) + return 1; +#endif + + char* long_name = dir_entry->long_name; +#if FAT_LFN_SUPPORT + if(buffer[11] == 0x0f) + { + /* checksum validation */ + if(arg->checksum == 0 || arg->checksum != buffer[13]) + { + /* reset directory entry */ + memset(dir_entry, 0, sizeof(*dir_entry)); + + arg->checksum = buffer[13]; + dir_entry->entry_offset = offset; + } + + /* lfn supports unicode, but we do not, for now. + * So we assume pure ascii and read only every + * second byte. + */ + uint16_t char_offset = ((buffer[0] & 0x3f) - 1) * 13; + const uint8_t char_mapping[] = { 1, 3, 5, 7, 9, 14, 16, 18, 20, 22, 24, 28, 30 }; + for(uint8_t i = 0; i <= 12 && char_offset + i < sizeof(dir_entry->long_name) - 1; ++i) + long_name[char_offset + i] = buffer[char_mapping[i]]; + + return 1; + } + else +#endif + { +#if FAT_LFN_SUPPORT + /* if we do not have a long name or the previous lfn does not match, take the 8.3 name */ + if(long_name[0] == '\0' || arg->checksum != fat_calc_83_checksum(buffer)) +#endif + { + /* reset directory entry */ + memset(dir_entry, 0, sizeof(*dir_entry)); + dir_entry->entry_offset = offset; + + uint8_t i; + for(i = 0; i < 8; ++i) + { + if(buffer[i] == ' ') + break; + long_name[i] = buffer[i]; + + /* Windows NT and later versions do not store lfn entries + * for 8.3 names which have a lowercase basename, extension + * or both when everything else is uppercase. They use two + * extra bits to signal a lowercase basename or extension. + */ + if((buffer[12] & 0x08) && buffer[i] >= 'A' && buffer[i] <= 'Z') + long_name[i] += 'a' - 'A'; + } + if(long_name[0] == 0x05) + long_name[0] = (char) FAT_DIRENTRY_DELETED; + + if(buffer[8] != ' ') + { + long_name[i++] = '.'; + + uint8_t j = 8; + for(; j < 11; ++j) + { + if(buffer[j] == ' ') + break; + long_name[i] = buffer[j]; + + /* See above for the lowercase 8.3 name handling of + * Windows NT and later. + */ + if((buffer[12] & 0x10) && buffer[j] >= 'A' && buffer[j] <= 'Z') + long_name[i] += 'a' - 'A'; + + ++i; + } + } + + long_name[i] = '\0'; + } + + /* extract properties of file and store them within the structure */ + dir_entry->attributes = buffer[11]; + dir_entry->cluster = read16(&buffer[26]); +#if FAT_FAT32_SUPPORT + dir_entry->cluster |= ((cluster_t) read16(&buffer[20])) << 16; +#endif + dir_entry->file_size = read32(&buffer[28]); + +#if FAT_DATETIME_SUPPORT + dir_entry->modification_time = read16(&buffer[22]); + dir_entry->modification_date = read16(&buffer[24]); +#endif + + arg->finished = 1; + return 0; + } +} + +#if DOXYGEN || FAT_LFN_SUPPORT +/** + * \ingroup fat_fs + * Calculates the checksum for 8.3 names used within the + * corresponding lfn directory entries. + * + * \param[in] file_name_83 The 11-byte file name buffer. + * \returns The checksum of the given file name. + */ +uint8_t fat_calc_83_checksum(const uint8_t* file_name_83) +{ + uint8_t checksum = file_name_83[0]; + for(uint8_t i = 1; i < 11; ++i) + checksum = ((checksum >> 1) | (checksum << 7)) + file_name_83[i]; + + return checksum; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Searches for space where to store a directory entry. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] parent The directory in which to search. + * \param[in] dir_entry The directory entry for which to search space. + * \returns 0 on failure, a device offset on success. + */ +offset_t fat_find_offset_for_dir_entry(struct fat_fs_struct* fs, const struct fat_dir_struct* parent, const struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry) + return 0; + + /* search for a place where to write the directory entry to disk */ +#if FAT_LFN_SUPPORT + uint8_t free_dir_entries_needed = (strlen(dir_entry->long_name) + 12) / 13 + 1; + uint8_t free_dir_entries_found = 0; +#endif + cluster_t cluster_num = parent->dir_entry.cluster; + offset_t dir_entry_offset = 0; + offset_t offset = 0; + offset_t offset_to = 0; +#if FAT_FAT32_SUPPORT + uint8_t is_fat32 = (fs->partition->type == PARTITION_TYPE_FAT32); +#endif + + if(cluster_num == 0) + { +#if FAT_FAT32_SUPPORT + if(is_fat32) + { + cluster_num = fs->header.root_dir_cluster; + } + else +#endif + { + /* we read/write from the root directory entry */ + offset = fs->header.root_dir_offset; + offset_to = fs->header.cluster_zero_offset; + dir_entry_offset = offset; + } + } + + while(1) + { + if(offset == offset_to) + { + if(cluster_num == 0) + /* We iterated through the whole root directory and + * could not find enough space for the directory entry. + */ + return 0; + + if(offset) + { + /* We reached a cluster boundary and have to + * switch to the next cluster. + */ + + cluster_t cluster_next = fat_get_next_cluster(fs, cluster_num); + if(!cluster_next) + { + cluster_next = fat_append_clusters(fs, cluster_num, 1); + if(!cluster_next) + return 0; + + /* we appended a new cluster and know it is free */ + dir_entry_offset = fs->header.cluster_zero_offset + + (offset_t) (cluster_next - 2) * fs->header.cluster_size; + + /* clear cluster to avoid garbage directory entries */ + fat_clear_cluster(fs, cluster_next); + + break; + } + cluster_num = cluster_next; + } + + offset = fat_cluster_offset(fs, cluster_num); + offset_to = offset + fs->header.cluster_size; + dir_entry_offset = offset; +#if FAT_LFN_SUPPORT + free_dir_entries_found = 0; +#endif + } + + /* read next lfn or 8.3 entry */ + uint8_t first_char; + if(!fs->partition->device_read(offset, &first_char, sizeof(first_char))) + return 0; + + /* check if we found a free directory entry */ + if(first_char == FAT_DIRENTRY_DELETED || !first_char) + { + /* check if we have the needed number of available entries */ +#if FAT_LFN_SUPPORT + ++free_dir_entries_found; + if(free_dir_entries_found >= free_dir_entries_needed) +#endif + break; + + offset += 32; + } + else + { + offset += 32; + dir_entry_offset = offset; +#if FAT_LFN_SUPPORT + free_dir_entries_found = 0; +#endif + } + } + + return dir_entry_offset; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_fs + * Writes a directory entry to disk. + * + * \note The file name is not checked for invalid characters. + * + * \note The generation of the short 8.3 file name is quite + * simple. The first eight characters are used for the filename. + * The extension, if any, is made up of the first three characters + * following the last dot within the long filename. If the + * filename (without the extension) is longer than eight characters, + * the lower byte of the cluster number replaces the last two + * characters to avoid name clashes. In any other case, it is your + * responsibility to avoid name clashes. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] dir_entry The directory entry to write. + * \returns 0 on failure, 1 on success. + */ +uint8_t fat_write_dir_entry(const struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry) + return 0; + +#if FAT_DATETIME_SUPPORT + { + uint16_t year; + uint8_t month; + uint8_t day; + uint8_t hour; + uint8_t min; + uint8_t sec; + + fat_get_datetime(&year, &month, &day, &hour, &min, &sec); + fat_set_file_modification_date(dir_entry, year, month, day); + fat_set_file_modification_time(dir_entry, hour, min, sec); + } +#endif + + device_write_t device_write = fs->partition->device_write; + offset_t offset = dir_entry->entry_offset; + const char* name = dir_entry->long_name; + uint8_t name_len = strlen(name); +#if FAT_LFN_SUPPORT + uint8_t lfn_entry_count = (name_len + 12) / 13; +#endif + uint8_t buffer[32]; + + /* write 8.3 entry */ + + /* generate 8.3 file name */ + memset(&buffer[0], ' ', 11); + char* name_ext = strrchr(name, '.'); + if(name_ext && *++name_ext) + { + uint8_t name_ext_len = strlen(name_ext); + name_len -= name_ext_len + 1; + + if(name_ext_len > 3) +#if FAT_LFN_SUPPORT + name_ext_len = 3; +#else + return 0; +#endif + + memcpy(&buffer[8], name_ext, name_ext_len); + } + + if(name_len <= 8) + { + memcpy(buffer, name, name_len); + +#if FAT_LFN_SUPPORT + /* For now, we create lfn entries for all files, + * except the "." and ".." directory references. + * This is to avoid difficulties with capitalization, + * as 8.3 filenames allow uppercase letters only. + * + * Theoretically it would be possible to leave + * the 8.3 entry alone if the basename and the + * extension have no mixed capitalization. + */ + if(name[0] == '.' && + ((name[1] == '.' && name[2] == '\0') || + name[1] == '\0') + ) + lfn_entry_count = 0; +#endif + } + else + { +#if FAT_LFN_SUPPORT + memcpy(buffer, name, 8); + + /* Minimize 8.3 name clashes by appending + * the lower byte of the cluster number. + */ + uint8_t num = dir_entry->cluster & 0xff; + + buffer[6] = (num < 0xa0) ? ('0' + (num >> 4)) : ('a' + (num >> 4)); + num &= 0x0f; + buffer[7] = (num < 0x0a) ? ('0' + num) : ('a' + num); +#else + return 0; +#endif + } + if(buffer[0] == FAT_DIRENTRY_DELETED) + buffer[0] = 0x05; + + /* fill directory entry buffer */ + memset(&buffer[11], 0, sizeof(buffer) - 11); + buffer[0x0b] = dir_entry->attributes; +#if FAT_DATETIME_SUPPORT + write16(&buffer[0x16], dir_entry->modification_time); + write16(&buffer[0x18], dir_entry->modification_date); +#endif +#if FAT_FAT32_SUPPORT + write16(&buffer[0x14], (uint16_t) (dir_entry->cluster >> 16)); +#endif + write16(&buffer[0x1a], dir_entry->cluster); + write32(&buffer[0x1c], dir_entry->file_size); + + /* write to disk */ +#if FAT_LFN_SUPPORT + if(!device_write(offset + (uint16_t) lfn_entry_count * 32, buffer, sizeof(buffer))) +#else + if(!device_write(offset, buffer, sizeof(buffer))) +#endif + return 0; + +#if FAT_LFN_SUPPORT + /* calculate checksum of 8.3 name */ + uint8_t checksum = fat_calc_83_checksum(buffer); + + /* write lfn entries */ + for(uint8_t lfn_entry = lfn_entry_count; lfn_entry > 0; --lfn_entry) + { + memset(buffer, 0xff, sizeof(buffer)); + + /* set file name */ + const char* long_name_curr = name + (lfn_entry - 1) * 13; + uint8_t i = 1; + while(i < 0x1f) + { + buffer[i++] = *long_name_curr; + buffer[i++] = 0; + + switch(i) + { + case 0x0b: + i = 0x0e; + break; + case 0x1a: + i = 0x1c; + break; + } + + if(!*long_name_curr++) + break; + } + + /* set index of lfn entry */ + buffer[0x00] = lfn_entry; + if(lfn_entry == lfn_entry_count) + buffer[0x00] |= FAT_DIRENTRY_LFNLAST; + + /* mark as lfn entry */ + buffer[0x0b] = 0x0f; + + /* set 8.3 checksum */ + buffer[0x0d] = checksum; + + /* clear reserved bytes */ + buffer[0x0c] = 0; + buffer[0x1a] = 0; + buffer[0x1b] = 0; + + /* write entry */ + device_write(offset, buffer, sizeof(buffer)); + + offset += sizeof(buffer); + } +#endif + + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Creates a file. + * + * Creates a file and obtains the directory entry of the + * new file. If the file to create already exists, the + * directory entry of the existing file will be returned + * within the dir_entry parameter. + * + * \note The file name is not checked for invalid characters. + * + * \note The generation of the short 8.3 file name is quite + * simple. The first eight characters are used for the filename. + * The extension, if any, is made up of the first three characters + * following the last dot within the long filename. If the + * filename (without the extension) is longer than eight characters, + * the lower byte of the cluster number replaces the last two + * characters to avoid name clashes. In any other case, it is your + * responsibility to avoid name clashes. + * + * \param[in] parent The handle of the directory in which to create the file. + * \param[in] file The name of the file to create. + * \param[out] dir_entry The directory entry to fill for the new (or existing) file. + * \returns 0 on failure, 1 on success, 2 if the file already existed. + * \see fat_delete_file + */ +uint8_t fat_create_file(struct fat_dir_struct* parent, const char* file, struct fat_dir_entry_struct* dir_entry) +{ + if(!parent || !file || !file[0] || !dir_entry) + return 0; + + /* check if the file already exists */ + while(1) + { + if(!fat_read_dir(parent, dir_entry)) + break; + + if(strcmp(file, dir_entry->long_name) == 0) + { + fat_reset_dir(parent); + return 2; + } + } + + struct fat_fs_struct* fs = parent->fs; + + /* prepare directory entry with values already known */ + memset(dir_entry, 0, sizeof(*dir_entry)); + strncpy(dir_entry->long_name, file, sizeof(dir_entry->long_name) - 1); + + /* find place where to store directory entry */ + if(!(dir_entry->entry_offset = fat_find_offset_for_dir_entry(fs, parent, dir_entry))) + return 0; + + /* write directory entry to disk */ + if(!fat_write_dir_entry(fs, dir_entry)) + return 0; + + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Deletes a file or directory. + * + * If a directory is deleted without first deleting its + * subdirectories and files, disk space occupied by these + * files will get wasted as there is no chance to release + * it and mark it as free. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] dir_entry The directory entry of the file to delete. + * \returns 0 on failure, 1 on success. + * \see fat_create_file + */ +uint8_t fat_delete_file(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry) +{ + if(!fs || !dir_entry) + return 0; + + /* get offset of the file's directory entry */ + offset_t dir_entry_offset = dir_entry->entry_offset; + if(!dir_entry_offset) + return 0; + +#if FAT_LFN_SUPPORT + uint8_t buffer[12]; + while(1) + { + /* read directory entry */ + if(!fs->partition->device_read(dir_entry_offset, buffer, sizeof(buffer))) + return 0; + + /* mark the directory entry as deleted */ + buffer[0] = FAT_DIRENTRY_DELETED; + + /* write back entry */ + if(!fs->partition->device_write(dir_entry_offset, buffer, sizeof(buffer))) + return 0; + + /* check if we deleted the whole entry */ + if(buffer[11] != 0x0f) + break; + + dir_entry_offset += 32; + } +#else + /* mark the directory entry as deleted */ + uint8_t first_char = FAT_DIRENTRY_DELETED; + if(!fs->partition->device_write(dir_entry_offset, &first_char, 1)) + return 0; +#endif + + /* We deleted the directory entry. The next thing to do is + * marking all occupied clusters as free. + */ + return (dir_entry->cluster == 0 || fat_free_clusters(fs, dir_entry->cluster)); +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_file + * Moves or renames a file. + * + * Changes a file's name, optionally moving it into another + * directory as well. Before calling this function, the + * target file name must not exist. Moving a file to a + * different filesystem (i.e. \a parent_new doesn't lie on + * \a fs) is not supported. + * + * After successfully renaming (and moving) the file, the + * given directory entry is updated such that it points to + * the file's new location. + * + * \note The notes which apply to fat_create_file() also + * apply to this function. + * + * \param[in] fs The filesystem on which to operate. + * \param[in,out] dir_entry The directory entry of the file to move. + * \param[in] parent_new The handle of the new parent directory of the file. + * \param[in] file_new The file's new name. + * \returns 0 on failure, 1 on success. + * \see fat_create_file, fat_delete_file, fat_move_dir + */ +uint8_t fat_move_file(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry, struct fat_dir_struct* parent_new, const char* file_new) +{ + if(!fs || !dir_entry || !parent_new || (file_new && !file_new[0])) + return 0; + if(fs != parent_new->fs) + return 0; + + /* use existing file name if none has been specified */ + if(!file_new) + file_new = dir_entry->long_name; + + /* create file with new file name */ + struct fat_dir_entry_struct dir_entry_new; + if(!fat_create_file(parent_new, file_new, &dir_entry_new)) + return 0; + + /* copy members of directory entry which do not change with rename */ + dir_entry_new.attributes = dir_entry->attributes; +#if FAT_DATETIME_SUPPORT + dir_entry_new.modification_time = dir_entry->modification_time; + dir_entry_new.modification_date = dir_entry->modification_date; +#endif + dir_entry_new.cluster = dir_entry->cluster; + dir_entry_new.file_size = dir_entry->file_size; + + /* make the new file name point to the old file's content */ + if(!fat_write_dir_entry(fs, &dir_entry_new)) + { + fat_delete_file(fs, &dir_entry_new); + return 0; + } + + /* delete the old file, but not its clusters, which have already been remapped above */ + dir_entry->cluster = 0; + if(!fat_delete_file(fs, dir_entry)) + return 0; + + *dir_entry = dir_entry_new; + return 1; +} +#endif + +#if DOXYGEN || FAT_WRITE_SUPPORT +/** + * \ingroup fat_dir + * Creates a directory. + * + * Creates a directory and obtains its directory entry. + * If the directory to create already exists, its + * directory entry will be returned within the dir_entry + * parameter. + * + * \note The notes which apply to fat_create_file() also + * apply to this function. + * + * \param[in] parent The handle of the parent directory of the new directory. + * \param[in] dir The name of the directory to create. + * \param[out] dir_entry The directory entry to fill for the new directory. + * \returns 0 on failure, 1 on success. + * \see fat_delete_dir + */ +uint8_t fat_create_dir(struct fat_dir_struct* parent, const char* dir, struct fat_dir_entry_struct* dir_entry) +{ + if(!parent || !dir || !dir[0] || !dir_entry) + return 0; + + /* check if the file or directory already exists */ + while(fat_read_dir(parent, dir_entry)) + { + if(strcmp(dir, dir_entry->long_name) == 0) + { + fat_reset_dir(parent); + return 0; + } + } + + struct fat_fs_struct* fs = parent->fs; + + /* allocate cluster which will hold directory entries */ + cluster_t dir_cluster = fat_append_clusters(fs, 0, 1); + if(!dir_cluster) + return 0; + + /* clear cluster to prevent bogus directory entries */ + fat_clear_cluster(fs, dir_cluster); + + memset(dir_entry, 0, sizeof(*dir_entry)); + dir_entry->attributes = FAT_ATTRIB_DIR; + + /* create "." directory self reference */ + dir_entry->entry_offset = fs->header.cluster_zero_offset + + (offset_t) (dir_cluster - 2) * fs->header.cluster_size; + dir_entry->long_name[0] = '.'; + dir_entry->cluster = dir_cluster; + if(!fat_write_dir_entry(fs, dir_entry)) + { + fat_free_clusters(fs, dir_cluster); + return 0; + } + + /* create ".." parent directory reference */ + dir_entry->entry_offset += 32; + dir_entry->long_name[1] = '.'; + dir_entry->cluster = parent->dir_entry.cluster; + if(!fat_write_dir_entry(fs, dir_entry)) + { + fat_free_clusters(fs, dir_cluster); + return 0; + } + + /* fill directory entry */ + strncpy(dir_entry->long_name, dir, sizeof(dir_entry->long_name) - 1); + dir_entry->cluster = dir_cluster; + + /* find place where to store directory entry */ + if(!(dir_entry->entry_offset = fat_find_offset_for_dir_entry(fs, parent, dir_entry))) + { + fat_free_clusters(fs, dir_cluster); + return 0; + } + + /* write directory to disk */ + if(!fat_write_dir_entry(fs, dir_entry)) + { + fat_free_clusters(fs, dir_cluster); + return 0; + } + + return 1; +} +#endif + +/** + * \ingroup fat_dir + * Deletes a directory. + * + * This is just a synonym for fat_delete_file(). + * If a directory is deleted without first deleting its + * subdirectories and files, disk space occupied by these + * files will get wasted as there is no chance to release + * it and mark it as free. + * + * \param[in] fs The filesystem on which to operate. + * \param[in] dir_entry The directory entry of the directory to delete. + * \returns 0 on failure, 1 on success. + * \see fat_create_dir + */ +#ifdef DOXYGEN +uint8_t fat_delete_dir(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry); +#endif + +/** + * \ingroup fat_dir + * Moves or renames a directory. + * + * This is just a synonym for fat_move_file(). + * + * \param[in] fs The filesystem on which to operate. + * \param[in,out] dir_entry The directory entry of the directory to move. + * \param[in] parent_new The handle of the new parent directory. + * \param[in] dir_new The directory's new name. + * \returns 0 on failure, 1 on success. + * \see fat_create_dir, fat_delete_dir, fat_move_file + */ +#ifdef DOXYGEN +uint8_t fat_move_dir(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry, struct fat_dir_struct* parent_new, const char* dir_new); +#endif + +#if DOXYGEN || FAT_DATETIME_SUPPORT +/** + * \ingroup fat_file + * Returns the modification date of a file. + * + * \param[in] dir_entry The directory entry of which to return the modification date. + * \param[out] year The year the file was last modified. + * \param[out] month The month the file was last modified. + * \param[out] day The day the file was last modified. + */ +void fat_get_file_modification_date(const struct fat_dir_entry_struct* dir_entry, uint16_t* year, uint8_t* month, uint8_t* day) +{ + if(!dir_entry) + return; + + *year = 1980 + ((dir_entry->modification_date >> 9) & 0x7f); + *month = (dir_entry->modification_date >> 5) & 0x0f; + *day = (dir_entry->modification_date >> 0) & 0x1f; +} +#endif + +#if DOXYGEN || FAT_DATETIME_SUPPORT +/** + * \ingroup fat_file + * Returns the modification time of a file. + * + * \param[in] dir_entry The directory entry of which to return the modification time. + * \param[out] hour The hour the file was last modified. + * \param[out] min The min the file was last modified. + * \param[out] sec The sec the file was last modified. + */ +void fat_get_file_modification_time(const struct fat_dir_entry_struct* dir_entry, uint8_t* hour, uint8_t* min, uint8_t* sec) +{ + if(!dir_entry) + return; + + *hour = (dir_entry->modification_time >> 11) & 0x1f; + *min = (dir_entry->modification_time >> 5) & 0x3f; + *sec = ((dir_entry->modification_time >> 0) & 0x1f) * 2; +} +#endif + +#if DOXYGEN || (FAT_WRITE_SUPPORT && FAT_DATETIME_SUPPORT) +/** + * \ingroup fat_file + * Sets the modification time of a date. + * + * \param[in] dir_entry The directory entry for which to set the modification date. + * \param[in] year The year the file was last modified. + * \param[in] month The month the file was last modified. + * \param[in] day The day the file was last modified. + */ +void fat_set_file_modification_date(struct fat_dir_entry_struct* dir_entry, uint16_t year, uint8_t month, uint8_t day) +{ + if(!dir_entry) + return; + + dir_entry->modification_date = + ((year - 1980) << 9) | + ((uint16_t) month << 5) | + ((uint16_t) day << 0); +} +#endif + +#if DOXYGEN || (FAT_WRITE_SUPPORT && FAT_DATETIME_SUPPORT) +/** + * \ingroup fat_file + * Sets the modification time of a file. + * + * \param[in] dir_entry The directory entry for which to set the modification time. + * \param[in] hour The year the file was last modified. + * \param[in] min The month the file was last modified. + * \param[in] sec The day the file was last modified. + */ +void fat_set_file_modification_time(struct fat_dir_entry_struct* dir_entry, uint8_t hour, uint8_t min, uint8_t sec) +{ + if(!dir_entry) + return; + + dir_entry->modification_time = + ((uint16_t) hour << 11) | + ((uint16_t) min << 5) | + ((uint16_t) sec >> 1) ; +} +#endif + +/** + * \ingroup fat_fs + * Returns the amount of total storage capacity of the filesystem in bytes. + * + * \param[in] fs The filesystem on which to operate. + * \returns 0 on failure, the filesystem size in bytes otherwise. + */ +offset_t fat_get_fs_size(const struct fat_fs_struct* fs) +{ + if(!fs) + return 0; + +#if FAT_FAT32_SUPPORT + if(fs->partition->type == PARTITION_TYPE_FAT32) + return (offset_t) (fs->header.fat_size / 4 - 2) * fs->header.cluster_size; + else +#endif + return (offset_t) (fs->header.fat_size / 2 - 2) * fs->header.cluster_size; +} + +/** + * \ingroup fat_fs + * Returns the amount of free storage capacity on the filesystem in bytes. + * + * \note As the FAT filesystem is cluster based, this function does not + * return continuous values but multiples of the cluster size. + * + * \param[in] fs The filesystem on which to operate. + * \returns 0 on failure, the free filesystem space in bytes otherwise. + */ +offset_t fat_get_fs_free(const struct fat_fs_struct* fs) +{ + if(!fs) + return 0; + + uint8_t fat[32]; + struct fat_usage_count_callback_arg count_arg; + count_arg.cluster_count = 0; + count_arg.buffer_size = sizeof(fat); + + offset_t fat_offset = fs->header.fat_offset; + uint32_t fat_size = fs->header.fat_size; + while(fat_size > 0) + { + uintptr_t length = UINTPTR_MAX - 1; + if(fat_size < length) + length = fat_size; + + if(!fs->partition->device_read_interval(fat_offset, + fat, + sizeof(fat), + length, +#if FAT_FAT32_SUPPORT + (fs->partition->type == PARTITION_TYPE_FAT16) ? + fat_get_fs_free_16_callback : + fat_get_fs_free_32_callback, +#else + fat_get_fs_free_16_callback, +#endif + &count_arg + ) + ) + return 0; + + fat_offset += length; + fat_size -= length; + } + + return (offset_t) count_arg.cluster_count * fs->header.cluster_size; +} + +/** + * \ingroup fat_fs + * Callback function used for counting free clusters in a FAT. + */ +uint8_t fat_get_fs_free_16_callback(uint8_t* buffer, offset_t offset, void* p) +{ + struct fat_usage_count_callback_arg* count_arg = (struct fat_usage_count_callback_arg*) p; + uintptr_t buffer_size = count_arg->buffer_size; + + for(uintptr_t i = 0; i < buffer_size; i += 2, buffer += 2) + { + uint16_t cluster = read16(buffer); + if(cluster == HTOL16(FAT16_CLUSTER_FREE)) + ++(count_arg->cluster_count); + } + + return 1; +} + +#if DOXYGEN || FAT_FAT32_SUPPORT +/** + * \ingroup fat_fs + * Callback function used for counting free clusters in a FAT32. + */ +uint8_t fat_get_fs_free_32_callback(uint8_t* buffer, offset_t offset, void* p) +{ + struct fat_usage_count_callback_arg* count_arg = (struct fat_usage_count_callback_arg*) p; + uintptr_t buffer_size = count_arg->buffer_size; + + for(uintptr_t i = 0; i < buffer_size; i += 4, buffer += 4) + { + uint32_t cluster = read32(buffer); + if(cluster == HTOL32(FAT32_CLUSTER_FREE)) + ++(count_arg->cluster_count); + } + + return 1; +} +#endif + diff --git a/fat.h b/fat.h new file mode 100644 index 0000000..b67bb29 --- /dev/null +++ b/fat.h @@ -0,0 +1,131 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef FAT_H +#define FAT_H + +#include +#include "fat_config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup fat + * + * @{ + */ +/** + * \file + * FAT header (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * \addtogroup fat_file + * @{ + */ + +/** The file is read-only. */ +#define FAT_ATTRIB_READONLY (1 << 0) +/** The file is hidden. */ +#define FAT_ATTRIB_HIDDEN (1 << 1) +/** The file is a system file. */ +#define FAT_ATTRIB_SYSTEM (1 << 2) +/** The file is empty and has the volume label as its name. */ +#define FAT_ATTRIB_VOLUME (1 << 3) +/** The file is a directory. */ +#define FAT_ATTRIB_DIR (1 << 4) +/** The file has to be archived. */ +#define FAT_ATTRIB_ARCHIVE (1 << 5) + +/** The given offset is relative to the beginning of the file. */ +#define FAT_SEEK_SET 0 +/** The given offset is relative to the current read/write position. */ +#define FAT_SEEK_CUR 1 +/** The given offset is relative to the end of the file. */ +#define FAT_SEEK_END 2 + +/** + * @} + */ + +struct partition_struct; +struct fat_fs_struct; +struct fat_file_struct; +struct fat_dir_struct; + +/** + * \ingroup fat_file + * Describes a directory entry. + */ +struct fat_dir_entry_struct +{ + /** The file's name, truncated to 31 characters. */ + char long_name[32]; + /** The file's attributes. Mask of the FAT_ATTRIB_* constants. */ + uint8_t attributes; +#if FAT_DATETIME_SUPPORT + /** Compressed representation of modification time. */ + uint16_t modification_time; + /** Compressed representation of modification date. */ + uint16_t modification_date; +#endif + /** The cluster in which the file's first byte resides. */ + cluster_t cluster; + /** The file's size. */ + uint32_t file_size; + /** The total disk offset of this directory entry. */ + offset_t entry_offset; +}; + +struct fat_fs_struct* fat_open(struct partition_struct* partition); +void fat_close(struct fat_fs_struct* fs); + +struct fat_file_struct* fat_open_file(struct fat_fs_struct* fs, const struct fat_dir_entry_struct* dir_entry); +void fat_close_file(struct fat_file_struct* fd); +intptr_t fat_read_file(struct fat_file_struct* fd, uint8_t* buffer, uintptr_t buffer_len); +intptr_t fat_write_file(struct fat_file_struct* fd, const uint8_t* buffer, uintptr_t buffer_len); +uint8_t fat_seek_file(struct fat_file_struct* fd, int32_t* offset, uint8_t whence); +uint8_t fat_resize_file(struct fat_file_struct* fd, uint32_t size); + +struct fat_dir_struct* fat_open_dir(struct fat_fs_struct* fs, const struct fat_dir_entry_struct* dir_entry); +void fat_close_dir(struct fat_dir_struct* dd); +uint8_t fat_read_dir(struct fat_dir_struct* dd, struct fat_dir_entry_struct* dir_entry); +uint8_t fat_reset_dir(struct fat_dir_struct* dd); + +uint8_t fat_create_file(struct fat_dir_struct* parent, const char* file, struct fat_dir_entry_struct* dir_entry); +uint8_t fat_delete_file(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry); +uint8_t fat_move_file(struct fat_fs_struct* fs, struct fat_dir_entry_struct* dir_entry, struct fat_dir_struct* parent_new, const char* file_new); +uint8_t fat_create_dir(struct fat_dir_struct* parent, const char* dir, struct fat_dir_entry_struct* dir_entry); +#define fat_delete_dir fat_delete_file +#define fat_move_dir fat_move_file + +void fat_get_file_modification_date(const struct fat_dir_entry_struct* dir_entry, uint16_t* year, uint8_t* month, uint8_t* day); +void fat_get_file_modification_time(const struct fat_dir_entry_struct* dir_entry, uint8_t* hour, uint8_t* min, uint8_t* sec); + +uint8_t fat_get_dir_entry_of_path(struct fat_fs_struct* fs, const char* path, struct fat_dir_entry_struct* dir_entry); + +offset_t fat_get_fs_size(const struct fat_fs_struct* fs); +offset_t fat_get_fs_free(const struct fat_fs_struct* fs); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/fat_config.h b/fat_config.h new file mode 100644 index 0000000..0ca8d4c --- /dev/null +++ b/fat_config.h @@ -0,0 +1,128 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef FAT_CONFIG_H +#define FAT_CONFIG_H + +#include +#include "sd_raw_config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup fat + * + * @{ + */ +/** + * \file + * FAT configuration (license: GPLv2 or LGPLv2.1) + */ + +/** + * \ingroup fat_config + * Controls FAT write support. + * + * Set to 1 to enable FAT write support, set to 0 to disable it. + */ +#define FAT_WRITE_SUPPORT SD_RAW_WRITE_SUPPORT + +/** + * \ingroup fat_config + * Controls FAT long filename (LFN) support. + * + * Set to 1 to enable LFN support, set to 0 to disable it. + */ +#define FAT_LFN_SUPPORT 1 + +/** + * \ingroup fat_config + * Controls FAT date and time support. + * + * Set to 1 to enable FAT date and time stamping support. + */ +#define FAT_DATETIME_SUPPORT 0 + +/** + * \ingroup fat_config + * Controls FAT32 support. + * + * Set to 1 to enable FAT32 support. + */ +#define FAT_FAT32_SUPPORT SD_RAW_SDHC + +/** + * \ingroup fat_config + * Controls updates of directory entries. + * + * Set to 1 to delay directory entry updates until the file is closed. + * This can boost performance significantly, but may cause data loss + * if the file is not properly closed. + */ +#define FAT_DELAY_DIRENTRY_UPDATE 0 + +/** + * \ingroup fat_config + * Determines the function used for retrieving current date and time. + * + * Define this to the function call which shall be used to retrieve + * current date and time. + * + * \note Used only when FAT_DATETIME_SUPPORT is 1. + * + * \param[out] year Pointer to a \c uint16_t which receives the current year. + * \param[out] month Pointer to a \c uint8_t which receives the current month. + * \param[out] day Pointer to a \c uint8_t which receives the current day. + * \param[out] hour Pointer to a \c uint8_t which receives the current hour. + * \param[out] min Pointer to a \c uint8_t which receives the current minute. + * \param[out] sec Pointer to a \c uint8_t which receives the current sec. + */ +#define fat_get_datetime(year, month, day, hour, min, sec) \ + get_datetime(year, month, day, hour, min, sec) +/* forward declaration for the above */ +void get_datetime(uint16_t* year, uint8_t* month, uint8_t* day, uint8_t* hour, uint8_t* min, uint8_t* sec); + +/** + * \ingroup fat_config + * Maximum number of filesystem handles. + */ +#define FAT_FS_COUNT 1 + +/** + * \ingroup fat_config + * Maximum number of file handles. + */ +#define FAT_FILE_COUNT 1 + +/** + * \ingroup fat_config + * Maximum number of directory handles. + */ +#define FAT_DIR_COUNT 2 + +/** + * @} + */ + +#if FAT_FAT32_SUPPORT + typedef uint32_t cluster_t; +#else + typedef uint16_t cluster_t; +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/main.c b/main.c new file mode 100644 index 0000000..47298d4 --- /dev/null +++ b/main.c @@ -0,0 +1,666 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include "fat.h" +#include "fat_config.h" +#include "partition.h" +#include "sd_raw.h" +#include "sd_raw_config.h" +#include "uart.h" + +#define DEBUG 1 + +/** + * \mainpage MMC/SD/SDHC card library + * + * This project provides a general purpose library which implements read and write + * support for MMC, SD and SDHC memory cards. + * + * It includes + * - low-level \link sd_raw MMC, SD and SDHC read/write routines \endlink + * - \link partition partition table support \endlink + * - a simple \link fat FAT16/FAT32 read/write implementation \endlink + * + * \section circuit The circuit + * The circuit which was mainly used during development consists of an Atmel AVR + * microcontroller with some passive components. It is quite simple and provides + * an easy test environment. The circuit which can be downloaded on the + * project homepage has been + * improved with regard to operation stability. + * + * I used different microcontrollers during development, the ATmega8 with 8kBytes + * of flash, and its pin-compatible alternative, the ATmega168 with 16kBytes flash. + * The first one is the one I started with, but when I implemented FAT16 write + * support, I ran out of flash space and switched to the ATmega168. For FAT32, an + * ATmega328 is required. + * + * The circuit board is a self-made and self-soldered board consisting of a single + * copper layer and standard DIL components, except of the MMC/SD card connector. + * + * The connector is soldered to the bottom side of the board. It has a simple + * eject button which, when a card is inserted, needs some space beyond the connector + * itself. As an additional feature the connector has two electrical switches + * to detect wether a card is inserted and wether this card is write-protected. + * + * \section pictures Pictures + * \image html pic01.jpg "The circuit board used to implement and test this application." + * \image html pic02.jpg "The MMC/SD card connector on the soldering side of the circuit board." + * + * \section software The software + * The software is written in C (ISO C99). It might not be the smallest or + * the fastest one, but I think it is quite flexible. See the project's + * benchmark page to get an + * idea of the possible data rates. + * + * I implemented an example application providing a simple command prompt which is accessible + * via the UART at 9600 Baud. With commands similiar to the Unix shell you can browse different + * directories, read and write files, create new ones and delete them again. Not all commands are + * available in all software configurations. + * - cat \\n + * Writes a hexdump of \ to the terminal. + * - cd \\n + * Changes current working directory to \. + * - disk\n + * Shows card manufacturer, status, filesystem capacity and free storage space. + * - init\n + * Reinitializes and reopens the memory card. + * - ls\n + * Shows the content of the current directory. + * - mkdir \\n + * Creates a directory called \. + * - mv \ \\n + * Renames \ to \. + * - rm \\n + * Deletes \. + * - sync\n + * Ensures all buffered data is written to the card. + * - touch \\n + * Creates \. + * - write \ \\n + * Writes text to \, starting from \. The text is read + * from the UART, line by line. Finish with an empty line. + * + * \htmlonly + *

+ * The following table shows some typical code sizes in bytes, using the 20090330 release with a + * buffered read-write MMC/SD configuration, FAT16 and static memory allocation: + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
layercode sizestatic RAM usage
MMC/SD2410518
Partition45617
FAT167928188
+ * + *

+ * The static RAM is mostly used for buffering memory card access, which + * improves performance and reduces implementation complexity. + *

+ * + *

+ * Please note that the numbers above do not include the C library functions + * used, e.g. some string functions. These will raise the numbers somewhat + * if they are not already used in other program parts. + *

+ * + *

+ * When opening a partition, filesystem, file or directory, a little amount + * of RAM is used, as listed in the following table. Depending on the library + * configuration, the memory is either allocated statically or dynamically. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
descriptordynamic/static RAM
partition17
filesystem26
file53
directory49
+ * + * \endhtmlonly + * + * \section adaptation Adapting the software to your needs + * The only hardware dependent part is the communication layer talking to the + * memory card. The other parts like partition table and FAT support are + * completely independent, you could use them even for managing Compact Flash + * cards or standard ATAPI hard disks. + * + * By changing the MCU* variables in the Makefile, you can use other Atmel + * microcontrollers or different clock speeds. You might also want to change + * the configuration defines in the files fat_config.h, partition_config.h, + * sd_raw_config.h and sd-reader_config.h. For example, you could disable + * write support completely if you only need read support. + * + * For further information, visit the project's + * FAQ page. + * + * \section bugs Bugs or comments? + * If you have comments or found a bug in the software - there might be some + * of them - you may contact me per mail at feedback@roland-riegel.de. + * + * \section acknowledgements Acknowledgements + * Thanks go to Ulrich Radig, who explained on his homepage how to interface + * MMC cards to the Atmel microcontroller (http://www.ulrichradig.de/). + * I adapted his work for my circuit. + * + * \section copyright Copyright 2006-2012 by Roland Riegel + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation (http://www.gnu.org/copyleft/gpl.html). + * At your option, you can alternatively redistribute and/or modify the following + * files under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation (http://www.gnu.org/copyleft/lgpl.html): + * - byteordering.c + * - byteordering.h + * - fat.c + * - fat.h + * - fat_config.h + * - partition.c + * - partition.h + * - partition_config.h + * - sd_raw.c + * - sd_raw.h + * - sd_raw_config.h + * - sd-reader_config.h + */ + +static uint8_t read_line(char* buffer, uint8_t buffer_length); +static uint32_t strtolong(const char* str); +static uint8_t find_file_in_dir(struct fat_fs_struct* fs, struct fat_dir_struct* dd, const char* name, struct fat_dir_entry_struct* dir_entry); +static struct fat_file_struct* open_file_in_dir(struct fat_fs_struct* fs, struct fat_dir_struct* dd, const char* name); +static uint8_t print_disk_info(const struct fat_fs_struct* fs); + +int main() +{ + /* we will just use ordinary idle mode */ + set_sleep_mode(SLEEP_MODE_IDLE); + + /* setup uart */ + uart_init(); + + while(1) + { + /* setup sd card slot */ + if(!sd_raw_init()) + { +#if DEBUG + uart_puts_p(PSTR("MMC/SD initialization failed\n")); +#endif + continue; + } + + /* open first partition */ + struct partition_struct* partition = partition_open(sd_raw_read, + sd_raw_read_interval, +#if SD_RAW_WRITE_SUPPORT + sd_raw_write, + sd_raw_write_interval, +#else + 0, + 0, +#endif + 0 + ); + + if(!partition) + { + /* If the partition did not open, assume the storage device + * is a "superfloppy", i.e. has no MBR. + */ + partition = partition_open(sd_raw_read, + sd_raw_read_interval, +#if SD_RAW_WRITE_SUPPORT + sd_raw_write, + sd_raw_write_interval, +#else + 0, + 0, +#endif + -1 + ); + if(!partition) + { +#if DEBUG + uart_puts_p(PSTR("opening partition failed\n")); +#endif + continue; + } + } + + /* open file system */ + struct fat_fs_struct* fs = fat_open(partition); + if(!fs) + { +#if DEBUG + uart_puts_p(PSTR("opening filesystem failed\n")); +#endif + continue; + } + + /* open root directory */ + struct fat_dir_entry_struct directory; + fat_get_dir_entry_of_path(fs, "/", &directory); + + struct fat_dir_struct* dd = fat_open_dir(fs, &directory); + if(!dd) + { +#if DEBUG + uart_puts_p(PSTR("opening root directory failed\n")); +#endif + continue; + } + + /* print some card information as a boot message */ + print_disk_info(fs); + + /* provide a simple shell */ + char buffer[24]; + while(1) + { + /* print prompt */ + uart_putc('>'); + uart_putc(' '); + + /* read command */ + char* command = buffer; + if(read_line(command, sizeof(buffer)) < 1) + continue; + + /* execute command */ + if(strcmp_P(command, PSTR("init")) == 0) + { + break; + } + else if(strncmp_P(command, PSTR("cd "), 3) == 0) + { + command += 3; + if(command[0] == '\0') + continue; + + /* change directory */ + struct fat_dir_entry_struct subdir_entry; + if(find_file_in_dir(fs, dd, command, &subdir_entry)) + { + struct fat_dir_struct* dd_new = fat_open_dir(fs, &subdir_entry); + if(dd_new) + { + fat_close_dir(dd); + dd = dd_new; + continue; + } + } + + uart_puts_p(PSTR("directory not found: ")); + uart_puts(command); + uart_putc('\n'); + } + else if(strcmp_P(command, PSTR("ls")) == 0) + { + /* print directory listing */ + struct fat_dir_entry_struct dir_entry; + while(fat_read_dir(dd, &dir_entry)) + { + uint8_t spaces = sizeof(dir_entry.long_name) - strlen(dir_entry.long_name) + 4; + + uart_puts(dir_entry.long_name); + uart_putc(dir_entry.attributes & FAT_ATTRIB_DIR ? '/' : ' '); + while(spaces--) + uart_putc(' '); + uart_putdw_dec(dir_entry.file_size); + uart_putc('\n'); + } + } + else if(strncmp_P(command, PSTR("cat "), 4) == 0) + { + command += 4; + if(command[0] == '\0') + continue; + + /* search file in current directory and open it */ + struct fat_file_struct* fd = open_file_in_dir(fs, dd, command); + if(!fd) + { + uart_puts_p(PSTR("error opening ")); + uart_puts(command); + uart_putc('\n'); + continue; + } + + /* print file contents */ + uint8_t buffer[8]; + uint32_t offset = 0; + intptr_t count; + while((count = fat_read_file(fd, buffer, sizeof(buffer))) > 0) + { + uart_putdw_hex(offset); + uart_putc(':'); + for(intptr_t i = 0; i < count; ++i) + { + uart_putc(' '); + uart_putc_hex(buffer[i]); + } + uart_putc('\n'); + offset += 8; + } + + fat_close_file(fd); + } + else if(strcmp_P(command, PSTR("disk")) == 0) + { + if(!print_disk_info(fs)) + uart_puts_p(PSTR("error reading disk info\n")); + } +#if FAT_WRITE_SUPPORT + else if(strncmp_P(command, PSTR("rm "), 3) == 0) + { + command += 3; + if(command[0] == '\0') + continue; + + struct fat_dir_entry_struct file_entry; + if(find_file_in_dir(fs, dd, command, &file_entry)) + { + if(fat_delete_file(fs, &file_entry)) + continue; + } + + uart_puts_p(PSTR("error deleting file: ")); + uart_puts(command); + uart_putc('\n'); + } + else if(strncmp_P(command, PSTR("touch "), 6) == 0) + { + command += 6; + if(command[0] == '\0') + continue; + + struct fat_dir_entry_struct file_entry; + if(!fat_create_file(dd, command, &file_entry)) + { + uart_puts_p(PSTR("error creating file: ")); + uart_puts(command); + uart_putc('\n'); + } + } + else if(strncmp_P(command, PSTR("mv "), 3) == 0) + { + command += 3; + if(command[0] == '\0') + continue; + + char* target = command; + while(*target != ' ' && *target != '\0') + ++target; + + if(*target == ' ') + *target++ = '\0'; + else + continue; + + struct fat_dir_entry_struct file_entry; + if(find_file_in_dir(fs, dd, command, &file_entry)) + { + if(fat_move_file(fs, &file_entry, dd, target)) + continue; + } + + uart_puts_p(PSTR("error moving file: ")); + uart_puts(command); + uart_putc('\n'); + } + else if(strncmp_P(command, PSTR("write "), 6) == 0) + { + command += 6; + if(command[0] == '\0') + continue; + + char* offset_value = command; + while(*offset_value != ' ' && *offset_value != '\0') + ++offset_value; + + if(*offset_value == ' ') + *offset_value++ = '\0'; + else + continue; + + /* search file in current directory and open it */ + struct fat_file_struct* fd = open_file_in_dir(fs, dd, command); + if(!fd) + { + uart_puts_p(PSTR("error opening ")); + uart_puts(command); + uart_putc('\n'); + continue; + } + + int32_t offset = strtolong(offset_value); + if(!fat_seek_file(fd, &offset, FAT_SEEK_SET)) + { + uart_puts_p(PSTR("error seeking on ")); + uart_puts(command); + uart_putc('\n'); + + fat_close_file(fd); + continue; + } + + /* read text from the shell and write it to the file */ + uint8_t data_len; + while(1) + { + /* give a different prompt */ + uart_putc('<'); + uart_putc(' '); + + /* read one line of text */ + data_len = read_line(buffer, sizeof(buffer)); + if(!data_len) + break; + + /* write text to file */ + if(fat_write_file(fd, (uint8_t*) buffer, data_len) != data_len) + { + uart_puts_p(PSTR("error writing to file\n")); + break; + } + } + + fat_close_file(fd); + } + else if(strncmp_P(command, PSTR("mkdir "), 6) == 0) + { + command += 6; + if(command[0] == '\0') + continue; + + struct fat_dir_entry_struct dir_entry; + if(!fat_create_dir(dd, command, &dir_entry)) + { + uart_puts_p(PSTR("error creating directory: ")); + uart_puts(command); + uart_putc('\n'); + } + } +#endif +#if SD_RAW_WRITE_BUFFERING + else if(strcmp_P(command, PSTR("sync")) == 0) + { + if(!sd_raw_sync()) + uart_puts_p(PSTR("error syncing disk\n")); + } +#endif + else + { + uart_puts_p(PSTR("unknown command: ")); + uart_puts(command); + uart_putc('\n'); + } + } + + /* close directory */ + fat_close_dir(dd); + + /* close file system */ + fat_close(fs); + + /* close partition */ + partition_close(partition); + } + + return 0; +} + +uint8_t read_line(char* buffer, uint8_t buffer_length) +{ + memset(buffer, 0, buffer_length); + + uint8_t read_length = 0; + while(read_length < buffer_length - 1) + { + uint8_t c = uart_getc(); + + if(c == 0x08 || c == 0x7f) + { + if(read_length < 1) + continue; + + --read_length; + buffer[read_length] = '\0'; + + uart_putc(0x08); + uart_putc(' '); + uart_putc(0x08); + + continue; + } + + uart_putc(c); + + if(c == '\n') + { + buffer[read_length] = '\0'; + break; + } + else + { + buffer[read_length] = c; + ++read_length; + } + } + + return read_length; +} + +uint32_t strtolong(const char* str) +{ + uint32_t l = 0; + while(*str >= '0' && *str <= '9') + l = l * 10 + (*str++ - '0'); + + return l; +} + +uint8_t find_file_in_dir(struct fat_fs_struct* fs, struct fat_dir_struct* dd, const char* name, struct fat_dir_entry_struct* dir_entry) +{ + while(fat_read_dir(dd, dir_entry)) + { + if(strcmp(dir_entry->long_name, name) == 0) + { + fat_reset_dir(dd); + return 1; + } + } + + return 0; +} + +struct fat_file_struct* open_file_in_dir(struct fat_fs_struct* fs, struct fat_dir_struct* dd, const char* name) +{ + struct fat_dir_entry_struct file_entry; + if(!find_file_in_dir(fs, dd, name, &file_entry)) + return 0; + + return fat_open_file(fs, &file_entry); +} + +uint8_t print_disk_info(const struct fat_fs_struct* fs) +{ + if(!fs) + return 0; + + struct sd_raw_info disk_info; + if(!sd_raw_get_info(&disk_info)) + return 0; + + uart_puts_p(PSTR("manuf: 0x")); uart_putc_hex(disk_info.manufacturer); uart_putc('\n'); + uart_puts_p(PSTR("oem: ")); uart_puts((char*) disk_info.oem); uart_putc('\n'); + uart_puts_p(PSTR("prod: ")); uart_puts((char*) disk_info.product); uart_putc('\n'); + uart_puts_p(PSTR("rev: ")); uart_putc_hex(disk_info.revision); uart_putc('\n'); + uart_puts_p(PSTR("serial: 0x")); uart_putdw_hex(disk_info.serial); uart_putc('\n'); + uart_puts_p(PSTR("date: ")); uart_putw_dec(disk_info.manufacturing_month); uart_putc('/'); + uart_putw_dec(disk_info.manufacturing_year); uart_putc('\n'); + uart_puts_p(PSTR("size: ")); uart_putdw_dec(disk_info.capacity / 1024 / 1024); uart_puts_p(PSTR("MB\n")); + uart_puts_p(PSTR("copy: ")); uart_putw_dec(disk_info.flag_copy); uart_putc('\n'); + uart_puts_p(PSTR("wr.pr.: ")); uart_putw_dec(disk_info.flag_write_protect_temp); uart_putc('/'); + uart_putw_dec(disk_info.flag_write_protect); uart_putc('\n'); + uart_puts_p(PSTR("format: ")); uart_putw_dec(disk_info.format); uart_putc('\n'); + uart_puts_p(PSTR("free: ")); uart_putdw_dec(fat_get_fs_free(fs)); uart_putc('/'); + uart_putdw_dec(fat_get_fs_size(fs)); uart_putc('\n'); + + return 1; +} + +#if FAT_DATETIME_SUPPORT +void get_datetime(uint16_t* year, uint8_t* month, uint8_t* day, uint8_t* hour, uint8_t* min, uint8_t* sec) +{ + *year = 2007; + *month = 1; + *day = 1; + *hour = 0; + *min = 0; + *sec = 0; +} +#endif + + diff --git a/partition.c b/partition.c new file mode 100644 index 0000000..6c5d982 --- /dev/null +++ b/partition.c @@ -0,0 +1,155 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include "byteordering.h" +#include "partition.h" +#include "partition_config.h" +#include "sd-reader_config.h" + +#include + +#if USE_DYNAMIC_MEMORY + #include +#endif + +/** + * \addtogroup partition Partition table support + * + * Support for reading partition tables and access to partitions. + * + * @{ + */ +/** + * \file + * Partition table implementation (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * \addtogroup partition_config Configuration of partition table support + * Preprocessor defines to configure the partition support. + */ + +#if !USE_DYNAMIC_MEMORY +static struct partition_struct partition_handles[PARTITION_COUNT]; +#endif + +/** + * Opens a partition. + * + * Opens a partition by its index number and returns a partition + * handle which describes the opened partition. + * + * \note This function does not support extended partitions. + * + * \param[in] device_read A function pointer which is used to read from the disk. + * \param[in] device_read_interval A function pointer which is used to read in constant intervals from the disk. + * \param[in] device_write A function pointer which is used to write to the disk. + * \param[in] device_write_interval A function pointer which is used to write a data stream to disk. + * \param[in] index The index of the partition which should be opened, range 0 to 3. + * A negative value is allowed as well. In this case, the partition opened is + * not checked for existance, begins at offset zero, has a length of zero + * and is of an unknown type. Use this in case you want to open the whole device + * as a single partition (e.g. for "super floppy" use). + * \returns 0 on failure, a partition descriptor on success. + * \see partition_close + */ +struct partition_struct* partition_open(device_read_t device_read, device_read_interval_t device_read_interval, device_write_t device_write, device_write_interval_t device_write_interval, int8_t index) +{ + struct partition_struct* new_partition = 0; + uint8_t buffer[0x10]; + + if(!device_read || !device_read_interval || index >= 4) + return 0; + + if(index >= 0) + { + /* read specified partition table index */ + if(!device_read(0x01be + index * 0x10, buffer, sizeof(buffer))) + return 0; + + /* abort on empty partition entry */ + if(buffer[4] == 0x00) + return 0; + } + + /* allocate partition descriptor */ +#if USE_DYNAMIC_MEMORY + new_partition = malloc(sizeof(*new_partition)); + if(!new_partition) + return 0; +#else + new_partition = partition_handles; + uint8_t i; + for(i = 0; i < PARTITION_COUNT; ++i) + { + if(new_partition->type == PARTITION_TYPE_FREE) + break; + + ++new_partition; + } + if(i >= PARTITION_COUNT) + return 0; +#endif + + memset(new_partition, 0, sizeof(*new_partition)); + + /* fill partition descriptor */ + new_partition->device_read = device_read; + new_partition->device_read_interval = device_read_interval; + new_partition->device_write = device_write; + new_partition->device_write_interval = device_write_interval; + + if(index >= 0) + { + new_partition->type = buffer[4]; + new_partition->offset = read32(&buffer[8]); + new_partition->length = read32(&buffer[12]); + } + else + { + new_partition->type = 0xff; + } + + return new_partition; +} + +/** + * Closes a partition. + * + * This function destroys a partition descriptor which was + * previously obtained from a call to partition_open(). + * When this function returns, the given descriptor will be + * invalid. + * + * \param[in] partition The partition descriptor to destroy. + * \returns 0 on failure, 1 on success. + * \see partition_open + */ +uint8_t partition_close(struct partition_struct* partition) +{ + if(!partition) + return 0; + + /* destroy partition descriptor */ +#if USE_DYNAMIC_MEMORY + free(partition); +#else + partition->type = PARTITION_TYPE_FREE; +#endif + + return 1; +} + +/** + * @} + */ + diff --git a/partition.h b/partition.h new file mode 100644 index 0000000..d559c6e --- /dev/null +++ b/partition.h @@ -0,0 +1,212 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef PARTITION_H +#define PARTITION_H + +#include +#include "sd_raw_config.h" +#include "partition_config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup partition + * + * @{ + */ +/** + * \file + * Partition table header (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * The partition table entry is not used. + */ +#define PARTITION_TYPE_FREE 0x00 +/** + * The partition contains a FAT12 filesystem. + */ +#define PARTITION_TYPE_FAT12 0x01 +/** + * The partition contains a FAT16 filesystem with 32MB maximum. + */ +#define PARTITION_TYPE_FAT16_32MB 0x04 +/** + * The partition is an extended partition with its own partition table. + */ +#define PARTITION_TYPE_EXTENDED 0x05 +/** + * The partition contains a FAT16 filesystem. + */ +#define PARTITION_TYPE_FAT16 0x06 +/** + * The partition contains a FAT32 filesystem. + */ +#define PARTITION_TYPE_FAT32 0x0b +/** + * The partition contains a FAT32 filesystem with LBA. + */ +#define PARTITION_TYPE_FAT32_LBA 0x0c +/** + * The partition contains a FAT16 filesystem with LBA. + */ +#define PARTITION_TYPE_FAT16_LBA 0x0e +/** + * The partition is an extended partition with LBA. + */ +#define PARTITION_TYPE_EXTENDED_LBA 0x0f +/** + * The partition has an unknown type. + */ +#define PARTITION_TYPE_UNKNOWN 0xff + +/** + * A function pointer used to read from the partition. + * + * \param[in] offset The offset on the device where to start reading. + * \param[out] buffer The buffer into which to place the data. + * \param[in] length The count of bytes to read. + */ +typedef uint8_t (*device_read_t)(offset_t offset, uint8_t* buffer, uintptr_t length); +/** + * A function pointer passed to a \c device_read_interval_t. + * + * \param[in] buffer The buffer which contains the data just read. + * \param[in] offset The offset from which the data in \c buffer was read. + * \param[in] p An opaque pointer. + * \see device_read_interval_t + */ +typedef uint8_t (*device_read_callback_t)(uint8_t* buffer, offset_t offset, void* p); +/** + * A function pointer used to continuously read units of \c interval bytes + * and call a callback function. + * + * This function starts reading at the specified offset. Every \c interval bytes, + * it calls the callback function with the associated data buffer. + * + * By returning zero, the callback may stop reading. + * + * \param[in] offset Offset from which to start reading. + * \param[in] buffer Pointer to a buffer which is at least interval bytes in size. + * \param[in] interval Number of bytes to read before calling the callback function. + * \param[in] length Number of bytes to read altogether. + * \param[in] callback The function to call every interval bytes. + * \param[in] p An opaque pointer directly passed to the callback function. + * \returns 0 on failure, 1 on success + * \see device_read_t + */ +typedef uint8_t (*device_read_interval_t)(offset_t offset, uint8_t* buffer, uintptr_t interval, uintptr_t length, device_read_callback_t callback, void* p); +/** + * A function pointer used to write to the partition. + * + * \param[in] offset The offset on the device where to start writing. + * \param[in] buffer The buffer which to write. + * \param[in] length The count of bytes to write. + */ +typedef uint8_t (*device_write_t)(offset_t offset, const uint8_t* buffer, uintptr_t length); +/** + * A function pointer passed to a \c device_write_interval_t. + * + * \param[in] buffer The buffer which receives the data to write. + * \param[in] offset The offset to which the data in \c buffer will be written. + * \param[in] p An opaque pointer. + * \returns The number of bytes put into \c buffer + * \see device_write_interval_t + */ +typedef uintptr_t (*device_write_callback_t)(uint8_t* buffer, offset_t offset, void* p); +/** + * A function pointer used to continuously write a data stream obtained from + * a callback function. + * + * This function starts writing at the specified offset. To obtain the + * next bytes to write, it calls the callback function. The callback fills the + * provided data buffer and returns the number of bytes it has put into the buffer. + * + * By returning zero, the callback may stop writing. + * + * \param[in] offset Offset where to start writing. + * \param[in] buffer Pointer to a buffer which is used for the callback function. + * \param[in] length Number of bytes to write in total. May be zero for endless writes. + * \param[in] callback The function used to obtain the bytes to write. + * \param[in] p An opaque pointer directly passed to the callback function. + * \returns 0 on failure, 1 on success + * \see device_write_t + */ +typedef uint8_t (*device_write_interval_t)(offset_t offset, uint8_t* buffer, uintptr_t length, device_write_callback_t callback, void* p); + +/** + * Describes a partition. + */ +struct partition_struct +{ + /** + * The function which reads data from the partition. + * + * \note The offset given to this function is relative to the whole disk, + * not to the start of the partition. + */ + device_read_t device_read; + /** + * The function which repeatedly reads a constant amount of data from the partition. + * + * \note The offset given to this function is relative to the whole disk, + * not to the start of the partition. + */ + device_read_interval_t device_read_interval; + /** + * The function which writes data to the partition. + * + * \note The offset given to this function is relative to the whole disk, + * not to the start of the partition. + */ + device_write_t device_write; + /** + * The function which repeatedly writes data to the partition. + * + * \note The offset given to this function is relative to the whole disk, + * not to the start of the partition. + */ + device_write_interval_t device_write_interval; + + /** + * The type of the partition. + * + * Compare this value to the PARTITION_TYPE_* constants. + */ + uint8_t type; + /** + * The offset in blocks on the disk where this partition starts. + */ + uint32_t offset; + /** + * The length in blocks of this partition. + */ + uint32_t length; +}; + +struct partition_struct* partition_open(device_read_t device_read, device_read_interval_t device_read_interval, device_write_t device_write, device_write_interval_t device_write_interval, int8_t index); +uint8_t partition_close(struct partition_struct* partition); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/partition_config.h b/partition_config.h new file mode 100644 index 0000000..0b49138 --- /dev/null +++ b/partition_config.h @@ -0,0 +1,44 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef PARTITION_CONFIG_H +#define PARTITION_CONFIG_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup partition + * + * @{ + */ +/** + * \file + * Partition configuration (license: GPLv2 or LGPLv2.1) + */ + +/** + * \ingroup partition_config + * Maximum number of partition handles. + */ +#define PARTITION_COUNT 1 + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd-reader_config.h b/sd-reader_config.h new file mode 100644 index 0000000..16957f8 --- /dev/null +++ b/sd-reader_config.h @@ -0,0 +1,53 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef SD_READER_CONFIG_H +#define SD_READER_CONFIG_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup config Sd-reader configuration + * + * @{ + */ + +/** + * \file + * Common sd-reader configuration used by all modules (license: GPLv2 or LGPLv2.1) + * + * \note This file contains only configuration items relevant to + * all sd-reader implementation files. For module specific configuration + * options, please see the files fat_config.h, partition_config.h + * and sd_raw_config.h. + */ + +/** + * Controls allocation of memory. + * + * Set to 1 to use malloc()/free() for allocation of structures + * like file and directory handles, set to 0 to use pre-allocated + * fixed-size handle arrays. + */ +#define USE_DYNAMIC_MEMORY 0 + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_raw.c b/sd_raw.c new file mode 100644 index 0000000..5e80ed3 --- /dev/null +++ b/sd_raw.c @@ -0,0 +1,998 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#include +#include +#include "sd_raw.h" + +/** + * \addtogroup sd_raw MMC/SD/SDHC card raw access + * + * This module implements read and write access to MMC, SD + * and SDHC cards. It serves as a low-level driver for the + * higher level modules such as partition and file system + * access. + * + * @{ + */ +/** + * \file + * MMC/SD/SDHC raw access implementation (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * \addtogroup sd_raw_config MMC/SD configuration + * Preprocessor defines to configure the MMC/SD support. + */ + +/** + * @} + */ + +/* commands available in SPI mode */ + +/* CMD0: response R1 */ +#define CMD_GO_IDLE_STATE 0x00 +/* CMD1: response R1 */ +#define CMD_SEND_OP_COND 0x01 +/* CMD8: response R7 */ +#define CMD_SEND_IF_COND 0x08 +/* CMD9: response R1 */ +#define CMD_SEND_CSD 0x09 +/* CMD10: response R1 */ +#define CMD_SEND_CID 0x0a +/* CMD12: response R1 */ +#define CMD_STOP_TRANSMISSION 0x0c +/* CMD13: response R2 */ +#define CMD_SEND_STATUS 0x0d +/* CMD16: arg0[31:0]: block length, response R1 */ +#define CMD_SET_BLOCKLEN 0x10 +/* CMD17: arg0[31:0]: data address, response R1 */ +#define CMD_READ_SINGLE_BLOCK 0x11 +/* CMD18: arg0[31:0]: data address, response R1 */ +#define CMD_READ_MULTIPLE_BLOCK 0x12 +/* CMD24: arg0[31:0]: data address, response R1 */ +#define CMD_WRITE_SINGLE_BLOCK 0x18 +/* CMD25: arg0[31:0]: data address, response R1 */ +#define CMD_WRITE_MULTIPLE_BLOCK 0x19 +/* CMD27: response R1 */ +#define CMD_PROGRAM_CSD 0x1b +/* CMD28: arg0[31:0]: data address, response R1b */ +#define CMD_SET_WRITE_PROT 0x1c +/* CMD29: arg0[31:0]: data address, response R1b */ +#define CMD_CLR_WRITE_PROT 0x1d +/* CMD30: arg0[31:0]: write protect data address, response R1 */ +#define CMD_SEND_WRITE_PROT 0x1e +/* CMD32: arg0[31:0]: data address, response R1 */ +#define CMD_TAG_SECTOR_START 0x20 +/* CMD33: arg0[31:0]: data address, response R1 */ +#define CMD_TAG_SECTOR_END 0x21 +/* CMD34: arg0[31:0]: data address, response R1 */ +#define CMD_UNTAG_SECTOR 0x22 +/* CMD35: arg0[31:0]: data address, response R1 */ +#define CMD_TAG_ERASE_GROUP_START 0x23 +/* CMD36: arg0[31:0]: data address, response R1 */ +#define CMD_TAG_ERASE_GROUP_END 0x24 +/* CMD37: arg0[31:0]: data address, response R1 */ +#define CMD_UNTAG_ERASE_GROUP 0x25 +/* CMD38: arg0[31:0]: stuff bits, response R1b */ +#define CMD_ERASE 0x26 +/* ACMD41: arg0[31:0]: OCR contents, response R1 */ +#define CMD_SD_SEND_OP_COND 0x29 +/* CMD42: arg0[31:0]: stuff bits, response R1b */ +#define CMD_LOCK_UNLOCK 0x2a +/* CMD55: arg0[31:0]: stuff bits, response R1 */ +#define CMD_APP 0x37 +/* CMD58: arg0[31:0]: stuff bits, response R3 */ +#define CMD_READ_OCR 0x3a +/* CMD59: arg0[31:1]: stuff bits, arg0[0:0]: crc option, response R1 */ +#define CMD_CRC_ON_OFF 0x3b + +/* command responses */ +/* R1: size 1 byte */ +#define R1_IDLE_STATE 0 +#define R1_ERASE_RESET 1 +#define R1_ILL_COMMAND 2 +#define R1_COM_CRC_ERR 3 +#define R1_ERASE_SEQ_ERR 4 +#define R1_ADDR_ERR 5 +#define R1_PARAM_ERR 6 +/* R1b: equals R1, additional busy bytes */ +/* R2: size 2 bytes */ +#define R2_CARD_LOCKED 0 +#define R2_WP_ERASE_SKIP 1 +#define R2_ERR 2 +#define R2_CARD_ERR 3 +#define R2_CARD_ECC_FAIL 4 +#define R2_WP_VIOLATION 5 +#define R2_INVAL_ERASE 6 +#define R2_OUT_OF_RANGE 7 +#define R2_CSD_OVERWRITE 7 +#define R2_IDLE_STATE (R1_IDLE_STATE + 8) +#define R2_ERASE_RESET (R1_ERASE_RESET + 8) +#define R2_ILL_COMMAND (R1_ILL_COMMAND + 8) +#define R2_COM_CRC_ERR (R1_COM_CRC_ERR + 8) +#define R2_ERASE_SEQ_ERR (R1_ERASE_SEQ_ERR + 8) +#define R2_ADDR_ERR (R1_ADDR_ERR + 8) +#define R2_PARAM_ERR (R1_PARAM_ERR + 8) +/* R3: size 5 bytes */ +#define R3_OCR_MASK (0xffffffffUL) +#define R3_IDLE_STATE (R1_IDLE_STATE + 32) +#define R3_ERASE_RESET (R1_ERASE_RESET + 32) +#define R3_ILL_COMMAND (R1_ILL_COMMAND + 32) +#define R3_COM_CRC_ERR (R1_COM_CRC_ERR + 32) +#define R3_ERASE_SEQ_ERR (R1_ERASE_SEQ_ERR + 32) +#define R3_ADDR_ERR (R1_ADDR_ERR + 32) +#define R3_PARAM_ERR (R1_PARAM_ERR + 32) +/* Data Response: size 1 byte */ +#define DR_STATUS_MASK 0x0e +#define DR_STATUS_ACCEPTED 0x05 +#define DR_STATUS_CRC_ERR 0x0a +#define DR_STATUS_WRITE_ERR 0x0c + +/* status bits for card types */ +#define SD_RAW_SPEC_1 0 +#define SD_RAW_SPEC_2 1 +#define SD_RAW_SPEC_SDHC 2 + +#if !SD_RAW_SAVE_RAM +/* static data buffer for acceleration */ +static uint8_t raw_block[512]; +/* offset where the data within raw_block lies on the card */ +static offset_t raw_block_address; +#if SD_RAW_WRITE_BUFFERING +/* flag to remember if raw_block was written to the card */ +static uint8_t raw_block_written; +#endif +#endif + +/* card type state */ +static uint8_t sd_raw_card_type; + +/* private helper functions */ +static void sd_raw_send_byte(uint8_t b); +static uint8_t sd_raw_rec_byte(); +static uint8_t sd_raw_send_command(uint8_t command, uint32_t arg); + +/** + * \ingroup sd_raw + * Initializes memory card communication. + * + * \returns 0 on failure, 1 on success. + */ +uint8_t sd_raw_init() +{ + /* enable inputs for reading card status */ + configure_pin_available(); + configure_pin_locked(); + + /* enable outputs for MOSI, SCK, SS, input for MISO */ + configure_pin_mosi(); + configure_pin_sck(); + configure_pin_ss(); + configure_pin_miso(); + + unselect_card(); + + /* initialize SPI with lowest frequency; max. 400kHz during identification mode of card */ + SPCR = (0 << SPIE) | /* SPI Interrupt Enable */ + (1 << SPE) | /* SPI Enable */ + (0 << DORD) | /* Data Order: MSB first */ + (1 << MSTR) | /* Master mode */ + (0 << CPOL) | /* Clock Polarity: SCK low when idle */ + (0 << CPHA) | /* Clock Phase: sample on rising SCK edge */ + (1 << SPR1) | /* Clock Frequency: f_OSC / 128 */ + (1 << SPR0); + SPSR &= ~(1 << SPI2X); /* No doubled clock frequency */ + + /* initialization procedure */ + sd_raw_card_type = 0; + + if(!sd_raw_available()) + return 0; + + /* card needs 74 cycles minimum to start up */ + for(uint8_t i = 0; i < 10; ++i) + { + /* wait 8 clock cycles */ + sd_raw_rec_byte(); + } + + /* address card */ + select_card(); + + /* reset card */ + uint8_t response; + for(uint16_t i = 0; ; ++i) + { + response = sd_raw_send_command(CMD_GO_IDLE_STATE, 0); + if(response == (1 << R1_IDLE_STATE)) + break; + + if(i == 0x1ff) + { + unselect_card(); + return 0; + } + } + +#if SD_RAW_SDHC + /* check for version of SD card specification */ + response = sd_raw_send_command(CMD_SEND_IF_COND, 0x100 /* 2.7V - 3.6V */ | 0xaa /* test pattern */); + if((response & (1 << R1_ILL_COMMAND)) == 0) + { + sd_raw_rec_byte(); + sd_raw_rec_byte(); + if((sd_raw_rec_byte() & 0x01) == 0) + return 0; /* card operation voltage range doesn't match */ + if(sd_raw_rec_byte() != 0xaa) + return 0; /* wrong test pattern */ + + /* card conforms to SD 2 card specification */ + sd_raw_card_type |= (1 << SD_RAW_SPEC_2); + } + else +#endif + { + /* determine SD/MMC card type */ + sd_raw_send_command(CMD_APP, 0); + response = sd_raw_send_command(CMD_SD_SEND_OP_COND, 0); + if((response & (1 << R1_ILL_COMMAND)) == 0) + { + /* card conforms to SD 1 card specification */ + sd_raw_card_type |= (1 << SD_RAW_SPEC_1); + } + else + { + /* MMC card */ + } + } + + /* wait for card to get ready */ + for(uint16_t i = 0; ; ++i) + { + if(sd_raw_card_type & ((1 << SD_RAW_SPEC_1) | (1 << SD_RAW_SPEC_2))) + { + uint32_t arg = 0; +#if SD_RAW_SDHC + if(sd_raw_card_type & (1 << SD_RAW_SPEC_2)) + arg = 0x40000000; +#endif + sd_raw_send_command(CMD_APP, 0); + response = sd_raw_send_command(CMD_SD_SEND_OP_COND, arg); + } + else + { + response = sd_raw_send_command(CMD_SEND_OP_COND, 0); + } + + if((response & (1 << R1_IDLE_STATE)) == 0) + break; + + if(i == 0x7fff) + { + unselect_card(); + return 0; + } + } + +#if SD_RAW_SDHC + if(sd_raw_card_type & (1 << SD_RAW_SPEC_2)) + { + if(sd_raw_send_command(CMD_READ_OCR, 0)) + { + unselect_card(); + return 0; + } + + if(sd_raw_rec_byte() & 0x40) + sd_raw_card_type |= (1 << SD_RAW_SPEC_SDHC); + + sd_raw_rec_byte(); + sd_raw_rec_byte(); + sd_raw_rec_byte(); + } +#endif + + /* set block size to 512 bytes */ + if(sd_raw_send_command(CMD_SET_BLOCKLEN, 512)) + { + unselect_card(); + return 0; + } + + /* deaddress card */ + unselect_card(); + + /* switch to highest SPI frequency possible */ + SPCR &= ~((1 << SPR1) | (1 << SPR0)); /* Clock Frequency: f_OSC / 4 */ + SPSR |= (1 << SPI2X); /* Doubled Clock Frequency: f_OSC / 2 */ + +#if !SD_RAW_SAVE_RAM + /* the first block is likely to be accessed first, so precache it here */ + raw_block_address = (offset_t) -1; +#if SD_RAW_WRITE_BUFFERING + raw_block_written = 1; +#endif + if(!sd_raw_read(0, raw_block, sizeof(raw_block))) + return 0; +#endif + + return 1; +} + +/** + * \ingroup sd_raw + * Checks wether a memory card is located in the slot. + * + * \returns 1 if the card is available, 0 if it is not. + */ +uint8_t sd_raw_available() +{ + return get_pin_available() == 0x00; +} + +/** + * \ingroup sd_raw + * Checks wether the memory card is locked for write access. + * + * \returns 1 if the card is locked, 0 if it is not. + */ +uint8_t sd_raw_locked() +{ + return get_pin_locked() == 0x00; +} + +/** + * \ingroup sd_raw + * Sends a raw byte to the memory card. + * + * \param[in] b The byte to sent. + * \see sd_raw_rec_byte + */ +void sd_raw_send_byte(uint8_t b) +{ + SPDR = b; + /* wait for byte to be shifted out */ + while(!(SPSR & (1 << SPIF))); + SPSR &= ~(1 << SPIF); +} + +/** + * \ingroup sd_raw + * Receives a raw byte from the memory card. + * + * \returns The byte which should be read. + * \see sd_raw_send_byte + */ +uint8_t sd_raw_rec_byte() +{ + /* send dummy data for receiving some */ + SPDR = 0xff; + while(!(SPSR & (1 << SPIF))); + SPSR &= ~(1 << SPIF); + + return SPDR; +} + +/** + * \ingroup sd_raw + * Send a command to the memory card which responses with a R1 response (and possibly others). + * + * \param[in] command The command to send. + * \param[in] arg The argument for command. + * \returns The command answer. + */ +uint8_t sd_raw_send_command(uint8_t command, uint32_t arg) +{ + uint8_t response; + + /* wait some clock cycles */ + sd_raw_rec_byte(); + + /* send command via SPI */ + sd_raw_send_byte(0x40 | command); + sd_raw_send_byte((arg >> 24) & 0xff); + sd_raw_send_byte((arg >> 16) & 0xff); + sd_raw_send_byte((arg >> 8) & 0xff); + sd_raw_send_byte((arg >> 0) & 0xff); + switch(command) + { + case CMD_GO_IDLE_STATE: + sd_raw_send_byte(0x95); + break; + case CMD_SEND_IF_COND: + sd_raw_send_byte(0x87); + break; + default: + sd_raw_send_byte(0xff); + break; + } + + /* receive response */ + for(uint8_t i = 0; i < 10; ++i) + { + response = sd_raw_rec_byte(); + if(response != 0xff) + break; + } + + return response; +} + +/** + * \ingroup sd_raw + * Reads raw data from the card. + * + * \param[in] offset The offset from which to read. + * \param[out] buffer The buffer into which to write the data. + * \param[in] length The number of bytes to read. + * \returns 0 on failure, 1 on success. + * \see sd_raw_read_interval, sd_raw_write, sd_raw_write_interval + */ +uint8_t sd_raw_read(offset_t offset, uint8_t* buffer, uintptr_t length) +{ + offset_t block_address; + uint16_t block_offset; + uint16_t read_length; + while(length > 0) + { + /* determine byte count to read at once */ + block_offset = offset & 0x01ff; + block_address = offset - block_offset; + read_length = 512 - block_offset; /* read up to block border */ + if(read_length > length) + read_length = length; + +#if !SD_RAW_SAVE_RAM + /* check if the requested data is cached */ + if(block_address != raw_block_address) +#endif + { +#if SD_RAW_WRITE_BUFFERING + if(!sd_raw_sync()) + return 0; +#endif + + /* address card */ + select_card(); + + /* send single block request */ +#if SD_RAW_SDHC + if(sd_raw_send_command(CMD_READ_SINGLE_BLOCK, (sd_raw_card_type & (1 << SD_RAW_SPEC_SDHC) ? block_address / 512 : block_address))) +#else + if(sd_raw_send_command(CMD_READ_SINGLE_BLOCK, block_address)) +#endif + { + unselect_card(); + return 0; + } + + /* wait for data block (start byte 0xfe) */ + while(sd_raw_rec_byte() != 0xfe); + +#if SD_RAW_SAVE_RAM + /* read byte block */ + uint16_t read_to = block_offset + read_length; + for(uint16_t i = 0; i < 512; ++i) + { + uint8_t b = sd_raw_rec_byte(); + if(i >= block_offset && i < read_to) + *buffer++ = b; + } +#else + /* read byte block */ + uint8_t* cache = raw_block; + for(uint16_t i = 0; i < 512; ++i) + *cache++ = sd_raw_rec_byte(); + raw_block_address = block_address; + + memcpy(buffer, raw_block + block_offset, read_length); + buffer += read_length; +#endif + + /* read crc16 */ + sd_raw_rec_byte(); + sd_raw_rec_byte(); + + /* deaddress card */ + unselect_card(); + + /* let card some time to finish */ + sd_raw_rec_byte(); + } +#if !SD_RAW_SAVE_RAM + else + { + /* use cached data */ + memcpy(buffer, raw_block + block_offset, read_length); + buffer += read_length; + } +#endif + + length -= read_length; + offset += read_length; + } + + return 1; +} + +/** + * \ingroup sd_raw + * Continuously reads units of \c interval bytes and calls a callback function. + * + * This function starts reading at the specified offset. Every \c interval bytes, + * it calls the callback function with the associated data buffer. + * + * By returning zero, the callback may stop reading. + * + * \note Within the callback function, you can not start another read or + * write operation. + * \note This function only works if the following conditions are met: + * - (offset - (offset % 512)) % interval == 0 + * - length % interval == 0 + * + * \param[in] offset Offset from which to start reading. + * \param[in] buffer Pointer to a buffer which is at least interval bytes in size. + * \param[in] interval Number of bytes to read before calling the callback function. + * \param[in] length Number of bytes to read altogether. + * \param[in] callback The function to call every interval bytes. + * \param[in] p An opaque pointer directly passed to the callback function. + * \returns 0 on failure, 1 on success + * \see sd_raw_write_interval, sd_raw_read, sd_raw_write + */ +uint8_t sd_raw_read_interval(offset_t offset, uint8_t* buffer, uintptr_t interval, uintptr_t length, sd_raw_read_interval_handler_t callback, void* p) +{ + if(!buffer || interval == 0 || length < interval || !callback) + return 0; + +#if !SD_RAW_SAVE_RAM + while(length >= interval) + { + /* as reading is now buffered, we directly + * hand over the request to sd_raw_read() + */ + if(!sd_raw_read(offset, buffer, interval)) + return 0; + if(!callback(buffer, offset, p)) + break; + offset += interval; + length -= interval; + } + + return 1; +#else + /* address card */ + select_card(); + + uint16_t block_offset; + uint16_t read_length; + uint8_t* buffer_cur; + uint8_t finished = 0; + do + { + /* determine byte count to read at once */ + block_offset = offset & 0x01ff; + read_length = 512 - block_offset; + + /* send single block request */ +#if SD_RAW_SDHC + if(sd_raw_send_command(CMD_READ_SINGLE_BLOCK, (sd_raw_card_type & (1 << SD_RAW_SPEC_SDHC) ? offset / 512 : offset - block_offset))) +#else + if(sd_raw_send_command(CMD_READ_SINGLE_BLOCK, offset - block_offset)) +#endif + { + unselect_card(); + return 0; + } + + /* wait for data block (start byte 0xfe) */ + while(sd_raw_rec_byte() != 0xfe); + + /* read up to the data of interest */ + for(uint16_t i = 0; i < block_offset; ++i) + sd_raw_rec_byte(); + + /* read interval bytes of data and execute the callback */ + do + { + if(read_length < interval || length < interval) + break; + + buffer_cur = buffer; + for(uint16_t i = 0; i < interval; ++i) + *buffer_cur++ = sd_raw_rec_byte(); + + if(!callback(buffer, offset + (512 - read_length), p)) + { + finished = 1; + break; + } + + read_length -= interval; + length -= interval; + + } while(read_length > 0 && length > 0); + + /* read rest of data block */ + while(read_length-- > 0) + sd_raw_rec_byte(); + + /* read crc16 */ + sd_raw_rec_byte(); + sd_raw_rec_byte(); + + if(length < interval) + break; + + offset = offset - block_offset + 512; + + } while(!finished); + + /* deaddress card */ + unselect_card(); + + /* let card some time to finish */ + sd_raw_rec_byte(); + + return 1; +#endif +} + +#if DOXYGEN || SD_RAW_WRITE_SUPPORT +/** + * \ingroup sd_raw + * Writes raw data to the card. + * + * \note If write buffering is enabled, you might have to + * call sd_raw_sync() before disconnecting the card + * to ensure all remaining data has been written. + * + * \param[in] offset The offset where to start writing. + * \param[in] buffer The buffer containing the data to be written. + * \param[in] length The number of bytes to write. + * \returns 0 on failure, 1 on success. + * \see sd_raw_write_interval, sd_raw_read, sd_raw_read_interval + */ +uint8_t sd_raw_write(offset_t offset, const uint8_t* buffer, uintptr_t length) +{ + if(sd_raw_locked()) + return 0; + + offset_t block_address; + uint16_t block_offset; + uint16_t write_length; + while(length > 0) + { + /* determine byte count to write at once */ + block_offset = offset & 0x01ff; + block_address = offset - block_offset; + write_length = 512 - block_offset; /* write up to block border */ + if(write_length > length) + write_length = length; + + /* Merge the data to write with the content of the block. + * Use the cached block if available. + */ + if(block_address != raw_block_address) + { +#if SD_RAW_WRITE_BUFFERING + if(!sd_raw_sync()) + return 0; +#endif + + if(block_offset || write_length < 512) + { + if(!sd_raw_read(block_address, raw_block, sizeof(raw_block))) + return 0; + } + raw_block_address = block_address; + } + + if(buffer != raw_block) + { + memcpy(raw_block + block_offset, buffer, write_length); + +#if SD_RAW_WRITE_BUFFERING + raw_block_written = 0; + + if(length == write_length) + return 1; +#endif + } + + /* address card */ + select_card(); + + /* send single block request */ +#if SD_RAW_SDHC + if(sd_raw_send_command(CMD_WRITE_SINGLE_BLOCK, (sd_raw_card_type & (1 << SD_RAW_SPEC_SDHC) ? block_address / 512 : block_address))) +#else + if(sd_raw_send_command(CMD_WRITE_SINGLE_BLOCK, block_address)) +#endif + { + unselect_card(); + return 0; + } + + /* send start byte */ + sd_raw_send_byte(0xfe); + + /* write byte block */ + uint8_t* cache = raw_block; + for(uint16_t i = 0; i < 512; ++i) + sd_raw_send_byte(*cache++); + + /* write dummy crc16 */ + sd_raw_send_byte(0xff); + sd_raw_send_byte(0xff); + + /* wait while card is busy */ + while(sd_raw_rec_byte() != 0xff); + sd_raw_rec_byte(); + + /* deaddress card */ + unselect_card(); + + buffer += write_length; + offset += write_length; + length -= write_length; + +#if SD_RAW_WRITE_BUFFERING + raw_block_written = 1; +#endif + } + + return 1; +} +#endif + +#if DOXYGEN || SD_RAW_WRITE_SUPPORT +/** + * \ingroup sd_raw + * Writes a continuous data stream obtained from a callback function. + * + * This function starts writing at the specified offset. To obtain the + * next bytes to write, it calls the callback function. The callback fills the + * provided data buffer and returns the number of bytes it has put into the buffer. + * + * By returning zero, the callback may stop writing. + * + * \param[in] offset Offset where to start writing. + * \param[in] buffer Pointer to a buffer which is used for the callback function. + * \param[in] length Number of bytes to write in total. May be zero for endless writes. + * \param[in] callback The function used to obtain the bytes to write. + * \param[in] p An opaque pointer directly passed to the callback function. + * \returns 0 on failure, 1 on success + * \see sd_raw_read_interval, sd_raw_write, sd_raw_read + */ +uint8_t sd_raw_write_interval(offset_t offset, uint8_t* buffer, uintptr_t length, sd_raw_write_interval_handler_t callback, void* p) +{ +#if SD_RAW_SAVE_RAM + #error "SD_RAW_WRITE_SUPPORT is not supported together with SD_RAW_SAVE_RAM" +#endif + + if(!buffer || !callback) + return 0; + + uint8_t endless = (length == 0); + while(endless || length > 0) + { + uint16_t bytes_to_write = callback(buffer, offset, p); + if(!bytes_to_write) + break; + if(!endless && bytes_to_write > length) + return 0; + + /* as writing is always buffered, we directly + * hand over the request to sd_raw_write() + */ + if(!sd_raw_write(offset, buffer, bytes_to_write)) + return 0; + + offset += bytes_to_write; + length -= bytes_to_write; + } + + return 1; +} +#endif + +#if DOXYGEN || SD_RAW_WRITE_SUPPORT +/** + * \ingroup sd_raw + * Writes the write buffer's content to the card. + * + * \note When write buffering is enabled, you should + * call this function before disconnecting the + * card to ensure all remaining data has been + * written. + * + * \returns 0 on failure, 1 on success. + * \see sd_raw_write + */ +uint8_t sd_raw_sync() +{ +#if SD_RAW_WRITE_BUFFERING + if(raw_block_written) + return 1; + if(!sd_raw_write(raw_block_address, raw_block, sizeof(raw_block))) + return 0; + raw_block_written = 1; +#endif + return 1; +} +#endif + +/** + * \ingroup sd_raw + * Reads informational data from the card. + * + * This function reads and returns the card's registers + * containing manufacturing and status information. + * + * \note: The information retrieved by this function is + * not required in any way to operate on the card, + * but it might be nice to display some of the data + * to the user. + * + * \param[in] info A pointer to the structure into which to save the information. + * \returns 0 on failure, 1 on success. + */ +uint8_t sd_raw_get_info(struct sd_raw_info* info) +{ + if(!info || !sd_raw_available()) + return 0; + + memset(info, 0, sizeof(*info)); + + select_card(); + + /* read cid register */ + if(sd_raw_send_command(CMD_SEND_CID, 0)) + { + unselect_card(); + return 0; + } + while(sd_raw_rec_byte() != 0xfe); + for(uint8_t i = 0; i < 18; ++i) + { + uint8_t b = sd_raw_rec_byte(); + + switch(i) + { + case 0: + info->manufacturer = b; + break; + case 1: + case 2: + info->oem[i - 1] = b; + break; + case 3: + case 4: + case 5: + case 6: + case 7: + info->product[i - 3] = b; + break; + case 8: + info->revision = b; + break; + case 9: + case 10: + case 11: + case 12: + info->serial |= (uint32_t) b << ((12 - i) * 8); + break; + case 13: + info->manufacturing_year = b << 4; + break; + case 14: + info->manufacturing_year |= b >> 4; + info->manufacturing_month = b & 0x0f; + break; + } + } + + /* read csd register */ + uint8_t csd_read_bl_len = 0; + uint8_t csd_c_size_mult = 0; +#if SD_RAW_SDHC + uint16_t csd_c_size = 0; +#else + uint32_t csd_c_size = 0; +#endif + uint8_t csd_structure = 0; + if(sd_raw_send_command(CMD_SEND_CSD, 0)) + { + unselect_card(); + return 0; + } + while(sd_raw_rec_byte() != 0xfe); + for(uint8_t i = 0; i < 18; ++i) + { + uint8_t b = sd_raw_rec_byte(); + + if(i == 0) + { + csd_structure = b >> 6; + } + else if(i == 14) + { + if(b & 0x40) + info->flag_copy = 1; + if(b & 0x20) + info->flag_write_protect = 1; + if(b & 0x10) + info->flag_write_protect_temp = 1; + info->format = (b & 0x0c) >> 2; + } + else + { +#if SD_RAW_SDHC + if(csd_structure == 0x01) + { + switch(i) + { + case 7: + b &= 0x3f; + case 8: + case 9: + csd_c_size <<= 8; + csd_c_size |= b; + break; + } + if(i == 9) + { + ++csd_c_size; + info->capacity = (offset_t) csd_c_size * 512 * 1024; + } + } + else if(csd_structure == 0x00) +#endif + { + switch(i) + { + case 5: + csd_read_bl_len = b & 0x0f; + break; + case 6: + csd_c_size = b & 0x03; + csd_c_size <<= 8; + break; + case 7: + csd_c_size |= b; + csd_c_size <<= 2; + break; + case 8: + csd_c_size |= b >> 6; + ++csd_c_size; + break; + case 9: + csd_c_size_mult = b & 0x03; + csd_c_size_mult <<= 1; + break; + case 10: + csd_c_size_mult |= b >> 7; + + info->capacity = (uint32_t) csd_c_size << (csd_c_size_mult + csd_read_bl_len + 2); + break; + } + } + } + } + + unselect_card(); + + return 1; +} + diff --git a/sd_raw.h b/sd_raw.h new file mode 100644 index 0000000..6aa9750 --- /dev/null +++ b/sd_raw.h @@ -0,0 +1,148 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef SD_RAW_H +#define SD_RAW_H + +#include +#include "sd_raw_config.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup sd_raw + * + * @{ + */ +/** + * \file + * MMC/SD/SDHC raw access header (license: GPLv2 or LGPLv2.1) + * + * \author Roland Riegel + */ + +/** + * The card's layout is harddisk-like, which means it contains + * a master boot record with a partition table. + */ +#define SD_RAW_FORMAT_HARDDISK 0 +/** + * The card contains a single filesystem and no partition table. + */ +#define SD_RAW_FORMAT_SUPERFLOPPY 1 +/** + * The card's layout follows the Universal File Format. + */ +#define SD_RAW_FORMAT_UNIVERSAL 2 +/** + * The card's layout is unknown. + */ +#define SD_RAW_FORMAT_UNKNOWN 3 + +/** + * This struct is used by sd_raw_get_info() to return + * manufacturing and status information of the card. + */ +struct sd_raw_info +{ + /** + * A manufacturer code globally assigned by the SD card organization. + */ + uint8_t manufacturer; + /** + * A string describing the card's OEM or content, globally assigned by the SD card organization. + */ + uint8_t oem[3]; + /** + * A product name. + */ + uint8_t product[6]; + /** + * The card's revision, coded in packed BCD. + * + * For example, the revision value \c 0x32 means "3.2". + */ + uint8_t revision; + /** + * A serial number assigned by the manufacturer. + */ + uint32_t serial; + /** + * The year of manufacturing. + * + * A value of zero means year 2000. + */ + uint8_t manufacturing_year; + /** + * The month of manufacturing. + */ + uint8_t manufacturing_month; + /** + * The card's total capacity in bytes. + */ + offset_t capacity; + /** + * Defines wether the card's content is original or copied. + * + * A value of \c 0 means original, \c 1 means copied. + */ + uint8_t flag_copy; + /** + * Defines wether the card's content is write-protected. + * + * \note This is an internal flag and does not represent the + * state of the card's mechanical write-protect switch. + */ + uint8_t flag_write_protect; + /** + * Defines wether the card's content is temporarily write-protected. + * + * \note This is an internal flag and does not represent the + * state of the card's mechanical write-protect switch. + */ + uint8_t flag_write_protect_temp; + /** + * The card's data layout. + * + * See the \c SD_RAW_FORMAT_* constants for details. + * + * \note This value is not guaranteed to match reality. + */ + uint8_t format; +}; + +typedef uint8_t (*sd_raw_read_interval_handler_t)(uint8_t* buffer, offset_t offset, void* p); +typedef uintptr_t (*sd_raw_write_interval_handler_t)(uint8_t* buffer, offset_t offset, void* p); + +uint8_t sd_raw_init(); +uint8_t sd_raw_available(); +uint8_t sd_raw_locked(); + +uint8_t sd_raw_read(offset_t offset, uint8_t* buffer, uintptr_t length); +uint8_t sd_raw_read_interval(offset_t offset, uint8_t* buffer, uintptr_t interval, uintptr_t length, sd_raw_read_interval_handler_t callback, void* p); +uint8_t sd_raw_write(offset_t offset, const uint8_t* buffer, uintptr_t length); +uint8_t sd_raw_write_interval(offset_t offset, uint8_t* buffer, uintptr_t length, sd_raw_write_interval_handler_t callback, void* p); +uint8_t sd_raw_sync(); + +uint8_t sd_raw_get_info(struct sd_raw_info* info); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/sd_raw_config.h b/sd_raw_config.h new file mode 100644 index 0000000..ca88eaa --- /dev/null +++ b/sd_raw_config.h @@ -0,0 +1,139 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of either the GNU General Public License version 2 + * or the GNU Lesser General Public License version 2.1, both as + * published by the Free Software Foundation. + */ + +#ifndef SD_RAW_CONFIG_H +#define SD_RAW_CONFIG_H + +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +/** + * \addtogroup sd_raw + * + * @{ + */ +/** + * \file + * MMC/SD support configuration (license: GPLv2 or LGPLv2.1) + */ + +/** + * \ingroup sd_raw_config + * Controls MMC/SD write support. + * + * Set to 1 to enable MMC/SD write support, set to 0 to disable it. + */ +#define SD_RAW_WRITE_SUPPORT 1 + +/** + * \ingroup sd_raw_config + * Controls MMC/SD write buffering. + * + * Set to 1 to buffer write accesses, set to 0 to disable it. + * + * \note This option has no effect when SD_RAW_WRITE_SUPPORT is 0. + */ +#define SD_RAW_WRITE_BUFFERING 1 + +/** + * \ingroup sd_raw_config + * Controls MMC/SD access buffering. + * + * Set to 1 to save static RAM, but be aware that you will + * lose performance. + * + * \note When SD_RAW_WRITE_SUPPORT is 1, SD_RAW_SAVE_RAM will + * be reset to 0. + */ +#define SD_RAW_SAVE_RAM 1 + +/** + * \ingroup sd_raw_config + * Controls support for SDHC cards. + * + * Set to 1 to support so-called SDHC memory cards, i.e. SD + * cards with more than 2 gigabytes of memory. + */ +#define SD_RAW_SDHC 0 + +/** + * @} + */ + +/* defines for customisation of sd/mmc port access */ +#if defined(__AVR_ATmega8__) || \ + defined(__AVR_ATmega48__) || \ + defined(__AVR_ATmega48P__) || \ + defined(__AVR_ATmega88__) || \ + defined(__AVR_ATmega88P__) || \ + defined(__AVR_ATmega168__) || \ + defined(__AVR_ATmega168P__) || \ + defined(__AVR_ATmega328P__) + #define configure_pin_mosi() DDRB |= (1 << DDB3) + #define configure_pin_sck() DDRB |= (1 << DDB5) + #define configure_pin_ss() DDRB |= (1 << DDB2) + #define configure_pin_miso() DDRB &= ~(1 << DDB4) + + #define select_card() PORTB &= ~(1 << PORTB2) + #define unselect_card() PORTB |= (1 << PORTB2) +#elif defined(__AVR_ATmega16__) || \ + defined(__AVR_ATmega32__) + #define configure_pin_mosi() DDRB |= (1 << DDB5) + #define configure_pin_sck() DDRB |= (1 << DDB7) + #define configure_pin_ss() DDRB |= (1 << DDB4) + #define configure_pin_miso() DDRB &= ~(1 << DDB6) + + #define select_card() PORTB &= ~(1 << PORTB4) + #define unselect_card() PORTB |= (1 << PORTB4) +#elif defined(__AVR_ATmega64__) || \ + defined(__AVR_ATmega128__) || \ + defined(__AVR_ATmega169__) + #define configure_pin_mosi() DDRB |= (1 << DDB2) + #define configure_pin_sck() DDRB |= (1 << DDB1) + #define configure_pin_ss() DDRB |= (1 << DDB0) + #define configure_pin_miso() DDRB &= ~(1 << DDB3) + + #define select_card() PORTB &= ~(1 << PORTB0) + #define unselect_card() PORTB |= (1 << PORTB0) +#else + #error "no sd/mmc pin mapping available!" +#endif + +#define configure_pin_available() DDRC &= ~(1 << DDC4) +#define configure_pin_locked() DDRC &= ~(1 << DDC5) + +#define get_pin_available() (PINC & (1 << PINC4)) +#define get_pin_locked() (PINC & (1 << PINC5)) + +#if SD_RAW_SDHC + typedef uint64_t offset_t; +#else + typedef uint32_t offset_t; +#endif + +/* configuration checks */ +#if SD_RAW_WRITE_SUPPORT +#undef SD_RAW_SAVE_RAM +#define SD_RAW_SAVE_RAM 0 +#else +#undef SD_RAW_WRITE_BUFFERING +#define SD_RAW_WRITE_BUFFERING 0 +#endif + +#ifdef __cplusplus +} +#endif + +#endif + diff --git a/uart.c b/uart.c new file mode 100644 index 0000000..df5e1fd --- /dev/null +++ b/uart.c @@ -0,0 +1,198 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include + +#include "uart.h" + +/* some mcus have multiple uarts */ +#ifdef UDR0 +#define UBRRH UBRR0H +#define UBRRL UBRR0L +#define UDR UDR0 + +#define UCSRA UCSR0A +#define UDRE UDRE0 +#define RXC RXC0 + +#define UCSRB UCSR0B +#define RXEN RXEN0 +#define TXEN TXEN0 +#define RXCIE RXCIE0 + +#define UCSRC UCSR0C +#define URSEL +#define UCSZ0 UCSZ00 +#define UCSZ1 UCSZ01 +#define UCSRC_SELECT 0 +#else +#define UCSRC_SELECT (1 << URSEL) +#endif + +#ifndef USART_RXC_vect +#if defined(UART0_RX_vect) +#define USART_RXC_vect UART0_RX_vect +#elif defined(UART_RX_vect) +#define USART_RXC_vect UART_RX_vect +#elif defined(USART0_RX_vect) +#define USART_RXC_vect USART0_RX_vect +#elif defined(USART_RX_vect) +#define USART_RXC_vect USART_RX_vect +#elif defined(USART0_RXC_vect) +#define USART_RXC_vect USART0_RXC_vect +#elif defined(USART_RXC_vect) +#define USART_RXC_vect USART_RXC_vect +#else +#error "Uart receive complete interrupt not defined!" +#endif +#endif + +#define BAUD 9600UL +#define UBRRVAL (F_CPU/(BAUD*16)-1) +#define USE_SLEEP 1 + +void uart_init() +{ + /* set baud rate */ + UBRRH = UBRRVAL >> 8; + UBRRL = UBRRVAL & 0xff; + /* set frame format: 8 bit, no parity, 1 bit */ + UCSRC = UCSRC_SELECT | (1 << UCSZ1) | (1 << UCSZ0); + /* enable serial receiver and transmitter */ +#if !USE_SLEEP + UCSRB = (1 << RXEN) | (1 << TXEN); +#else + UCSRB = (1 << RXEN) | (1 << TXEN) | (1 << RXCIE); +#endif +} + +void uart_putc(uint8_t c) +{ + if(c == '\n') + uart_putc('\r'); + + /* wait until transmit buffer is empty */ + while(!(UCSRA & (1 << UDRE))); + + /* send next byte */ + UDR = c; +} + +void uart_putc_hex(uint8_t b) +{ + /* upper nibble */ + if((b >> 4) < 0x0a) + uart_putc((b >> 4) + '0'); + else + uart_putc((b >> 4) - 0x0a + 'a'); + + /* lower nibble */ + if((b & 0x0f) < 0x0a) + uart_putc((b & 0x0f) + '0'); + else + uart_putc((b & 0x0f) - 0x0a + 'a'); +} + +void uart_putw_hex(uint16_t w) +{ + uart_putc_hex((uint8_t) (w >> 8)); + uart_putc_hex((uint8_t) (w & 0xff)); +} + +void uart_putdw_hex(uint32_t dw) +{ + uart_putw_hex((uint16_t) (dw >> 16)); + uart_putw_hex((uint16_t) (dw & 0xffff)); +} + +void uart_putw_dec(uint16_t w) +{ + uint16_t num = 10000; + uint8_t started = 0; + + while(num > 0) + { + uint8_t b = w / num; + if(b > 0 || started || num == 1) + { + uart_putc('0' + b); + started = 1; + } + w -= b * num; + + num /= 10; + } +} + +void uart_putdw_dec(uint32_t dw) +{ + uint32_t num = 1000000000; + uint8_t started = 0; + + while(num > 0) + { + uint8_t b = dw / num; + if(b > 0 || started || num == 1) + { + uart_putc('0' + b); + started = 1; + } + dw -= b * num; + + num /= 10; + } +} + +void uart_puts(const char* str) +{ + while(*str) + uart_putc(*str++); +} + +void uart_puts_p(PGM_P str) +{ + while(1) + { + uint8_t b = pgm_read_byte_near(str++); + if(!b) + break; + + uart_putc(b); + } +} + +uint8_t uart_getc() +{ + /* wait until receive buffer is full */ +#if USE_SLEEP + uint8_t sreg = SREG; + sei(); + + while(!(UCSRA & (1 << RXC))) + sleep_mode(); + + SREG = sreg; +#else + while(!(UCSRA & (1 << RXC))); +#endif + + uint8_t b = UDR; + if(b == '\r') + b = '\n'; + + return b; +} + +EMPTY_INTERRUPT(USART_RXC_vect) + diff --git a/uart.h b/uart.h new file mode 100644 index 0000000..d94d93d --- /dev/null +++ b/uart.h @@ -0,0 +1,42 @@ + +/* + * Copyright (c) 2006-2012 by Roland Riegel + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#ifndef UART_H +#define UART_H + +#include +#include + +#ifdef __cplusplus +extern "C" +{ +#endif + +void uart_init(); + +void uart_putc(uint8_t c); + +void uart_putc_hex(uint8_t b); +void uart_putw_hex(uint16_t w); +void uart_putdw_hex(uint32_t dw); + +void uart_putw_dec(uint16_t w); +void uart_putdw_dec(uint32_t dw); + +void uart_puts(const char* str); +void uart_puts_p(PGM_P str); + +uint8_t uart_getc(); + +#ifdef __cplusplus +} +#endif + +#endif +