aboutsummaryrefslogtreecommitdiff
path: root/mubios/drivers/serial.c
diff options
context:
space:
mode:
Diffstat (limited to 'mubios/drivers/serial.c')
-rw-r--r--mubios/drivers/serial.c113
1 files changed, 113 insertions, 0 deletions
diff --git a/mubios/drivers/serial.c b/mubios/drivers/serial.c
index 3da2093..c8b9adf 100644
--- a/mubios/drivers/serial.c
+++ b/mubios/drivers/serial.c
@@ -30,6 +30,7 @@
#define COM_LCR_OFFSET 0x03u
#define COM_MCR_OFFSET 0x04u
#define COM_LSR_OFFSET 0x05u
+#define COM_MSR_OFFSET 0x06u
#define COM_LCR_DLAB 0x80u
#define COM_LCR_8N1 0x03u
@@ -44,6 +45,12 @@
#define COM_MCR_OUT2 0x08u
#define COM_LSR_THRE 0x20u
+#define COM_LSR_IDLE 0x40u
+#define COM_LSR_DATA_READY 0x01u
+#define BIOS_SERIAL_TIMEOUT 0x80u
+
+#define PIT_COUNTER0 0x0040u
+#define PIT_COMMAND 0x0043u
static u16
com1_port (u16 offset)
@@ -51,6 +58,112 @@ com1_port (u16 offset)
return (u16)(COM1_BASE + offset);
}
+/* Latching channel 0 works while the protected-mode service has IRQs off. */
+u16
+serial_timer_counter (void)
+{
+ u16 counter;
+
+ io_out8 (PIT_COMMAND, 0x00u);
+ counter = io_in8 (PIT_COUNTER0);
+ counter |= (u16)((u16)io_in8 (PIT_COUNTER0) << 8);
+ return counter;
+}
+
+static int
+serial_wait (u16 base, u8 mask, u8 timeout, u8 *status)
+{
+ u16 previous;
+ u16 current;
+ u32 elapsed;
+ u32 duration;
+
+ previous = serial_timer_counter ();
+ elapsed = 0;
+ /* One BDA timeout unit is one 65536-clock BIOS timer tick. */
+ duration = (u32)timeout << 16;
+
+ for (;;)
+ {
+ *status = io_in8 ((u16)(base + COM_LSR_OFFSET));
+ if ((*status & mask) == mask)
+ return 1;
+
+ if (elapsed >= duration)
+ {
+ *status |= BIOS_SERIAL_TIMEOUT;
+ return 0;
+ }
+
+ current = serial_timer_counter ();
+ elapsed += (u16)(previous - current);
+ previous = current;
+ }
+}
+
+u16
+serial_status (u16 base)
+{
+ u8 line;
+ u8 modem;
+
+ line = io_in8 ((u16)(base + COM_LSR_OFFSET));
+ modem = io_in8 ((u16)(base + COM_MSR_OFFSET));
+ return (u16)(((u16)line << 8) | modem);
+}
+
+u16
+serial_configure (u16 base, u8 parameters)
+{
+ static const u16 divisors[]
+ = { 1047u, 768u, 384u, 192u, 96u, 48u, 24u, 12u };
+ u16 divisor;
+
+ /* INT 14h AL: bits 7:5 select baud; bits 4:0 are the UART format. */
+ divisor = divisors[parameters >> 5];
+ io_out8 ((u16)(base + COM_LCR_OFFSET), COM_LCR_DLAB);
+ io_out8 ((u16)(base + COM_DATA_OFFSET), (u8)divisor);
+ io_out8 ((u16)(base + COM_IER_OFFSET), (u8)(divisor >> 8));
+ io_out8 ((u16)(base + COM_LCR_OFFSET), parameters & 0x1Fu);
+ /* Do not reset the FIFO here: pending terminal input must survive. */
+ return serial_status (base);
+}
+
+u8
+serial_transmit (u16 base, u8 value, u8 timeout)
+{
+ u8 status;
+
+ if (serial_wait (base, COM_LSR_THRE | COM_LSR_IDLE, timeout, &status))
+ io_out8 ((u16)(base + COM_DATA_OFFSET), value);
+
+ return status;
+}
+
+u8
+serial_receive (u16 base, u8 *value, u8 timeout)
+{
+ u8 status;
+
+ if (serial_wait (base, COM_LSR_DATA_READY, timeout, &status))
+ *value = io_in8 ((u16)(base + COM_DATA_OFFSET));
+
+ return status;
+}
+
+int
+serial_try_receive (u16 base, u8 *value)
+{
+ u8 status;
+
+ status = io_in8 ((u16)(base + COM_LSR_OFFSET));
+ if ((status & COM_LSR_DATA_READY) == 0u)
+ return 0;
+
+ *value = io_in8 ((u16)(base + COM_DATA_OFFSET));
+ return 1;
+}
+
void
serial_init (void)
{