{{ ' fsrw.spin 1.6 Copyright 2008 Radical Eye Software ' ' See end of file for terms of use. ' ' This object provides FAT16 file read/write access on a block device. ' Only one file open at a time. Open modes are 'r' (read), 'a' (append), ' 'w' (write), and 'd' (delete). Only the root directory is supported. ' No long filenames are supported. We also support traversing the ' root directory. ' ' In general, negative return values are errors; positive return ' values are success. Other than -1 on popen when the file does not ' exist, all negative return values will be "aborted" rather than ' returned. ' ' Changes: ' v1.1 28 December 2006 Fixed offset for ctime ' v1.2 29 December 2006 Made default block driver be fast one ' v1.3 6 January 2007 Added some docs, and a faster asm ' v1.4 4 February 2007 Rearranged vars to save memory; ' eliminated need for adjacent pins; ' reduced idle current consumption; added ' sample code with abort code data ' v1.5 7 April 2007 Fixed problem when directory is larger ' than a cluster. ' v1.6 23 September 2008 Fixed a bug found when mixing pputc ' with pwrite. Also made the assembly ' routines a bit more cautious. }} ' ' Constants describing FAT volumes. ' con SECTORSIZE = 512 SECTORSHIFT = 9 DIRSIZE = 32 DIRSHIFT = 5 ' ' The object that provides the block-level access. ' obj sdspi: "admflash-sdspiqasm" var ' ' ' Variables concerning the open file. ' long fclust ' the current cluster number long filesize ' the total current size of the file long floc ' the seek position of the file long frem ' how many bytes remain in this cluster from this file long bufat ' where in the buffer our current character is long bufend ' the last valid character (read) or free position (write) long direntry ' the byte address of the directory entry (if open for write) long writelink ' the byte offset of the disk location to store a new cluster long fatptr ' the byte address of the most recently written fat entry long fsize ' dateigröße long fattrib ' dateiattribute long ftime ' zeitstempel ' ' Variables used when mounting to describe the FAT layout of the card. ' long rootdir ' the byte address of the start of the root directory long rootdirend ' the byte immediately following the root directory. long dataregion ' the start of the data region, offset by two sectors long clustershift ' log base 2 of blocks per cluster long fat1 ' the block address of the fat1 space long totclusters ' how many clusters in the volume long sectorsperfat ' how many sectors per fat ' ' Variables controlling the caching. ' long lastread ' the block address of the buf2 contents long dirty ' nonzero if buf2 is dirty ' ' Buffering: two sector buffers. These two buffers must be longword ' aligned! To ensure this, make sure they are the first byte variables ' defined in this object. ' byte buf[SECTORSIZE] ' main data buffer byte buf2[SECTORSIZE] ' main metadata buffer byte padname[11] ' filename buffer CON { SEKTORINTERFACE das sektorinterface arbeitet mit zusammenhängenden containerdateien und ermöglicht einen wahlfreien zugriff auf die daten. die containerdatei werden mit dem entsprechenden tool auf einer leeren und frisch formatierten karte erzeugt. } PUB sec_start(strptr): s | err 'sec: startsektor einer containerdatei ermitteln {{ conainerdatei wird geöffnet und startsektor berechnet. die sektornummer wird zurückgeliefert; bei einem fehler der wert -1}} s~ err := popen(strptr,"r") 'containerdatei öffnen ifnot err 'kein fehler bei öffnen des containers s := (fclust << clustershift) + dataregion 'startsektor berechnen pclose 'containerdatei schließen return s else return -1 ' sequenz aus pfillbuf: ' sdspi.readblock(datablock, @buf) 'cluster einlesen ' sequenz aus datablock: ' return (fclust << clustershift) + dataregion + ((floc >> SECTORSHIFT) & ((1 << clustershift) - 1)) PUB sec_readblock(secnr, badr) sdspi.readblock(secnr, badr) PUB sec_writeblock(secnr, badr) sdspi.writeblock(secnr, badr) CON { FAT16-Code } pri writeblock2(n, b) ' ' On metadata writes, if we are updating the FAT region, also update ' the second FAT region. ' sdspi.writeblock(n, b) if (n => fat1 and n < fat1 + sectorsperfat) sdspi.writeblock(n+sectorsperfat, b) pri flushifdirty ' ' If the metadata block is dirty, write it out. ' if (dirty) writeblock2(lastread, @buf2) dirty := 0 pri readblockc(n) ' ' Read a block into the metadata buffer, if that block is not already ' there. ' if (n <> lastread) flushifdirty sdspi.readblock(n, @buf2) lastread := n pri brword(b) ' ' Read a byte-reversed word from a (possibly odd) address. ' return (byte[b]) + ((byte[b][1]) << 8) pri brlong(b) ' ' Read a byte-reversed long from a (possibly odd) address. ' return brword(b) + (brword(b+2) << 16) pri brwword(w, v) ' ' Write a byte-reversed word to a (possibly odd) address, and ' mark the metadata buffer as dirty. ' byte[w++] := v byte[w] := v >> 8 dirty := 1 pri brwlong(w, v) ' ' Write a byte-reversed long to a (possibly odd) address, and ' mark the metadata buffer as dirty. ' brwword(w, v) brwword(w+2, v >> 16) pub mount(basepin) | start, sectorspercluster, reserved, rootentries, sectors {{ ' Mount a volume. The address passed in is passed along to the block ' layer; see the currently used block layer for documentation. If the ' volume mounts, a 0 is returned, else abort is called. }} sdspi.start(basepin) lastread := -1 dirty := 0 sdspi.readblock(0, @buf) if (brlong(@buf+$36) == constant("F" + ("A" << 8) + ("T" << 16) + ("1" << 24))) start := 0 else start := brlong(@buf+$1c6) sdspi.readblock(start, @buf) if (brlong(@buf+$36) <> constant("F" + ("A" << 8) + ("T" << 16) + ("1" << 24)) or buf[$3a] <> "6") return 1 ' not a fat16 volume if (brword(@buf+$0b) <> SECTORSIZE) return 2 ' bad bytes per sector sectorspercluster := buf[$0d] if (sectorspercluster & (sectorspercluster - 1)) return 3 ' bad sectors per cluster clustershift := 0 repeat while (sectorspercluster > 1) clustershift++ sectorspercluster >>= 1 sectorspercluster := 1 << clustershift reserved := brword(@buf+$0e) if (buf[$10] <> 2) return 4 ' not two FATs rootentries := brword(@buf+$11) sectors := brword(@buf+$13) if (sectors == 0) sectors := brlong(@buf+$20) sectorsperfat := brword(@buf+$16) if (brword(@buf+$1fe) <> $aa55) return 5 ' bad FAT signature fat1 := start + reserved rootdir := (fat1 + 2 * sectorsperfat) << SECTORSHIFT rootdirend := rootdir + (rootentries << DIRSHIFT) dataregion := 1 + ((rootdirend - 1) >> SECTORSHIFT) - 2 * sectorspercluster totclusters := ((sectors - dataregion + start) >> clustershift) if (totclusters > $fff0) return 6 ' too many clusters return 0 pri readbytec(byteloc) ' ' Read a byte address from the disk through the metadata buffer and ' return a pointer to that location. ' readblockc(byteloc >> SECTORSHIFT) return @buf2 + (byteloc & constant(SECTORSIZE - 1)) pri readfat(clust) ' ' Read a fat location and return a pointer to the location of that ' entry. ' fatptr := (fat1 << SECTORSHIFT) + (clust << 1) return readbytec(fatptr) pri followchain | clust ' ' Follow the fat chain and update the writelink. ' clust := brword(readfat(fclust)) writelink := fatptr return clust pri nextcluster | clust ' ' Read the next cluster and return it. Set up writelink to ' point to the cluster we just read, for later updating. If the ' cluster number is bad, return a negative number. ' clust := followchain if (clust < 2 or clust => totclusters) abort(-9) ' bad cluster value return clust pri freeclusters(clust) | bp ' ' Free an entire cluster chain. Used by remove and by overwrite. ' Assumes the pointer has already been cleared/set to $ffff. ' repeat while (clust < $fff0) if (clust < 2) abort(-26) ' bad cluster number") bp := readfat(clust) clust := brword(bp) brwword(bp, 0) flushifdirty pri datablock ' ' Calculate the block address of the current data location. ' return (fclust << clustershift) + dataregion + ((floc >> SECTORSHIFT) & ((1 << clustershift) - 1)) pri uc(c) ' ' Compute the upper case version of a character. ' if ("a" =< c and c =< "z") return c - 32 return c pri pflushbuf(r, metadata) | cluststart, newcluster, count, i ' ' Flush the current buffer, if we are open for write. This may ' allocate a new cluster if needed. If metadata is true, the ' metadata is written through to disk including any FAT cluster ' allocations and also the file size in the directory entry. ' if (direntry == 0) abort(-27) ' not open for writing if (r > 0) ' must *not* allocate cluster if flushing an empty buffer if (frem < SECTORSIZE) ' find a new clustercould be anywhere! If possible, stay on the ' same page used for the last cluster. newcluster := -1 cluststart := fclust & constant(!((SECTORSIZE >> 1) - 1)) count := 2 repeat readfat(cluststart) repeat i from 0 to constant(SECTORSIZE - 2) step 2 if (buf2[i]==0 and buf2[i+1]==0) newcluster := cluststart + (i >> 1) if (newcluster => totclusters) newcluster := -1 quit if (newcluster > 1) brwword(@buf2+i, -1) brwword(readbytec(writelink), newcluster) writelink := fatptr + i fclust := newcluster frem := SECTORSIZE << clustershift quit else cluststart += constant(SECTORSIZE >> 1) if (cluststart => totclusters) cluststart := 0 count-- if (count < 0) r := -5 ' No space left on device quit if (frem => SECTORSIZE) sdspi.writeblock(datablock, @buf) if (r == SECTORSIZE) ' full buffer, clear it floc += r frem -= r bufat := 0 bufend := r else ' not a full blockleave pointers alone if (r < 0 or metadata) ' update metadata even if error readblockc(direntry >> SECTORSHIFT) ' flushes unwritten FAT too brwlong(@buf2+(direntry & constant(SECTORSIZE-1))+28, floc+bufat) flushifdirty if (r < 0) abort(r) return r pub pflush {{ ' Call flush with the current data buffer location, and the flush ' metadata flag set. }} return pflushbuf(bufat, 1) pri pfillbuf | r ' ' Get some data into an empty buffer. If no more data is available, ' return -1. Otherwise return the number of bytes read into the ' buffer. ' if (floc => filesize) return -1 if (frem == 0) fclust := nextcluster 'nächster cluster frem := SECTORSIZE << clustershift if (frem + floc > filesize) frem := filesize - floc sdspi.readblock(datablock, @buf) 'cluster einlesen r := SECTORSIZE if (floc + r => filesize) r := filesize - floc floc += r frem -= r bufat := 0 bufend := r return r pub pclose | r {{ ' Flush and close the currently open file if any. Also reset the ' pointers to valid values. If there is no error, 0 will be returned. }} r := 0 if (direntry) r := pflush bufat := 0 bufend := 0 filesize := 0 floc := 0 frem := 0 writelink := 0 direntry := 0 fclust := 0 return r pri pdate {{ ' Get the current date and time, as a long, in the format required ' by FAT16. Right now it"s hardwired to return the date this ' software was created on (April 7, 2007). You can change this ' to return a valid date/time if you have access to this data in ' your setup. }} return constant(((2007-1980) << 25) + (1 << 21) + (7 << 16) + (4 << 11)) pub popen(s, mode) | i, sentinel, dirptr, freeentry {{ ' Close any currently open file, and open a new one with the given ' file name and mode. Mode can be "r" "w" "a" or "d" (delete). ' If the file is opened successfully, 0 will be returned. If the ' file did not exist, and the mode was not "w" or "a", -1 will be ' returned. Otherwise abort will be called with a negative error ' code. ' ' s - zeiger auf string mit dateinamen ' mode - r, w, a, d }} pclose ' ----------------------------------------------------------- dateinamen aufbereiten i := 0 repeat while (i<8 and byte[s] and byte[s] <> ".") 'kopiert dateinamen ohne extender padname[i++] := uc(byte[s++]) 'uc wandelt in kleinbuchstaben repeat while (i<8) padname[i++] := " " repeat while (byte[s] and byte[s] <> ".") s++ if (byte[s] == ".") s++ repeat while (i<11 and byte[s]) padname[i++] := uc(byte[s++]) repeat while (i < 11) padname[i++] := " " ' ----------------------------------------------------------- datei im verzeichnis suchen sentinel := 0 freeentry := 0 repeat dirptr from rootdir to rootdirend - DIRSIZE step DIRSIZE s := readbytec(dirptr) if (freeentry == 0 and (byte[s] == 0 or byte[s] == $e5)) freeentry := dirptr if (byte[s] == 0) sentinel := dirptr quit repeat i from 0 to 10 'vergleicht eintrag mit dateinamen if (padname[i] <> byte[s][i]) 'bei gleichheit i == 11 quit if (i == 11 and 0 == (byte[s][$0b] & $18)) 'dateiname gefunden und kein verzeichnis/volume fclust := brword(s+$1a) 'startcluster der datei lesen und als aktuell setzen filesize := brlong(s+$1c) 'dateigröße der datei lesen fsize := filesize 'dateigröße der datei lesen if (mode == "r") 'DATEI LESEN frem := SECTORSIZE << clustershift if (frem > filesize) frem := filesize return 0 if (byte[s][11] & $d9) ' datei ist schreibgeschützt abort(-6) ' no permission to write if (mode == "d") 'DATEI LÖSCHEN brwword(s, $e5) 'verzeichniseintrag als gelöscht markieren freeclusters(fclust) 'cluster freigeben flushifdirty return 0 if (mode == "w") 'DATEI SCHREIBEN/ÜBERSCHREIBEN brwword(s+26, -1) 'bestehende clusterreferenz ungültig machen brwlong(s+28, 0) 'größe der neuen datei auf 0 setzen writelink := dirptr + 26 direntry := dirptr freeclusters(fclust) 'bestehende clusterkette freigeben bufend := SECTORSIZE fclust := 0 filesize := 0 frem := 0 return 0 elseif (mode == "a") 'DATEI ANHÄNGEN ' this code will eventually be moved to seek frem := filesize freeentry := SECTORSIZE << clustershift 'freeentry = clustergröße in bytes if (fclust => $fff0) fclust := 0 repeat while (frem > freeentry) 'bis zum letzten cluster springen if (fclust < 2) abort(-7) ' eof repeat while following chain fclust := nextcluster 'springe zum nächsten cluster frem -= freeentry 'berechne neue größe bis dateiende 'in frem bleiben anzahl bytes im letzten cluster floc := filesize & constant(!(SECTORSIZE - 1)) 'sektornummer dateiende berechnen bufend := SECTORSIZE bufat := frem & constant(SECTORSIZE - 1) writelink := dirptr + 26 direntry := dirptr if (bufat) sdspi.readblock(datablock, @buf) frem := freeentry - (floc & (freeentry - 1)) else if (fclust < 2 or frem == freeentry) frem := 0 else frem := freeentry - (floc & (freeentry - 1)) if (fclust => 2) followchain return 0 else abort(-3) ' bad argument ' ----------------------------------------------------------- datei nicht gefunden, neue datei erzeugen if (mode <> "w" and mode <> "a") return -1 ' not found direntry := freeentry if (direntry == 0) abort(-2) ' no empty directory entry ' write (or new append): create valid directory entry s := readbytec(direntry) bytefill(s, 0, DIRSIZE) bytemove(s, @padname, 11) brwword(s+26, -1) i := pdate brwlong(s+$e, i) ' write create time and date brwlong(s+$16, i) ' write last modified date and time if (direntry == sentinel and direntry + DIRSIZE < rootdirend) brwword(readbytec(direntry+DIRSIZE), 0) flushifdirty writelink := direntry + 26 fclust := 0 bufend := SECTORSIZE return 0 pub pseek(bytenr) | freeentry,dirptr 'setzt zeiger auf position {{ - springt erst alle ganzen cluster weiter - restbytes im letzten cluster werden geladen - code funktioniert nicht!!! }} {{ frem := bytenr freeentry := SECTORSIZE << clustershift 'freeentry = clustergröße in bytes if (fclust => $fff0) fclust := 0 repeat while (frem > freeentry) 'bis zum cluster springen if (fclust < 2) abort(-7) 'eof repeat while following chain fclust := nextcluster 'springe zum nächsten cluster frem -= freeentry 'berechne neue größe bis dateiende 'in frem bleiben anzahl bytes im letzten cluster floc := frem & constant(!(SECTORSIZE - 1)) 'sektornummer berechnen bufend := SECTORSIZE bufat := frem & constant(SECTORSIZE - 1) 'restbytes über clustergrenze berechnen writelink := dirptr + 26 direntry := dirptr if (bufat) sdspi.readblock(datablock, @buf) 'restbytes einlesen in buf frem := freeentry - (floc & (freeentry - 1)) else if (fclust < 2 or frem == freeentry) frem := 0 else frem := freeentry - (floc & (freeentry - 1)) if (fclust => 2) followchain return 0 }} pub pread(ubuf, count) | r, t {{ ' Read count bytes into the buffer ubuf. Returns the number of bytes ' successfully read, or a negative number if there is an error. ' The buffer may be as large as you want. }} r := 0 repeat while (count > 0) if (bufat => bufend) t := pfillbuf if (t =< 0) if (r > 0) return r return t t := bufend - bufat if (t > count) t := count bytemove(ubuf, @buf+bufat, t) bufat += t r += t ubuf += t count -= t return r pub pgetc | t {{ ' Read and return a single character. If the end of file is ' reached, -1 will be returned. If an error occurs, a negative ' number will be returned. }} if (bufat => bufend) t := pfillbuf if (t =< 0) return -1 return (buf[bufat++]) pub pwrite(ubuf, count) | r, t {{ ' Write count bytes from the buffer ubuf. Returns the number of bytes ' successfully written, or a negative number if there is an error. ' The buffer may be as large as you want. }} t := 0 repeat while (count > 0) if (bufat => bufend) t := pflushbuf(bufat, 0) t := bufend - bufat if (t > count) t := count bytemove(@buf+bufat, ubuf, t) r += t bufat += t ubuf += t count -= t return t pub pputc(c) {{ ' Write a single character into the file open for write. Returns ' 0 if successful, or a negative number if some error occurred. }} if (bufat == SECTORSIZE) 'ist sectorende erreicht? pflushbuf(SECTORSIZE, 0) buf[bufat++] := c return 0 pub opendir | off {{ ' Close the currently open file, and set up the read buffer for ' calls to nextfile. }} pclose off := rootdir - (dataregion << SECTORSHIFT) fclust := off >> (clustershift + SECTORSHIFT) floc := off - (fclust << (clustershift + SECTORSHIFT)) frem := rootdirend - rootdir filesize := floc + frem return 0 pub nextfile(fbuf) | i, t, at, lns {{ ' Find the next file in the root directory and extract its ' (8.3) name into fbuf. Fbuf must be sized to hold at least ' 13 characters (8 + 1 + 3 + 1). If there is no next file, ' -1 will be returned. If there is, 0 will be returned. }} repeat if (bufat => bufend) t := pfillbuf if (t < 0) return t if (((floc >> SECTORSHIFT) & ((1 << clustershift) - 1)) == 0) fclust++ at := @buf + bufat if (byte[at] == 0) 'verzeichnisende erreicht return -1 bufat += DIRSIZE if (byte[at] <> $e5 and (byte[at][$0b] & $18) == 0) fsize := brlong(at+$1c) 'dateigröße der datei lesen fattrib := byte[at][$0b] 'attribute setzen ftime := brlong(at+$0e) 'zeitstempel setzen lns := fbuf repeat i from 0 to 10 byte[fbuf] := byte[at][i] fbuf++ if (byte[at][i] <> " ") lns := fbuf if (i == 7 or i == 10) fbuf := lns if (i == 7) byte[fbuf] := "." fbuf++ byte[fbuf] := 0 return 0 pub getfsize 'dateigröße übergeben return fsize pub getfattrib 'attribute übergeben return fattrib pub getftime 'zeitstempel übergeben return ftime {{ ' Permission is hereby granted, free of charge, to any person obtaining ' a copy of this software and associated documentation files ' (the "Software"), to deal in the Software without restriction, ' including without limitation the rights to use, copy, modify, merge, ' publish, distribute, sublicense, and/or sell copies of the Software, ' and to permit persons to whom the Software is furnished to do so, ' subject to the following conditions: ' ' The above copyright notice and this permission notice shall be included ' in all copies or substantial portions of the Software. ' ' THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, ' EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF ' MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. ' IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY ' CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, ' TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ' SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. }}