SXTVRENDEZVOUS = $8000 - 4 RESERVED = SXTVRENDEZVOUS - 4 SDSPIRENDEZVOUS = RESERVED - 3 * 4 SXFS2RENDEZVOUS = SDSPIRENDEZVOUS - 4 * 4 ' four rendezvous variables SXFSRENDEZVOUS = SXFS2RENDEZVOUS - 4 * 4 ' four rendezvous variables METADATABUFFER = SXFSRENDEZVOUS - 512 _free = ($8000 - METADATABUFFER) / 4 con SECTORSIZE = 512 ' 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 pub Start( pbrSector ) 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 ' start sxfs2 cog long[pCommand] := "?" if cognew( @Sxfs2Entry, 0 ) < 0 abort -1 repeat while long[pCommand] pri PeekW( a ) return byte[a++] + byte[a] << 8 pri PeekL( a ) return byte[a++] + byte[a++] << 8 + byte[a++] << 16 + byte[a] << 24 dat org 0 Sxfs2Entry AckNoReturnCode mov temp, #0 ' clear return code AckReturnCode ' enter here with temp = return code wrlong temp, pParam mov temp, #0 wrlong temp, pCommand ' clear command to signal operation complete. mov dirty, temp ' Ensure that metatdata is flushed before coming here. neg temp, #1 mov currentSector, temp :ready ' wait for new command rdlong temp, pCommand wz if_z jmp #:ready cmp temp, #"?" wz if_e jmp #AckNoReturnCode 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 ' return code -100 : bad command jmp #AckReturnCode '********************************************************************************************************************* '* * '* Write stuff * '* * '********************************************************************************************************************* Write ' ( pFilestuff, ptr, n ) rdlong pFilestuff, pSxfsParam0 rdlong srcPtr, pSxfsParam1 rdlong nBytes, pSxfsParam2 mov retcode, nBytes mov p, pFilestuff ' long[ pFilestuff+_length ] += n 'add p, #_length (unnecessary cuz _length = 0) rdlong temp, p add temp, nBytes wrlong temp, p ' repeat while (leftInSector := SECTORSIZE - word[pIoblock+_bp]) < n :while mov p, pFilestuff add p, #_bp ' p points to pFilestuff->bp mov leftInSector, k512 rdword temp, p ' temp = pFilestuff->bp sub leftInSector, temp cmp leftInSector, nBytes wc, wz if_ae jmp #:done ' bytemove( pFilestuff + _buffer + word[pFilestuff+_bp], ptr, leftInSector ) mov p, pFilestuff add p, #_buffer add p, temp ' p now points bp bytes into buffer. mov q, srcPtr mov n, leftInSector call #ByteCopy ' ptr += leftInSector add srcPtr, leftInSector ' advance the source pointer by #bytes written. ' n -= leftInSector sub nBytes, leftInSector ' decrease #bytes to write by #bytes written. ' WriteNextSector( pIoblock ) call #WriteNextSector jmp #:while :done ' bytemove( pFilestuff + _buffer + word[pIoblock+_bp], ptr, n ) mov p, pFilestuff add p, #_buffer add p, temp ' temp = pFilestuff->bp mov q, srcPtr mov n, nBytes call #ByteCopy ' word[pFilestuff+_bp] += n mov p, pFilestuff add p, #_bp rdword temp, p add temp, nBytes ' advance bp by #bytes written. wrword temp, p call #FlushMetadataSector mov temp, retcode jmp #AckReturnCode WriteNextSector mov p, pFilestuff add p, #_sector rdword temp, p wz if_nz jmp #:writeSector ' sector# <> 0? Write that sector. ' Writing sector 0 implies that we're starting a new cluster, so find an empty slot in the FAT mov cluster, #0 mov sector, fatSector0 mov i, sectorsPerFat :forEachFatSector call #ReadMetadataSector mov p, pMetadataBuffer mov j, #256 :forEachSlotInSector rdword temp, p wz ' examine current slot if_z jmp #:foundEmptySlot add p, #2 add cluster, #1 djnz j, #:forEachSlotInSector add sector, #1 djnz i, #:forEachFatSector neg temp, #110 ' -110: FAT full. jmp #AckReturnCode :foundEmptySlot neg temp, #1 wrword temp, p ' Claim empty slot by writing $ffff. mov dirty, #1 ' We've modified metadata. mov sop, sector shl sop, #9 sub p, pMetadataBuffer add sop, p ' sop "points" to slot. mov p, pFilestuff add p, #_cluster wrword cluster, p ' save cluster# in pFilestuff->cluster sub p, #_cluster-_sopTail ' p points to pFilestuff->sopTail rdlong sopTail, p ' get "pointer" to end of cluster list. mov sector, sopTail ' Read the sector that sopTail "points" to. shr sector, #9 call #ReadMetadataSector and sopTail, #$1ff add sopTail, pMetadataBuffer ' Use sopTail as pointer into buffer. wrword cluster, sopTail ' Now the word pointed to by pFilestuff->sopTail ' is updated with the new cluster#. mov dirty, #1 wrlong sop, p ' p is still pointing to pFilestuff->sopTail so ' update pFilestuff->sopTail to point to new tail. :writeSector mov p, pFilestuff add p, #_cluster ' p points to pFilestuff->cluster rdword i, p ' using i for sector# sub i, #2 shl i, clusterShift add i, dataSector0 ' At this point, i is the first sector of the cluster. add p, #_sector-_cluster ' p points to pFilestuff->sector rdword temp, p add i, temp ' Now i is the correct sector of the cluster. add temp, #1 wrword temp, p ' Increment pFilestuff->sector cmp temp, sectorsPerCluster wz mov temp, #0 ' if pFilestuff->sector = sectorsPerCluster, if_e wrword temp, p ' pFilestuff->sector := 0. add p, #_bp-_sector ' p points to pFilestuff->bp wrword temp, p ' pFilestuff->bp = 0. wrlong i, pSdspiBlockno add p, #_buffer-_bp wrlong p, pSdspiParam mov temp, #"W" call #SdspiCommand WriteNextSector_ret ret '********************************************************************************************************************* '* * '* Close stuff * '* * '********************************************************************************************************************* Close ' ( pFilestuff ) rdlong pFilestuff, pSxfsParam0 mov p, pFilestuff add p, #_bp rdword temp, p wz ' If there's stuff waiting to be written if_nz call #WriteNextSector ' (i.e. bp <> 0), write it. mov p, pFilestuff add p, #_sopDir rdlong sop, p ' sop "points" to directory entry. mov sector, sop shr sector, #9 call #ReadMetadataSector ' We want to update metadata length info. and sop, #$1ff add sop, pMetadataBuffer add sop, #$1c ' sop points to length in metadata buffer. mov p, pFilestuff ' p points to pFilestuff->length. rdlong temp, p wrlong temp, sop ' store length in metadata. mov dirty, #1 call #FlushMetadataSector jmp #AckNoReturnCode '********************************************************************************************************************* '* * '* Execute stuff * '* * '********************************************************************************************************************* Execute ' ( pFilestuff, execmode, cogid ) { Loads 63 sectors from file and executes. Requires that the file be contiguous on the SD card, which is generally accomplished by formatting the card with cluster size >= 32k. This reads 63 sectors instead of 64 to avoid writing junk into the rendezvous areas. It is conceivable that some executable files will not run because of this. This routine does handle clock speed changes. If execmode is 0, start Spin interpreter in cog specified by cogid. If execmode is 1, start Spin interpreter in cog specified by cogid and stop all other cogs. } call #FlushMetadataSector ' just in case neg currentsector, #1 ' invalidate current sector 'cuz metadata buffer is about to be overwritten rdlong pFilestuff, pSxfsParam0 rdlong filesize, pFilestuff wz ' length of file if_z neg temp, #1 ' if file is empty, return -1: EOF if_z jmp #AckReturnCode ' This is a bit of a hack. The Sphinx compiler really ' should delete outdated .bin or .eep files to force ' (re)linking, but since sxfs doesn't know how to ' delete files (yet(?)), the compiler just makes ' a file empty instead of deleting it. This test ' here catches empty executable files and bails, ' preventing a crash. rdlong execmode, pSxfsParam1 rdbyte callingCog, pSxfsParam2 ' stop Spin interpreter cogstop callingCog rdlong oldClkfreq, #0 ' save clkfreq rdbyte oldClkmode, #4 ' save clkmode mov p, pFilestuff add p, #_cluster rdword temp, p ' temp = (cluster sub temp, #2 ' - 2) shl temp, clusterShift ' << clusterShift add temp, dataSector0 ' + dataSector0 mov p, temp ' p repurposed here as sector# mov i, #63 ' read 63 contiguous sectors mov q, #0 :loop wrlong p, pSdspiBlockno ' Prepare to read sector p wrlong q, pSdspiParam ' to memory pointed to by q mov temp, #"R" call #SdspiCommand add p, #1 ' Next sector add q, k512 ' Next 512-byte block of memory djnz i, #:loop ' clear memory above the loaded file mov i, #1 shl i, #15 ' i = 32768 sub i, filesize wz ' - file size if_z jmp #:z mov temp, #0 :y wrbyte temp, filesize ' repurpose filesize as pointer add filesize, #1 djnz i, #:y :z ' store those nutty $fff9ffff longs rdword p, #$0a sub p, #4 wrlong kfff9ffff, p sub p, #4 wrlong kfff9ffff, p tjz execmode, #:dontChangeClock ' execmode = 0? skip cog-killing and clock change cogid temp mov i, #0 mov j, #8 :cogkill cmp i, temp wz ' kill all cogs except self if_ne cogstop i ' (suicide comes later) add i, #1 djnz j, #:cogkill ' The following code is borrowed liberally from sdspiFemto: rdlong temp, #0 ' new clock frequency cmp temp, oldClkfreq wz ' compared to old rdbyte temp, #4 ' get new clock mode in advance if_ne jmp #:changeClock cmp temp, oldClkmode wz ' compare new clock mode to old if_e jmp #:dontChangeClock :changeClock and temp, #$f8 ' Force use of RCFAST clock while clkset temp ' letting requested clock start mov i, time_xtal :startupDelay djnz i, #:startupDelay ' Allow 20ms@20MHz for xtal/pll to settle rdbyte temp, #4 ' Then switch to selected clock clkset temp jmp #:x :dontChangeClock wrlong oldClkfreq, #0 ' Restore old clock frequency wrbyte oldClkmode, #4 ' and mode. :x or interpreter, callingCog coginit interpreter ' start new Spin interpreter in the same cog. tjz execmode, #AckNoReturnCode ' execmode = 0? Done. cogid temp ' kill cogstop temp ' self interpreter long ($0004 << 16) | ($F004 << 2) | %0000 ' Thanks to sdspiFemto time_xtal long 20 * 20000 / 4 / 1 ' 20ms (@20MHz, 1 inst/loop) '********************************************************************************************************************* '* * '* Utility stuff * '* * '********************************************************************************************************************* { Out'(outch) ''' :w rdbyte outtemp, pTerm wz if_nz jmp #:w wrbyte outch, pTerm Out_ret ret OutHex'(outx, outn) ''' mov outtemp, outn sub outtemp, #1 shl outtemp, #2 ror outx, outtemp :loop mov outch, outx and outch, #$0f add outch, #"0" cmp outch, #"9" wc, wz if_a add outch, #"a"-"0"-10 call #out rol outx, #4 djnz outn, #:loop Outhex_ret ret pTerm long $7ffc outch long 0 outx long 0 outn long 0 outtemp long 0 ''' } 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 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 k512 long 512 kfff9ffff long $fff9ffff dirty long 0 currentSector long -1 pMetadataBuffer long METADATABUFFER pSxfsCommand long SXFSRENDEZVOUS+0 pSxfsParam0 long SXFSRENDEZVOUS+4 pSxfsParam1 long SXFSRENDEZVOUS+8 pSxfsParam2 long SXFSRENDEZVOUS+12 pSdspiCommand long SDSPIRENDEZVOUS+0 pSdspiParam long SDSPIRENDEZVOUS+4 pSdspiBlockno long SDSPIRENDEZVOUS+8 pCommand long SXFS2RENDEZVOUS+0 pParam 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 i res 1 j res 1 n res 1 p res 1 q res 1 pFilestuff res 1 sop res 1 sopTail res 1 srcPtr res 1 nBytes res 1 cluster res 1 sector res 1 leftInSector res 1 filesize res 1 execmode res 1 callingCog res 1 oldClkfreq res 1 oldClkmode 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. | +------------------------------------------------------------------------------------------------------------------------------+ }}