From 9216b3359a270413db8b1d7448a4df1bd7543c41 Mon Sep 17 00:00:00 2001 From: Lucia Ceionia Date: Wed, 21 Sep 2022 17:14:11 -0500 Subject: [PATCH] Fixed some Task stuff, added DOSFS filesystem --- Makefile | 6 +- boot.nasm | 2 +- dosfs/dosfs.c | 1273 +++++++++++++++++++++++++++++++++++++++++++++ dosfs/dosfs.h | 374 +++++++++++++ dosfs/readme.txt | 366 +++++++++++++ dosfs/tmpstring.c | 99 ++++ entry.nasm | 2 +- interrupt.c | 2 +- interrupt.h | 1 + kernel.c | 122 ++++- print.c | 16 +- print.h | 7 +- task.nasm | 54 +- tss.c | 2 +- v86.nasm | 10 +- 15 files changed, 2280 insertions(+), 56 deletions(-) create mode 100755 dosfs/dosfs.c create mode 100755 dosfs/dosfs.h create mode 100755 dosfs/readme.txt create mode 100644 dosfs/tmpstring.c diff --git a/Makefile b/Makefile index 47a2591..0f120b3 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,11 @@ -objects = entry.o kernel.o task.o handler.o interrupt.o v86.o print.o tss.o +objects = entry.o kernel.o task.o handler.o interrupt.o v86.o print.o tss.o dosfs/dosfs.o CFLAGS = -target "i686-elf" -m32 -mgeneral-regs-only -ffreestanding -march=pentium-m -fno-stack-protector -nostdlib -c %.o: %.nasm nasm -f elf32 -o $@ $< %.o: %.c - clang $(CFLAGS) -O2 $< + clang $(CFLAGS) -O2 -o $@ $< all: $(objects) nasm boot.nasm -o boot.bin @@ -17,3 +17,5 @@ virtdisk: dd bs=1M count=32 if=/dev/zero of=virtdisk.bin echo -n -e '\x55\xaa' | dd bs=1 seek=510 conv=notrunc of=virtdisk.bin +clean: + rm $(objects) diff --git a/boot.nasm b/boot.nasm index 3cba20c..7dfa109 100644 --- a/boot.nasm +++ b/boot.nasm @@ -26,6 +26,6 @@ string: db 'DISK ERROR' addr_packet: db 0x10, 0x00 ; size, reserved -dw 0x20 ; blocks +dw 0x39 ; blocks dd 0x8000 ; transfer buffer dq 1 ; start block diff --git a/dosfs/dosfs.c b/dosfs/dosfs.c new file mode 100755 index 0000000..3649dfe --- /dev/null +++ b/dosfs/dosfs.c @@ -0,0 +1,1273 @@ +/* + DOSFS Embedded FAT-Compatible Filesystem + (C) 2005 Lewin A.R.W. Edwards (sysadm@zws.com) + + You are permitted to modify and/or use this code in your own projects without + payment of royalty, regardless of the license(s) you choose for those projects. + + You cannot re-copyright or restrict use of the code as released by Lewin Edwards. +*/ + +#include "dosfs.h" +#include "tmpstring.c" +#include "../interrupt.h" + +struct __attribute((__packed__)) Int13DiskPacket_t { + uint8_t size; // 0x10 + uint8_t reserved; // 0x00 + uint16_t blocks; + uint32_t transfer_buffer; // 0x2300:0000 + uint64_t start_block; +}; + +extern struct Int13DiskPacket_t v86disk_addr_packet; + +extern void enter_v86(uint32_t ss, uint32_t esp, uint32_t cs, uint32_t eip); +extern void v86DiskRead(); + +// all reading at 0x23000 - be careful! +uint32_t DFS_ReadSector(uint8_t unit, uint8_t *buffer, uint32_t sector, uint32_t count) { + v86disk_addr_packet.start_block = sector; + v86disk_addr_packet.blocks = count; + FARPTR v86_entry = i386LinearToFp(v86DiskRead); + enter_v86(0x8000, 0xFF00, FP_SEG(v86_entry), FP_OFF(v86_entry)); + return 0; +} +uint32_t DFS_WriteSector(uint8_t unit, uint8_t *buffer, uint32_t sector, uint32_t count) { + for(;;); +} + +/* + Get starting sector# of specified partition on drive #unit + NOTE: This code ASSUMES an MBR on the disk. + scratchsector should point to a SECTOR_SIZE scratch area + Returns 0xffffffff for any error. + If pactive is non-NULL, this function also returns the partition active flag. + If pptype is non-NULL, this function also returns the partition type. + If psize is non-NULL, this function also returns the partition size. +*/ +uint32_t DFS_GetPtnStart(uint8_t unit, uint8_t *scratchsector, uint8_t pnum, uint8_t *pactive, uint8_t *pptype, uint32_t *psize) +{ + uint32_t result; + PMBR mbr = (PMBR) scratchsector; + + // DOS ptable supports maximum 4 partitions + if (pnum > 3) + return DFS_ERRMISC; + + // Read MBR from target media + if (DFS_ReadSector(unit,scratchsector,0,1)) { + return DFS_ERRMISC; + } + + result = (uint32_t) mbr->ptable[pnum].start_0 | + (((uint32_t) mbr->ptable[pnum].start_1) << 8) | + (((uint32_t) mbr->ptable[pnum].start_2) << 16) | + (((uint32_t) mbr->ptable[pnum].start_3) << 24); + + if (pactive) + *pactive = mbr->ptable[pnum].active; + + if (pptype) + *pptype = mbr->ptable[pnum].type; + + if (psize) + *psize = (uint32_t) mbr->ptable[pnum].size_0 | + (((uint32_t) mbr->ptable[pnum].size_1) << 8) | + (((uint32_t) mbr->ptable[pnum].size_2) << 16) | + (((uint32_t) mbr->ptable[pnum].size_3) << 24); + + return result; +} + + +/* + Retrieve volume info from BPB and store it in a VOLINFO structure + You must provide the unit and starting sector of the filesystem, and + a pointer to a sector buffer for scratch + Attempts to read BPB and glean information about the FS from that. + Returns 0 OK, nonzero for any error. +*/ +uint32_t DFS_GetVolInfo(uint8_t unit, uint8_t *scratchsector, uint32_t startsector, PVOLINFO volinfo) +{ + PLBR lbr = (PLBR) scratchsector; + volinfo->unit = unit; + volinfo->startsector = startsector; + + if(DFS_ReadSector(unit,scratchsector,startsector,1)) + return DFS_ERRMISC; + +// tag: OEMID, refer dosfs.h +// strncpy(volinfo->oemid, lbr->oemid, 8); +// volinfo->oemid[8] = 0; + + volinfo->secperclus = lbr->bpb.secperclus; + volinfo->reservedsecs = (uint16_t) lbr->bpb.reserved_l | + (((uint16_t) lbr->bpb.reserved_h) << 8); + + volinfo->numsecs = (uint16_t) lbr->bpb.sectors_s_l | + (((uint16_t) lbr->bpb.sectors_s_h) << 8); + + if (!volinfo->numsecs) + volinfo->numsecs = (uint32_t) lbr->bpb.sectors_l_0 | + (((uint32_t) lbr->bpb.sectors_l_1) << 8) | + (((uint32_t) lbr->bpb.sectors_l_2) << 16) | + (((uint32_t) lbr->bpb.sectors_l_3) << 24); + + // If secperfat is 0, we must be in a FAT32 volume; get secperfat + // from the FAT32 EBPB. The volume label and system ID string are also + // in different locations for FAT12/16 vs FAT32. + volinfo->secperfat = (uint16_t) lbr->bpb.secperfat_l | + (((uint16_t) lbr->bpb.secperfat_h) << 8); + if (!volinfo->secperfat) { + volinfo->secperfat = (uint32_t) lbr->ebpb.ebpb32.fatsize_0 | + (((uint32_t) lbr->ebpb.ebpb32.fatsize_1) << 8) | + (((uint32_t) lbr->ebpb.ebpb32.fatsize_2) << 16) | + (((uint32_t) lbr->ebpb.ebpb32.fatsize_3) << 24); + + memcpy(volinfo->label, lbr->ebpb.ebpb32.label, 11); + volinfo->label[11] = 0; + +// tag: OEMID, refer dosfs.h +// memcpy(volinfo->system, lbr->ebpb.ebpb32.system, 8); +// volinfo->system[8] = 0; + } + else { + memcpy(volinfo->label, lbr->ebpb.ebpb.label, 11); + volinfo->label[11] = 0; + +// tag: OEMID, refer dosfs.h +// memcpy(volinfo->system, lbr->ebpb.ebpb.system, 8); +// volinfo->system[8] = 0; + } + + // note: if rootentries is 0, we must be in a FAT32 volume. + volinfo->rootentries = (uint16_t) lbr->bpb.rootentries_l | + (((uint16_t) lbr->bpb.rootentries_h) << 8); + + // after extracting raw info we perform some useful precalculations + volinfo->fat1 = startsector + volinfo->reservedsecs; + + // The calculation below is designed to round up the root directory size for FAT12/16 + // and to simply ignore the root directory for FAT32, since it's a normal, expandable + // file in that situation. + if (volinfo->rootentries) { + volinfo->rootdir = volinfo->fat1 + (volinfo->secperfat * 2); + volinfo->dataarea = volinfo->rootdir + (((volinfo->rootentries * 32) + (SECTOR_SIZE - 1)) / SECTOR_SIZE); + } + else { + volinfo->dataarea = volinfo->fat1 + (volinfo->secperfat * 2); + volinfo->rootdir = (uint32_t) lbr->ebpb.ebpb32.root_0 | + (((uint32_t) lbr->ebpb.ebpb32.root_1) << 8) | + (((uint32_t) lbr->ebpb.ebpb32.root_2) << 16) | + (((uint32_t) lbr->ebpb.ebpb32.root_3) << 24); + } + + // Calculate number of clusters in data area and infer FAT type from this information. + volinfo->numclusters = (volinfo->numsecs - volinfo->dataarea) / volinfo->secperclus; + if (volinfo->numclusters < 4085) + volinfo->filesystem = FAT12; + else if (volinfo->numclusters < 65525) + volinfo->filesystem = FAT16; + else + volinfo->filesystem = FAT32; + + return DFS_OK; +} + +/* + Fetch FAT entry for specified cluster number + You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO + Returns a FAT32 BAD_CLUSTER value for any error, otherwise the contents of the desired + FAT entry. + scratchcache should point to a UINT32. This variable caches the physical sector number + last read into the scratch buffer for performance enhancement reasons. +*/ +uint32_t DFS_GetFAT(PVOLINFO volinfo, uint8_t *scratch, uint32_t *scratchcache, uint32_t cluster) +{ + uint32_t offset, sector, result; + + if (volinfo->filesystem == FAT12) { + offset = cluster + (cluster / 2); + } + else if (volinfo->filesystem == FAT16) { + offset = cluster * 2; + } + else if (volinfo->filesystem == FAT32) { + offset = cluster * 4; + } + else + return 0x0ffffff7; // FAT32 bad cluster + + // at this point, offset is the BYTE offset of the desired sector from the start + // of the FAT. Calculate the physical sector containing this FAT entry. + sector = ldiv(offset, SECTOR_SIZE).quot + volinfo->fat1; + + // If this is not the same sector we last read, then read it into RAM + if (sector != *scratchcache) { + if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) { + // avoid anyone assuming that this cache value is still valid, which + // might cause disk corruption + *scratchcache = 0; + return 0x0ffffff7; // FAT32 bad cluster + } + *scratchcache = sector; + } + + // At this point, we "merely" need to extract the relevant entry. + // This is easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry + // may span a sector boundary. The normal way around this is always to read two + // FAT sectors, but that luxury is (by design intent) unavailable to DOSFS. + offset = ldiv(offset, SECTOR_SIZE).rem; + + if (volinfo->filesystem == FAT12) { + // Special case for sector boundary - Store last byte of current sector. + // Then read in the next sector and put the first byte of that sector into + // the high byte of result. + if (offset == SECTOR_SIZE - 1) { + result = (uint32_t) scratch[offset]; + sector++; + if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) { + // avoid anyone assuming that this cache value is still valid, which + // might cause disk corruption + *scratchcache = 0; + return 0x0ffffff7; // FAT32 bad cluster + } + *scratchcache = sector; + // Thanks to Claudio Leonel for pointing out this missing line. + result |= ((uint32_t) scratch[0]) << 8; + } + else { + result = (uint32_t) scratch[offset] | + ((uint32_t) scratch[offset+1]) << 8; + } + if (cluster & 1) + result = result >> 4; + else + result = result & 0xfff; + } + else if (volinfo->filesystem == FAT16) { + result = (uint32_t) scratch[offset] | + ((uint32_t) scratch[offset+1]) << 8; + } + else if (volinfo->filesystem == FAT32) { + result = ((uint32_t) scratch[offset] | + ((uint32_t) scratch[offset+1]) << 8 | + ((uint32_t) scratch[offset+2]) << 16 | + ((uint32_t) scratch[offset+3]) << 24) & 0x0fffffff; + } + else + result = 0x0ffffff7; // FAT32 bad cluster + return result; +} + + +/* + Set FAT entry for specified cluster number + You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO + Returns DFS_ERRMISC for any error, otherwise DFS_OK + scratchcache should point to a UINT32. This variable caches the physical sector number + last read into the scratch buffer for performance enhancement reasons. + + NOTE: This code is HIGHLY WRITE-INEFFICIENT, particularly for flash media. Considerable + performance gains can be realized by caching the sector. However this is difficult to + achieve on FAT12 without requiring 2 sector buffers of scratch space, and it is a design + requirement of this code to operate on a single 512-byte scratch. + + If you are operating DOSFS over flash, you are strongly advised to implement a writeback + cache in your physical I/O driver. This will speed up your code significantly and will + also conserve power and flash write life. +*/ +uint32_t DFS_SetFAT(PVOLINFO volinfo, uint8_t *scratch, uint32_t *scratchcache, uint32_t cluster, uint32_t new_contents) +{ + uint32_t offset, sector, result; + if (volinfo->filesystem == FAT12) { + offset = cluster + (cluster / 2); + new_contents &=0xfff; + } + else if (volinfo->filesystem == FAT16) { + offset = cluster * 2; + new_contents &=0xffff; + } + else if (volinfo->filesystem == FAT32) { + offset = cluster * 4; + new_contents &=0x0fffffff; // FAT32 is really "FAT28" + } + else + return DFS_ERRMISC; + + // at this point, offset is the BYTE offset of the desired sector from the start + // of the FAT. Calculate the physical sector containing this FAT entry. + sector = ldiv(offset, SECTOR_SIZE).quot + volinfo->fat1; + + // If this is not the same sector we last read, then read it into RAM + if (sector != *scratchcache) { + if(DFS_ReadSector(volinfo->unit, scratch, sector, 1)) { + // avoid anyone assuming that this cache value is still valid, which + // might cause disk corruption + *scratchcache = 0; + return DFS_ERRMISC; + } + *scratchcache = sector; + } + + // At this point, we "merely" need to extract the relevant entry. + // This is easy for FAT16 and FAT32, but a royal PITA for FAT12 as a single entry + // may span a sector boundary. The normal way around this is always to read two + // FAT sectors, but that luxury is (by design intent) unavailable to DOSFS. + offset = ldiv(offset, SECTOR_SIZE).rem; + + if (volinfo->filesystem == FAT12) { + + // If this is an odd cluster, pre-shift the desired new contents 4 bits to + // make the calculations below simpler + if (cluster & 1) + new_contents = new_contents << 4; + + // Special case for sector boundary + if (offset == SECTOR_SIZE - 1) { + + // Odd cluster: High 12 bits being set + if (cluster & 1) { + scratch[offset] = (scratch[offset] & 0x0f) | new_contents & 0xf0; + } + // Even cluster: Low 12 bits being set + else { + scratch[offset] = new_contents & 0xff; + } + result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1); + // mirror the FAT into copy 2 + if (DFS_OK == result) + result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1); + + // If we wrote that sector OK, then read in the subsequent sector + // and poke the first byte with the remainder of this FAT entry. + if (DFS_OK == result) { + *scratchcache++; + result = DFS_ReadSector(volinfo->unit, scratch, *scratchcache, 1); + if (DFS_OK == result) { + // Odd cluster: High 12 bits being set + if (cluster & 1) { + scratch[0] = new_contents & 0xff00; + } + // Even cluster: Low 12 bits being set + else { + scratch[0] = (scratch[0] & 0xf0) | new_contents & 0x0f; + } + result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1); + // mirror the FAT into copy 2 + if (DFS_OK == result) + result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1); + } + else { + // avoid anyone assuming that this cache value is still valid, which + // might cause disk corruption + *scratchcache = 0; + } + } + } // if (offset == SECTOR_SIZE - 1) + + // Not a sector boundary. But we still have to worry about if it's an odd + // or even cluster number. + else { + // Odd cluster: High 12 bits being set + if (cluster & 1) { + scratch[offset] = (scratch[offset] & 0x0f) | new_contents & 0xf0; + scratch[offset+1] = new_contents & 0xff00; + } + // Even cluster: Low 12 bits being set + else { + scratch[offset] = new_contents & 0xff; + scratch[offset+1] = (scratch[offset+1] & 0xf0) | new_contents & 0x0f; + } + result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1); + // mirror the FAT into copy 2 + if (DFS_OK == result) + result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1); + } + } + else if (volinfo->filesystem == FAT16) { + scratch[offset] = (new_contents & 0xff); + scratch[offset+1] = (new_contents & 0xff00) >> 8; + result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1); + // mirror the FAT into copy 2 + if (DFS_OK == result) + result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1); + } + else if (volinfo->filesystem == FAT32) { + scratch[offset] = (new_contents & 0xff); + scratch[offset+1] = (new_contents & 0xff00) >> 8; + scratch[offset+2] = (new_contents & 0xff0000) >> 16; + scratch[offset+3] = (scratch[offset+3] & 0xf0) | ((new_contents & 0x0f000000) >> 24); + // Note well from the above: Per Microsoft's guidelines we preserve the upper + // 4 bits of the FAT32 cluster value. It's unclear what these bits will be used + // for; in every example I've encountered they are always zero. + result = DFS_WriteSector(volinfo->unit, scratch, *scratchcache, 1); + // mirror the FAT into copy 2 + if (DFS_OK == result) + result = DFS_WriteSector(volinfo->unit, scratch, (*scratchcache)+volinfo->secperfat, 1); + } + else + result = DFS_ERRMISC; + + return result; +} + +/* + Convert a filename element from canonical (8.3) to directory entry (11) form + src must point to the first non-separator character. + dest must point to a 12-byte buffer. +*/ +uint8_t *DFS_CanonicalToDir(uint8_t *dest, uint8_t *src) +{ + uint8_t *destptr = dest; + + memset(dest, ' ', 11); + dest[11] = 0; + + while (*src && (*src != DIR_SEPARATOR) && (destptr - dest < 11)) { + if (*src >= 'a' && *src <='z') { + *destptr++ = (*src - 'a') + 'A'; + src++; + } + else if (*src == '.') { + src++; + destptr = dest + 8; + } + else { + *destptr++ = *src++; + } + } + + return dest; +} + +/* + Find the first unused FAT entry + You must provide a scratch buffer for one sector (SECTOR_SIZE) and a populated VOLINFO + Returns a FAT32 BAD_CLUSTER value for any error, otherwise the contents of the desired + FAT entry. + Returns FAT32 bad_sector (0x0ffffff7) if there is no free cluster available +*/ +uint32_t DFS_GetFreeFAT(PVOLINFO volinfo, uint8_t *scratch) +{ + uint32_t i, result = 0xffffffff, scratchcache = 0; + + // Search starts at cluster 2, which is the first usable cluster + // NOTE: This search can't terminate at a bad cluster, because there might + // legitimately be bad clusters on the disk. + for (i=2; i < volinfo->numclusters; i++) { + result = DFS_GetFAT(volinfo, scratch, &scratchcache, i); + if (!result) { + return i; + } + } + return 0x0ffffff7; // Can't find a free cluster +} + + +/* + Open a directory for enumeration by DFS_GetNextDirEnt + You must supply a populated VOLINFO (see DFS_GetVolInfo) + The empty string or a string containing only the directory separator are + considered to be the root directory. + Returns 0 OK, nonzero for any error. +*/ +uint32_t DFS_OpenDir(PVOLINFO volinfo, uint8_t *dirname, PDIRINFO dirinfo) +{ + // Default behavior is a regular search for existing entries + dirinfo->flags = 0; + + if (!strlen((char *) dirname) || (strlen((char *) dirname) == 1 && dirname[0] == DIR_SEPARATOR)) { + if (volinfo->filesystem == FAT32) { + dirinfo->currentcluster = volinfo->rootdir; + dirinfo->currentsector = 0; + dirinfo->currententry = 0; + + // read first sector of directory + return DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((volinfo->rootdir - 2) * volinfo->secperclus), 1); + } + else { + dirinfo->currentcluster = 0; + dirinfo->currentsector = 0; + dirinfo->currententry = 0; + + // read first sector of directory + return DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->rootdir, 1); + } + } + + // This is not the root directory. We need to find the start of this subdirectory. + // We do this by devious means, using our own companion function DFS_GetNext. + else { + uint8_t tmpfn[12]; + uint8_t *ptr = dirname; + uint32_t result; + DIRENT de; + + if (volinfo->filesystem == FAT32) { + dirinfo->currentcluster = volinfo->rootdir; + dirinfo->currentsector = 0; + dirinfo->currententry = 0; + + // read first sector of directory + if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((volinfo->rootdir - 2) * volinfo->secperclus), 1)) + return DFS_ERRMISC; + } + else { + dirinfo->currentcluster = 0; + dirinfo->currentsector = 0; + dirinfo->currententry = 0; + + // read first sector of directory + if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->rootdir, 1)) + return DFS_ERRMISC; + } + + // skip leading path separators + while (*ptr == DIR_SEPARATOR && *ptr) + ptr++; + + // Scan the path from left to right, finding the start cluster of each entry + // Observe that this code is inelegant, but obviates the need for recursion. + while (*ptr) { + DFS_CanonicalToDir(tmpfn, ptr); + + de.name[0] = 0; + + do { + result = DFS_GetNext(volinfo, dirinfo, &de); + } while (!result && memcmp(de.name, tmpfn, 11)); + + if (!memcmp(de.name, tmpfn, 11) && ((de.attr & ATTR_DIRECTORY) == ATTR_DIRECTORY)) { + if (volinfo->filesystem == FAT32) { + dirinfo->currentcluster = (uint32_t) de.startclus_l_l | + ((uint32_t) de.startclus_l_h) << 8 | + ((uint32_t) de.startclus_h_l) << 16 | + ((uint32_t) de.startclus_h_h) << 24; + } + else { + dirinfo->currentcluster = (uint32_t) de.startclus_l_l | + ((uint32_t) de.startclus_l_h) << 8; + } + dirinfo->currentsector = 0; + dirinfo->currententry = 0; + + if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((dirinfo->currentcluster - 2) * volinfo->secperclus), 1)) + return DFS_ERRMISC; + } + else if (!memcmp(de.name, tmpfn, 11) && !(de.attr & ATTR_DIRECTORY)) + return DFS_NOTFOUND; + + // seek to next item in list + while (*ptr != DIR_SEPARATOR && *ptr) + ptr++; + if (*ptr == DIR_SEPARATOR) + ptr++; + } + + if (!dirinfo->currentcluster) + return DFS_NOTFOUND; + } + return DFS_OK; +} + +/* + Get next entry in opened directory structure. Copies fields into the dirent + structure, updates dirinfo. Note that it is the _caller's_ responsibility to + handle the '.' and '..' entries. + A deleted file will be returned as a NULL entry (first char of filename=0) + by this code. Filenames beginning with 0x05 will be translated to 0xE5 + automatically. Long file name entries will be returned as NULL. + returns DFS_EOF if there are no more entries, DFS_OK if this entry is valid, + or DFS_ERRMISC for a media error +*/ +uint32_t DFS_GetNext(PVOLINFO volinfo, PDIRINFO dirinfo, PDIRENT dirent) +{ + uint32_t tempint; // required by DFS_GetFAT + + // Do we need to read the next sector of the directory? + if (dirinfo->currententry >= SECTOR_SIZE / sizeof(DIRENT)) { + dirinfo->currententry = 0; + dirinfo->currentsector++; + + // Root directory; special case handling + // Note that currentcluster will only ever be zero if both: + // (a) this is the root directory, and + // (b) we are on a FAT12/16 volume, where the root dir can't be expanded + if (dirinfo->currentcluster == 0) { + // Trying to read past end of root directory? + if (dirinfo->currentsector * (SECTOR_SIZE / sizeof(DIRENT)) >= volinfo->rootentries) + return DFS_EOF; + + // Otherwise try to read the next sector + if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->rootdir + dirinfo->currentsector, 1)) + return DFS_ERRMISC; + } + + // Normal handling + else { + if (dirinfo->currentsector >= volinfo->secperclus) { + dirinfo->currentsector = 0; + if ((dirinfo->currentcluster >= 0xff7 && volinfo->filesystem == FAT12) || + (dirinfo->currentcluster >= 0xfff7 && volinfo->filesystem == FAT16) || + (dirinfo->currentcluster >= 0x0ffffff7 && volinfo->filesystem == FAT32)) { + + // We are at the end of the directory chain. If this is a normal + // find operation, we should indicate that there is nothing more + // to see. + if (!(dirinfo->flags & DFS_DI_BLANKENT)) + return DFS_EOF; + + // On the other hand, if this is a "find free entry" search, + // we need to tell the caller to allocate a new cluster + else + return DFS_ALLOCNEW; + } + dirinfo->currentcluster = DFS_GetFAT(volinfo, dirinfo->scratch, &tempint, dirinfo->currentcluster); + } + if (DFS_ReadSector(volinfo->unit, dirinfo->scratch, volinfo->dataarea + ((dirinfo->currentcluster - 2) * volinfo->secperclus) + dirinfo->currentsector, 1)) + return DFS_ERRMISC; + } + } + + memcpy(dirent, &(((PDIRENT) dirinfo->scratch)[dirinfo->currententry]), sizeof(DIRENT)); + + if (dirent->name[0] == 0) { // no more files in this directory + // If this is a "find blank" then we can reuse this name. + if (dirinfo->flags & DFS_DI_BLANKENT) + return DFS_OK; + else + return DFS_EOF; + } + + if (dirent->name[0] == 0xe5) // handle deleted file entries + dirent->name[0] = 0; + else if ((dirent->attr & ATTR_LONG_NAME) == ATTR_LONG_NAME) + dirent->name[0] = 0; + else if (dirent->name[0] == 0x05) // handle kanji filenames beginning with 0xE5 + dirent->name[0] = 0xe5; + + dirinfo->currententry++; + + return DFS_OK; +} + +/* + INTERNAL + Find a free directory entry in the directory specified by path + This function MAY cause a disk write if it is necessary to extend the directory + size. + Note - di.scratch must be preinitialized to point to a sector scratch buffer + de is a scratch structure + Returns DFS_ERRMISC if a new entry could not be located or created + de is updated with the same return information you would expect from DFS_GetNext +*/ +uint32_t DFS_GetFreeDirEnt(PVOLINFO volinfo, uint8_t *path, PDIRINFO di, PDIRENT de) +{ + uint32_t tempclus,i; + + if (DFS_OpenDir(volinfo, path, di)) + return DFS_NOTFOUND; + + // Set "search for empty" flag so DFS_GetNext knows what we're doing + di->flags |= DFS_DI_BLANKENT; + + // We seek through the directory looking for an empty entry + // Note we are reusing tempclus as a temporary result holder. + tempclus = 0; + do { + tempclus = DFS_GetNext(volinfo, di, de); + + // Empty entry found + if (tempclus == DFS_OK && (!de->name[0])) { + return DFS_OK; + } + + // End of root directory reached + else if (tempclus == DFS_EOF) + return DFS_ERRMISC; + + else if (tempclus == DFS_ALLOCNEW) { + tempclus = DFS_GetFreeFAT(volinfo, di->scratch); + if (tempclus == 0x0ffffff7) + return DFS_ERRMISC; + + // write out zeroed sectors to the new cluster + memset(di->scratch, 0, SECTOR_SIZE); + for (i=0;isecperclus;i++) { + if (DFS_WriteSector(volinfo->unit, di->scratch, volinfo->dataarea + ((tempclus - 2) * volinfo->secperclus) + i, 1)) + return DFS_ERRMISC; + } + // Point old end cluster to newly allocated cluster + i = 0; + DFS_SetFAT(volinfo, di->scratch, &i, di->currentcluster, tempclus); + + // Update DIRINFO so caller knows where to place the new file + di->currentcluster = tempclus; + di->currentsector = 0; + di->currententry = 1; // since the code coming after this expects to subtract 1 + + // Mark newly allocated cluster as end of chain + switch(volinfo->filesystem) { + case FAT12: tempclus = 0xff8; break; + case FAT16: tempclus = 0xfff8; break; + case FAT32: tempclus = 0x0ffffff8; break; + default: return DFS_ERRMISC; + } + DFS_SetFAT(volinfo, di->scratch, &i, di->currentcluster, tempclus); + } + } while (!tempclus); + + // We shouldn't get here + return DFS_ERRMISC; +} + +/* + Open a file for reading or writing. You supply populated VOLINFO, a path to the file, + mode (DFS_READ or DFS_WRITE) and an empty fileinfo structure. You also need to + provide a pointer to a sector-sized scratch buffer. + Returns various DFS_* error states. If the result is DFS_OK, fileinfo can be used + to access the file from this point on. +*/ +uint32_t DFS_OpenFile(PVOLINFO volinfo, uint8_t *path, uint8_t mode, uint8_t *scratch, PFILEINFO fileinfo) +{ + uint8_t tmppath[MAX_PATH]; + uint8_t filename[12]; + uint8_t *p; + DIRINFO di; + DIRENT de; + + // larwe 2006-09-16 +1 zero out file structure + memset(fileinfo, 0, sizeof(FILEINFO)); + + // save access mode + fileinfo->mode = mode; + + // Get a local copy of the path. If it's longer than MAX_PATH, abort. + strncpy((char *) tmppath, (char *) path, MAX_PATH); + tmppath[MAX_PATH - 1] = 0; + if (strcmp((char *) path,(char *) tmppath)) { + return DFS_PATHLEN; + } + + // strip leading path separators + while (tmppath[0] == DIR_SEPARATOR) + strcpy((char *) tmppath, (char *) tmppath + 1); + + // Parse filename off the end of the supplied path + p = tmppath; + while (*(p++)); + + p--; + while (p > tmppath && *p != DIR_SEPARATOR) // larwe 9/16/06 ">=" to ">" bugfix + p--; + if (*p == DIR_SEPARATOR) + p++; + + DFS_CanonicalToDir(filename, p); + + if (p > tmppath) + p--; + if (*p == DIR_SEPARATOR || p == tmppath) // larwe 9/16/06 +"|| p == tmppath" bugfix + *p = 0; + + // At this point, if our path was MYDIR/MYDIR2/FILE.EXT, filename = "FILE EXT" and + // tmppath = "MYDIR/MYDIR2". + di.scratch = scratch; + if (DFS_OpenDir(volinfo, tmppath, &di)) + return DFS_NOTFOUND; + + while (!DFS_GetNext(volinfo, &di, &de)) { + if (!memcmp(de.name, filename, 11)) { + // You can't use this function call to open a directory. + if (de.attr & ATTR_DIRECTORY) + return DFS_NOTFOUND; + + fileinfo->volinfo = volinfo; + fileinfo->pointer = 0; + // The reason we store this extra info about the file is so that we can + // speedily update the file size, modification date, etc. on a file that is + // opened for writing. + if (di.currentcluster == 0) + fileinfo->dirsector = volinfo->rootdir + di.currentsector; + else + fileinfo->dirsector = volinfo->dataarea + ((di.currentcluster - 2) * volinfo->secperclus) + di.currentsector; + fileinfo->diroffset = di.currententry - 1; + if (volinfo->filesystem == FAT32) { + fileinfo->cluster = (uint32_t) de.startclus_l_l | + ((uint32_t) de.startclus_l_h) << 8 | + ((uint32_t) de.startclus_h_l) << 16 | + ((uint32_t) de.startclus_h_h) << 24; + } + else { + fileinfo->cluster = (uint32_t) de.startclus_l_l | + ((uint32_t) de.startclus_l_h) << 8; + } + fileinfo->firstcluster = fileinfo->cluster; + fileinfo->filelen = (uint32_t) de.filesize_0 | + ((uint32_t) de.filesize_1) << 8 | + ((uint32_t) de.filesize_2) << 16 | + ((uint32_t) de.filesize_3) << 24; + + return DFS_OK; + } + } + + // At this point, we KNOW the file does not exist. If the file was opened + // with write access, we can create it. + if (mode & DFS_WRITE) { + uint32_t cluster, temp; + + // Locate or create a directory entry for this file + if (DFS_OK != DFS_GetFreeDirEnt(volinfo, tmppath, &di, &de)) + return DFS_ERRMISC; + + // put sane values in the directory entry + memset(&de, 0, sizeof(de)); + memcpy(de.name, filename, 11); + de.crttime_l = 0x20; // 01:01:00am, Jan 1, 2006. + de.crttime_h = 0x08; + de.crtdate_l = 0x11; + de.crtdate_h = 0x34; + de.lstaccdate_l = 0x11; + de.lstaccdate_h = 0x34; + de.wrttime_l = 0x20; + de.wrttime_h = 0x08; + de.wrtdate_l = 0x11; + de.wrtdate_h = 0x34; + + // allocate a starting cluster for the directory entry + cluster = DFS_GetFreeFAT(volinfo, scratch); + + de.startclus_l_l = cluster & 0xff; + de.startclus_l_h = (cluster & 0xff00) >> 8; + de.startclus_h_l = (cluster & 0xff0000) >> 16; + de.startclus_h_h = (cluster & 0xff000000) >> 24; + + // update FILEINFO for our caller's sake + fileinfo->volinfo = volinfo; + fileinfo->pointer = 0; + // The reason we store this extra info about the file is so that we can + // speedily update the file size, modification date, etc. on a file that is + // opened for writing. + if (di.currentcluster == 0) + fileinfo->dirsector = volinfo->rootdir + di.currentsector; + else + fileinfo->dirsector = volinfo->dataarea + ((di.currentcluster - 2) * volinfo->secperclus) + di.currentsector; + fileinfo->diroffset = di.currententry - 1; + fileinfo->cluster = cluster; + fileinfo->firstcluster = cluster; + fileinfo->filelen = 0; + + // write the directory entry + // note that we no longer have the sector containing the directory entry, + // tragically, so we have to re-read it + if (DFS_ReadSector(volinfo->unit, scratch, fileinfo->dirsector, 1)) + return DFS_ERRMISC; + memcpy(&(((PDIRENT) scratch)[di.currententry-1]), &de, sizeof(DIRENT)); + if (DFS_WriteSector(volinfo->unit, scratch, fileinfo->dirsector, 1)) + return DFS_ERRMISC; + + // Mark newly allocated cluster as end of chain + switch(volinfo->filesystem) { + case FAT12: cluster = 0xff8; break; + case FAT16: cluster = 0xfff8; break; + case FAT32: cluster = 0x0ffffff8; break; + default: return DFS_ERRMISC; + } + temp = 0; + DFS_SetFAT(volinfo, scratch, &temp, fileinfo->cluster, cluster); + + return DFS_OK; + } + + return DFS_NOTFOUND; +} + +/* + Read an open file + You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a + pointer to a SECTOR_SIZE scratch buffer. + Note that returning DFS_EOF is not an error condition. This function updates the + successcount field with the number of bytes actually read. +*/ +uint32_t DFS_ReadFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len) +{ + uint32_t remain; + uint32_t result = DFS_OK; + uint32_t sector; + uint32_t bytesread; + + // Don't try to read past EOF + if (len > fileinfo->filelen - fileinfo->pointer) + len = fileinfo->filelen - fileinfo->pointer; + + remain = len; + *successcount = 0; + + while (remain && result == DFS_OK) { + // This is a bit complicated. The sector we want to read is addressed at a cluster + // granularity by the fileinfo->cluster member. The file pointer tells us how many + // extra sectors to add to that number. + sector = fileinfo->volinfo->dataarea + + ((fileinfo->cluster - 2) * fileinfo->volinfo->secperclus) + + div(div(fileinfo->pointer,fileinfo->volinfo->secperclus * SECTOR_SIZE).rem, SECTOR_SIZE).quot; + + // Case 1 - File pointer is not on a sector boundary + if (div(fileinfo->pointer, SECTOR_SIZE).rem) { + uint16_t tempreadsize; + + // We always have to go through scratch in this case + result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1); + + // This is the number of bytes that we actually care about in the sector + // just read. + tempreadsize = SECTOR_SIZE - (div(fileinfo->pointer, SECTOR_SIZE).rem); + + // Case 1A - We want the entire remainder of the sector. After this + // point, all passes through the read loop will be aligned on a sector + // boundary, which allows us to go through the optimal path 2A below. + if (remain >= tempreadsize) { + memcpy(buffer, scratch + (SECTOR_SIZE - tempreadsize), tempreadsize); + bytesread = tempreadsize; + buffer += tempreadsize; + fileinfo->pointer += tempreadsize; + remain -= tempreadsize; + } + // Case 1B - This read concludes the file read operation + else { + memcpy(buffer, scratch + (SECTOR_SIZE - tempreadsize), remain); + + buffer += remain; + fileinfo->pointer += remain; + bytesread = remain; + remain = 0; + } + } + // Case 2 - File pointer is on sector boundary + else { + // Case 2A - We have at least one more full sector to read and don't have + // to go through the scratch buffer. You could insert optimizations here to + // read multiple sectors at a time, if you were thus inclined (note that + // the maximum multi-read you could perform is a single cluster, so it would + // be advantageous to have code similar to case 1A above that would round the + // pointer to a cluster boundary the first pass through, so all subsequent + // [large] read requests would be able to go a cluster at a time). + if (remain >= SECTOR_SIZE) { + result = DFS_ReadSector(fileinfo->volinfo->unit, buffer, sector, 1); + remain -= SECTOR_SIZE; + buffer += SECTOR_SIZE; + fileinfo->pointer += SECTOR_SIZE; + bytesread = SECTOR_SIZE; + } + // Case 2B - We are only reading a partial sector + else { + result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1); + memcpy(buffer, scratch, remain); + buffer += remain; + fileinfo->pointer += remain; + bytesread = remain; + remain = 0; + } + } + + *successcount += bytesread; + + // check to see if we stepped over a cluster boundary + if (div(fileinfo->pointer - bytesread, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot != + div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) { + // An act of minor evil - we use bytesread as a scratch integer, knowing that + // its value is not used after updating *successcount above + bytesread = 0; + if (((fileinfo->volinfo->filesystem == FAT12) && (fileinfo->cluster >= 0xff8)) || + ((fileinfo->volinfo->filesystem == FAT16) && (fileinfo->cluster >= 0xfff8)) || + ((fileinfo->volinfo->filesystem == FAT32) && (fileinfo->cluster >= 0x0ffffff8))) + result = DFS_EOF; + else + fileinfo->cluster = DFS_GetFAT(fileinfo->volinfo, scratch, &bytesread, fileinfo->cluster); + } + } + + return result; +} + +/* + Seek file pointer to a given position + This function does not return status - refer to the fileinfo->pointer value + to see where the pointer wound up. + Requires a SECTOR_SIZE scratch buffer +*/ +void DFS_Seek(PFILEINFO fileinfo, uint32_t offset, uint8_t *scratch) +{ + uint32_t tempint; + + // larwe 9/16/06 bugfix split case 0a/0b and changed fallthrough handling + // Case 0a - Return immediately for degenerate case + if (offset == fileinfo->pointer) { + return; + } + + // Case 0b - Don't allow the user to seek past the end of the file + if (offset > fileinfo->filelen) { + offset = fileinfo->filelen; + // NOTE NO RETURN HERE! + } + + // Case 1 - Simple rewind to start + // Note _intentional_ fallthrough from Case 0b above + if (offset == 0) { + fileinfo->cluster = fileinfo->firstcluster; + fileinfo->pointer = 0; + return; // larwe 9/16/06 +1 bugfix + } + // Case 2 - Seeking backwards. Need to reset and seek forwards + else if (offset < fileinfo->pointer) { + fileinfo->cluster = fileinfo->firstcluster; + fileinfo->pointer = 0; + // NOTE NO RETURN HERE! + } + + // Case 3 - Seeking forwards + // Note _intentional_ fallthrough from Case 2 above + + // Case 3a - Seek size does not cross cluster boundary - + // very simple case + // larwe 9/16/06 changed .rem to .quot in both div calls, bugfix + if (div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot == + div(fileinfo->pointer + offset, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) { + fileinfo->pointer = offset; + } + // Case 3b - Seeking across cluster boundary(ies) + else { + // round file pointer down to cluster boundary + fileinfo->pointer = div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot * + fileinfo->volinfo->secperclus * SECTOR_SIZE; + + // seek by clusters + // larwe 9/30/06 bugfix changed .rem to .quot in both div calls + while (div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot != + div(fileinfo->pointer + offset, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) { + + fileinfo->cluster = DFS_GetFAT(fileinfo->volinfo, scratch, &tempint, fileinfo->cluster); + // Abort if there was an error + if (fileinfo->cluster == 0x0ffffff7) { + fileinfo->pointer = 0; + fileinfo->cluster = fileinfo->firstcluster; + return; + } + fileinfo->pointer += SECTOR_SIZE * fileinfo->volinfo->secperclus; + } + + // since we know the cluster is right, we have no more work to do + fileinfo->pointer = offset; + } +} + +/* + Delete a file + scratch must point to a sector-sized buffer +*/ +uint32_t DFS_UnlinkFile(PVOLINFO volinfo, uint8_t *path, uint8_t *scratch) +{ + PDIRENT de = (PDIRENT) scratch; + FILEINFO fi; + uint32_t cache = 0; + uint32_t tempclus; + + // DFS_OpenFile gives us all the information we need to delete it + if (DFS_OK != DFS_OpenFile(volinfo, path, DFS_READ, scratch, &fi)) + return DFS_NOTFOUND; + + // First, read the directory sector and delete that entry + if (DFS_ReadSector(volinfo->unit, scratch, fi.dirsector, 1)) + return DFS_ERRMISC; + ((PDIRENT) scratch)[fi.diroffset].name[0] = 0xe5; + if (DFS_WriteSector(volinfo->unit, scratch, fi.dirsector, 1)) + return DFS_ERRMISC; + + // Now follow the cluster chain to free the file space + while (!((volinfo->filesystem == FAT12 && fi.firstcluster >= 0x0ff7) || + (volinfo->filesystem == FAT16 && fi.firstcluster >= 0xfff7) || + (volinfo->filesystem == FAT32 && fi.firstcluster >= 0x0ffffff7))) { + tempclus = fi.firstcluster; + + fi.firstcluster = DFS_GetFAT(volinfo, scratch, &cache, fi.firstcluster); + DFS_SetFAT(volinfo, scratch, &cache, tempclus, 0); + + } + return DFS_OK; +} + + +/* + Write an open file + You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a + pointer to a SECTOR_SIZE scratch buffer. + This function updates the successcount field with the number of bytes actually written. +*/ +uint32_t DFS_WriteFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len) +{ + uint32_t remain; + uint32_t result = DFS_OK; + uint32_t sector; + uint32_t byteswritten; + + // Don't allow writes to a file that's open as readonly + if (!(fileinfo->mode & DFS_WRITE)) + return DFS_ERRMISC; + + remain = len; + *successcount = 0; + + while (remain && result == DFS_OK) { + // This is a bit complicated. The sector we want to read is addressed at a cluster + // granularity by the fileinfo->cluster member. The file pointer tells us how many + // extra sectors to add to that number. + sector = fileinfo->volinfo->dataarea + + ((fileinfo->cluster - 2) * fileinfo->volinfo->secperclus) + + div(div(fileinfo->pointer,fileinfo->volinfo->secperclus * SECTOR_SIZE).rem, SECTOR_SIZE).quot; + + // Case 1 - File pointer is not on a sector boundary + if (div(fileinfo->pointer, SECTOR_SIZE).rem) { + uint16_t tempsize; + + // We always have to go through scratch in this case + result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1); + + // This is the number of bytes that we don't want to molest in the + // scratch sector just read. + tempsize = div(fileinfo->pointer, SECTOR_SIZE).rem; + + // Case 1A - We are writing the entire remainder of the sector. After + // this point, all passes through the read loop will be aligned on a + // sector boundary, which allows us to go through the optimal path + // 2A below. + if (remain >= SECTOR_SIZE - tempsize) { + memcpy(scratch + tempsize, buffer, SECTOR_SIZE - tempsize); + if (!result) + result = DFS_WriteSector(fileinfo->volinfo->unit, scratch, sector, 1); + + byteswritten = SECTOR_SIZE - tempsize; + buffer += SECTOR_SIZE - tempsize; + fileinfo->pointer += SECTOR_SIZE - tempsize; + if (fileinfo->filelen < fileinfo->pointer) { + fileinfo->filelen = fileinfo->pointer; + } + remain -= SECTOR_SIZE - tempsize; + } + // Case 1B - This concludes the file write operation + else { + memcpy(scratch + tempsize, buffer, remain); + if (!result) + result = DFS_WriteSector(fileinfo->volinfo->unit, scratch, sector, 1); + + buffer += remain; + fileinfo->pointer += remain; + if (fileinfo->filelen < fileinfo->pointer) { + fileinfo->filelen = fileinfo->pointer; + } + byteswritten = remain; + remain = 0; + } + } // case 1 + // Case 2 - File pointer is on sector boundary + else { + // Case 2A - We have at least one more full sector to write and don't have + // to go through the scratch buffer. You could insert optimizations here to + // write multiple sectors at a time, if you were thus inclined. Refer to + // similar notes in DFS_ReadFile. + if (remain >= SECTOR_SIZE) { + result = DFS_WriteSector(fileinfo->volinfo->unit, buffer, sector, 1); + remain -= SECTOR_SIZE; + buffer += SECTOR_SIZE; + fileinfo->pointer += SECTOR_SIZE; + if (fileinfo->filelen < fileinfo->pointer) { + fileinfo->filelen = fileinfo->pointer; + } + byteswritten = SECTOR_SIZE; + } + // Case 2B - We are only writing a partial sector and potentially need to + // go through the scratch buffer. + else { + // If the current file pointer is not yet at or beyond the file + // length, we are writing somewhere in the middle of the file and + // need to load the original sector to do a read-modify-write. + if (fileinfo->pointer < fileinfo->filelen) { + result = DFS_ReadSector(fileinfo->volinfo->unit, scratch, sector, 1); + if (!result) { + memcpy(scratch, buffer, remain); + result = DFS_WriteSector(fileinfo->volinfo->unit, scratch, sector, 1); + } + } + else { + result = DFS_WriteSector(fileinfo->volinfo->unit, buffer, sector, 1); + } + + buffer += remain; + fileinfo->pointer += remain; + if (fileinfo->filelen < fileinfo->pointer) { + fileinfo->filelen = fileinfo->pointer; + } + byteswritten = remain; + remain = 0; + } + } + + *successcount += byteswritten; + + // check to see if we stepped over a cluster boundary + if (div(fileinfo->pointer - byteswritten, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot != + div(fileinfo->pointer, fileinfo->volinfo->secperclus * SECTOR_SIZE).quot) { + uint32_t lastcluster; + + // We've transgressed into another cluster. If we were already at EOF, + // we need to allocate a new cluster. + // An act of minor evil - we use byteswritten as a scratch integer, knowing + // that its value is not used after updating *successcount above + byteswritten = 0; + + lastcluster = fileinfo->cluster; + fileinfo->cluster = DFS_GetFAT(fileinfo->volinfo, scratch, &byteswritten, fileinfo->cluster); + + // Allocate a new cluster? + if (((fileinfo->volinfo->filesystem == FAT12) && (fileinfo->cluster >= 0xff8)) || + ((fileinfo->volinfo->filesystem == FAT16) && (fileinfo->cluster >= 0xfff8)) || + ((fileinfo->volinfo->filesystem == FAT32) && (fileinfo->cluster >= 0x0ffffff8))) { + uint32_t tempclus; + + tempclus = DFS_GetFreeFAT(fileinfo->volinfo, scratch); + byteswritten = 0; // invalidate cache + if (tempclus == 0x0ffffff7) + return DFS_ERRMISC; + + // Link new cluster onto file + DFS_SetFAT(fileinfo->volinfo, scratch, &byteswritten, lastcluster, tempclus); + fileinfo->cluster = tempclus; + + // Mark newly allocated cluster as end of chain + switch(fileinfo->volinfo->filesystem) { + case FAT12: tempclus = 0xff8; break; + case FAT16: tempclus = 0xfff8; break; + case FAT32: tempclus = 0x0ffffff8; break; + default: return DFS_ERRMISC; + } + DFS_SetFAT(fileinfo->volinfo, scratch, &byteswritten, fileinfo->cluster, tempclus); + + result = DFS_OK; + } + // No else clause is required. + } + } + + // Update directory entry + if (DFS_ReadSector(fileinfo->volinfo->unit, scratch, fileinfo->dirsector, 1)) + return DFS_ERRMISC; + ((PDIRENT) scratch)[fileinfo->diroffset].filesize_0 = fileinfo->filelen & 0xff; + ((PDIRENT) scratch)[fileinfo->diroffset].filesize_1 = (fileinfo->filelen & 0xff00) >> 8; + ((PDIRENT) scratch)[fileinfo->diroffset].filesize_2 = (fileinfo->filelen & 0xff0000) >> 16; + ((PDIRENT) scratch)[fileinfo->diroffset].filesize_3 = (fileinfo->filelen & 0xff000000) >> 24; + if (DFS_WriteSector(fileinfo->volinfo->unit, scratch, fileinfo->dirsector, 1)) + return DFS_ERRMISC; + return result; +} diff --git a/dosfs/dosfs.h b/dosfs/dosfs.h new file mode 100755 index 0000000..427bf50 --- /dev/null +++ b/dosfs/dosfs.h @@ -0,0 +1,374 @@ +/* + DOSFS Embedded FAT-Compatible Filesystem + (C) 2005 Lewin A.R.W. Edwards (sysadm@zws.com) +*/ + +#ifndef _DOSFS_H +#define _DOSFS_H + +#include + +//=================================================================== +// User-supplied functions +uint32_t DFS_ReadSector(uint8_t unit, uint8_t *buffer, uint32_t sector, uint32_t count); +uint32_t DFS_WriteSector(uint8_t unit, uint8_t *buffer, uint32_t sector, uint32_t count); + + +//=================================================================== +// Configurable items +#define MAX_PATH 64 // Maximum path length (increasing this will + // GREATLY increase stack requirements!) +#define DIR_SEPARATOR '/' // character separating directory components + +// End of configurable items +//=================================================================== + +//=================================================================== +// 32-bit error codes +#define DFS_OK 0 // no error +#define DFS_EOF 1 // end of file (not an error) +#define DFS_WRITEPROT 2 // volume is write protected +#define DFS_NOTFOUND 3 // path or file not found +#define DFS_PATHLEN 4 // path too long +#define DFS_ALLOCNEW 5 // must allocate new directory cluster +#define DFS_ERRMISC 0xffffffff // generic error + +//=================================================================== +// File access modes +#define DFS_READ 1 // read-only +#define DFS_WRITE 2 // write-only + +//=================================================================== +// Miscellaneous constants +#define SECTOR_SIZE 512 // sector size in bytes + +//=================================================================== +// Internal subformat identifiers +#define FAT12 0 +#define FAT16 1 +#define FAT32 2 + +//=================================================================== +// DOS attribute bits +#define ATTR_READ_ONLY 0x01 +#define ATTR_HIDDEN 0x02 +#define ATTR_SYSTEM 0x04 +#define ATTR_VOLUME_ID 0x08 +#define ATTR_DIRECTORY 0x10 +#define ATTR_ARCHIVE 0x20 +#define ATTR_LONG_NAME (ATTR_READ_ONLY | ATTR_HIDDEN | ATTR_SYSTEM | ATTR_VOLUME_ID) + + +/* + Directory entry structure + note: if name[0] == 0xe5, this is a free dir entry + if name[0] == 0x00, this is a free entry and all subsequent entries are free + if name[0] == 0x05, the first character of the name is 0xe5 [a kanji nicety] + + Date format: bit 0-4 = day of month (1-31) + bit 5-8 = month, 1=Jan..12=Dec + bit 9-15 = count of years since 1980 (0-127) + Time format: bit 0-4 = 2-second count, (0-29) + bit 5-10 = minutes (0-59) + bit 11-15= hours (0-23) +*/ +typedef struct _tagDIRENT { + uint8_t name[11]; // filename + uint8_t attr; // attributes (see ATTR_* constant definitions) + uint8_t reserved; // reserved, must be 0 + uint8_t crttimetenth; // create time, 10ths of a second (0-199 are valid) + uint8_t crttime_l; // creation time low byte + uint8_t crttime_h; // creation time high byte + uint8_t crtdate_l; // creation date low byte + uint8_t crtdate_h; // creation date high byte + uint8_t lstaccdate_l; // last access date low byte + uint8_t lstaccdate_h; // last access date high byte + uint8_t startclus_h_l; // high word of first cluster, low byte (FAT32) + uint8_t startclus_h_h; // high word of first cluster, high byte (FAT32) + uint8_t wrttime_l; // last write time low byte + uint8_t wrttime_h; // last write time high byte + uint8_t wrtdate_l; // last write date low byte + uint8_t wrtdate_h; // last write date high byte + uint8_t startclus_l_l; // low word of first cluster, low byte + uint8_t startclus_l_h; // low word of first cluster, high byte + uint8_t filesize_0; // file size, low byte + uint8_t filesize_1; // + uint8_t filesize_2; // + uint8_t filesize_3; // file size, high byte +} DIRENT, *PDIRENT; + +/* + Partition table entry structure +*/ +typedef struct _tagPTINFO { + uint8_t active; // 0x80 if partition active + uint8_t start_h; // starting head + uint8_t start_cs_l; // starting cylinder and sector (low byte) + uint8_t start_cs_h; // starting cylinder and sector (high byte) + uint8_t type; // type ID byte + uint8_t end_h; // ending head + uint8_t end_cs_l; // ending cylinder and sector (low byte) + uint8_t end_cs_h; // ending cylinder and sector (high byte) + uint8_t start_0; // starting sector# (low byte) + uint8_t start_1; // + uint8_t start_2; // + uint8_t start_3; // starting sector# (high byte) + uint8_t size_0; // size of partition (low byte) + uint8_t size_1; // + uint8_t size_2; // + uint8_t size_3; // size of partition (high byte) +} PTINFO, *PPTINFO; + +/* + Master Boot Record structure +*/ +typedef struct _tagMBR { + uint8_t bootcode[0x1be]; // boot sector + PTINFO ptable[4]; // four partition table structures + uint8_t sig_55; // 0x55 signature byte + uint8_t sig_aa; // 0xaa signature byte +} MBR, *PMBR; + +/* + BIOS Parameter Block structure (FAT12/16) +*/ +typedef struct _tagBPB { + uint8_t bytepersec_l; // bytes per sector low byte (0x00) + uint8_t bytepersec_h; // bytes per sector high byte (0x02) + uint8_t secperclus; // sectors per cluster (1,2,4,8,16,32,64,128 are valid) + uint8_t reserved_l; // reserved sectors low byte + uint8_t reserved_h; // reserved sectors high byte + uint8_t numfats; // number of FAT copies (2) + uint8_t rootentries_l; // number of root dir entries low byte (0x00 normally) + uint8_t rootentries_h; // number of root dir entries high byte (0x02 normally) + uint8_t sectors_s_l; // small num sectors low byte + uint8_t sectors_s_h; // small num sectors high byte + uint8_t mediatype; // media descriptor byte + uint8_t secperfat_l; // sectors per FAT low byte + uint8_t secperfat_h; // sectors per FAT high byte + uint8_t secpertrk_l; // sectors per track low byte + uint8_t secpertrk_h; // sectors per track high byte + uint8_t heads_l; // heads low byte + uint8_t heads_h; // heads high byte + uint8_t hidden_0; // hidden sectors low byte + uint8_t hidden_1; // (note - this is the number of MEDIA sectors before + uint8_t hidden_2; // first sector of VOLUME - we rely on the MBR instead) + uint8_t hidden_3; // hidden sectors high byte + uint8_t sectors_l_0; // large num sectors low byte + uint8_t sectors_l_1; // + uint8_t sectors_l_2; // + uint8_t sectors_l_3; // large num sectors high byte +} BPB, *PBPB; + +/* + Extended BIOS Parameter Block structure (FAT12/16) +*/ +typedef struct _tagEBPB { + uint8_t unit; // int 13h drive# + uint8_t head; // archaic, used by Windows NT-class OSes for flags + uint8_t signature; // 0x28 or 0x29 + uint8_t serial_0; // serial# + uint8_t serial_1; // serial# + uint8_t serial_2; // serial# + uint8_t serial_3; // serial# + uint8_t label[11]; // volume label + uint8_t system[8]; // filesystem ID +} EBPB, *PEBPB; + +/* + Extended BIOS Parameter Block structure (FAT32) +*/ +typedef struct _tagEBPB32 { + uint8_t fatsize_0; // big FAT size in sectors low byte + uint8_t fatsize_1; // + uint8_t fatsize_2; // + uint8_t fatsize_3; // big FAT size in sectors high byte + uint8_t extflags_l; // extended flags low byte + uint8_t extflags_h; // extended flags high byte + uint8_t fsver_l; // filesystem version (0x00) low byte + uint8_t fsver_h; // filesystem version (0x00) high byte + uint8_t root_0; // cluster of root dir, low byte + uint8_t root_1; // + uint8_t root_2; // + uint8_t root_3; // cluster of root dir, high byte + uint8_t fsinfo_l; // sector pointer to FSINFO within reserved area, low byte (2) + uint8_t fsinfo_h; // sector pointer to FSINFO within reserved area, high byte (0) + uint8_t bkboot_l; // sector pointer to backup boot sector within reserved area, low byte (6) + uint8_t bkboot_h; // sector pointer to backup boot sector within reserved area, high byte (0) + uint8_t reserved[12]; // reserved, should be 0 + + uint8_t unit; // int 13h drive# + uint8_t head; // archaic, used by Windows NT-class OSes for flags + uint8_t signature; // 0x28 or 0x29 + uint8_t serial_0; // serial# + uint8_t serial_1; // serial# + uint8_t serial_2; // serial# + uint8_t serial_3; // serial# + uint8_t label[11]; // volume label + uint8_t system[8]; // filesystem ID +} EBPB32, *PEBPB32; + +/* + Logical Boot Record structure (volume boot sector) +*/ +typedef struct _tagLBR { + uint8_t jump[3]; // JMP instruction + uint8_t oemid[8]; // OEM ID, space-padded + BPB bpb; // BIOS Parameter Block + union { + EBPB ebpb; // FAT12/16 Extended BIOS Parameter Block + EBPB32 ebpb32; // FAT32 Extended BIOS Parameter Block + } ebpb; + uint8_t code[420]; // boot sector code + uint8_t sig_55; // 0x55 signature byte + uint8_t sig_aa; // 0xaa signature byte +} LBR, *PLBR; + +/* + Volume information structure (Internal to DOSFS) +*/ +typedef struct _tagVOLINFO { + uint8_t unit; // unit on which this volume resides + uint8_t filesystem; // formatted filesystem + +// These two fields aren't very useful, so support for them has been commented out to +// save memory. (Note that the "system" tag is not actually used by DOS to determine +// filesystem type - that decision is made entirely on the basis of how many clusters +// the drive contains. DOSFS works the same way). +// See tag: OEMID in dosfs.c +// uint8_t oemid[9]; // OEM ID ASCIIZ +// uint8_t system[9]; // system ID ASCIIZ + uint8_t label[12]; // volume label ASCIIZ + uint32_t startsector; // starting sector of filesystem + uint8_t secperclus; // sectors per cluster + uint16_t reservedsecs; // reserved sectors + uint32_t numsecs; // number of sectors in volume + uint32_t secperfat; // sectors per FAT + uint16_t rootentries; // number of root dir entries + + uint32_t numclusters; // number of clusters on drive + + // The fields below are PHYSICAL SECTOR NUMBERS. + uint32_t fat1; // starting sector# of FAT copy 1 + uint32_t rootdir; // starting sector# of root directory (FAT12/FAT16) or cluster (FAT32) + uint32_t dataarea; // starting sector# of data area (cluster #2) +} VOLINFO, *PVOLINFO; + +/* + Flags in DIRINFO.flags +*/ +#define DFS_DI_BLANKENT 0x01 // Searching for blank entry + +/* + Directory search structure (Internal to DOSFS) +*/ +typedef struct _tagDIRINFO { + uint32_t currentcluster; // current cluster in dir + uint8_t currentsector; // current sector in cluster + uint8_t currententry; // current dir entry in sector + uint8_t *scratch; // ptr to user-supplied scratch buffer (one sector) + uint8_t flags; // internal DOSFS flags +} DIRINFO, *PDIRINFO; + +/* + File handle structure (Internal to DOSFS) +*/ +typedef struct _tagFILEINFO { + PVOLINFO volinfo; // VOLINFO used to open this file + uint32_t dirsector; // physical sector containing dir entry of this file + uint8_t diroffset; // # of this entry within the dir sector + uint8_t mode; // mode in which this file was opened + uint32_t firstcluster; // first cluster of file + uint32_t filelen; // byte length of file + + uint32_t cluster; // current cluster + uint32_t pointer; // current (BYTE) pointer +} FILEINFO, *PFILEINFO; + +/* + Get starting sector# of specified partition on drive #unit + NOTE: This code ASSUMES an MBR on the disk. + scratchsector should point to a SECTOR_SIZE scratch area + Returns 0xffffffff for any error. + If pactive is non-NULL, this function also returns the partition active flag. + If pptype is non-NULL, this function also returns the partition type. + If psize is non-NULL, this function also returns the partition size. +*/ +uint32_t DFS_GetPtnStart(uint8_t unit, uint8_t *scratchsector, uint8_t pnum, uint8_t *pactive, uint8_t *pptype, uint32_t *psize); + +/* + Retrieve volume info from BPB and store it in a VOLINFO structure + You must provide the unit and starting sector of the filesystem, and + a pointer to a sector buffer for scratch + Attempts to read BPB and glean information about the FS from that. + Returns 0 OK, nonzero for any error. +*/ +uint32_t DFS_GetVolInfo(uint8_t unit, uint8_t *scratchsector, uint32_t startsector, PVOLINFO volinfo); + +/* + Open a directory for enumeration by DFS_GetNextDirEnt + You must supply a populated VOLINFO (see DFS_GetVolInfo) + The empty string or a string containing only the directory separator are + considered to be the root directory. + Returns 0 OK, nonzero for any error. +*/ +uint32_t DFS_OpenDir(PVOLINFO volinfo, uint8_t *dirname, PDIRINFO dirinfo); + +/* + Get next entry in opened directory structure. Copies fields into the dirent + structure, updates dirinfo. Note that it is the _caller's_ responsibility to + handle the '.' and '..' entries. + A deleted file will be returned as a NULL entry (first char of filename=0) + by this code. Filenames beginning with 0x05 will be translated to 0xE5 + automatically. Long file name entries will be returned as NULL. + returns DFS_EOF if there are no more entries, DFS_OK if this entry is valid, + or DFS_ERRMISC for a media error +*/ +uint32_t DFS_GetNext(PVOLINFO volinfo, PDIRINFO dirinfo, PDIRENT dirent); + +/* + Open a file for reading or writing. You supply populated VOLINFO, a path to the file, + mode (DFS_READ or DFS_WRITE) and an empty fileinfo structure. You also need to + provide a pointer to a sector-sized scratch buffer. + Returns various DFS_* error states. If the result is DFS_OK, fileinfo can be used + to access the file from this point on. +*/ +uint32_t DFS_OpenFile(PVOLINFO volinfo, uint8_t *path, uint8_t mode, uint8_t *scratch, PFILEINFO fileinfo); + +/* + Read an open file + You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a + pointer to a SECTOR_SIZE scratch buffer. + Note that returning DFS_EOF is not an error condition. This function updates the + successcount field with the number of bytes actually read. +*/ +uint32_t DFS_ReadFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len); + +/* + Write an open file + You must supply a prepopulated FILEINFO as provided by DFS_OpenFile, and a + pointer to a SECTOR_SIZE scratch buffer. + This function updates the successcount field with the number of bytes actually written. +*/ +uint32_t DFS_WriteFile(PFILEINFO fileinfo, uint8_t *scratch, uint8_t *buffer, uint32_t *successcount, uint32_t len); + +/* + Seek file pointer to a given position + This function does not return status - refer to the fileinfo->pointer value + to see where the pointer wound up. + Requires a SECTOR_SIZE scratch buffer +*/ +void DFS_Seek(PFILEINFO fileinfo, uint32_t offset, uint8_t *scratch); + +/* + Delete a file + scratch must point to a sector-sized buffer +*/ +uint32_t DFS_UnlinkFile(PVOLINFO volinfo, uint8_t *path, uint8_t *scratch); + +// If we are building a host-emulation version, include host support +#ifdef HOSTVER +#include "hostemu.h" +#endif + +#endif // _DOSFS_H diff --git a/dosfs/readme.txt b/dosfs/readme.txt new file mode 100755 index 0000000..94aa81e --- /dev/null +++ b/dosfs/readme.txt @@ -0,0 +1,366 @@ +README.TXT (C) Copyright 2006 +DOSFS Level 1 Version 1.02 Lewin A.R.W. Edwards (sysadm@zws.com) +===================================================================== + +Abstract +======== +DOSFS is a FAT-compatible filesystem intended for fairly low-end +embedded applications. It is not the leanest possible implementation +(the leanest FAT implementations operate in << 512 bytes of RAM, with +heavy restrictions). This code strikes a good balance between size +and functionality, with an emphasis on RAM footprint. + +Intended target systems would be in the ballpark of 1K RAM, 4K ROM +or more. + +Features: +* Supports FAT12, FAT16 and FAT32 volumes +* Supports storage devices up to 2048Gbytes in size (LBA32) +* Supports devices with or without MBRs (hard disks vs. floppy disks + or ZIP drives formatted as "big floppies") +* Supports multiple partitions on disks with MBRs +* Supports subdirectories +* Can be operated with a single global 512-byte sector buffer +* Fully reentrant code (assuming the underlying physical device driver + is reentrant and global sector buffers are not used). There are no + global variables in the filesystem +* Does not perform any memory allocation +* Partial support for random-access files + +Applications: +* Firmware upgrades +* Failsafe IPL +* Media playback +* Data logging +* Configuration storage + +There is no technical support for this free product; however, if you +have questions or suggestions, you are encouraged to email Lewin +Edwards at sysadm@zws.com. If you need custom additions to the code, +or if you have other projects for which you need engineering +assistance, please feel free to email or call (646) 549-3715. + +License +======= +The license for DOSFS is very simple but verbose to state. + +1. DOSFS is (C) Copyright 2006 by Lewin A.R.W. Edwards ("Author"). + All rights not explicitly granted herein are reserved. The DOSFS + code is the permanent property of the Author and no transfer of + ownership is implied by this license. + +2. DOSFS is an educational project, provided as-is. No guarantee of + performance or suitability for any application is stated or + implied. You use this product entirely at your own risk. Use of + this product in any manner automatically waives any right to seek + compensation or damages of any sort from the Author. Since the + products you might make are entirely out of the Author's control, + use of this product also constitutes an agreement by you to take + full responsibility for and indemnify the Author against any + action for any loss or damage (including economic loss of any + type, and specifically including patent litigation) that arises + from a product made by you that incorporates any portion of + the DOSFS code. + +3. If you live under the jurisdiction of any legislation that would + prohibit or limit any condition in this license, you cannot be + licensed to use this product. + +4. If you do not fall into the excluded category in point 3, you are + hereby licensed to use the DOSFS code in any application that you + see fit. You are not required to pay any fee or notify the Author + that you are using DOSFS. Any modifications made by you to the + DOSFS code are your property and you may distribute the modified + version in any manner that you wish. You are not required to + disclose sourcecode to such modifications, either to the Author or + to any third party. Any such disclosure made to the Author will + irrevocably become the property of the Author in the absence of a + formal agreement to the contrary, established prior to such + disclosure being made. + +To summarize the intent of the above: DOSFS is free. You can do what +you want with it. Anything that happens as a result is entirely your +responsibility. You can't take ownership of my code and stop me from +doing whatever I want with it. If you do something nifty with DOSFS +and send me the sourcecode, I may include your changes in the next +distribution and it will be released to the world as free software. +If someone sues you because your DOSFS-containing product causes +any sort of legal, financial or other problem, it's your lawsuit, +not mine, and you'll exclude me from the proceedings. + +User-Supplied Functions +======================= +You must provide functions to read sectors into memory and write +them back to the target media. The demo suite includes an emulation +module that reads/writes a disk image file (#define HOSTVER pulls +in hostemu.h which wraps the prototypes for these functions). +There are various tools for UNIX, DOS, Windows et al, to create +images from storage media; my preferred utility is dd. + +The functions you must supply in your embedded app are: + +DFS_ReadSector(unit,buffer,sector,count) +DFS_WriteSector(unit,buffer,sector,count) + +These two functions read and write, respectively, "count" sectors of +size SECTOR_SIZE (512 bytes; see below) from/to physical sector +#"sector" of device "unit", to/from the scratch buffer "buffer". They +should return 0 for success or nonzero for failure. In the current +implementation of DOSFS, count will always be 1. + +The "unit" argument is designed to permit implementation of multiple +storage devices, for example multiple media slots on a single device, +or to differentiate between master and slave devices on an ATAPI bus. + +This code is designed for 512-byte sectors. Although the sector size +is a #define, you should not tinker with it because the vast majority +of FAT filesystems use 512-byte sectors, and the DOSFS code doesn't +support runtime determination of sector size. This will not affect the +vast majority of users. + +Example Code +============ +Refer to the tests in main.c to see how to call DOSFS functions. +(These tests are all commented out). Note that the only two files +you need to add to your project are dosfs.c and dosfs.h. + + +Mounting Volumes +================ +--If the device has a partition table (practically all removable flash + media are formatted this way), call DFS_GetPtnStart to get the + starting sector# of the desired partition. You can optionally also + retrieve the active state, partition type byte and partition size + in this step. The reason this step is broken out separately is so + you can support devices that are formatted like a floppy disk, i.e. + the volume starts directly at physical sector 0 of the media. + +--Call DFS_GetVolInfo to read filesystem info into a VOLINFO structure. + DFS_GetVolInfo needs to know the unit number and partition starting + sector (as returned by DFS_GetPtnStart, or 0 if this is a "floppy- + format" volume without an MBR). + +From this point on, the VOLINFO structure is all you'll need - you can +forget the unit and partition start sector numbers. + +Enumerating Directory Contents +============================== +--Call DFS_Opendir and supply a path, populated VOLINFO and a + DIRINFO structure to receive the results. Note - you must PREPOPULATE + the DIRINFO.scratch field with a pointer to a sector scratch buffer. + This buffer must remain unmolested while you have the directory open + for searching. +--Call DFS_GetNext to receive the DIRENT contents for the next directory + item. This function returns DFS_OK for no error, and DFS_EOF if there + are no more entries in the directory being searched. + Before using the DIRENT, check the first character of the name. If it + is NULL, then this is an unusable entry - call DFS_GetNext again to + keep searching. LFN directory entries are automatically tagged this way + so your application will not be pestered by them. + + Note: A designed side-effect of this code is that when you locate the + file of interest, the DIRINFO.currentcluster, DIRINFO.currentsector + and DIRINFO.currententry-1 fields will identify the directory entry of + interest. + +Reading a File +============== +--Call DFS_OpenFile with mode = DFS_READ and supply a path and the relevant + VOLINFO structure. DFS_OpenFile will populate a FILEINFO that can be used + to refer to the file. +--Optionally call DFS_Seek to set the file pointer. If you attempt to set + the file pointer past the end of file, the file will NOT be extended. Check + the FILEINFO.pointer value after DFS_Seek to verify that the pointer is + where you expect it to be. +--Observe that functionality similar to the "whence" parameter of fseek() can + be obtained by using simple arithmetic on the FILEINFO.pointer and + FILEINFO.filelen members. +--Call DFS_ReadFile with the FILEINFO you obtained from OpenFile, and a + pointer to a buffer plus the desired number of bytes to read, and a + pointer to a sector-sized scratch buffer. The reason a scratch sector is + required is because the underlying sector read function doesn't know + about partial reads. +--Note that a file opened for reading cannot be written. If you need r/w + access, open with mode = DFS_WRITE (see below). + +Writing a file +============== +--Call DFS_OpenFile with mode = DFS_WRITE and supply a path and the relevant + VOLINFO structure. DFS_OpenFile will populate a FILEINFO that can be used to + refer to the file. +--Optionally call DFS_Seek to set the file pointer. Refer to the notes on + this topic in the section on reading files, above. +--Call DFS_WriteFile with the FILEINFO you obtained from OpenFile, and a + pointer to the source buffer, and a pointer to a sector-sized scratch + buffer. +--Note that a file open for writing can also be read. +--Files are created automatically if they do not exist. Subdirectories are + NOT automatically created. +--If you open an existing file for writing, the file pointer will start at + the beginning of the data; if you want to append, seek to the end before + writing new data. +--If you perform random-access writes to a file, the length will NOT change + unless you exceed the file's original length. There is currently no + function to truncate a file at the current pointer position. +--On-disk consistency is guaranteed when DFS_WriteFile exits, unless your + physical layer has a writeback cache in it. + +Deleting a file +=============== +--Call DFS_UnlinkFile +--WARNING: This call will delete a subdirectory (correctly) but will NOT + first recurse the directory to delete the contents - so you will end up + with lost clusters. + +Notes +===== +Some platforms may require explicit pragmas or attributes to the structures +and unions. For example, arm-gcc will require __attribute__ ((__packed__)) +otherwise it will try to be "smart" and place the uint8_t members on 4-byte +boundaries. There is no truly elegant compiler-independent method to get +around this sort of problem. + +The code assumes either a von Neumann architecture, or a compiler that +is smart enough to understand where your pointers are aimed and emit +the right kind of memory read and write instructions. The implications +of this statement depend on your target processor and the compiler you +are using. Be very careful not to straddle bank boundaries on bank- +switched memory systems. + +Physical 32-bit sector numbers are used throughout. Therefore, the +CHS geometry (if any) of the storage media is not known to DOSFS. Your +sector r/w functions may need to query the CHS geometry and perform +mapping. + +File timestamps set by DOSFS are always 1:01:00am on Jan 1, 2006. If +your system has a concept of real time, you can enhance this. + +FILEINFO structures contain a pointer to the corresponding VOLINFO +used to open the file, mainly in order to avoid mixups but also to +obviate the need for an extra parameter to every file read/write. DOSFS +assumes that the VOLINFO won't move around. If you need to move or +destroy VOLINFOs pertaining to open files, you'll have to fix up the +pointer in the FILEINFO structure yourself. + +The subdirectory delimiter is a forward slash ( '/' ) by default. The +reason for this is to avoid the common programming error of forgetting +that backslash is an escape character in C strings; i.e. "\MYDIR\FILE" +is NOT what you want; "\\MYDIR\\FILE" is what you wanted to type. If you +are porting DOS code into an embedded environment, feel free to change +this #define. + +DOSFS does not have a concept of "current directory". A current directory +is owned by a process, and a process is an operating system concept. +DOSFS is a filesystem library, not an operating system. Therefore, any +path you provide to a DOSFS call is assumed to be relative to the root of +the volume. + +There is no call to close a file or directory that is open for reading or +writing. You can simply destroy or reuse the data structures allocated for +that operation; there is no internal state in DOSFS so no cleanup is +necessary. Similarly, there is no call to close a file that is open for +writing. (Observe that dosfs.c has no global variables. All state information +is stored in data structures provided by the caller). + +MAX_PATH is defined as 64. MS-type DOS filesystems support 128 characters +or more in paths. You can increase this define, but it may GREATLY +increase memory requirements. + +VFAT long filenames are not supported. There is a certain amount of +patent controversy about them, but more importantly they don't really +belong in the scope of a "minimalist embedded filesystem". + +Improving Performance +===================== +Read performance is fairly good, but can be improved by implementing read +caching on the FAT (see below) and, depending on your hardware platform, +possibly by implementing multi-sector reads. + +Write performance may benefit ENORMOUSLY from platform-specific +optimization, especially if you are working with a flash media type that +has a large erase block size. While it is not possible to offer detailed +platform-independent advice, my general advice is to implement writeback +caching on the FAT area. One method for doing this would be to have a +cache system that lives in the DFS_ReadSector/WriteSector functions (on +top of the physical sector r/w functions) and is initially switched off. +Once you have called DFS_GetVolInfo, you then extract the VOLINFO.fat1 +and VOLINFO.rootdir parameters and pass them to your caching layer. +Sectors >= fat1 and < rootdir should be cached. The cache strategy is +determined by the physical storage medium underlying the filesystem. + +CACHING HINT: +Observe that there will be numerous read-modify-write operations in the +region from VOLINFO.fat1 through VOLINFO.fat1+VOLINFO.secperfat-1, but +in the region from VOLINFO.fat1+VOLINFO.secperfat through VOLINFO.rootdir +there will ONLY be write operations. + +Platform Compatibility +====================== +DOSFS was derived from code originally written for ARM7TDMI but +designed to be portable. It has been tested on AVR (using avrgcc), +MSP430 (using Rowley's CrossWorks) and PPC603e (using gcc); the host +test suite has also been validated on x86 using gcc under both Cygwin +and 32-bit Fedora Core 4 Linux. + +TODO list +========= +* Add function to create subdirectory +* Make DFS_UnlinkFile recognize non-empty subdirectories +* Support "fast write" files where the FAT is not updated, for + logging applications where latency is important. + +Test cases for V1.02 +==================== +Version 1.02 has NOT been through full regression testing. However the +bugs fixed in this version are important, and people have been asking +about them. + +Test cases for V1.01 +==================== +See below. + +Test cases for V1.00 +==================== +These are the test cases that were used to validate the correct +functionality of the DOSFS suite. Each test was performed on FAT12, +FAT16 and FAT32 volumes. P=Pass, F=Fail. + +Case F12 F16 F32 +--------------------------------------------------------------------- +Get volume information P P P +Open root directory P P P +List contents of root directory (fully populated) P P P +Open subdirectory P P P +List contents of subdirectory (<= 1 cluster) P P P +List contents of large subdirectory (> 1 cluster) P P P +Open 5-level nested subdirectory P P P +Open existing file for reading P P P +Open nonexistent file for reading P P P +Seek past EOF, file open for reading P P P +Seek to cluster boundary P P P +Seek past cluster boundary P P P +Seek backwards to nonzero offset, pointer > cluster size P P P +Block-read entire file >1 cluster in size, odd size P P P +Seek to odd location in file P P P +Perform <1 sector reads from random file locations P P P +Open nonexistent file for writing in root dir P P P +Open nonexistent file for writing in subdir P P P +Repeat prev. 2 tests on volume with 0 free clusters P P P +Seek past EOF, file open for writing P P P +Open existing file for writing in root dir P P P +Write random-length records to file, 20 clusters total P P P +MS-DOS 6.0 SCANDISK cross-check P P P + +Revision History +================ +Jan-06-2005 larwe Initial release (1.0) +Jan-29-2006 larwe Bugfix release (1.01) + - Fixed error in FAT12 FAT read on boundary of sector + - Improved compilability under avrgcc +Sep-16-2006 larwe Bugfix release (1.02) + - DFS_Seek would not correctly rewind to start of file + - DFS_Seek would not correctly seek to a position not on a cluster + boundary + - DFS_OpenFile fencepost error caused memory access at [start of + string-1] with a local variable + - DFS_OpenFile could not open a file in the root directory diff --git a/dosfs/tmpstring.c b/dosfs/tmpstring.c new file mode 100644 index 0000000..7fe3b94 --- /dev/null +++ b/dosfs/tmpstring.c @@ -0,0 +1,99 @@ +#pragma once +#include +#include + +void *memcpy(void *restrict dest, const void *restrict src, size_t n) +{ + unsigned char *d = dest; + const unsigned char *s = src; + for (; n; n--) *d++ = *s++; + return dest; +} + +void *memset(void *dest, int c, size_t n) +{ + unsigned char *s = dest; + size_t k; + + /* Fill head and tail with minimal branching. Each + * conditional ensures that all the subsequently used + * offsets are well-defined and in the dest region. */ + + if (!n) return dest; + s[0] = c; + s[n-1] = c; + if (n <= 2) return dest; + s[1] = c; + s[2] = c; + s[n-2] = c; + s[n-3] = c; + if (n <= 6) return dest; + s[3] = c; + s[n-4] = c; + if (n <= 8) return dest; + + /* Advance pointer to align it at a 4-byte boundary, + * and truncate n to a multiple of 4. The previous code + * already took care of any head/tail that get cut off + * by the alignment. */ + + k = -(uintptr_t)s & 3; + s += k; + n -= k; + n &= -4; + + /* Pure C fallback with no aliasing violations. */ + for (; n; n--, s++) *s = c; + + return dest; +} + +size_t strlen(const char *s) +{ + const char *a = s; + for (; *s; s++); + return s-a; +} + +int memcmp(const void *vl, const void *vr, size_t n) +{ + const unsigned char *l=vl, *r=vr; + for (; n && *l == *r; n--, l++, r++); + return n ? *l-*r : 0; +} + +char *strncpy(char *restrict d, const char *restrict s, size_t n) +{ + for (; n && (*d=*s); n--, s++, d++); + memset(d, 0, n); + return d; +} + +char *strcpy(char *restrict dest, const char *restrict src) +{ + char *restrict d = dest; + const char *restrict s = src; + for (; (*d=*s); s++, d++); + + return d; +} + +int strcmp(const char *l, const char *r) +{ + for (; *l==*r && *l; l++, r++); + return *(unsigned char *)l - *(unsigned char *)r; +} + + +/* STDLIB DIV FUNCTIONS */ +typedef struct { int quot, rem; } div_t; +typedef struct { long quot, rem; } ldiv_t; +div_t div(int num, int den) +{ + return (div_t){ num/den, num%den }; +} +ldiv_t ldiv(long num, long den) +{ + return (ldiv_t){ num/den, num%den }; +} + diff --git a/entry.nasm b/entry.nasm index 15acb90..ff67c47 100644 --- a/entry.nasm +++ b/entry.nasm @@ -77,7 +77,7 @@ div bl ; Unhandled DIV0 exception global jmp_usermode_test jmp_usermode_test: pop eax ; return address -mov ebp, esp ; return stack +mov ecx, esp ; return stack call save_current_task mov esp, 0x500000 ; usermode stack mov eax, 0x20 | 3 diff --git a/interrupt.c b/interrupt.c index 0d9555f..8fbfbac 100644 --- a/interrupt.c +++ b/interrupt.c @@ -175,7 +175,7 @@ void gpf_handler_v86(struct interrupt_frame *frame, unsigned long error_code) { frame->eip = (uint16_t)(frame->eip + 1); goto done; case 0xCD: // INT n - vga[0] = 'I'; vga[2]++; if (vga[2] < '0') vga[2] = '0'; + //vga[0] = 'I'; vga[2]++; if (vga[2] < '0') vga[2] = '0'; switch (ip[1]) { case 0x30: return_prev_task(); diff --git a/interrupt.h b/interrupt.h index 55f31fc..69343a2 100644 --- a/interrupt.h +++ b/interrupt.h @@ -27,6 +27,7 @@ typedef uint32_t FARPTR; #define EFLAG_IF ((uint32_t)1 << 9) #define EFLAG_VM ((uint32_t)1 << 17) +FARPTR i386LinearToFp(void *ptr); __attribute__ ((interrupt)) diff --git a/kernel.c b/kernel.c index c511c72..9c42767 100644 --- a/kernel.c +++ b/kernel.c @@ -1,5 +1,6 @@ #include +#include "dosfs/dosfs.h" #include "print.h" #include "interrupt.h" @@ -52,13 +53,21 @@ void print_cr4() { printDword(reg, 0xB8000 + (160*5) + 50 + 8*4 + 4); } -__attribute((__no_caller_saved_registers__)) +struct __attribute((__packed__)) Int13DiskPacket_t { + uint8_t size; // 0x10 + uint8_t reserved; // 0x00 + uint16_t blocks; + uint32_t transfer_buffer; // 0x2300:0000 + uint64_t start_block; +}; + +extern struct Int13DiskPacket_t v86disk_addr_packet; + extern void enter_v86(uint32_t ss, uint32_t esp, uint32_t cs, uint32_t eip); extern void v86Test(); extern void v86GfxMode(); extern void v86TextMode(); extern void v86DiskRead(); -__attribute((__no_caller_saved_registers__)) extern char *jmp_usermode_test(); __attribute((__no_caller_saved_registers__)) extern void kbd_wait(); @@ -88,6 +97,92 @@ Protected Only (1MB+) 400000 - 500000 Usermode Stack (1mB) */ +void TestV86() { + FARPTR v86_entry = i386LinearToFp(v86Test); + enter_v86(0x8000, 0xFF00, FP_SEG(v86_entry), FP_OFF(v86_entry)); +} +void TestGfx() { + FARPTR v86_entry = i386LinearToFp(v86GfxMode); + enter_v86(0x8000, 0xFF00, FP_SEG(v86_entry), FP_OFF(v86_entry)); + char *vga = jmp_usermode_test(); + for (int i = 0; i < 320; i++) { + vga[i] = i; + } +} +void TestDiskRead() { + FARPTR v86_entry = i386LinearToFp(v86TextMode); + enter_v86(0x8000, 0xFF00, FP_SEG(v86_entry), FP_OFF(v86_entry)); + v86_entry = i386LinearToFp(v86DiskRead); + enter_v86(0x8000, 0xFF00, FP_SEG(v86_entry), FP_OFF(v86_entry)); + word *vga_text = (word *)0xb8000; + char *diskReadBuf = (char *)0x23000; + for (int i = 0; i < (80*25)/2; i++) { + printByte(diskReadBuf[i], &vga_text[i*2]); + } +} +void TestFAT() { + word *vga_text = (word *)0xb8000; + uint8_t *diskReadBuf = (uint8_t *)0x23000; + for (int i = 0; i < 80*25; i++) + vga_text[i] = 0x0f00; + VOLINFO vi; + + uint8_t pactive, ptype; + uint32_t pstart, psize; + pstart = DFS_GetPtnStart(0, diskReadBuf, 0, &pactive, &ptype, &psize); + vga_text = (word *)0xb8000; + vga_text += printStr("PartStart: ", vga_text); + vga_text += printDword(pstart, vga_text); + vga_text += 2; + vga_text += printStr("PartSize: ", vga_text); + vga_text += printDword(psize, vga_text); + vga_text += 2; + vga_text += printStr("PartActive: ", vga_text); + vga_text += printByte(pactive, vga_text); + vga_text += 2; + vga_text += printStr("PartType: ", vga_text); + vga_text += printByte(ptype, vga_text); + vga_text = (word *)((((((uintptr_t)vga_text)-0xb8000) - ((((uintptr_t)vga_text)-0xb8000) % 160)) + 160)+0xb8000); + + DFS_GetVolInfo(0, diskReadBuf, pstart, &vi); + vga_text += printStr("Label: ", vga_text); + vga_text += printStr((char*)vi.label, vga_text); + vga_text += 2; + vga_text += printStr("Sec/Clus: ", vga_text); + vga_text += printByte(vi.secperclus, vga_text); + vga_text += 2; + vga_text += printStr("ResrvSec: ", vga_text); + vga_text += printWord(vi.reservedsecs, vga_text); + vga_text += 2; + vga_text += printStr("NumSec: ", vga_text); + vga_text += printDword(vi.numsecs, vga_text); + vga_text += 2; + vga_text += printStr("Sec/FAT: ", vga_text); + vga_text += printDword(vi.secperfat, vga_text); + vga_text += 2; + vga_text += printStr("FAT1@: ", vga_text); + vga_text += printDword(vi.fat1, vga_text); + vga_text += 2; + vga_text += printStr("ROOT@: ", vga_text); + vga_text += printDword(vi.rootdir, vga_text); + vga_text = (word *)((((((uintptr_t)vga_text)-0xb8000) - ((((uintptr_t)vga_text)-0xb8000) % 160)) + 160)+0xb8000); + + vga_text += printStr("Files in root:", vga_text); + vga_text = (word *)((((((uintptr_t)vga_text)-0xb8000) - ((((uintptr_t)vga_text)-0xb8000) % 160)) + 160)+0xb8000); + DIRINFO di; + DIRENT de; + di.scratch = 0x23000; + while (!DFS_GetNext(&vi, &di, &de)) { + if (de.name[0]) { + for (int i = 0; i < 11 && de.name[i]; i++) { + *(uint8_t *)vga_text = de.name[i]; + vga_text++; + } + vga_text = (word *)((((((uintptr_t)vga_text)-0xb8000) - ((((uintptr_t)vga_text)-0xb8000) % 160)) + 160)+0xb8000); + } + } +} + void start() { word *vga_text = (word *)0xb8000; char h[] = "LuciaOS"; @@ -122,25 +217,14 @@ void start() { print_cr0(); print_cr3(); print_cr4(); - FARPTR v86_entry = i386LinearToFp(v86Test); - enter_v86(0x8000, 0xFF00, FP_SEG(v86_entry), FP_OFF(v86_entry)); - v86_entry = i386LinearToFp(v86GfxMode); - enter_v86(0x8000, 0xFF00, FP_SEG(v86_entry), FP_OFF(v86_entry)); - char *vga = jmp_usermode_test(); //asm ("xchgw %bx, %bx"); - for (int i = 0; i < 320; i++) { - vga[i] = i; - } + TestV86(); // has int 3 wait in v86 + TestGfx(); + kbd_wait(); + TestDiskRead(); + kbd_wait(); + TestFAT(); kbd_wait(); - v86_entry = i386LinearToFp(v86TextMode); - enter_v86(0x8000, 0xFF00, FP_SEG(v86_entry), FP_OFF(v86_entry)); - v86_entry = i386LinearToFp(v86DiskRead); - enter_v86(0x8000, 0xFF00, FP_SEG(v86_entry), FP_OFF(v86_entry)); - vga_text = (word *)0xb8000; - char *bootloader = (char *)0x23000; - for (int i = 0; i < (80*25)/2; i++) { - printByte(bootloader[i], &vga_text[i*2]); - } } diff --git a/print.c b/print.c index 080c77a..d57a5a3 100644 --- a/print.c +++ b/print.c @@ -3,15 +3,25 @@ char nibbleToHex(uint8_t n) { return n > 9 ? (n - 10) + 'A' : n + '0'; } -void printByte(uint8_t v, uint16_t *buff) { +uintptr_t printByte(uint8_t v, uint16_t *buff) { *(char *)&buff[0] = nibbleToHex((v >> 4) & 0xF); *(char *)&buff[1] = nibbleToHex(v & 0xF); + return 2; } -void printWord(uint16_t v, uint16_t *buff) { +uintptr_t printWord(uint16_t v, uint16_t *buff) { printByte(v >> 8, buff); printByte(v, &buff[2]); + return 4; } -void printDword(uint32_t v, uint16_t *buff) { +uintptr_t printDword(uint32_t v, uint16_t *buff) { printWord(v >> 16, buff); printWord(v, &buff[4]); + return 8; +} + +uintptr_t printStr(char *v, uint16_t *buff) { + char *s; + for (s = v;*s;s++,buff++) + *(char*)buff = *s; + return s - v; } diff --git a/print.h b/print.h index ade22fe..5aaab79 100644 --- a/print.h +++ b/print.h @@ -1,6 +1,7 @@ #pragma once #include -void printByte(uint8_t v, uint16_t *buff); -void printWord(uint16_t v, uint16_t *buff); -void printDword(uint32_t v, uint16_t *buff); +uintptr_t printByte(uint8_t v, uint16_t *buff); +uintptr_t printWord(uint16_t v, uint16_t *buff); +uintptr_t printDword(uint32_t v, uint16_t *buff); +uintptr_t printStr(char *v, uint16_t *buff); diff --git a/task.nasm b/task.nasm index c575eee..384869e 100644 --- a/task.nasm +++ b/task.nasm @@ -1,12 +1,16 @@ task_ptr: equ (0x310000-4) +; return address in EAX +; return stack in ECX +; we can modify EAX, ECX, EDX +; i.e. save all others in task global save_current_task save_current_task: -push ebx -mov ebx, esp +push edx +mov edx, esp ; EDX holds our tmp stack, unsaved mov esp, dword [task_ptr] ; load current task pointer push ss -push ebp ; return stack +push ecx ; return stack pushfd push cs push eax ; return address @@ -14,33 +18,41 @@ push ds ; other segs, pop push es ; before iret push fs ; in exit handler push gs +push ebp ; saved +push ebx ; saved +push esi ; saved +push edi ; saved mov dword [task_ptr], esp ; save new task pointer -mov esp, ebx -pop ebx +mov esp, edx +pop edx ret global return_prev_task return_prev_task: -mov edi, eax ; save for later -mov esi, dword [task_ptr] ; load current task pointer -add dword [task_ptr], 36 ; adjust to last task pointer -mov eax, [esi+0] ; gs +mov ecx, eax ; save return value for later +mov edx, dword [task_ptr] ; load current task pointer +add dword [task_ptr], 52 ; adjust to last task pointer +mov edi, [edx+0] +mov esi, [edx+4] +mov ebx, [edx+8] +mov ebp, [edx+12] +mov eax, [edx+0+16] ; gs mov gs, ax -mov eax, [esi+4] ; fs +mov eax, [edx+4+16] ; fs mov fs, ax -mov eax, [esi+8] ; es +mov eax, [edx+8+16] ; es mov es, ax -mov ebx, [esi+16] ; eip -mov ecx, [esi+20] ; cs -mov edx, [esi+24] ; eflags ; SS:ESP <- return stack -mov esp, [esi+28] ; esp -mov eax, [esi+32] ; ss +mov esp, [edx+28+16] ; esp +mov eax, [edx+32+16] ; ss mov ss, ax -mov eax, [esi+12] ; ds +mov eax, [edx+24+16] ; eflags +push eax +mov eax, [edx+20+16] ; cs +push eax +mov eax, [edx+16+16] ; eip +push eax +mov eax, [edx+12+16] ; ds mov ds, ax -push edx ; eflags -push ecx ; cs -push ebx ; eip -mov eax, edi ; restore return value +mov eax, ecx ; restore return value iret diff --git a/tss.c b/tss.c index 3b2e5c5..770d1eb 100644 --- a/tss.c +++ b/tss.c @@ -42,7 +42,7 @@ void write_tss() { // not technically TSS but set up task pointer uint32_t *current_task_ptr = (uint32_t*)(0x310000-4); - *current_task_ptr = 0x310000-40; // each task is 9 dwords, plus 1 for pointer + *current_task_ptr = 0x310000-(20*4); // each task is 12 dwords, plus 1 for pointer /* TODO setup null recovery task at start */ } extern void flushTSS(); diff --git a/v86.nasm b/v86.nasm index 0935cbe..c005aaf 100644 --- a/v86.nasm +++ b/v86.nasm @@ -67,18 +67,20 @@ mov si, v86disk_addr_packet ; ds:si int 0x13 int 0x30 jmp $ +global v86disk_addr_packet v86disk_addr_packet: db 0x10, 0x00 ; size, reserved -dw 0x20 ; blocks +dw 0x1 ; blocks dd 0x23000000 ; transfer buffer 0x23000 -dq 0 ; start block +dq 0x1 ; start block [BITS 32] ; extern void enter_v86(uint32_t ss, uint32_t esp, uint32_t cs, uint32_t eip); global enter_v86 enter_v86: -pop eax -mov ebp, esp ; save stack pointer +pop eax ; return address +mov ecx, esp ; return stack call save_current_task +mov ebp, esp ; save stack pointer push dword [ebp+0] ; ss push dword [ebp+4] ; esp pushfd ; eflags