TriOS-alt/flash/administra/admflash-fsrw.spin

711 lines
27 KiB
Plaintext
Raw Blame History

{{
' 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<67><72>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<6E>ngenden containerdateien und erm<72>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<67>ffnet und startsektor berechnet. die sektornummer wird zur<75>ckgeliefert;
bei einem fehler der wert -1}}
s~
err := popen(strptr,"r") 'containerdatei <20>ffnen
ifnot err 'kein fehler bei <20>ffnen des containers
s := (fclust << clustershift) + dataregion 'startsektor berechnen
pclose 'containerdatei schlie<69>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<67><72>e der datei lesen
fsize := filesize 'dateigr<67><72>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<63>tzt
abort(-6) ' no permission to write
if (mode == "d") 'DATEI L<>SCHEN
brwword(s, $e5) 'verzeichniseintrag als gel<65>scht markieren
freeclusters(fclust) 'cluster freigeben
flushifdirty
return 0
if (mode == "w") 'DATEI SCHREIBEN/<2F>BERSCHREIBEN
brwword(s+26, -1) 'bestehende clusterreferenz ung<6E>ltig machen
brwlong(s+28, 0) 'gr<67><72>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<4E>NGEN
' this code will eventually be moved to seek
frem := filesize
freeentry := SECTORSIZE << clustershift 'freeentry = clustergr<67><72>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<67><72>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<67><72>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<67><72>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 <20>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<67><72>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<67><72>e <20>bergeben
return fsize
pub getfattrib 'attribute <20>bergeben
return fattrib
pub getftime 'zeitstempel <20>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.
}}