zx128: first bits of trying to build a betadisk driver
authorAlan Cox <alan@linux.intel.com>
Thu, 19 Feb 2015 11:35:25 +0000 (11:35 +0000)
committerAlan Cox <alan@linux.intel.com>
Thu, 19 Feb 2015 11:35:25 +0000 (11:35 +0000)
This is ugly stuff, we can only access it via the ROM and the ROM doesn't
contain the functionality we need.

Kernel/platform-zx128/betadisk.s [new file with mode: 0644]
Kernel/platform-zx128/devbeta.c [new file with mode: 0644]

diff --git a/Kernel/platform-zx128/betadisk.s b/Kernel/platform-zx128/betadisk.s
new file mode 100644 (file)
index 0000000..37de574
--- /dev/null
@@ -0,0 +1,261 @@
+;
+;      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
diff --git a/Kernel/platform-zx128/devbeta.c b/Kernel/platform-zx128/devbeta.c
new file mode 100644 (file)
index 0000000..bc531c3
--- /dev/null
@@ -0,0 +1,112 @@
+#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);
+}