{-----------------------------------------------------------------------+ | | | GTOS | | | | !!! Work in progress !!! | | | | Goal is to provide basic operating system services | | - Terminal | | - Card access | | - Extensibility | | | +-----------------------------------------------------------------------} gcl0x { XXX API and registers +-------+ +--//--+-----//------+-------+-------+ | GTOS | | | r0, r1, ... |vIRQ_PC|vIRQ_AC| +-------+ +--//--+-----//------+-------+-------+ vLR ^ ^ ^ vSP $e0 $100 .L2: LD vLR STW MyGtos ... LDI 0 CALL MyGtos Path/command sequences: name 0name <0:name >0:name >>0:name A:name A:name:command -------------------------- A>type file.txt hello gigatron A>_ -------------------------- Function codes: 0. Warm restart and reload Command.gt1 1. Read single console input with echo 2. Console output 3. X Read from reader 4. X Write to punch 5. X Write to list device 6. Read console input without echo 10. Read line buffer 11. Console ready? 12. Read list status 30. Set list echo status 31. Read list echo status Send char to console Read char from line buffer Open directory Walk through directory Open (after lookup) Read char from file Logical devices CON Console (in/out) RDR Paper tape reader device PUN Paper tape punch device LST List output device Physical devices TTY Teletype CRT Screen BAT Batch processing (??) UC1 User-defined console PTR Paper tape reader (high speed reader) UR1 UR2 PTP Paper tape punch LPT Line printer Function set in QIODevice https://doc.qt.io/archives/qt-4.8/qiodevice.html XXX Positive card detection: Read MISO's Invert Write to ZP Read MISO XXX https://cdn.hackaday.io/images/1744261554112919559.png XXX SYS extension for copying to and from object variables XXX LoadtGt1: proper closing of last sector XXX Compliancy: Reconsider pull-down resistors on MISO lines Some cards [30%?] [MMC?] have MISO in open collector during init" But if this is only during CMD0, we can live with it... XXX Compliancy: Follow cluster chain for directories as well XXX Speed: Transfer 256 bytes at once XXX Think about card detect and monitoring it (inserted = low) Put we can always use the MOSI line for this, or check the init status when opening a file XXX Figure out why Apple formats card with type 0x0b XXX Check CardType, reject byte addressing } {-----------------------------------------------------------------------+ | | | Variables | | | +-----------------------------------------------------------------------} zpReset=$c0 {Start GCL vars from $c0 instead of $30} _TermOut=$300 _OpenFile=$400 _Buffer=$500 {General purpose 512-byte buffer} _ClusterList=$7fa0 {Room for 96/4 = 24 clusters} { XXX These must move into an object Registers --------- ValueL,H XXXX 32-bit accumulator OffsetL,H XXXX Offset to be added to ValueL,H Sector reader ============= Variable Bytes Description -------- ----- ----------- ClusterBaseL,H ---- Cluster origin (hypothetical cluster 0) SectorL,H XXXX Block number of last sector CurrentDirL,H ---- First cluster of current directory ClusterSize X Sectors per cluster: 1, 2, ... 128 ClusterMask X XXX ClusterSize - 1 File reader =========== Variable Bytes Description -------- ----- ----------- FatBaseL,H ---- XXX FileSizeL,H ???? File size from directory entry FilePosL,H --XX Current R/W position in file: 0..FileSize } {-----------------------------------------------------------------------+ | RAM page 2 | +-----------------------------------------------------------------------} [ \romType, \romTypeValue_DEVROM- [if<0 {Version mismatch} \CharOut _r0= {Give error using just ROM v1 features} [def push _r0!] _r1= [def #10 `Need`DEVROM #0] _r1! ] \TermOut _dev1= {Closure uses CALLI_DEVROM} [def #10 `GTOS`DEV`32K #0] _dev1! { Reads MBR and finds the primary partition's start block Check if it is FAT32 } 0 SectorL= SectorH= {First sector on device} \ReadSector! {Read MBR} $6fe deek k= {Fetch signature} $aa55 k^ {Check signature} [if=0 $6c6 deek SectorL= {Primary partition's first sector on disk} $6c8 deek SectorH= $6c2 peek k= {Filesystem type code} $0b k^ [if<>0 $07^] {Accepts $0b and $0c} {XXX In case of $0b: check for CHS vs LBA ???} ] { Reads the first block of the partition. Mind that, despite it's name, this block doesn't hold the volume label: that is stored in the root directory instead. } \ReadSector! {SectorL,H as set by ReadMBR} $50b deek k= {Confirm expected sector length} $200 k^ {Only $200 is ok} { Calculate cluster base and set current directory to root ClusterBaseL,H = PartitionL,H from MBR + Reserved Sectors from VolumeId + (Number of FATs * FAT size) from VolumeId - 2 * ClusterSize from VolumeId } $50d peek {Sectors per cluster} ClusterSize= SectorL ValueL= {Partition's first sector, from MBR} SectorH ValueH= $50e deek OffsetL= {Number of reserved sectors} 0 OffsetH= \AddOffset! FatBaseL= ValueH FatBaseH= {Begin of primary FAT area} $524 deek OffsetL= {FAT size in sectors} $526 deek OffsetH= \AddOffset! \AddOffset! {Number of FATs is always 2} 0 ClusterSize- ClusterSize- {Subtract twice to arrive at ClusterBase} OffsetL= $ffff OffsetH= \AddOffset! ClusterBaseL= ValueH ClusterBaseH= { First cluster of root directory } $52c deek CurrentDirL= $52e deek CurrentDirH= \ReadDirectory! {Read root directory} [do loop] ] {-----------------------------------------------------------------------+ | RAM page 3 | +-----------------------------------------------------------------------} *=$300 {-----------------------------------------------------------------------+ | | | Video terminal section | | | +-----------------------------------------------------------------------} { Closure for writing to video terminal } _TermOut=* [ { Function } push \CharOut! { Variables } ##$7802 {Pos} ] { CharOut -- writes _r1 _sysFn _sysArgs[01245] } _CharOut=* [ _r0= [do _r0, if<>0 10^ [if<>0 {Test for CR} _vLR, 155- if>0 0] { or line wrap} [if=0 { Clear new line first } \sysArgs2. {Set all-zero output pattern} $20 \sysArgs0. {Blue} $e0+ _r1= {Video table} 255+ deek 255| 255^ {Go to what is now still the top text row} _sysArgs4= {Start of line for clearing} 2+ _vLR: {Indent by 2 pixels for text} \SYS_VDrawBits_134 _sysFn= {SYS call to draw 8 pixels vertically} [do 134!! <_sysArgs4++ {Step 1 pixel right} \sysArgs4, 160^ {Test for end of line} if<>0loop] { Then scroll up by modifying videoTable } [do _r1 16+ _r2= {_r2 looks 8 entries ahead of _r1} peek \sysArgs0. {Swap scanlines} _r1, _r2. \sysArgs0, _r1. <_r1++ <_r1++ {Advance to next entry in video table} <_r1, 224^ if<>0loop] {Until all done} ] { Draw ASCII character (>=32) on screen using the 5x8 pixel built-in font } _r0, 32- { XXX TODO 32- [if<0 127 else 96- {Map any non-ASCII to block symbol 127} if>=0 127] 127& } [if>=0 50- {Map ASCII code to offset in font table} [if<0 50+ _r1= &_font32up {First page for ASCII 32..81} else _r1= &_font82up] _r2= {Second page is ASCII 82..127} _r1 2<< _r1+ {Multiply by 5} _r2+ _r2= {Add page address to reach bitmap data} $3f20 _sysArgs0= {White on blue} _vLR; _sysArgs4= {Position of character} 6+ _vLR: {Advance position by 6 pixels for next call} \SYS_VDrawBits_134 _sysFn= {Prepare SYS calls} 5 [do _r1= {Loop to draw 5 vertical slices of 8 pixels} _r2 0?? \sysArgs2. {Get byte from ROM using `LUP 0' instruction} 134!! {Invoke SYS function to draw 8 vertical pixels} <_r2++ <_sysArgs4++ {Advance to next slice in ROM and on screen} _r1 1- if>0loop] {Looping} ] <_r0++ loop] pop ret {Closure return} ] {-----------------------------------------------------------------------+ | RAM page 4 | +-----------------------------------------------------------------------} *=$400 _OpenFile=* [ { Prepare for reading file whose directory entry is pointed at by p } push 0 FilePosL= FilePosH= {Reset position in file} p $1c+ deek FileSizeL= {Length of file in bytes} p $1e+ deek FileSizeH= p $1a+ deek ValueL= {First cluster for file} p $14+ deek ValueH= { \ReadClusterChain! } \ClusterToSector! pop ret ] _ReadSector=* [ { Read sector from card into memory (clobbers ValueL,H and OffsetL,H) } push \SYS_ExpanderControl_v4_40 {SYS function} _sysFn= $8078 40!! {Enable SPI0, keep MOSI high, bank=1} { CMD17 READ_SINGLE_BLOCK Reads a block of the size selected by the SET_BLOCKLEN command } [def #$51 #0 #0 #0 #0 #0] {CMD17} p= q= >SectorH, SectorL, 0loop] {Looping} \SendOnesToCard! {Read 16-bit checksum} \SendOnesToCard! \SYS_ExpanderControl_v4_40 {SYS function} _sysFn= $807c 40!! {Disable SPI0, keep MOSI high, bank=1} pop ret ] {-----------------------------------------------------------------------+ | | | Memory card section | | | +-----------------------------------------------------------------------} {-----------------------------------------------------------------------+ | RAM page $7f | +-----------------------------------------------------------------------} *=$7fa0 { Bus ROM v4+ --- -------- A0 SCLK A1 (unused) A2 /SS0 A3 /SS1 A4 /SS2 A5 /SS3 A6 B0 A7 B1 A8-A14 (unused) A15 MOSI } _SendOnesToCard=* [ 255 \sysArgs6. {Place byte in exchange buffer} \sysArgs6 _sysArgs0= {Begin} 1+ _sysArgs2= {End} \SYS_SpiExchangeBytes_v4_134 {SYS function} _sysFn= 134!! {Exchanges a single byte} \sysArgs6, {Reply byte} ret ] {-----------------------------------------------------------------------+ | RAM page $7e | +-----------------------------------------------------------------------} *=$7ea0 _SendCommandToCard=* [ push { Setup command bytes } p= \Buffer q= 255 q. 0loop] {Looping} { Send to SPI device } \Buffer _sysArgs0= {Begin} 8+ _sysArgs2= {End and overwrite exchange buffer} \SYS_SpiExchangeBytes_v4_134 {SYS function} _sysFn= 134!! pop ret ] _WaitForCardReply=* [ push { | """To receive this message, your program should continuously toggle | the SD CLK signal and observe the MISO line for data, while keeping | the MOSI line high and the CS line low. Your program can detect the | message, because every message begins with a 0 bit, and when the | SD card sends no data it keeps the MISO line high.""" | | """Note that the response to each command is sent by the card a few | SD CLK cycles later. If the expected response is not received within | 16 clock cycles after sending the reset command, the reset command | has to be sent again.""" } 8 [do i= {Poll for upto 8 reply bytes} \SendOnesToCard! {Keep MOSI line high by only sending ones} 128& {Note: communication is byte-aligned} if<>0 {Break out when valid message detected} i 1- if>0loop] {Or when loop counter exhausted} \sysArgs6, {Return reply from card} pop ret ] {-----------------------------------------------------------------------+ | | | FAT32 section | | | +-----------------------------------------------------------------------} {-----------------------------------------------------------------------+ | RAM page $7d | +-----------------------------------------------------------------------} *=$7da0 _ReadDirectory=* [ { Read first sector of current directory } push { Sector = ClusterBase + CurrentDir * ClusterSize } CurrentDirL ValueL= CurrentDirH ValueH= \ClusterToSector! \ReadSector! ValueH, 15& >ValueH. {Clear the top 4 reserved bits} 1 [do k= ClusterSize- if<0 \ShiftLeft! k k+ loop] ClusterBaseL OffsetL= ClusterBaseH OffsetH= \AddOffset! ValueL SectorL= {Set as next sector to read} ValueH SectorH= pop ret ] {-----------------------------------------------------------------------+ | RAM page $7c | +-----------------------------------------------------------------------} *=$7ca0 _ShiftLeft=* [ { Shift left ValueL,H by 1 bit (clobbers OffsetL,H) } push ValueL OffsetL= {Double value} ValueH OffsetH= \AddOffset! OffsetH {Return old high word} pop ret ] _AddOffset=* [ { Add 32-bit OffsetL,H to 32-bit ValueL,H and store result there } ValueL OffsetL^ [if<0 {Compare lower halves' most significant bits} ValueL {MSB unequal: carry taken from their sum} else $8000 {MSB equal: carry taken from either term} ] OffsetL+ {Carry now in MSB of vAC (inverted)} [if>=0 ValueH 1+ ValueH=] {Apply carry to upper half} ValueH OffsetH+ ValueH= {Sum upper half} ValueL OffsetL+ ValueL= {Sum lower half and return this as well} ret ] {-----------------------------------------------------------------------+ | | | File reading section | | | +-----------------------------------------------------------------------} {-----------------------------------------------------------------------+ | RAM page $7b | +-----------------------------------------------------------------------} *=$7ba0 {*** some FAT functions ***} _NextSector=* [def push 0 {Optionally test low word, ignore bit 0:2} \NextCluster! loop] \ClusterList {Reset} List= deek ValueL= List 2+ deek ValueH= pop ret ] {-----------------------------------------------------------------------+ | RAM page $79 | +-----------------------------------------------------------------------} *=$79a0 _NextCluster=* [ { Find next cluster in FAT } push >ValueL, > 8} OffsetL. >ValueH, OffsetH= > 7} FatBaseL OffsetL= {Value += First sector for FAT} FatBaseH OffsetH= \AddOffset! SectorL^ {Compare sector number to what we already have} [if=0 ValueH SectorH^] [if<>0 ValueL SectorL= {Read new sector from FAT area} ValueH SectorH= \ReadSector! ] m 127& 2<< m= {Index in FAT sector at 32-bit word i & $7f} $500 m+ deek ValueL= {Fetch next cluster number in the chain} $502 m+ deek ValueH= pop ret ] {-----------------------------------------------------------------------+ | RAM page $78 | +-----------------------------------------------------------------------} *=$78a0 { Byte reader vAC input Uses _r0.._r2 variables Execution address _r0 output } _LoadGt1=* [ { Load GT1 file into RAM and return start address. See Docs/GT1-files.txt for details on GT1 XXX Not robust against early EOF XXX Final EOF must be checked by caller } push _r1= {Byte reader} { Read file contents byte by byte } _r1! {Fetch first byte} [do {Chunk copy loop} >_r0. {High-address comes first} _r1! <_r0. {Then the low address} _r1! {Byte count (0 means 256)} [do {Byte copy loop} <_r2. {Implicitly chops counter to 8 bits} _r1! _r0. {Poke next byte into memory} <_r0++ {Advance write pointer in page} <_r2, 1- {Decrement counter} if<>0loop] _r1! {Go to next block} if<>0loop] _r1! >_r0. {Load execution address} _r1! <_r0. pop ret ] _ByteLoader=* [ $700 _r3^ [if=0 [do loop] {XXX Read next sector and/or cluster} ] _r3,