From: Alan Cox Date: Wed, 9 Jan 2019 03:08:08 +0000 (+0000) Subject: linc80: continued work on proper IM2 support X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=647be4c7c38fe6a30027881f0ff1435f21112fb7;p=FUZIX.git linc80: continued work on proper IM2 support Add IM2 driven serial interrupts with proper queues. We don't yet do all the status and error bits right. This enables IM2 serial but we don't gain much from it because we have yet to tackle the core changes needed to leave other interrupt lines running for most cases where we today DI, and to soft count, skip re-entry of the interrupt_handler/timer proper. --- diff --git a/Kernel/platform-linc80/README b/Kernel/platform-linc80/README index 45ff1c31..1611cc23 100644 --- a/Kernel/platform-linc80/README +++ b/Kernel/platform-linc80/README @@ -41,3 +41,29 @@ Version that supports multiple 16K banks and multiple processes in memory ZX style. Also code to probe for and use the 32K hack which is far nicer. Use IM2 properly and do serial buffering code + + + +In progress serial + +TODO: + +CTS/RTS flow control option +Unblocking flow control from drivers +Check auto flow is right also +Carrier recording and drop logging + + +Why does reboot muck up SD card ?? emulator bug ? + + +Core code IM2 enabling + +- Add a DI and EI macro +- Use them in cases where we don't need to really disable interrupts (because + the stack is valid), use di/ei and __hard_() methods for the other cases +- With an IM2 platform enable real interrupts in interrupt_handler at the + first point we can, and for interrupt_handler do an inc and defer for the + next tick if we can't re-enter (so we don't lose ticks and we don't miss + latencies) + diff --git a/Kernel/platform-linc80/crt0.s b/Kernel/platform-linc80/crt0.s index 2d2b7491..d8883c49 100644 --- a/Kernel/platform-linc80/crt0.s +++ b/Kernel/platform-linc80/crt0.s @@ -21,6 +21,7 @@ .area _GSFINAL ; unused .area _DISCARD .area _COMMONMEM + .area _CONSOLE .area _PAGE0 ; unused but stops binman changing us ; exported symbols @@ -40,11 +41,6 @@ .include "kernel.def" - .area _COMMONMEM - -start: - jp init - .area _CODE init: diff --git a/Kernel/platform-linc80/devtty.c b/Kernel/platform-linc80/devtty.c index 8924323d..97119bcc 100644 --- a/Kernel/platform-linc80/devtty.c +++ b/Kernel/platform-linc80/devtty.c @@ -50,6 +50,10 @@ static void sio2_setup(uint8_t minor, uint8_t flags) r |= 0x02; sio_r[3] = r; sio_r[5] = 0x8A | ((t->c_cflag & CSIZE) << 1); + if (minor == 1) + sio2a_wr5 = sio_r[5]; + else + sio2b_wr5 = sio_r[5]; } void tty_setup(uint8_t minor, uint8_t flags) @@ -74,8 +78,20 @@ int tty_carrier(uint8_t minor) return 0; } -void tty_pollirq_sio(void) +void tty_drain_sio(void) { + uint8_t c; +#if 1 + while(sio2b_rxl) { + c = sio2b_rx_get(); + tty_inproc(1, c); + } + while(sio2a_rxl) { + c = sio2a_rx_get(); + tty_inproc(2, c); + } + /* TODO: error, tx flow, modem change etc */ +#else static uint8_t old_ca, old_cb; uint8_t ca, cb; uint8_t progress; @@ -128,14 +144,22 @@ void tty_pollirq_sio(void) tty_carrier_drop(1); } } while(progress); +#endif } void tty_putc(uint8_t minor, unsigned char c) { +#if 1 + if (minor == 1) + sio2b_txqueue(c); + else + sio2a_txqueue(c); +#else if (minor == 2) { SIOA_D = c; } else if (minor == 1) SIOB_D = c; +#endif } /* We will need this for SIO once we implement flow control signals */ @@ -155,26 +179,10 @@ void tty_sleeping(uint8_t minor) ttyready_t tty_writeready(uint8_t minor) { - irqflags_t irq; - uint8_t c; - - irq = di(); - if (minor == 2) { - SIOA_C = 0; /* read register 0 */ - c = SIOA_C; - irqrestore(irq); - if (c & 0x04) /* THRE? */ - return TTY_READY_NOW; + if (minor == 1 && sio2b_txl == 255) return TTY_READY_SOON; - } else if (minor == 1) { - SIOB_C = 0; /* read register 0 */ - c = SIOB_C; - irqrestore(irq); - if (c & 0x04) /* THRE? */ - return TTY_READY_NOW; + if (minor == 2 && sio2a_txl == 255) return TTY_READY_SOON; - } - irqrestore(irq); return TTY_READY_NOW; } @@ -186,7 +194,12 @@ void tty_data_consumed(uint8_t minor) /* kernel writes to system console -- never sleep! */ void kputchar(char c) { - tty_putc(TTYDEV - 512, c); + /* Can't use the normal paths as we must survive interrupts off */ + /* FIXME: would be nicer to just disable tx int and re-enable it ? */ + irqflags_t irq = di(); + while(!(SIOA_C & 0x04)); + SIOA_D = c; if (c == '\n') - tty_putc(TTYDEV - 512, '\r'); + kputchar('\r'); + irqrestore(irq); } diff --git a/Kernel/platform-linc80/devtty.h b/Kernel/platform-linc80/devtty.h index 0857f805..88c239ed 100644 --- a/Kernel/platform-linc80/devtty.h +++ b/Kernel/platform-linc80/devtty.h @@ -10,6 +10,24 @@ __sfr __at (SIO0_BASE + 3) SIOB_C; extern void sio2_otir(uint8_t port) __z88dk_fastcall; void tty_putc(uint8_t minor, unsigned char c); -void tty_pollirq_sio(void); + +extern void sio2a_txqueue(uint8_t c) __z88dk_fastcall; +extern void sio2a_flow_control_on(void); +extern uint16_t sio2a_rx_get(void); +extern uint8_t sio2a_error_get(void); +extern uint8_t sio2a_rxl; +extern uint8_t sio2a_txl; +extern uint8_t sio2a_wr5; + +extern void sio2b_txqueue(uint8_t c) __z88dk_fastcall; +extern void sio2b_flow_control_on(void); +extern uint16_t sio2b_rx_get(void); +extern uint8_t sio2b_error_get(void); +extern uint8_t sio2b_rxl; +extern uint8_t sio2b_txl; +extern uint8_t sio2b_wr5; + +extern void tty_drain_sio(void); + #endif diff --git a/Kernel/platform-linc80/fuzix.lnk b/Kernel/platform-linc80/fuzix.lnk index 67cca019..4d0056fc 100644 --- a/Kernel/platform-linc80/fuzix.lnk +++ b/Kernel/platform-linc80/fuzix.lnk @@ -1,7 +1,8 @@ -mwxuy -i fuzix.ihx --b _COMMONMEM=0x0100 --b _CODE=0x0B00 +-b _CONSOLE=0x0100 +-b _COMMONMEM=0x0300 +-b _CODE=0x0E00 -l z80 platform-linc80/crt0.rel platform-linc80/commonmem.rel diff --git a/Kernel/platform-linc80/kernel.def b/Kernel/platform-linc80/kernel.def index 636b59fa..84680e74 100644 --- a/Kernel/platform-linc80/kernel.def +++ b/Kernel/platform-linc80/kernel.def @@ -1,6 +1,6 @@ ; FUZIX mnemonics for memory addresses etc -U_DATA .equ 0x0103 ; (this is struct u_data from kernel.h) +U_DATA .equ 0x0300 ; (this is struct u_data from kernel.h) U_DATA__TOTALSIZE .equ 0x0200 ; 256+256+256 bytes. Z80_TYPE .equ 0 ; CMOS diff --git a/Kernel/platform-linc80/linc80.s b/Kernel/platform-linc80/linc80.s index 0dd2a999..a8c0b732 100644 --- a/Kernel/platform-linc80/linc80.s +++ b/Kernel/platform-linc80/linc80.s @@ -37,6 +37,7 @@ .globl null_handler .globl nmi_handler .globl outcharhex + .globl init .globl s__COMMONMEM .globl l__COMMONMEM @@ -137,6 +138,7 @@ platform_interrupt_all: .globl spurious ; so we can debug trap on it spurious: + ei reti mapreg: @@ -171,14 +173,21 @@ init_hardware: ld (0x88),hl ; PIO A ld (0x8A),hl ; PIO B - ld hl,#spurious ; For now + ld hl,#sio2b_txd ld (0x90),hl ; SIO B TX empty + ld hl,#sio2b_status ld (0x92),hl ; SIO B External status + ld hl,#sio2b_rx_ring ld (0x94),hl ; SIO B Receive + ld hl,#sio2b_special ld (0x96),hl ; SIO B Special + ld hl,#sio2a_txd ld (0x98),hl ; SIO A TX empty + ld hl,#sio2a_status ld (0x9A),hl ; SIO A External status + ld hl,#sio2a_rx_ring ld (0x9C),hl ; SIO A Received + ld hl,#sio2a_special ld (0x9E),hl ; SIO A Special ld hl, #80 ld (_ramsize), hl @@ -221,15 +230,15 @@ sio_setup: .byte 0x00 .byte 0x18 ; Reset .byte 0x04 - .byte 0xC4 + .byte 0xC4 ; x64 async 1 stop no parity .byte 0x01 - .byte 0x18 + .byte 0x1F ; status affects vector, ti, ei, int all .byte 0x03 - .byte 0xE1 + .byte 0xE1 ; 8bit, autoen, rx en .byte 0x05 - .byte RTS_LOW - .byte 0x02 ; IRQ vector (B only) - .byte 0x90 + .byte RTS_LOW ; dtr, 8bit, tx en, rts + .byte 0x02 + .byte 0x90 ; IRQ vector (B only) .area _CODE @@ -269,3 +278,472 @@ ocloop_sio: out (SIOA_D),a ret +; +; SIO2 IM2 console driver +; +; The buffers are 256 bytes per channel and page aligned. The lower +; half is used for receive the upper for transmit. Both are rings. + +; +; Transmit data from the queue. We need a stop system for this but +; actually the logical one might be to just turn that IRQ off, so long +; as we remember to kickstart it again properly. Check if it's enough +; to just unmask the IRQ bit ? +; +; All of this lives in common space so we don't bank switch so much. + + .area _CONSOLE + +sio2a_rx: + jp init ; for boot only + .ds 128-3 ; we wrote a JP init in the first 3 +sio2a_tx: ; that will be recycled + .ds 128 +sio2b_rx: + .ds 128 +sio2b_tx: + .ds 128 + + .area _COMMONMEM +_sio2a_txl: + .db 0 +sio2a_error: + .db 0 +sio2a_rxover: + .db 0 +sio2a_stat: + .db 0 +sio2a_txp: + .dw sio2a_tx +sio2a_txe: + .dw sio2a_tx +_sio2a_rxl: + .db 0 +sio2a_rxp: + .dw sio2a_rx +sio2a_rxe: + .dw sio2a_rx + +_sio2b_txl: + .db 0 +sio2b_error: + .db 0 +sio2b_rxover: + .db 0 +sio2b_stat: + .db 0 +sio2b_txp: + .dw sio2a_tx +sio2b_txe: + .dw sio2a_tx +_sio2b_rxl: + .db 0 +sio2b_rxp: + .dw sio2a_rx +sio2b_rxe: + .dw sio2a_rx + +; +; Interrupt vector handler for port A transmit empty +; +sio2a_txd: + push af + ld a,(_sio2a_txl) + or a + jr z, tx_a_none + push hl + dec a + ld (_sio2a_txl),a + ld hl,(sio2a_txp) + ld a,(hl) + out (SIOA_D),a + inc hl + set 7,l + ld (sio2a_txp),hl + pop hl +tx_a_none: + ld a,#0x28 + out (SIOA_C),a ; silence tx interrupt + pop af + ei + reti +; +; Interrupt vector handler for port A receive ready +; +sio2a_rx_ring: + push af + push hl +sio2a_rx_next: + in a,(SIOA_D) ; read ASAP + ld l,a + ld a,(_sio2a_rxl) + inc a + jp m, a_rx_over + ld (_sio2a_rxl),a + ; should we check bit 5/6 and if appropriate flow control on bit 5/6 + ; high ? +;sio2a_flow_patch: ; patch with fastest 5 byte nop +; cp #0x60 ; flow control threshold +; call z, _sio2a_flow_control_on + ld a,l + ld hl,(sio2a_rxp) + ld (hl),a + inc l + res 7,l + ld (sio2a_rxp),hl + ; + ; The chip has a small FIFO and bytes can also arrive as we + ; read. To maximise performance try and empty it each time. + ; + ; This is bounded as worst case at high data rate and low + ; CPU speed we will overrun and bail out. + ; + in a,(SIOA_C) ; RR 0 + rra + jr c, sio2a_rx_next + pop hl + pop af + ei + reti +a_rx_over: + ld a,(sio2a_error) + or #0x20 ; Fake an RX overflow bit + ld (sio2a_rxover),a + pop af + ei + reti +; +; Interrupt vector for a port A status change +; +; FIXME: +; log the or of changes seem (dcd down, up) and last state +; of both CTS and DCD. The CTS last state is sufficient for flow +; and we can use thw two edges of DCD to work out if we had a hangup +; and if we are now open. +; +sio2a_status: + ; CTS or DCD change + push af + push hl + ; RR0 + in a,(SIOA_C) + ; Clear the latched values + ld a,#0x10 + out (SIOA_C),a + pop hl + pop af + ei + reti + +; +; Interrupt vector for a port A error +; +sio2a_special: + ; Parity, RX Overrun, Framing + ; Probably want to record them, but we at least must clean up + push af + ld a,#1 + out (SIOA_C),a ; RR1 please + in a,(SIOA_C) ; clear events + ld (sio2a_error),a ; Save error bits + ; Clear the latched values + ld a,#0x30 + out (SIOA_C),a + pop af + ei + reti + +; +; Interrupt vector handler for port B transmit empty +; +sio2b_txd: + push af + ld a,(_sio2b_txl) + or a + jr z, tx_b_none + push hl + dec a + ld (_sio2b_txl),a + ld hl,(sio2b_txp) + ld a,(hl) + out (SIOB_D),a + inc hl + set 7,l + ld (sio2b_txp),hl + pop hl +tx_b_none: + ld a,#0x28 + out (SIOB_C),a ; silence tx interrupt + pop af + ei + reti +; +; Interrupt vector handler for port B receive ready +; +sio2b_rx_ring: + push af + push hl +sio2b_rx_next: + in a,(SIOB_D) ; read ASAP + ld l,a + ld a,(_sio2b_rxl) + inc a + jp m, b_rx_over + ld (_sio2b_rxl),a + ; should we check bit 5/6 and if appropriate flow control on bit 5/6 + ; high ? +;sio2b_flow_patch: ; patch with fastest 5 byte nop +; cp #0x60 ; flow control threshold +; call z, _sio2b_flow_control_on + ld a,l + ld hl,(sio2b_rxp) + ld (hl),a + inc l + res 7,l + ld (sio2b_rxp),hl + ; + ; The chip has a small FIFO and bytes can also arrive as we + ; read. To maximise performance try and empty it each time. + ; + ; This is bounded as worst case at high data rate and low + ; CPU speed we will overrun and bail out. + ; + in a,(SIOB_C) ; RR 0 + rra + jr c, sio2b_rx_next + pop hl + pop af + ei + reti +b_rx_over: + ld a,(sio2b_error) + or #0x20 ; Fake an RX overflow bit + ld (sio2b_rxover),a + pop af + ei + reti +; +; Interrupt vector for a port B status change +; +; FIXME: +; log the or of changes seem (dcd down, up) and last state +; of both CTS and DCD. The CTS last state is sufficient for flow +; and we can use thw two edges of DCD to work out if we had a hangup +; and if we are now open. +; +sio2b_status: + ; CTS or DCD change + push af + push hl + ; RR0 + in a,(SIOB_C) + ; Clear the latched values + ld a,#0x10 + out (SIOB_C),a + pop hl + pop af + ei + reti + +; +; Interrupt vector for a port B error +; +sio2b_special: + ; Parity, RX Overrun, Framing + ; Probably want to record them, but we at least must clean up + push af + ld a,#1 + out (SIOB_C),a ; RR1 please + in a,(SIOB_C) ; clear events + ld (sio2b_error),a ; Save error bits + ; Clear the latched values + ld a,#0x30 + out (SIOB_C),a + pop af + ei + reti + +; +; C interface methods +; + .globl _sio2a_txqueue + .globl _sio2a_flow_control_on + .globl _sio2a_rx_get + .globl _sio2a_error_get + + .globl _sio2a_rxl + .globl _sio2a_txl + .globl _sio2a_wr5 + + .globl _sio2b_txqueue + .globl _sio2b_flow_control_on + .globl _sio2b_rx_get + .globl _sio2b_error_get + + .globl _sio2b_rxl + .globl _sio2b_txl + .globl _sio2b_wr5 + +_sio2a_wr5: + .db 0xEA ; DTR, 8bit, tx enabled +_sio2b_wr5: + .db 0xEA ; DTR, 8bit, tx enabled + +; +; Queue a byte to be sent (DI required) +; +; l = byte +; +; Need a way to halt processing somewhere here or a_tx ? +; (or can we use hardware ?) +; 128 byte ring buffer aligned to upper half (rx is in lower) +; +_sio2a_txqueue: + ld a,(_sio2a_txl) + or a + jr z, sio2a_direct_maybe ; if can tx now then do + inc a + jp m, txa_overflow +sio2a_queue: + ld (_sio2a_txl),a + ld a,l + ld hl,(sio2a_txe) + ld (hl),a + inc l + set 7,l + ld (sio2a_txe),hl + ld l,#0 + ret +txa_overflow: + ; some kind of flag for error + ld l,#1 + ret +sio2a_direct_maybe: + ; check RR + in a,(SIOA_C) + and #0x04 ; RX space ? + ; if space + ld a,#1 + jr nz, sio2a_queue + ; bypass the queue and kickstart the interrupt machine + ld a,l + out (SIOA_D),a + ld l,#0 + ret + ; Call with DI + +_sio2a_flow_control_on: + ld a,#5 + out(SIOA_C),a ; WR 5 + ld a,(_sio2a_wr5) + or #0x02 + out (SIOA_C),a ; Turn on RTS + ret + + ; DI required + ; Returns char in L + ; + ; Caller responsible for making post buffer fetch decisions about + ; RTS +_sio2a_rx_get: + ld a,(_sio2a_rxl) + or a + ret z + dec a + ld (_sio2a_rxl),a + ld hl,(sio2a_rxe) + ld a,(hl) + inc l + res 7,l + ld (sio2a_rxe),hl + scf + ld l,a + ret + + ; DI required +_sio2a_error_get: + ld hl,#sio2a_error + ld a,(hl) + ld (hl),#0 + ld l,a + ret + +; +; Queue a byte to be sent (DI required) +; +; l = byte +; +; Need a way to halt processing somewhere here or a_tx ? +; (or can we use hardware ?) +; 128 byte ring buffer aligned to upper half (rx is in lower) +; +_sio2b_txqueue: + ld a,(_sio2b_txl) + or a + jr z, sio2b_direct_maybe ; if can tx now then do + inc a + jp m, txb_overflow +sio2b_queue: + ld (_sio2b_txl),a + ld a,l + ld hl,(sio2b_txe) + ld (hl),a + inc l + set 7,l + ld (sio2b_txe),hl + ld l,#0 + ret +txb_overflow: + ; some kind of flag for error + ld l,#1 + ret +sio2b_direct_maybe: + ; check RR + in a,(SIOB_C) + and #0x04 ; RX space ? + ; if space + ld a,#1 + jr z, sio2b_queue + ; bypass the queue and kickstart the interrupt machine + ld a,l + out (SIOB_D),a + ld l,#0 + ret + ; Call with DI + +_sio2b_flow_control_on: + ld a,#5 + out(SIOB_C),a ; WR 5 + ld a,(_sio2b_wr5) + or #0x02 + out (SIOB_C),a ; Turn on RTS + ret + + ; DI required + ; Returns char in L + ; + ; Caller responsible for making post buffer fetch decisions about + ; RTS +_sio2b_rx_get: + ld a,(_sio2b_rxl) + or a + ret z + dec a + ld (_sio2b_rxl),a + ld hl,(sio2b_rxe) + ld a,(hl) + inc l + res 7,l + ld (sio2b_rxe),hl + scf + ld l,a + ret + + ; DI required +_sio2b_error_get: + ld hl,#sio2b_error + ld a,(hl) + ld (hl),#0 + ld l,a + ret + diff --git a/Kernel/platform-linc80/main.c b/Kernel/platform-linc80/main.c index 2f88a938..98ff3e4a 100644 --- a/Kernel/platform-linc80/main.c +++ b/Kernel/platform-linc80/main.c @@ -39,8 +39,7 @@ uint8_t platform_param(unsigned char *p) void platform_interrupt(void) { - tty_pollirq_sio(); /* For now as we don't have nice - tty set up yet */ + tty_drain_sio(); timer_interrupt(); }