/* ********************************************************************* * Broadcom Common Firmware Environment (CFE) * * FAT file system File: cfe_fatfs.c * * This module knows how to read files from a FAT formatted * file system (12 or 16 bit fats only for now) * * Eventually, we'll also include support for the FAT Translation * Layer (FTL) on PCMCIA flash file systems. * * Author: Mitch Lichtenberg * ********************************************************************* * * Copyright 2000,2001,2002,2003 * Broadcom Corporation. All rights reserved. * * This software is furnished under license and may be used and * copied only in accordance with the following terms and * conditions. Subject to these conditions, you may download, * copy, install, use, modify and distribute modified or unmodified * copies of this software in source and/or binary form. No title * or ownership is transferred hereby. * * 1) Any source code used, modified or distributed must reproduce * and retain this copyright notice and list of conditions * as they appear in the source file. * * 2) No right is granted to use any trade name, trademark, or * logo of Broadcom Corporation. The "Broadcom Corporation" * name may not be used to endorse or promote products derived * from this software without the prior written permission of * Broadcom Corporation. * * 3) THIS SOFTWARE IS PROVIDED "AS-IS" AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, ANY IMPLIED * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR * PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT * SHALL BROADCOM BE LIABLE FOR ANY DAMAGES WHATSOEVER, AND IN * PARTICULAR, BROADCOM SHALL NOT BE LIABLE FOR DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE), EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. ********************************************************************* */ #include "cfe.h" #include "cfe_fileops.h" #include "cfe_loader.h" /* ********************************************************************* * Constants ********************************************************************* */ #define SECTORSIZE 512 #define DIRENTRYSIZE 32 #define DIRPERSECTOR (SECTORSIZE/DIRENTRYSIZE) /*#define _FATFS_DEBUG_*/ /* * Bios Parameter Block offsets and values */ #define BPB_JMPINSTR 0x00 #define BPB_JMPINSTR_VALUE 0xEB #define BPB_JMPINSTR_VALUE2 0xE9 #define BPB_SEAL 0x1FE #define BPB_SEAL_VALUE 0xAA55 #define BPB_BYTESPERSECTOR 0x0B #define BPB_SECTORSPERCLUSTER 0x0D #define BPB_RESERVEDSECTORS 0x0E #define BPB_NUMFATS 0x10 #define BPB_MAXROOTDIR 0x11 #define BPB_TOTALSECTORS 0x13 #define BPB_SECTORSPERFAT 0x16 #define BPB_SECTORSPERTRACK 0x18 #define BPB_NUMHEADS 0x1A #define BPB_HIDDENSECTORS 0x1C #define BPB_SYSTEMID 54 #define BPB_MEDIADESCRIPTOR 21 #define BPB_SIGNATURE 38 #define BPB_SIGNATURE_VALUE1 0x28 #define BPB_SIGNATURE_VALUE2 0x29 /* * Partition types */ #define PARTTYPE_EMPTY 0 #define PARTTYPE_FAT12 1 #define PARTTYPE_FAT16 4 #define PARTTYPE_FAT16BIG 6 #define PARTTYPE_FAT32 0x0B /* * Partition table offsets */ #define PTABLE_STATUS 0 #define PTABLE_STARTHEAD 1 #define PTABLE_STARTSECCYL 2 /* 2 bytes */ #define PTABLE_TYPE 4 #define PTABLE_ENDHEAD 5 #define PTABLE_ENDSECCYL 6 /* 2 bytes */ #define PTABLE_BOOTSECTOR 8 /* 4 bytes */ #define PTABLE_NUMSECTORS 12 /* 4 bytes */ #define PTABLE_SIZE 16 #define PTABLE_COUNT 4 #define PTABLE_OFFSET (512-2-(PTABLE_COUNT*PTABLE_SIZE)) #define PTABLE_STATUS_ACTIVE 0x80 /* * Directory attributes */ #define ATTRIB_NORMAL 0x00 #define ATTRIB_READONLY 0x01 #define ATTRIB_HIDDEN 0x02 #define ATTRIB_SYSTEM 0x04 #define ATTRIB_LABEL 0x08 #define ATTRIB_DIR 0x10 #define ATTRIB_ARCHIVE 0x20 #define ATTRIB_LFN 0x0F /* * Macros to read fields in directory & BPB entries */ #define READWORD(buffer,x) (((unsigned int) (buffer)[(x)]) | \ (((unsigned int) (buffer)[(x)+1]) << 8)) #define READWORD32(buffer,x) (READWORD(buffer,(x)) | (READWORD(buffer,(x)+2) << 16)) #define READBYTE(buffer,x) ((unsigned int) (buffer)[(x)]) /* * Directory entry offsets and values */ #define DIR_CHECKSUM 13 #define DIR_FILELENGTH 28 #define DIR_STARTCLUSTER 26 #define DIR_ATTRIB 11 #define DIR_NAMEOFFSET 0 #define DIR_NAMELEN 8 #define DIR_EXTOFFSET 8 #define DIR_EXTLEN 3 #define DIRENTRY_CHECKSUM(e) READBYTE(e,DIR_CHECKSUM) #define DIRENTRY_FILELENGTH(e) READWORD32(e,DIR_FILELENGTH) #define DIRENTRY_STARTCLUSTER(e) READWORD(e,DIR_STARTCLUSTER) #define DIRENTRY_ATTRIB(e) READBYTE(e,DIR_ATTRIB) #define DIRENTRY_LAST 0 #define DIRENTRY_DELETED 0xE5 #define DIRENTRY_PARENTDIR 0x2E #define DIRENTRY_LFNIDX(e) READBYTE(e,0) #define LFNIDX_MASK 0x1F #define LFNIDX_END 0x40 #define LFNIDX_MAX 20 /* ********************************************************************* * Types ********************************************************************* */ /* * Internalized BPB */ typedef struct bpb_s { unsigned int bpb_bytespersector; unsigned int bpb_sectorspercluster; unsigned int bpb_reservedsectors; unsigned int bpb_numfats; unsigned int bpb_maxrootdir; unsigned int bpb_totalsectors; unsigned int bpb_sectorsperfat; unsigned int bpb_sectorspertrack; unsigned int bpb_numheads; unsigned int bpb_hiddensectors; } bpb_t; /* * FAT Filesystem descriptor - contains working information * about an "open" file system */ typedef struct fatfs_s { int fat_fh; int fat_refcnt; bpb_t fat_bpb; int fat_bits; int fat_partstart; uint8_t fat_dirsector[SECTORSIZE]; int fat_dirsecnum; uint8_t fat_fatsector[SECTORSIZE]; int fat_fatsecnum; } fatfs_t; /* * FAT Chain - describes a series of FAT entries */ typedef struct fatchain_s { int fat_start; uint16_t *fat_entries; int fat_count; } fatchain_t; /* * FAT File descriptor - contains working information * about an open file (including the filesystem info) */ typedef struct fatfile_s { fatfs_t *ff_fat; int ff_filelength; fatchain_t ff_chain; int ff_curpos; int ff_cursector; uint8_t ff_sector[SECTORSIZE]; } fatfile_t; /* ********************************************************************* * Prototypes ********************************************************************* */ static int fatfs_fileop_xinit(void **fsctx,void *filename); static int fatfs_fileop_pinit(void **fsctx,void *filename); static int fatfs_fileop_open(void **ref,void *fsctx,char *filename,int mode); static int fatfs_fileop_read(void *ref,hsaddr_t buf,int len); static int fatfs_fileop_write(void *ref,hsaddr_t buf,int len); static int fatfs_fileop_seek(void *ref,int offset,int how); static void fatfs_fileop_close(void *ref); static void fatfs_fileop_uninit(void *fsctx); static int fatfs_check_for_partition_table(fatfs_t *fatfs); /* ********************************************************************* * FAT fileio dispatch table ********************************************************************* */ /* * Raw FAT (no partition table) - used only on floppies */ const fileio_dispatch_t fatfs_fileops = { "rfat", LOADFLG_NOBB, fatfs_fileop_xinit, fatfs_fileop_open, fatfs_fileop_read, fatfs_fileop_write, fatfs_fileop_seek, fatfs_fileop_close, fatfs_fileop_uninit }; /* * Partitioned FAT - used on Zip disks, removable hard disks, * hard disks, flash cards, etc. */ const fileio_dispatch_t pfatfs_fileops = { "fat", LOADFLG_NOBB, fatfs_fileop_pinit, fatfs_fileop_open, fatfs_fileop_read, fatfs_fileop_write, fatfs_fileop_seek, fatfs_fileop_close, fatfs_fileop_uninit }; /* ********************************************************************* * fat_readsector(fatfs,sector,numsec,buffer) * * Read one or more sectors from the disk into memory * * Input parameters: * fatfs - fat filesystem descriptor * sector - sector number * numsec - number of sectors to read * buffer - buffer to read sectors into * * Return value: * 0 if ok * else error code ********************************************************************* */ static int fat_readsector(fatfs_t *fatfs,int sector,int numsec,uint8_t *buffer) { int res; res = cfe_readblk(fatfs->fat_fh,(sector+fatfs->fat_partstart)*SECTORSIZE, PTR2HSADDR(buffer),numsec*SECTORSIZE); if (res != numsec*SECTORSIZE) return CFE_ERR_IOERR; return 0; } /* ********************************************************************* * fat_dumpbpb(bpb) * * Debug function; display fields in a BPB * * Input parameters: * bpb - BIOS parameter block structure * * Return value: * nothing ********************************************************************* */ #ifdef _FATFS_DEBUG_ static void fat_dumpbpb(bpb_t *bpb) { xprintf("Bytes per sector %d\n",bpb->bpb_bytespersector); xprintf("Sectors per cluster %d\n",bpb->bpb_sectorspercluster); xprintf("Reserved sectors %d\n",bpb->bpb_reservedsectors); xprintf("Number of FATs %d\n",bpb->bpb_numfats); xprintf("Root dir entries %d\n",bpb->bpb_maxrootdir); xprintf("Total sectors %d\n",bpb->bpb_totalsectors); xprintf("Sectors per FAT %d\n",bpb->bpb_sectorsperfat); xprintf("Sectors per track %d\n",bpb->bpb_sectorspertrack); xprintf("Number of heads %d\n",bpb->bpb_numheads); xprintf("Hidden sectors %d\n",bpb->bpb_hiddensectors); } #endif /* ********************************************************************* * fat_findpart(fatfs) * * For partitioned disks, locate the active partition * and set "fat_partstart" accordingly * * Input parameters: * fatfs - FAT filesystem descriptor * * Return value: * 0 if we found a valid partition table; else error code ********************************************************************* */ static int fat_findpart(fatfs_t *fatfs) { uint8_t buffer[SECTORSIZE]; uint8_t *part; int res; int idx; fatfs->fat_partstart = 0; /* make sure we get real boot sector */ res = fat_readsector(fatfs,0,1,buffer); if (res < 0) return res; /* * Normally you're supposed to check for a JMP instruction. * At least that's what many people do. Flash MBRs don't start * with JMP instructions, so just look for the seal. * * * if (READBYTE(buffer,BPB_JMPINSTR) != BPB_JMPINSTR_VALUE) { * return CFE_ERR_BADFILESYS; * } */ /* * Check the seal at the end of th sector */ if (READWORD(buffer,BPB_SEAL) != BPB_SEAL_VALUE) return CFE_ERR_BADFILESYS; /* * Look for an active FAT partition. The partition we want must * be the active one. We do not deal with extended partitions * here. Hey, this is supposed to be boot code! */ part = &buffer[PTABLE_OFFSET]; for (idx = 0; idx < PTABLE_COUNT; idx++) { if ((part[PTABLE_STATUS] == PTABLE_STATUS_ACTIVE) && ((part[PTABLE_TYPE] == PARTTYPE_FAT12) || (part[PTABLE_TYPE] == PARTTYPE_FAT16) || (part[PTABLE_TYPE] == PARTTYPE_FAT16BIG))) { break; } part += PTABLE_SIZE; } if (idx == PTABLE_COUNT) return CFE_ERR_BADFILESYS; /* * The info we want is really just the pointer to the * boot (BPB) sector. Get that and we'll use it for an * offset into the disk later. */ fatfs->fat_partstart = READWORD32(part,PTABLE_BOOTSECTOR); return 0; } /* ********************************************************************* * fat_readbpb(fatfs) * * Read and internalize the BIOS Parameter Block * * Input parameters: * fatfs - FAT filesystem descriptor * * Return value: * 0 if ok * else error code (usually, BPB is not valid) ********************************************************************* */ static int fat_readbpb(fatfs_t *fatfs) { uint8_t buffer[SECTORSIZE]; int res; int datasectors; res = fat_readsector(fatfs,0,1,buffer); if (res < 0) return res; if (READBYTE(buffer,BPB_JMPINSTR) != BPB_JMPINSTR_VALUE) return CFE_ERR_BADFILESYS; if (READWORD(buffer,BPB_SEAL) != BPB_SEAL_VALUE) return CFE_ERR_BADFILESYS; fatfs->fat_bpb.bpb_bytespersector = READWORD(buffer,BPB_BYTESPERSECTOR); fatfs->fat_bpb.bpb_sectorspercluster = READBYTE(buffer,BPB_SECTORSPERCLUSTER); fatfs->fat_bpb.bpb_reservedsectors = READWORD(buffer,BPB_RESERVEDSECTORS); fatfs->fat_bpb.bpb_numfats = READBYTE(buffer,BPB_NUMFATS); fatfs->fat_bpb.bpb_maxrootdir = READWORD(buffer,BPB_MAXROOTDIR); fatfs->fat_bpb.bpb_totalsectors = READWORD(buffer,BPB_TOTALSECTORS); fatfs->fat_bpb.bpb_sectorsperfat = READWORD(buffer,BPB_SECTORSPERFAT); fatfs->fat_bpb.bpb_sectorspertrack = READWORD(buffer,BPB_SECTORSPERTRACK); fatfs->fat_bpb.bpb_numheads = READWORD(buffer,BPB_NUMHEADS); fatfs->fat_bpb.bpb_hiddensectors = READWORD(buffer,BPB_HIDDENSECTORS); if (fatfs->fat_bpb.bpb_bytespersector != SECTORSIZE) return CFE_ERR_BADFILESYS; if (fatfs->fat_bpb.bpb_numfats > 2) return CFE_ERR_BADFILESYS; /* * XXX sanity check other fields */ /* Count data clusters to select FAT12 or FAT16 */ datasectors = fatfs->fat_bpb.bpb_totalsectors - (fatfs->fat_bpb.bpb_reservedsectors + (fatfs->fat_bpb.bpb_maxrootdir * DIRENTRYSIZE)/SECTORSIZE + (fatfs->fat_bpb.bpb_numfats * fatfs->fat_bpb.bpb_sectorsperfat)); fatfs->fat_bits = ((datasectors / fatfs->fat_bpb.bpb_sectorspercluster) > 4084) ? 16 : 12; #ifdef _FATFS_DEBUG_ fat_dumpbpb(&(fatfs->fat_bpb)); #endif return 0; } /* ********************************************************************* * fat_getentry(fatfs,entry) * * Read a FAT entry. This is more involved than you'd think, * since we have to deal with 12 and 16 (and someday 32) bit FATs, * and the nasty case where a 12-bit FAT entry crosses a sector * boundary. * * Input parameters: * fatfs - FAT filesystem descriptor * entry - index of FAT entry * * Return value: * FAT entry, or <0 if an error occured ********************************************************************* */ static int fat_getfatentry(fatfs_t *fatfs,int entry) { int fatsect; int byteoffset; int fatstart; int fatoffset; uint8_t b1,b2,b3; int res; fatstart = fatfs->fat_bpb.bpb_reservedsectors; if (fatfs->fat_bits == 12) { int odd; odd = entry & 1; byteoffset = ((entry & ~1) * 3) / 2; fatsect = byteoffset / SECTORSIZE; fatoffset = byteoffset % SECTORSIZE; if (fatfs->fat_fatsecnum != fatsect) { res = fat_readsector(fatfs,fatsect+fatstart,1,fatfs->fat_fatsector); if (res < 0) { return res; } fatfs->fat_fatsecnum = fatsect; } b1 = fatfs->fat_fatsector[fatoffset]; if ((fatoffset+1) >= SECTORSIZE) { res = fat_readsector(fatfs,fatsect+1+fatstart,1,fatfs->fat_fatsector); if (res < 0) { return res; } fatfs->fat_fatsecnum = fatsect+1; fatoffset -= SECTORSIZE; } b2 = fatfs->fat_fatsector[fatoffset+1]; if ((fatoffset+2) >= SECTORSIZE) { res = fat_readsector(fatfs,fatsect+1+fatstart,1,fatfs->fat_fatsector); if (res < 0) { return res; } fatfs->fat_fatsecnum = fatsect+1; fatoffset -= SECTORSIZE; } b3 = fatfs->fat_fatsector[fatoffset+2]; if (odd) { return ((unsigned int) b3 << 4) + ((unsigned int) (b2 & 0xF0) >> 4); } else { return ((unsigned int) (b2 & 0x0F) << 8) + ((unsigned int) b1); } } else { byteoffset = entry * 2; fatsect = byteoffset / SECTORSIZE; fatoffset = byteoffset % SECTORSIZE; if (fatfs->fat_fatsecnum != fatsect) { res = fat_readsector(fatfs,fatsect+fatstart,1,fatfs->fat_fatsector); if (res < 0) { return res; } fatfs->fat_fatsecnum = fatsect; } b1 = fatfs->fat_fatsector[fatoffset]; b2 = fatfs->fat_fatsector[fatoffset+1]; return ((unsigned int) b1) + (((unsigned int) b2) << 8); } } /* ********************************************************************* * fat_getrootdirentry(fatfs,entryidx,entry) * * Read a root directory entry. The FAT12/16 root directory * is a contiguous group of sectors, whose size is specified in * the BPB. This routine just digs out an entry from there * * Input parameters: * fatfs - FAT filesystem descriptor * entryidx - 0-based entry index to read * entry - pointer to directory entry (32 bytes) * * Return value: * 0 if ok * <0 if error occured ********************************************************************* */ static int fat_getrootdirentry(fatfs_t *fatfs,int entryidx,uint8_t *entry) { int rootdirstart; int rootdirsize; int dirsecnum; int res; if (entryidx >= fatfs->fat_bpb.bpb_maxrootdir) { memset(entry,0,DIRENTRYSIZE); return CFE_ERR_INV_PARAM; } rootdirstart = fatfs->fat_bpb.bpb_reservedsectors + fatfs->fat_bpb.bpb_numfats * fatfs->fat_bpb.bpb_sectorsperfat; rootdirsize = fatfs->fat_bpb.bpb_maxrootdir / DIRENTRYSIZE; dirsecnum = rootdirstart + entryidx / DIRPERSECTOR; if (fatfs->fat_dirsecnum != dirsecnum) { res = fat_readsector(fatfs,dirsecnum,1,fatfs->fat_dirsector); if (res < 0) { return res; } fatfs->fat_dirsecnum = dirsecnum; } memcpy(entry,&(fatfs->fat_dirsector[(entryidx % DIRPERSECTOR)*DIRENTRYSIZE]), DIRENTRYSIZE); return 0; } /* ********************************************************************* * fat_checksumname(name) * * Calculate the "long filename" checksum for a given short name. * All LFN directory entries associated with the short name are * given the same checksum byte, to help keep the long name * consistent. * * Input parameters: * name - pointer to 32-byte directory entry * * Return value: * checksum value ********************************************************************* */ static uint8_t fat_checksumname(uint8_t *name) { uint8_t sum = 0; uint8_t newbit; int idx; for (idx = 0; idx < 11; idx++) { newbit = (sum & 1) ? 0x80 : 0x00; sum >>= 1; sum |= newbit; sum += name[idx]; } return sum; } #ifdef _FATFS_DEBUG_ void fat_dumpdirentry(uint8_t *entry); void fat_dumpdirentry(uint8_t *entry) { uint8_t name[32]; int idx; if (entry[11] != ATTRIB_LFN) { memcpy(name,entry,11); name[11] = 0; xprintf("%s %02X %04X %d\n", name,DIRENTRY_ATTRIB(entry), DIRENTRY_STARTCLUSTER(entry), DIRENTRY_FILELENGTH(entry)); } else { for (idx = 0; idx < 5; idx++) { name[idx] = entry[(idx*2)+1]; } for (idx = 0; idx < 6; idx++) { name[idx+5] = entry[(idx*2)+14]; } for (idx = 0; idx < 2; idx++) { name[idx+11] = entry[(idx*2)+28]; } name[13] = '\0'; xprintf("%02X: %s %04X cksum %02X\n",entry[0], name,READWORD(entry,0x1A),entry[13]); } } #endif /* ********************************************************************* * fat_walkfatchain(fat,start,arg,func) * * Walk a FAT chain, calling a callback routine for each entry * we find along the way. * * Input parameters: * fat - FAT filesystem descriptor * start - starting FAT entry (from the directory, usually) * arg - argument to pass to callback routine * func - function to call for each FAT entry * * Return value: * 0 if all elements processed * <0 if error occured * >0 if callback routine returned a nonzero value ********************************************************************* */ static int fat_walkfatchain(fatfs_t *fat,int start, void *arg, int (*func)(fatfs_t *fat, int e, int prev_e, void *arg)) { int prev_e = 0; int ending_e; int e; int res = 0; e = start; /* Note: ending FAT entry can be 0x(F)FF8..0x(F)FFF. We assume that the 'getfatentry' call won't return values above that. */ if (fat->fat_bits == 12) { ending_e = 0xFF8; } else { ending_e = 0xFFF8; } while (e < ending_e) { res = (*func)(fat,e,prev_e,arg); if (res) break; prev_e = e; e = fat_getfatentry(fat,e); if (e < 0) return e; } return res; } /* ********************************************************************* * fat_getwalkfunc(fat,e,prev_e,arg) * * Callback routien to collect all of the FAT entries into * a FAT chain descriptor * * Input parameters: * fat - FAT filesystem descriptor * e - current entry * prev_e - previous entry (0 if first entry) * arg - argument passed to fat_walkfatchain * * Return value: * 0 to keep walking * else value to return from fat_walkfatchain ********************************************************************* */ static int fat_getwalkfunc(fatfs_t *fat,int e, int prev_e,void *arg) { fatchain_t *chain = arg; if (chain->fat_entries) { chain->fat_entries[chain->fat_count] = (uint16_t) e; } chain->fat_count++; return 0; } /* ********************************************************************* * fat_getchain(fat,start,chain) * * Walk an entire FAT chain and remember the chain in a * FAT chain descriptor * * Input parameters: * fat - FAT filesystem descriptor * start - starting FAT entry * chain - chain descriptor * * Return value: * 0 if ok * else error code ********************************************************************* */ static int fat_getchain(fatfs_t *fat,int start,fatchain_t *chain) { int res; chain->fat_entries = NULL; chain->fat_count = 0; chain->fat_start = start; /* * walk once to count the entries. * * For regular files, you probably don't have to do this * since you can predict exactly how many FAT entries * there are given the file length. */ res = fat_walkfatchain(fat,start,chain,fat_getwalkfunc); if (res < 0) return res; /* * allocate space for the entries. Include one extra * slot for the first entry, since the first entry * does not actually appear in the FAT (the fat is * only the 'next' pointers). */ if (chain->fat_count == 0) return 0; chain->fat_entries = KMALLOC((chain->fat_count+1)*sizeof(uint16_t),0); chain->fat_count = 0; /* * walk again to collect entries */ res = fat_walkfatchain(fat,start,chain,fat_getwalkfunc); if (res < 0) return res; return chain->fat_count; } /* ********************************************************************* * fat_freechain(chain) * * Free memory associated with a FAT chain * * Input parameters: * chain - chain descriptor * * Return value: * nothing ********************************************************************* */ static void fat_freechain(fatchain_t *chain) { if (chain->fat_entries) { KFREE(chain->fat_entries); chain->fat_entries = NULL; } chain->fat_count = 0; } /* ********************************************************************* * fat_clusteridx(fat,chain,idx) * * Index into a FAT chain and return the nth cluster number * from the chain * * Input parameters: * fat - fat filesystem descriptor * chain - chain descriptor * idx - index into FAT chain * * Return value: * FAT entry at the nth index, or * <0 if an error occured ********************************************************************* */ static int fat_clusteridx(fatfs_t *fat,fatchain_t *chain,int idx) { if (idx >= chain->fat_count) return CFE_ERR_INV_PARAM; /* error! */ return (int) (unsigned int) chain->fat_entries[idx]; } /* ********************************************************************* * fat_sectoridx(fat,chain,idx) * * Return the sector nunber of the nth sector in a given * FAT chain. This routine knows how to translate cluster * numbers into sector numbers. * * Input parameters: * fat - FAT filesystem descriptor * chain - FAT chain * idx - index of which sector to find * * Return value: * sector number * <0 if an error occured ********************************************************************* */ static int fat_sectoridx(fatfs_t *fat,fatchain_t *chain,int idx) { int clusteridx; int sectoridx; int sector; int fatentry; clusteridx = idx / fat->fat_bpb.bpb_sectorspercluster; sectoridx = idx % fat->fat_bpb.bpb_sectorspercluster; fatentry = fat_clusteridx(fat,chain,clusteridx); if (fatentry < 0) xprintf("ran off end of fat chain!\n"); if (fatentry < 2) xprintf("fat entries should be >= 2\n"); sector = fat->fat_bpb.bpb_reservedsectors + (fat->fat_bpb.bpb_maxrootdir * DIRENTRYSIZE)/SECTORSIZE + (fat->fat_bpb.bpb_numfats * fat->fat_bpb.bpb_sectorsperfat) + (fatentry - 2) * fat->fat_bpb.bpb_sectorspercluster + sectoridx; return sector; } /* ********************************************************************* * fat_getsubdirentry(fat,chain,idx,direntry) * * This routine is similar to fat_getrootdirentry except it * works on a subdirectory. FAT subdirectories are like files * containing directory entries, so we use the "get nth sector * in chain" routines to walk the chains of sectors reading directory * entries. * * Input parameters: * fat - FAT filesystem descriptor * chain - FAT chain * idx - index of entry to read * direntry - place to put directory entry we read * * Return value: * 0 if ok * else error code ********************************************************************* */ static int fat_getsubdirentry(fatfs_t *fat,fatchain_t *chain, int idx,uint8_t *direntry) { int sector; int res; sector = fat_sectoridx(fat,chain,idx/DIRPERSECTOR); if (sector < 0) return sector; if (fat->fat_dirsecnum != sector) { res = fat_readsector(fat,sector,1,fat->fat_dirsector); if (res < 0) { return res; } fat->fat_dirsecnum = sector; } memcpy(direntry,&(fat->fat_dirsector[(idx % DIRPERSECTOR)*DIRENTRYSIZE]), DIRENTRYSIZE); return 0; } /* ********************************************************************* * fat_getshortname(direntry,name) * * Read the short filename from a directory entry, converting * it into its classic 8.3 form * * Input parameters: * direntry - directory entry * name - place to put 8.3 name * * Return value: * nothing ********************************************************************* */ static void fat_getshortname(uint8_t *direntry,char *name) { int idx; /* * Collect the base file name */ for (idx = DIR_NAMEOFFSET; idx < (DIR_NAMEOFFSET+DIR_NAMELEN); idx++) { if (direntry[idx] == ' ') break; *name++ = direntry[idx]; } /* * Put in the dot for the extension only if there * is an extension. */ if (direntry[DIR_EXTOFFSET] != ' ') *name++ = '.'; /* * Collect the extension */ for (idx = DIR_EXTOFFSET; idx < (DIR_EXTOFFSET+DIR_EXTLEN); idx++) { if (direntry[idx] == ' ') break; *name++ = direntry[idx]; } *name = '\0'; } /* ********************************************************************* * fat_getlongname(fat,chain,diridx,shortentry,longname) * * Look backwards in the directory to locate the long file name * that corresponds to the short file name passed in 'shortentry' * * Input parameters: * fat - FAT filesystem descriptor * chain - chain describing current directory, or NULL * if the current directory is the root directory * diridx - index of the short file name * shortentry - points to the short directory entry * longname - buffer to receive long file name (up to 261 chars) * * Return value: * 0 if ok * else error code ********************************************************************* */ static int fat_getlongname(fatfs_t *fat,fatchain_t *chain,int diridx, uint8_t *shortentry,char *longname) { int lfnidx = 1; uint8_t checksum; uint8_t direntry[DIRENTRYSIZE]; int idx; char *lfnptr; int badlfn = 0; *longname = '\0'; /* * idx is the entry # of the short name */ checksum = fat_checksumname(shortentry); /* * Start working backwards from current entry * and collect pieces of the lfn */ lfnptr = longname; diridx--; while (diridx >= 0) { /* * Read previous entry */ if (chain) { fat_getsubdirentry(fat,chain,diridx,direntry); } else { fat_getrootdirentry(fat,diridx,direntry); } /* * Checksum must match, it must have the right entry index, * and it must have the LFN attribute */ if (DIRENTRY_CHECKSUM(direntry) != checksum) { badlfn = 1; break; } if ((DIRENTRY_LFNIDX(direntry) & LFNIDX_MASK) != lfnidx) { badlfn = 1; break; } if (DIRENTRY_ATTRIB(direntry) != ATTRIB_LFN) { badlfn = 1; break; } /* * Collect the chars from the filename. Note we * don't deal well with real unicode chars here. */ for (idx = 0; idx < 5; idx++) { *lfnptr++ = direntry[(idx*2)+1]; } for (idx = 0; idx < 6; idx++) { *lfnptr++ = direntry[(idx*2)+14]; } for (idx = 0; idx < 2; idx++) { *lfnptr++ = direntry[(idx*2)+28]; } /* * Don't go too far */ if (DIRENTRY_LFNIDX(direntry) & LFNIDX_END) break; lfnidx++; if (lfnidx > LFNIDX_MAX) { badlfn = 1; break; } diridx--; } /* * Null terminate the lfn */ *lfnptr = 0; if (badlfn) { longname[0] = 0; return CFE_ERR_FILENOTFOUND; } return 0; } /* ********************************************************************* * fat_scandir(fat,chain,name,direntry) * * Scan a single directory looking for a file name * * Input parameters: * fat - FAT filesystem descriptor * chain - FAT chain for directory or NULL for root directory * name - name of file to look for (short or long name) * direntry - place to put directory entry if we find one * * Return value: * 1 if name was found * 0 if name was not found * else <0 is error code ********************************************************************* */ static int fat_scandir(fatfs_t *fat,fatchain_t *chain, char *name,uint8_t *direntry) { int idx; int count; char shortname[16]; char longname[280]; /* * Get directory size */ if (chain) { count = (chain->fat_count * fat->fat_bpb.bpb_sectorspercluster) * DIRPERSECTOR; } else { count = (int) fat->fat_bpb.bpb_maxrootdir; } /* * Scan whole directory */ for (idx = 0; idx < count; idx++) { /* * Get entry by root or chain depending... */ if (chain) { fat_getsubdirentry(fat,chain,idx,direntry); } else { fat_getrootdirentry(fat,idx,direntry); } /* * Ignore stuff we don't want to see */ if (direntry[0] == DIRENTRY_LAST) break; /* stop if at end of dir */ if (direntry[0] == DIRENTRY_DELETED) continue; /* skip deleted entries */ if (direntry[0] == DIRENTRY_PARENTDIR) continue; /* skip ./.. entries */ if (DIRENTRY_ATTRIB(direntry) == ATTRIB_LFN) continue; /* skip LFNs */ if (DIRENTRY_ATTRIB(direntry) & ATTRIB_LABEL) continue; /* skip volume labels */ /* * Get actual file names from directory */ fat_getshortname(direntry,shortname); fat_getlongname(fat,chain,idx,direntry,longname); if (name) { if (strcmpi(name,shortname) == 0) return 1; if (longname[0] && (strcmpi(name,longname) == 0)) return 1; } else { xprintf("%-30s",longname[0] ? longname : shortname); // xprintf(" Clus=%04X",DIRENTRY_STARTCLUSTER(direntry)); // xprintf(" Attrib=%02X",DIRENTRY_ATTRIB(direntry)); // xprintf(" Size=%d",DIRENTRY_FILELENGTH(direntry)); xprintf("%d",DIRENTRY_FILELENGTH(direntry)); xprintf("\n"); } } return 0; } /* ********************************************************************* * fat_findfile(fat,name,direntry) * * Locate a directory entry given a complete path name * * Input parameters: * fat - FAT filesystem descriptor * name - name of file to locate (forward or reverse slashses ok) * direntry - place to put directory entry we find * * Return value: * 0 if file not found * 1 if file was found * <0 if error occurs ********************************************************************* */ static int fat_findfile(fatfs_t *fat,char *name,uint8_t *direntry) { char *namecopy; char *namepart; char *ptr; fatchain_t chain; int res; int e; /* * Copy the name, we're going to hack it up */ namecopy = strdup(name); /* * Chew off the first piece up to the first slash. Remove * a leading slash if it is there. */ ptr = namecopy; if ((*ptr == '/') || (*ptr == '\\')) ptr++; namepart = ptr; while (*ptr && (*ptr != '/') && (*ptr != '\\')) ptr++; if (*ptr) *ptr++ = '\0'; /* * Scan the root directory looking for the first piece */ res = fat_scandir(fat,NULL,namepart,direntry); if (res == 0) { KFREE(namecopy); return 0; /* file not found */ } /* * Start scanning subdirectories until we run out * of directory components. */ namepart = ptr; while (*ptr && (*ptr != '/') && (*ptr != '\\')) ptr++; if (*ptr) *ptr++ = '\0'; if (!*namepart) namepart = NULL; while (namepart) { /* * Scan the subdirectory */ e = DIRENTRY_STARTCLUSTER(direntry); memset(&chain,0,sizeof(chain)); fat_getchain(fat,e,&chain); res = fat_scandir(fat,&chain,namepart,direntry); if (res == 0) { break; } fat_freechain(&chain); /* * Advance to the next piece */ namepart = ptr; while (*ptr && (*ptr != '/') && (*ptr != '\\')) ptr++; if (*ptr) *ptr++ = '\0'; if (!*namepart) namepart = NULL; /* * If there's more to go and we hit something that * is not a directory, stop here. */ if (namepart && !(DIRENTRY_ATTRIB(direntry) & ATTRIB_DIR)) { res = 0; } } KFREE(namecopy); /* * The last piece we enumerate has to be a file. */ if ((res > 0) && (DIRENTRY_ATTRIB(direntry) & ATTRIB_DIR)) { return 0; } return res; } /* ********************************************************************* * fat_init(fat,name) * * Create the filesystem descriptor and attach to the hardware * device. * * Input parameters: * fat - filesystem descriptor * name - hardware device name * part - true to look for partition tables * * Return value: * 0 if ok * else error code ********************************************************************* */ static int fat_init(fatfs_t *fat,char *name,int part) { int res; memset(fat,0,sizeof(fatfs_t)); fat->fat_dirsecnum = -1; fat->fat_fatsecnum = -1; fat->fat_fh = cfe_open(name); if (fat->fat_fh < 0) return fat->fat_fh; res = fatfs_check_for_partition_table(fat); /* If we were able to figure it out, use that as the default */ if (res >= 0) part = res; if (part) { res = fat_findpart(fat); if (res < 0) { cfe_close(fat->fat_fh); fat->fat_fh = -1; return res; } } res = fat_readbpb(fat); if (res != 0) { cfe_close(fat->fat_fh); fat->fat_fh = -1; return res; } return 0; } /* ********************************************************************* * fat_uninit(fat) * * Uninit the filesystem descriptor and release any resources * we allocated. * * Input parameters: * fat - filesystem descriptor * * Return value: * nothing ********************************************************************* */ static void fat_uninit(fatfs_t *fat) { if (fat->fat_fh >= 0) cfe_close(fat->fat_fh); fat->fat_fh = -1; } int fatfs_fileop_dir(void *fsctx); int fatfs_fileop_dir(void *fsctx) { fatfs_t *fatfs = fsctx; uint8_t direntry[32]; fat_scandir(fatfs,NULL,NULL,direntry); return 0; } /* ********************************************************************* * fatfs_fileop_init(fsctx,devname) * * Create a FAT filesystem context and open the associated * block device. * * Input parameters: * fsctx - file system context (return pointer) * devname - device name to open * part - true to look for a partition table * * Return value: * 0 if ok, else error ********************************************************************* */ static int fatfs_fileop_init(void **fsctx,char *devname,int part) { int res; fatfs_t *fatfs; /* * Allocate a file system context */ fatfs = (fatfs_t *) KMALLOC(sizeof(fatfs_t),0); if (!fatfs) return CFE_ERR_NOMEM; /* * Open a handle to the underlying device */ res = fat_init(fatfs,devname,part); if (res != 0) { KFREE(fatfs); return res; } *fsctx = fatfs; return 0; } /* ********************************************************************* * fatfs_check_for_partition_table(fatfs) * * This routine attempts to determine if the disk contains a * partition table or if it contains a standard MS-DOS boot recod. * We try to find both, and return what we find, or an error * if it is still unclear. * * Input parameters: * fatfs - fat filesystem context * * Return value: * 0 if no partition table * 1 if partition table * <0 = error occured, could not tell or I/O error ********************************************************************* */ static int fatfs_check_for_partition_table(fatfs_t *fatfs) { int res; uint8_t buffer[SECTORSIZE]; uint8_t *part; int idx; int foundit = 0; /* * Read sector 0 */ fatfs->fat_partstart = 0; res = fat_readsector(fatfs,0,1,buffer); if (res < 0) return res; /* * Check the seal at the end of the sector. Both * boot sector and MBR should contain this seal. */ if (READWORD(buffer,BPB_SEAL) != BPB_SEAL_VALUE) { res = CFE_ERR_BADFILESYS; return res; } /* * See Microsoft Knowledgebase article # Q140418, it contains * a good description of the boot sector format. * * If the extended information is present, and SystemID is "FAT" * and the "bytes per sector" is 512, assume it's a regular boot block */ if (((buffer[BPB_SIGNATURE] == BPB_SIGNATURE_VALUE1) || (buffer[BPB_SIGNATURE] == BPB_SIGNATURE_VALUE2)) && (memcmp(&buffer[BPB_SYSTEMID],"FAT",3) == 0) && (READWORD(buffer,BPB_BYTESPERSECTOR) == 512)) { /* Not partitioned */ res = 0; return res; } /* If no extended information is present, check a few other key values. */ if ((READWORD(buffer,BPB_BYTESPERSECTOR) == 512) && (READWORD(buffer,BPB_RESERVEDSECTORS) >= 1) && ((READWORD(buffer,BPB_MEDIADESCRIPTOR) & 0xF0) == 0xF0)) { res = 0; return res; } /* * If we're still confused, look for a partition table with a valid FAT * partition on it. We might not detect a partition table that has * only non-FAT partitions on it, like a disk with all Linux partitions, * but that is fine here in the FATFS module, since we only want to * find FAT partitions anyway. */ part = &buffer[PTABLE_OFFSET]; for (idx = 0; idx < PTABLE_COUNT; idx++) { if (((part[PTABLE_STATUS] == PTABLE_STATUS_ACTIVE) || (part[PTABLE_STATUS] == 0x00)) && ((part[PTABLE_TYPE] == PARTTYPE_FAT12) || (part[PTABLE_TYPE] == PARTTYPE_FAT16) || (part[PTABLE_TYPE] == PARTTYPE_FAT16BIG))) { foundit = 1; res = 1; /*Partition table present*/ break; } part += PTABLE_SIZE; } /* * If at this point we did not find what we were looking for, * return an error. */ if (foundit) { res = 1; /*Partition table is present.*/ } else { /*Error! We can't decide if partition table exists or not*/ res = CFE_ERR_BADFILESYS; } return res; } static int fatfs_fileop_xinit(void **fsctx,void *dev) { char *devname = (char *) dev; return fatfs_fileop_init(fsctx,devname,0); } static int fatfs_fileop_pinit(void **fsctx,void *dev) { char *devname = (char *) dev; return fatfs_fileop_init(fsctx,devname,1); } /* ********************************************************************* * fatfs_fileop_open(ref,name) * * Open a file on the FAT device. * * Input parameters: * ref - place to store pointer to fileinfo * fsctx - filesystem context * name - name of file to open * * Return value: * 0 if ok * else error code ********************************************************************* */ static int fatfs_fileop_open(void **ref,void *fsctx,char *name,int mode) { int res; uint8_t direntry[DIRENTRYSIZE]; fatfile_t *ff; fatfs_t *fatfs; if (mode != FILE_MODE_READ) return CFE_ERR_UNSUPPORTED; fatfs = (fatfs_t *) fsctx; ff = (fatfile_t *) KMALLOC(sizeof(fatfile_t),0); if (ff == NULL) return CFE_ERR_NOMEM; memset(ff,0,sizeof(fatfile_t)); ff->ff_fat = fatfs; res = fat_findfile(ff->ff_fat,name,direntry); if (res <= 0) { return CFE_ERR_FILENOTFOUND; /* not found */ } /* * Okay, the file was found. Enumerate the FAT chain * associated with this file. */ ff->ff_filelength = DIRENTRY_FILELENGTH(direntry); ff->ff_curpos = 0; ff->ff_cursector = -1; res = fat_getchain(ff->ff_fat, DIRENTRY_STARTCLUSTER(direntry), &(ff->ff_chain)); if (res < 0) { KFREE(ff); return res; } /* * Return the file handle */ fatfs->fat_refcnt++; *ref = (void *) ff; return 0; } /* ********************************************************************* * fatfs_fileop_close(ref) * * Close the file. * * Input parameters: * ref - pointer to open file information * * Return value: * nothing ********************************************************************* */ static void fatfs_fileop_close(void *ref) { fatfile_t *file = (fatfile_t *) ref; fatfs_t *fatctx = file->ff_fat; fatctx->fat_refcnt--; fat_freechain(&(file->ff_chain)); KFREE(file); } /* ********************************************************************* * fatfs_fileop_uninit(ref) * * Uninitialize the file system. * * Input parameters: * fsctx - filesystem context * * Return value: * nothing ********************************************************************* */ static void fatfs_fileop_uninit(void *fsctx) { fatfs_t *fatctx = (fatfs_t *) fsctx; if (fatctx->fat_refcnt) { xprintf("fatfs_fileop_unint: warning: refcnt should be zero\n"); } fat_uninit(fatctx); KFREE(fatctx); } /* ********************************************************************* * fatfs_fileop_seek(ref,offset,how) * * Move the file pointer within the file * * Input parameters: * ref - pointer to open file information * offset - new file location or distance to move * how - method for moving * * Return value: * new file offset * <0 if error occured ********************************************************************* */ static int fatfs_fileop_seek(void *ref,int offset,int how) { fatfile_t *file = (fatfile_t *) ref; switch (how) { case FILE_SEEK_BEGINNING: file->ff_curpos = offset; break; case FILE_SEEK_CURRENT: file->ff_curpos += offset; break; default: break; } if (file->ff_curpos >= file->ff_filelength) { file->ff_curpos = file->ff_filelength; } return file->ff_curpos; } /* ********************************************************************* * fatfs_fileop_read(ref,buf,len) * * Read data from the file. * * Input parameters: * ref - pointer to open file information * buf - buffer to read data into * len - number of bytes to read * * Return value: * number of bytes read * <0 if error occured * 0 means eof ********************************************************************* */ static int fatfs_fileop_read(void *ref,hsaddr_t buf,int len) { fatfile_t *file = (fatfile_t *) ref; int amtcopy; int ttlcopy = 0; int offset; int sector; int secidx; int origpos; int res; uint8_t temp_buf[SECTORSIZE]; /* * Remember orig position in case we have an error */ origpos = file->ff_curpos; /* * bounds check the length based on the file length */ if ((file->ff_curpos + len) > file->ff_filelength) { len = file->ff_filelength - file->ff_curpos; } res = 0; /* * while ther is still data to be transferred */ while (len) { /* * Calculate the sector offset and index in the sector */ offset = file->ff_curpos % SECTORSIZE; secidx = file->ff_curpos / SECTORSIZE; sector = fat_sectoridx(file->ff_fat,&(file->ff_chain),secidx); if (sector < 0) { xprintf("should not happen, sector = -1!\n"); return sector; } /* * first transfer up to the sector boundary */ amtcopy = len; if (amtcopy > (SECTORSIZE-offset)) { amtcopy = (SECTORSIZE-offset); } /* * If transferring exactly a sector, on a sector * boundary, read the data directly into the user buffer * * Extra credit: See if we can transfer more than one * sector at a time, by determining if we can read a run of * contiguous sectors (very likely) * * Otherwise: read into the sector buffer and * transfer the data to user memory. */ if ((offset == 0) && (amtcopy == SECTORSIZE)) { res = fat_readsector(file->ff_fat,sector,1,temp_buf); if (res < 0) { xprintf("I/O error!\n"); break; } hs_memcpy_to_hs(buf,temp_buf,amtcopy); } else { if (file->ff_cursector != sector) { res = fat_readsector(file->ff_fat,sector,1,file->ff_sector); if (res < 0) { break; } file->ff_cursector = sector; } hs_memcpy_to_hs(buf,&(file->ff_sector[offset]),amtcopy); } /* * Adjust/update all our pointers. */ buf += amtcopy; file->ff_curpos += amtcopy; ttlcopy += amtcopy; len -= amtcopy; /* * see if we ran off the end of the file. Should not * be necessary. */ if (file->ff_curpos >= file->ff_filelength) { /* should not be necessary */ break; } } /* * If an error occured, get out now. */ if (res < 0) { file->ff_curpos = origpos; return res; } return ttlcopy; } static int fatfs_fileop_write(void *ref,hsaddr_t buf,int len) { return CFE_ERR_UNSUPPORTED; } #if 0 void main(int argc,char *argv[]) { fatfs_t fat; int idx; unsigned int e; uint8_t direntry[DIRENTRYSIZE]; fatchain_t chain; int res; fat_init(&fat,"floppy.raw"); if (fat_readbpb(&fat) == 0) { fat_dumpbpb(&fat.fat_bpb); } #if 0 for (idx = 0; idx < (int) fat.fat_bpb.bpb_maxrootdir; idx++) { fat_getrootdirentry(&fat,idx,direntry); if (direntry[0] == 0) break; if (direntry[0] == 0xE5) continue; xprintf("%3d: ",idx); fat_dumpdirentry(direntry); } #endif fat_scandir(&fat,NULL,NULL,direntry); for (e = 0x150; e < 0x160; e++) { xprintf("Entry %03X is %03X\n",e,fat_getfatentry(&fat,e)); } #if 0 e = 0x36E; while (e != 0xFFF) { e = fat_getfatentry(&fat,e); xprintf("%03X ",e); } #endif xprintf("\n\n"); e = 0x36E; memset(&chain,0,sizeof(chain)); fat_getchain(&fat,e,&chain); fat_scandir(&fat,&chain,NULL,direntry); fat_freechain(&chain); xprintf("\n\n"); e = 0x36F; memset(&chain,0,sizeof(chain)); fat_getchain(&fat,e,&chain); fat_scandir(&fat,&chain,NULL,direntry); fat_freechain(&chain); xprintf("\n\n"); e = 0x370; memset(&chain,0,sizeof(chain)); fat_getchain(&fat,e,&chain); fat_scandir(&fat,&chain,NULL,direntry); fat_freechain(&chain); xprintf("\n\n"); res = fat_findfile(&fat,argc > 1 ? argv[1] : "/usr/local/include/ansidecl.h",direntry); xprintf("res = %d\n",res); if (res) fat_dumpdirentry(direntry); close(fat.fat_fh); } void main(int argc,char *argv[]) { void *ref; int res; char buffer[257]; int total = 0; // res = fatfs_fileop_open(&ref,"floppy.raw:/usr/local/include/bfdlink.h"); res = fatfs_fileop_open(&ref,"floppy.raw:/idedrv.h"); if (res != 0) { xprintf("Could not open file: %d\n",res); exit(1); } for (;;) { res = fatfs_fileop_read(ref,buffer,39); // xprintf("read returned %d\n",res); if (res <= 0) break; if (res > 0) { total += res; buffer[res] = 0; xprintf("%s",buffer); } } if (res < 0) xprintf("error! \n"); xprintf("[total %d]\n",total); fatfs_fileop_close(ref); exit(0); } #endif