--- /dev/null
+;
+; Interface glue for the beta disk.
+;
+; This is a work in progress. The betadisk is only accessible when the
+; built in ROM is mapped. It unmaps itself when we return to RAM. It
+; has a sort of "BIOS" type ABI but that assumes we are in ZX Basic
+; and also screws around with memory behind the caller.
+;
+; So we have to do things differently. Instead we treat the TR-DOS 5
+; ROM as a ROP exploit target.
+;
+; This is a WIP and assumes the TR-DOS v5 ROM
+;
+
+ .module betadisk
+
+ .globl _betadev, _betaaddr, _betasector, _betatrack
+ .globl _betauser, _betacount
+
+ .globl _trdos_init, _trdos_read, _trdos_write
+
+ .globl map_process_save, map_kernel_restore
+
+ .globl outhl
+
+ .area _COMMONDATA
+;
+; Ordering matters as we load some as pairs
+;
+_betadev: .db 0 ; disk number
+_betaaddr: .dw 0 ; buffer ptr
+_betasector: .db 0 ; sector (1 based)
+_betatrack: .db 0 ; track (low bit is side)
+_betauser: .db 0 ; paging info
+_betacount: .db 0 ; count of blocks
+_betacmd: .db 0 ; command byte
+_betatrackreg: .db 0 ; used to track h/w
+saved_sp: .dw 0 ; stack for unwinds
+saved_hl: .dw 0 ; HL for trdos entry
+
+ .area _COMMONMEM
+
+_trdos_read:
+ ld a, #0x80
+ ld (_betacmd), a
+trdos_op:
+ ld bc, (_betasector)
+ call seekhead
+ jr nz, error
+ ld a, (_betauser)
+ or a
+ push af
+ call z, map_process_save
+ call trdos_doit
+ pop af
+ call z, map_kernel_restore
+ ld h, b
+ ld c, l
+ ret
+error:
+ ld hl, #-1
+ ret
+
+_trdos_write:
+ ld a, #0xA0
+ ld (_betacmd), a
+ jr trdos_op
+
+
+;
+; Head is on the right track, density is right, drive is right
+;
+trdos_doit:
+ ld hl, (_betaaddr)
+ ld a, (_betacmd)
+ cp #0x80
+ jr nz, dowrite
+ call write_cmd_delay
+ call wait_drq_and_read
+ call read_status
+ ret
+dowrite: call write_cmd_delay
+ call wait_drq_and_write
+ call read_status
+ ; Check status
+ ret
+
+
+;
+; Hook into the ROM to issue a reset and seek to 0
+;
+trdos_reset:
+ ; Set the frame up for the exception path
+ push bc
+ push de
+ ld (saved_sp), sp
+ ld hl, #0x3d98
+ call trdos_call
+ xor a
+ ; We can't easily read the real register so shadow it
+ ld (_betatrackreg), a
+ pop de
+ pop bc
+ ret
+
+;
+; Drive head and seek management. Assumes track register is
+; correctly loaded
+;
+seekhead:
+ ld a, (_betadev)
+ or #0x24 ; MFM, don't reset
+ ld b, a
+ ld a, (_betatrack)
+ or a
+ rra
+ jr c, topside
+ ; bottom
+ set 3,b
+topside:
+ ld (_betatrack), a
+ ld a, b
+ call trdos_outsys ; Select the drive/side
+
+ ; need delays on the various side/disk switches
+ ; check if need to seek
+ call trdos_outdata
+; call nap
+ ld a, #0x18 ; head load, 6ms
+ ld hl, #0x2f57 ; issue command, wait for INT
+ call trdos_call
+ call read_status
+ ; Check status - save new value
+ ret
+;
+; Arbitrary code execution in the TRDOS ROM
+;
+trdos_call: push hl
+ ld hl, (saved_hl)
+ jp 0x3D2F
+
+;
+; An entry point that writes A to the command register and returns
+;
+trdos_outctrl: ld hl, #0x2fc3
+ call trdos_call
+ ret
+;
+; An entry point that writes A into the track register and returns
+;
+trdos_outtrk: push hl
+ ld hl, #0x1e3a
+ call trdos_call
+ pop hl
+ ret
+;
+; Write A into the interface control register
+;
+trdos_outsys: push hl
+ ld hl,#0x1ff3
+ call trdos_call
+ pop hl
+ ret
+;
+; Write the other registers via 0x20B8
+;
+trdos_outsec: push bc
+ push de
+ push hl
+ ld bc, #0x015F
+;
+; Write D into register C B times
+;
+trdos_outit:
+ ld d, a
+ ld hl, #0x20B8
+ call trdos_call
+ pop hl
+ pop de
+ pop bc
+ ret
+
+trdos_outdata: push bc
+ push de
+ push hl
+ ld bc, #0x017F
+ jr trdos_outit
+
+;
+; Low level I/O routines we can borrow
+;
+
+wait_drq_and_read:
+ ld (saved_hl), hl
+ ld hl, #0x3fd5
+ call trdos_call
+ ret
+
+wait_drq_and_write:
+ ld (saved_hl), hl
+ ld hl, #0x3fba
+ call trdos_call
+ ret
+
+write_cmd_delay:
+ push hl
+ ld hl, #0x2f57
+ call trdos_call
+ pop hl
+ ret
+
+;
+; This is a somewhat evil dive into the middle of some code which in
+; some cases will attempt to jump back into BASIC. We catch it trying
+; to do so on errors and longjmp out of it.
+;
+read_status:
+ push bc
+ push de
+ ld (saved_sp), sp
+ ld d, #1
+ xor a
+ ld (0x5d15), a
+ dec a
+ ld (0x5d0e), a
+ ld hl, #0x3f33
+ call trdos_call
+unwind:
+ ; b now holds status
+ ld l, b
+ ld h, #0
+ pop de
+ pop bc
+ ret
+
+;
+; TRDOS tried to print an error message. Grab it as it tries to
+; go to the BASIC ROM and instead recover our stack and return an
+; error
+;
+_trdos_exception:
+ pop hl
+ ld a, l
+ cp #0x54 ; checking break key
+ jr z, cbreak
+ ld sp, (saved_sp)
+ jr unwind
+cbreak:
+ scf ; no break (make timer based)
+ ret
+
+
+;
+; Interceptor for ROM
+;
+_trdos_init:
+ ld a, #0xc3
+ ld (0x5cc2), a
+ ld hl, #_trdos_exception
+ ld (0x5cc3), hl
+ ret
--- /dev/null
+#include <kernel.h>
+#include <kdata.h>
+#include <printf.h>
+#include <devfd.h>
+#include <devbeta.h>
+
+#define MAX_FD 4
+
+static uint8_t lastdisc = 0xFF;
+
+/*
+ * First cut at a beta driver. This uses the ROM entry points as the
+ * beta has a design where you can't access the disk except with the
+ * ROM paged in.
+ *
+ * We treat the ROM as a set of pieces to build a ROP type exploit.
+ *
+ * Note: the ROM has 39xx all set to 0xFF which means that we can
+ * continue to take interrupts with this ROM paged in as we'll read
+ * 0xFFFF and then take the vector as JR xx (where 0000 is still 0xF3
+ * as this rom also starts with a DI).
+ */
+
+static int beta_transfer(uint8_t minor, bool is_read, uint8_t rawflag)
+{
+ blkno_t block;
+ uint16_t ret;
+
+ if(rawflag == 2)
+ goto bad2;
+
+ /* Select the right disc */
+
+ if (lastdisc != minor) {
+ betadev = minor;
+ if (trdos_init())
+ goto bad2;
+ lastdisc = minor;
+ }
+
+ /* 16 sectors/track, 40 or 80 tracks but the interface expects logical
+ tracks 0-n alternating sides. 256 bytes/sector always */
+
+ if (rawflag == 0) {
+ betaaddr = (uint16_t)udata.u_buf->bf_data;
+ block = udata.u_buf->bf_blk;
+ betacount = 2;
+ } else {
+ if ((udata.u_offset|udata.u_count) & 0x1FF)
+ goto bad2;
+ betaaddr = (uint16_t)udata.u_base;
+ block = udata.u_offset >> 9;
+ betacount = udata.u_count >> 8;
+ }
+ betasector = ((block >> 5) & 0x0F) + 1;
+ betatrack = block >> 5;
+ betauser = rawflag;
+ betadev = minor;
+ di();
+ if (is_read)
+ ret = trdos_read();
+ else
+ ret = trdos_write();
+ ei();
+ if (ret == 0)
+ return betacount >> 1;
+ kprintf("bfd%d: error %d\n", ret);
+bad2:
+ udata.u_error = EIO;
+ return -1;
+}
+
+/* FIXME: how do we detect beta is present sanely ? */
+int beta_open(uint8_t minor, uint16_t flag)
+{
+ int ret;
+
+ flag;
+
+ if(minor >= MAX_FD) {
+ udata.u_error = ENODEV;
+ return -1;
+ }
+ betadev = minor;
+
+ /* Stop the TR-DOS code trying to make workspaces and crap into
+ basic by putting its 'initialized' token into place */
+ *(uint8_t *)0x5CB6 = 0xF4;
+ *(uint8_t *)0x5D16 = 0x00; /*FIXME: syscfg bits */
+
+ ret = trdos_init();
+
+ if (ret) {
+ udata.u_error = EIO;
+ lastdisc = 0xFF;
+ return -1;
+ }
+ lastdisc = minor;
+ return 0;
+}
+
+int beta_read(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ return beta_transfer(minor, true, rawflag);
+}
+
+int beta_write(uint8_t minor, uint8_t rawflag, uint8_t flag)
+{
+ flag;
+ return beta_transfer(minor, false, rawflag);
+}