~vdupras/duskos

372317f468ddec958424588d948a063ce8fac45e — Virgil Dupras 20 days ago c13c5be
lib/drive: new unit

See doc/lib/drive
8 files changed, 140 insertions(+), 33 deletions(-)

M Makefile
D fs/doc/drive.txt
A fs/doc/lib/drive.txt
A fs/lib/drive.fs
A fs/lib/drivelo.fs
M fs/tests/lib/all.fs
A fs/tests/lib/drive.fs
M fs/tests/sys/file.fs
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