From ac08a29c830c35baf7e9efc170e20804ee5eb933 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Thu, 19 Feb 2015 11:35:25 +0000 Subject: [PATCH] zx128: first bits of trying to build a betadisk driver 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 | 261 +++++++++++++++++++++++++++++++ Kernel/platform-zx128/devbeta.c | 112 +++++++++++++ 2 files changed, 373 insertions(+) create mode 100644 Kernel/platform-zx128/betadisk.s create mode 100644 Kernel/platform-zx128/devbeta.c diff --git a/Kernel/platform-zx128/betadisk.s b/Kernel/platform-zx128/betadisk.s new file mode 100644 index 00000000..37de5745 --- /dev/null +++ b/Kernel/platform-zx128/betadisk.s @@ -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 index 00000000..bc531c35 --- /dev/null +++ b/Kernel/platform-zx128/devbeta.c @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include + +#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); +} -- 2.34.1