1566 lines
48 KiB
Plaintext
1566 lines
48 KiB
Plaintext
|
|
{-----------------------------------------------------------------------+
|
|
| CardBoot |
|
|
| |
|
|
| This has been modified by lb3361 to make the boot process faster |
|
|
| and more reliable. No attempt was made to make this code usable |
|
|
| for anything but booting from SD with minimal footprint. |
|
|
| |
|
|
+-----------------------------------------------------------------------}
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| |
|
|
| 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
|
|
|
|
{
|
|
??? Positive card detection: ( Read MISO's/Invert/Write to ZP/Read MIOS )
|
|
WONT Support loading of segments in zero page
|
|
WONT 64K systems: put code in highest pages ($ffff and down)
|
|
WONT 32K systems: put code in bottom screen memory ($7f9f and down)
|
|
OK On the matter of scrolling: When to restore the video table?
|
|
OK LoadtGt1: better closing of sector
|
|
OK Compliancy: Byte addressing for V2 cards when CCS bit in CMD58 OCR is 0
|
|
NO Compliancy: MMC v3 card detection with CMD1
|
|
OK Compliancy: Don't set block size with CMD16 if CCS bit in OCS is 1
|
|
OK 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...
|
|
OK Compliancy: Follow cluster chain for directories as well (LB3361: DONE)
|
|
OK Speed: Transfer 256 bytes at once
|
|
WONT Think about card detect and monitoring it (inserted = low)
|
|
NA Figure out why Apple formats card with type 0x0b
|
|
|
|
References:
|
|
http://elm-chan.org/docs/mmc/mmc_e.html
|
|
http://www.dejazzer.com/ee379/lecture_notes/lec12_sd_card.pdf
|
|
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
|
|
Ptr XX Pointer to the current byte in an open file
|
|
Len 0X Number of bytes valid at Ptr in current page
|
|
|
|
Memory usage
|
|
------------
|
|
$58a0 $7bff Cardboot code
|
|
$7c00 $7cff Page filled with $FF for SpiExchangeBytes
|
|
$7d00 $7eff Sector buffer
|
|
$7f00 $7fff Cluster list
|
|
}
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| RAM page 2 |
|
|
+-----------------------------------------------------------------------}
|
|
|
|
\romType, \romTypeValue_ROMv5- {Version check >= ROMv5}
|
|
[if<0 do _frameCount _vPCH: loop]
|
|
|
|
\SectorBuffer Address= {512 bytes memory for one sector}
|
|
|
|
{ Make a black screen.
|
|
- Set videoTop to hide the top two lines.
|
|
- Clear the rest of the screen with a black background. }
|
|
|
|
\videoTop_v5 pVideoTop=
|
|
$0 \ResetScreen!
|
|
$20 pVideoTop.
|
|
|
|
{ Uncomment this line to hide the buffers $7c00-$7fff }
|
|
|
|
$1e8 i= [do $7b i. i 2+ i= $1f0 i^ if<>0loop]
|
|
|
|
{ Fill page $7c with $ff for SYS_SpiExchangeBytes }
|
|
|
|
\FFPage \sysArgs3. $ff \sysArgs1: 0 \sysArgs0. 54!!
|
|
|
|
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|}$58a0 _vLR= [ret]{ RAM page $58 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$58a0
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| |
|
|
| Memory card section |
|
|
| |
|
|
+-----------------------------------------------------------------------}
|
|
|
|
{ CMD0: GO_IDLE_STATE }
|
|
[def _CMD0=*
|
|
{
|
|
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
|
|
]
|
|
|
|
[def _CMD0Retries=*
|
|
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}
|
|
pop ret]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $59 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$59a0
|
|
|
|
{ CMD8: SEND_IF_COND }
|
|
[def _CMD8=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $5a |
|
|
+-----------------------------------------------------------------------}
|
|
*=$5aa0
|
|
|
|
{ CMD58: READ_OCR }
|
|
[def _CMD58=*
|
|
push
|
|
{[def `CMD58` #0] \PrintText!}
|
|
\EnableCard!
|
|
[def #$7a #0 #0 #0 #0 #0] {CMD58}
|
|
\SendCommandToCard!
|
|
\WaitForCardReply!
|
|
\SendOnesToCard! {R3 response}
|
|
$40& [if<>0 4 CardType=] {CCS bit signals card is SDXC/SDHC}
|
|
\SendOnesToCard!
|
|
\SendOnesToCard!
|
|
CardReply 254& {\PrintResult!} {Only 0 and 1 mean success}
|
|
pop ret
|
|
]
|
|
|
|
{ CMD55: APP_CMD }
|
|
[def _CMD55=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $5b |
|
|
+-----------------------------------------------------------------------}
|
|
*=$5ba0
|
|
|
|
{ ACMD41 SD_SEND_OP_COND }
|
|
[def _ACMD41=*
|
|
{
|
|
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
|
|
]
|
|
|
|
[def _ACMD41Retries=*
|
|
push
|
|
[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}
|
|
pop ret
|
|
]
|
|
|
|
{ CMD16: SET_BLOCKLEN }
|
|
[def _CMD16=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $5c |
|
|
+-----------------------------------------------------------------------}
|
|
*=$5ca0
|
|
|
|
{ CMD17: READ_SINGLE_BLOCK }
|
|
[def _CMD17=*
|
|
{
|
|
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=
|
|
SectorL ValueL=
|
|
SectorH ValueH=
|
|
CardType 2- [if<=0 \SectorToByte!] {Version 1.X cards do byte addressing}
|
|
>ValueH, <q++ q. {Put SectorL,H in argument, big-endian order}
|
|
<ValueH, <q++ q.
|
|
>ValueL, <q++ q.
|
|
<ValueL, <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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_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 _EnableCard=*
|
|
\SYS_ExpanderControl_v4_40 {SYS function}
|
|
_sysFn= $8078 40!! {Enable SPI0, keep MOSI high, bank=1}
|
|
ret
|
|
]
|
|
|
|
{ \DisableCard }
|
|
[def _DisableCard=*
|
|
\SYS_ExpanderControl_v4_40 {SYS function}
|
|
_sysFn= $807c 40!! {Disable SPI0, keep MOSI high, bank=1}
|
|
ret
|
|
]
|
|
|
|
{ \SendOnesToCard }
|
|
[def _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
|
|
]
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $5e |
|
|
+-----------------------------------------------------------------------}
|
|
*=$5ea0
|
|
|
|
{ \UpdateCrc16 -- COMMENTED OUT
|
|
|
|
[def _UpdateCrc16=*
|
|
{
|
|
Update checksum for 1 byte in vACL.
|
|
One caveat with SDC/MMC checksums is that reading all-zeroes
|
|
still results in a valid check.
|
|
This is no longer used.
|
|
}
|
|
#\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
|
|
]
|
|
}
|
|
|
|
{ \CheckBootBlock }
|
|
[def _CheckBootBlock=*
|
|
{
|
|
Return 0 for potential MBR, 1 for potential FAT32 boot block
|
|
}
|
|
[def `FAT32``` #0 ] q=
|
|
$52 Address+ i=
|
|
[do
|
|
q, <q++ k= [if=0 1 ret]
|
|
i, <i++ k^ if=0loop]
|
|
$1fe Address+ deek k=
|
|
$aa55 k^
|
|
ret
|
|
]
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $5f |
|
|
+-----------------------------------------------------------------------}
|
|
*=$5fa0
|
|
|
|
{ \SendCommandToCard }
|
|
[def _SendCommandToCard=*
|
|
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
|
|
]
|
|
|
|
{ \WaitForCardReply }
|
|
[def _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}
|
|
|
|
{32 \PrintChar! {Space}}
|
|
\sysArgs6, CardReply= {Store reply from card}
|
|
{\PrintByte! {Print hex value}
|
|
CardReply {As return value}}
|
|
pop ret
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $60 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$60a0
|
|
|
|
{ \InitCard }
|
|
[def _InitCard=*
|
|
{
|
|
Put card in SPI mode and set block size
|
|
}
|
|
push
|
|
\CMD0Retries! {No more than 1 second}
|
|
\CMD8! {Detect version 1.X or later}
|
|
\ACMD41Retries! {No more than 2 seconds}
|
|
CardType 2- [if=0 \CMD58!] {Detect standard capacity or SDHC/SDXC}
|
|
CardType 2- [if<=0 \CMD16!] {Set block size to 512 bytes}
|
|
\DisableCard! {Initialisation completed}
|
|
pop ret
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| |
|
|
| FAT32 section |
|
|
| |
|
|
+-----------------------------------------------------------------------}
|
|
|
|
{ \ReadVolumeId }
|
|
[def _ReadVolumeId=*
|
|
{
|
|
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. (LB) In fact this routine just checks that
|
|
we have a good looking FAT32 boot block with an
|
|
appropriate logical sector size.
|
|
}
|
|
push
|
|
\ReadSector! {SectorL,H as set by ReadMBR}
|
|
[def `FAT32` #0] \PrintText! {Check FAT32 block}
|
|
\CheckBootBlock! 1^
|
|
[if=0
|
|
$00b Address+ deek k= 512 k^ {Confirm expected sector length}
|
|
]
|
|
\PrintResult!
|
|
pop ret
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $61 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$61a0
|
|
|
|
{ \ReadMBR }
|
|
[def _ReadMBR=*
|
|
{
|
|
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!
|
|
\CheckBootBlock! {Return 0 if potential MBR}
|
|
[if=0
|
|
$1c6 Address+ deek SectorL= {Primary partition's first sector on disk}
|
|
$1c8 Address+ deek SectorH=
|
|
$1c2 Address+ peek k= {Filesystem type code}
|
|
else
|
|
$ff {FF when not a MBR}
|
|
]
|
|
\PrintByte!
|
|
$0b k^ [if<>0 $07^] {Accepts $0b and $0c}
|
|
pop ret
|
|
]
|
|
|
|
{ \CompCluster }
|
|
[def _CompCluster=*
|
|
ClusterSize=
|
|
ClusterSize+ 1- \vACH. $ff| ClusterMask=
|
|
ret]
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $62 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$62a0
|
|
|
|
{ \InitFat32 }
|
|
[def _InitFat32=*
|
|
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 \CompCluster! {Sectors per cluster}
|
|
|
|
SectorL ValueL= {Partition's first sector, from MBR}
|
|
SectorH ValueH=
|
|
|
|
$00e Address+ deek OffsetL= {Number of reserved sectors}
|
|
\AddSmallOffset!
|
|
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $63 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$63a0
|
|
|
|
|
|
{ \ReadSector }
|
|
[def _ReadSector=*
|
|
{
|
|
Read sector from card into memory (clobbers ValueL,H and OffsetL,H)
|
|
}
|
|
push
|
|
\EnableCard!
|
|
\CMD17! {Request block of data}
|
|
\sysArgs6, $fe^
|
|
[if=0
|
|
\SYS_SpiExchangeBytes_v4_134 \sysFn:
|
|
$7c00 \sysArgs0:
|
|
Address \sysArgs2:
|
|
134!! { Read first 256 bytes}
|
|
<\sysArgs3++
|
|
134!!
|
|
\SendOnesToCard! {Skip 16-bit checksum}
|
|
\SendOnesToCard!
|
|
\DisableCard!
|
|
0 {Success}
|
|
]
|
|
pop ret
|
|
]
|
|
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $64 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$64a0
|
|
|
|
{ \LoadData }
|
|
[def _LoadData=*
|
|
{
|
|
Makes Ptr point to available data.
|
|
Returns number of bytes available at this address
|
|
before reaching the end of the page
|
|
}
|
|
push
|
|
FilePosH FileSizeH^ [if=0 FilePosL FileSizeL^ \vACH,]
|
|
[if=0 FileSizeL else FilePosL $ff| 1+]
|
|
FilePosL- Len=
|
|
[if<>0
|
|
$1ff FilePosL& {Read new sector}
|
|
[if<>0
|
|
Address+ {Pointer position}
|
|
else
|
|
\ReadSector! {Reads next sector into buffer}
|
|
[if=0
|
|
\NextSector! {and determines next sector}
|
|
{0 <Pos. FilePosH \PrintWord! FilePosL \PrintWord!}
|
|
Address
|
|
else {error}
|
|
FilePosH FileSizeH= FilePosL FileSizeL=
|
|
0 ] ] ]
|
|
Ptr= Len
|
|
pop ret
|
|
]
|
|
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $65 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$65a0
|
|
|
|
{ \OpenFile }
|
|
[def _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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| |
|
|
| 32-bit arithmetic section |
|
|
| |
|
|
+-----------------------------------------------------------------------}
|
|
|
|
{ \ClusterToSector }
|
|
[def _ClusterToSector=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $66 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$66a0
|
|
|
|
{ \SectorToByte, \ShiftLeft }
|
|
[def
|
|
_SectorToByte=*
|
|
{
|
|
Multiply 32-bit ValueL,H by 512 (clobbers OffsetL,H)
|
|
}
|
|
push
|
|
<ValueH, >ValueH. {First shift left by one byte}
|
|
>ValueL, <ValueH.
|
|
<ValueL, >ValueL.
|
|
0 <ValueL.
|
|
|
|
_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
|
|
]
|
|
|
|
{ \AddSmallOffset \AddOffset }
|
|
[def
|
|
_AddSmallOffset=*
|
|
{
|
|
Add 16-bit OffsetL to 32-bit ValueL,H and store result there
|
|
}
|
|
0 OffsetH=
|
|
|
|
_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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $67 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$67a0
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| |
|
|
| Video terminal section |
|
|
| |
|
|
+-----------------------------------------------------------------------}
|
|
|
|
{ \SafePrintChar }
|
|
[def _SafePrintChar=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{ \PrintValue }
|
|
[def _PrintValue=*
|
|
{
|
|
Print 32-bit ValueL,H in hexadecimal
|
|
}
|
|
push
|
|
ValueH \PrintWord!
|
|
ValueL \PrintWord!
|
|
\Newline!
|
|
pop ret
|
|
]
|
|
|
|
{ \PrintWord }
|
|
[def _PrintWord=*
|
|
{
|
|
Print 16-bit word in hexadecimal
|
|
}
|
|
push
|
|
k=
|
|
>k, \PrintByte!
|
|
<k, \PrintByte!
|
|
pop ret
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $68 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$68a0
|
|
|
|
{ \PrintByte }
|
|
[def _PrintByte=*
|
|
{
|
|
Print byte value in vAC as hexadecimal number
|
|
}
|
|
push
|
|
2-- %0=
|
|
4<< \vACH, \PrintHexDigit! {High nibble}
|
|
%0 2++ \PrintHexDigit! {Low nibble}
|
|
pop ret
|
|
]
|
|
|
|
{ \PrintResult }
|
|
[def _PrintResult=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{ \PrintHexDigit }
|
|
[def _PrintHexDigit=*
|
|
{
|
|
Print lowest nibble from vAC as hex digit
|
|
}
|
|
push
|
|
15&
|
|
10- [if<0 $3a+ else $41+] \PrintChar!
|
|
pop ret
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $69 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$69a0
|
|
|
|
{ \PrintText -- writes q }
|
|
[def _PrintText=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{ \PrintVolumeLabel }
|
|
[def _PrintVolumeLabel=*
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $6a |
|
|
+-----------------------------------------------------------------------}
|
|
*=$6aa0
|
|
|
|
{ \PrintTwoDecimals }
|
|
[def _PrintTwoDecimals=*
|
|
push
|
|
Number=
|
|
$30 k= {Do print leading zeroes}
|
|
10 \PrintDigit! {Print tens}
|
|
Number $30+ \PrintChar! {Print ones}
|
|
pop ret
|
|
]
|
|
|
|
{ \PrintDate -- reads p, writes i j k q Pos _sysFn _sysArgs[01245] }
|
|
[def _PrintDate=*
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $6b |
|
|
+-----------------------------------------------------------------------}
|
|
*=$6ba0
|
|
|
|
{ \PrintDirEntry -- reads p k, writes i j k q Pos _sysFn _sysArgs[01245] }
|
|
[def _PrintDirEntry=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{ \PrintName }
|
|
[def _PrintName=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $6c |
|
|
+-----------------------------------------------------------------------}
|
|
*=$6ca0
|
|
|
|
{ \PrintDigit -- reads k Value, writes i j k Number Pos _sysFn _sysArgs[01245] }
|
|
[def _PrintDigit=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $6d |
|
|
+-----------------------------------------------------------------------}
|
|
*=$6da0
|
|
|
|
{ \Newline -- writes i Pos _sysFn _sysArgs[01245] }
|
|
{ !!! This version scrolls only pixel row 16 through 111 !!! }
|
|
[def _Newline=*
|
|
{Clear new line first}
|
|
$3f00 _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. 192 {Last entry at $100+238, minus 30, minus 16}
|
|
[do
|
|
30+ <i.
|
|
i, 112- [if<0 120+
|
|
else 24+] i. {Rotate by 8 in 24..119 range}
|
|
<i, 32- if>0loop] {Move to previous entry in video table}
|
|
|
|
ret
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $6e |
|
|
+-----------------------------------------------------------------------}
|
|
*=$6ea0
|
|
|
|
{ \PrintChar -- reads Pos, writes i j Pos _sysFn _sysArgs[01245] }
|
|
[def _PrintChar=*
|
|
{
|
|
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}
|
|
$3f00 _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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $6f |
|
|
+-----------------------------------------------------------------------}
|
|
*=$6fa0
|
|
|
|
{ \PrintDirectory }
|
|
[def _PrintDirectory=*
|
|
{
|
|
Print directory contents
|
|
Execute SYSTEM.GT1 if found
|
|
}
|
|
push
|
|
[do
|
|
\LoadData!
|
|
Ptr 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}
|
|
208 pVideoTop. {Blank many lines for speed}
|
|
\LoadGt1! {Load file}
|
|
\Execute! {Execute otherwise}
|
|
]
|
|
]
|
|
]
|
|
32 \IncFilePos! {Next slot}
|
|
loop]
|
|
pop ret
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $70 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$70a0
|
|
|
|
{ \PrintSize }
|
|
[def _PrintSize=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $71 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$71a0
|
|
|
|
{ \ValueToDecimal }
|
|
[def _ValueToDecimal=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $72 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$72a0
|
|
|
|
[def `---------- #0] Buffer= {String space for 32-bit unsigned decimal}
|
|
|
|
{ ResetScreen }
|
|
[def _ResetScreen=*
|
|
{
|
|
Reset screen with vAC color and regular videoTable
|
|
Still save the top two lines
|
|
}
|
|
push \sysArgs1.
|
|
\SYS_SetMemory_v2_54 \sysFn:
|
|
$18 i= $120 k=
|
|
[do i k. \sysArgs3. 0 \sysArgs2. $A0 \sysArgs0. 54!!
|
|
1 i+ i= 2 k+ k= $1f0 k^ if<>0loop]
|
|
0 pVideoTop.
|
|
pop ret
|
|
]
|
|
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| |
|
|
| File reading section |
|
|
| |
|
|
+-----------------------------------------------------------------------}
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $73 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$73a0
|
|
|
|
{ \LoadGt1 }
|
|
[def _LoadGt1=*
|
|
{
|
|
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}
|
|
>caddr. {High-address comes first}
|
|
\LoadByte! <caddr. {Then the low address}
|
|
{caddr \PrintWord!}
|
|
\LoadByte! 1- 255& 1+ clen=
|
|
{\PrintByte! \Newline!}
|
|
\CopyGt1Segment!
|
|
clen if=0 {Check we did it}
|
|
\LoadByte! {Go to next block}
|
|
if<>0loop]
|
|
\LoadByte! >caddr. {Load execution address}
|
|
\LoadByte! <caddr.
|
|
|
|
\LoadByte! [if>=0 0 caddr=] {Expect EOF, clear caddr when missing}
|
|
|
|
\DisableCard!
|
|
|
|
pop ret
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $74 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$74a0
|
|
|
|
{ \IsBootGt1 }
|
|
[def _IsBootGt1=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{ \Execute }
|
|
[def _Execute=*
|
|
$20 \ResetScreen! {Restore video table}
|
|
caddr
|
|
[do if=0loop] {Stop if not executable}
|
|
call {Run...}
|
|
]
|
|
|
|
{ \IncSector }
|
|
[def _IncSector=*
|
|
1 SectorL+ SectorL= {To next sector}
|
|
[if=0 1 SectorH+ SectorH=]
|
|
ret
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $75 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$75a0
|
|
|
|
{ \NextSector }
|
|
[def _NextSector=*
|
|
push
|
|
\IncSector! {Next sector}
|
|
$200 FilePosL+ ClusterMask&
|
|
[if=0
|
|
List 4+ List= deek ValueL= {Get next cluster from ClusterList}
|
|
List 2+ deek ValueH=
|
|
\ClusterToSector!
|
|
]
|
|
pop ret
|
|
]
|
|
|
|
{ \IncFilePos }
|
|
[def _IncFilePos=*
|
|
{
|
|
Increment file position by vAC bytes (strong carry assumptions here)
|
|
Side effect: store vAC in i
|
|
}
|
|
i= FilePosL+ FilePosL=
|
|
[if=0 1 FilePosH+ FilePosH= ]
|
|
Len i- Len=
|
|
ret
|
|
]
|
|
|
|
{ \LoadByte }
|
|
[def _LoadByte=*
|
|
push
|
|
{
|
|
Returns next byte or -1 if EOF
|
|
}
|
|
\LoadData!
|
|
[if>0
|
|
1 \IncFilePos!
|
|
Ptr,
|
|
else
|
|
-1]
|
|
pop ret
|
|
]
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $76 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$76a0
|
|
|
|
{ \ReadClusterChain }
|
|
[def _ReadClusterChain=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $77 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$77a0
|
|
|
|
{ \NextCluster }
|
|
[def _NextCluster=*
|
|
{
|
|
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
|
|
]
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $78 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$78a0
|
|
|
|
{ \CopyGt1Segment }
|
|
[def _CopyGt1Segment=*
|
|
|
|
{
|
|
Copy data for segment caddr,caddr+clen
|
|
}
|
|
push
|
|
[do
|
|
\LoadData! if>0
|
|
clen- [if>0 0] clen+ \IncFilePos!
|
|
Ptr \sysArgs2:
|
|
caddr \sysArgs0: i+ caddr=
|
|
\SYS_CopyMemory_v6_80 \sysFn:
|
|
i 80!!
|
|
clen i- clen=
|
|
if>0loop]
|
|
pop ret
|
|
]
|
|
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $79 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$79a0
|
|
|
|
{ \OpenMainDirectory }
|
|
[def _OpenMainDirectory=*
|
|
{
|
|
Prepare for reading main directory
|
|
}
|
|
push
|
|
0 FilePosL= FilePosH= {Reset position in file}
|
|
0 FileSizeL= 1 FileSizeH= {Excessive length}
|
|
CurrentDirL ValueL=
|
|
CurrentDirH ValueH=
|
|
\ReadClusterChain!
|
|
\ClusterToSector!
|
|
pop ret
|
|
]
|
|
|
|
{ \PrintCardType }
|
|
[def _PrintCardType=*
|
|
{
|
|
Show detected card version
|
|
}
|
|
push
|
|
[def `CardType` #0] \PrintText!
|
|
CardType \PrintByte! \Newline!
|
|
pop ret
|
|
]
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
|} >_vLR++ [ret] { RAM page $79 |
|
|
+-----------------------------------------------------------------------}
|
|
*=$7aa0
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| |
|
|
| Main program |
|
|
| |
|
|
+-----------------------------------------------------------------------}
|
|
|
|
|
|
[def #10 `***`CardBoot #10 #0] \PrintText!
|
|
0 \frameCount. {Reset timer}
|
|
\InitCard!
|
|
\PrintCardType!
|
|
\ReadMBR! \PrintResult! {Master Boot Record}
|
|
\ReadVolumeId! {Read first block of FAT partition}
|
|
[if=0
|
|
\InitFat32!
|
|
\OpenMainDirectory! {Read root directory XXX Move into PrintDir}
|
|
\PrintDirectory! {List directory, find SYSTEM.GT1 and execute it}
|
|
]
|
|
32 pVideoTop. {Not found!}
|
|
\Newline!
|
|
[def `SYSTEM.GT1`not`found! #0 ] \PrintText!
|
|
##\HALT {Halt if not found}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| RAM page $7c...7f |
|
|
+-----------------------------------------------------------------------}
|
|
|
|
_FFPage=$7c
|
|
_SectorBuffer=$7d00 {512 bytes}
|
|
_ClusterList=$7f00 {Room for 256/4 = 64 clusters = 32KB}
|
|
|
|
_SYS_CopyMemory_v6_80=$e9
|
|
|
|
{-----------------------------------------------------------------------+
|
|
| |
|
|
+-----------------------------------------------------------------------}
|