Quite some time ago, in October 2011, I built a simple Z80 based computer with an IDE interface embedded Forth interpreter, etc. This machine is described in more detail here. Since the overall system occupies a 10 inch enclosure and is completely wire-wrapped, it is not as portable as I wished it was. Thus I decided to re-implement it and design a real printed circuit board using EAGLE - the free version of this CAD system allows one to design boards of up to 100 mm X 80 mm in size which is exactly the size I envisioned for this new Z80 system.
This small size required some sacrifices - most notably there is longer an IDE-subsystem and the only IO-port is a serial line with which the Z80 board can be connected to a terminal or even better a host computer. This small computer is extremely simple to build, the printed circuit boards can be manufactured based on the EAGLE files enclosed below and using the builtin Forth interpreter is really fun.
The overall system is shown on the left. From left to right the bottom row of ICs contains a 16C550 UART (serial line interface), a Z80B processor running at a whopping 6 MHz, 32 kB of RAM (62256) and 32 kB EPROM (27C512) containing the monitor and the Forth interpreter. Above the RAM and ROM are the two TTL quartz oscillators for the UART (1.8432 MHz) and the CPU (6 MHz) and, on the far right, a 74LS32 for address decoding. The remaining parts are a MAX232 line driver for the serial line (next to the six small electrolytic capacitors) and a 74HCT14 which is mainly used for the reset circuitry (any 74xx14 should do fine here).
On the upper left, the power supply connector can be seen (a simple floppy disk like jack) with the tiny yellow reset switch just below. The ribbon cable connected on the upper right is the serial line.
The schematic of this board is shown on the right (click on the image to get a larger and readable version). In the center, the Z80 CPU can be seen. The reset circuit, which consists of an 10 uF electrolytic capacitor which is loaded via a 10k resistor and feeds two Schmitt trigger inverter gates of the 74HCT14, can be seen on the left of the drawing. When the reset switch is depressed, the capacitor is discharged and a suitable reset pulse is supplied to the CPU (active low) and the UART (active high).
The 16 bit address space of the CPU is divided into two 32 kB areas by means of A15. The lower 32 kB from $0000 to $7FFF are occupied by ROM (27C512 EPROM) while the upper 32 kB from $8000 to $FFFF are RAM area. The UART 16C550 with its associated level converter MAX232 can be seen in the lower right corner of the schematic.
Based on this schematic a printed circuit board was created using EAGLE's auto-router (as you can see I was lazy and did not take any special care of power supply lines since there are so few components which need no special precautions).
There are many companies which can create a board based on the Z80_mini.brd file which can be found in this ZIP-file. My board was made by Jackaltac and I am very satisfied by the result. No, I am not affiliated with this company, nor do I intend to make any advertising - it is just that I have made some good experiences with their services.
The overall system requires a 5 V power supply at about 120 mA with the parts specified above and running at 6 MHz.
A computer without software is useless, so I needed at least a simple EPROM based monitor for this computer to work with it at all. This monitor is based on the monitor of the predecessor Z80-system, the Tiny Z80 system mentioned above.
Writing a monitor turned out to be really fun and what once started with the goal of something capable of loading and running programs from Hex files became a monitor with embedded Forth interpreter (I ported Brad Rodriguez' CAMEL Forth) and many other useful features. The monitor intended for the Tiny Z80 system contains routines for an IDE interface - these routines are also included in the monitor for the Z80_mini described here, but can not be used due to the missing hardware!
I use the zasm assembler to assembly the monitor sources and produce an Intel-Hex file suitable for an EPROM programmer. The complete sources of the monitor can be found here. This ZIP-archive contains a README.txt file with additional information on how to build a monitor EPROM image file. If you just want to program a 27C512 EPROM with version 0.14a of the monitor (which is the latest version as of 28-JUN-2013) you can download a suitable Intel-Hex file here. If you have no EPROM programmer you can send me a mail - see below - and ask me to program it for you given that you send me an empty 27(C)512 EPROM and the necessary postage and packaging to return it to you.
On power on, the monitor prints this welcome-message:
Simple Z80-monitor - V 0.14a (B. Ulmann, September 2011 - June 2013)
This monitor contains Brad Rodriguez' CAMEL Forth,
John Kerr's Z80 disassembler
-------------------------------------------------------------------------------
Z>
The command "language" is quite simple: All commands are grouped in so-called "command groups". Each such group is selected by a single letter and contains commands which are select by a second single letter. So there is no need to press "ENTER" as in conventional shells or type long commands. If a command requires parameters it prompts the user to enter all required values. There is one command group containing only one command so that a single letter, instead of two as written above, is sufficient to execute this command which is the HELP-command:
Z> HELP: Known command groups and commands:
C(ontrol group):
C(old start), I(nfo), S(tart), W(arm start)
D(isk group):
I(nfo), M(ount), T(ransfer), U(nmount)
R(ead), W(rite)
F(ile group):
C(at), D(irectory), L(oad), R(un)
H(elp)
M(emory group):
(dis)A(ssemble), D(ump), E(xamine), F(ill), I(ntel Hex Load),
L(oad), R(egister dump),
S(ubsystem group):
F(orth)
It should be noted again that all commands regarding disk and file IO will not work and will most likely crash the system since there is no IDE controller. I was too lazy to surgically remove all of the IDE support which is quite scattered within the monitor.
The following example shows the use of a command which requires a start and an end address to work - a disassembler listing covering the memory area from $0000 to $0050 is generated (the disassembler has been developed by John Kerr):
Z> MEMORY/DISASSEMBLE: START=0000 END=0050
0000 F3 DI
0001 31 3B FB LD SP,0FB3BH
0004 18 17 JR 1DH
0006 00 NOP
0007 00 NOP
0008 C5 PUSH BC
0009 E5 PUSH HL
000A DD E5 PUSH IX
000C E1 POP HL
000D 29 ADD HL,HL
000E 01 4C 1D LD BC,1D4CH
0011 09 ADD HL,BC
0012 4E LD C,(HL)
0013 23 INC HL
0014 46 LD B,(HL)
0015 2B DEC HL
0016 C5 PUSH BC
0017 DD E1 POP IX
0019 E1 POP HL
001A C1 POP BC
001B DD E9 JP (IX)
001D 3E 80 LD A,80H
001F D3 03 OUT (3),A
0021 3E 0C LD A,0CH
0023 D3 00 OUT (0),A
0025 AF XOR A
0026 D3 01 OUT (1),A
0028 3E 03 LD A,3
002A D3 03 OUT (3),A
002C 3E 07 LD A,7
002E D3 02 OUT (2),A
0030 21 5A 01 LD HL,15AH
0033 CD 4B 12 CALL 124BH
0036 3A 3C FB LD A,(0FB3CH)
0039 FE AA CP 0AAH
003B 28 18 JR Z,55H
003D 21 DA 02 LD HL,2DAH
0040 CD 4B 12 CALL 124BH
0043 21 00 80 LD HL,8000H
0046 54 LD D,H
0047 5D LD E,L
0048 13 INC DE
0049 01 FF 7F LD BC,7FFFH
004C 36 00 LD (HL),0
004E ED B0 LDIR
0050 21 3C FB LD HL,0FB3CH
END OF DISASSEMBLER RUN.
Z>
To call the disassembler command only two keys have to be pressed: "M" to select the "M"emory-command-group and "A" to select the dis"A"ssembler.
Just in case you are as curious as I am and you like reading other people's code as much as I do, version 0.14a of the monitor is listed in the following. Just in case Deft is reading this: there is plenty of room for improvement. There are way too many push/pop instructions, etc. But after all it was and still is a pet-project.
0000: N8VEM equ 0 ; If set to 1, the monitor is built
0000: ; for the N8VEM, otherwise, the
0000: ; old homebrew Z80 computer is targeted.
0000: FEATURE_BASIC equ 0 ; If set to 0, the BASIC interpreter
0000: ; will not be included.
0000: ;******************************************************************************
0000: ;
0000: ; Small monitor for the Z80 single board computer consisting of 32 kB ROM
0000: ; ($0000 to $ffff), 32 kB RAM ($8000 to $ffff) and a 16c550 UART.
0000: ;
0000: ; B. Ulmann, 28-SEP-2011, 29-SEP-2011, 01-OCT-2011, 02-OCT-2011, 30-OCT-2011,
0000: ; 01-NOV-2011, 02-NOV-2011, 03-NOV-2011, 06/07/08-JAN-2012
0000: ; I. Kloeckl, 06/07/08-JAN-2012 (FAT implementation for reading files)
0000: ; B. Ulmann, 14-JAN-2011
0000: ;
0000: ; 07-MAR-2012: Fabio Zanicotti spotted an error in the ide_get_id-routine -
0000: ; the value $0a should have been written to ide_lba3 but was
0000: ; instead written into ide_status_cmd (I am quite puzzled why
0000: ; the routine worked nevertheless). This has been corrected. :-)
0000: ;
0000: ; B. Ulmann, 20-MAY-2012 port to N8VEM - no IDE support at the moment
0000: ; 27-MAY-2012 PPIDE support for N8VEM
0000: ; 29-MAY-2012 New system call uart_status introduced (used by the
0000: ; 4th interpreter)
0000: ; 01-JUN-2012 CTRL-Y introduced
0000: ; 03-JUN-2012 F/R command added, UART initialization changed
0000: ; 06-JUN-2012 CAMEL-Forth added
0000: ; 07-JUN-2012 Minor bug fixes (load routine), added rom2ram
0000: ; 16-JUN-2012 stroup added, CAMEL Forth modified to use gets etc.
0000: ; 12-MAY-2013 Added support for the old homebrew Z80 computer.
0000: ; 20-MAY-2013 Added BASIC-subsystem.
0000: ; 05-JUN-2013 Added John Kerr's Z80 disassembler.
0000: ; 28-JUN-2013 Removed BASIC-support for the Z80mini
0000: ;
0000: ; Version 0.14a
0000: ;
0000: ;******************************************************************************
0000: ;
0000: ; TODO:
0000: ; Read and print IDE error status codes in case of error!
0000: ;
0000: ; Known issues:
0000: ; Memory Dump has a problem when the end address is >= FF00
0000: ;
0000: ;******************************************************************************
0000: ;
0000: ; RST $00 will enter the monitor (do not care about the return address pushed
0000: ; onto the stack - the stack pointer will be reinitialized during cold as well
0000: ; as during warm starts.
0000: ;
0000: ; Monitor routines will generally called by placing the necessary parameters
0000: ; into some processor registers and then issuing RST $08. More about this later.
0000: ;
0000: ; Whenever a call to the system routine getc is issued, it is tested if the
0000: ; character entered was a CTRL-Y (like in VMS :-) ). If so, a restart of the
0000: ; monitor takes place. Although this interrupt possibility requires a running
0000: ; program to call getc from time to time it is better than nothing since the
0000: ; monitor currently does not take care of interrupts at all.
0000: ;
0000: ; Programs running in memory should not make use of memory above about $FB00
0000: ; to leave some space for the monitor stack.
0000: ;
0000: ; Memory layout is as follows:
0000: ;
0000: ; +-------+
0000: ; ! $FFFF ! General purpose 512 byte buffer
0000: ; ! --- !
0000: ; ! $FE00 !
0000: ; +-------+
0000: ; ! $DFFF ! FAT control block
0000: ; ! --- !
0000: ; ! $FDDC !
0000: ; +-------+
0000: ; ! $FDDB ! File control block
0000: ; ! --- !
0000: ; ! $FBBE !
0000: ; +-------+
0000: ; ! $FBBD ! 81 byte string buffer
0000: ; ! --- !
0000: ; ! $FB6D !
0000: ; +-------+
0000: ; ! $FB6C ! 12 byte string buffer
0000: ; ! --- !
0000: ; ! $FB61 !
0000: ; +-------+
0000: ; ! $FB60 ! Buffers for various routines
0000: ; ! --- !
0000: ; ! $FB4D !
0000: ; +-------+
0000: ; ! $FB4C ! Scratch area (16 bytes)
0000: ; ! --- !
0000: ; ! $FB3D !
0000: ; +-------+
0000: ; ! $FB3C ! Cold/warm start control (1 byte)
0000: ; +-------+
0000: ; ! $FB3B ! Stack
0000: ; ! ... !
0000: ; ! $8000 ! Begin of RAM
0000: ; +-------+
0000: ; ! $7FFF ! ROM area
0000: ; ! --- ! RST $08 calls a system routine
0000: ; ! $0000 ! RST $00 restarts the monitor
0000: ; +-------+
0000: ;
0000: ;
0000: LOADABLE equ 0 ; This is necessary for the built-in
0000: ; Forth-interpreter to work in ROM.
0000: monitor_start equ $0000 ; $0000 -> ROM, $8000 -> Test image
0000: #target rom ; ((inserted by zasm))
0000: #code 0,$10000 ; ((inserted by zasm))
0000: org monitor_start
0000: ;
0000: ; Definitions used by the monitor and all programs based on this monitor.
0000: ;
0000: ; 01-JUN-2013 B. Ulmann 1st implementation
0000: ;
0000: rom_start equ $0
0000: rom_end equ $7fff
0000: ram_start equ $8000
0000: ram_end equ $ffff
0000: buffer equ ram_end - $1ff ; 512 byte IDE general purpose buffer
0000: last_user_ram equ $f9ff
0000: ;
0000: ; Define the FAT control block memory addresses:
0000: ;
0000: datastart equ buffer - 4 ; Data area start vector
0000: rootstart equ datastart - 4 ; Root directory start vector
0000: fat1start equ rootstart - 4 ; Start vector to first FAT
0000: psiz equ fat1start - 4 ; Size of partition (in sectors)
0000: pstart equ psiz - 4 ; First sector of partition
0000: rootlen equ pstart - 2 ; Maximum number of entries in directory
0000: fatsec equ rootlen - 2 ; FAT size in sectors
0000: ressec equ fatsec - 2 ; Number of reserved sectors
0000: clusiz equ ressec - 1 ; Size of a cluster (in sectors)
0000: fatname equ clusiz - 9 ; Name of the FAT (null terminated)
0000: fatcb equ fatname ; Start of the FATCB
0000: ;
0000: ; Define a file control block (FCB) memory addresses and displacements:
0000: ;
0000: file_buffer equ fatcb - $200 ; 512 byte sector buffer
0000: cluster_sector equ file_buffer - 1 ; Current sector in cluster
0000: current_sector equ cluster_sector - 4 ; Current sector address
0000: current_cluster equ current_sector - 2 ; Current cluster number
0000: file_pointer equ current_cluster - 4 ; Pointer for file position
0000: file_type equ file_pointer - 1 ; 0 -> not found, else OK
0000: first_cluster equ file_type - 2 ; First cluster of file
0000: file_size equ first_cluster - 4 ; Size of file
0000: file_name equ file_size - 12 ; Canonical name of file
0000: fcb equ file_name ; Start of the FCB
0000: ;
0000: fcb_filename equ 0
0000: fcb_file_size equ $c
0000: fcb_first_cluster equ $10
0000: fcb_file_type equ $12
0000: fcb_file_pointer equ $13
0000: fcb_current_cluster equ $17
0000: fcb_current_sector equ $19
0000: fcb_cluster_sector equ $1d
0000: fcb_file_buffer equ $1e
0000: ;
0000: ; We also need some general purpose string buffers:
0000: ;
0000: string_81_bfr equ fcb - 81
0000: string_12_bfr equ string_81_bfr - 12
0000: ;
0000: ; A number of routines need a bit of scratch RAM, too. Since these are
0000: ; sometimes interdependent, each routine gets its own memory cells (only
0000: ; possible since the routines are not recursively called).
0000: ;
0000: load_file_scrat equ string_12_bfr - 2 ; Two bytes for load_file
0000: str2filename_de equ load_file_scrat - 2 ; Two bytes for str2filename
0000: fopen_eob equ str2filename_de - 2 ; Eight bytes for fopen
0000: fopen_rsc equ fopen_eob - 4
0000: fopen_scr equ fopen_rsc - 2
0000: dirlist_scratch equ fopen_scr - 2 ; Eight bytes for fopen
0000: dirlist_eob equ dirlist_scratch - 2
0000: dirlist_rootsec equ dirlist_eob - 4
0000: scratch_area equ dirlist_rootsec - $1 ; Scratch memory (16 byte)
0000: ;
0000: start_type equ scratch_area - $10 ; Distinguish cold/warm start
0000: ;
0000: ; System calls are implemented by rst08 which expects the number of the
0000: ; call to be executed in ix. The numbers of valid calls are defined here:
0000: ;
0000: _cold_start equ $0
0000: _is_hex equ $1
0000: _is_print equ $2
0000: _to_upper equ $3
0000: _crlf equ $4
0000: _getc equ $5
0000: _putc equ $6
0000: _puts equ $7
0000: _strcmp equ $8
0000: _gets equ $9
0000: _fgetc equ $a
0000: _dump_fcb equ $b
0000: _fopen equ $c
0000: _dirlist equ $d
0000: _fatmount equ $e
0000: _fatunmount equ $f
0000: _strchr equ $10
0000: _uart_status equ $11
0000: _getc_nowait equ $12
0000: _print_word equ $13
0000: _print_byte equ $14
0000: _stroup equ $15
0000: _get_word equ $16
0000: ;
0000: ; Some useful ASCII (control) characters:
0000: ;
0000: eos equ $00 ; End of string
0000: cr equ $0d ; Carriage return
0000: lf equ $0a ; Line feed
0000: space equ $20 ; Space
0000: tab equ $09 ; Tabulator
0000: bs equ $08 ; Backspace
0000: bel equ $07 ; Bell
0000: ctrl_y equ 25 ; CTRL-Y character
0000:
0000: #include "mondef.asm"
0000: ;
0000: #if N8VEM = 1
0000: ;
0000: ; 82C55 registers (the 82C55 is used to implement a simple IDE interface):
0000: ;
0000: reg_ppi_base equ $60 ; 82C55 base address
0000: reg_ppi_cntl equ reg_ppi_base + 3 ; 82C55 control register
0000: ;
0000: reg_ppide_lsb equ reg_ppi_base + 0 ; 82C55 port A
0000: reg_ppide_msb equ reg_ppi_base + 1 ; 82C55 port B
0000: reg_ppide_cntl equ reg_ppi_base + 2 ; 82C55 port C
0000: #else
0000: ide_base equ $10
0000: #endif ; N8VEM = 1?
0000: ;
0000: ; 16C550 registers:
0000: ;
0000: #if N8VEM = 1
0000: uart_base equ $68
0000: #else
0000: uart_base equ $0
0000: #endif
0000: ;
0000: uart_register_0 equ uart_base + 0 ; Read/write a character
0000: uart_register_1 equ uart_base + 1
0000: uart_register_2 equ uart_base + 2
0000: uart_register_3 equ uart_base + 3
0000: uart_register_4 equ uart_base + 4
0000: uart_register_5 equ uart_base + 5 ; RX/TX status
0000: uart_register_6 equ uart_base + 6
0000: uart_register_7 equ uart_base + 7
0000: ;
0000: #if N8VEM = 1
0000: ;
0000: ; Memory Page Configuration Latches:
0000: ;
0000: mpcl_ram equ $78
0000: mpcl_rom equ $7c
0000: ;
0000: #endif
0000: ;
0000: ; Main entry point (RST 00H):
0000: ;
0000: F3 rst_00 di ; Disable interrupts
0001: ;
0001: ; The stackpointer will be predecremented by a push instruction. Therefore
0001: ; we set the start of the stack to the end of the reserved memory area in
0001: ; high memory.
0001: ;
0001: 313BFB ld sp, start_type - $1
0004: 1817 jr initialize ; Jump over the RST-area
0006: ;
0006: ; RST-area - here is the main entry point into the monitor. The calling
0006: ; standard looks like this:
0006: ;
0006: ; 1) Set register IX to the number of the system routine to be called.
0006: ; 2) Set the remaining registers according to the routine's documentation.
0006: ; 3) Execute RST $08 to actually call the system routine.
0006: ; 4) Evaluate the values returned in the registers as described by the
0006: ; Routine's documentation.
0006: ;
0006: ; (Currently there are no plans to use more RST entry points, so this routine
0006: ; just runs as long as necessary in memory. If more RSTs will be used, this
0006: ; routine should to be moved to the end of the used ROM area with only a
0006: ; simple jump at the RST $08-location.)
0006: ;
0006: ; This technique of calling system routines can be used as the following
0006: ; example program that just echos characters read from the serial line
0006: ; demonstrates:
0006: ;
0006: ; org $8000 ; Start in lower RAM
0006: ; loop ld ix, 5 ; Prepare call to getc
0006: ; rst 08 ; Execute getc
0006: ; cp 3 ; CTRL-C pressed?
0006: ; jr z, exit ; Yes - exit
0006: ; ld ix, 6 ; Prepare call to putc
0006: ; rst 08 ; Execute putx
0006: ; jr loop ; Process next character
0006: ; exit ld ix, 4 ; Exit - print a CR/LF pair
0006: ; rst 08 ; Call CRLF
0006: ; ld hl, msg ; Pointer to exit message
0006: ; ld ix, 7 ; Prepare calling puts
0006: ; rst 08 ; Call puts
0006: ; rst 00 ; Restart monitor (warm start)
0006: ; msg defb "That's all folks.", $d, $a, 0
0006: ;
0006: ; IMPORTANT: The content of ix is destroyed during the call, so it is NOT
0006: ; possible to perform successive calls to the same system service
0006: ; in a sequence without reloading the ix-register!
0006: ;
0006: ; Currently the following functions are available (a more detailed description
0006: ; can be found in the dispatch table itself - search for the label
0006: ; dispatch_table):
0006: ;
0006: ; $00: cold_start
0006: ; $01: is_hex
0006: ; $02: is_print
0006: ; $03: to_upper
0006: ; $04: crlf
0006: ; $05: getc
0006: ; $06: putc
0006: ; $07: puts
0006: ; $08: strcmp
0006: ; $09: gets
0006: ; $0A: fgetc
0006: ; $0B: dump_fcb
0006: ; $0C: fopen
0006: ; $0D: dirlist
0006: ; $0E: fatmount
0006: ; $0F: fatunmount
0006: ; $10: strchr
0006: ; $11: uart_status
0006: ; $12: getc_nowait
0006: ; $13: print_word
0006: ; $14: print_byte
0006: ; $15: stroup
0006: ; $16: get_word
0006: ;
0006: ; org monitor_start + $08
0006: 00 nop ; Beware: zasm is buggy concerning
0007: 00 nop ; ORG. Therefore we need two nops to
0008: ; get to address $0008.
0008: C5 rst_08 push bc ; Save bc and hl
0009: E5 push hl
000A: DDE5 push ix ; Copy the contents of ix
000C: E1 pop hl ; into hl
000D: 29 add hl, hl ; Double to get displacement in table
000E: 014C1D ld bc, dispatch_table
0011: 09 add hl, bc ; Calculate displacement in table
0012: 4E23462B ld bc, (hl) ; Load bc with the destination address
0016: C5 push bc
0017: DDE1 pop ix ; Load ix with the destination address
0019: E1 pop hl ; Restore hl
001A: C1 pop bc ; and bc
001B: DDE9 jp (ix) ; Jump to the destination routine
001D: ;
001D: ; Initialize UART to 9600,8N1:
001D: ;
001D: 3E80 initialize ld a, $80
001F: D303 out (uart_register_3), a
0021: 3E0C ld a, $c ; 1843200 / (16 * 9600)
0023: D300 out (uart_register_0), a
0025: AF xor a
0026: D301 out (uart_register_1), a
0028: 3E03 ld a, $3 ; 8N1
002A: D303 out (uart_register_3), a
002C: 3E07 ld a, $7 ; FIFO enable, reset RCVR/XMIT FIFO
002E: D302 out (uart_register_2), a
0030: ;
0030: ; Print welcome message:
0030: ;
0030: 215A01 ld hl, hello_msg
0033: CD4B12 call puts
0036: ;
0036: ; If this is a cold start (the location start_type does not contain $aa)
0036: ; all available RAM will be reset to $00 and a message will be printed.
0036: ;
0036: 3A3CFB init_mem ld a, (start_type)
0039: FEAA cp $aa ; Warm start?
003B: 2818 jr z, main_loop ; Yes - enter command loop
003D: 21DA02 ld hl, cold_start_msg
0040: CD4B12 call puts ; Print cold start message
0043: 210080 ld hl, ram_start ; Start of block to be filled with $00
0046: 545D ld de, hl ; End address of block
0048: 13 inc de ; plus 1 (for ldir)
0049: 01FF7F ld bc, ram_end - ram_start
004C: 3600 ld (hl), $00 ; Load first memory location
004E: EDB0 ldir ; And copy this value down
0050: 213CFB ld hl, start_type
0053: 36AA ld (hl), $aa ; Cold start done, remember this
0055: ;
0055: ; Read characters from the serial line and interpret them:
0055: ;
0055: 216202 main_loop ld hl, monitor_prompt
0058: CD4B12 call puts
005B: ;
005B: ; The monitor is rather simple: All commands consist of one or two letters.
005B: ; The first character selects a command group, the second the desired command
005B: ; out of that group. When a command is recognized, it will be spelled out
005B: ; automatically and the user will be prompted for arguments if applicable.
005B: ;
005B: CDF902 call monitor_key ; Read a key
005E: ; Which group did we get?
005E: FE43 cp 'C' ; Control group?
0060: 2022 jr nz, disk_group ; No - test next group
0062: 216802 ld hl, cg_msg ; Print group prompt
0065: CD4B12 call puts
0068: CDF902 call monitor_key ; Get command key
006B: FE43 cp 'C' ; Cold start?
006D: CA1C14 jp z, cold_start
0070: FE57 cp 'W' ; Warm start?
0072: CA2114 jp z, warm_start
0075: FE53 cp 'S' ; Start?
0077: CAC610 jp z, start
007A: FE49 cp 'I' ; Info?
007C: CC8B0D call z, info
007F: 28D4 jr z, main_loop
0081: C34E01 jp cmd_error ; Unknown control-group-command
0084: FE44 disk_group cp 'D' ; Disk group?
0086: 2028 jr nz, file_group ; No - file group?
0088: 217102 ld hl, dg_msg ; Print group prompt
008B: CD4B12 call puts
008E: CDF902 call monitor_key ; Get command
0091: FE49 cp 'I' ; Info?
0093: CC1B08 call z, disk_info
0096: 28BD jr z, main_loop
0098: FE4D cp 'M' ; Mount?
009A: CC7C0F call z, mount
009D: 28B6 jr z, main_loop
009F: FE54 cp 'T' ; Read from disk?
00A1: CC4708 call z, disk_transfer
00A4: 28AF jr z, main_loop
00A6: FE55 cp 'U' ; Unmount?
00A8: CCE010 call z, unmount
00AB: 28A8 jr z, main_loop
00AD: C34E01 jp cmd_error ; Unknown disk-group-command
00B0: FE46 file_group cp 'F' ; File group?
00B2: 2028 jr nz, help_group ; No - help group?
00B4: 217702 ld hl, fg_msg ; Print group prompt
00B7: CD4B12 call puts
00BA: CDF902 call monitor_key ; Get command
00BD: FE43 cp 'C' ; Cat?
00BF: CC0A03 call z, cat_file
00C2: 2891 jr z, main_loop
00C4: FE44 cp 'D' ; Directory?
00C6: CC4903 call z, directory
00C9: 288A jr z, main_loop
00CB: FE4C cp 'L' ; Load?
00CD: CCDB0E call z, load_file
00D0: 2883 jr z, main_loop
00D2: FE52 cp 'R' ; Run an executable?
00D4: CC6A0E call z, load_and_run
00D7: CA5500 jp z, main_loop
00DA: 1872 jr cmd_error ; Unknown file-group-command
00DC: FE48 help_group cp 'H' ; Help? (No further level expected.)
00DE: CCC40A call z, help ; Yes :-)
00E1: CA5500 jp z, main_loop
00E4: FE4D memory_group cp 'M' ; Memory group?
00E6: C23401 jp nz, subsys_group; No - subsystem group?
00E9: 217D02 ld hl, mg_msg ; Print group prompt
00EC: CD4B12 call puts
00EF: CDF902 call monitor_key ; Get command key
00F2: FE41 cp 'A' ; Disassemble?
00F4: CC6103 call z, disassemble
00F7: CA5500 jp z, main_loop
00FA: FE44 cp 'D' ; Dump?
00FC: CC5409 call z, dump
00FF: CA5500 jp z, main_loop
0102: FE45 cp 'E' ; Examine?
0104: CCCA09 call z, examine
0107: CA5500 jp z, main_loop
010A: FE46 cp 'F' ; Fill?
010C: CC1D0A call z, fill
010F: CA5500 jp z, main_loop
0112: FE49 cp 'I' ; INTEL-Hex load?
0114: CCD50C call z, ih_load
0117: CA5500 jp z, main_loop
011A: FE4C cp 'L' ; Load?
011C: CCA10D call z, load
011F: CA5500 jp z, main_loop
0122: FE4D cp 'M' ; Move?
0124: CC920F call z, move
0127: CA5500 jp z, main_loop
012A: FE52 cp 'R' ; Register dump?
012C: CC0A10 call z, rdump
012F: CA5500 jp z, main_loop
0132: #if N8VEM = 1
0132: cp 'S' ; Switch ROM to RAM?
0132: call z, rom2ram ; This routine won't return
0132: jp z, main_loop
0132: #endif
0132: 181A jr cmd_error ; Unknown memory-group-command
0134: FE53 subsys_group cp 'S' ; Subsystem group?
0136: C24901 jp nz, group_error ; No - print an error message
0139: 218502 ld hl, sg_msg ; Print group prompt
013C: CD4B12 call puts
013F: CDF902 call monitor_key ; Get command key
0142: FE46 cp 'F' ; Forth?
0144: CA7A1D jp z, forth_subsystem
0147: #if FEATURE_BASIC = 1
0147: cp 'B' ; BASIC?
0147: jp z, basic_subsystem
0147: #endif
0147: 1805 jr cmd_error ; Unknown subsytem-group-command
0149: 21B602 group_error ld hl, group_err_msg
014C: 1803 jr print_error
014E: 219002 cmd_error ld hl, command_err_msg
0151: CD4012 print_error call putc ; Echo the illegal character
0154: CD4B12 call puts ; and print the error message
0157: C35500 jp main_loop
015A: ;
015A: ; Some constants for the monitor:
015A: ;
015A: 0D0A0D0A
015E: 53696D70
0162: 6C65205A
0166: 38302D6D
016A: 6F6E6974
016E: 6F72202D
0172: 20562030
0176: 2E313461
017A: 20 hello_msg defb cr, lf, cr, lf, "Simple Z80-monitor - V 0.14a "
017B: 28422E20
017F: 556C6D61
0183: 6E6E2C20
0187: 53657074
018B: 656D6265
018F: 72203230
0193: 3131202D
0197: 204A756E
019B: 65203230
019F: 3133290D
01A3: 0A defb "(B. Ulmann, September 2011 - June 2013)", cr, lf
01A4: 20202054
01A8: 68697320
01AC: 6D6F6E69
01B0: 746F7220
01B4: 636F6E74
01B8: 61696E73
01BC: 20427261
01C0: 6420526F
01C4: 64726967
01C8: 75657A27
01CC: 20 defb " This monitor contains Brad Rodriguez' "
01CD: 43414D45
01D1: 4C20466F
01D5: 7274682C
01D9: 200D0A defb "CAMEL Forth, ", cr, lf
01DC: 20202020
...
01F5: 4A6F686E
01F9: 204B6572
01FD: 72277320
0201: 5A383020 defb " John Kerr's Z80 "
0205: 64697361
0209: 7373656D
020D: 626C6572 defb "disassembler"
0211: #if FEATURE_BASIC = 1
0211: defb ", and", cr, lf
0211: defb " BASIC 4.7 (C) Microsoft"
0211: #endif
0211: 0D0A defb cr, lf
0213: 2D2D2D2D
...
023B: defb "----------------------------------------"
023B: 2D2D2D2D
...
0262: defb "---------------------------------------"
0262: 0D0A5A3E
0266: 2000 monitor_prompt defb cr, lf, "Z> ", eos
0268: 434F4E54
026C: 524F4C2F
0270: 00 cg_msg defb "CONTROL/", eos
0271: 4449534B
0275: 2F00 dg_msg defb "DISK/", eos
0277: 46494C45
027B: 2F00 fg_msg defb "FILE/", eos
027D: 4D454D4F
0281: 52592F00 mg_msg defb "MEMORY/", eos
0285: 53554253
0289: 59535445
028D: 4D2F00 sg_msg defb "SUBSYSTEM/", eos
0290: 3A205379
0294: 6E746178
0298: 20657272
029C: 6F72202D
02A0: 20636F6D
02A4: 6D616E64
02A8: 206E6F74
02AC: 20666F75
02B0: 6E64210D
02B4: 0A00 command_err_msg defb ": Syntax error - command not found!", cr, lf, eos
02B6: 3A205379
02BA: 6E746178
02BE: 20657272
02C2: 6F72202D
02C6: 2067726F
02CA: 7570206E
02CE: 6F742066
02D2: 6F756E64
02D6: 210D0A00 group_err_msg defb ": Syntax error - group not found!", cr, lf, eos
02DA: 436F6C64
02DE: 20737461
02E2: 72742C20
02E6: 636C6561
02EA: 72696E67
02EE: 206D656D
02F2: 6F72792E
02F6: 0D0A00 cold_start_msg defb "Cold start, clearing memory.", cr, lf, eos
02F9: ;
02F9: ; Read a key for command group and command (this routine differs slightly
02F9: ; from getc as it can also return to the monitor prompt with a dirty trick
02F9: ; readjusting the stack):
02F9: ;
02F9: CD5911 monitor_key call getc
02FC: FE0A cp lf ; Ignore LF
02FE: 28F9 jr z, monitor_key ; Just get the next character
0300: CD4311 call to_upper
0303: FE0D cp cr ; A CR will return to the prompt
0305: C0 ret nz ; No - just return
0306: 33 inc sp ; Correct SP to and avoid ret!
0307: C35500 jp main_loop
030A: ;
030A: ;******************************************************************************
030A: ;***
030A: ;*** The following routines are used in the interactive part of the monitor
030A: ;***
030A: ;******************************************************************************
030A: ;
030A: ; Print a file's contents to STDOUT:
030A: ;
030A: C5 cat_file push bc
030B: D5 push de
030C: E5 push hl
030D: FDE5 push iy
030F: 213A03 ld hl, cat_file_prompt
0312: CD4B12 call puts
0315: 216DFB ld hl, string_81_bfr
0318: 0651 ld b, 81
031A: CDBA11 call gets ; Read the filename into buffer
031D: CD5B12 call stroup ; Convert to upper case
0320: FD21BEFB ld iy, fcb ; Prepare fopen (only one FCB currently)
0324: 1161FB ld de, string_12_bfr
0327: CD1C17 call fopen
032A: CD6114 cat_file_loop call fgetc ; Get a single character
032D: 3805 jr c, cat_file_exit
032F: CD4012 call putc ; Print character if not EOF
0332: 18F6 jr cat_file_loop ; Next character
0334: FDE1 cat_file_exit pop iy
0336: E1 pop hl
0337: D1 pop de
0338: C1 pop bc
0339: C9 ret
033A: 4341543A
033E: 2046494C
0342: 454E414D
0346: 453D00 cat_file_prompt defb "CAT: FILENAME=", eos
0349: ;
0349: ; directory - a simple wrapper for dirlist (necessary for printing the command
0349: ; name)
0349: ;
0349: E5 directory push hl
034A: 215503 ld hl, directory_msg
034D: CD4B12 call puts
0350: CD6F18 call dirlist
0353: E1 pop hl
0354: C9 ret
0355: 44495245
0359: 43544F52
035D: 590D0A00 directory_msg defb "DIRECTORY", cr, lf, eos
0361: ;
0361: ; Disassemble a memory area
0361: ;
0361: F5 disassemble push af
0362: C5 push bc
0363: D5 push de
0364: E5 push hl
0365: DDE5 push ix
0367: 219D03 ld hl, dismsg1 ; Prompt for the start address
036A: CD4B12 call puts
036D: CDAF11 call get_word ; Read user input
0370: E5 push hl ; Save start address for later
0371: 21B103 ld hl, dismsg2 ; Prompt for end address
0374: CD4B12 call puts
0377: CDAF11 call get_word
037A: CD4C11 call crlf
037D: CD4C11 call crlf
0380: D1 pop de ; Start address -> de
0381: E5 push hl ; Push end address
0382: CDD403 disloop call disz80 ; Disassemble one instruction
0385: CD4C11 call crlf
0388: E1 pop hl
0389: E5 push hl
038A: A7 and a ; Clear carry, just in case
038B: ED52 sbc hl, de ; End address reached?
038D: 30F3 jr nc, disloop ; No, continue disassembling
038F: 21B703 ld hl, dismsg3 ; Yes, print end message
0392: CD4B12 call puts
0395: E1 pop hl ; Remove the end address copy
0396: DDE1 pop ix
0398: E1 pop hl
0399: D1 pop de
039A: C1 pop bc
039B: F1 pop af
039C: C9 ret
039D: 44495341
03A1: 5353454D
03A5: 424C453A
03A9: 20535441
03AD: 52543D00 dismsg1 defb "DISASSEMBLE: START=", eos
03B1: 20454E44
03B5: 3D00 dismsg2 defb " END=", eos
03B7: 0D0A454E
03BB: 44204F46
03BF: 20444953
03C3: 41535345
03C7: 4D424C45
03CB: 52205255
03CF: 4E2E0D0A
03D3: 00 dismsg3 defb cr, lf, "END OF DISASSEMBLER RUN.", cr, lf, eos
;... ...John Kerr's Disassembler resides here... ...
081A: #include "../disassembler/dis_z80.asm"
081B: ;
081B: ;
081B: ; Get and print disk info:
081B: ;
081B: F5 disk_info push af
081C: E5 push hl
081D: 213F08 ld hl, disk_info_msg
0820: CD4B12 call puts
0823: CDBC12 call ide_get_id ; Read the disk info into the IDE buffer
0826: 2113FE ld hl, buffer + $13
0829: 3609 ld (hl), tab
082B: CD4B12 call puts ; Print vendor information
082E: CD4C11 call crlf
0831: 212DFE ld hl, buffer + $2d
0834: 3609 ld (hl), tab
0836: CD4B12 call puts
0839: CD4C11 call crlf
083C: E1 pop hl
083D: F1 pop af
083E: C9 ret
083F: 494E464F
0843: 3A0D0A00 disk_info_msg defb "INFO:", cr, lf, eos
0847: ;
0847: ; Read data from disk to memory
0847: ;
0847: F5 disk_transfer push af
0848: C5 push bc
0849: D5 push de
084A: E5 push hl
084B: DDE5 push ix
084D: 21C608 ld hl, disk_trx_msg_0
0850: CD4B12 call puts ; Print Read/Write prompt
0853: CD5911 disk_trx_rwlp call getc
0856: CD4311 call to_upper
0859: FE52 cp 'R' ; Read?
085B: 2009 jr nz, disk_trx_nr ; No
085D: DD215613 ld ix, ide_rs ; Yes, we will call ide_rs later
0861: 21D008 ld hl, disk_trx_msg_1r
0864: 180B jr disk_trx_main ; Prompt the user for parameters
0866: FE57 disk_trx_nr cp 'W' ; Write?
0868: 20E9 jr nz, disk_trx_rwlp
086A: DD21C413 ld ix, ide_ws ; Yes, we will call ide_ws later
086E: 21EA08 ld hl, disk_trx_msg_1w
0871: CD4B12 disk_trx_main call puts ; Print start address prompt
0874: CDAF11 call get_word ; Get memory start address
0877: E5 push hl
0878: 210509 ld hl, disk_trx_msg_2
087B: CD4B12 call puts ; Prompt for number of blocks
087E: CD8A11 call get_byte ; There are only 128 block of memory!
0881: FE00 cp 0 ; Did the user ask for 00 blocks?
0883: 2008 jr nz, disk_trx_1 ; No, continue prompting
0885: 213209 ld hl, disk_trx_msg_4
0888: CD4B12 call puts
088B: 1830 jr disk_trx_exit
088D: 212309 disk_trx_1 ld hl, disk_trx_msg_3
0890: CD4B12 call puts ; Prompt for disk start sector
0893: CDAF11 call get_word ; This is a four byte address!
0896: 444D ld bc, hl
0898: CDAF11 call get_word
089B: 545D ld de, hl
089D: E1 pop hl ; Restore memory start address
089E: ; Register contents:
089E: ; A: Number of blocks
089E: ; BC: LBA3/2
089E: ; DE: LBA1/0
089E: ; HL: Memory start address
089E: F5 disk_trx_loop push af ; Save number of sectors
089F: CDC408 call disk_trampoline ; Read/write one sector (F is changed!)
08A2: E5 push hl ; Save memory address
08A3: C5 push bc ; Save LBA3/2
08A4: 626B ld hl, de ; Increment DE (LBA1/0)
08A6: 010100 ld bc, $0001 ; by one and
08A9: 09 add hl, bc ; generate a carry if necessary
08AA: 545D ld de, hl ; Save new LBA1/0
08AC: E1 pop hl ; Restore LBA3/2 into HL (!)
08AD: 3001 jr nc, disk_trx_skip
08AF: 09 add hl, bc ; Increment BC if there was a carry
08B0: 444D disk_trx_skip ld bc, hl ; Write new LBA3/2 into BC
08B2: E1 pop hl ; Restore memory address
08B3: C5 push bc ; Save LBA3/2
08B4: 010002 ld bc, $200 ; 512 byte per block
08B7: 09 add hl, bc ; Set pointer to next memory block
08B8: C1 pop bc ; Restore LBA3/2
08B9: F1 pop af
08BA: 3D dec a ; One block already done
08BB: 20E1 jr nz, disk_trx_loop
08BD: DDE1 disk_trx_exit pop ix
08BF: E1 pop hl
08C0: D1 pop de
08C1: C1 pop bc
08C2: F1 pop af
08C3: C9 ret
08C4: DDE9 disk_trampoline jp (ix)
08C6: 5452414E
08CA: 53464552
08CE: 2F00 disk_trx_msg_0 defb "TRANSFER/", eos
08D0: 52454144
08D4: 3A200D0A
08D8: 20202020
08DC: 4D454D4F
08E0: 52592053
08E4: 54415254
08E8: 3D00 disk_trx_msg_1r defb "READ: ", cr, lf, " MEMORY START=", eos
08EA: 57524954
08EE: 453A200D
08F2: 0A202020
08F6: 204D454D
08FA: 4F525920
08FE: 53544152
0902: 543D00 disk_trx_msg_1w defb "WRITE: ", cr, lf, " MEMORY START=", eos
0905: 204E554D
0909: 42455220
090D: 4F462042
0911: 4C4F434B
0915: 53202835
0919: 31322042
091D: 59544529
0921: 3D00 disk_trx_msg_2 defb " NUMBER OF BLOCKS (512 BYTE)=", eos
0923: 20535441
0927: 52542053
092B: 4543544F
092F: 523D00 disk_trx_msg_3 defb " START SECTOR=", eos
0932: 204E6F74
0936: 68696E67
093A: 20746F20
093E: 646F2066
0942: 6F72207A
0946: 65726F20
094A: 626C6F63
094E: 6B732E0D
0952: 0A00 disk_trx_msg_4 defb " Nothing to do for zero blocks.", cr, lf, eos
0954: ;
0954: ; Dump a memory area
0954: ;
0954: F5 dump push af
0955: C5 push bc
0956: D5 push de
0957: E5 push hl
0958: 21B409 ld hl, dump_msg_1
095B: CD4B12 call puts ; Print prompt
095E: CDAF11 call get_word ; Read start address
0961: E5 push hl ; Save start address
0962: 21C109 ld hl, dump_msg_2 ; Prompt for end address
0965: CD4B12 call puts
0968: CDAF11 call get_word ; Get end address
096B: CD4C11 call crlf
096E: 23 inc hl ; Increment stop address for comparison
096F: 545D ld de, hl ; DE now contains the stop address
0971: E1 pop hl ; HL is the start address again
0972: ; This loop will dump 16 memory locations at once - even
0972: ; if this turns out to be more than requested.
0972: 0610 dump_line ld b, $10 ; This loop will process 16 bytes
0974: E5 push hl ; Save HL again
0975: CD3312 call print_word ; Print address
0978: 21C709 ld hl, dump_msg_3 ; and a colon
097B: CD4B12 call puts
097E: E1 pop hl ; Restore address
097F: E5 push hl ; We will need HL for the ASCII dump
0980: 7E dump_loop ld a, (hl) ; Get the memory content
0981: CD1212 call print_byte ; and print it
0984: 3E20 ld a, ' ' ; Print a space
0986: CD4012 call putc
0989: 23 inc hl ; Increment address counter
098A: 10F4 djnz dump_loop ; Continue with this line
098C: ; This loop will dump the very same 16 memory locations - but
098C: ; this time printable ASCII characters will be written.
098C: 0610 ld b, $10 ; 16 characters at a time
098E: 3E20 ld a, ' ' ; We need some spaces
0990: CD4012 call putc ; to print
0993: CD4012 call putc
0996: E1 pop hl ; Restore the start address
0997: 7E dump_ascii_loop ld a, (hl) ; Get byte
0998: CD0A11 call is_print ; Is it printable?
099B: 3802 jr c, dump_al_1 ; Yes
099D: 3E2E ld a, '.' ; No - print a dot
099F: CD4012 dump_al_1 call putc ; Print the character
09A2: 23 inc hl ; Increment address to read from
09A3: 10F2 djnz dump_ascii_loop
09A5: ; Now we are finished with printing one line of dump output.
09A5: CD4C11 call crlf ; CR/LF for next line on terminal
09A8: E5 push hl ; Save the current address for later
09A9: A7 and a ; Clear carry
09AA: ED52 sbc hl, de ; Have we reached the last address?
09AC: E1 pop hl ; restore the address
09AD: 38C3 jr c, dump_line ; Dump next line of 16 bytes
09AF: E1 pop hl
09B0: D1 pop de
09B1: C1 pop bc
09B2: F1 pop af
09B3: C9 ret
09B4: 44554D50
09B8: 3A205354
09BC: 4152543D
09C0: 00 dump_msg_1 defb "DUMP: START=", eos
09C1: 20454E44
09C5: 3D00 dump_msg_2 defb " END=", eos
09C7: 3A2000 dump_msg_3 defb ": ", eos
09CA: ;
09CA: ; Examine a memory location:
09CA: ;
09CA: F5 examine push af
09CB: E5 push hl
09CC: 21F809 ld hl, examine_msg_1
09CF: CD4B12 call puts
09D2: CDAF11 call get_word ; Wait for a four-nibble address
09D5: E5 push hl ; Save address for later
09D6: 21160A ld hl, examine_msg_2
09D9: CD4B12 call puts
09DC: E1 examine_loop pop hl ; Restore address
09DD: 7E ld a, (hl) ; Get content of address
09DE: 23 inc hl ; Prepare for next examination
09DF: E5 push hl ; Save hl again for later use
09E0: CD1212 call print_byte ; Print the byte
09E3: CD5911 call getc ; Get a character
09E6: FE20 cp ' ' ; A blank?
09E8: 2007 jr nz, examine_exit; No - exit
09EA: 3E20 ld a, ' ' ; Print a blank character
09EC: CD4012 call putc
09EF: 18EB jr examine_loop
09F1: E1 examine_exit pop hl ; Get rid of save hl value
09F2: CD4C11 call crlf ; Print CR/LF
09F5: E1 pop hl
09F6: F1 pop af
09F7: C9 ret
09F8: 4558414D
09FC: 494E4520
0A00: 28747970
0A04: 65202720
0A08: 272F5245
0A0C: 54293A20
0A10: 41444452
0A14: 3D00 examine_msg_1 defb "EXAMINE (type ' '/RET): ADDR=", eos
0A16: 20444154
0A1A: 413D00 examine_msg_2 defb " DATA=", eos
0A1D: ;
0A1D: ; Fill a block of memory with a single byte - the user is prompted for the
0A1D: ; start address, the length of the block and the fill value.
0A1D: ;
0A1D: F5 fill push af ; We will need nearly all registers
0A1E: C5 push bc
0A1F: D5 push de
0A20: E5 push hl
0A21: 21770A ld hl, fill_msg_1 ; Prompt for start address
0A24: CD4B12 call puts
0A27: CDAF11 call get_word ; Get the start address
0A2A: E5 push hl ; Store the start address
0A2B: A7 and a ; Clear carry
0A2C: 010080 ld bc, ram_start
0A2F: ED42 sbc hl, bc ; Is the address in the RAM area?
0A31: 3009 jr nc, fill_get_length
0A33: 21950A ld hl, fill_msg_4 ; No!
0A36: CD4B12 call puts ; Print error message
0A39: E1 pop hl ; Clean up the stack
0A3A: 1836 jr fill_exit ; Leave routine
0A3C: 21840A fill_get_length ld hl, fill_msg_2 ; Prompt for length information
0A3F: CD4B12 call puts
0A42: CDAF11 call get_word ; Get the length of the block
0A45: ; Now make sure that start + length is still in RAM:
0A45: 444D ld bc, hl ; BC contains the length
0A47: E1 pop hl ; HL now contains the start address
0A48: E5 push hl ; Save the start address again
0A49: C5 push bc ; Save the length
0A4A: 09 add hl, bc ; Start + length
0A4B: A7 and a ; Clear carry
0A4C: 010080 ld bc, ram_start
0A4F: ED42 sbc hl, bc ; Compare with ram_start
0A51: 300A jr nc, fill_get_value
0A53: 21A90A ld hl, fill_msg_5 ; Print error message
0A56: CD4B12 call puts
0A59: C1 pop bc ; Clean up the stack
0A5A: E1 pop hl
0A5B: 1815 jr fill_exit ; Leave the routine
0A5D: 218D0A fill_get_value ld hl, fill_msg_3 ; Prompt for fill value
0A60: CD4B12 call puts
0A63: CD8A11 call get_byte ; Get the fill value
0A66: C1 pop bc ; Get the length from the stack
0A67: E1 pop hl ; Get the start address again
0A68: 545D ld de, hl ; DE = HL + 1
0A6A: 13 inc de
0A6B: 0B dec bc
0A6C: ; HL = start address
0A6C: ; DE = destination address = HL + 1
0A6C: ; Please note that this is necessary - LDIR does not
0A6C: ; work with DE == HL. :-)
0A6C: ; A = fill value
0A6C: 77 ld (hl), a ; Store A into first memory location
0A6D: EDB0 ldir ; Fill the memory
0A6F: CD4C11 call crlf
0A72: E1 fill_exit pop hl ; Restore the register contents
0A73: D1 pop de
0A74: C1 pop bc
0A75: F1 pop af
0A76: C9 ret
0A77: 46494C4C
0A7B: 3A205354
0A7F: 4152543D
0A83: 00 fill_msg_1 defb "FILL: START=", eos
0A84: 204C454E
0A88: 4754483D
0A8C: 00 fill_msg_2 defb " LENGTH=", eos
0A8D: 2056414C
0A91: 55453D00 fill_msg_3 defb " VALUE=", eos
0A95: 20496C6C
0A99: 6567616C
0A9D: 20616464
0AA1: 72657373
0AA5: 210D0A00 fill_msg_4 defb " Illegal address!", cr, lf, eos
0AA9: 20426C6F
0AAD: 636B2065
0AB1: 78636565
0AB5: 64732052
0AB9: 414D2061
0ABD: 72656121
0AC1: 0D0A00 fill_msg_5 defb " Block exceeds RAM area!", cr, lf, eos
0AC4: ;
0AC4: ; Help
0AC4: ;
0AC4: E5 help push hl
0AC5: 21CD0A ld hl, help_msg
0AC8: CD4B12 call puts
0ACB: E1 pop hl
0ACC: C9 ret
0ACD: 48454C50
0AD1: 3A204B6E
0AD5: 6F776E20
0AD9: 636F6D6D
0ADD: 616E6420
0AE1: 67726F75
0AE5: 70732061
0AE9: 6E642063
0AED: 6F6D6D61
0AF1: 6E64733A
0AF5: 0D0A help_msg defb "HELP: Known command groups and commands:", cr, lf
0AF7: 0D0A defb cr, lf
0AF9: 20202020
...
0B02: 43286F6E
0B06: 74726F6C
0B0A: 2067726F
0B0E: 7570293A
0B12: 0D0A defb " C(ontrol group):", cr, lf
0B14: 20202020
...
0B21: 43286F6C
0B25: 64207374
0B29: 61727429
0B2D: 2C204928
0B31: 6E666F29
0B35: 2C205328
0B39: 74617274
0B3D: 292C20 defb " C(old start), I(nfo), S(tart), "
0B40: 57286172
0B44: 6D207374
0B48: 61727429
0B4C: 0D0A defb "W(arm start)", cr, lf
0B4E: 20202020
...
0B57: 44286973
0B5B: 6B206772
0B5F: 6F757029
0B63: 3A0D0A defb " D(isk group):", cr, lf
0B66: 20202020
...
0B73: 49286E66
0B77: 6F292C20
0B7B: 4D286F75
0B7F: 6E74292C
0B83: 20542872
0B87: 616E7366
0B8B: 6572292C defb " I(nfo), M(ount), T(ransfer),"
0B8F: 2055286E
0B93: 6D6F756E
0B97: 74290D0A defb " U(nmount)", cr, lf
0B9B: 20202020
...
0BA8: 52286561
0BAC: 64292C20
0BB0: 57287269
0BB4: 7465290D
0BB8: 0A defb " R(ead), W(rite)", cr, lf
0BB9: 20202020
...
0BC2: 4628696C
0BC6: 65206772
0BCA: 6F757029
0BCE: 3A0D0A defb " F(ile group):", cr, lf
0BD1: 20202020
...
0BDE: 43286174
0BE2: 292C2044
0BE6: 28697265
0BEA: 63746F72
0BEE: 79292C20
0BF2: 4C286F61
0BF6: 64292C20
0BFA: 5228756E
0BFE: 290D0A defb " C(at), D(irectory), L(oad), R(un)", cr, lf
0C01: 20202020
...
0C0A: 4828656C
0C0E: 70290D0A defb " H(elp)", cr, lf
0C12: 20202020
...
0C1B: 4D28656D
0C1F: 6F727920
0C23: 67726F75
0C27: 70293A0D
0C2B: 0A defb " M(emory group):", cr, lf
0C2C: 20202020
...
0C39: 28646973
0C3D: 29412873
0C41: 73656D62
0C45: 6C65292C
0C49: 20442875
0C4D: 6D70292C
0C51: 20452878
0C55: 616D696E
0C59: 65292C20 defb " (dis)A(ssemble), D(ump), E(xamine), "
0C5D: 4628696C
0C61: 6C292C20
0C65: 49286E74
0C69: 656C2048
0C6D: 6578204C
0C71: 6F616429
0C75: 2C200D0A defb "F(ill), I(ntel Hex Load), ", cr, lf
0C79: 20202020
...
0C86: 4C286F61
0C8A: 64292C20
0C8E: 52286567
0C92: 69737465
0C96: 72206475
0C9A: 6D70292C
0C9E: 20 defb " L(oad), R(egister dump), "
0C9F: #if N8VEM = 1
0C9F: defb " S(witch ROM to RAM)", cr, lf
0C9F: #else
0C9F: 0D0A defb cr, lf
0CA1: #endif
0CA1: 20202020
...
0CAA: 53287562
0CAE: 73797374
0CB2: 656D2067
0CB6: 726F7570
0CBA: 293A0D0A defb " S(ubsystem group):", cr, lf
0CBE: 20202020
...
0CCB: 46286F72
0CCF: 746829 defb " F(orth)"
0CD2: #if FEATURE_BASIC = 1
0CD2: defb ", B(ASIC)"
0CD2: #endif
0CD2: 0D0A00 defb cr, lf, eos
0CD5: ;
0CD5: ; Load an INTEL-Hex file (a ROM image) into memory. This routine has been
0CD5: ; more or less stolen from a boot program written by Andrew Lynch and adapted
0CD5: ; to this simple Z80 based machine.
0CD5: ;
0CD5: ; The INTEL-Hex format looks a bit awkward - a single line contains these
0CD5: ; parts:
0CD5: ; ':', Record length (2 hex characters), load address field (4 hex characters),
0CD5: ; record type field (2 characters), data field (2 * n hex characters),
0CD5: ; checksum field. Valid record types are 0 (data) and 1 (end of file).
0CD5: ;
0CD5: ; Please note that this routine will not echo what it read from stdin but
0CD5: ; what it "understood". :-)
0CD5: ;
0CD5: F5 ih_load push af
0CD6: D5 push de
0CD7: E5 push hl
0CD8: 21580D ld hl, ih_load_msg_1
0CDB: CD4B12 call puts
0CDE: CD5911 ih_load_loop call getc ; Get a single character
0CE1: FE0D cp cr ; Don't care about CR
0CE3: 28F9 jr z, ih_load_loop
0CE5: FE0A cp lf ; ...or LF
0CE7: 28F5 jr z, ih_load_loop
0CE9: FE20 cp space ; ...or a space
0CEB: 28F1 jr z, ih_load_loop
0CED: CD4311 call to_upper ; Convert to upper case
0CF0: CD4012 call putc ; Echo character
0CF3: FE3A cp ':' ; Is it a colon?
0CF5: 204E jr nz, ih_load_error
0CF7: CD8A11 call get_byte ; Get record length into A
0CFA: 57 ld d, a ; Length is now in D
0CFB: 1E00 ld e, $0 ; Clear checksum
0CFD: CD520D call ih_load_chk ; Compute checksum
0D00: CDAF11 call get_word ; Get load address into HL
0D03: 7C ld a, h ; Update checksum by this address
0D04: CD520D call ih_load_chk
0D07: 7D ld a, l
0D08: CD520D call ih_load_chk
0D0B: CD8A11 call get_byte ; Get the record type
0D0E: CD520D call ih_load_chk ; Update checksum
0D11: FE01 cp $1 ; Have we reached the EOF marker?
0D13: 2012 jr nz, ih_load_data; No - get some data
0D15: CD8A11 call get_byte ; Yes - EOF, read checksum data
0D18: CD520D call ih_load_chk ; Update our own checksum
0D1B: 7B ld a, e
0D1C: A7 and a ; Is our checksum zero (as expected)?
0D1D: 282C jr z, ih_load_exit ; Yes - exit this routine
0D1F: 217A0D ih_load_chk_err ld hl, ih_load_msg_3
0D22: CD4B12 call puts ; No - print an error message
0D25: 1824 jr ih_load_exit ; and exit
0D27: 7A ih_load_data ld a, d ; Record length is now in A
0D28: A7 and a ; Did we process all bytes?
0D29: 280B jr z, ih_load_eol ; Yes - process end of line
0D2B: CD8A11 call get_byte ; Read two hex digits into A
0D2E: CD520D call ih_load_chk ; Update checksum
0D31: 77 ld (hl), a ; Store byte into memory
0D32: 23 inc hl ; Increment pointer
0D33: 15 dec d ; Decrement remaining record length
0D34: 18F1 jr ih_load_data ; Get next byte
0D36: CD8A11 ih_load_eol call get_byte ; Read the last byte in the line
0D39: CD520D call ih_load_chk ; Update checksum
0D3C: 7B ld a, e
0D3D: A7 and a ; Is the checksum zero (as expected)?
0D3E: 20DF jr nz, ih_load_chk_err
0D40: CD4C11 call crlf
0D43: 1899 jr ih_load_loop ; Yes - read next line
0D45: 216B0D ih_load_error ld hl, ih_load_msg_2
0D48: CD4B12 call puts ; Print error message
0D4B: CD4C11 ih_load_exit call crlf
0D4E: E1 pop hl ; Restore registers
0D4F: D1 pop de
0D50: F1 pop af
0D51: C9 ret
0D52: ;
0D52: 4F ih_load_chk ld c, a ; All in all compute E = E - A
0D53: 7B ld a, e
0D54: 91 sub c
0D55: 5F ld e, a
0D56: 79 ld a, c
0D57: C9 ret
0D58: 494E5445
0D5C: 4C204845
0D60: 58204C4F
0D64: 41443A20
0D68: 0D0A00 ih_load_msg_1 defb "INTEL HEX LOAD: ", cr, lf, eos
0D6B: 2053796E
0D6F: 74617820
0D73: 6572726F
0D77: 722100 ih_load_msg_2 defb " Syntax error!", eos
0D7A: 20436865
0D7E: 636B7375
0D82: 6D206572
0D86: 726F7221
0D8A: 00 ih_load_msg_3 defb " Checksum error!", eos
0D8B: ;
0D8B: ; Print version information etc.
0D8B: ;
0D8B: E5 info push hl
0D8C: 219A0D ld hl, info_msg
0D8F: CD4B12 call puts
0D92: 215A01 ld hl, hello_msg
0D95: CD4B12 call puts
0D98: E1 pop hl
0D99: C9 ret
0D9A: 494E464F
0D9E: 3A2000 info_msg defb "INFO: ", eos
0DA1: ;
0DA1: ; Load data into memory. The user is prompted for a 16 bit start address. Then
0DA1: ; a sequence of bytes in hexadecimal notation may be entered until a character
0DA1: ; that is not 0-9 or a-f is encountered.
0DA1: ;
0DA1: F5 load push af
0DA2: C5 push bc
0DA3: D5 push de
0DA4: E5 push hl
0DA5: 210C0E ld hl, load_msg_1 ; Print command name
0DA8: CD4B12 call puts
0DAB: CDAF11 call get_word ; Wait for the start address (2 bytes)
0DAE: E5 push hl ; Remember address
0DAF: A7 and a ; Clear carry
0DB0: 010080 ld bc, ram_start ; Check if the address is valid
0DB3: ED42 sbc hl, bc ; by subtracting the RAM start address
0DB5: E1 pop hl ; Restore address
0DB6: 110000 ld de, 0 ; Counter for bytes loaded
0DB9: 3008 jr nc, load_loop ; OK - start reading hex characters
0DBB: E5 push hl ; Save address
0DBC: 213D0E ld hl, load_msg_3 ; Print warning message
0DBF: CD4B12 call puts
0DC2: E1 pop hl ; Restore address
0DC3: ;
0DC3: ; All in all we need two hex nibbles per byte. If two characters
0DC3: ; in a row are valid hexadecimal digits we will convert them
0DC3: ; to a byte and store this in memory. If one character is
0DC3: ; illegal, the load routine terminates and returns to the
0DC3: ; monitor.
0DC3: ;
0DC3: 3E20 load_loop ld a, ' '
0DC5: CD4012 call putc ; Write a space as byte delimiter
0DC8: CD5911 call getc ; Read first character
0DCB: CD4311 call to_upper ; Convert to upper case
0DCE: CDF610 call is_hex ; Is it a hex digit?
0DD1: 3026 jr nc, load_exit ; No - exit the load routine
0DD3: CD1311 call nibble2val ; Convert character to value
0DD6: CD2312 call print_nibble ; Echo hex digit
0DD9: CB07 rlc a
0DDB: CB07 rlc a
0DDD: CB07 rlc a
0DDF: CB07 rlc a
0DE1: 47 ld b, a ; Save the upper four bits for later
0DE2: CD5911 call getc ; Read second character and proceed...
0DE5: CD4311 call to_upper ; Convert to upper case
0DE8: CDF610 call is_hex
0DEB: 300C jr nc, load_exit
0DED: CD1311 call nibble2val
0DF0: CD2312 call print_nibble
0DF3: B0 or b ; Combine lower 4 bits with upper
0DF4: 77 ld (hl), a ; Save value to memory
0DF5: 23 inc hl
0DF6: 13 inc de
0DF7: 18CA jr load_loop ; Get next byte (or at least try to)
0DF9: CD4C11 load_exit call crlf ; Finished...
0DFC: 626B ld hl, de ; Print number of bytes loaded
0DFE: CD3312 call print_word
0E01: 212C0E ld hl, load_msg_2
0E04: CD4B12 call puts
0E07: E1 pop hl
0E08: D1 pop de
0E09: C1 pop bc
0E0A: F1 pop af
0E0B: C9 ret
0E0C: 4C4F4144
0E10: 20287878
0E14: 206F7220
0E18: 656C7365
0E1C: 20746F20
0E20: 656E6429
0E24: 3A204144
0E28: 44523D00 load_msg_1 defb "LOAD (xx or else to end): ADDR=", eos
0E2C: 20627974
0E30: 6573206C
0E34: 6F616465
0E38: 642E0D0A
0E3C: 00 load_msg_2 defb " bytes loaded.", cr, lf, eos
0E3D: 0D0A5772
0E41: 69746520
0E45: 746F206C
0E49: 6F776572
0E4D: 206D656D
0E51: 6F727920
0E55: 62616E6B
0E59: 2C207072
0E5D: 6F626162
0E61: 6C792052
0E65: 4F4D3A20 load_msg_3 defb cr, lf, "Write to lower memory bank, probably ROM: "
0E69: 00 defb eos
0E6A: ;
0E6A: ;
0E6A: ; Load and run an executable.
0E6A: ;
0E6A: F5 load_and_run push af
0E6B: C5 push bc
0E6C: D5 push de
0E6D: E5 push hl
0E6E: FDE5 push iy
0E70: 21B20E ld hl, lar_msg_1
0E73: CD4B12 call puts ; Prompt for the filename
0E76: 216DFB ld hl, string_81_bfr
0E79: 0651 ld b, 81 ; Buffer length
0E7B: CDBA11 call gets ; Read filename
0E7E: CD5B12 call stroup ; Convert to upper case
0E81: FD21BEFB ld iy, fcb ; Prepare file open operation
0E85: 1161FB ld de, string_12_bfr
0E88: CD1C17 call fopen ; Open the file
0E8B: 210080 ld hl, ram_start ; The program is loaded into RAM
0E8E: 110000 ld de, 0 ; Counter for the number of bytes read
0E91: CD6114 lar_loop call fgetc ; Get one byte from the file
0E94: 3805 jr c, lar_exit ; EOF reached?
0E96: 77 ld (hl), a ; Save one byte into RAM
0E97: 23 inc hl ; Increment address pointer
0E98: 13 inc de ; Increment counter of bytes loaded
0E99: 18F6 jr lar_loop ; Get next byte
0E9B: CD4C11 lar_exit call crlf
0E9E: 626B ld hl, de ; How many bytes were read?
0EA0: CD3312 call print_word
0EA3: 21C10E ld hl, lar_msg_2
0EA6: CD4B12 call puts
0EA9: FDE1 pop iy
0EAB: E1 pop hl
0EAC: D1 pop de
0EAD: C1 pop bc
0EAE: F1 pop af
0EAF: C30080 jp ram_start ; Start the executable
0EB2: 52554E3A
0EB6: 2046494C
0EBA: 454E414D
0EBE: 453D00 lar_msg_1 defb "RUN: FILENAME=", eos
0EC1: 20627974
0EC5: 6573206C
0EC9: 6F616465
0ECD: 642E2052
0ED1: 756E2E2E
0ED5: 2E0D0A0D
0ED9: 0A00 lar_msg_2 defb " bytes loaded. Run...", cr, lf, cr, lf, eos
0EDB: ;
0EDB: ; Load a file's contents into memory:
0EDB: ;
0EDB: F5 load_file push af
0EDC: C5 push bc
0EDD: D5 push de
0EDE: E5 push hl
0EDF: FDE5 push iy
0EE1: 213D0F ld hl, load_file_msg_1
0EE4: CD4B12 call puts ; Print first prompt (start address)
0EE7: CDAF11 call get_word ; Wait for the start address (2 bytes)
0EEA: 225FFB ld (load_file_scrat), hl
0EED: A7 and a ; Clear carry
0EEE: 010080 ld bc, ram_start ; Check if the address is valid
0EF1: ED42 sbc hl, bc ; by subtracting the RAM start address
0EF3: 3008 jr nc, load_file_1
0EF5: 214E0F ld hl, load_file_msg_2
0EF8: CD4B12 call puts
0EFB: 182B jr load_file_exit ; Illegal address - exit routine
0EFD: 21710F load_file_1 ld hl, load_file_msg_4
0F00: CD4B12 call puts ; Prompt for filename
0F03: 216DFB ld hl, string_81_bfr
0F06: 0651 ld b, 81 ; Buffer length
0F08: CDBA11 call gets ; Read file name into bfr
0F0B: CD5B12 call stroup ; Convert to upper case
0F0E: FD21BEFB ld iy, fcb ; Prepare open (only one FCB currently)
0F12: 1161FB ld de, string_12_bfr
0F15: CD1C17 call fopen ; Open the file (if possible)
0F18: 2A5FFB ld hl, (load_file_scrat)
0F1B: 110000 ld de, 0 ; Counter for bytes loaded
0F1E: CD6114 load_file_loop call fgetc ; Get one byte from the file
0F21: 3805 jr c, load_file_exit
0F23: 77 ld (hl), a ; Store byte and
0F24: 23 inc hl ; increment pointer
0F25: 13 inc de
0F26: 18F6 jr load_file_loop ; Process next byte
0F28: CD4C11 load_file_exit call crlf
0F2B: 626B ld hl, de ; Print number of bytes loaded
0F2D: CD3312 call print_word
0F30: 21600F ld hl, load_file_msg_3
0F33: CD4B12 call puts
0F36: FDE1 pop iy
0F38: E1 pop hl
0F39: D1 pop de
0F3A: C1 pop bc
0F3B: F1 pop af
0F3C: C9 ret
0F3D: 4C4F4144
0F41: 2046494C
0F45: 453A2041
0F49: 4444523D
0F4D: 00 load_file_msg_1 defb "LOAD FILE: ADDR=", eos
0F4E: 20496C6C
0F52: 6567616C
0F56: 20616464
0F5A: 72657373
0F5E: 2100 load_file_msg_2 defb " Illegal address!", eos
0F60: 20627974
0F64: 6573206C
0F68: 6F616465
0F6C: 642E0D0A
0F70: 00 load_file_msg_3 defb " bytes loaded.", cr, lf, eos
0F71: 2046494C
0F75: 454E414D
0F79: 453D00 load_file_msg_4 defb " FILENAME=", eos
0F7C: ;
0F7C: ; mount - a wrapper for fatmount (necessary for printing the command's name)
0F7C: ;
0F7C: E5 mount push hl
0F7D: 21880F ld hl, mount_msg
0F80: CD4B12 call puts
0F83: CD4A1A call fatmount
0F86: E1 pop hl
0F87: C9 ret
0F88: 4D4F554E
0F8C: 540D0A0D
0F90: 0A00 mount_msg defb "MOUNT", cr, lf, cr, lf, eos
0F92: ;
0F92: ; Move a memory block - the user is prompted for all necessary data:
0F92: ;
0F92: F5 move push af ; We won't even destroy the flags!
0F93: C5 push bc
0F94: D5 push de
0F95: E5 push hl
0F96: 21D20F ld hl, move_msg_1
0F99: CD4B12 call puts
0F9C: CDAF11 call get_word ; Get address of block to be moved
0F9F: E5 push hl ; Push this address
0FA0: 21DE0F ld hl, move_msg_2
0FA3: CD4B12 call puts
0FA6: CDAF11 call get_word ; Get destination start address
0FA9: 545D ld de, hl ; LDIR requires this in DE
0FAB: ; Is the destination address in RAM area?
0FAB: A7 and a ; Clear carry
0FAC: 010080 ld bc, ram_start
0FAF: ED42 sbc hl, bc ; Is the destination in RAM?
0FB1: 3009 jr nc, move_get_length
0FB3: 21EC0F ld hl, move_msg_4 ; No - print error message
0FB6: CD4B12 call puts
0FB9: E1 pop hl ; Clean up stack
0FBA: 180E jr move_exit
0FBC: 21E30F move_get_length ld hl, move_msg_3
0FBF: CD4B12 call puts
0FC2: CDAF11 call get_word ; Get length of block
0FC5: 444D ld bc, hl ; LDIR requires the length in BC
0FC7: E1 pop hl ; Get address of block to be moved
0FC8: ; I was lazy - there is no test to make sure that the block
0FC8: ; to be moved will fit into the RAM area.
0FC8: EDB0 ldir ; Move block
0FCA: CD4C11 move_exit call crlf ; Finished
0FCD: E1 pop hl ; Restore registers
0FCE: D1 pop de
0FCF: C1 pop bc
0FD0: F1 pop af
0FD1: C9 ret
0FD2: 4D4F5645
0FD6: 3A204652
0FDA: 4F4D3D00 move_msg_1 defb "MOVE: FROM=", eos
0FDE: 20544F3D
0FE2: 00 move_msg_2 defb " TO=", eos
0FE3: 204C454E
0FE7: 4754483D
0FEB: 00 move_msg_3 defb " LENGTH=", eos
0FEC: 20496C6C
0FF0: 6567616C
0FF4: 20646573
0FF8: 74696E61
0FFC: 74696F6E
1000: 20616464
1004: 72657373
1008: 2100 move_msg_4 defb " Illegal destination address!", eos
100A: ;
100A: ; Dump the contents of both register banks:
100A: ;
100A: F5 rdump push af
100B: E5 push hl
100C: 215110 ld hl, rdump_msg_1 ; Print first two lines
100F: CD4B12 call puts
1012: E1 pop hl
1013: CD8210 call rdump_one_set
1016: D9 exx
1017: 08 ex af, af'
1018: E5 push hl
1019: 216810 ld hl, rdump_msg_2
101C: CD4B12 call puts
101F: E1 pop hl
1020: CD8210 call rdump_one_set
1023: 08 ex af, af'
1024: D9 exx
1025: E5 push hl
1026: 216E10 ld hl, rdump_msg_3
1029: CD4B12 call puts
102C: DDE5 push ix
102E: E1 pop hl
102F: CD3312 call print_word
1032: 217810 ld hl, rdump_msg_4
1035: CD4B12 call puts
1038: FDE5 push iy
103A: E1 pop hl
103B: CD3312 call print_word
103E: 217D10 ld hl, rdump_msg_5
1041: CD4B12 call puts
1044: 210000 ld hl, 0
1047: 39 add hl, sp
1048: CD3312 call print_word
104B: CD4C11 call crlf
104E: E1 pop hl
104F: F1 pop af
1050: C9 ret
1051: 52454749
1055: 53544552
1059: 2044554D
105D: 500D0A0D
1061: 0A093173
1065: 743A00 rdump_msg_1 defb "REGISTER DUMP", cr, lf, cr, lf, tab, "1st:", eos
1068: 09326E64
106C: 3A00 rdump_msg_2 defb tab, "2nd:", eos
106E: 09505452
1072: 3A204958
1076: 3D00 rdump_msg_3 defb tab, "PTR: IX=", eos
1078: 2049593D
107C: 00 rdump_msg_4 defb " IY=", eos
107D: 2053503D
1081: 00 rdump_msg_5 defb " SP=", eos
1082: ;
1082: E5 rdump_one_set push hl ; Print one register set
1083: 21B210 ld hl, rdump_os_msg_1
1086: CD4B12 call puts
1089: F5 push af ; Move AF into HL
108A: E1 pop hl
108B: CD3312 call print_word ; Print contents of AF
108E: 21B710 ld hl, rdump_os_msg_2
1091: CD4B12 call puts
1094: 6069 ld hl, bc
1096: CD3312 call print_word ; Print contents of BC
1099: 21BC10 ld hl, rdump_os_msg_3
109C: CD4B12 call puts
109F: 626B ld hl, de
10A1: CD3312 call print_word ; Print contents of DE
10A4: 21C110 ld hl, rdump_os_msg_4
10A7: CD4B12 call puts
10AA: E1 pop hl ; Restore original HL
10AB: CD3312 call print_word ; Print contents of HL
10AE: CD4C11 call crlf
10B1: C9 ret
10B2: 2041463D
10B6: 00 rdump_os_msg_1 defb " AF=", eos
10B7: 2042433D
10BB: 00 rdump_os_msg_2 defb " BC=", eos
10BC: 2044453D
10C0: 00 rdump_os_msg_3 defb " DE=", eos
10C1: 20484C3D
10C5: 00 rdump_os_msg_4 defb " HL=", eos
10C6: ;
10C6: #if N8VEM = 1
10C6: ;
10C6: ; rom2ram copies the ROM contents to upper RAM, switches the lower 32 kB memory
10C6: ; bank to RAM, and copies the ROM contents back. This functionality is quite
10C6: ; handy to perform patches to the monitor without having to burn an EPROM.
10C6: ;
10C6: rom2ram push hl
10C6: ld hl, rom2ram_m1
10C6: call puts
10C6: ld hl, rom_start ; Copy from this address
10C6: ld de, ram_start ; to this address
10C6: ld bc, end_of_monitor - rom_start + 1
10C6: ldir ; Copy BC bytes
10C6: ; Now we have a copy of this monitor in the upper 32 KB
10C6: ; RAM bank. We will now jump into this copy to perform the
10C6: ; second copy step without crashing:
10C6: jp rom2ram_switch + ram_start
10C6: rom2ram_switch ld a, $8f ; Select highest RAM bank
10C6: out (mpcl_ram), a ; for lower 32 kB of memory
10C6: ld a, $80 ; Disable ROM, enable RAM
10C6: out (mpcl_rom), a ; Switch it!
10C6: ld hl, ram_start ; Prepare second copy step - source,
10C6: ld de, rom_start ; destination, length remains the same
10C6: ldir ; Copy block
10C6: ld hl, rom2ram_m2 ; Print completion message
10C6: call puts
10C6: pop hl
10C6: ret
10C6: rom2ram_m1 defb "ROM2RAM", cr, lf, eos
10C6: rom2ram_m2 defb tab, "The monitor is now running in lower RAM."
10C6: defb cr, lf, eos
10C6: ;
10C6: #endif
10C6: ;
10C6: ; Start a program - this will prompt for a four digital hexadecimal start
10C6: ; address. A program should end with "jp $0" to enter the monitor again.
10C6: ;
10C6: 21D310 start ld hl, start_msg
10C9: CD4B12 call puts
10CC: CDAF11 call get_word ; Wait for a four-nibble address
10CF: CD4C11 call crlf
10D2: E9 jp (hl) ; Start program (and hope for the best)
10D3: 53544152
10D7: 543A2041
10DB: 4444523D
10DF: 00 start_msg defb "START: ADDR=", eos
10E0: ;
10E0: ;
10E0: ; unmount - simple wrapper for fatunmount (necessary for printing the command
10E0: ; name)
10E0: ;
10E0: E5 unmount push hl
10E1: 21EC10 ld hl, unmount_msg
10E4: CD4B12 call puts
10E7: CD421D call fatunmount
10EA: E1 pop hl
10EB: C9 ret
10EC: 554E4D4F
10F0: 554E540D
10F4: 0A00 unmount_msg defb "UNMOUNT", cr, lf, eos
10F6: ;
10F6: ;******************************************************************************
10F6: ;***
10F6: ;*** String routines
10F6: ;***
10F6: ;******************************************************************************
10F6: ;
10F6: ; is_hex checks a character stored in A for being a valid hexadecimal digit.
10F6: ; A valid hexadecimal digit is denoted by a set C flag.
10F6: ;
10F6: FE47 is_hex cp 'F' + 1 ; Greater than 'F'?
10F8: D0 ret nc ; Yes
10F9: FE30 cp '0' ; Less than '0'?
10FB: 3002 jr nc, is_hex_1 ; No, continue
10FD: 3F ccf ; Complement carry (i.e. clear it)
10FE: C9 ret
10FF: FE3A is_hex_1 cp '9' + 1 ; Less or equal '9*?
1101: D8 ret c ; Yes
1102: FE41 cp 'A' ; Less than 'A'?
1104: 3002 jr nc, is_hex_2 ; No, continue
1106: 3F ccf ; Yes - clear carry and return
1107: C9 ret
1108: 37 is_hex_2 scf ; Set carry
1109: C9 ret
110A: ;
110A: ; is_print checks if a character is a printable ASCII character. A valid
110A: ; character is denoted by a set C flag.
110A: ;
110A: FE20 is_print cp space
110C: 3002 jr nc, is_print_1
110E: 3F ccf
110F: C9 ret
1110: FE7F is_print_1 cp $7f
1112: C9 ret
1113: ;
1113: ; nibble2val expects a hexadecimal digit (upper case!) in A and returns the
1113: ; corresponding value in A.
1113: ;
1113: FE3A nibble2val cp '9' + 1 ; Is it a digit (less or equal '9')?
1115: 3802 jr c, nibble2val_1 ; Yes
1117: D607 sub 7 ; Adjust for A-F
1119: D630 nibble2val_1 sub '0' ; Fold back to 0..15
111B: E60F and $f ; Only return lower 4 bits
111D: C9 ret
111E: ;
111E: ; strchr: HL points to the string to be searched, A contains the desired
111E: ; character. On return HL contains the address of the position where
111E: ; the character was found. If carry is set upon return the character
111E: ; was found.
111E: ;
111E: C5 strchr push bc
111F: 47 ld b, a ; Remember character
1120: 7E strchr_loop ld a, (hl) ; Compare one character
1121: FE00 cp 0 ; Not really necessary...
1123: 2806 jr z, strchr_not ; Terminating 0 found?
1125: B8 cp b ; Is it the one we are lkg. for?
1126: 2806 jr z, strchr_found ; Yes!
1128: 23 inc hl ; Increment pointer
1129: 18F5 jr strchr_loop ; Cmp. next character
112B: A7 strchr_not and a ; Reset carry flag
112C: 1801 jr strchr_exit
112E: 37 strchr_found scf ; Set carry
112F: C1 strchr_exit pop bc
1130: C9 ret
1131: ;
1131: ; Compare two null terminated strings, return >0 / 0 / exit
113B: 23 inc hl ; Prepare comparing the next
113C: 13 inc de ; characters
113D: 18F4 jr strcmp_loop
113F: 96 strcmp_exit sub (hl)
1140: E1 pop hl
1141: D1 pop de
1142: C9 ret
1143: ;
1143: ; Convert a single character contained in A to upper case:
1143: ;
1143: FE61 to_upper cp 'a' ; Nothing to do if not lower case
1145: D8 ret c
1146: FE7B cp 'z' + 1 ; > 'z'?
1148: D0 ret nc ; Nothing to do, either
1149: E65F and $5f ; Convert to upper case
114B: C9 ret
114C: ;
114C: ;******************************************************************************
114C: ;***
114C: ;*** IO routines
114C: ;***
114C: ;******************************************************************************
114C: ;
114C: ; Send a CR/LF pair:
114C: ;
114C: F5 crlf push af
114D: 3E0D ld a, cr
114F: CD4012 call putc
1152: 3E0A ld a, lf
1154: CD4012 call putc
1157: F1 pop af
1158: C9 ret
1159: ;
1159: ; Read a single character from the serial line, result is in A. This is a
1159: ; blocking system call - it will wait for a character to read! If a non
1159: ; blocking getc is needed, call getc_nowait instead.
1159: ;
1159: DB05 getc in a, (uart_register_5)
115B: CB47 bit 0, a
115D: 28FA jr z, getc ; Wait until there is a character
115F: DB00 getc_nowait in a, (uart_register_0)
1161: FE19 cp ctrl_y ; Was it a CTRL-Y?
1163: CA6711 jp z, getc_ctrl_y ; Yes, reset stack pointer and
1166: C9 ret
1167: 313BFB getc_ctrl_y ld sp, start_type - $1
116A: 217311 ld hl, ctrl_y_msg ; HL is no longer needed since we will
116D: ; reenter the main loop
116D: CD4B12 call puts
1170: C35500 jp main_loop ; reenter the monitor without
1173: ; printing a welcome message
1173: 0D0A092A
1177: 2A2A2049
117B: 4E544552
117F: 52555054
1183: 202A2A2A
1187: 0D0A00 ctrl_y_msg defb cr, lf, tab, "*** INTERRUPT ***", cr, lf, eos
118A: ;
118A: ; Get a byte in hexadecimal notation. The result is returned in A. Since
118A: ; the routine get_nibble is used only valid characters are accepted - the
118A: ; input routine only accepts characters 0-9a-f.
118A: ;
118A: C5 get_byte push bc ; Save contents of B (and C)
118B: CD9D11 call get_nibble ; Get upper nibble
118E: CB07 rlc a
1190: CB07 rlc a
1192: CB07 rlc a
1194: CB07 rlc a
1196: 47 ld b, a ; Save upper four bits
1197: CD9D11 call get_nibble ; Get lower nibble
119A: B0 or b ; Combine both nibbles
119B: C1 pop bc ; Restore B (and C)
119C: C9 ret
119D: ;
119D: ; Get a hexadecimal digit from the serial line. This routine blocks until
119D: ; a valid character (0-9a-f) has been entered. A valid digit will be echoed
119D: ; to the serial line interface. The lower 4 bits of A contain the value of
119D: ; that particular digit.
119D: ;
119D: CD5911 get_nibble call getc ; Read a character
11A0: CD4311 call to_upper ; Convert to upper case
11A3: CDF610 call is_hex ; Was it a hex digit?
11A6: 30F5 jr nc, get_nibble ; No, get another character
11A8: CD1311 call nibble2val ; Convert nibble to value
11AB: CD2312 call print_nibble
11AE: C9 ret
11AF: ;
11AF: ; Get a word (16 bit) in hexadecimal notation. The result is returned in HL.
11AF: ; Since the routines get_byte and therefore get_nibble are called, only valid
11AF: ; characters (0-9a-f) are accepted.
11AF: ;
11AF: F5 get_word push af
11B0: CD8A11 call get_byte ; Get the upper byte
11B3: 67 ld h, a
11B4: CD8A11 call get_byte ; Get the lower byte
11B7: 6F ld l, a
11B8: F1 pop af
11B9: C9 ret
11BA: ;
11BA: ; Read a string from STDIN - HL contains the buffer start address,
11BA: ; B contains the buffer length. This routine handles the backspace character
11BA: ; correctly and will echo deleted characters enclosed in backspaces (as it
11BA: ; was customary with real Teletypes used as terminals). If there are more
11BA: ; backspaces entered than there are characters to be deleted, a BEL character
11BA: ; is sent and no further action is taken.
11BA: ;
11BA: F5 gets push af
11BB: C5 push bc
11BC: D5 push de
11BD: E5 push hl
11BE: 0E00 ld c, 0 ; Input mode
11C0: 545D ld de, hl ; Remember start of buffer
11C2: CD5911 gets_loop call getc ; Get a single character
11C5: FE0D cp cr ; Skip CR characters
11C7: 28F9 jr z, gets_loop ; only LF will terminate input
11C9: FE08 cp bs ; Is it a backspace?
11CB: 2021 jr nz, gets_1 ; No
11CD: 79 ld a, c ; In which mode are we?
11CE: FE00 cp 0
11D0: 2007 jr nz, gets_bs ; Already in backspace
11D2: 0E01 ld c, 1 ; First time in backspace mode
11D4: 3E2F ld a, '/' ; Print a slash
11D6: CD4012 call putc
11D9: E5 gets_bs push hl ; Remember HL (needed soon)
11DA: A7 and a ; Clear carry
11DB: ED52 sbc hl, de ; Are we at the buffer start?
11DD: E1 pop hl ; Restore HL
11DE: 2007 jr nz, gets_del ; Not at start, delete char.
11E0: 3E07 ld a, bel ; Too many backspaces,
11E2: CD4012 call putc ; ring the bell
11E5: 18DB jr gets_loop ; and read the next character
11E7: 2B gets_del dec hl ; Delete one character
11E8: 7E ld a, (hl) ; Get character from buffer
11E9: CD4012 call putc ; and echo it
11EC: 18D4 jr gets_loop ; Read next character
11EE: F5 gets_1 push af ; Remember character
11EF: 79 ld a, c ; Did we come from backspace?
11F0: FE00 cp 0
11F2: 2807 jr z, gets_2 ; No
11F4: 0E00 ld c, 0 ; Yes - reset mode
11F6: 3E5C ld a, '\' ; Print backslash
11F8: CD4012 call putc
11FB: F1 gets_2 pop af ; Restore character
11FC: CD4012 call putc ; Echo character
11FF: FE0A cp lf ; Terminate string at
1201: 2808 jr z, gets_exit ; LF or
1203: FE0D cp cr ; CR?
1205: 2804 jr z, gets_exit
1207: 77 ld (hl), a ; Copy character to buffer
1208: 23 inc hl
1209: 10B7 djnz gets_loop
120B: 3600 gets_exit ld (hl), 0 ; Insert termination byte
120D: E1 pop hl
120E: D1 pop de
120F: C1 pop bc
1210: F1 pop af
1211: C9 ret
1212: ;
1212: ; print_byte prints a single byte in hexadecimal notation to the serial line.
1212: ; The byte to be printed is expected to be in A.
1212: ;
1212: F5 print_byte push af ; Save the contents of the registers
1213: C5 push bc
1214: 47 ld b, a
1215: 0F rrca
1216: 0F rrca
1217: 0F rrca
1218: 0F rrca
1219: CD2312 call print_nibble ; Print high nibble
121C: 78 ld a, b
121D: CD2312 call print_nibble ; Print low nibble
1220: C1 pop bc ; Restore original register contents
1221: F1 pop af
1222: C9 ret
1223: ;
1223: ; print_nibble prints a single hex nibble which is contained in the lower
1223: ; four bits of A:
1223: ;
1223: F5 print_nibble push af ; We won't destroy the contents of A
1224: E60F and $f ; Just in case...
1226: C630 add '0' ; If we have a digit we are done here.
1228: FE3A cp '9' + 1 ; Is the result > 9?
122A: 3802 jr c, print_nibble_1
122C: C607 add 'A' - '0' - $a ; Take care of A-F
122E: CD4012 print_nibble_1 call putc ; Print the nibble and
1231: F1 pop af ; restore the original value of A
1232: C9 ret
1233: ;
1233: ; print_word prints the four hex digits of a word to the serial line. The
1233: ; word is expected to be in HL.
1233: ;
1233: E5 print_word push hl
1234: F5 push af
1235: 7C ld a, h
1236: CD1212 call print_byte
1239: 7D ld a, l
123A: CD1212 call print_byte
123D: F1 pop af
123E: E1 pop hl
123F: C9 ret
1240: ;
1240: ; Send a single character to the serial line (a contains the character):
1240: ;
1240: F5 putc push af
1241: DB05 tx_ready_loop in a, (uart_register_5)
1243: CB6F bit 5, a
1245: 28FA jr z, tx_ready_loop
1247: F1 pop af
1248: D300 out (uart_register_0), a
124A: C9 ret
124B: ;
124B: ; Send a string to the serial line, HL contains the pointer to the string:
124B: ;
124B: F5 puts push af
124C: E5 push hl
124D: 7E puts_loop ld a, (hl)
124E: FE00 cp eos ; End of string reached?
1250: 2806 jr z, puts_end ; Yes
1252: CD4012 call putc
1255: 23 inc hl ; Increment character pointer
1256: 18F5 jr puts_loop ; Transmit next character
1258: E1 puts_end pop hl
1259: F1 pop af
125A: C9 ret
125B: ;
125B: ; stroup converts a string to upper case
125B: ;
125B: F5 stroup push af
125C: E5 push hl
125D: 7E stroup_loop ld a, (hl) ; Get a character
125E: FE00 cp 0 ; End of string reached?
1260: 2807 jr z, stroup_exit ; Yes
1262: CD4311 call to_upper ; No, convert to upper case
1265: 77 ld (hl), a ; Write the character back to memory
1266: 23 inc hl ; Prepare for next character
1267: 18F4 jr stroup_loop
1269: E1 stroup_exit pop hl
126A: F1 pop af
126B: C9 ret
126C: ;
126C: ; Test the UART status, RX status -> carry flag, TX status -> Z flag
126C: ; C == 1: A character is available in the buffer.
126C: ; Z == 1: A character can be sent.
126C: ;
126C: DB05 uart_status in a, (uart_register_5)
126E: 0F rrca ; Rotate RX status into carry
126F: CB67 bit 4, a ; Check TX status (after rot!)
1271: C9 ret
1272: ;
1272: ;
1272: ;******************************************************************************
1272: ;***
1272: ;*** IDE routines
1272: ;***
1272: ;******************************************************************************
1272: ;
1272: ; Miscellaneous contants:
1272: ;
1272: ide_retries equ $ff
1272: ;
1272: #if N8VEM = 1
1272: ;
1272: ; Control bytes for setting up the 82C55 for reading/writing from/to an
1272: ; IDE device:
1272: ;
1272: ppide_rd equ $92 ; CTL -> out, LSB/MSB out
1272: ;
1272: ; Constants for IDE control lines:
1272: ;
1272: line_ide_a0 equ $01
1272: line_ide_a1 equ $02
1272: line_ide_a2 equ $04
1272: line_ide_cs0 equ $08
1272: line_ide_cs1 equ $10
1272: line_ide_wr equ $20
1272: line_ide_rd equ $40
1272: line_ide_rst equ $80
1272: ;
1272: ; Combined constants for various IDE-registers:
1272: ;
1272: reg_ide_data equ line_ide_cs0
1272: reg_ide_err equ line_ide_cs0 + line_ide_a0
1272: ;
1272: ; Bit mapping of ide_error_code register:
1272: ;
1272: ; 0: 1 = DAM not found
1272: ; 1: 1 = Track 0 not found
1272: ; 2: 1 = Command aborted
1272: ; 3: Reserved
1272: ; 4: 1 = ID not found
1272: ; 5: Reserved
1272: ; 6: 1 = Uncorrectable ECC error
1272: ; 7: 1 = Bad block detected
1272: ;
1272: reg_ide_secnum equ line_ide_cs0 + line_ide_a1
1272: ;
1272: ; Typically set to 1 sector to be transferred
1272: ;
1272: reg_ide_lba0 equ line_ide_cs0 + line_ide_a1 + line_ide_a0
1272: reg_ide_lba1 equ line_ide_cs0 + line_ide_a2
1272: reg_ide_lba2 equ line_ide_cs0 + line_ide_a2 + line_ide_a0
1272: reg_ide_lba3 equ line_ide_cs0 + line_ide_a2 + line_ide_a1
1272: ;
1272: ; Bit mapping of ide_lba3 register:
1272: ;
1272: ; 0 - 3: LBA bits 24 - 27
1272: ; 4 : Master (0) or slave (1) selection
1272: ; 5 : Always 1
1272: ; 6 : Set to 1 for LBA access
1272: ; 7 : Always 1
1272: ;
1272: reg_ide_cmd equ line_ide_cs0 + line_ide_a2 + line_ide_a1 + line_ide_a0
1272: reg_ide_status equ reg_ide_cmd
1272: ;
1272: ; Useful commands (when written):
1272: ;
1272: ; $20: Read sectors with retry
1272: ; $30: Write sectors with retry
1272: ; $EC: Identify drive
1272: ;
1272: ; Status bits (when read):
1272: ;
1272: ; 0 = ERR: 1 = Previous command resulted in an error
1272: ; 1 = IDX: Unused
1272: ; 2 = CORR: Unused
1272: ; 3 = DRQ: 1 = Data Request Ready (sector buffer ready)
1272: ; 4 = DSC: Unused
1272: ; 5 = DF: 1 = Write fault
1272: ; 6 = RDY: 1 = Ready to accept command
1272: ; 7 = BUSY: 1 = Controller is busy executing a command
1272: ;
1272: reg_ide_cntl equ line_ide_cs1 + line_ide_a2 + line_ide_a1
1272: reg_ide_astatus equ line_ide_cs1 + line_ide_a2 + line_ide_a1 + line_ide_a0
1272: ;
1272: ; IDE commands:
1272: ;
1272: ide_cmd_recal equ $10
1272: ide_cmd_read equ $20
1272: ide_cmd_write equ $30
1272: ide_cmd_init equ $91
1272: ide_cmd_id equ $ec
1272: ide_cmd_down equ $e0
1272: ide_cmd_spinup equ $e1
1272: ;
1272: ; IDE routines:
1272: ;
1272: ; Test if the buffer of the IDE disk drive is ready for transfer. If not,
1272: ; carry will be set, otherwise carry is reset. The contents of register A will
1272: ; be destroyed!
1272: ;
1272: ide_bfr_ready push bc
1272: and a ; Clear carry assuming no error
1272: ld b, ide_retries ; How many retries?
1272: ide_bfr_loop ld a, reg_ide_status ; Prepare reading the IDE SR
1272: call ppide_read ; Read status register
1272: ld a, c ; Get lower 8 bits
1272: bit 3, a ; Check DRQ bit
1272: jr nz, ide_bfr_exit ; Buffer is ready
1272: push bc
1272: ld b, $0 ; Wait a moment
1272: ide_bfr_wait nop
1272: djnz ide_bfr_wait
1272: pop bc
1272: djnz ide_bfr_loop ; Retry
1272: scf ; Set carry to indicate timeout
1272: ld hl, ide_bfr_rdy_err
1272: call puts
1272: ide_bfr_exit pop bc
1272: ret
1272: ide_bfr_rdy_err defb "FATAL(IDE): ide_bfr_ready timeout!", cr, lf, eos
1272: ;
1272: ; Test if there is any error flagged by the drive. If carry is cleared, no
1272: ; error occured, otherwise carry will be set. The contents of register A will
1272: ; be destroyed.
1272: ;
1272: ide_error_check and a ; Clear carry (no err expected)
1272: ld a, reg_ide_status ; Prepare reading the IDE SR
1272: call ppide_read ; Read the status register
1272: ld a, c ; Get lower 8 bits
1272: bit 0, a ; Test error bit
1272: jr z, ide_ec_exit ; Everything is OK
1272: scf ; Set carry due to error
1272: ide_ec_exit ret
1272: ;
1272: ; Get ID information from drive. HL is expected to point to a 512 byte byte
1272: ; sector buffer. If carry is set, the function did not complete correctly and
1272: ; was aborted.
1272: ;
1272: ide_get_id push af
1272: push bc
1272: push hl
1272: call ide_ready ; Is the drive ready?
1272: jr c, ide_get_id_err ; No - timeout!
1272: ld c, $a0 ; Master, no LBA addressing
1272: ld a, reg_ide_lba3 ; Prepare writing LBA3
1272: call ppide_write ; Perform write access
1272: call ide_ready ; Did the command complete?
1272: jr c, ide_get_id_err ; Timeout!
1272: ld c, $ec ; Command to read ID
1272: ld a, reg_ide_cmd ; Prepare writing CMD register
1272: call ppide_write
1272: call ide_ready ; Can we proceed?
1272: jr c, ide_get_id_err ; No - timeout, propagate carry
1272: call ide_error_check ; Any errors?
1272: jr c, ide_get_id_err ; Yes - something went wrong
1272: call ide_bfr_ready ; Is the buffer ready to read?
1272: jr c, ide_get_id_err ; No
1272: ld hl, buffer ; Load the buffer's address
1272: ld b, $0 ; We will read 256 words
1272: ide_get_id_lp push bc ; PPIDE routines destroy BC!
1272: ld a, reg_ide_data ; Read 16 bits of data
1272: call ppide_read ; into BC (MSB/LSB)
1272: ld (hl), b ; Store high byte
1272: inc hl ; Increment address pointer
1272: ld (hl), c ; Store low byte
1272: inc hl ; Increment address pointer
1272: pop bc ; Restore BC (loop counter)
1272: djnz ide_get_id_lp ; Read next word
1272: jr ide_get_id_exit ; Everything OK, just exit
1272: ide_get_id_err ld hl, ide_get_id_msg ; Print error message
1272: call puts
1272: ide_get_id_exit pop hl
1272: pop bc
1272: pop af
1272: ret
1272: ide_get_id_msg defb "FATAL(IDE): Aborted!", cr, lf
1272: ;
1272: ; Test if the IDE drive is not busy and ready to accept a command. If it is
1272: ; ready the carry flag will be reset and the function returns. If a time out
1272: ; occurs, C will be set prior to returning to the caller. Register A will
1272: ; be destroyed!
1272: ;
1272: ide_ready push bc
1272: and a ; Clear carry assuming no error
1272: ld b, ide_retries ; Number of retries to timeout
1272: ide_ready_loop ld a, reg_ide_status ; Prepare reading the IDE SR
1272: call ppide_read ; Read status register
1272: ld a, c ; Get lower 8 bits
1272: and a, $c0 ; Only bits 7 and 6 are needed
1272: xor $40 ; Invert the ready flag
1272: jr z, ide_ready_exit ; Exit if ready and not busy
1272: push bc
1272: ld b, $0 ; Wait a moment
1272: ide_ready_wait nop
1272: djnz ide_ready_wait
1272: pop bc
1272: djnz ide_ready_loop ; Retry
1272: scf ; Set carry due to timeout
1272: ld hl, ide_rdy_error
1272: call puts
1272: ld a, reg_ide_err ; Prepare reading error code
1272: call ppide_read
1272: ld a, c
1272: call print_byte
1272: ide_ready_exit pop bc
1272: ret
1272: ide_rdy_error defb "FATAL(IDE): ide_ready timeout!", cr, lf, eos
1272: ;
1272: ; Read a sector from the drive. If carry is set after return, the function did
1272: ; not complete correctly due to a timeout. HL is expected to contain the start
1272: ; address of the sector buffer while BC and DE contain the sector address
1272: ; (LBA3, 2, 1 and 0). Register A's contents will be destroyed!
1272: ;
1272: ide_rs push bc
1272: push hl
1272: call ide_ready ; Is the drive ready?
1272: jr c, ide_rs_err ; No - timeout!
1272: call ide_set_lba ; Setup the drive's registers
1272: call ide_ready ; Everything OK?
1272: jr c, ide_rs_err ; No - timeout!
1272: ld c, ide_cmd_read ; Prepare a read command
1272: ld a, reg_ide_cmd
1272: call ppide_write ; Issue read command
1272: call ide_ready ; Can we proceed?
1272: jr c, ide_rs_err ; No - timeout, set carry
1272: call ide_error_check ; Any errors?
1272: jr c, ide_rs_err ; Yes - something went wrong
1272: call ide_bfr_ready ; Is the buffer ready to read?
1272: jr c, ide_rs_err ; No
1272: ld b, $0 ; We will read 256 words
1272: ide_rs_loop push bc ; PPIDE-routines destroy BC!
1272: ld a, reg_ide_data ; Read 16 bits of data
1272: call ppide_read ; into BC (MSB/LSB)
1272: ld (hl), c ; Store low byte
1272: inc hl ; Increment address pointer
1272: ld (hl), b ; Store high byte
1272: inc hl ; Increment pointer
1272: pop bc
1272: djnz ide_rs_loop ; Read next word until done
1272: jr ide_rs_exit
1272: ide_rs_err ld hl, ide_rs_err_msg ; Print error message
1272: call puts
1272: ide_rs_exit pop hl
1272: pop bc
1272: ret
1272: ide_rs_err_msg defb "FATAL(IDE): ide_rs timeout!", cr, lf, eos
1272: ;
1272: ; Set sector count and LBA registers of the drive. Registers BC and DE contain
1272: ; the sector address (LBA 3, 2, 1 and 0).
1272: ;
1272: ide_set_lba push af
1272: push bc ; BC will be destroyed
1272: push bc ; ...twice!
1272: ld c, $1 ; We will transfer
1272: ld a, reg_ide_secnum ; one sector at a time
1272: call ppide_write
1272: ld c, e ; Set LBA0
1272: ld a, reg_ide_lba0
1272: call ppide_write
1272: ld c, d ; Set LBA1
1272: ld a, reg_ide_lba1
1272: call ppide_write
1272: pop bc ; Restore BC to LBA2/3
1272: ld a, reg_ide_lba2 ; Set LBA2
1272: call ppide_write
1272: ld a, b ; Special treatment for LBA3
1272: and $0f ; Only bits 0 - 3 are LBA3
1272: or $e0 ; Select LBA and master drive
1272: ld c, a ; Set LBA3
1272: ld a, reg_ide_lba3
1272: call ppide_write
1272: pop bc
1272: pop af
1272: ret
1272: ;
1272: ; Write a sector to the drive. If carry is set after return, the function did
1272: ; not complete correctly due to a timeout. HL is expected to contain the start
1272: ; address of the sector buffer while BC and DE contain the sector address
1272: ; (LBA3, 2, 1 and 0). Register A's contents will be destroyed!
1272: ;
1272: ide_ws push bc
1272: push hl
1272: call ide_ready ; Is the drive ready?
1272: jr c, ide_ws_err ; No - timeout!
1272: call ide_set_lba ; Setup the drive's registers
1272: call ide_ready ; Everything OK?
1272: jr c, ide_ws_err ; No - timeout!
1272: ld c, ide_cmd_write ; Prepare write command
1272: ld a, reg_ide_cmd
1272: call ppide_write ; Execute read command
1272: call ide_ready ; Can we proceed?
1272: jr c, ide_ws_err ; No - timeout, set carry
1272: call ide_error_check ; Any errors?
1272: jr c, ide_ws_err ; Yes - something went wrong
1272: call ide_bfr_ready ; Is the buffer ready to read?
1272: jr c, ide_ws_err ; No
1272: ld b, $0 ; We will write 256 words
1272: ide_ws_loop push bc ; BC will be destroyed
1272: ld c, (hl) ; Read low byte
1272: inc hl ; Increment pointer
1272: ld b, (hl) ; Read high byte
1272: inc hl
1272: ld a, reg_ide_data ; Prepare writing to data reg.
1272: call ppide_write
1272: pop bc
1272: djnz ide_ws_loop
1272: jr ide_ws_exit
1272: ide_ws_err ld hl, ide_ws_err_msg ; Print error message
1272: call puts
1272: ide_ws_exit pop hl
1272: pop bc
1272: ret
1272: ide_ws_err_msg defb "FATAL(IDE): ide_ws timeout!", cr, lf, eos
1272: ;
1272: ; PPIDE-low level IO routines:
1272: ;
1272: ; ppide_read: Read from an IDE controller register. A contains the IDE
1272: ; register address while B and C will hold the 16 bit read from that
1272: ; particular register. Please note that asserting the various control and
1272: ; address lines has to be done sequentially, first asserting the address
1272: ; bits then setting read, doing the actual read access, deasserting read
1272: ; and finally clearing the address bits. A and F will not be changed.
1272: ;
1272: ;
1272: ppide_read push af ; Save register number
1272: push af ; ...twice
1272: ld a, ppide_rd ; Set PPI ports for read acc.
1272: out (reg_ppi_cntl), a ; Configure PPI
1272: pop af ; Restore register number
1272: out (reg_ppide_cntl), a ; Setup address
1272: or line_ide_rd ; Assert read signal
1272: out (reg_ppide_cntl), a ; Send address + read
1272: push af ; We will need the reg. again
1272: in a, (reg_ppide_lsb) ; Read LSB from IDE
1272: ld c, a ; Store it into C
1272: in a, (reg_ppide_msb) ; Read MSB from IDE
1272: ld b, a ; Store it into B
1272: pop af ; Prepare deassertion
1272: xor line_ide_rd ; Clear read signal
1272: out (reg_ppide_cntl), a ; Deassert read
1272: xor a ; Clear address bits, too
1272: out (reg_ppide_cntl), a
1272: pop af ; Restore original contents
1272: ret
1272: ;
1272: ; ppide_write: Perform a write access to an IDE controller register. Register
1272: ; A contains the desired register address, B and C contain the MSB/LSB of the
1272: ; value to be written to the IDE controller. A and F will not be changed.
1272: ;
1272: ppide_write push af ; Save register number
1272: push af ; ...twice
1272: ld a, ppide_wr ; Set PPI ports for write acc.
1272: out (reg_ppi_cntl), a ; Configure PPI
1272: ld a, c ; Get LSB to be written
1272: out (reg_ppide_lsb), a ; Set PPI lines
1272: ld a, b ; Get MSB
1272: out (reg_ppide_msb), a ; Set PPI lines
1272: pop af ; Restore register number
1272: out (reg_ppide_cntl), a ; Setup address lines
1272: or line_ide_wr ; Prepare write line
1272: out (reg_ppide_cntl), a ; Assert write signal
1272: xor line_ide_wr ; Reset write bit
1272: out (reg_ppide_cntl), a ; Deassert write signal
1272: xor a ; Reset address lines
1272: out (reg_ppide_cntl), a ; Deassert all signals
1272: pop af ; Restore original contents
1272: ret
1272: ;
1272: #else ; Homebrew Z80 computer
1272: ;
1272: ide_data_low equ ide_base + $0
1272: ide_data_high equ ide_base + $8
1272: ide_error_code equ ide_base + $1
1272: ;
1272: ; Bit mapping of ide_error_code register:
1272: ;
1272: ; 0: 1 = DAM not found
1272: ; 1: 1 = Track 0 not found
1272: ; 2: 1 = Command aborted
1272: ; 3: Reserved
1272: ; 4: 1 = ID not found
1272: ; 5: Reserved
1272: ; 6: 1 = Uncorrectable ECC error
1272: ; 7: 1 = Bad block detected
1272: ;
1272: ide_secnum equ ide_base + $2
1272: ;
1272: ; Typically set to 1 sector to be transf.
1272: ;
1272: ide_lba0 equ ide_base + $3
1272: ide_lba1 equ ide_base + $4
1272: ide_lba2 equ ide_base + $5
1272: ide_lba3 equ ide_base + $6
1272: ;
1272: ; Bit mapping of ide_lba3 register:
1272: ;
1272: ; 0 - 3: LBA bits 24 - 27
1272: ; 4 : Master (0) or slave (1) selection
1272: ; 5 : Always 1
1272: ; 6 : Set to 1 for LBA access
1272: ; 7 : Always 1
1272: ;
1272: ide_status_cmd equ ide_base + $7
1272: ;
1272: ; Useful commands (when written):
1272: ;
1272: ; $20: Read sectors with retry
1272: ; $30: Write sectors with retry
1272: ; $EC: Identify drive
1272: ;
1272: ; Status bits (when read):
1272: ;
1272: ; 0 = ERR: 1 = Previous command resulted in an error
1272: ; 1 = IDX: Unused
1272: ; 2 = CORR: Unused
1272: ; 3 = DRQ: 1 = Data Request Ready (sector buffer ready)
1272: ; 4 = DSC: Unused
1272: ; 5 = DF: 1 = Write fault
1272: ; 6 = RDY: 1 = Ready to accept command
1272: ; 7 = BUSY: 1 = Controller is busy executing a command
1272: ;
1272: ; Test if the buffer of the IDE disk drive is ready for transfer. If not,
1272: ; carry will be set, otherwise carry is reset. The contents of register A will
1272: ; be destroyed!
1272: ;
1272: C5 ide_bfr_ready push bc
1273: A7 and a ; Clear carry assuming no error
1274: 06FF ld b, ide_retries ; How many retries?
1276: DB17 ide_bfr_loop in a, (ide_status_cmd) ; Read IDE status register
1278: CB5F bit 3, a ; Check DRQ bit
127A: 2010 jr nz, ide_bfr_exit ; Buffer is ready
127C: C5 push bc
127D: 0600 ld b, $0 ; Wait a moment
127F: 00 ide_bfr_wait nop
1280: 10FD djnz ide_bfr_wait
1282: C1 pop bc
1283: 10F1 djnz ide_bfr_loop ; Retry
1285: 37 scf ; Set carry to indicate timeout
1286: 218E12 ld hl, ide_bfr_rdy_err
1289: CD4B12 call puts
128C: C1 ide_bfr_exit pop bc
128D: C9 ret
128E: 46415441
1292: 4C284944
1296: 45293A20
129A: 6964655F
129E: 6266725F
12A2: 72656164
12A6: 79207469
12AA: 6D656F75
12AE: 74210D0A
12B2: 00 ide_bfr_rdy_err defb "FATAL(IDE): ide_bfr_ready timeout!", cr, lf, eos
12B3: ;
12B3: ; Test if there is any error flagged by the drive. If carry is cleared, no
12B3: ; error occured, otherwise carry will be set. The contents of register A will
12B3: ; be destroyed.
12B3: ;
12B3: A7 ide_error_check and a ; Clear carry (no err expected)
12B4: DB17 in a, (ide_status_cmd) ; Read status register
12B6: CB47 bit 0, a ; Test error bit
12B8: 2801 jr z, ide_ec_exit ; Everything is OK
12BA: 37 scf ; Set carry due to error
12BB: C9 ide_ec_exit ret
12BC: ;
12BC: ; Get ID information from drive. HL is expected to point to a 512 byte byte
12BC: ; sector buffer. If carry is set, the function did not complete correctly and
12BC: ; was aborted.
12BC: ;
12BC: F5 ide_get_id push af
12BD: C5 push bc
12BE: E5 push hl
12BF: CD1213 call ide_ready ; Is the drive ready?
12C2: 382E jr c, ide_get_id_err ; No - timeout!
12C4: 3EA0 ld a, $a0 ; Master, no LBA addressing
12C6: D316 out (ide_lba3), a
12C8: CD1213 call ide_ready ; Did the command complete?
12CB: 3825 jr c, ide_get_id_err ; Timeout!
12CD: 3EEC ld a, $ec ; Command to read ID
12CF: D317 out (ide_status_cmd), a ; Write command to drive
12D1: CD1213 call ide_ready ; Can we proceed?
12D4: 381C jr c, ide_get_id_err ; No - timeout, propagate carry
12D6: CDB312 call ide_error_check ; Any errors?
12D9: 3817 jr c, ide_get_id_err ; Yes - something went wrong
12DB: CD7212 call ide_bfr_ready ; Is the buffer ready to read?
12DE: 3812 jr c, ide_get_id_err ; No
12E0: 2100FE ld hl, buffer ; Load the buffer's address
12E3: 0600 ld b, $0 ; We will read 256 words
12E5: DB10 ide_get_id_lp in a, (ide_data_low) ; Read high (!) byte
12E7: 4F ld c, a
12E8: DB18 in a, (ide_data_high) ; Read low (!) byte
12EA: 77 ld (hl), a
12EB: 23 inc hl
12EC: 71 ld (hl), c
12ED: 23 inc hl
12EE: 10F5 djnz ide_get_id_lp ; Read next word
12F0: 1806 jr ide_get_id_exit ; Everything OK, just exit
12F2: 21FC12 ide_get_id_err ld hl, ide_get_id_msg ; Print error message
12F5: CD4B12 call puts
12F8: E1 ide_get_id_exit pop hl
12F9: C1 pop bc
12FA: F1 pop af
12FB: C9 ret
12FC: 46415441
1300: 4C284944
1304: 45293A20
1308: 41626F72
130C: 74656421
1310: 0D0A ide_get_id_msg defb "FATAL(IDE): Aborted!", cr, lf
1312: ;
1312: ; Test if the IDE drive is not busy and ready to accept a command. If it is
1312: ; ready the carry flag will be reset and the function returns. If a time out
1312: ; occurs, C will be set prior to returning to the caller. Register A will
1312: ; be destroyed!
1312: ;
1312: C5 ide_ready push bc
1313: A7 and a ; Clear carry assuming no error
1314: 06FF ld b, ide_retries ; Number of retries to timeout
1316: DB17 ide_ready_loop in a, (ide_status_cmd) ; Read drive status
1318: E6C0 and a, $c0 ; Only bits 7 and 6 are needed
131A: EE40 xor $40 ; Invert the ready flag
131C: 2815 jr z, ide_ready_exit ; Exit if ready and not busy
131E: C5 push bc
131F: 0600 ld b, $0 ; Wait a moment
1321: 00 ide_ready_wait nop
1322: 10FD djnz ide_ready_wait
1324: C1 pop bc
1325: 10EF djnz ide_ready_loop ; Retry
1327: 37 scf ; Set carry due to timeout
1328: 213513 ld hl, ide_rdy_error
132B: CD4B12 call puts
132E: DB11 in a, (ide_error_code)
1330: CD1212 call print_byte
1333: C1 ide_ready_exit pop bc
1334: C9 ret
1335: 46415441
1339: 4C284944
133D: 45293A20
1341: 6964655F
1345: 72656164
1349: 79207469
134D: 6D656F75
1351: 74210D0A
1355: 00 ide_rdy_error defb "FATAL(IDE): ide_ready timeout!", cr, lf, eos
1356: ;
1356: ; Read a sector from the drive. If carry is set after return, the function did
1356: ; not complete correctly due to a timeout. HL is expected to contain the start
1356: ; address of the sector buffer while BC and DE contain the sector address
1356: ; (LBA3, 2, 1 and 0). Register A's contents will be destroyed!
1356: ;
1356: C5 ide_rs push bc
1357: E5 push hl
1358: CD1213 call ide_ready ; Is the drive ready?
135B: 3829 jr c, ide_rs_err ; No - timeout!
135D: CDAD13 call ide_set_lba ; Setup the drive's registers
1360: CD1213 call ide_ready ; Everything OK?
1363: 3821 jr c, ide_rs_err ; No - timeout!
1365: 3E20 ld a, $20
1367: D317 out (ide_status_cmd), a ; Issue read command
1369: CD1213 call ide_ready ; Can we proceed?
136C: 3818 jr c, ide_rs_err ; No - timeout, set carry
136E: CDB312 call ide_error_check ; Any errors?
1371: 3813 jr c, ide_rs_err ; Yes - something went wrong
1373: CD7212 call ide_bfr_ready ; Is the buffer ready to read?
1376: 380E jr c, ide_rs_err ; No
1378: 0600 ld b, $0 ; We will read 256 words
137A: DB10 ide_rs_loop in a, (ide_data_low) ; Read low byte
137C: 77 ld (hl), a ; Store this byte
137D: 23 inc hl
137E: DB18 in a, (ide_data_high) ; Read high byte
1380: 77 ld (hl), a
1381: 23 inc hl
1382: 10F6 djnz ide_rs_loop ; Read next word until done
1384: 1806 jr ide_rs_exit
1386: 218F13 ide_rs_err ld hl, ide_rs_err_msg ; Print error message
1389: CD4B12 call puts
138C: E1 ide_rs_exit pop hl
138D: C1 pop bc
138E: C9 ret
138F: 46415441
1393: 4C284944
1397: 45293A20
139B: 6964655F
139F: 72732074
13A3: 696D656F
13A7: 7574210D
13AB: 0A00 ide_rs_err_msg defb "FATAL(IDE): ide_rs timeout!", cr, lf, eos
13AD: ;
13AD: ; Set sector count and LBA registers of the drive. Registers BC and DE contain
13AD: ; the sector address (LBA 3, 2, 1 and 0).
13AD: ;
13AD: F5 ide_set_lba push af
13AE: 3E01 ld a, $1 ; We will transfer
13B0: D312 out (ide_secnum), a ; one sector at a time
13B2: 7B ld a, e
13B3: D313 out (ide_lba0), a ; Set LBA0, 1 and 2 directly
13B5: 7A ld a, d
13B6: D314 out (ide_lba1), a
13B8: 79 ld a, c
13B9: D315 out (ide_lba2), a
13BB: 78 ld a, b ; Special treatment for LBA3
13BC: E60F and $0f ; Only bits 0 - 3 are LBA3
13BE: F6E0 or $e0 ; Select LBA and master drive
13C0: D316 out (ide_lba3), a
13C2: F1 pop af
13C3: C9 ret
13C4: ;
13C4: ; Write a sector from the drive. If carry is set after return, the function did
13C4: ; not complete correctly due to a timeout. HL is expected to contain the start
13C4: ; address of the sector buffer while BC and DE contain the sector address
13C4: ; (LBA3, 2, 1 and 0). Register A's contents will be destroyed!
13C4: ;
13C4: C5 ide_ws push bc
13C5: E5 push hl
13C6: CD1213 call ide_ready ; Is the drive ready?
13C9: 382A jr c, ide_ws_err ; No - timeout!
13CB: CDAD13 call ide_set_lba ; Setup the drive's registers
13CE: CD1213 call ide_ready ; Everything OK?
13D1: 3822 jr c, ide_ws_err ; No - timeout!
13D3: 3E30 ld a, $30
13D5: D317 out (ide_status_cmd), a ; Issue read command
13D7: CD1213 call ide_ready ; Can we proceed?
13DA: 3819 jr c, ide_ws_err ; No - timeout, set carry
13DC: CDB312 call ide_error_check ; Any errors?
13DF: 3814 jr c, ide_ws_err ; Yes - something went wrong
13E1: CD7212 call ide_bfr_ready ; Is the buffer ready to read?
13E4: 380F jr c, ide_ws_err ; No
13E6: 0600 ld b, $0 ; We will write 256 word
13E8: 7E ide_ws_loop ld a, (hl) ; Get first byte from memory
13E9: 4F ld c, a
13EA: 23 inc hl
13EB: 7E ld a, (hl) ; Get next byte
13EC: D318 out (ide_data_high), a ; Write high byte to controller
13EE: 79 ld a, c ; Recall low byte again
13EF: D310 out (ide_data_low), a ; Write low byte -> strobe
13F1: 10F5 djnz ide_ws_loop
13F3: 1806 jr ide_ws_exit
13F5: 21FE13 ide_ws_err ld hl, ide_ws_err_msg ; Print error message
13F8: CD4B12 call puts
13FB: E1 ide_ws_exit pop hl
13FC: C1 pop bc
13FD: C9 ret
13FE: 46415441
1402: 4C284944
1406: 45293A20
140A: 6964655F
140E: 77732074
1412: 696D656F
1416: 7574210D
141A: 0A00 ide_ws_err_msg defb "FATAL(IDE): ide_ws timeout!", cr, lf, eos
141C: ;
141C: #endif ; N8VEM = 1?
141C: ;
141C: ;******************************************************************************
141C: ;***
141C: ;*** Miscellaneous functions
141C: ;***
141C: ;******************************************************************************
141C: ;
141C: ; Clear the computer (not to be called - jump into this routine):
141C: ;
141C: 213CFB cold_start ld hl, start_type
141F: 3600 ld (hl), $00
1421: 212D14 warm_start ld hl, clear_msg
1424: CD4B12 call puts
1427: 3E00 ld a, $00
1429: 32FFFF ld (ram_end), a
142C: C7 rst $00
142D: 434C4541
1431: 520D0A00 clear_msg defb "CLEAR", cr, lf, eos
1435: ;
1435: ;
1435: ;******************************************************************************
1435: ;***
1435: ;*** Mathematical routines
1435: ;***
1435: ;******************************************************************************
1435: ;
1435: ; 32 bit add routine from
1435: ; http://www.andreadrian.de/oldcpu/Z80_number_cruncher.html
1435: ;
1435: ; ADD ROUTINE 32+32BIT=32BIT
1435: ; H'L'HL = H'L'HL + D'E'DE
1435: ; CHANGES FLAGS
1435: ;
1435: 19 ADD32: ADD HL,DE ; 16-BIT ADD OF HL AND DE
1436: D9 EXX
1437: ED5A ADC HL,DE ; 16-BIT ADD OF HL AND DE WITH CARRY
1439: D9 EXX
143A: C9 RET
143B: ;
143B: ; 32 bit multiplication routine from
143B: ; http://www.andreadrian.de/oldcpu/Z80_number_cruncher.html
143B: ;
143B: ; MULTIPLY ROUTINE 32*32BIT=32BIT
143B: ; H'L'HL = B'C'BC * D'E'DE; NEEDS REGISTER A, CHANGES FLAGS
143B: ;
143B: A7 MUL32: AND A ; RESET CARRY FLAG
143C: ED62 SBC HL,HL ; LOWER RESULT = 0
143E: D9 EXX
143F: ED62 SBC HL,HL ; HIGHER RESULT = 0
1441: 78 LD A,B ; MPR IS AC'BC
1442: 0620 LD B,32 ; INITIALIZE LOOP COUNTER
1444: MUL32LOOP:
1444: CB2F SRA A ; RIGHT SHIFT MPR
1446: CB19 RR C
1448: D9 EXX
1449: CB18 RR B
144B: CB19 RR C ; LOWEST BIT INTO CARRY
144D: 3005 JR NC,MUL32NOADD
144F: 19 ADD HL,DE ; RESULT += MPD
1450: D9 EXX
1451: ED5A ADC HL,DE
1453: D9 EXX
1454: MUL32NOADD:
1454: CB23 SLA E ; LEFT SHIFT MPD
1456: CB12 RL D
1458: D9 EXX
1459: CB13 RL E
145B: CB12 RL D
145D: 10E5 DJNZ MUL32LOOP
145F: D9 EXX
1460: C9 RET
1461: ;
1461: ;******************************************************************************
1461: ;***
1461: ;*** FAT file system routines
1461: ;***
1461: ;******************************************************************************
1461: ;
1461: ; Read a single byte from a file. IY points to the FCB. The byte read is
1461: ; returned in A, on EOF the carry flag will be set.
1461: ;
1461: C5 fgetc push bc
1462: D5 push de
1463: E5 push hl
1464: ; Check if fcb_file_pointer == fcb_file_size. In this case we have reached
1464: ; EOF and will return with a set carry bit. (As a side effect, the attempt to
1464: ; read from a file which has not been successfully opened before will be
1464: ; handled like encountering an EOF at the first fgetc call.)
1464: FD7E0C ld a, (iy + fcb_file_size)
1467: FDBE13 cp (iy + fcb_file_pointer)
146A: 201C jr nz, fgetc_start
146C: FD7E0D ld a, (iy + fcb_file_size + 1)
146F: FDBE14 cp (iy + fcb_file_pointer + 1)
1472: 2014 jr nz, fgetc_start
1474: FD7E0E ld a, (iy + fcb_file_size + 2)
1477: FDBE15 cp (iy + fcb_file_pointer + 2)
147A: 200C jr nz, fgetc_start
147C: FD7E0F ld a, (iy + fcb_file_size + 3)
147F: FDBE16 cp (iy + fcb_file_pointer + 3)
1482: 2004 jr nz, fgetc_start
1484: ; We have reached EOF, so set carry and leave this routine:
1484: 37 scf
1485: C37115 jp fgetc_exit
1488: ; Check if the lower 9 bits of the file pointer are zero. In this case
1488: ; we need to read another sector (maybe from another cluster):
1488: FD7E13 fgetc_start ld a, (iy + fcb_file_pointer)
148B: FE00 cp 0
148D: C23D15 jp nz, fgetc_getc ; Bits 0-7 are not zero
1490: FD7E14 ld a, (iy + fcb_file_pointer + 1)
1493: E601 and 1
1495: C23D15 jp nz, fgetc_getc ; Bit 8 is not zero
1498: ; The file_pointer modulo 512 is zero, so we have to load the next sector:
1498: ; We have to check if fcb_current_cluster == 0 which will be the case in the
1498: ; initial run. Then we will copy fcb_first_cluster into fcb_current_cluster.
1498: FD7E17 ld a, (iy + fcb_current_cluster)
149B: FE00 cp 0
149D: 2015 jr nz, fgetc_continue ; Not the initial case
149F: FD7E18 ld a, (iy + fcb_current_cluster + 1)
14A2: FE00 cp 0
14A4: 200E jr nz, fgetc_continue ; Not the initial case
14A6: ; Initial case: We have to fill fcb_current_cluster with fcb_first_cluste:
14A6: FD7E10 ld a, (iy + fcb_first_cluster)
14A9: FD7717 ld (iy + fcb_current_cluster), a
14AC: FD7E11 ld a, (iy + fcb_first_cluster + 1)
14AF: FD7718 ld (iy + fcb_current_cluster + 1), a
14B2: 1837 jr fgetc_clu2sec
14B4: ; Here is the normal case - we will check if fcb_cluster_sector is zero -
14B4: ; in this case we have to determine the next sector to be loaded by looking
14B4: ; up the FAT. Otherwise (fcb_cluster_sector != 0) we will just get the next
14B4: ; sector in the current cluster.
14B4: FD7E1D fgetc_continue ld a, (iy + fcb_cluster_sector)
14B7: 2043 jr nz, fgetc_same ; The current cluster is valid
14B9: ; Here we know that we need the first sector of the next cluster of the file.
14B9: ; The upper eight bits of the fcb_current_cluster point to the sector of the
14B9: ; FAT where the entry we are looking for is located (this is true since a
14B9: ; sector contains 512 bytes which corresponds to 256 FAT entries). So we must
14B9: ; load the sector with the number fatstart + fcb_current_cluster[15-8] into
14B9: ; the IDE buffer and locate the entry with the address
14B9: ; fcb_current_cluster[7-0] * 2. This entry contains the sector number we are
14B9: ; looking for.
14B9: 2AF4FD ld hl, (fat1start)
14BC: FD4E18 ld c, (iy + fcb_current_cluster + 1)
14BF: 0600 ld b, 0
14C1: 09 add hl, bc
14C2: 545D ld de, hl ; Needed for ide_rs
14C4: 010000 ld bc, 0
14C7: 2AF6FD ld hl, (fat1start + 2)
14CA: ED4A adc hl, bc
14CC: 444D ld bc, hl ; Needed for ide_rs
14CE: 2100FE ld hl, buffer
14D1: CD5613 call ide_rs
14D4: ; Now the sector containing the FAT entry we are looking for is available in
14D4: ; the IDE buffer. Now we need fcb_current_cluster[7-0] * 2
14D4: 0600 ld b, 0
14D6: FD4E17 ld c, (iy + fcb_current_cluster)
14D9: CB21 sla c
14DB: CB10 rl b
14DD: ; Now get the entry:
14DD: 2100FE ld hl, buffer
14E0: 09 add hl, bc
14E1: 4E23462B ld bc, (hl)
14E5: FD7117 ld (iy + fcb_current_cluster), c
14E8: FD7017 ld (iy + fcb_current_cluster), b
14EB: ; Now we determine the first sector of the cluster to be read:
14EB: 3AE5FD fgetc_clu2sec ld a, (clusiz) ; Initialize fcb_cluster_sector
14EE: FD771D ld (iy + fcb_cluster_sector), a
14F1: FD6E17 ld l, (iy + fcb_current_cluster)
14F4: FD6618 ld h, (iy + fcb_current_cluster + 1)
14F7: CD3818 call clu2sec ; Convert cluster to sector
14FA: 1826 jr fgetc_rs
14FC: A7 fgetc_same and a ; Clear carry
14FD: 010100 ld bc, 1 ; Increment fcb_current_sector
1500: FD6E19 ld l, (iy + fcb_current_sector)
1503: FD661A ld h, (iy + fcb_current_sector + 1)
1506: 09 add hl, bc
1507: FD7519 ld (iy + fcb_current_sector), l
150A: 5D ld e, l ; Needed for ide_rs
150B: FD741A ld (iy + fcb_current_sector + 1), h
150E: 54 ld d, h ; Needed for ide_rs
150F: FD6E1B ld l, (iy + fcb_current_sector + 2)
1512: FD661C ld h, (iy + fcb_current_sector + 3)
1515: 010000 ld bc, 0
1518: ED4A adc hl, bc
151A: FD751B ld (iy + fcb_current_sector + 2), l
151D: 4D ld c, l ; Needed for ide_rs
151E: FD741C ld (iy + fcb_current_sector + 3), h
1521: 44 ld b, h ; Neede for ide_rs
1522: FD7319 fgetc_rs ld (iy + fcb_current_sector), e ; Now read the sector
1525: FD721A ld (iy + fcb_current_sector + 1), d
1528: FD711B ld (iy + fcb_current_sector + 2), c
152B: FD701C ld (iy + fcb_current_sector + 3), b
152E: ; Let HL point to the sector buffer in the FCB:
152E: FDE5 push iy ; Start of FCB
1530: E1 pop hl
1531: C5 push bc
1532: 011E00 ld bc, fcb_file_buffer ; Displacement of sector buffer
1535: 09 add hl, bc
1536: C1 pop bc
1537: CD5613 call ide_rs ; Read a single sector from disk
153A: ; Since we have read a sector we have to decrement fcb_cluster_sector
153A: FD351D dec (iy + fcb_cluster_sector)
153D: ; Here we read and return a single character from the sector buffer:
153D: FDE5 fgetc_getc push iy
153F: E1 pop hl ; Copy IY to HL
1540: 011E00 ld bc, fcb_file_buffer
1543: 09 add hl, bc ; HL points to the sector bfr.
1544: ; Get the lower 9 bits of the file pointer as displacement for the buffer:
1544: FD4E13 ld c, (iy + fcb_file_pointer)
1547: FD7E14 ld a, (iy + fcb_file_pointer + 1)
154A: E601 and 1 ; Get rid of bits 9-15
154C: 47 ld b, a
154D: 09 add hl, bc ; Add byte offset
154E: 7E ld a, (hl) ; get one byte from buffer
154F: ; Increment the file pointer:
154F: FD6E13 ld l, (iy + fcb_file_pointer)
1552: FD6614 ld h, (iy + fcb_file_pointer + 1)
1555: 010100 ld bc, 1
1558: 09 add hl, bc
1559: FD7513 ld (iy + fcb_file_pointer), l
155C: FD7414 ld (iy + fcb_file_pointer + 1), h
155F: 010000 ld bc, 0
1562: FD6E15 ld l, (iy + fcb_file_pointer + 2)
1565: FD6616 ld h, (iy + fcb_file_pointer + 3)
1568: ED4A adc hl, bc
156A: FD7515 ld (iy + fcb_file_pointer + 2), l
156D: FD7416 ld (iy + fcb_file_pointer + 3), h
1570: ;
1570: A7 and a ; Clear carry
1571: E1 fgetc_exit pop hl
1572: D1 pop de
1573: C1 pop bc
1574: C9 ret
1575: ;
1575: ; Clear the FCB to which IY points -- this should be called every time one
1575: ; creates a new FCB. (Please note that fopen does its own call to clear_fcb.)
1575: ;
1575: F5 clear_fcb push af ; We have to save so many
1576: C5 push bc ; Registers since the FCB is
1577: D5 push de ; cleared using LDIR.
1578: E5 push hl
1579: 3E00 ld a, 0
157B: FDE5 push iy
157D: E1 pop hl
157E: 77 ld (hl), a ; Clear first byte of FCB
157F: 545D ld de, hl
1581: 13 inc de
1582: 011E00 ld bc, fcb_file_buffer
1585: EDB0 ldir ; And transfer this zero byte
1587: E1 pop hl ; down to the relevant rest
1588: D1 pop de ; of the buffer.
1589: C1 pop bc
158A: F1 pop af
158B: C9 ret
158C: ;
158C: ; Dump a file control block (FCB) - the start address is expected in IY.
158C: ;
158C: F5 dump_fcb push af
158D: E5 push hl
158E: 211E16 ld hl, dump_fcb_1
1591: CD4B12 call puts
1594: FDE5 push iy ; Load HL with
1596: E1 pop hl ; the contents of IY
1597: CD3312 call print_word
159A: ; Print the filename:
159A: 213716 ld hl, dump_fcb_2
159D: CD4B12 call puts
15A0: FDE5 push iy
15A2: E1 pop hl
15A3: CD4B12 call puts
15A6: ; Print file size:
15A6: 214C16 ld hl, dump_fcb_3
15A9: CD4B12 call puts
15AC: FD660F ld h, (iy + fcb_file_size + 3)
15AF: FD6E0E ld l, (iy + fcb_file_size + 2)
15B2: CD3312 call print_word
15B5: FD660D ld h, (iy + fcb_file_size + 1)
15B8: FD6E0C ld l, (iy + fcb_file_size)
15BB: CD3312 call print_word
15BE: ; Print cluster number:
15BE: 216116 ld hl, dump_fcb_4
15C1: CD4B12 call puts
15C4: FD6611 ld h, (iy + fcb_first_cluster + 1)
15C7: FD6E10 ld l, (iy + fcb_first_cluster)
15CA: CD3312 call print_word
15CD: ; Print file type:
15CD: 217616 ld hl, dump_fcb_5
15D0: CD4B12 call puts
15D3: FD7E12 ld a, (iy + fcb_file_type)
15D6: CD1212 call print_byte
15D9: ; Print file pointer:
15D9: 218B16 ld hl, dump_fcb_6
15DC: CD4B12 call puts
15DF: FD6616 ld h, (iy + fcb_file_pointer + 3)
15E2: FD6E15 ld l, (iy + fcb_file_pointer + 2)
15E5: CD3312 call print_word
15E8: FD6614 ld h, (iy + fcb_file_pointer + 1)
15EB: FD6E13 ld l, (iy + fcb_file_pointer)
15EE: CD3312 call print_word
15F1: ; Print current cluster number:
15F1: 21A016 ld hl, dump_fcb_7
15F4: CD4B12 call puts
15F7: FD6618 ld h, (iy + fcb_current_cluster + 1)
15FA: FD6E17 ld l, (iy + fcb_current_cluster)
15FD: CD3312 call print_word
1600: ; Print current sector:
1600: 21B516 ld hl, dump_fcb_8
1603: CD4B12 call puts
1606: FD661C ld h, (iy + fcb_current_sector + 3)
1609: FD6E1B ld l, (iy + fcb_current_sector + 2)
160C: CD3312 call print_word
160F: FD661A ld h, (iy + fcb_current_sector + 1)
1612: FD6E19 ld l, (iy + fcb_current_sector)
1615: CD3312 call print_word
1618: CD4C11 call crlf
161B: E1 pop hl
161C: F1 pop af
161D: C9 ret
161E: 44756D70
1622: 206F6620
1626: 46434220
162A: 61742061
162E: 64647265
1632: 73733A20
1636: 00 dump_fcb_1 defb "Dump of FCB at address: ", eos
1637: 0D0A0946
163B: 696C6520
163F: 6E616D65
1643: 20202020
1647: 20203A20
164B: 00 dump_fcb_2 defb cr, lf, tab, "File name : ", eos
164C: 0D0A0946
1650: 696C6520
1654: 73697A65
1658: 20202020
165C: 20203A20
1660: 00 dump_fcb_3 defb cr, lf, tab, "File size : ", eos
1661: 0D0A0931
1665: 73742063
1669: 6C757374
166D: 65722020
1671: 20203A20
1675: 00 dump_fcb_4 defb cr, lf, tab, "1st cluster : ", eos
1676: 0D0A0946
167A: 696C6520
167E: 74797065
1682: 20202020
1686: 20203A20
168A: 00 dump_fcb_5 defb cr, lf, tab, "File type : ", eos
168B: 0D0A0946
168F: 696C6520
1693: 706F696E
1697: 74657220
169B: 20203A20
169F: 00 dump_fcb_6 defb cr, lf, tab, "File pointer : ", eos
16A0: 0D0A0943
16A4: 75727265
16A8: 6E742063
16AC: 6C757374
16B0: 65723A20
16B4: 00 dump_fcb_7 defb cr, lf, tab, "Current cluster: ", eos
16B5: 0D0A0943
16B9: 75727265
16BD: 6E742073
16C1: 6563746F
16C5: 72203A20
16C9: 00 dump_fcb_8 defb cr, lf, tab, "Current sector : ", eos
16CA: ;
16CA: ; Convert a user specified filename to an 8.3-filename without dot and
16CA: ; with terminating null byte. HL points to the input string, DE points to
16CA: ; a 12 character buffer for the filename. This function is used by
16CA: ; fopen which expects a human readable string that will be transformed into
16CA: ; an 8.3-filename without the dot for the following directory lookup.
16CA: ;
16CA: F5 str2filename push af
16CB: C5 push bc
16CC: D5 push de
16CD: E5 push hl
16CE: ED535DFB ld (str2filename_de), de
16D2: 3E20 ld a, ' ' ; Initialize output buffer
16D4: 060B ld b, $b ; Fill 11 bytes with spaces
16D6: 12 str2filiniloop ld (de), a
16D7: 13 inc de
16D8: 10FC djnz str2filiniloop
16DA: 3E00 ld a, 0 ; Add terminating null byte
16DC: 12 ld (de), a
16DD: ED5B5DFB ld de, (str2filename_de) ; Restore DE pointer
16E1: ; Start string conversion
16E1: 0608 ld b, 8
16E3: 7E str2filini_nam ld a, (hl)
16E4: FE00 cp 0 ; End of string reached?
16E6: 282F jr z, str2filini_x
16E8: FE2E cp '.' ; Dot found?
16EA: 2812 jr z, str2filini_ext
16EC: 12 ld (de), a
16ED: 13 inc de
16EE: 23 inc hl
16EF: 05 dec b
16F0: 20F1 jr nz, str2filini_nam
16F2: 7E str2filini_skip ld a, (hl)
16F3: FE00 cp 0 ; End of string without dot?
16F5: 2820 jr z, str2filini_x ; Nothing more to do
16F7: FE2E cp '.'
16F9: 2803 jr z, str2filini_ext ; Take care of extension
16FB: 23 inc hl ; Prepare for next character
16FC: 18F4 jr str2filini_skip ; Skip more characters
16FE: 23 str2filini_ext inc hl ; Skip the dot
16FF: E5 push hl ; Make sure DE points
1700: 2A5DFB ld hl, (str2filename_de) ; into the filename buffer
1703: 010800 ld bc, 8 ; at the start position
1706: 09 add hl, bc ; of the filename extension
1707: 545D ld de, hl
1709: E1 pop hl
170A: 0603 ld b, 3
170C: 7E str2filini_elp ld a, (hl)
170D: FE00 cp 0 ; End of string reached?
170F: 2806 jr z, str2filini_x ; Nothing more to do
1711: 12 ld (de), a
1712: 13 inc de
1713: 23 inc hl
1714: 05 dec b
1715: 20F5 jr nz, str2filini_elp ; Next extension character
1717: E1 str2filini_x pop hl
1718: D1 pop de
1719: C1 pop bc
171A: F1 pop af
171B: C9 ret
171C: ;
171C: ; Open a file with given filename (format: 'FFFFFFFFXXX') in the root directory
171C: ; and return the 1st cluster number for that file. If the file can not
171C: ; be found, $0000 will be returned in the FCB.
171C: ; At entry, HL must point to the string buffer while IY points to a valid
171C: ; file control block that will hold all necessary data for future file accesses.
171C: ; In addition to that DE must point to a 12 character string buffer.
171C: ;
171C: F5 fopen push af
171D: C5 push bc
171E: D5 push de
171F: E5 push hl
1720: DDE5 push ix
1722: 2255FB ld (fopen_scr), hl
1725: 21DCFD ld hl, fatname ; Check if a disk has been
1728: 7E ld a, (hl) ; mounted.
1729: FE00 cp 0
172B: CAD417 jp z, fopen_e1 ; No disk - error exit
172E: CD7515 call clear_fcb
1731: FDE5 push iy ; Copy IY to DE
1733: D1 pop de
1734: 2A55FB ld hl, (fopen_scr) ; Create the filename
1737: CDCA16 call str2filename ; Convert string to a filename
173A: 2100FE ld hl, buffer ; Compute buffer overflow
173D: 010002 ld bc, $0200 ; address - this is the bfr siz.
1740: 09 add hl, bc ; and will be used in the loop
1741: 225BFB ld (fopen_eob), hl ; This is the buffer end addr.
1744: ;
1744: 2AF8FD ld hl, (rootstart) ; Remember the initial root
1747: 2257FB ld (fopen_rsc), hl ; sector number
174A: 2AFAFD ld hl, (rootstart + 2)
174D: 2259FB ld (fopen_rsc + 2), hl
1750: ; Read one root directory sector
1750: ED4B59FB fopen_nbf ld bc, (fopen_rsc + 2)
1754: ED5B57FB ld de, (fopen_rsc)
1758: 2100FE ld hl, buffer
175B: CD5613 call ide_rs ; Read one sector
175E: DAD917 jp c, fopen_e2 ; Exit on read error
1761: 2255FB fopen_lp ld (fopen_scr), hl
1764: AF xor a ; Last entry?
1765: BE cp (hl) ; The last entry has first
1766: CADF17 jp z, fopen_x ; byte = $0
1769: 3EE5 ld a, $e5 ; Deleted entry?
176B: BE cp (hl)
176C: 284B jr z, fopen_nxt ; Get next entry
176E: ; ld (fopen_scr), hl
176E: DD2A55FB ld ix, (fopen_scr)
1772: DD7E0B ld a, (ix + $b) ; Get attribute byte
1775: FE0F cp $0f
1777: 2840 jr z, fopen_nxt ; Skip long name
1779: CB67 bit 4, a ; Skip directories
177B: 203C jr nz, fopen_nxt
177D: ; Compare the filename with the one we are looking for:
177D: DD360B00 ld (ix + $b), 0 ; Clear attribute byte
1781: ED5B55FB ld de, (fopen_scr)
1785: FDE5 push iy ; Prepare string comparison
1787: E1 pop hl
1788: CD3111 call strcmp ; Compare filename with string
178B: FE00 cp 0 ; Are strings equal?
178D: 202A jr nz, fopen_nxt ; No - check next entry
178F: DD7E1B ld a, (ix + $1a + 1) ; Read cluster number and
1792: ; Save cluster_number into fcb_first_cluster:
1792: FD7711 ld (iy + fcb_first_cluster + 1), a
1795: DD7E1A ld a, (ix + $1a)
1798: FD7710 ld (iy + fcb_first_cluster), a
179B: DD7E1C ld a, (ix + $1c) ; Save file size to FCB
179E: FD770C ld (iy + fcb_file_size), a
17A1: DD7E1D ld a, (ix + $1d) ; Save file size to FCB
17A4: FD770D ld (iy + fcb_file_size + 1), a
17A7: DD7E1E ld a, (ix + $1e) ; Save file size to FCB
17AA: FD770E ld (iy + fcb_file_size + 2), a
17AD: DD7E1F ld a, (ix + $1f) ; Save file size to FCB
17B0: FD770F ld (iy + fcb_file_size + 3), a
17B3: FD361201 ld (iy + fcb_file_type), 1 ; Set file type to found
17B7: 1826 jr fopen_x ; Terminate lookup loop
17B9: 012000 fopen_nxt ld bc, $20
17BC: 2A55FB ld hl, (fopen_scr)
17BF: 09 add hl, bc
17C0: 2255FB ld (fopen_scr), hl
17C3: ED4B5BFB ld bc, (fopen_eob) ; Check for end of buffer
17C7: A7 and a ; Clear carry
17C8: ED42 sbc hl, bc ; ...no 16 bit cp :-(
17CA: C26117 jp nz, fopen_lp ; Buffer is still valid
17CD: 2157FB ld hl, fopen_rsc ; Increment sector number
17D0: 34 inc (hl) ; 16 bits are enough :-)
17D1: C35017 jp fopen_nbf ; Read next directory sector
17D4: 21E617 fopen_e1 ld hl, fopen_nmn ; No disk mounted
17D7: 1803 jr fopen_err ; Print error message
17D9: 210718 fopen_e2 ld hl, fopen_rer ; Directoy sector read error
17DC: CD4B12 fopen_err call puts
17DF: DDE1 fopen_x pop ix
17E1: E1 pop hl
17E2: D1 pop de
17E3: C1 pop bc
17E4: F1 pop af
17E5: C9 ret
17E6: 46415441
17EA: 4C28464F
17EE: 50454E29
17F2: 3A204E6F
17F6: 20646973
17FA: 6B206D6F
17FE: 756E7465
1802: 64210D0A
1806: 00 fopen_nmn defb "FATAL(FOPEN): No disk mounted!", cr, lf, eos
1807: 46415441
180B: 4C28464F
180F: 50454E29
1813: 3A20436F
1817: 756C6420
181B: 6E6F7420
181F: 72656164
1823: 20646972
1827: 6563746F
182B: 72792073
182F: 6563746F
1833: 7221 fopen_rer defb "FATAL(FOPEN): Could not read directory sector!"
1835: 0D0A00 defb cr, lf, eos
1838: ;
1838: ; Convert a cluster number into a sector number. The cluster number is
1838: ; expected in HL, the corresponding sector number will be returned in
1838: ; BC and DE, thus ide_rs or ide_ws can be called afterwards.
1838: ;
1838: ; SECNUM = (CLUNUM - 2) * CLUSIZ + DATASTART
1838: ;
1838: F5 clu2sec push af ; Since the 32 bit
1839: E5 push hl ; multiplication routine
183A: D9 exx ; needs shadow registers
183B: C5 push bc ; we have to push many,
183C: D5 push de ; many registers here
183D: E5 push hl
183E: 010000 ld bc, 0 ; Clear BC' and DE' for
1841: 5059 ld de, bc ; 32 bit multiplication
1843: D9 exx
1844: 010200 ld bc, 2 ; Subtract 2
1847: ED42 sbc hl, bc ; HL = CLUNUM - 2
1849: 444D ld bc, hl ; BC = HL; BC' = 0
184B: 3AE5FD ld a, (clusiz)
184E: 1600 ld d, 0 ; CLUSIZ bits 8 to 15
1850: 5F ld e, a ; DE = CLUSIZ
1851: CD3B14 call MUL32 ; HL = (CLUNUM - 2) * CLUSIZ
1854: ED5BFCFD ld de, (datastart)
1858: D9 exx
1859: ED5BFEFD ld de, (datastart + 2)
185D: D9 exx
185E: CD3514 call ADD32 ; HL = HL + DATASTART
1861: D9 exx
1862: E5 push hl
1863: D9 exx
1864: C1 pop bc
1865: 545D ld de, hl
1867: D9 exx
1868: E1 pop hl
1869: D1 pop de
186A: C1 pop bc
186B: D9 exx
186C: E1 pop hl
186D: F1 pop af
186E: C9 ret
186F: ;
186F: ; Print a directory listing
186F: ;
186F: F5 dirlist push af
1870: C5 push bc
1871: D5 push de
1872: E5 push hl
1873: DDE5 push ix
1875: 21DCFD ld hl, fatname
1878: 7E ld a, (hl)
1879: FE00 cp 0
187B: CA4219 jp z, dirlist_nodisk
187E: DD216DFB ld ix, string_81_bfr
1882: DD36082E ld (ix + 8), '.' ; Dot between name and extens.
1886: DD360C00 ld (ix + 12), 0 ; String terminator
188A: 217219 ld hl, dirlist_0 ; Print title line
188D: CD4B12 call puts
1890: 2100FE ld hl, buffer ; Compute buffer overflow
1893: 010002 ld bc, $0200 ; address - this is the bfr siz.
1896: 09 add hl, bc
1897: 2251FB ld (dirlist_eob), hl ; This is the buffer end addr.
189A: ;
189A: 2AF8FD ld hl, (rootstart) ; Remember the initial root
189D: 224DFB ld (dirlist_rootsec), hl ; sector number
18A0: 2AFAFD ld hl, (rootstart + 2)
18A3: 224FFB ld (dirlist_rootsec + 2), hl
18A6: ; Read one root directory sector
18A6: ED4B4FFB dirlist_nbfr ld bc, (dirlist_rootsec + 2)
18AA: ED5B4DFB ld de, (dirlist_rootsec)
18AE: 2100FE ld hl, buffer
18B1: CD5613 call ide_rs
18B4: DA3D19 jp c, dirlist_e1
18B7: AF dirlist_loop xor a ; Last entry?
18B8: BE cp (hl) ; The last entry has first
18B9: CA4819 jp z, dirlist_exit ; byte = $0
18BC: 3EE5 ld a, $e5 ; Deleted entry?
18BE: BE cp (hl)
18BF: 2867 jr z, dirlist_next
18C1: 2253FB ld (dirlist_scratch), hl
18C4: DD2A53FB ld ix, (dirlist_scratch)
18C8: DD7E0B ld a, (ix + $b) ; Get attribute byte
18CB: FE0F cp $0f
18CD: 2859 jr z, dirlist_next ; Skip long name
18CF: 116DFB ld de, string_81_bfr ; Prepare for output
18D2: 010800 ld bc, 8 ; Copy first eight characters
18D5: EDB0 ldir
18D7: 13 inc de
18D8: 010300 ld bc, 3 ; Copy extension
18DB: EDB0 ldir
18DD: ; ld hl, de
18DD: ; ld (hl), 0 ; String terminator
18DD: 216DFB ld hl, string_81_bfr
18E0: CD4B12 call puts
18E3: 21471A ld hl, dirlist_NODIR ; Flag directories with "DIR"
18E6: CB67 bit 4, a
18E8: 2803 jr z, dirlist_prtdir
18EA: 21411A ld hl, dirlist_DIR
18ED: CD4B12 dirlist_prtdir call puts
18F0: DD661F ld h, (ix + $1c + 3) ; Get and print file size
18F3: DD6E1E ld l, (ix + $1c + 2)
18F6: CD3312 call print_word
18F9: DD661D ld h, (ix + $1c + 1)
18FC: DD6E1C ld l, (ix + $1c)
18FF: CD3312 call print_word
1902: ; Get and print start sector
1902: 3E09 ld a, tab
1904: CD4012 call putc
1907: DD661B ld h, (ix + $1a + 1) ; Get cluster number
190A: DD6E1A ld l, (ix + $1a)
190D: 010000 ld bc, 0 ; Is file empty?
1910: A7 and a ; Clear carry
1911: ED42 sbc hl, bc ; Empty file -> Z set
1913: 280D jr z, dirlist_nosize
1915: CD3818 call clu2sec
1918: 6069 ld hl, bc
191A: CD3312 call print_word
191D: 626B ld hl, de
191F: CD3312 call print_word
1922: CD4C11 dirlist_nosize call crlf
1925: 2A53FB ld hl, (dirlist_scratch)
1928: 012000 dirlist_next ld bc, $20
192B: 09 add hl, bc
192C: ED4B51FB ld bc, (dirlist_eob) ; Check for end of buffer
1930: A7 and a
1931: ED42 sbc hl, bc
1933: C2B718 jp nz, dirlist_loop ; Buffer is still valid
1936: 214DFB ld hl, dirlist_rootsec
1939: 34 inc (hl)
193A: C3A618 jp dirlist_nbfr
193D: 210F1A dirlist_e1 ld hl, dirlist_1
1940: 1803 jr dirlist_x
1942: 214F19 dirlist_nodisk ld hl, dirlist_nomnt
1945: CD4B12 dirlist_x call puts
1948: DDE1 dirlist_exit pop ix
194A: E1 pop hl
194B: D1 pop de
194C: C1 pop bc
194D: F1 pop af
194E: C9 ret
194F: 46415441
1953: 4C284449
1957: 524C4953
195B: 54293A20
195F: 4E6F2064
1963: 69736B20
1967: 6D6F756E
196B: 74656421
196F: 0D0A00 dirlist_nomnt defb "FATAL(DIRLIST): No disk mounted!", cr, lf, eos
1972: 44697265
1976: 63746F72
197A: 7920636F
197E: 6E74656E
1982: 74733A0D
1986: 0A dirlist_0 defb "Directory contents:", cr, lf
1987: 2D2D2D2D
...
19B2: 0D0A defb "-------------------------------------------", cr, lf
19B4: 46494C45
19B8: 4E414D45
19BC: 2E455854
19C0: 20204449
19C4: 523F2020
19C8: 2053495A
19CC: 45202842
19D0: 59544553
19D4: 29 defb "FILENAME.EXT DIR? SIZE (BYTES)"
19D5: 20203153
19D9: 54205345
19DD: 43540D0A defb " 1ST SECT", cr, lf
19E1: 2D2D2D2D
...
1A0C: 0D0A defb "-------------------------------------------", cr, lf
1A0E: 00 defb eos
1A0F: 46415441
1A13: 4C284449
1A17: 524C4953
1A1B: 54293A20
1A1F: 436F756C
1A23: 64206E6F
1A27: 74207265
1A2B: 61642064
1A2F: 69726563
1A33: 746F7279
1A37: 20736563
1A3B: 746F72 dirlist_1 defb "FATAL(DIRLIST): Could not read directory sector"
1A3E: 0D0A00 defb cr, lf, eos
1A41: 09444952
1A45: 0900 dirlist_DIR defb tab, "DIR", tab, eos
1A47: 090900 dirlist_NODIR defb tab, tab, eos
1A4A: ;
1A4A: ; Perform a disk mount
1A4A: ;
1A4A: F5 fatmount push af
1A4B: C5 push bc
1A4C: D5 push de
1A4D: E5 push hl
1A4E: DDE5 push ix
1A50: 2100FE ld hl, buffer ; Read MBR into buffer
1A53: 010000 ld bc, 0
1A56: 110000 ld de, 0
1A59: CD5613 call ide_rs
1A5C: DAC21B jp c, fatmount_e1 ; Error reading MBR?
1A5F: DD21FEFF ld ix, buffer + $1fe ; Check for $55AA as MBR trailer
1A63: 3E55 ld a, $55
1A65: DDBE00 cp (ix)
1A68: C2C71B jp nz, fatmount_e2
1A6B: 3EAA ld a, $aa
1A6D: DDBE01 cp (ix + 1)
1A70: C2C71B jp nz, fatmount_e2
1A73: 010800 ld bc, 8 ; Get partition start and size
1A76: 21C6FF ld hl, buffer + $1c6
1A79: 11ECFD ld de, pstart
1A7C: EDB0 ldir
1A7E: 2100FE ld hl, buffer ; Read partition boot block
1A81: ED5BECFD ld de, (pstart)
1A85: ED4BEEFD ld bc, (pstart + 2)
1A89: CD5613 call ide_rs
1A8C: DACC1B jp c, fatmount_e3 ; Error reading boot block?
1A8F: 010800 ld bc, 8 ; Copy FAT name
1A92: 2103FE ld hl, buffer + 3
1A95: 11DCFD ld de, fatname
1A98: EDB0 ldir
1A9A: DD2100FE ld ix, buffer
1A9E: 3E02 ld a, 2 ; Check for two FATs
1AA0: DDBE10 cp (ix + $10)
1AA3: C2D11B jp nz, fatmount_e4 ; Wrong number of FATs
1AA6: AF xor a ; Check for 512 bytes / sector
1AA7: DDBE0B cp (ix + $b)
1AAA: C2D61B jp nz, fatmount_e5
1AAD: 3E02 ld a, 2
1AAF: DDBE0C cp (ix + $c)
1AB2: C2D61B jp nz, fatmount_e5
1AB5: 3A0DFE ld a, (buffer + $d) ; Get cluster size
1AB8: 32E5FD ld (clusiz), a
1ABB: ED4B0EFE ld bc, (buffer + $e) ; Get reserved sector number
1ABF: ED43E6FD ld (ressec), bc
1AC3: ED4B16FE ld bc, (buffer + $16) ; Get FAT size in sectors
1AC7: ED43E8FD ld (fatsec), bc
1ACB: ED4B11FE ld bc, (buffer + $11) ; Get length of root directory
1ACF: ED43EAFD ld (rootlen), bc
1AD3: 2AECFD ld hl, (pstart) ; Compute
1AD6: ED4BE6FD ld bc, (ressec) ; FAT1START = PSTART + RESSEC
1ADA: 09 add hl, bc
1ADB: 22F4FD ld (fat1start), hl
1ADE: 2AEEFD ld hl, (pstart + 2)
1AE1: 010000 ld bc, 0
1AE4: ED4A adc hl, bc
1AE6: 22F6FD ld (fat1start + 2), hl
1AE9: 2AE8FD ld hl, (fatsec) ; Compute ROOTSTART for two FATs
1AEC: 29 add hl, hl ; ROOTSTART = FAT1START +
1AED: 444D ld bc, hl ; 2 * FATSIZ
1AEF: 2AF4FD ld hl, (fat1start)
1AF2: 09 add hl, bc
1AF3: 22F8FD ld (rootstart), hl
1AF6: 2AF6FD ld hl, (fat1start + 2)
1AF9: 010000 ld bc, 0
1AFC: ED4A adc hl, bc
1AFE: 22FAFD ld (rootstart + 2), hl
1B01: ED4BEAFD ld bc, (rootlen) ; Compute rootlen / 16
1B05: CB28 sra b ; By shifting it four places
1B07: CB19 rr c ; to the right
1B09: CB28 sra b ; This value will be used
1B0B: CB19 rr c ; for the calculation of
1B0D: CB28 sra b ; DATASTART
1B0F: CB19 rr c
1B11: CB28 sra b
1B13: CB19 rr c
1B15: 2AF8FD ld hl, (rootstart) ; Computer DATASTART
1B18: 09 add hl, bc
1B19: 22FCFD ld (datastart), hl
1B1C: 2AFAFD ld hl, (rootstart + 2)
1B1F: 010000 ld bc, 0
1B22: ED4A adc hl, bc
1B24: 22FEFD ld (datastart + 2), hl
1B27: 21C21C ld hl, fatmount_s1 ; Print mount summary
1B2A: CD4B12 call puts
1B2D: 21DCFD ld hl, fatname
1B30: CD4B12 call puts
1B33: 21CD1C ld hl, fatmount_s2
1B36: CD4B12 call puts
1B39: 3AE5FD ld a, (clusiz)
1B3C: CD1212 call print_byte
1B3F: 21D91C ld hl, fatmount_s3
1B42: CD4B12 call puts
1B45: 2AE6FD ld hl, (ressec)
1B48: CD3312 call print_word
1B4B: 21E51C ld hl, fatmount_s4
1B4E: CD4B12 call puts
1B51: 2AE8FD ld hl, (fatsec)
1B54: CD3312 call print_word
1B57: 21F11C ld hl, fatmount_s5
1B5A: CD4B12 call puts
1B5D: 2AEAFD ld hl, (rootlen)
1B60: CD3312 call print_word
1B63: 21FE1C ld hl, fatmount_s6
1B66: CD4B12 call puts
1B69: 2AF2FD ld hl, (psiz + 2)
1B6C: CD3312 call print_word
1B6F: 2AF0FD ld hl, (psiz)
1B72: CD3312 call print_word
1B75: 21091D ld hl, fatmount_s7
1B78: CD4B12 call puts
1B7B: 2AEEFD ld hl, (pstart + 2)
1B7E: CD3312 call print_word
1B81: 2AECFD ld hl, (pstart)
1B84: CD3312 call print_word
1B87: 21151D ld hl, fatmount_s8
1B8A: CD4B12 call puts
1B8D: 2AF6FD ld hl, (fat1start + 2)
1B90: CD3312 call print_word
1B93: 2AF4FD ld hl, (fat1start)
1B96: CD3312 call print_word
1B99: 21241D ld hl, fatmount_s9
1B9C: CD4B12 call puts
1B9F: 2AFAFD ld hl, (rootstart + 2)
1BA2: CD3312 call print_word
1BA5: 2AF8FD ld hl, (rootstart)
1BA8: CD3312 call print_word
1BAB: 21331D ld hl, fatmount_sa
1BAE: CD4B12 call puts
1BB1: 2AFEFD ld hl, (datastart + 2)
1BB4: CD3312 call print_word
1BB7: 2AFCFD ld hl, (datastart)
1BBA: CD3312 call print_word
1BBD: CD4C11 call crlf
1BC0: 181A jr fatmount_exit
1BC2: 21E31B fatmount_e1 ld hl, fatmount_1
1BC5: 1812 jr fatmount_x
1BC7: 210A1C fatmount_e2 ld hl, fatmount_2
1BCA: 180D jr fatmount_x
1BCC: 212A1C fatmount_e3 ld hl, fatmount_3
1BCF: 1808 jr fatmount_x
1BD1: 21611C fatmount_e4 ld hl, fatmount_4
1BD4: 1803 jr fatmount_x
1BD6: 218E1C fatmount_e5 ld hl, fatmount_5
1BD9: CD4B12 fatmount_x call puts
1BDC: DDE1 fatmount_exit pop ix
1BDE: E1 pop hl
1BDF: D1 pop de
1BE0: C1 pop bc
1BE1: F1 pop af
1BE2: C9 ret
1BE3: 46415441
1BE7: 4C284641
1BEB: 544D4F55
1BEF: 4E54293A
1BF3: 20436F75
1BF7: 6C64206E
1BFB: 6F742072
1BFF: 65616420
1C03: 4D425221
1C07: 0D0A00 fatmount_1 defb "FATAL(FATMOUNT): Could not read MBR!", cr, lf, eos
1C0A: 46415441
1C0E: 4C284641
1C12: 544D4F55
1C16: 4E54293A
1C1A: 20496C6C
1C1E: 6567616C
1C22: 204D4252
1C26: 210D0A00 fatmount_2 defb "FATAL(FATMOUNT): Illegal MBR!", cr, lf, eos
1C2A: 46415441
1C2E: 4C284641
1C32: 544D4F55
1C36: 4E54293A
1C3A: 20436F75
1C3E: 6C64206E
1C42: 6F742072
1C46: 65616420
1C4A: 70617274
1C4E: 6974696F
1C52: 6E20626F
1C56: 6F742062
1C5A: 6C6F636B fatmount_3 defb "FATAL(FATMOUNT): Could not read partition boot block"
1C5E: 0D0A00 defb cr, lf, eos
1C61: 46415441
1C65: 4C284641
1C69: 544D4F55
1C6D: 4E54293A
1C71: 20464154
1C75: 206E756D
1C79: 62657220
1C7D: 6E6F7420
1C81: 65717561
1C85: 6C207477
1C89: 6F21 fatmount_4 defb "FATAL(FATMOUNT): FAT number not equal two!"
1C8B: 0D0A00 defb cr, lf, eos
1C8E: 46415441
1C92: 4C284641
1C96: 544D4F55
1C9A: 4E54293A
1C9E: 20536563
1CA2: 746F7220
1CA6: 73697A65
1CAA: 206E6F74
1CAE: 20657175
1CB2: 616C2035
1CB6: 31322062
1CBA: 79746573
1CBE: 21 fatmount_5 defb "FATAL(FATMOUNT): Sector size not equal 512 bytes!"
1CBF: 0D0A00 defb cr, lf, eos
1CC2: 09464154
1CC6: 4E414D45
1CCA: 3A0900 fatmount_s1 defb tab, "FATNAME:", tab, eos
1CCD: 0D0A0943
1CD1: 4C555349
1CD5: 5A3A0900 fatmount_s2 defb cr, lf, tab, "CLUSIZ:", tab, eos
1CD9: 0D0A0952
1CDD: 45535345
1CE1: 433A0900 fatmount_s3 defb cr, lf, tab, "RESSEC:", tab, eos
1CE5: 0D0A0946
1CE9: 41545345
1CED: 433A0900 fatmount_s4 defb cr, lf, tab, "FATSEC:", tab, eos
1CF1: 0D0A0952
1CF5: 4F4F544C
1CF9: 454E3A09
1CFD: 00 fatmount_s5 defb cr, lf, tab, "ROOTLEN:", tab, eos
1CFE: 0D0A0950
1D02: 53495A3A
1D06: 090900 fatmount_s6 defb cr, lf, tab, "PSIZ:", tab, tab, eos
1D09: 0D0A0950
1D0D: 53544152
1D11: 543A0900 fatmount_s7 defb cr, lf, tab, "PSTART:", tab, eos
1D15: 0D0A0946
1D19: 41543153
1D1D: 54415254
1D21: 3A0900 fatmount_s8 defb cr, lf, tab, "FAT1START:", tab, eos
1D24: 0D0A0952
1D28: 4F4F5453
1D2C: 54415254
1D30: 3A0900 fatmount_s9 defb cr, lf, tab, "ROOTSTART:", tab, eos
1D33: 0D0A0944
1D37: 41544153
1D3B: 54415254
1D3F: 3A0900 fatmount_sa defb cr, lf, tab, "DATASTART:", tab, eos
1D42: ;
1D42: ; Dismount a FAT volume (invalidate the FAT control block by setting the
1D42: ; first byte (of fatname) to zero.
1D42: ;
1D42: F5 fatunmount push af
1D43: E5 push hl
1D44: AF xor a
1D45: 21DCFD ld hl, fatname
1D48: 77 ld (hl), a ; Clear first byte of fatname
1D49: E1 pop hl
1D4A: F1 pop af
1D4B: C9 ret
1D4C: ;
1D4C: ; Here the dispatch table for calling system routines starts. Every entry
1D4C: ; must contain only the destination address (2 bytes).
1D4C: ;
1D4C: 1C14 dispatch_table defw cold_start ; $00 = clear etc.
1D4E: ; Parameters: N/A
1D4E: ; Action: Performs a cold start (memory is cleared!)
1D4E: ; Return values: N/A
1D4E: ;
1D4E: F610 defw is_hex
1D50: ; Parameters: A contains a character code
1D50: ; Action: Tests ('0' 0
1D5E: ;
1D5E: BA11 defw gets
1D60: ; Parameters: HL contains a buffer address, B contains the
1D60: ; buffer length (including the terminating
1D60: ; null byte!)
1D60: ; Action: Reads a string from STDIN. Terminates when
1D60: ; either the buffer is full or the string is
1D60: ; terminated by CR/LF
1D60: ; Return values: N/A
1D60: ;
1D60: 6114 defw fgetc
1D62: ; Parameters: IY (pointer to a valid FCB)
1D62: ; Action: Reads a character from a FAT file
1D62: ; Return values: Character in A, if EOF has been encountered,
1D62: ; the carry flag will be set
1D62: ;
1D62: 8C15 defw dump_fcb
1D64: ; Parameters: IY (pointer to a valid FCB)
1D64: ; Action: Prints the contents of the FCB in human
1D64: ; readable format to STDOUT
1D64: ; Return values: N/A
1D64: ;
1D64: 1C17 defw fopen
1D66: ; Parameters: HL (points to a buffer containing the file
1D66: ; file name), IY (points to an empty FCB),
1D66: ; DE (points to a 12 character string buffer)
1D66: ; Action: Opens a file for reading
1D66: ; Return values: N/A (All information is contained in the FCB)
1D66: ;
1D66: 6F18 defw dirlist
1D68: ; Parameters: N/A (relies on a valid FAT control block)
1D68: ; Action: Writes a directory listing to STDOUT
1D68: ; Return values: N/A
1D68: ;
1D68: 4A1A defw fatmount
1D6A: ; Parameters: N/A (needs the global FAT control block)
1D6A: ; Action: Mounts a disk (populates the FAT CB)
1D6A: ; Return values: N/A
1D6A: ;
1D6A: 421D defw fatunmount
1D6C: ; Parameters: N/A (needs the global FAT control block)
1D6C: ; Action: Invalidates the global FAT control block
1D6C: ; Return values: N/A
1D6C: 1E11 defw strchr
1D6E: ; Parameters: HL contains buffer address, A contains
1D6E: ; character to be searched in buffer
1D6E: ; Action: Look for the first occurrence of a character
1D6E: ; in a string
1D6E: ; Return values: Carry bit set if character was found. HL
1D6E: ; points to the address of this character in
1D6E: ; the buffer
1D6E: 6C12 defw uart_status
1D70: ; Parameters: N/A
1D70: ; Action: Checks the UART status
1D70: ; Return values: RX status in carry flag, TX status in Z flag.
1D70: ; Register A and F are modified!
1D70: 5F11 defw getc_nowait
1D72: ; Parameters: N/A
1D72: ; Action: Reads a character code from the serial line
1D72: ; but does not wait for a character to be there.
1D72: ; This function is usable eg. for a Forth
1D72: ; interpreter etc.
1D72: ; Return values: A contains a character code
1D72: 3312 defw print_word
1D74: ; Parameters: HL contains the 16 bit value to be printed
1D74: ; Action: Prints a 16 bit value in hexadecimal repre-
1D74: ; sentation
1D74: ; Return values: N/A
1D74: 1212 defw print_byte
1D76: ; Parameters: A contains the byte to be printed
1D76: ; Action: Prints a byte in hexadecimal notation
1D76: ; Return values: N/A
1D76: 5B12 defw stroup
1D78: ; Parameters: HL points to the string to be converted
1D78: ; Action: Converts a string to upper case
1D78: ; Return values: N/A
1D78: AF11 defw get_word
1D7A: ; Parameters: N/A
1D7A: ; Action: Reads a four nibble hex-word from stdin
1D7A: ; Return values: HL contains the value read
1D7A: ;
1D7A: ;******************************************************************************
1D7A: ;***
1D7A: ;*** From here on various subsystems can be included (like Forth, BASIC etc.).
1D7A: ;***
1D7A: ;******************************************************************************
1D7A: ;
1D7A: 215334 forth_subsystem ld hl, forth_msg
1D7D: CD4B12 call puts
345B: 00 end_of_monitor defb 0
;... ...The included CAMEL Forth interpreter resides here... ...
345C: #end ; ((inserted by zasm))
28-JUN-2013
Comments