* - 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 <kernel.h>
#include <devtty.h>
#include <propio2.h>
+/* The 16x50 UART I/O ports */
__sfr __at 0x68 uart_tx;
__sfr __at 0x68 uart_rx;
__sfr __at 0x68 uart_ls;
__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')
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;
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;
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,
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;
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;
}
}
+/*
+ * 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)
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]);
}
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)
{
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;
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) {
while(tmp--) {
timer_interrupt();
}
+ /* Poll the PropIOv2 */
platform_interrupt();
}
re_enter--;
}
}
+/*
+ * 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)
{
}