871 lines
44 KiB
Plaintext
871 lines
44 KiB
Plaintext
SXTVRENDEZVOUS = $8000 - 4
|
|
SXKBRENDEZVOUS = SXTVRENDEZVOUS - 4
|
|
SDSPIRENDEZVOUS = SXKBRENDEZVOUS - 3 * 4
|
|
SXFS2RENDEZVOUS = SDSPIRENDEZVOUS - 4 * 4 ' four rendezvous variables
|
|
SXFSRENDEZVOUS = SXFS2RENDEZVOUS - 4 * 4 ' four rendezvous variables
|
|
METADATABUFFER = SXFSRENDEZVOUS - 512
|
|
|
|
_free = ($8000 - METADATABUFFER) / 4
|
|
|
|
{
|
|
sxfsrendezvous sdspirendezvous
|
|
--------+ +--------+ +-------+
|
|
| <===> command <===> | | <=====> command <=====> | |
|
|
Spin | | | | |
|
|
routines| <===> param0 <====> | SXFS | <=====> param <=====> | SDSPI |
|
|
| | cog | | cog |
|
|
| ----> param1 -----> | | ------> blockno ------> | |
|
|
| | | +-------+
|
|
| ----> param2 -----> | |
|
|
--------+ | | sxfs2rendezvous
|
|
| | +-------+
|
|
| | <====> command <======> | |
|
|
| | | | <==> (sxfsrendezvous)
|
|
| | <====> param0 <=======> | SXFS2 |
|
|
| | | cog | <==> (sdspirendezvous)
|
|
| | | |
|
|
+--------+ +-------+
|
|
|
|
|
|
SXFS cog handles file reading: open, read, execute, close(?).
|
|
SXFS2 cog, under SXFS's direction, handles file writing: open, write, close.
|
|
SDSPI cog handles SD card sector reading and writing.
|
|
|
|
SXFS cog communicates with main program via sxfsrendezvous and Spin interface routines.
|
|
SXFS cog communicates with SXFS2 cog via sxfs2rendezvous.
|
|
SXFS cog communicates with SDSPI cog via sdspirendezvous.
|
|
|
|
-1: eof, general error
|
|
-100: bad command
|
|
-101: not FAT16
|
|
-102: file not found
|
|
-104: bad mode
|
|
-106: file not closed
|
|
-107: directory full
|
|
-109: file not properly opened
|
|
-110: FAT full.
|
|
-113: bad filename
|
|
|
|
}
|
|
|
|
obj
|
|
sxfs2: "sxfs2"
|
|
sdspi : "sxsdspiq"
|
|
|
|
pub Start( sdPin ) | pbrSector
|
|
|
|
' Determine if sxfs cog and, by association, sxfs2 and sdspi cogs, are already running.
|
|
' If not, start them up.
|
|
' Returns true if the cogs needed to be started, false if the cogs were already running.
|
|
|
|
ifnot Ping
|
|
result~~
|
|
' start sdspi cog and initialize SD card
|
|
long[pSdspiCommand] := "I"
|
|
sdspi.start( sdPin, pSdspiCommand )
|
|
repeat while long[pSdspiCommand]
|
|
if long[pSdspiParam]
|
|
abort long[pSdspiParam]
|
|
|
|
pbrSector := ReadPbr
|
|
|
|
' start sxfs cog
|
|
long[pCommand] := "?"
|
|
if cognew( @sxfsEntry, 0 ) < 0
|
|
abort -1
|
|
repeat while long[pCommand]
|
|
|
|
' start sxfs2 cog
|
|
sxfs2.Start( pbrSector )
|
|
|
|
pri ReadPbr : pbrSector
|
|
repeat
|
|
sdspi.readblock( pbrSector, pMetadataBuffer )
|
|
if bytecompare( pMetadataBuffer+$36, string("FAT16"), 5 )
|
|
quit
|
|
if pbrSector
|
|
abort -101 ' not FAT16
|
|
pbrSector := PeekL( pMetadataBuffer + $1c6 )
|
|
|
|
bytesPerSector := PeekW( pMetadataBuffer + $0b )
|
|
sectorsPerCluster := byte[pMetadataBuffer][$0d]
|
|
clusterShift := >| sectorsPerCluster - 1
|
|
reservedSectors := PeekW( pMetadataBuffer + $0e )
|
|
numberOfFats := byte[pMetadataBuffer][$10]
|
|
sectorsPerFat := PeekW( pMetadataBuffer + $16 )
|
|
RootDirEntries := PeekW( pMetadataBuffer + $11 )
|
|
fatSector0 := pbrSector + reservedSectors
|
|
dirSector0 := fatSector0 + numberOfFats * sectorsPerFat
|
|
dataSector0 := dirSector0 + rootDirEntries >> 4
|
|
|
|
pri bytecompare( _p, _q, _n )
|
|
repeat _n
|
|
if byte[_p++] <> byte[_q++]
|
|
return
|
|
return true
|
|
|
|
pri PeekW( a )
|
|
return byte[a++] + byte[a] << 8
|
|
|
|
pri PeekL( a )
|
|
return byte[a++] + byte[a++] << 8 + byte[a++] << 16 + byte[a] << 24
|
|
|
|
pri Ping
|
|
{{
|
|
returns 1 if bmfs cog is active, 0 otherwise.
|
|
}}
|
|
long[pCommand] := "?"
|
|
if long[pCommand]
|
|
return 0
|
|
else
|
|
return 1
|
|
|
|
con
|
|
SECTORSIZE = 512
|
|
SECTORSHIFT = 9
|
|
DIRSIZE = 32
|
|
|
|
con
|
|
' filestuff definitions
|
|
' filestuff must be long-aligned
|
|
{ long } _length = 0
|
|
{ long } _leftInFile = 4
|
|
{ long } _sopDir = 8 ' sector/offset "pointer" to directory entry
|
|
{ long } _sopTail = 12 ' sector/offset "pointer" to last cluster# in FAT chain
|
|
{ word } _cluster = 16
|
|
{ word } _sector = 18 ' sector# within cluster; ranges from 0 to sectorsPerCluster-1
|
|
{ word } _bp = 20 ' byte pointer (actually an index into the sector buffer); range [0..511]
|
|
{ word } _mode = 22 ' 0: closed; "R": open for reading; "W": open for writing.
|
|
_buffer = 24 ' 512-byte buffer starts here
|
|
|
|
SIZEOFFILESTUFF = SECTORSIZE + 24
|
|
|
|
dat
|
|
{{
|
|
command param0 param1 param2
|
|
"?" -- -- -- Do nothing, but acknowledge to indicate that sxfs cog is active.
|
|
"O" filestuff filename mode Open a file, populate filestuff fields. mode is "R" or "W".
|
|
"R" filestuff buffer num Reads num bytes into buffer from file specified by filestuff.
|
|
"W" filestuff buffer num Writes num bytes from buffer to file specified by filestuff.
|
|
"C" filestuff -- -- Closes file specified by filestuff.
|
|
"X" filestuff execmode cog Executes file.
|
|
|
|
}}
|
|
org 0
|
|
SXFSEntry
|
|
|
|
AckNoReturnCode
|
|
mov temp, #0 ' clear return code
|
|
AckReturnCode ' enter here with temp = return code
|
|
wrlong temp, pParam0
|
|
mov temp, #0
|
|
wrlong temp, pCommand ' clear command to signal operation complete.
|
|
|
|
:ready ' wait for new command
|
|
rdlong temp, pCommand wz
|
|
if_z jmp #:ready
|
|
|
|
cmp temp, #"?" wz
|
|
if_e jmp #AckNoReturnCode
|
|
cmp temp, #"O" wz
|
|
if_e jmp #Open
|
|
cmp temp, #"R" wz
|
|
if_e jmp #Read
|
|
cmp temp, #"W" wz
|
|
if_e jmp #Write
|
|
cmp temp, #"C" wz
|
|
if_e jmp #Close
|
|
cmp temp, #"X" wz
|
|
if_e jmp #Execute
|
|
' else
|
|
neg temp, #100 ' -100: bad command
|
|
jmp #AckReturnCode
|
|
|
|
|
|
'*********************************************************************************************************************
|
|
'* *
|
|
'* Open stuff *
|
|
'* *
|
|
'*********************************************************************************************************************
|
|
Open ' ( param0 = pFilestuff, param1 = pFilename, param2 = mode )
|
|
{
|
|
Return value:
|
|
File found File not found Error
|
|
mode = "R" 0 1 < 0
|
|
mode = "W" 0 0 < 0
|
|
|
|
Mode W will either open an existing file or create a new file.
|
|
}
|
|
rdword pFilestuff, pParam0
|
|
rdword pFilename, pParam1
|
|
|
|
mov p, pFilestuff
|
|
add p, #_mode
|
|
rdword temp, p wz
|
|
if_nz neg temp, #106 ' -106: file not closed
|
|
if_nz jmp #AckReturnCode
|
|
|
|
call #MassageFilename
|
|
|
|
rdword temp, pParam2
|
|
cmp temp, #"R" wz
|
|
if_e jmp #:openForReading
|
|
cmp temp, #"W" wz
|
|
if_e jmp #:openForWriting
|
|
neg temp, #104 ' -104: bad mode
|
|
jmp #AckReturnCode
|
|
:openForReading
|
|
call #FindFile
|
|
|
|
mov p, pFilestuff
|
|
add p, #_sopDir ' pFilestuff->sopDir = 0 => file not found
|
|
rdlong temp, p wz
|
|
if_z mov temp, #1 ' return 1 to indicate file not found
|
|
if_z jmp #AckReturnCode
|
|
|
|
mov p, sop ' sop sector is still in memory
|
|
and p, #$1ff
|
|
add p, pMetadataBuffer
|
|
add p, #$1a ' point to cluster information
|
|
rdword temp, p
|
|
|
|
mov q, pFilestuff
|
|
add q, #_cluster
|
|
wrword temp, q
|
|
|
|
add p, #2 ' point to length information
|
|
rdlong temp, p
|
|
|
|
sub q, #_cluster-_length ' q points to length
|
|
wrlong temp, q
|
|
add q, #_leftInFile-_length ' q points to leftInFile
|
|
wrlong temp, q
|
|
|
|
add q, #_sector-_leftInFile ' q points to sector
|
|
mov temp, #0
|
|
wrword temp, q
|
|
|
|
add q, #_bp-_sector ' q points to bp
|
|
wrword k512, q
|
|
|
|
mov p, pFilestuff
|
|
add p, #_mode
|
|
mov temp, #"R"
|
|
wrword temp, p ' save mode
|
|
|
|
jmp #AckNoReturnCode
|
|
|
|
:openForWriting
|
|
mov sop, dirSector0 ' sop "points" to directory entries:
|
|
shl sop, #9 ' incremented by 32 each time through the loop.
|
|
mov sopDir, #0 ' sopDir "points" to the directory entry
|
|
' that will be used or reused for the file being opened.
|
|
mov i, rootDirEntries
|
|
:forEachDirEntry ' Loop through the directory entries, looking for a deleted entry,
|
|
' an empty entry, or an entry that matches pFilename.
|
|
mov sector, sop
|
|
shr sector, #9
|
|
call #ReadMetadataSector
|
|
mov p, sop
|
|
and p, #$1ff
|
|
add p, pMetadataBuffer ' p points to the current directory entry
|
|
add p, #$0b
|
|
rdbyte temp, p ' p[$0b] = attribute byte
|
|
sub p, #$0b
|
|
and temp, #$02 wz ' If hidden,
|
|
if_nz jmp #:next ' skip. Hidden dir entries include long name entries.
|
|
|
|
rdbyte temp, p ' p[0]
|
|
cmp temp, #$e5 wz ' = $e5? (deleted directory entry?)
|
|
if_e cmp sopDir, #0 wz ' save pointer in sopDir (just the first time)
|
|
if_e mov sopDir, sop
|
|
|
|
tjz temp, #:quit ' empty directory entry? We're done
|
|
|
|
mov q, pFilename ' matching filename? Done.
|
|
mov n, #11
|
|
call #ByteComp
|
|
if_e jmp #:quit
|
|
:next
|
|
add sop, #DIRSIZE
|
|
djnz i, #:forEachDirEntry
|
|
:quit
|
|
cmp i, #0 wz ' did we go through the whole directory?
|
|
if_z neg temp, #107 ' -107: directory full
|
|
if_z jmp #AckReturnCode
|
|
|
|
cmp sopDir, #0 wz ' if sopDir hasn't been set yet,
|
|
if_e mov sopDir, sop ' set it to sop.
|
|
|
|
mov p, pFilestuff
|
|
add p, #_sopDir
|
|
wrlong sopDir, p ' pFilestuff->sopDir := sopDir
|
|
add p, #_sopTail-_sopDir
|
|
add sopDir, #$1a
|
|
wrlong sopDir, p ' pFilestuff->sopTail := sopDir + $1a
|
|
' (points to directory entry's cluster field).
|
|
mov sector, sopDir
|
|
shr sector, #9 ' Get the chosen directory entry into memory
|
|
call #ReadMetadataSector ' if it isn't already.
|
|
|
|
mov p, sopDir ' Recall that sopDir was changed
|
|
and p, #$1ff ' to point to the cluster field,
|
|
add p, pMetadataBuffer ' so p now points to the cluster field in the buffer.
|
|
|
|
rdword cluster, p ' Save the cluster field.
|
|
mov temp, #0
|
|
wrword temp, p ' Zero the cluster field.
|
|
sub p, #$1a ' Point to start of directory entry
|
|
|
|
rdbyte temp, p ' Check to see if this was a deleted dir entry.
|
|
cmp temp, #$e5 wz ' If it was, we definitely do not want to zero the
|
|
if_e mov cluster, #0 ' cluster chain, so set cluster to 0.
|
|
|
|
mov q, pFilename ' Store the filename in the directory entry.
|
|
mov n, #11
|
|
call #ByteCopy
|
|
|
|
mov temp, #$20 ' Set the attribute byte (immediately following name)
|
|
wrbyte temp, p
|
|
add p, #$10-$0b ' Point to creation date.
|
|
mov temp, aDate ' 2001/01/01
|
|
wrword temp, p
|
|
add p, #$12-$10 ' Point to last access date.
|
|
wrword temp, p
|
|
add p, #$18-$12 ' Point to last update date.
|
|
wrword temp, p
|
|
|
|
mov dirty, #1 ' We've modified metadata.
|
|
|
|
tjz cluster, #:done
|
|
:forEachClusterInList ' Traverse the cluster list and zero each link.
|
|
|
|
mov sector, cluster
|
|
shr sector, #8
|
|
add sector, fatSector0
|
|
call #ReadMetadataSector ' Read FAT sector for current cluster
|
|
|
|
mov p, cluster
|
|
and p, #$ff
|
|
shl p, #1
|
|
add p, pMetadataBuffer ' Point p at cluster entry in FAT
|
|
|
|
rdword cluster, p ' Get next cluster link.
|
|
cmp cluster, #1 wc, wz ' Break if cluster <= 1
|
|
if_be jmp #:done
|
|
|
|
mov temp, #0
|
|
wrword temp, p ' Zero the cluster link.
|
|
mov dirty, #1 ' We've modified metadata.
|
|
|
|
cmp cluster, kfff0 wc, wz ' repeat unless new cluster >= $fff0
|
|
if_b jmp #:forEachClusterInList
|
|
:done
|
|
mov p, pFilestuff
|
|
add p, #_sector
|
|
mov temp, #0
|
|
wrword temp, p ' pFilestuff->sector := 0
|
|
add p, #_bp-_sector
|
|
wrword temp, p ' pFilestuff->bp := 0
|
|
|
|
mov p, pFilestuff
|
|
mov temp, #0
|
|
wrlong temp, p ' pFilestuff->length := 0
|
|
add p, #_mode
|
|
mov temp, #"W"
|
|
wrword temp, p ' save mode
|
|
|
|
jmp #AckNoReturnCode
|
|
|
|
MassageFilename
|
|
{
|
|
Take the null-terminated 8.3 filename pointed to by pFilename,
|
|
move it into memory starting at pFilestuff->buffer and expand it to 11 characters;
|
|
change pFilename to point to the expanded filename.
|
|
}
|
|
mov p, pFilestuff
|
|
add p, #_buffer
|
|
mov q, p
|
|
add p, #1
|
|
mov temp, #" "
|
|
wrbyte temp, q
|
|
mov n, #10
|
|
call #ByteCopy
|
|
|
|
mov q, pFilename ' q is src
|
|
mov p, pFilestuff ' p is dst
|
|
add p, #_buffer
|
|
mov i, #9 ' Copy up to 8 chars (stop at . or null).
|
|
:upTo8
|
|
rdbyte temp, q
|
|
add q, #1
|
|
tjz temp, #:done
|
|
cmp temp, #"." wz
|
|
if_z jmp #:dot
|
|
call #ValidateChar
|
|
wrbyte temp, p
|
|
add p, #1
|
|
djnz i, #:upTo8
|
|
' If we fall through, we've copied 9 characters (tsk tsk).
|
|
neg temp, #113 ' -113: bad filename
|
|
jmp #AckReturnCode
|
|
:dot
|
|
mov p, pFilestuff
|
|
add p, #_buffer+8
|
|
mov i, #4 ' Copy up to 3 chars (stop at null).
|
|
:upTo3
|
|
rdbyte temp, q
|
|
add q, #1
|
|
tjz temp, #:done
|
|
call #ValidateChar
|
|
wrbyte temp, p
|
|
add p, #1
|
|
djnz i, #:upTo3
|
|
' If we fall through, we've copied 4 characters (tsk tsk ).
|
|
neg temp, #113 ' -113: bad filename
|
|
jmp #AckReturnCode
|
|
:done
|
|
mov pFilename, pFilestuff
|
|
add pFilename, #_buffer
|
|
|
|
MassageFilename_ret ret
|
|
|
|
FindFile
|
|
{
|
|
pFilename points to 11-character buffer (e.g. "BLAH TXT").
|
|
pFilestuff points to a filestuff structure.
|
|
Return value:
|
|
If file is found, pFilestuff->sopDir is disk address of directory entry.
|
|
If file is not found, pFilestuff->sopDir is 0.
|
|
}
|
|
mov sop, dirSector0 ' sop starts at dirSector0<<9, counts up by DIRSIZE
|
|
shl sop, #SECTORSHIFT
|
|
mov i, rootDirEntries
|
|
:loop
|
|
mov sector, sop
|
|
shr sector, #9
|
|
call #readMetadataSector
|
|
mov p, sop
|
|
and p, #$1ff
|
|
add p, pMetadataBuffer
|
|
rdbyte temp, p wz
|
|
if_z jmp #:notfound
|
|
|
|
add p, #$0b
|
|
rdbyte temp, p ' p[$0b] = attribute byte
|
|
sub p, #$0b
|
|
and temp, #$02 wz ' If hidden,
|
|
if_nz jmp #:next ' skip. Hidden dir entries include long name entries.
|
|
|
|
mov q, pFilename
|
|
mov n, #11
|
|
call #ByteComp
|
|
if_e jmp #:found
|
|
:next
|
|
add sop, #DIRSIZE
|
|
djnz i, #:loop
|
|
:notfound
|
|
mov sop, #0
|
|
:found
|
|
mov p, pFilestuff
|
|
add p, #_sopDir
|
|
wrlong sop, p
|
|
FindFile_ret ret
|
|
|
|
|
|
'*********************************************************************************************************************
|
|
'* *
|
|
'* Read stuff *
|
|
'* *
|
|
'*********************************************************************************************************************
|
|
Read ' ( param0 = pFilestuff, param1 = ptr, param2 = n )
|
|
{
|
|
Read n bytes from file described by pIoblock into memory starting at p.
|
|
Returns number of bytes actually read or -1 if attempting to read past EOF.
|
|
}
|
|
rdlong pFilestuff, pParam0
|
|
rdlong destPtr, pParam1
|
|
rdlong nBytes, pParam2
|
|
|
|
mov p, pFilestuff ' Verify that file was opened for reading.
|
|
add p, #_mode
|
|
rdword temp, p
|
|
cmp temp, #"R" wz
|
|
if_ne neg temp, #109 ' -109: file not properly opened
|
|
if_ne jmp #AckReturnCode
|
|
sub p, #_mode-_leftInFile
|
|
|
|
' Adjust nBytes depending on how much is left to read in file.
|
|
' E.g. if we're 10 bytes from EOF and we try to read 15 bytes,
|
|
' just read 10. If we're at EOF and try to read 15 bytes, return -1.
|
|
rdlong temp, p
|
|
max nBytes, temp ' nBytes is lesser of nBytes and leftInFile
|
|
tjnz temp, #:x
|
|
neg temp, #1 ' -1: eof
|
|
jmp #ackReturnCode
|
|
:x
|
|
mov retcode, nBytes
|
|
:while
|
|
mov leftInSector, k512 ' leftInSector = 512 - pFilestuff->bp
|
|
mov p, pFilestuff
|
|
add p, #_bp
|
|
rdword temp, p ' temp = bp
|
|
sub leftInSector, temp
|
|
cmp leftInSector, nBytes wc, wz
|
|
if_ae jmp #:endwhile
|
|
mov p, destPtr
|
|
mov q, pFilestuff
|
|
add q, #_buffer ' q points to buffer area
|
|
add q, temp ' offset by bp (= temp)
|
|
mov n, leftInSector
|
|
call #ByteCopy ' bytemove( p, q, n )
|
|
add destPtr, leftInSector ' destPtr += leftInSector
|
|
sub nBytes, leftInSector ' nBytes -= leftInSector
|
|
mov p, pFilestuff ' long[pIoblock+_leftInFile] -= leftInSector
|
|
add p, #_leftInFile
|
|
rdlong temp, p
|
|
sub temp, leftInSector
|
|
wrlong temp, p
|
|
|
|
' sdspi.readblock( dataSector0 + (word[pFilestuff+_cluster] - 2) << clusterShift + word[pFilestuff+_sector], pFilestuff+_buffer )
|
|
add p, #_cluster-_leftInFile
|
|
rdword temp, p ' temp = (cluster
|
|
sub temp, #2 ' - 2)
|
|
shl temp, clusterShift ' << clusterShift
|
|
add temp, dataSector0 ' + dataSector0
|
|
add p, #_sector-_cluster
|
|
rdword temp1, p
|
|
add temp, temp1 ' + cluster
|
|
wrlong temp, pSdspiBlockno ' Prepare to read sector
|
|
add p, #_buffer-_sector
|
|
wrlong p, pSdspiParam
|
|
mov temp, #"R"
|
|
call #SdspiCommand
|
|
|
|
' if ++word[pFilestuff+_sector] == sectorsPerCluster
|
|
mov p, pFilestuff
|
|
add p, #_sector
|
|
rdword temp, p
|
|
add temp, #1
|
|
wrword temp, p
|
|
cmp temp, sectorsPerCluster wz
|
|
if_ne jmp #:y
|
|
|
|
' word[pIoblock+_sector]~
|
|
mov temp, #0
|
|
wrword temp, p
|
|
' word[pFilestuff+_cluster] := NextCluster( word[pFilestuff+_cluster] )
|
|
sub p, #_sector-_cluster
|
|
rdword cluster, p
|
|
call #NextCluster '( cluster )
|
|
mov p, pFilestuff
|
|
add p, #_cluster
|
|
wrword cluster, p
|
|
:y
|
|
' word[pFilestuff+_bp]~
|
|
mov p, pFilestuff
|
|
add p, #_bp
|
|
mov temp, #0
|
|
wrword temp, p
|
|
|
|
jmp #:while
|
|
:endwhile
|
|
' bytemove( destPtr, pIoblock + word[pIoblock+_bp], n )
|
|
mov p, destPtr
|
|
mov q, pFilestuff
|
|
add q, #_bp
|
|
rdword temp, q
|
|
add q, #_buffer-_bp
|
|
add q, temp
|
|
mov n, nBytes
|
|
call #ByteCopy
|
|
|
|
' long[pIoblock+_leftInFile] -= n
|
|
mov p, pFilestuff
|
|
add p, #_leftInFile
|
|
rdlong temp, p
|
|
sub temp, nBytes
|
|
wrlong temp, p
|
|
' word[pIoblock+_bp] += n
|
|
add p, #_bp-_leftInFile
|
|
rdword temp, p
|
|
add temp, nBytes
|
|
wrword temp, p
|
|
|
|
mov temp, retcode
|
|
jmp #AckReturnCode
|
|
|
|
NextCluster ' ( cluster )
|
|
{
|
|
Given cluster, determines next cluster in FAT.
|
|
Result in cluster.
|
|
}
|
|
cmp cluster, #1 wc, wz
|
|
if_be jmp #NextCluster_ret
|
|
cmp cluster, kfff0 wc, wz
|
|
if_ae jmp #NextCluster_ret
|
|
|
|
mov sector, cluster
|
|
shr sector, #8
|
|
add sector, fatSector0
|
|
call #ReadMetadataSector
|
|
mov p, cluster
|
|
and p, #$ff
|
|
shl p, #1
|
|
add p, pMetadataBuffer
|
|
rdword cluster, p
|
|
|
|
NextCluster_ret ret
|
|
|
|
kfff0 long $fff0
|
|
|
|
'*********************************************************************************************************************
|
|
'* *
|
|
'* Write stuff *
|
|
'* *
|
|
'*********************************************************************************************************************
|
|
Write
|
|
rdlong p, pParam0 ' p = pFilestuff
|
|
add p, #_mode ' Verify that file was opened for writing.
|
|
rdword temp, p
|
|
cmp temp, #"W" wz
|
|
if_ne neg temp, #109 ' -109: file not properly opened
|
|
if_ne jmp #AckReturnCode
|
|
|
|
call #FlushMetadataSector
|
|
neg currentsector, #1 ' invalidate current sector 'cuz we're about to hand the reins
|
|
' to sxfs2 cog.
|
|
mov temp, #"W"
|
|
wrlong temp, pSxfs2Command
|
|
:wait rdlong temp, pSxfs2Command wz
|
|
if_nz jmp #:wait
|
|
|
|
rdlong temp, pSxfs2Param
|
|
jmp #AckReturnCode
|
|
|
|
'*********************************************************************************************************************
|
|
'* *
|
|
'* Close stuff *
|
|
'* *
|
|
'*********************************************************************************************************************
|
|
Close ' ( param0 = pFilestuff )
|
|
rdlong pFilestuff, pParam0
|
|
mov p, pFilestuff
|
|
add p, #_mode
|
|
rdword temp, p
|
|
|
|
cmp temp, #"W" wz
|
|
if_e jmp #:closew
|
|
|
|
' Files opened in mode "W" need special closing code, but for mode "R" or anything else
|
|
' (like file not even open) we can just do this:
|
|
|
|
mov temp, #0
|
|
wrword temp, p ' clear mode
|
|
jmp #AckNoReturnCode ' and we're done
|
|
|
|
:closew
|
|
mov temp, #0 ' clear mode
|
|
wrword temp, p
|
|
|
|
call #FlushMetadataSector
|
|
neg currentsector, #1 ' invalidate current sector 'cuz we're about to hand the reins
|
|
' to sxfs2 cog.
|
|
mov temp, #"C"
|
|
wrlong temp, pSxfs2Command
|
|
:wait rdlong temp, pSxfs2Command wz
|
|
if_nz jmp #:wait
|
|
|
|
rdlong temp, pSxfs2Param
|
|
jmp #AckReturnCode
|
|
|
|
'*********************************************************************************************************************
|
|
'* *
|
|
'* Execute stuff *
|
|
'* *
|
|
'*********************************************************************************************************************
|
|
Execute ' ( pFilestuff, execmode, cogid )
|
|
call #FlushMetadataSector
|
|
neg currentsector, #1 ' invalidate current sector 'cuz we're about to hand the reins
|
|
' to sxfs2 cog.
|
|
mov temp, #"X"
|
|
wrlong temp, pSxfs2Command
|
|
:wait rdlong temp, pSxfs2Command wz
|
|
if_nz jmp #:wait
|
|
|
|
rdlong temp, pSxfs2Param
|
|
jmp #AckReturnCode
|
|
|
|
'*********************************************************************************************************************
|
|
'* *
|
|
'* Utility stuff *
|
|
'* *
|
|
'*********************************************************************************************************************
|
|
|
|
ByteCopy ' ( p, q, n )
|
|
tjz n, #ByteCopy_ret
|
|
:loop rdbyte temp, q
|
|
add q, #1
|
|
wrbyte temp, p
|
|
add p, #1
|
|
djnz n, #:loop
|
|
|
|
ByteCopy_ret ret
|
|
|
|
ByteComp ' ( p, q, n )
|
|
{
|
|
Compares j bytes starting at p and q.
|
|
Returns Z if they match, NZ if they differ.
|
|
Destroys p, q, and j.
|
|
}
|
|
rdbyte temp, p
|
|
add p, #1
|
|
rdbyte temp1, q
|
|
add q, #1
|
|
cmp temp, temp1 wz
|
|
if_z djnz n, #ByteComp
|
|
|
|
ByteComp_ret ret
|
|
|
|
|
|
ValidateChar
|
|
{
|
|
Make sure that temp is a valid filename character (for our purposes, 0-9, A-Z, _).
|
|
}
|
|
cmp temp, #"a" wc, wz
|
|
if_b jmp #:notLowerCase
|
|
cmp temp, #"z" wc, wz
|
|
if_a jmp #:notLowerCase
|
|
sub temp, #"a"-"A" ' convert to upper-case
|
|
jmp #ValidateChar_ret
|
|
:notLowerCase
|
|
cmp temp, #"A" wc, wz
|
|
if_b jmp #:notAlpha
|
|
cmp temp, #"Z" wc, wz
|
|
if_be jmp #ValidateChar_ret
|
|
:notAlpha
|
|
cmp temp, #"0" wc, wz
|
|
if_b jmp #:notNumeric
|
|
cmp temp, #"9" wc, wz
|
|
if_be jmp #ValidateChar_ret
|
|
:notNumeric
|
|
cmp temp, #"_" wz
|
|
if_e jmp #ValidateChar_ret
|
|
|
|
mov temp, #113 ' -113: bad filename
|
|
jmp #AckReturnCode
|
|
|
|
ValidateChar_ret ret
|
|
|
|
|
|
|
|
|
|
|
|
ReadMetadataSector ' ( sector )
|
|
call #FlushMetadataSector
|
|
|
|
cmp sector, currentSector wz
|
|
if_e jmp #ReadMetadataSector_ret
|
|
wrlong pMetadataBuffer, pSdspiParam
|
|
wrlong sector, pSdspiBlockno
|
|
mov currentSector, sector
|
|
|
|
mov temp, #"R"
|
|
call #SdspiCommand
|
|
ReadMetadataSector_ret ret
|
|
|
|
FlushMetadataSector
|
|
tjz dirty, #FlushMetadataSector_ret
|
|
mov dirty, #0
|
|
|
|
' write current sector
|
|
wrlong pMetadataBuffer, pSdspiParam
|
|
wrlong currentSector, pSdspiBlockno
|
|
mov temp, #"W"
|
|
call #SdspiCommand
|
|
FlushMetadataSector_ret ret
|
|
|
|
SdspiCommand
|
|
wrlong temp, pSdspiCommand
|
|
:wait
|
|
rdlong temp, pSdspiCommand wz
|
|
if_nz jmp #:wait
|
|
|
|
rdlong temp, pSdspiParam wz
|
|
if_nz jmp #AckReturnCode
|
|
|
|
SdspiCommand_ret ret
|
|
|
|
kFAT1 long "F" + "A"<<8 + "T"<<16 + "1"<<24
|
|
k512 long 512
|
|
dirty long 0
|
|
currentSector long -1
|
|
aDate long $2a21
|
|
|
|
pMetadataBuffer long METADATABUFFER
|
|
|
|
pCommand long SXFSRENDEZVOUS+0
|
|
pParam0 long SXFSRENDEZVOUS+4
|
|
pParam1 long SXFSRENDEZVOUS+8
|
|
pParam2 long SXFSRENDEZVOUS+12
|
|
|
|
pSdspiCommand long SDSPIRENDEZVOUS+0
|
|
pSdspiParam long SDSPIRENDEZVOUS+4
|
|
pSdspiBlockno long SDSPIRENDEZVOUS+8
|
|
|
|
pSxfs2Command long SXFS2RENDEZVOUS+0
|
|
pSxfs2Param long SXFS2RENDEZVOUS+4
|
|
|
|
bytesPerSector long 0
|
|
sectorsPerCluster long 0
|
|
clusterShift long 0
|
|
reservedSectors long 0
|
|
numberOfFats long 0
|
|
sectorsPerFat long 0
|
|
rootDirEntries long 0
|
|
fatSector0 long 0
|
|
dirSector0 long 0
|
|
dataSector0 long 0
|
|
|
|
temp res 1
|
|
temp1 res 1
|
|
i res 1
|
|
j res 1
|
|
n res 1
|
|
p res 1
|
|
q res 1
|
|
sop res 1
|
|
byte4 res 0
|
|
sopDir res 1
|
|
sector res 1
|
|
cluster res 1
|
|
|
|
pFilename res 1
|
|
pFilestuff res 1
|
|
destPtr res 1
|
|
nBytes res 1
|
|
leftInFile res 1
|
|
leftInSector res 1
|
|
retcode res 1
|
|
|
|
fit
|
|
|
|
{{
|
|
Copyright (c) 2009 Michael Park
|
|
+------------------------------------------------------------------------------------------------------------------------------+
|
|
| TERMS OF USE: MIT License |
|
|
+------------------------------------------------------------------------------------------------------------------------------+
|
|
|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. |
|
|
+------------------------------------------------------------------------------------------------------------------------------+
|
|
}}
|
|
|