From 397572a75d3f821016f7926281cf1bb658abbb64 Mon Sep 17 00:00:00 2001 From: Alan Cox Date: Tue, 28 Aug 2018 00:22:06 +0100 Subject: [PATCH] sbcv2: add lots of comments as this is meant to be a porting reference --- Kernel/platform-sbcv2/devices.c | 10 +++ Kernel/platform-sbcv2/devtty.c | 112 +++++++++++++++++++++++++++++--- Kernel/platform-sbcv2/main.c | 55 ++++++++++++++-- 3 files changed, 164 insertions(+), 13 deletions(-) diff --git a/Kernel/platform-sbcv2/devices.c b/Kernel/platform-sbcv2/devices.c index b91e2db5..1aa1560b 100644 --- a/Kernel/platform-sbcv2/devices.c +++ b/Kernel/platform-sbcv2/devices.c @@ -9,6 +9,16 @@ #include #include +/* + * This table is the glue that holds all the kernel device driver + * logic together. Each device driver provides methods for + * open, close, read, write and ioctl, although it can opt to use + * defaults as well. + * + * The validdev function is the same for all platforms but has to live + * in the same file as the table. Just paste it into each. + */ + struct devsw dev_tab[] = /* The device driver switch table */ { /* 0: /dev/hd Hard disc block devices */ diff --git a/Kernel/platform-sbcv2/devtty.c b/Kernel/platform-sbcv2/devtty.c index d2d53c1b..3674e012 100644 --- a/Kernel/platform-sbcv2/devtty.c +++ b/Kernel/platform-sbcv2/devtty.c @@ -5,6 +5,12 @@ * - Hardware flow control * - Support for abuse of 16x50 as interrupt controller * - Support for timer hack + * + * This file implements the serial ports for the platform. Fuzix implements + * a reasonable subset of the System 5 termios. Certain things that are + * rarely relevant like XCASE, delay fills and parity are left to the + * driver if desired. + * */ #include @@ -14,6 +20,7 @@ #include #include +/* The 16x50 UART I/O ports */ __sfr __at 0x68 uart_tx; __sfr __at 0x68 uart_rx; __sfr __at 0x68 uart_ls; @@ -26,21 +33,35 @@ __sfr __at 0x6D uart_lsr; __sfr __at 0x6E uart_msr; __sfr __at 0x6F uart_scr; +/* + * One buffer for each tty + */ static char tbuf1[TTYSIZ]; static char tbuf2[TTYSIZ]; -/* Updated early in boot to 0,2,1 if PropIO present */ -uint8_t ttymap[NUM_DEV_TTY + 1] = { - 0, 1, 2 -}; - +/* + * One entry per tty. The 0th entry is never used as tty minor 0 is + * special (/dev/tty) and it's cheaper to waste a few bytes that keep + * doing subtractions. + */ struct s_queue ttyinq[NUM_DEV_TTY + 1] = { /* ttyinq[0] is never used */ {NULL, NULL, NULL, 0, 0, 0}, {tbuf1, tbuf1, tbuf1, TTYSIZ, 0, TTYSIZ / 2}, {tbuf2, tbuf2, tbuf2, TTYSIZ, 0, TTYSIZ / 2}, }; -/* Write to system console */ + +/* Updated early in boot to 0,2,1 if PropIO present. This table works both + ways purely because of the possible mappings. If that changes we'll need + a forward and backward table. Most platforms have a fixed idea of the console + so don't need this remapping layer */ +uint8_t ttymap[NUM_DEV_TTY + 1] = { + 0, 1, 2 +}; + +/* Write to system console. This is the backend to all the kernel messages, + kprintf(), panic() etc. */ + void kputchar(char c) { if (c == '\n') @@ -48,6 +69,26 @@ void kputchar(char c) tty_putc(1, c); } +/* + * See if the given tty is able to transmit data without blocking. This + * may be done by checking the hardware, or if there is a software + * transmit queue by checking the queue is full. + * + * There are three possible returns + * TTY_READY_NOW means fire away + * TTY_READY_SOON means we will spin trying until pre-empted. As the + * 8bit processors are slow relative to baud rates it's often + * more efficient to do this + * TTY_READY_LATER means we will give up the CPU. This is best if the + * baud rate is low, the link is blocked by flow control signals + * or the CPU is fast. + * + * If TTY_READY_LATER is returned then the kernel will also call + * tty_sleeping(minor) before sleeping on the tty so that the driver + * can turn on or off tx complete interrupts. + * + * A video display that never blocks will just return TTY_READY_NOW + */ uint8_t tty_writeready(uint8_t minor) { minor; @@ -57,6 +98,15 @@ uint8_t tty_writeready(uint8_t minor) return prop_tty_writeready(); } +/* + * Write a character to a tty. This is the normal user space path for + * each outbound byte. It gets called in the normal tty flow, but may + * also be called from an interrupt to echo characters even if the + * tty is busy. This one reason to implement a small transmit queue. + * + * If the character echo doesn't fit just drop it. It should pretty much + * never occur and there is nothing else to do. + */ void tty_putc(uint8_t minor, unsigned char c) { minor; @@ -66,6 +116,10 @@ void tty_putc(uint8_t minor, unsigned char c) prop_tty_write(c); } +/* + * 16x50 conversion betwen a Bxxxx speed rate (see tty.h) and the values + * to stuff into the chip. + */ static uint16_t clocks[] = { 12, /* Not a real rate */ 2304, @@ -85,6 +139,15 @@ static uint16_t clocks[] = { 1 }; +/* + * This function is called whenever the terminal interface is opened + * or the settings changed. It is responsible for making the requested + * changes to the port if possible. Strictly speaking it should write + * back anything that cannot be implemented to the state it selected. + * + * That needs tidying up in many platforms and we also need a proper way + * to say 'this port is fixed config' before making it so. + */ void tty_setup(uint8_t minor) { uint8_t d; @@ -92,7 +155,7 @@ void tty_setup(uint8_t minor) struct termios *t = &ttydata[minor].termios; if (ttymap[minor] == 1) { /* 16x50. Can actually be configured */ - d = 0x80; /* DLAB */ + d = 0x80; /* DLAB (so we can write the speed) */ d |= (t->c_cflag & CSIZE) >> 4; if(t->c_cflag & CSTOPB) d |= 0x04; @@ -112,11 +175,20 @@ void tty_setup(uint8_t minor) } } +/* + * This function is called when the kernel is about to sleep on a tty. + * We don't care about this. + */ void tty_sleeping(uint8_t minor) { minor; } +/* + * Return 1 if the carrier on the terminal is raised. If the port has + * no carrier signal always return 1. It is used to block a port on open + * until carrier. + */ int tty_carrier(uint8_t minor) { if (ttymap[minor] == 1) @@ -124,18 +196,40 @@ int tty_carrier(uint8_t minor) return 1; } +/* + * When the input queue is part drained this method is called from the + * kernel so that hardware flow control signals can be updated. + */ void tty_data_consumed(uint8_t minor) { } +/* + * Our platform specific code so we have a function to call to poll the + * serial ports for activity. + */ void tty_poll(void) { + uint8_t msr; + uint8_t minor = ttymap[1]; /* UART minor number */ + /* Should be IRQ driven but we might not be so poll anyway if pending. IRQs are off here so this is safe */ if (uart_lsr & 0x01) - tty_inproc(ttymap[1], uart_rx); + tty_inproc(minor, uart_rx); + msr = uart_msr; /* If we have a 10MHz clock wired to DSR then do timer interrupts */ - if (timermsr && (uart_msr & 0x40)) + if (timermsr && (msr & 0x04)) timer_interrupt(); + /* DCD changed - tell the kernel so it can hangup or open ports */ + if (msr & 0x08) { + if (msr & 0x80) + tty_carrier_raise(minor); + else + tty_carrier_drop(minor); + } + /* TODO: CTS/RTS */ + + /* Now as the PropIO driver to poll its input */ prop_tty_poll(ttymap[2]); } diff --git a/Kernel/platform-sbcv2/main.c b/Kernel/platform-sbcv2/main.c index 4de9430c..8945a0b0 100644 --- a/Kernel/platform-sbcv2/main.c +++ b/Kernel/platform-sbcv2/main.c @@ -10,28 +10,49 @@ uint16_t ramtop = PROGTOP; uint16_t swap_dev = 0xFFFF; uint8_t timermsr = 0; -/* On idle we spin checking for the terminals. Gives us more responsiveness - for the polled ports */ +/* + * This routine is called continually when the machine has nothing else + * it needs to execute. On a machine with entirely interrupt driven + * hardware this could just halt for interrupt. + * + * The SBCv2 has no interrupts so we must call sync_clock(), and as the + * PropIO tty is not interrupt driven we also poll the ttys. This gives + * us a nice interactive feel when the machine is idle, even if a polled + * tty can otherwise suck. + */ void platform_idle(void) { + /* Disable interrupts so we don't accidentally process a polled tty + and interrupt call at once and make a mess */ irqflags_t irq = di(); sync_clock(); tty_poll(); + /* Restore prior state. */ irqrestore(irq); } +/* + * This routine is called from the interrupt handler code to process + * interrupts. All of the nasty stuff (register saving, bank switching, + * reti instructions) is dealt with for you. + * + * Most platforms would read something to identify the interrupt source + * but in our case the only possible source is the serial uart. + */ void platform_interrupt(void) { tty_poll(); } +/* This points to the last buffer in the disk buffers. There must be at least + four buffers to avoid deadlocks. */ struct blkbuf *bufpool_end = bufpool + NBUFS; /* * We pack discard into the memory image is if it were just normal * code but place it at the end after the buffers. When we finish up * booting we turn everything from the buffer pool to common into - * buffers. + * buffers. This blows away the _DISCARD segment. */ void platform_discard(void) { @@ -62,7 +83,12 @@ static uint8_t oldticks; static uint8_t re_enter; -void sync_clock_read(void) +/* + * Hardware specific logic to get the seconds. We really ought to enhance + * this to check minutes as well just in case something gets stuck for + * ages. + */ +static void sync_clock_read(void) { uint8_t s; oldticks = newticks; @@ -71,6 +97,21 @@ void sync_clock_read(void) newticks = s; } +/* + * The OS core will invoke this routine when idle (via platform_idle) but + * also after a system call and in certain other spots to ensure the clock + * is roughly valid. It may be called from interrupts, without interrupts + * or even recursively so it must protect itself using the framework + * below. + * + * Having worked out how much time has passed in 1/10ths of a second it + * performs that may timer_interrupt events in order to advance the clock. + * The core kernel logic ensures that we won't do anything silly from a + * jump forward of many seconds. + * + * We also choose to poll the ttys here so the user has some chance of + * getting control back on a messed up process. + */ void sync_clock(void) { if (!timermsr) { @@ -86,6 +127,7 @@ void sync_clock(void) while(tmp--) { timer_interrupt(); } + /* Poll the PropIOv2 */ platform_interrupt(); } re_enter--; @@ -94,6 +136,11 @@ void sync_clock(void) } } +/* + * This method is called if the kernel has changed the system clock. We + * don't work out how much work we need to do by using it as a reference + * so we don't care. + */ void update_sync_clock(void) { } -- 2.34.1