mirror of
https://github.com/enjoy-digital/litex.git
synced 2025-01-04 09:52:26 -05:00
1a4a6eb445
Libbase should keep its RAM usage to a minimum as it is meant to be executed before the SDRAM is up and running. (Having lots of code is OK though as we XIP from the flash)
440 lines
11 KiB
C
440 lines
11 KiB
C
/*
|
|
* Milkymist SoC (Software)
|
|
* Copyright (C) 2007, 2008, 2009, 2010 Sebastien Bourdeauducq
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, version 3 of the License.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <endian.h>
|
|
#include <console.h>
|
|
#include <blockdev.h>
|
|
|
|
#include <fatfs.h>
|
|
|
|
//#define DEBUG
|
|
|
|
#define BLOCK_SIZE 512
|
|
|
|
struct partition_descriptor {
|
|
unsigned char flags;
|
|
unsigned char start_head;
|
|
unsigned short start_cylinder;
|
|
unsigned char type;
|
|
unsigned char end_head;
|
|
unsigned short end_cylinder;
|
|
unsigned int start_sector;
|
|
unsigned int end_sector;
|
|
} __attribute__((packed));
|
|
|
|
struct firstsector {
|
|
unsigned char bootsector[446];
|
|
struct partition_descriptor partitions[4];
|
|
unsigned char signature[2];
|
|
} __attribute__((packed));
|
|
|
|
|
|
struct fat16_firstsector {
|
|
/* Common to FATxx */
|
|
char jmp[3];
|
|
char oem[8];
|
|
unsigned short bytes_per_sector;
|
|
unsigned char sectors_per_cluster;
|
|
unsigned short reserved_sectors;
|
|
unsigned char number_of_fat;
|
|
unsigned short max_root_entries;
|
|
unsigned short total_sectors_short;
|
|
unsigned char media_descriptor;
|
|
unsigned short sectors_per_fat;
|
|
unsigned short sectors_per_track;
|
|
unsigned short head_count;
|
|
unsigned int hidden_sectors;
|
|
unsigned int total_sectors;
|
|
|
|
/* FAT16 specific */
|
|
unsigned char drive_nr;
|
|
unsigned char reserved;
|
|
unsigned char ext_boot_signature;
|
|
unsigned int id;
|
|
unsigned char volume_label[11];
|
|
unsigned char fstype[8];
|
|
unsigned char bootcode[448];
|
|
unsigned char signature[2];
|
|
} __attribute__((packed));
|
|
|
|
struct directory_entry {
|
|
unsigned char filename[8];
|
|
unsigned char extension[3];
|
|
unsigned char attributes;
|
|
unsigned char reserved;
|
|
unsigned char create_time_ms;
|
|
unsigned short create_time;
|
|
unsigned short create_date;
|
|
unsigned short last_access;
|
|
unsigned short ea_index;
|
|
unsigned short lastm_time;
|
|
unsigned short lastm_date;
|
|
unsigned short first_cluster;
|
|
unsigned int file_size;
|
|
} __attribute__((packed));
|
|
|
|
struct directory_entry_lfn {
|
|
unsigned char seq;
|
|
unsigned short name1[5]; /* UTF16 */
|
|
unsigned char attributes;
|
|
unsigned char reserved;
|
|
unsigned char checksum;
|
|
unsigned short name2[6];
|
|
unsigned short first_cluster;
|
|
unsigned short name3[2];
|
|
} __attribute__((packed));
|
|
|
|
#define PARTITION_TYPE_FAT16 0x06
|
|
#define PARTITION_TYPE_FAT32 0x0b
|
|
|
|
static int fatfs_partition_start_sector; /* Sector# of the beginning of the FAT16 partition */
|
|
|
|
static int fatfs_sectors_per_cluster;
|
|
static int fatfs_fat_sector; /* Sector of the first FAT */
|
|
static int fatfs_fat_entries; /* Number of entries in the FAT */
|
|
static int fatfs_max_root_entries;
|
|
static int fatfs_root_table_sector; /* Sector# of the beginning of the root table */
|
|
|
|
static int fatfs_fat_cached_sector;
|
|
static unsigned short int fatfs_fat_sector_cache[BLOCK_SIZE/2];
|
|
|
|
static int fatfs_dir_cached_sector;
|
|
static struct directory_entry fatfs_dir_sector_cache[BLOCK_SIZE/sizeof(struct directory_entry)];
|
|
|
|
static int fatfs_data_start_sector;
|
|
|
|
int fatfs_init(int devnr)
|
|
{
|
|
struct firstsector s0;
|
|
struct fat16_firstsector s;
|
|
int i;
|
|
|
|
if(!bd_init(devnr)) {
|
|
printf("E: Unable to initialize memory card driver\n");
|
|
return 0;
|
|
}
|
|
|
|
if(bd_has_part_table(devnr)) {
|
|
/* Read sector 0, with partition table */
|
|
if(!bd_readblock(0, (void *)&s0)) {
|
|
printf("E: Unable to read block 0\n");
|
|
return 0;
|
|
}
|
|
|
|
fatfs_partition_start_sector = -1;
|
|
for(i=0;i<4;i++)
|
|
if((s0.partitions[i].type == PARTITION_TYPE_FAT16)
|
|
||(s0.partitions[i].type == PARTITION_TYPE_FAT32)) {
|
|
#ifdef DEBUG
|
|
printf("I: Using partition #%d: start sector %08x, end sector %08x\n", i,
|
|
le32toh(s0.partitions[i].start_sector), le32toh(s0.partitions[i].end_sector));
|
|
#endif
|
|
fatfs_partition_start_sector = le32toh(s0.partitions[i].start_sector);
|
|
break;
|
|
}
|
|
if(fatfs_partition_start_sector == -1) {
|
|
printf("E: No FAT partition was found\n");
|
|
return 0;
|
|
}
|
|
} else
|
|
fatfs_partition_start_sector = 0;
|
|
|
|
/* Read first FAT16 sector */
|
|
if(!bd_readblock(fatfs_partition_start_sector, (void *)&s)) {
|
|
printf("E: Unable to read first FAT sector\n");
|
|
return 0;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
char oem[9];
|
|
char volume_label[12];
|
|
memcpy(oem, s.oem, 8);
|
|
oem[8] = 0;
|
|
memcpy(volume_label, s.volume_label, 11);
|
|
volume_label[11] = 0;
|
|
printf("I: OEM name: %s\n", oem);
|
|
printf("I: Volume label: %s\n", volume_label);
|
|
}
|
|
#endif
|
|
|
|
if(le16toh(s.bytes_per_sector) != BLOCK_SIZE) {
|
|
printf("E: Unexpected number of bytes per sector (%d)\n", le16toh(s.bytes_per_sector));
|
|
return 0;
|
|
}
|
|
fatfs_sectors_per_cluster = s.sectors_per_cluster;
|
|
|
|
fatfs_fat_entries = (le16toh(s.sectors_per_fat)*BLOCK_SIZE)/2;
|
|
fatfs_fat_sector = fatfs_partition_start_sector + 1;
|
|
fatfs_fat_cached_sector = -1;
|
|
|
|
fatfs_max_root_entries = le16toh(s.max_root_entries);
|
|
fatfs_root_table_sector = fatfs_fat_sector + s.number_of_fat*le16toh(s.sectors_per_fat);
|
|
fatfs_dir_cached_sector = -1;
|
|
|
|
fatfs_data_start_sector = fatfs_root_table_sector + (fatfs_max_root_entries*sizeof(struct directory_entry))/BLOCK_SIZE;
|
|
|
|
if(fatfs_max_root_entries == 0) {
|
|
printf("E: Your memory card uses FAT32, which is not supported.\n");
|
|
printf("E: Please reformat your card using FAT16, e.g. use mkdosfs -F 16\n");
|
|
printf("E: FAT32 support would be an appreciated contribution.\n");
|
|
return 0;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
printf("I: Cluster is %d sectors, FAT has %d entries, FAT 1 is at sector %d,\nI: root table is at sector %d (max %d), data is at sector %d, nb of fat: %d\n",
|
|
fatfs_sectors_per_cluster, fatfs_fat_entries, fatfs_fat_sector,
|
|
fatfs_root_table_sector, fatfs_max_root_entries,
|
|
fatfs_data_start_sector, s.number_of_fat);
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
static int fatfs_read_fat(int offset)
|
|
{
|
|
int wanted_sector;
|
|
|
|
if((offset < 0) || (offset >= fatfs_fat_entries)) {
|
|
printf("E: Incorrect offset %d in fatfs_read_fat\n", offset);
|
|
return -1;
|
|
}
|
|
|
|
wanted_sector = fatfs_fat_sector + (offset*2)/BLOCK_SIZE;
|
|
if(wanted_sector != fatfs_fat_cached_sector) {
|
|
if(!bd_readblock(wanted_sector, (void *)&fatfs_fat_sector_cache)) {
|
|
printf("E: Memory card failed (FAT), sector %d\n", wanted_sector);
|
|
return -1;
|
|
}
|
|
fatfs_fat_cached_sector = wanted_sector;
|
|
}
|
|
|
|
return le16toh(fatfs_fat_sector_cache[offset % (BLOCK_SIZE/2)]);
|
|
}
|
|
|
|
static const struct directory_entry *fatfs_read_root_directory(int offset)
|
|
{
|
|
int wanted_sector;
|
|
|
|
if((offset < 0) || (offset >= fatfs_max_root_entries))
|
|
return NULL;
|
|
|
|
wanted_sector = fatfs_root_table_sector + (offset*sizeof(struct directory_entry))/BLOCK_SIZE;
|
|
|
|
if(wanted_sector != fatfs_dir_cached_sector) {
|
|
if(!bd_readblock(wanted_sector, (void *)&fatfs_dir_sector_cache)) {
|
|
printf("E: Memory card failed (Rootdir), sector %d\n", wanted_sector);
|
|
return NULL;
|
|
}
|
|
fatfs_dir_cached_sector = wanted_sector;
|
|
}
|
|
return &fatfs_dir_sector_cache[offset % (BLOCK_SIZE/sizeof(struct directory_entry))];
|
|
}
|
|
|
|
static void lfn_to_ascii(const struct directory_entry_lfn *entry, char *name, int terminate)
|
|
{
|
|
int i;
|
|
unsigned short c;
|
|
|
|
for(i=0;i<5;i++) {
|
|
c = le16toh(entry->name1[i]);
|
|
if(c <= 255) {
|
|
*name = c;
|
|
name++;
|
|
if(c == 0) return;
|
|
}
|
|
}
|
|
for(i=0;i<6;i++) {
|
|
c = le16toh(entry->name2[i]);
|
|
if(c <= 255) {
|
|
*name = c;
|
|
name++;
|
|
if(c == 0) return;
|
|
}
|
|
}
|
|
for(i=0;i<2;i++) {
|
|
c = le16toh(entry->name3[i]);
|
|
if(c <= 255) {
|
|
*name = c;
|
|
name++;
|
|
if(c == 0) return;
|
|
}
|
|
}
|
|
|
|
if(terminate)
|
|
*name = 0;
|
|
}
|
|
|
|
static int fatfs_is_regular(const struct directory_entry *entry)
|
|
{
|
|
return ((entry->attributes & 0x10) == 0)
|
|
&& ((entry->attributes & 0x08) == 0)
|
|
&& (entry->filename[0] != 0xe5);
|
|
}
|
|
|
|
int fatfs_list_files(fatfs_dir_callback cb, void *param)
|
|
{
|
|
const struct directory_entry *entry;
|
|
char fmtbuf[8+1+3+1];
|
|
char longname[131];
|
|
int has_longname;
|
|
int i, j, k;
|
|
|
|
has_longname = 0;
|
|
longname[sizeof(longname)-1] = 0; /* avoid crashing when reading a corrupt FS */
|
|
for(k=0;k<fatfs_max_root_entries;k++) {
|
|
entry = fatfs_read_root_directory(k);
|
|
#ifdef DEBUG
|
|
printf("I: Read entry with attribute %02x\n", entry->attributes);
|
|
#endif
|
|
if(entry->attributes == 0x0f) {
|
|
const struct directory_entry_lfn *entry_lfn;
|
|
unsigned char frag;
|
|
int terminate;
|
|
|
|
entry_lfn = (const struct directory_entry_lfn *)entry;
|
|
frag = entry_lfn->seq & 0x3f;
|
|
terminate = entry_lfn->seq & 0x40;
|
|
if(frag*13 < sizeof(longname)) {
|
|
lfn_to_ascii((const struct directory_entry_lfn *)entry, &longname[(frag-1)*13], terminate);
|
|
if(frag == 1) has_longname = 1;
|
|
}
|
|
continue;
|
|
} else {
|
|
if(!fatfs_is_regular(entry)) {
|
|
has_longname = 0;
|
|
continue;
|
|
}
|
|
}
|
|
if(entry == NULL) return 0;
|
|
if(entry->filename[0] == 0) {
|
|
has_longname = 0;
|
|
break;
|
|
}
|
|
j = 0;
|
|
for(i=0;i<8;i++) {
|
|
if(entry->filename[i] == ' ') break;
|
|
fmtbuf[j++] = entry->filename[i];
|
|
}
|
|
fmtbuf[j++] = '.';
|
|
for(i=0;i<3;i++) {
|
|
if(entry->extension[i] == ' ') break;
|
|
fmtbuf[j++] = entry->extension[i];
|
|
}
|
|
fmtbuf[j++] = 0;
|
|
if(!cb(fmtbuf, has_longname ? longname : fmtbuf, param)) return 0;
|
|
has_longname = 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static const struct directory_entry *fatfs_find_file_by_name(const char *filename)
|
|
{
|
|
char searched_filename[8];
|
|
char searched_extension[3];
|
|
char *dot;
|
|
const char *c;
|
|
int i;
|
|
const struct directory_entry *entry;
|
|
|
|
dot = strrchr(filename, '.');
|
|
if(dot == NULL)
|
|
return NULL;
|
|
|
|
memset(searched_filename, ' ', 8);
|
|
memset(searched_extension, ' ', 3);
|
|
i = 0;
|
|
for(c=filename;c<dot;c++)
|
|
searched_filename[i++] = toupper(*c);
|
|
|
|
i = 0;
|
|
for(c=dot+1;*c!=0;c++)
|
|
searched_extension[i++] = toupper(*c);
|
|
|
|
for(i=0;i<fatfs_max_root_entries;i++) {
|
|
entry = fatfs_read_root_directory(i);
|
|
if(entry == NULL) break;
|
|
if(entry->filename[0] == 0) break;
|
|
if(!fatfs_is_regular(entry)) continue;
|
|
if(!memcmp(searched_filename, entry->filename, 8)
|
|
&&!memcmp(searched_extension, entry->extension, 3))
|
|
return entry;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int fatfs_load_cluster(int clustern, char *buffer, int maxsectors)
|
|
{
|
|
int startsector;
|
|
int i;
|
|
int toread;
|
|
|
|
clustern = clustern - 2;
|
|
startsector = fatfs_data_start_sector + clustern*fatfs_sectors_per_cluster;
|
|
if(maxsectors < fatfs_sectors_per_cluster)
|
|
toread = maxsectors;
|
|
else
|
|
toread = fatfs_sectors_per_cluster;
|
|
for(i=0;i<toread;i++)
|
|
if(!bd_readblock(startsector+i, (unsigned char *)buffer+i*BLOCK_SIZE)) {
|
|
printf("E: Memory card failed (Cluster), sector %d\n", startsector+i);
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int fatfs_load(const char *filename, char *buffer, int size, int *realsize)
|
|
{
|
|
const struct directory_entry *entry;
|
|
int cluster_size;
|
|
int cluster;
|
|
int n;
|
|
|
|
cluster_size = fatfs_sectors_per_cluster*BLOCK_SIZE;
|
|
size /= BLOCK_SIZE;
|
|
|
|
entry = fatfs_find_file_by_name(filename);
|
|
if(entry == NULL) {
|
|
printf("E: File not found: %s\n", filename);
|
|
return 0;
|
|
}
|
|
|
|
if(realsize != NULL) *realsize = le32toh(entry->file_size);
|
|
|
|
n = 0;
|
|
cluster = le16toh(entry->first_cluster);
|
|
while(size > 0) {
|
|
if(!fatfs_load_cluster(cluster, buffer+n*cluster_size, size))
|
|
return 0;
|
|
size -= fatfs_sectors_per_cluster;
|
|
n++;
|
|
cluster = fatfs_read_fat(cluster);
|
|
if(cluster >= 0xFFF8) break;
|
|
if(cluster == -1) return 0;
|
|
}
|
|
//putsnonl("\n");
|
|
|
|
return n*cluster_size;
|
|
}
|
|
|
|
void fatfs_done(void)
|
|
{
|
|
bd_done();
|
|
}
|