M Makefile => Makefile +3 -3
@@ 61,9 61,9 @@ testlib: dusk
testcc: dusk
echo "' byefail ' abort realias f<< tests/comp/c/all.fs bye" | ./dusk || (echo; exit 1)
-.PHONY: testfat
-testfat: dusk
- echo "' byefail ' abort realias f<< tests/fs/fat.fs bye" | ./dusk || (echo; exit 1)
+.PHONY: testdrv
+testdrv: dusk
+ echo "' byefail ' abort realias f<< tests/lib/drive.fs bye" | ./dusk || (echo; exit 1)
.PHONY: clean
clean:
D fs/doc/drive.txt => fs/doc/drive.txt +0 -26
@@ 1,26 0,0 @@
-# Drive API
-
-TODO: the Drive struct is defined in xcomp/bootlo and I can't think of an
-adequate place for this documentation. For now, it's here.
-
-The Drive struct offers a unified API to read and write to mass storage. It
-does so in blocks of bytes of a definite size which we call "sector". Sectors
-in the Drive API are organized in a linear manner (LBA, Logical Block
-Addressing). If the underlying mass storage driver interfaces with a CHS
-(cylinder, head, sector) system, it's the driver's responsibility to convert LBA
-to CHS.
-
-Fields:
-
-secsz size in bytes of each sector read and written by the driver.
- It's often 512.
-seccnt Number of sectors in the drive
-
-Methods:
-
-:sec@ ( sec dst drv -- )
- Read sector "sec" in memory address "dst", which needs to be a buffer at least
- "secsz" in size. "drv" is a pointer to a Drive structure.
-
-:sec! ( sec src drv -- )
- Write sector "sec" from memory address "src".
A fs/doc/lib/drive.txt => fs/doc/lib/drive.txt +82 -0
@@ 0,0 1,82 @@
+# Drive
+
+The Drive struct offers a unified API to read and write to mass storage. It
+does so in blocks of bytes of a definite size which we call "sector". Sectors
+in the Drive API are organized in a linear manner (LBA, Logical Block
+Addressing). If the underlying mass storage driver interfaces with a CHS
+(cylinder, head, sector) system, it's the driver's responsibility to convert LBA
+to CHS.
+
+The /lib/drive unit contains utilities around the Drive structure. This unit in
+split in two, between /lib/drivelo and lib/drive. /lib/drivelo is designed to be
+used at boot time. /lib/drive complements it with additional features.
+
+Fields:
+
+secsz size in bytes of each sector read and written by the driver.
+ It's often 512.
+seccnt Number of sectors in the drive
+
+Methods:
+
+:sec@ ( sec dst drv -- )
+ Read sector "sec" in memory address "dst", which needs to be a buffer at least
+ "secsz" in size. "drv" is a pointer to a Drive structure.
+
+:sec! ( sec src drv -- )
+ Write sector "sec" from memory address "src".
+
+## SectorWindow
+
+A SectorWindow is a buffer referencing one or more sectors and holding one
+sector in memory. It manages the "dirty" status of the buffer and makes it easy
+to seek within the window, automatically managing buffer status.
+
+For example, one could open a SectorWindow of 4 512b sectors, starting at sector
+$2a. The buffer would begin holding sector $2a in its :buf(. The user could
+:seek relative address $212 within it, which would make it select sector $2b and
+return address $12 relative to :buf(. If the dirty flag is set, the buffer
+ensures that the contents is saved before loading a new one.
+
+Fields:
+
+drv Target Drive
+fsec First sector of the window
+cnt Number of sectors in the window
+sec Sector currently in buffer
+dirty Whether the buffer is dirty
+
+Words:
+
+:new ( drv -- secwin )
+ Create a new buffer targeting Drive "drv", on sector 0, cnt 1. Current buffer
+ starts uninitialized.
+
+:buf( ( self -- a )
+ Address of the buffer.
+
+:)buf ( self -- a )
+ End of the buffer.
+
+:move ( fsec cnt self -- )
+ Move window to "fsec" with "cnt" sectors. This doesn't change or flush the
+ current buffer.
+
+:load ( sec self -- )
+ Load sector "sec" in the buffer. Aborts if outside the window.
+
+:flush ( self -- )
+ If buffer is dirty, save it.
+
+:seek ( pos self -- a? n-or-0 )
+ Load, if needed, the correct sector so that the position "pos", in bytes
+ relative to the beginning of "fsec", is available in the buffer. Returns its
+ address in "a" as well as the number of bytes that can be read before the end
+ of the buffer is reaches, as "n". If "pos" is outside the window, "n" is 0 and
+ "a" is absent.
+
+:next ( self -- a? n-or-0 )
+ :seek the first byte of the sector following the one currently in the buffer.
+
+:dirty! ( self -- )
+ Make the buffer dirty.
A fs/lib/drive.fs => fs/lib/drive.fs +1 -0
@@ 0,0 1,1 @@
+?f<< /lib/drivelo.fs
A fs/lib/drivelo.fs => fs/lib/drivelo.fs +27 -0
@@ 0,0 1,27 @@
+struct[ SectorWindow
+ sfield drv
+ sfield fsec
+ sfield cnt
+ sfield sec
+ sfield dirty
+ SZ &+ :buf(
+ : :drv [compile] drv [compile] Drive ; immediate
+ : :)buf bi :buf( | drv Drive secsz + ;
+ : :dirty! 1 swap to dirty ;
+ : :flush ( self -- )
+ 0 over to@! dirty if tri sec | :buf( | :drv :sec! else drop then ;
+ : :load ( sec self -- )
+ 2dup sec <> if
+ 2dup tuck fsec - swap cnt >= if abort" secwin oor" then ( sec self )
+ dup :flush 2dup to sec tuck :buf( rot :drv :sec@ else 2drop then ;
+ : :move ( fsec cnt self -- ) swap 1 max over to cnt to fsec ;
+ : :seek ( pos self -- a? n-or-0 )
+ r! :drv secsz /mod ( off relsec ) \ V1=self
+ dup r@ cnt < if ( off relsec )
+ r@ fsec + r@ :load ( off )
+ bi r@ :buf( + | r> :drv secsz -^ ( a n )
+ else rdrop 2drop 0 then ;
+ : :next bi+ sec 1+ | :drv secsz * :seek ;
+ : :new ( drv -- secwin )
+ here >r dup , 0 , 1 , -1 , 0 , Drive secsz allot r> ;
+]struct
M fs/tests/lib/all.fs => fs/tests/lib/all.fs +1 -0
@@ 16,3 16,4 @@ f<< /tests/lib/tree.fs
f<< /tests/lib/fmt.fs
f<< /tests/lib/crc.fs
f<< /tests/lib/endian.fs
+f<< /tests/lib/drive.fs
A fs/tests/lib/drive.fs => fs/tests/lib/drive.fs +21 -0
@@ 0,0 1,21 @@
+?f<< /tests/harness.fs
+?f<< /drv/ramdrive.fs
+?f<< /lib/drive.fs
+testbegin
+\ Tests for lib/drive
+4 const TOTSEC
+$200 TOTSEC RAMDrive :new structbind RAMDrive mydrv
+$deadbeef mydrv :buf( !
+$cafebabe mydrv :buf( $22a + !
+
+mydrv :self SectorWindow :new structbind SectorWindow secwin
+0 secwin :seek $200 #eq @ $deadbeef #eq
+$22a secwin :seek 0 #eq \ outside window
+0 2 secwin :move
+$22a secwin :seek $200 $2a - #eq @ $cafebabe #eq
+$312 secwin :seek drop $12345678 swap !
+secwin :dirty!
+0 secwin :seek 2drop \ implicit flush
+mydrv :buf( $312 + @ $12345678 #eq
+mydrv :buf( $22a + @ $cafebabe #eq
+testend
M fs/tests/sys/file.fs => fs/tests/sys/file.fs +5 -4
@@ 66,9 66,10 @@ S" big.fs" mydir Path :newfile to mydst
S" /sys/file.fs" curpath :find# to mysrc
mydst mysrc Path :copyfile
mydst Path :info FSInfo size mysrc Path :info FSInfo size #eq
-\ copy a dir
+\ copy a dir. Note that TOTSEC can be too tight. The directory that is chosen is\
+\ one of the smaller ones so that we don't make the test too memory intensive.
S" dstdir" myroot :newdir value dstdir
-dstdir S" lib" curpath :find# Path :copydir
-S" dstdir/str.fs" myroot :find# Path :info FSInfo size
- S" /lib/str.fs" curpath :find# Path :info FSInfo size #eq
+dstdir S" gr" curpath :find# Path :copydir
+S" dstdir/rect.fs" myroot :find# Path :info FSInfo size
+ S" /gr/rect.fs" curpath :find# Path :info FSInfo size #eq
testend