From: Alan Cox Date: Sat, 6 Dec 2014 22:00:25 +0000 (+0000) Subject: trs80: first cut at a floppy driver based upon the Dragon work X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=f2c9206d6f6d55a8113e8498d97556b73d0eae47;p=FUZIX.git trs80: first cut at a floppy driver based upon the Dragon work --- diff --git a/Kernel/platform-trs80/Makefile b/Kernel/platform-trs80/Makefile index b309f392..2803d028 100644 --- a/Kernel/platform-trs80/Makefile +++ b/Kernel/platform-trs80/Makefile @@ -3,7 +3,7 @@ CSRCS = devlpr.c devtty.c devfd.c CSRCS += devices.c main.c ASRCS = trs80.s crt0.s -ASRCS += tricks.s commonmem.s +ASRCS += tricks.s commonmem.s floppy.s COBJS = $(CSRCS:.c=.rel) AOBJS = $(ASRCS:.s=.rel) diff --git a/Kernel/platform-trs80/devfd.c b/Kernel/platform-trs80/devfd.c index caabc228..bc838839 100644 --- a/Kernel/platform-trs80/devfd.c +++ b/Kernel/platform-trs80/devfd.c @@ -1,249 +1,99 @@ -/* - * TRS80 disk driver (TODO) - * - */ - #include #include #include +#include -#define FD_TIMEOUT 100 /* FIXME */ - -/* Floppy controller */ -__sfr __at 0xF0 fd_command; -__sfr __at 0xF0 fd_status; -#define FD_BUSY 1 -#define FD_DRQ 2 - -#define CMD_RESET 0x0B -#define CMD_SEEK 0x1B -#define CMD_READ 0x8C -#define CMD_WRITE 0xAC -__sfr __at 0xF1 fd_track; -__sfr __at 0xF2 fd_sector; -__sfr __at 0xF3 fd_data; -/* Drive select */ -__sfr __at 0xF4 fd_select; - -/* floppies. 26 128 byte sectors, not a nice way to use all of them in 512's */ -static int sectrack[16] = { - 18 -}; - -static uint8_t track[4]; /* only one controller register for all 4 drives */ -static uint8_t curdrive = 0xFF; -static uint8_t fd_timer; - -static int fd_transfer(bool is_read, uint8_t minor, uint8_t rawflag); - -/* Replace with proper asm delay */ -static void nap(void) -{ - int i; - for(i=0;i<16;i++); -} - -/* To write */ -static int fd_wait_idle(void) -{ - return 0xFF; -} - -int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag) -{ - flag; - return fd_transfer(true, minor, rawflag); -} - -int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag) -{ - flag; - return fd_transfer(false, minor, rawflag); -} - -static uint8_t fd_seek(uint8_t track) -{ - uint8_t status; - fd_track = track; - nap(); - fd_command = CMD_SEEK; - nap(); - status = fd_wait_idle(); - /* FIXME: bits of status */ - return 0; -} - -static uint8_t fd_writedata(uint8_t *dptr) -{ - uint8_t status; - irqflags_t irq; - uint8_t r; - uint16_t a; - - fd_command = CMD_WRITE; - nap(); - do { - r = fd_status; - if (r & FD_DRQ) { - irq = di(); - for (a = 0; a < 256; a++) { - fd_data = *dptr++; - } - irqrestore(irq); - status = fd_wait_idle(); - /* bits of status */ - return status & 0x5C; - } - } - while (r & FD_BUSY); - /* Went clear without asking for data */ - return -1; -} - -static uint8_t fd_readdata(uint8_t *dptr) -{ - uint8_t status; - irqflags_t irq; - uint8_t r; - unsigned int a; - - fd_command = CMD_READ; - nap(); - do { - r = fd_status; - if (r & FD_DRQ) { - irq = di(); - for (a = 0; a < 256; a++) { - *dptr++= fd_data;; - } - irqrestore(irq); - status = fd_wait_idle(); - /* bits of status */ - return status & 0x1C; - } - } - while (r & FD_BUSY); - /* Went clear without asking for data */ - return -1; -} - -static uint8_t fd_reset(void) -{ - uint8_t status; - - fd_command = CMD_RESET; - nap(); - status = fd_wait_idle(); - return 0; -} - -static uint8_t fd_geom(int minor, blkno_t block) -{ - /* Turn block int track/sector - and write to the controller. - Forced to do real / and % */ - uint8_t trackw = block / sectrack[minor]; - uint8_t sectorw = block % sectrack[minor]; - uint8_t status = 0; +#define MAX_FD 4 - if (trackw != track[curdrive]) { - status = fd_seek(trackw); - track[curdrive] = trackw; - } - fd_sector = sectorw; - fd_timer = FD_TIMEOUT; - nap(); - return status & 0x10; -} +#define OPDIR_NONE 0 +#define OPDIR_READ 1 +#define OPDIR_WRITE 2 -/* Deselect drive, motor off, may be called from an IRQ */ -static void fd_deselect(void) -{ - if (curdrive != 0xFF) { - track[curdrive] = fd_track; - curdrive = 0xFF; - } - fd_select = 0; -} +#define FD_READ 0x88 /* 2797 needs 0x88, 1797 needs 0x80 */ +#define FD_WRITE 0xA8 /* Likewise A8 v A0 */ -static void sdcc_bug(void) -{ -} +static uint8_t motorct; +static uint8_t fd_selected = 0xFF; +static uint8_t fd_tab[MAX_FD]; -static void fd_drivesel(uint8_t minor) -{ - irqflags_t irq = di(); - uint8_t sdcc_tmp; +/* + * We only support normal block I/O for the moment. We do need to + * add swapping! + */ - if (minor != curdrive) { - if (curdrive != 0xFF) - fd_deselect(); - track[curdrive] = fd_track; - /* nap needed anywhere ? */ - fd_track = track[minor]; - curdrive = minor; - /* FIXME: check and check for spin up times */ +static uint8_t selmap[4] = { 0x01, 0x02, 0x04, 0x08 }; - /* Do this in two steps to stop SDCC 3.4 crashing and - we need the dummy call to stop it optimising it back into - the broken version */ - sdcc_tmp = 1 << minor; - sdcc_bug(); - fd_select = sdcc_tmp; - } - fd_timer = FD_TIMEOUT; - irqrestore(irq); -} - -static int fd_transfer(bool is_read, uint8_t minor, uint8_t rawflag) +static int fd_transfer(uint8_t minor, bool is_read, uint8_t rawflag) { blkno_t block; - int block_xfer; /* r/w return value (number of 512 byte blocks transferred) */ - uint8_t *dptr; - int dlen; + uint16_t dptr; int ct = 0; - int st; int tries; + uint8_t err = 0; + uint8_t *driveptr = fd_tab + minor; + uint8_t cmd[6]; + + if(rawflag) + goto bad2; - if(rawflag) { - dlen = udata.u_count; - dptr = udata.u_base; - block = udata.u_offset >> 9; - block_xfer = dlen >> 8; /* 256 byte blocks */ - } else { /* rawflag == 0 */ - dlen = 512; - dptr = udata.u_buf->bf_data; - block = udata.u_buf->bf_blk; - block_xfer = 2; + if (fd_selected != minor) { + uint8_t err = fd_motor_on(selmap[minor]); + if (err) + goto bad; } - fd_drivesel(minor); - while (ct < block_xfer) { - for (tries = 0; tries < 3; tries++) { - fd_geom(minor, block); - if (tries > 0) - fd_reset(); - if (is_read) - st = fd_readdata(dptr); - else - st = fd_writedata(dptr); - if (st == 0) + dptr = (uint16_t)udata.u_buf->bf_data; + block = udata.u_buf->bf_blk; + +// kprintf("Issue command: drive %d\n", minor); + cmd[0] = is_read ? FD_READ : FD_WRITE; + cmd[1] = block / 9; /* 2 sectors per block */ + cmd[2] = ((block % 9) << 1) + 1; /*eww.. */ + cmd[3] = is_read ? OPDIR_READ: OPDIR_WRITE; + cmd[4] = dptr & 0xFF; + cmd[5] = dptr >> 8; + + while (ct < 2) { + for (tries = 0; tries < 4 ; tries++) { + err = fd_operation(cmd, driveptr); + if (err == 0) break; + if (tries > 1) + fd_reset(driveptr); } + /* FIXME: should we try the other half and then bale out ? */ if (tries == 3) - kprintf("fd%d: disk error %02X\n", st); - block++; + goto bad; + cmd[5]++; /* Move on 256 bytes in the buffer */ + cmd[2]++; /* Next sector for 2nd block */ ct++; - dptr += 256; } - return ct/2; + return 1; +bad: + kprintf("fd%d: error %x\n", minor, err); +bad2: + udata.u_error = EIO; + return -1; } -int fd_open(uint8_t minor) +int fd_open(uint8_t minor, uint16_t flag) { - if(minor >= 3 || !sectrack[minor]) { + flag; + if(minor > MAX_FD) { udata.u_error = ENODEV; return -1; } return 0; } + +int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag) +{ + flag; + return fd_transfer(minor, true, rawflag); +} + +int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag) +{ + flag; + return fd_transfer(minor, false, rawflag); +} diff --git a/Kernel/platform-trs80/devfd.h b/Kernel/platform-trs80/devfd.h index da2a70c9..0ecc83b5 100644 --- a/Kernel/platform-trs80/devfd.h +++ b/Kernel/platform-trs80/devfd.h @@ -6,4 +6,10 @@ int fd_read(uint8_t minor, uint8_t rawflag, uint8_t flag); int fd_write(uint8_t minor, uint8_t rawflag, uint8_t flag); int fd_open(uint8_t minor, uint16_t flag); +/* low level interface */ +uint16_t fd_reset(uint8_t *driveptr); +uint16_t fd_operation(uint8_t *cmd, uint8_t *driveptr); +uint16_t fd_motor_on(uint16_t drivesel); +uint16_t fd_motor_off(uint16_t driveptr); + #endif /* __DEVRD_DOT_H__ */ diff --git a/Kernel/platform-trs80/floppy.s b/Kernel/platform-trs80/floppy.s new file mode 100644 index 00000000..324f6101 --- /dev/null +++ b/Kernel/platform-trs80/floppy.s @@ -0,0 +1,378 @@ +; +; Core floppy routines for the TRS80 1791 FDC +; Based on the 6809 code +; +; FIXME: better drive spin up wait +; FIXME: double sided media +; FIXME: correct step rates (per drive ?) +; FIXME: precompensation +; - not on single density +; - track dependant for double density based on trsdos dir pos +; +; + + .globl _fd_reset + .globl _fd_operation + .globl _fd_motor_on + .globl _fd_motor_off + .globl fd_nmi_handler + +FDCREG .equ 0xF0 +FDCTRK .equ 0xF1 +FDCSEC .equ 0xF2 +FDCDATA .equ 0x43 +FDCCTRL .equ 0xF4 +FDCINT .equ 0xE4 +; +; interrupt register reports 0x80 for interrut, 0x40 for drq +; (0x20 is the unrelated reset button) +; + +; +; Structures we use +; +; +; Per disk structure to hold device state +; +TRKCOPY .equ 0 + +; +; Command issue +; +CMD .equ 0 +TRACK .equ 1 +SECTOR .equ 2 +DIRECT .equ 3 ; 0 = read 2 = write 1 = status +DATA .equ 4 + + .area _COMMONMEM +; +; Simple routine for pauses +; +nap: dec bc + ld a, b + or c + jr nz, nap + ret +; +; The motor off logic is driven from hardware +; +fd_nmi_handler: + xor a + out (FDCINT), a + ld bc, #100 + call nap + pop af ; discard return address + jp fdio_nmiout ; and jump + +; +; Wait for the drive controller to become ready +; Preserve HL, DE +; +waitdisk: + ld bc, #0 +waitdisk_l: + in a, (FDCREG) + bit 0, a + ret z + ; + ; Keep poking fdcctrl to avoid a hardware motor timeout + ; + ld a, (fdcctrl) + out (FDCCTRL), a + djnz waitdisk_l + dec c + jr nz, waitdisk_l + ld a, #0xD0 ; reset + out (FDCREG), a + ex (sp), hl + ex (sp),hl + ex (sp),hl + ex (sp),hl + in a, (FDCREG) ; read to reset int status + bit 0, a + ret +; +; Set up and perform a disk operation +; +; IX points to the command block +; HL points to the buffer +; DE points to the track reg copy +; +fdsetup: + ld a, (de) + out (FDCTRK), a + cp TRACK(ix) + jr z, fdiosetup + + ; + ; So we can verify + ; + ld a, SECTOR(ix) + out (FDCSEC), a + ; + ; Need to seek the disk + ; + ld a, #0x14 ; seek + out (FDCREG), a + ex (sp),hl + ex (sp),hl + ex (sp),hl + ex (sp),hl + call waitdisk + jr nz, setuptimeout + and #0x18 ; error bits + jr z, fdiosetup + ; seek failed, not good +setuptimeout: ; NE = bad + ld a, #0xff ; we have no idea where we are, force a seek + ld (de), a ; zap track info + ret +; +; Head in the right place +; +fdiosetup: + ld a, TRACK(ix) + ld (de), a ; save track +; cmp #22 ; FIXME +; jr nc, noprecomp +; ld a, (fdcctrl) +; or #0x10 ; Precomp on +; jr precomp1 +;noprecomp: + ld a, (fdcctrl) +;precomp1: + out (FDCCTRL), a + ld a, SECTOR(ix) + out (FDCSEC), a + in a, (FDCREG) ; Clear any pending status + + ld a, CMD(ix) + + ld de, #0 ; timeout handling + + out (FDCREG), a ; issue the command + ex (sp),hl ; give the FDC a moment to think + ex (sp),hl + ex (sp),hl + ex (sp),hl + ld a, DIRECT(ix) + dec a + ld a, (fdcctrl) + ld d, a ; we need this in a register + ; to meet timing + set 6,d ; halt mode bit + jr z, fdio_in + jr nc, fdio_out +; +; Status registers +; +fdxferdone: + ei +fdxferdone2: + in a, (FDCREG) + and #0x19 ; Error bits + busy + bit 0, a ; Wait for busy to drop, return in a + ret z + ld a, (fdcctrl) + out (FDCCTRL), a + jr fdxferdone2 +; +; Write to the disk - HL points to the target buffer +; +fdio_in: + ld e, #0x16 ; bits to check + ld bc, #FDCDATA ; 256 bytes/sector, c is our port +fdio_inl: + in a, (FDCREG) + and e + jr z, fdio_in + ini + di + ld a, d +fdio_inbyte: + out (FDCCTRL), a ; stalls + ini + jr nz, fdio_inbyte + jr fdxferdone + +; +; Read from the disk - HL points to the target buffer +; +fdio_out: + ld bc, #FDCDATA + 0xFF00 ; 256 bytes/sector, c is our port + ld e, #0x76 +fdio_outl: + in a, (FDCREG) ; Wait for DRQ (or error) + and e + jr z, fdio_outl + outi ; Stuff byte into FDC while we think + di + in a, (FDCREG) ; No longer busy ?? + rra + jr nc, fdxferbad ; Bugger... + ld a, #0xC0 ; Turn on magic floppy NMI interface + out (FDCINT), a + ld b, #50 ; Spin for it +spin1: djnz spin1 + ld b, (hl) ; Next byte + inc hl +fdio_waitlock: + ld a, d + out (FDCCTRL), a ; wait states on + in a, (FDCREG) + and e + jr z, fdio_waitlock + out (c), b + ld a, d +fdio_outbyte: + out (FDCCTRL), a ; stalls + outi + jr fdio_outbyte +fdio_nmiout: +; +; Now tidy up +; + jr fdxferdone + +fdxferbad: + ld a, #0xff + ret + +; +; C glue interface. +; +; Because of the brain dead memory paging we dump the bits into +; kernel space always. The thought of taking an NMI while in the +; user memory and bank flipping to recover is just too odious ! +; + +; +; Reset to track 0, wait for the command then idle +; +; fd_reset(uint8_t *drvptr) +; +_fd_reset: + pop de + pop hl + push hl + push de + ld a, (fdcctrl) + out (FDCCTRL), a + ld a, #1 + out (FDCSEC), a + xor a + out (FDCTRK), a + out (FDCREG), a ; restore + dec a + ld (hl), a ; Zap track pointer + ex (sp),hl ; give the FDC a moment to think + ex (sp),hl + ex (sp),hl + ex (sp),hl + + call waitdisk + cp #0xff + ret z + and #0x10 ; Error bit from the reset + ret nz + ld (hl), a ; Track 0 correctly hit + ret +; +; fd_operation(uint16_t *cmd, uint16_t *drive) +; +; The caller must ensure the drive has been selected and the motor is +; running. +; +_fd_operation: + pop bc ; return address + pop hl ; command + pop de ; drive track ptr + push de + push hl + push bc + push ix + push hl + pop ix + ld l, DATA(ix) + ld h, DATA+1(ix) + call fdsetup ; Set up for a command + ld l, a + ld h, #0 + pop ix + ret +; +; C interface fd_motor_on(uint16_t drivesel) +; +; Selects this drive and turns on the motors. Also pass in the +; choice of density +; +; bits 0-3: select that drive +; bit 4: side (must rewrite each drive change) +; bit 5: precompensation (not set here but in the I/O ops) +; bit 6: synchronize I/O by stalling the CPU (don't set this) +; bit 7: set for double density (MFM) +; +; +_fd_motor_on: + pop de + pop hl + push hl + push de + ; + ; Select drive B, turn on motor if needed + ; + ld a,(motor_running) ; nothing selected + or a + jr z, notsel + + cp l + jr z, motor_was_on +; +; Select our drive +; +notsel: + ld h, a ; save state as it was + or l + out (FDCCTRL), a + out (FDCCTRL), a ; TRS80 erratum apparently needs this + ld (fdcctrl), a + bit 4, h ; FIXME - motor bit + jr nz, motor_was_on + ld bc, #0x7F00 ; Long delay (may need FE or FF for some disks) + call nap + ; FIXME: longer motor spin up delay goes here (0.5 or 1 second) + + call waitdisk +; +; All is actually good +; +motor_was_on: + ld hl, #0 + ret + +; +; C interface fd_motor_off(void) +; +; Turns off the drive motors, deselects all drives +; +_fd_motor_off: + ld a, (motor_running) + or a + ret z + ; Should we seek to track 0 ? + in a, (FDCCTRL) + and #0xF0 ; clear drive bits + out (FDCCTRL), a + xor a + ld (motor_running), a + ret + + .area _COMMONDATA +curdrive: + .db 0xff +motor_running: + .db 0 +fdcctrl: + .db 0 + \ No newline at end of file diff --git a/Kernel/platform-trs80/trs80.s b/Kernel/platform-trs80/trs80.s index 6247f354..86071357 100644 --- a/Kernel/platform-trs80/trs80.s +++ b/Kernel/platform-trs80/trs80.s @@ -30,7 +30,7 @@ .globl unix_syscall_entry .globl trap_illegal .globl outcharhex - .globl nmi_handler + .globl fd_nmi_handler .globl null_handler .include "kernel.def" @@ -158,7 +158,7 @@ _program_vectors: ld (0x0001), hl ld (0x0066), a ; Set vector for NMI - ld hl, #nmi_handler + ld hl, #fd_nmi_handler ld (0x0067), hl ; diff --git a/Kernel/platform-trs80/trsfd.s b/Kernel/platform-trs80/trsfd.s deleted file mode 100644 index 3d506a9e..00000000 --- a/Kernel/platform-trs80/trsfd.s +++ /dev/null @@ -1,123 +0,0 @@ - -FDCSTAT .equ 0xF0 -FDCA .equ 0xF0 -FDCTRK .equ 0xF1 -FDCSEC .equ 0xF2 -FDCDAT .equ 0xF3 -DRVSEL .equ 0xF4 - -; -; Write a command to the FDC then nap briefly while the -; FDC digests it -; -writecmd: - out (FDCA), a ; off we go -cmd_1: ; wait for controller - ld b, 18 - djnz cmd_1 - ret - - -; -; Set the desired track -; -; B = the track we think we are on -; C = the sector we will want -; D = the track we will want -; E = the step rate (0-3) -; -seekto: call reselect - out (FDCTRK), b ; current track - out (FDCSEC), c ; sector we want - out (FDCDATA), d ; track we want - ld a, b - cp d ; do we think we are there ? - ld b, 0x18 ; SEEK - jr z, seekto_1 - ld b, 0x1C ; SEEK with verify -seekto_1: ld a, b - or e ; step rate - call writecmd - ret - -; -; Perform a read transfer once we have been through the selection -; process -; -; d = track -; -; -; -read_xfer: ld c, FDCTRK - out (c), d ; track we want - - call writecmd - - ld bc, FDCDAT ; 256 bytes/sector, and load c with - ; our port - ld e, 0x16 ; mask of bits we are checking -; -; Wait for DRQ, and then block transfer the bytes -; -wait_drq: in a, (FDCSTAT) - and e - jr z, wait_drq - ini - di - ld a, d -wait_go: out (DRVSEL), a ; wait stating - ini ; byte in - jr nz, wait_go ; repeat -; -; Sector data has landed -; - call fdcwait -; status in A - ret - - -fdcwait: - in a, (FDCSTAT) - bit 0, a ; need a in return so don't use the rra - ; shortcut - ret z ; not busy ??? - ld a, drvsel - out (DRVSEL), a - jr fdcwait - - -select: push bc - call reselect - ld b, a ; save the status - rlca - rla ; magic - move bits 6/4 into bits 7/4 - sraa - and 0x90 - ld c, a - bit 7, a ; double density ? - jr z, nocomp - ld a, precomp_start - cp d ; track needs precomp ? - jr nc, nocomp - set 5, c - ld a, drivesel - and 0x0f - or c - - out (DRVSEL), a ; select drive - out (DRVSEL), a ; in case the trash80 wasn't listening - - bit 1, b ; delay time ? - call z, fdcspin ; long - call fdcspin ; short (1/2 long) - pop bc - ret - -fdcspin: ld b, 0x7f - pause... - ret - - - -fdc_ - diff --git a/Kernel/platform-trs80/uzi.lnk b/Kernel/platform-trs80/uzi.lnk index 78e488ac..04d2a902 100644 --- a/Kernel/platform-trs80/uzi.lnk +++ b/Kernel/platform-trs80/uzi.lnk @@ -17,6 +17,7 @@ platform-trs80/main.rel timer.rel kdata.rel platform-trs80/devfd.rel +platform-trs80/floppy.rel platform-trs80/devices.rel devio.rel filesys.rel