gigatron/rom/Apps/CardBoot/CardBoot_v1.gcl
2025-01-28 19:17:01 +03:00

1481 lines
47 KiB
Plaintext

{-----------------------------------------------------------------------+
| |
| CardBoot |
| |
| !!! Work in progress !!! |
| |
| The goal is to connect to a FAT32-formatted SDC/MMC card |
| over the SPI interface, and then read its root directory. |
| If there is a SYSTEM.GT1 file, load and execute it. |
| |
+-----------------------------------------------------------------------}
gcl0x
{
XXX Positive card detection:
Read MISO's
Invert
Write to ZP
Read MIOS
XXX Support loading of segments in zero page
- Bricks 82.ef 110 bytes
- Tetronis a0.ef 80 bytes
- Frogstroll 82.ef 110 bytes
Approach 1: Through page 1
Kludge but very simple.
Visual artifacts are transient, and only with certain files
We could also map it to a different page, e.g. one
that will be scrolled out anyway
Approach 2: Decoding after load
Faster loading, but 2nd pass needed
Can't hide visual artifacts, even for simple files
Approach 3: Reduce footprint with CALLI and move variables to $c0
Lots of work, but this is a good idea anyway
XXX 64K systems: put code in highest pages ($ffff and down)
--> Conflicts with Apple-1 (WozMon)
XXX 32K systems: put code in bottom screen memory ($7f9f and down)
--> Conflicts with Apple-1. So how about $800.$1000
16*160 = 2560 bytes
Con: It overwrites *** Gigatron 32K *** --> So be it
We can always copy that to the bottom of screen memory (silly)
Dynamically mak jump table at $f8: if you use it, ALLOC -8
$FE PrintChar, Newline, Open, LoadByte, ...
XXX On the matter of scrolling: When to restore the video table?
A: When not loading to $503
XXX DIR -> Builtin command. Only names
XXX LIST -> External command: file size, date, volume label
XXX CLS
XXX STAT
XXX TYPE command (consider passing of ARGS)
XXX Device letters or not? A: B: C:
XXX https://cdn.hackaday.io/images/1744261554112919559.png
XXX LoadtGt1: better closing of sector
XXX Compliancy: Byte addressing for V2 cards when CCS bit in CMD58 OCR is 0
XXX Compliancy: MMC v3 card detection with CMD1
XXX Compliancy: Don't set block size with CMD16 if CCS bit in OCS is 1
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
References:
http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf
SPI and SD cards
https://electronics.stackexchange.com/questions/303745/sd-card-initialization-problem-cmd8-wrong-response
SD card initialization problem - CMD8 wrong response
http://www.rjhcoding.com/avrc-sd-interface-1.php
http://www.rjhcoding.com/avrc-sd-interface-2.php
http://www.rjhcoding.com/avrc-sd-interface-3.php
http://www.rjhcoding.com/avrc-sd-interface-4.php
Interfacing an SD Card Part 1-4
https://www.pjrc.com/tech/8051/ide/fat32.html
Understanding FAT32 Filesystems
https://bits4device.wordpress.com/2017/12/16/sd-crc7-crc16-implementation/
CRC definition
}
{-----------------------------------------------------------------------+
| |
| Variables |
| |
+-----------------------------------------------------------------------}
{
Variable Bytes Description
-------- ----- -----------
CardType X Detected card version
Address XX RAM for transfer buffer
ValueL,H XXXX 32-bit accumulator
OffsetL,H XXXX Offset to be added to ValueL,H
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
ClusterBaseL,H ---- Cluster origin (hypothetical cluster 0)
FatBaseL,H ---- XXX
FileSizeL,H ???? File size from directory entry
FilePosL,H --XX Current R/W position in file: 0..FileSize
Checksum ---- CRC16 checksum over sector data
Memory usage
------------
$500 $6ff Buffer for reading FAT sectors
$58a0 $xxff Code not needed for GT1 reading
Eg. detecting the card settings
$xxa0 $78ff GT1 reading core
$7fa0 ... Cluster list
}
{-----------------------------------------------------------------------+
| RAM page 2 |
+-----------------------------------------------------------------------}
{
The version check below is a BUG of ROM v5a, and the reason that
ROM v5a won't boot from an SD card
}
\romType, \romTypeValue_DEVROM- {Version check >= DEVROM}
[if<0 do _frameCount _vPCH: loop]
$500 Address= {512 bytes memory for one sector}
{-----------------------------------------------------------------------+
|}$58a0 _vLR= [ret]{ RAM page $58 |
+-----------------------------------------------------------------------}
*=$58a0
{-----------------------------------------------------------------------+
| |
| Memory card section |
| |
+-----------------------------------------------------------------------}
{ CMD0: GO_IDLE_STATE }
[def
{
Resets the SD Memory Card
}
push
[def `CMD0`` #0] PrintText!
0 CardType=
{
| """To communicate with the SD card, your program has to place the SD
| card into the SPI mode. To do this, set the MOSI and CS lines to
| logic value 1 and toggle SD CLK for at least 74 cycles."""
}
10 [do i= {Lets do 10 bytes for 80 cycles}
SendOnesToCard! {Keep MOSI line high by only sending ones}
i 1- if>0loop]
{
| """After the 74 cycles (or more) have occurred, your program
| should set the CS line to 0 and send the command CMD0:
| 01.000000 00000000 00000000 00000000 00000000 1001010.1
| This is the reset command, which puts the SD card into the SPI
| mode if executed when the CS line is low."""
}
EnableCard! {Sets /SS0 to 0}
[def #$40 #0 #0 #0 #0 #$95] {CMD0 Reset}
SendCommandToCard!
{
| """The SD card will respond to the reset command by sending a basic
| 8-bit response on the MISO line. The structure of this response is
| shown in Fig.6. The first bit is always a 0, while the other bits
| specify any errors that may have occured when processing the last
| message. If the command you sent was successfully received, then
| you will receive the message (00000001)_2.
|
| 7 6 5 4 3 2 1 0
| +---+---+---+---+---+---+---+---+
| | 0 | | | | | | | |
| +---+---+---+---+---+---+---+---+
| ^ ^ ^ ^ ^ ^ ^
| | | | | | | `--------in idle state
| | | | | | `------------erase state
| | | | | `----------------illegal command
| | | | `--------------------CRC error
| | | `------------------------erase sequence error
| | `----------------------------address error
| `--------------------------------parameter error
| Figure 6 Format of a basic 8-bit Response to every command in SPI mode"""
}
WaitForCardReply!
1^ PrintResult! {Only 1 means success}
pop ret
] CMD0=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $59 |
+-----------------------------------------------------------------------}
*=$59a0
{ CMD8: SEND_IF_COND }
[def
{
Send interface condition
}
push
[def `CMD8`` #0] PrintText!
{
| """Following a successful reset, test if your system can successfully
| communicate with the SD card by sending a different command. For
| example, send one of the following commands, while keeping the CS
| at value 0: (i) Command CMD8
| 01.001000 00000000 00000000 00000001 10101010 1000011.1
| (Fixed CRC -marcelk ^^^^^^^)
|
| This command is only available in the latest cards, compatible with
| SD card Specifications version 2.0. For most older cards this command
| should fail and cause the SD card to respond with a message that
| this command is illegal.
| (ii) Command CMD58
| 01.111010 00000000 00000000 00000000 00000000 0111010.1
| This command requests the contents of the operating conditions
| register for the connected card.
|
| A response to these commands consists of 40 bits (see Fig.7), where
| the first 8 bits are identical to the basic 8-bit response, while
| the remaining 32 bits contain specific information about the SD
| card. Although the actual contents of the remaining 32 bits are not
| important for this discussion, a valid response indicates that your
| command was transmitted and processed successfully. If successful,
| the first 8 bits of the response will be either 00000001 or 00000101
| depending on the version of your SD card.
|
| 39 38 37 36 35 34 33 32 31...28 27 .. 12 11 .. 8 7 .. 0
| +--+--+--+--+--+--+--+--+-------+--------+-------+-------------+
| | 0| | | | | | | |Version|Reserved|Voltage|Check Pattern|
| +--+--+--+--+--+--+--+--+-------+--------+-------+-------------+
| ^ ^ ^ ^ ^ ^ ^
| | | | | | | `-----in idle state
| | | | | | `--------erase state
| | | | | `-----------illegal command
| | | | `--------------CRC error
| | | `-----------------erase sequence error
| | `--------------------address error
| `-----------------------parameter error
| Figure 7 The format of the 40-bit Response."""
}
EnableCard!
[def #$48 #0 #0 #1 #$aa #$87] {CMD8 Get Version, voltage 2.7-3.6V}
SendCommandToCard!
WaitForCardReply!
$ff^ [if<>0
CardReply 4& [if<>0
1 CardType= {Version 1}
else
SendOnesToCard! {R7 response}
SendOnesToCard!
SendOnesToCard!
SendOnesToCard!
$aa^ [if=0 {If pattern response is correct}
2 CardType= {Version 2}
]
]
]
CardReply 250& PrintResult! {Return result: 0, 1, 4 or 5 means success}
pop ret
] CMD8=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $5a |
+-----------------------------------------------------------------------}
*=$5aa0
{ CMD58: READ_OCR }
[def
push
[def `CMD58` #0] PrintText!
EnableCard!
[def #$7a #0 #0 #0 #0 #0] {CMD58}
SendCommandToCard!
WaitForCardReply!
SendOnesToCard! {R3 response}
SendOnesToCard!
SendOnesToCard!
$c0& $c0^ [if=0 4 CardType=] {CCS bit signals card is SDXC/SDHC}
CardReply 254& PrintResult! {Only 0 and 1 mean success}
pop ret
] CMD58=
{ CMD55: APP_CMD }
[def
{
Defines to the card that the next command is an application specific
command rather than a standard command
}
push
[def `CMD55` #0] PrintText!
[def #$77 #0 #0 #0 #0 #0] {CMD55}
SendCommandToCard!
WaitForCardReply!
254& PrintResult! {Only 0 and 1 mean success}
pop ret
] CMD55=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $5b |
+-----------------------------------------------------------------------}
*=$5ba0
{ ACMD41 SD_SEND_OP_COND }
[def
{
Sends hosts capacity support information and activates the card's
initialization process. Reserved bits shall be set to '0'
}
push
[def `ACMD41 #0] PrintText!
CardType 1^ [if=0
[def #$69 #0 #0 #0 #0 #0] {ACMD41 for version 1.X}
else
[def #$69 #$40 #0 #0 #0 #0] {ACMD41 for version 2.0+}
]
SendCommandToCard!
WaitForCardReply!
254& PrintResult! {Only 0 and 1 mean success}
pop ret
] ACMD41=
{ CMD16: SET_BLOCKLEN }
[def
{
Set block size to 512 bytes
}
push
[def `CMD16` #0] PrintText!
[def #$50 #0 #0 #$02 #0 #0] {CMD16}
SendCommandToCard!
WaitForCardReply!
254& PrintResult! {Only 0 and 1 mean success}
pop ret
] CMD16=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $5c |
+-----------------------------------------------------------------------}
*=$5ca0
{ CMD17: READ_SINGLE_BLOCK }
[def
{
Reads a block of the size selected by the SET_BLOCKLEN command
}
push
{[def `CMD17` #0] PrintText!}
[def #$51 #0 #0 #0 #0 #0] {CMD17}
p= q=
>SectorH, <q++ q. {Put SectorL,H in argument, big-endian order}
<SectorH, <q++ q.
>SectorL, <q++ q.
<SectorL, <q++ q.
p SendCommandToCard!
WaitForCardReply!
254& {PrintResult!} {Only 0 and 1 mean success}
[if=0
{Wait for first data byte}
{[def `Wait``` #0] PrintText!}
[do
SendOnesToCard! {XXX Can we use WaitForCardReply here?}
$ff^ if=0loop] {XXX Loop needs a timeout}
{\sysArgs6, PrintByte!}
{\sysArgs6, $fe^ PrintResult! {Only $fe is OK}}
]
pop ret
] CMD17=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $5d |
+-----------------------------------------------------------------------}
*=$5da0
{
Bus ROM v4+
--- --------
A0 SCLK
A1 (unused)
A2 /SS0
A3 /SS1
A4 /SS2
A5 /SS3
A6 B0
A7 B1
A8-A14 (unused)
A15 MOSI
}
{ EnableCard }
[def
\SYS_ExpanderControl_v4_40 {SYS function}
_sysFn= $8078 40!! {Enable SPI0, keep MOSI high, bank=1}
ret
] EnableCard=
{ DisableCard }
[def
\SYS_ExpanderControl_v4_40 {SYS function}
_sysFn= $807c 40!! {Disable SPI0, keep MOSI high, bank=1}
ret
] DisableCard=
{ SendOnesToCard }
[def
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
] SendOnesToCard=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $5e |
+-----------------------------------------------------------------------}
*=$5ea0
{ UpdateCrc16 }
[def
{
Update checksum for 1 byte in vACL.
One caveat with SDC/MMC checksums is that reading all-zeroes
still results in a valid check.
}
#\XORW #>Checksum {Inline assembly: XOR with Checksum's high byte}
>Checksum.
8 [do i= {Loop for 8 bits}
Checksum [if<0 {If bit 15 is 1}
Checksum+ Checksum= {Shift left}
$1021 Checksum^ {Apply CRC16-CCITT polynomial}
else {Else bit 15 is 0}
Checksum+] {Shift left}
Checksum= {And store result}
i 1- if>0loop]
ret
] UpdateCrc16=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $5f |
+-----------------------------------------------------------------------}
*=$5fa0
{ SendCommandToCard }
[def
push
{ Setup command bytes }
p= Buffer q=
255 q. <q++ q. <q++ {Start with two dummy bytes}
6 [do i= {Copy 6 command bytes to exchange buffer}
p, <p++ {Fetch byte and advance read pointer}
q. <q++ {Store byte and advance write pointer}
i 1- if>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
] SendCommandToCard=
{ WaitForCardReply }
[def
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}
{32 PrintChar! {Space}}
\sysArgs6, CardReply= {Store reply from card}
PrintByte! {Print hex value}
CardReply {As return value}
pop ret
] WaitForCardReply=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $60 |
+-----------------------------------------------------------------------}
*=$60a0
{ InitCard }
[def
{
Put card in SPI mode and set block size
}
push
[do CMD0! {Put card in SPI mode and enable card}
if<>0 DisableCard! {Retry on failure}
\frameCount, 60- if<0loop] {No more than 1 second}
CMD8! {Detect version 1.X or later}
[do
{ TODO: "In SPI mode CMD1 and ACMD41 have the same behavior" }
CMD55! ACMD41! {Query condition}
CardReply if<>0 {Until the card is initialised}
\frameCount, 120- if<0loop] {No more than 2 seconds}
CardType 2^ [if=0 CMD58!] {Detect standard capacity or SDHC/SDXC}
CMD16! {Set block size to 512 bytes}
DisableCard! {Initialisation completed}
pop ret
] InitCard=
{-----------------------------------------------------------------------+
| |
| FAT32 section |
| |
+-----------------------------------------------------------------------}
{ ReadVolumeId }
[def
{
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.
}
push
ReadSector! {SectorL,H as set by ReadMBR}
[def `Vol.ID` #0] PrintText!
$00b Address+ deek k= {Confirm expected sector length}
PrintWord! {Print as little-endian word}
512 k^ PrintResult!
pop ret
] ReadVolumeId=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $61 |
+-----------------------------------------------------------------------}
*=$61a0
{ ReadMBR }
[def
{
Reads MBR and finds the primary partition's start block
Check if it is FAT32
}
push
0 SectorL= SectorH= {First sector on device}
ReadSector! {Read MBR}
[def `MBR` #0] PrintText!
$1fe Address+ deek k= {Fetch signature}
$aa55 k^ {Check signature}
[if=0
$1c6 Address+ deek SectorL= {Primary partition's first sector on disk}
$1c8 Address+ deek SectorH=
$1c2 Address+ peek k= {Filesystem type code}
PrintByte!
$0b k^ [if<>0 $07^] {Accepts $0b and $0c}
{XXX In case of $0b: check for CHS vs LBA ???}
]
pop ret
] ReadMBR=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $62 |
+-----------------------------------------------------------------------}
*=$62a0
{ InitFat32 }
[def
push
{
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
}
$00d Address+ peek {Sectors per cluster}
ClusterSize=
SectorL ValueL= {Partition's first sector, from MBR}
SectorH ValueH=
$00e Address+ deek OffsetL= {Number of reserved sectors}
0 OffsetH=
AddOffset!
FatBaseL=
ValueH FatBaseH= {Begin of primary FAT area}
$024 Address+ deek OffsetL= {FAT size in sectors}
$026 Address+ 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 }
$02c Address+ deek CurrentDirL=
$02e Address+ deek CurrentDirH=
pop ret
] {InitFat32=}
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $63 |
+-----------------------------------------------------------------------}
*=$63a0
InitFat32=
{ OpenSector }
[def
{
Tell card to read sector, but don't process the data yet
}
push
EnableCard!
CardType 2-
[if<0 SectorToByte!] {Version 1.X cards do byte addressing}
CMD17! {Request block of data}
0 Checksum= {Reset CRC16 checksum}
pop ret
] OpenSector=
{ ReadSector }
[def
{
Read sector from card into memory (clobbers ValueL,H and OffsetL,H)
}
push
OpenSector! {Start data stream}
Address q= {Setup write pointer}
0 Checksum=
512 [do k= {Number of bytes to read}
SendOnesToCard! {XXX Read directly into buffer}
q. {Store byte in buffer}
UpdateCrc16!
q 1+ q= {Advance write pointer, cross page boundaries}
k 1- if>0loop] {Looping}
SendOnesToCard! UpdateCrc16! {Read 16-bit checksum}
SendOnesToCard! UpdateCrc16!
DisableCard!
Checksum {CRC16 will be zero on success}
pop ret
] ReadSector=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $64 |
+-----------------------------------------------------------------------}
*=$64a0
{ LoadByte }
[def
{
Read next byte directly from file and advance position
!!! XXX This is slow, but doesn't use buffer space !!!
}
push
FilePosL FileSizeL^ {Check for EOF}
[if=0 FilePosH FileSizeH^]
[if<>0 {More to read}
$1ff FilePosL& {Read new sector}
[if=0
SendOnesToCard! {Ignore checksum}
SendOnesToCard!
OpenSector! {Open next sector}
NextSector!]
FilePosL 1+ FilePosL= {Increment position in file}
[if=0 FilePosH 1+ FilePosH=]
SendOnesToCard! {Returns the next data byte}
else
1- {Negative value for EOF}
]
pop ret
] LoadByte=
{ ReadDirectory }
[def
{
Read first sector of current directory
}
push
{ Sector = ClusterBase + CurrentDir * ClusterSize }
CurrentDirL ValueL=
CurrentDirH ValueH=
ClusterToSector!
ReadSector!
<SectorL++ {XXX Replace with ClusterList}
pop ret
] ReadDirectory=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $65 |
+-----------------------------------------------------------------------}
*=$65a0
{ OpenFile }
[def
{
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
] OpenFile=
{-----------------------------------------------------------------------+
| |
| 32-bit arithmetic section |
| |
+-----------------------------------------------------------------------}
{ ClusterToSector }
[def
{
Multiply ValueL,H by ClusterSize and add ClusterBase (clobbers OffsetL,H)
}
push
>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
] ClusterToSector=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $66 |
+-----------------------------------------------------------------------}
*=$66a0
{ SectorToByte }
[def
{
Multiply 32-bit ValueL,H by 512 (clobbers OffsetL,H)
XXX Figure out what to do with this! This CAN'T be correct any longer
}
push
0 <OffsetL. {First shift left by one byte}
<ValueL, >OffsetL.
>ValueL, <OffsetH.
<ValueH, >OffsetH.
OffsetL ValueL= {Then double once}
OffsetH ValueH= AddOffset!
pop ret
] SectorToByte=
{ ShiftLeft }
[def
{
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
] ShiftLeft=
{ AddOffset }
[def
{
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
] {AddOffset=}
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $67 |
+-----------------------------------------------------------------------}
*=$67a0
AddOffset=
{-----------------------------------------------------------------------+
| |
| Video terminal section |
| |
+-----------------------------------------------------------------------}
{ SafePrintChar }
[def
{
Print any byte value as character, map non-ASCII to block symbol
XXX Clear 6th slice
XXX Insert Newline when wrapping the line
}
push
32- [if<0 127 else 96- {Map any non-ASCII to block symbol 127}
if>=0 127] 127&
PrintChar! {And print it}
pop ret
] SafePrintChar=
{ PrintValue }
[def
{
Print 32-bit ValueL,H in hexadecimal
}
push
ValueH PrintWord!
ValueL PrintWord!
Newline!
pop ret
] PrintValue=
{ PrintWord }
[def
{
Print 16-bit word in hexadecimal
}
push
k=
>k, PrintByte!
<k, PrintByte!
pop ret
] PrintWord=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $68 |
+-----------------------------------------------------------------------}
*=$68a0
{ PrintByte }
[def
{
Print byte value in vAC as hexadecimal number
}
push
2-- %0=
4<< \vACH, PrintHexDigit! {High nibble}
%0 2++ PrintHexDigit! {Low nibble}
pop ret
] PrintByte=
{ PrintResult }
[def
{
Print OK or FAILED
}
push
k= {Preserve vAC for caller}
[if=0
[def ``OK #10 #0]
else
[def ``FAILED #10 #0]
] PrintText!
k
pop ret
] PrintResult=
{ PrintHexDigit }
[def
{
Print lowest nibble from vAC as hex digit
}
push
15&
10- [if<0 $3a+ else $41+] PrintChar!
pop ret
] PrintHexDigit=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $69 |
+-----------------------------------------------------------------------}
*=$69a0
{ PrintText -- writes q }
[def
{
Print text string, may include newline characters (#10)
Returns 0
}
push {Save vLR because this is not a leaf subroutine}
q=
[do {Loop over characters}
q, if<>0 {Next character to be printed, unless 0}
10^ [if<>0 10^ PrintChar! {Print the character and advance cursor}
else Newline!] {Or go to next line}
<q++ loop] {Advance text pointer and loop}
pop ret
] PrintText=
{ PrintVolumeLabel }
[def
push
[def `Volume` #0] PrintText!
{ Volume label 11 characters }
p q= {Read pointer}
11 [do k= {Loop over 12 positions}
q, <q++ {Print the next character}
SafePrintChar!
k 1- if>0loop]
Newline!
pop ret
] PrintVolumeLabel=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $6a |
+-----------------------------------------------------------------------}
*=$6aa0
{ PrintTwoDecimals }
[def
push
Number=
$30 k= {Do print leading zeroes}
10 PrintDigit! {Print tens}
Number $30+ PrintChar! {Print ones}
pop ret
] PrintTwoDecimals=
{ PrintDate -- reads p, writes i j k q Pos _sysFn _sysArgs[01245] }
[def
push
\SYS_LSRW1_48 _sysFn= {Prepare 1-bit right shift}
p 17+ peek {Select year, origin is 1980}
48!! 20- [if<0 100+]
PrintTwoDecimals!
\SYS_LSRW5_50 _sysFn= {Prepare 5-bit shift right}
p 16+ deek 50!! 15& {Select month 1-12}
PrintTwoDecimals!
p 16+ peek 31& {Select day 1-31}
PrintTwoDecimals!
pop ret
] PrintDate=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $6b |
+-----------------------------------------------------------------------}
*=$6ba0
{ PrintDirEntry -- reads p k, writes i j k q Pos _sysFn _sysArgs[01245] }
[def
{
Format directory entry as "YYMMDD <size> FILENAME.EXT"
}
push
PrintDate! {YYMMDD}
4 Pos+ Pos= {Small space}
PrintSize! {File size or directory indicator}
4 Pos+ Pos= {Small space}
PrintName! {FILENAME.EXT}
Newline! {End of line}
pop ret
] PrintDirEntry=
{ PrintName }
[def
{
Print 8.3 filename
}
push
p q= {Read pointer}
11 [do k= {Loop over 11 positions}
3^ [if=0
q, 32^ if<>0 {If there is an extension}
$2e PrintChar!] {Period '.' before extension}
q, <q++
32^ [if<>0 32^ {Skip spaces (padding)}
SafePrintChar!] {Print the next character}
k 1- if>0loop]
pop ret
] PrintName=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $6c |
+-----------------------------------------------------------------------}
*=$6ca0
{ PrintDigit -- reads k Value, writes i j k Number Pos _sysFn _sysArgs[01245] }
[def
{
Extract and print decimal digit or leading space
}
push
i= {Radix as argument, keep in i}
Number [do {Calculate Value/Radix}
i- {Subtract i}
if>=0 {As many times as fits}
Number=
<k++ {Increment 0..9 times}
loop]
k [if=0 {If leading zero digit}
32 PrintChar! {Space}
else
$30| PrintChar! {Map to $30..$39 range and print digit}
$30 k= {And mark all further zeroes as non-leading}
]
pop ret
] PrintDigit=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $6d |
+-----------------------------------------------------------------------}
*=$6da0
{ Newline -- writes i Pos _sysFn _sysArgs[01245] }
{ !!! This version scrolls only pixel row 16 through 119 !!! }
[def
{Clear new line first}
$3f20 _sysArgs0= {White on blue}
0 <Pos. $120 peek >Pos. {Go to start of next line}
Pos _sysArgs4= {Set screen position}
\sysArgs2. {Set all-zero output pattern}
[do
\SYS_VDrawBits_134 {SYS call to draw 8 pixels vertically}
_sysFn= 134!!
<_sysArgs4++ {Step 1 pixel right}
\sysArgs4, 160^ {Test for end of line}
if<>0loop]
{Then scroll up by modifying videoTable}
$01 >i. 208 {Last entry at $100+238, minus 30}
[do
30+ <i.
i, 120- [if<0 128+
else 24+] i. {Rotate by 8 in 24..127 range}
<i, 32- if>0loop] {Move to previous entry in video table}
ret
] Newline=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $6e |
+-----------------------------------------------------------------------}
*=$6ea0
{ PrintChar -- reads Pos, writes i j Pos _sysFn _sysArgs[01245] }
[def
{
Print ASCII character (>=32) on screen using the 5x8 pixel built-in font.
This is a bare bones version:
1. No handling of newline characters
2. No implicit linewrapping
3. No blanking of the 1 pixel horizontal space between characters
}
82- {Map ASCII code to offset in font table}
[if<0 50+ i= &_font32up {First page for ASCII 32..81}
else i= &_font82up] j= {Second page is ASCII 82..127}
i 2<< i+ {Multiply by 5}
j+ j= {Add page address to reach bitmap data}
$3f20 _sysArgs0= {White on blue}
Pos _sysArgs4= {Position of character}
6+ Pos= {Advance position by 6 pixels for next call}
\SYS_VDrawBits_134 _sysFn= {Prepare SYS calls}
5 [do i= {Loop to draw 5 vertical slices of 8 pixels}
j 0? \sysArgs2. {Get byte from ROM using `LUP 0' instruction}
134!! {Invoke SYS function to draw 8 vertical pixels}
<j++ <_sysArgs4++ {Advance to next slice in ROM and on screen}
i 1- if>0loop] {Looping}
ret
] PrintChar=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $6f |
+-----------------------------------------------------------------------}
*=$6fa0
{ PrintDirectory }
[def
{
Print directory contents
}
push
Address [do p= {Loop over all directory entries}
$200 Address+ p-
[if<=0 {If past the end of the segment}
ReadSector! {Then read more bytes from device}
<SectorL++ {XXX Replace with NextCluster/ClusterList}
Address loop] {And try again}
p peek if<>0 {Test for end of directory marker}
$e5^ [if<>0 {Skip unused entries}
p $0b+ peek k= {Fetch attributes}
$0f^ if<>0 {Skip long filename information}
2& if<>0 {Skip hidden entries}
k 8& [if<>0
PrintVolumeLabel! {Print label}
else
PrintDirEntry! {Print entry}
IsBootGt1! {Are we looking for this file?}
[if=0 {File found}
LoadGt1! {Load file}
Execute! {Execute otherwise}
]
]
]
p 32+ loop] {Hop to next slot}
pop ret
] PrintDirectory=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $70 |
+-----------------------------------------------------------------------}
*=$70a0
{ PrintSize }
[def
{
Print size in slot as 32-bit decimal ("%5lu")
or as " <DIR>" for directory slots
}
push
p $0b+ peek 16& [if<>0
[def ```<DIR> #0] {Directory}
else
p 28+ deek ValueL= {File size in bytes}
p 30+ deek ValueH=
ValueToDecimal! {Base conversion}
0 j=
8 [do i= {Loop over no more than 9 digits}
q, $30^ if=0 {Still a leading zero?}
i 6- [if<0 32 q. {Convert last 5 into a space}
else <j++] {But drop all earlier zeroes}
<q++
i 1- if>=0loop]
Buffer j+
]
PrintText!
pop ret
] PrintSize=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $71 |
+-----------------------------------------------------------------------}
*=$71a0
{ ValueToDecimal }
[def
{
Convert unsigned 32-bit ValueL,H to 10-digit decimal string
}
push
9 [do i= {For every position i}
Buffer i+ q= $30 q. {Buffer[i] = '0'}
i 1- if>=0loop]
31 [do j= {For every bit}
ShiftLeft! {ValueL,H <<= 1}
[if>=0 $6a {No carry}
else $69] k= {Carry}
9 [do i= {Double the decimal result with carry}
Buffer i+ q= {q = &Buffer[i]}
q, 1<< k- {Double the next decimal with carry}
[if<0 $3a+ q. $6a {No carry}
else $30+ q. $69] k= {Carry}
i 1- if>=0loop]
j 1- if>=0loop]
pop ret
] {ValueToDecimal=}
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $72 |
+-----------------------------------------------------------------------}
*=$72a0
ValueToDecimal=
[def `---------- #0] Buffer= {String space for 32-bit unsigned decimal}
{-----------------------------------------------------------------------+
| |
| File reading section |
| |
+-----------------------------------------------------------------------}
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $73 |
+-----------------------------------------------------------------------}
*=$73a0
{ LoadGt1 }
[def
{
Load GT1 file into RAM and store start address in Address.
See Docs/GT1-files.txt for details on GT1
XXX Support loading into audio channels
}
push
[def `Loading` #0] PrintText!
PrintName!
OpenFile! {Prepare for reading}
{ Read file contents byte by byte }
LoadByte! {Fetch first byte}
[do {Chunk copy loop}
>Address. {High-address comes first}
LoadByte! <Address. {Then the low address}
LoadByte! {Byte count (0 means 256)}
[do {Byte copy loop}
\sysArgs5. {Implicitly chops counter to 8 bits}
LoadByte! Address. {Poke next byte into memory}
<Address++ {Advance write pointer in page}
\sysArgs5, 1- {Decrement counter}
if<>0loop]
LoadByte! {Go to next block}
if<>0loop]
LoadByte! >Address. {Load execution address}
LoadByte! <Address.
LoadByte! [if>=0 0 Address=] {Expect EOF, clear Address when missing}
512 [do i= {Read tail of sector}
SendOnesToCard!
i 1- if>0loop]
DisableCard!
pop ret
] LoadGt1=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $74 |
+-----------------------------------------------------------------------}
*=$74a0
{ IsBootGt1 }
[def
{
Check if directrory entry p is SYSTEM.GT1
Returns 0 when name matches, non-zero otherwise
}
[def `SYSTEM``GT1 #0] q= {String to match}
p i=
[do
q, <q++ if<>0 j= {Compare until nul terminator}
i, <i++ j^ if=0loop] {Loop while equal}
ret
] IsBootGt1=
{ Execute }
[def
120 [do i= {Restore video table}
i+ $fe+ p=
i 7+ p. 8- if>0loop]
Address
[do if=0loop] {Stop if not executable}
call {Run...}
] Execute=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $75 |
+-----------------------------------------------------------------------}
*=$75a0
{*** some FAT functions ***}
{ NextSector }
[def
push
<SectorL++ {To next sector}
$1fff FileSizeL& {XXX Hardcoded. Should derive from ClusterSize}
[if=0
List 4+ List= deek ValueL= {Get next cluster from ClusterList}
List 2+ deek ValueH=
ClusterToSector!
]
pop ret
] NextSector=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $76 |
+-----------------------------------------------------------------------}
*=$76a0
{ ReadClusterChain }
[def
{
Traverse the FAT and collect the clusters used by this file
XXX TODO Don't do this if the file is smaller than the cluster size
}
push
\ClusterList List= {Reset}
Newline!
PrintValue!
[do
{ Store in list }
ValueL List: <List++ <List++
ValueH List: <List++ <List++
{ Break at End Of Cluster marker }
$f000 ValueH| 1+ [if=0 {Test high word first, ignore bit 28:31}
$0007 ValueL| 1+] if<>0 {Optionally test low word, ignore bit 0:2}
NextCluster!
PrintValue!
loop]
\ClusterList {Reset}
List= deek ValueL=
List 2+ deek ValueH=
pop ret
] ReadClusterChain=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $77 |
+-----------------------------------------------------------------------}
*=$77a0
{ NextCluster }
[def
{
Find next cluster in FAT
}
push
>ValueL, <OffsetL. {Offset = Cluster >> 8}
<ValueH, >OffsetL.
>ValueH, OffsetH=
<ValueL, m= {Park the lowest 8 bits in m}
128& peek ValueL= {Value = 1 if bit7 else 0}
0 ValueH=
AddOffset! AddOffset! {Value += 2 * Offset: now we have Cluster >> 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<< Address+ {Pointer in FAT sector at 32-bit word i & $7f}
m= deek ValueL= {Fetch next cluster number in the chain}
m 2+ deek ValueH=
pop ret
] NextCluster=
{-----------------------------------------------------------------------+
|} >_vLR++ [ret] { RAM page $78 |
+-----------------------------------------------------------------------}
*=$78a0
{-----------------------------------------------------------------------+
| |
| Main program |
| |
+-----------------------------------------------------------------------}
[def #10 `***`Memory`card #10 #0] PrintText!
0 \frameCount. {Reset timer}
InitCard!
[def `CardType` #0] PrintText!
CardType PrintByte! Newline! {Show detected card version}
ReadMBR! PrintResult! {Master Boot Record}
[if=0 ReadVolumeId!] {Read first block of FAT partition}
InitFat32!
ReadDirectory! {Read root directory XXX Move into PrintDir}
[if=0 PrintDirectory!] {List directory, find SYSTEM.GT1 and execute it}
##\HALT {Halt if not found}
{-----------------------------------------------------------------------+
| RAM page $7f |
+-----------------------------------------------------------------------}
_ClusterList=$7fa0 {Room for 96/4 = 24 clusters}
{-----------------------------------------------------------------------+
| |
+-----------------------------------------------------------------------}