diff --git a/master/master/lib/logger.c b/master/master/lib/logger.c new file mode 100644 --- /dev/null +++ b/master/master/lib/logger.c @@ -0,0 +1,227 @@ +/* + * CFile1.c + * + * Created: 11/7/2012 8:05:44 PM + * Author: mkanning + */ + +#include +#include +#include +#include "sd/fat.h" +#include "sd/fat_config.h" +#include "sd/partition.h" +#include "sd/sd_raw.h" +#include "sd/sd_raw_config.h" +#include "logger.h" + +/* + //console prompts and responses + * 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. + + //config edits + * 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. + + */ + +void logger_setup() +{ + while(1) + { + //check for sd exist/power/ready + /* setup sd card slot */ + if(!sd_raw_init()) //sd_raw.c + { + #if DEBUG + uart_puts_p(PSTR("MMC/SD initialization failed\n")); + #endif + continue; + } + + + //create partition BEGIN + /* open first partition */ + struct partition_struct* partition = partition_open(sd_raw_read, + sd_raw_read_interval, + #if SD_RAW_WRITE_SUPPORT //probably want this to be true ?? + sd_raw_write, + sd_raw_write_interval, + #else + 0, + 0, + #endif + 0 + ); + //check that partition was created correctly + 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 //probably want this to be true ?? + 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 partition END + + + //open file system BEGIN + /* open file system */ + struct fat_fs_struct* fs = fat_open(partition); + if(!fs) + { + #if DEBUG + uart_puts_p(PSTR("opening file system failed\n")); + #endif + continue; + } + //open file system END + + + //open directory BEGIN + /* 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; + } + //open directory END + + + //simplified version of console BEGIN + /* provide a simple shell */ + char buffer[24]; + while(1) + { + /* execute command */ + /* search file in current directory and open it */ + struct fat_file_struct* fd = open_file_in_dir(fs, dd, "data.csv"); //logger.h + if(!fd) + { + //error open file handling + continue; + } + + int32_t offset = 5;//strtolong(offset_value); + if(!fat_seek_file(fd, &offset, FAT_SEEK_SET)) //seek to begin or end or what ?? + { + //error seek to file handling + + fat_close_file(fd); + continue; + } + + /* read text from the shell and write it to the file */ + uint8_t data_len; + while(1) + { + /* 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); //may want to leave file open ?? + } + //simplified version of console END + + + //prepare for closing SD connection BEGIN + /* close directory */ + fat_close_dir(dd); //fat.c + + /* close file system */ + fat_close(fs); //fat.c + + /* close partition */ + partition_close(partition); //partition.c + //prepare for closing SD connection END + + } +} + +//writes a single line to the SD card +uint8_t logger_writeLine(char* dateLine, uint8_t length) +{ + +} + +//i think opens a file so it can be read/written +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); //fat.h +} + +//i think searches for a file +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; +} + +//i think this initializes the SD for read and write ?? \ No newline at end of file diff --git a/master/master/lib/logger.h b/master/master/lib/logger.h new file mode 100644 --- /dev/null +++ b/master/master/lib/logger.h @@ -0,0 +1,17 @@ +/* + * logger.h + * + * Created: 11/7/2012 8:06:16 PM + * Author: mkanning + */ + + +#ifndef LOGGER_H_ +#define LOGGER_H_ + +void logger_setup(); +uint8_t logger_writeLine(char* dateLine, uint8_t length); +struct fat_file_struct* open_file_in_dir(struct fat_fs_struct* fs, struct fat_dir_struct* dd, const char* name); +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); + +#endif /* LOGGER_H_ */ \ No newline at end of file diff --git a/master/master/lib/sd/ChangeLog b/master/master/lib/sd/ChangeLog new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/ChangeLog @@ -0,0 +1,124 @@ + +2012-06-12 sd-reader + * fix capacity readout from csd register depending on format version + * fix gcc strict-aliasing warnings (also somewhat enlarges code size) + +2011-04-23 sd-reader + * fix FAT access for cluster numbers beyond 2^15 (for FAT16) and 2^30 (for FAT32) (thanks to Darwin Engwer for testing) + * correctly return disk-full condition from fat_write_file() on certain conditions + * use byteorder memory access functions for fat_fs_get_free() + * be more specific on the return value of fat_write_file() + +2011-02-05 sd-reader + * implement renaming a file or directory + * rewrite byteorder handling to fix unaligned memory accesses on 32-bit and probably 16-bit architectures + * make fat_create_file() not return failure if the file already exists + * make the "cat" output respect the count of bytes actually read + * document how to use fat_seek_file() for retrieving the file position + +2010-10-10 sd-reader + * Fix equal file names when reading two successive 8.3 directory entries. + * Fix calculation of cluster positions beyond 4GB (32 bit integer overflow). + * Fix endless looping of directory listing (occured with valid entry at end of last cluster). + +2010-01-10 sd-reader + * Make LFN support configurable. + * Ignore LFN directory entries without 8.3 name. + * Ignore LFN directory entries which do not match the 8.3 name's checksum. + * Implement delayed directory entry updates (disabled by default) (thanks to Adam Mayer). + * Speedup search for free cluster. + * Fix memory leak when using the "init" command (thanks to Tibor Vilhan). + * Fix ATmega328P-specific pin mappings. + * Add some of the picoPower MCU variants. + +2009-03-30 sd-reader + * Make 8.3 basename and/or extension lowercase when told by Windows NT and later. + * Add ATmega328 pin configuration. + * Fix MMC/SD/SDHC distinction. + * Fix raw block read/write buffering (thanks to Kurt Sterckx). + * Fix fat size calculation for FAT16 when configured with FAT32. + * Fix compilation for read-only configurations. + * Fix card lock detection. + * Make it easier to link with a C++ application (thanks to Jérôme Despatis). + +2008-11-21 sd-reader + * Support for SDHC cards (disabled by default). + * Support for FAT32 (disabled by default). + +2008-06-08 sd-reader + * New "init" command to allow reinitialization of memory card. + * Fix searching through multi-cluster directories. + * Fix reading directory entries spanning a cluster border (backport from mega-eth). + * Do not abort the whole lfn entry when the file name is too long, just drop single characters (backport from mega-eth). + * Change fat16_get_dir_entry_of_path() to ignore a slash at the end (backport from mega-eth). + * Make listing a directory's content much faster (backport from mega-eth). + * Shrink code size by centralizing cluster offset calculation (backport from mega-eth). + * Some other small fixes and optimizations. + +2007-12-13 sd-reader + * Dual-license the major implementation modules under GPL and LGPL. + +2007-06-03 sd-reader + * Fix reading beyond cached block (by Benjamin Meier). + * Implement support for reading and writing file modification dates/times. + (Thanks to Torsten Seeboth for testing.) + +2007-03-01 sd-reader + * Avoid LFN directory entries for the "." and ".." directory references. + This prevented Windows from deleting directories. + * Handle special case where the 8.3 filename begins with 0xe5. + * Fix return value of fat16_delete_file() when deleting empty files. + * Fix fat16_clear_cluster() which was zeroing only 16 of every 32 bytes. + +2007-01-20 sd-reader + * Fix directory creation. + - Correctly create "." and ".." directory entries (8.3 <-> lfn versions). + - Correctly clear cluster containing the directory entries for new directory. + +2006-11-01 sd-reader + * Implement creation and deletion of directories. + * Clear the directory entries of new directory clusters. + * Prevent linkage against printf(). + * Make the use of malloc()/free() optional. + +2006-09-01 sd-reader + * Fix shortening files. + * Fix free disk space calculation. + +2006-08-24 sd-reader + * Improve sleep handling. + * Display extended card information on boot and + when executing the "disk" shell command. + * Correctly determine FAT type by cluster count. + * Fix cluster allocation beyond card capacity. + +2006-08-16 sd-reader + * Provide FAT16 capacity and usage information. + * Implement the backspace key in the mini shell. + * Enter idle mode when waiting for uart activity. + * Make the Card Select pin MCU dependent as well. + * Add mini shell commands to documentation. + +2006-08-08 sd-reader + * Thanks go to Torsten Seeboth for his ongoing efforts + to test changes, fix regressions and give suggestions. + Many of the changes below were initiated by him. + * Much more reliable card initialization. + * Highly improved performance + - optional write buffering + - better cluster handling + - remove unneeded SPI access when reading from buffered block + - use highest spi frequency after card initialization + * Add superfloppy support. + * Better checks when opening a FAT16 filesystem. + * Provide SPI pin mappings for commonly used ATmegas. + * Fix resizing files, hangs could occur. + * Fix overflow when creating files with names longer than 31 characters. + * Fix numerous other small things. + +2006-03-19 sd-reader + * Fix speed regressions. + +2006-03-16 sd-reader + * Initial release. + diff --git a/master/master/lib/sd/FAQ b/master/master/lib/sd/FAQ new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/FAQ @@ -0,0 +1,124 @@ +Frequently Asked Questions for sd-reader +======================================== + +General +------- + +Q: Which cards are supported? +A: All MMC/SD/SDHC/miniSD/microSD/microSDHC should work, although not all variants have been tested. + Some very old (low capacity) cards might be broken as well. Cards with a capacity of 16 MB or + less are usually formatted with FAT12, so these are supported in raw mode only, if at all. + +Q: What data rates can I expect? +A: See the benchmark page on the homepage. + http://www.roland-riegel.de/sd-reader/benchmarks/ + +Q: Are there boards available for purchase? +A: No. + +Hardware +-------- + +Q: Where can I find the schematic? +A: Get it on the homepage. + http://www.roland-riegel.de/sd-reader/sd-reader_circuit_latest.zip + +Q: What if my card socket has no Card-Detect and/or Card-Lock switches? +A: Change sd_raw_config.h such that it looks like + + #define configure_pin_available() /* nothing */ + #define configure_pin_locked() /* nothing */ + + #define get_pin_available() 0 + #define get_pin_locked() 1 + +Q: All attempts to write to the card fail, although reading works. What's the problem? +A: Enable write support within sd_raw_config.h. And probably, your card socket has no Card-lock + detection (see question above). + +Q: The card initialization fails. What can I do? +A: Usually this is some kind of hardware problem. + * Check the physical connections. + * Keep the signal lines to the card as short as possible. + * Do not use diodes to derive the card's supply voltage. Use a 3.3V voltage regulator instead. + * Have a stable, buffered power supply and use capacitors for correct voltage regulator + operation (see the schematics linked above). + * Use extra capacitors of 50uF and 100nF as close to the card as possible. + * When using an integrated level shifter or no level shifting at all (see the next question), + try adding a pullup of 50k from the data-out line of the card to 3.3V. + * Make sure the limiting frequency of the level shifter is not exceeded. Have a look into its + datasheet! + * Check the signals with a scope. + +Q: What alternatives to resistor level shifting exist? +A: If you want to use additional devices with SPI or the resistor solution appears too ugly, there + are two possibilities. Either operate the whole circuit with a single 3.3V supply and no level + shifting at all or use a level shifting IC which interfaces the memory card to the AVR. + Depending on your requirements, adequate devices could include MAX3378E, MAX3392E, MAX3395E, + 74LVC245 and 74HCT4050 (optionally together with 74HCT125). Please check the datasheets for the + required DC/AC characteristics! + +Software +-------- + +Q: What's the software license? +A: GPLv2 or (for most parts) LGPLv2.1. Before using a file, read its license terms included at the + beginning of the file. + +Q: What's the programming language used? +A: It's C, in compliance with the ISO C99 standard. + +Q: What are these .patch-files provided? +A: Those record the source code differences between the old and new release. If you edited your + private sd-reader version, it might be easier to apply the patch files using the "patch" utility + common on Unix-like systems, rather than manually inserting the changes by hand. For Windows + users, the GnuWin32 project provides a port of "patch". + +Q: Where can I learn more about the library interface and how to use it? +A: Look into the HTML documentation provided online or within each source distribution. Also take + the provided main.c as an example application and as a starting point. + +Q: How do I adapt it to an ATmegaXYZ and my circuit in particular? +A: Add your MCU-specific pin configuration to sd_raw_config.h. Some commonly used ones are already + included. + +Q: How do I adapt it to a different MCU clock? +A: Change the MCU_FREQ variable within the Makefile. + +Q: How do I adapt it to some different MCU architecture? +A: Change sd_raw_init(), sd_raw_send_byte(), sd_raw_rec_byte() within sd_raw.c and the pin + definitions within sd_raw_config.h appropriately. + +Q: Can the library be used with Arduino? +A: Yes. I do not have any experience with Arduino myself, but people report that it works with some + minor modifications to the library code needed due to some different compiler settings. Please + search the web for details. + +Q: Can I use software SPI? +A: Yes, but the code is not prepared for it. + +Q: My application crashes somewhere in the lib. Is there some bug hidden? +A: There might be a bug. Much more likely, however, is that you experience memory problems, + especially heap/stack collisions. The crashes can appear everywhere, but typically this is not + the place where the real problem is. Try to minimize the size of structures and other memory + blocks your application uses. Sum up the amount of memory your application allocates with global + and local variables and the memory you allocate dynamically with malloc(). The avr-nm utility + also helps a lot here. When called with the "--print-size --size-sort" parameters, it lists all + symbols and their code/memory size within the given file. See the avr-libc FAQ and the nm manual + for further information. + http://www.nongnu.org/avr-libc/user-manual/FAQ.html#faq_ramoverlap + http://sourceware.org/binutils/docs/binutils/nm.html + +Q: Opening the FAT filesystem fails. What can I do? +A: Make sure there is a FAT16 or FAT32 filesystem on the card. This usually isn't possible for old + cards with 16 MB or less. For larger ones, format the cards using the OS utilities, like the + Windows Explorer, the Unix/Linux mkdosfs command or special SD card format tools. + http://panasonic.jp/support/global/cs/sd/download/sd_formatter.html + http://www.sdcard.org/consumers/formatter/ + +Q: Writing to the card returns no failure, but when checking the file's content the data is not + there. What happens? +A: For performance reasons, writing to the card is always buffered. Before pulling the card out of + its socket (or issuing a reset of the MCU), make sure sd_raw_sync() gets called such that all + buffered data is written out to permanent card storage. + diff --git a/master/master/lib/sd/byteordering.c b/master/master/lib/sd/byteordering.c new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/byteordering.h b/master/master/lib/sd/byteordering.h new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/doc/Thumbs.db b/master/master/lib/sd/doc/Thumbs.db new file mode 100644 index 0000000000000000000000000000000000000000..ffd4e57f63c1edb4c2aa8605b979ebe8bd97f351 GIT binary patch literal 31744 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
    >RVB9T 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 + * + * 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/master/master/lib/sd/fat.h b/master/master/lib/sd/fat.h new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/fat_config.h b/master/master/lib/sd/fat_config.h new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/partition.c b/master/master/lib/sd/partition.c new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/partition.h b/master/master/lib/sd/partition.h new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/partition_config.h b/master/master/lib/sd/partition_config.h new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/sd-reader_config.h b/master/master/lib/sd/sd-reader_config.h new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/sd_raw.c b/master/master/lib/sd/sd_raw.c new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/sd_raw.h b/master/master/lib/sd/sd_raw.h new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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/master/master/lib/sd/sd_raw_config.h b/master/master/lib/sd/sd_raw_config.h new file mode 100644 --- /dev/null +++ b/master/master/lib/sd/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!" //commented out due to build error ?? +#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/master/master/lib/serparser.c b/master/master/lib/serparser.c --- a/master/master/lib/serparser.c +++ b/master/master/lib/serparser.c @@ -118,7 +118,6 @@ int uart_Parser(void) } } - } else { diff --git a/master/master/master.cproj b/master/master/master.cproj --- a/master/master/master.cproj +++ b/master/master/master.cproj @@ -126,12 +126,54 @@ compile + + compile + compile compile + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + + + compile + compile @@ -150,6 +192,7 @@ + \ No newline at end of file