summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Braun <rbraun@sceen.net>2017-05-26 22:39:31 +0200
committerRichard Braun <rbraun@sceen.net>2017-05-26 22:40:01 +0200
commitcab8846284996df401c5bdaa0088b224e076b510 (patch)
tree884c1dc40cbcf47e763da9c1c470aec73165c0a3
parent10f2d5f59a37532fb48cf9d6896efa238c9c6eb5 (diff)
x86: use the new intr module
In particular, the pic and ioapic modules register their respective devices as interrupt controllers. Selection between the legacy XT-PIC and the modern APIC system is made on the availability of ACPI, disregarding the multiprocessor specification entirely. The uart driver is also updated to register devices interrupt handlers.
-rw-r--r--arch/x86/machine/acpimp.c1
-rw-r--r--arch/x86/machine/boot.c7
-rw-r--r--arch/x86/machine/cpu.c9
-rw-r--r--arch/x86/machine/ioapic.c182
-rw-r--r--arch/x86/machine/ioapic.h13
-rw-r--r--arch/x86/machine/lapic.c23
-rw-r--r--arch/x86/machine/lapic.h14
-rw-r--r--arch/x86/machine/param.h5
-rw-r--r--arch/x86/machine/pic.c146
-rw-r--r--arch/x86/machine/uart.c39
-rw-r--r--arch/x86/machine/uart.h10
11 files changed, 293 insertions, 156 deletions
diff --git a/arch/x86/machine/acpimp.c b/arch/x86/machine/acpimp.c
index 4e98683..bba2b8c 100644
--- a/arch/x86/machine/acpimp.c
+++ b/arch/x86/machine/acpimp.c
@@ -495,7 +495,6 @@ acpimp_load_madt(void)
assert(table != NULL);
madt = structof(table, struct acpimp_madt, header);
lapic_setup(madt->lapic_addr);
- ioapic_setup();
is_bsp = 1;
/*
diff --git a/arch/x86/machine/boot.c b/arch/x86/machine/boot.c
index 1830600..0df9674 100644
--- a/arch/x86/machine/boot.c
+++ b/arch/x86/machine/boot.c
@@ -51,6 +51,7 @@
#include <kern/arg.h>
#include <kern/console.h>
#include <kern/init.h>
+#include <kern/intr.h>
#include <kern/kmem.h>
#include <kern/kernel.h>
#include <kern/macros.h>
@@ -468,7 +469,7 @@ boot_main(void)
thread_bootstrap();
console_setup();
cga_setup();
- uart_setup();
+ uart_bootstrap();
printf_setup();
boot_show_version();
arg_info();
@@ -481,8 +482,10 @@ boot_main(void)
vm_setup();
boot_save_data();
biosmem_free_usable();
- pic_setup();
+ intr_setup();
cpu_mp_probe();
+ pic_setup();
+ uart_setup();
kernel_main();
/* Never reached */
diff --git a/arch/x86/machine/cpu.c b/arch/x86/machine/cpu.c
index d62b68f..fbc1f95 100644
--- a/arch/x86/machine/cpu.c
+++ b/arch/x86/machine/cpu.c
@@ -650,9 +650,14 @@ cpu_mp_probe(void)
error = acpimp_setup();
- /* TODO Support UP with legacy PIC */
if (error) {
- panic("cpu: ACPI required to initialize local APIC");
+ /*
+ * For the sake of simplicity, it has been decided to ignore legacy
+ * specifications such as the multiprocessor specification, and use
+ * ACPI only. If ACPI is unavailable, consider the APIC system to
+ * be missing and fall back to using the legaxy XT-PIC.
+ */
+ lapic_setup_unused();
}
printf("cpu: %u processor(s) configured\n", cpu_count());
diff --git a/arch/x86/machine/ioapic.c b/arch/x86/machine/ioapic.c
index 48faf8d..4517c3f 100644
--- a/arch/x86/machine/ioapic.c
+++ b/arch/x86/machine/ioapic.c
@@ -22,6 +22,7 @@
#include <kern/assert.h>
#include <kern/error.h>
#include <kern/init.h>
+#include <kern/intr.h>
#include <kern/kmem.h>
#include <kern/panic.h>
#include <kern/spinlock.h>
@@ -55,6 +56,7 @@ struct ioapic_map {
};
struct ioapic {
+ struct spinlock lock;
unsigned int id;
unsigned int version;
volatile struct ioapic_map *map;
@@ -62,23 +64,6 @@ struct ioapic {
unsigned int last_intr;
};
-static struct ioapic *ioapic_devs;
-static unsigned int ioapic_nr_devs;
-
-/*
- * Global lock.
- *
- * Interrupts must be disabled when holding this lock.
- */
-static struct spinlock ioapic_lock;
-
-static struct ioapic *
-ioapic_get(unsigned int id)
-{
- assert(id < ioapic_nr_devs);
- return &ioapic_devs[id];
-}
-
static uint32_t
ioapic_read(struct ioapic *ioapic, uint8_t reg)
{
@@ -107,61 +92,26 @@ ioapic_write_entry_high(struct ioapic *ioapic, unsigned int id, uint32_t value)
ioapic_write(ioapic, IOAPIC_REG_IOREDTBL + (id * 2) + 1, value);
}
-static bool
-ioapic_has_intr(const struct ioapic *ioapic, unsigned int intr)
-{
- return ((intr >= ioapic->first_intr) && (intr <= ioapic->last_intr));
-}
-
-static unsigned int
-ioapic_compute_id(const struct ioapic *ioapic, unsigned int intr)
-{
- assert(ioapic_has_intr(ioapic, intr));
- return intr - ioapic->first_intr;
-}
-
-static void
-ioapic_enable_intr(struct ioapic *ioapic, unsigned int intr,
- unsigned int cpu, unsigned int vector)
-{
- unsigned int id;
-
- id = ioapic_compute_id(ioapic, intr);
- ioapic_write_entry_high(ioapic, id, cpu_apic_id(cpu) << 24);
- ioapic_write_entry_low(ioapic, id, vector);
-}
-
-static void
-ioapic_disable_intr(struct ioapic *ioapic, unsigned int intr)
-{
- unsigned int id;
-
- id = ioapic_compute_id(ioapic, intr);
- ioapic_write_entry_low(ioapic, id, IOAPIC_ENTLOW_INTRMASK);
-}
-
static void
ioapic_intr(struct trap_frame *frame)
{
- lapic_eoi();
- printf("ioapic: cpu:%u vector:%lu\n", cpu_id(), frame->vector);
-}
-
-void __init
-ioapic_setup(void)
-{
- ioapic_devs = NULL;
- ioapic_nr_devs = 0;
- spinlock_init(&ioapic_lock);
+ intr_handle(frame->vector - TRAP_INTR_FIRST);
}
-static void __init
-ioapic_init(struct ioapic *ioapic, unsigned int id,
- uintptr_t addr, unsigned int intr_base)
+static struct ioapic * __init
+ioapic_create(unsigned int id, uintptr_t addr, unsigned int intr_base)
{
+ struct ioapic *ioapic;
unsigned int i, nr_intrs;
uint32_t value;
+ ioapic = kmem_alloc(sizeof(*ioapic));
+
+ if (ioapic == NULL) {
+ panic("ioapic: unable to allocate memory for controller");
+ }
+
+ spinlock_init(&ioapic->lock);
ioapic->id = id;
ioapic->first_intr = intr_base;
@@ -182,84 +132,82 @@ ioapic_init(struct ioapic *ioapic, unsigned int id,
panic("ioapic: invalid interrupt range");
}
- printf("ioapic%u: version:%#x intrs:%u-%u\n", ioapic->id,
- ioapic->version, ioapic->first_intr, ioapic->last_intr);
-
for (i = ioapic->first_intr; i < ioapic->last_intr; i++) {
trap_register(TRAP_INTR_FIRST + i, ioapic_intr);
}
-}
-
-void __init
-ioapic_register(unsigned int id, uintptr_t addr, unsigned int intr_base)
-{
- struct ioapic *tmp;
-
- spinlock_lock(&ioapic_lock);
-
- tmp = kmem_alloc((ioapic_nr_devs + 1) * sizeof(*ioapic_devs));
- if (tmp == NULL) {
- panic("ioapic: unable to allocate memory for device");
- }
+ printf("ioapic%u: version:%#x intrs:%u-%u\n", ioapic->id,
+ ioapic->version, ioapic->first_intr, ioapic->last_intr);
- memcpy(tmp, ioapic_devs, ioapic_nr_devs);
- kmem_free(ioapic_devs, ioapic_nr_devs * sizeof(*ioapic_devs));
- ioapic_devs = tmp;
- ioapic_nr_devs++;
+ return ioapic;
+}
- ioapic_init(ioapic_get(ioapic_nr_devs - 1), id, addr, intr_base);
+static bool
+ioapic_has_intr(const struct ioapic *ioapic, unsigned int intr)
+{
+ return ((intr >= ioapic->first_intr) && (intr <= ioapic->last_intr));
+}
- spinlock_unlock(&ioapic_lock);
+static unsigned int
+ioapic_compute_id(const struct ioapic *ioapic, unsigned int intr)
+{
+ assert(ioapic_has_intr(ioapic, intr));
+ return intr - ioapic->first_intr;
}
-static struct ioapic *
-ioapic_lookup(unsigned int intr)
+static void
+ioapic_enable(void *priv, unsigned int intr, unsigned int cpu)
{
struct ioapic *ioapic;
- unsigned int i;
-
- for (i = 0; i < ioapic_nr_devs; i++) {
- ioapic = &ioapic_devs[i];
+ unsigned long flags;
+ unsigned int id;
- if ((intr >= ioapic->first_intr) && (intr <= ioapic->last_intr)) {
- return ioapic;
- }
- }
+ ioapic = priv;
+ id = ioapic_compute_id(ioapic, intr);
- return NULL;
+ spinlock_lock_intr_save(&ioapic->lock, &flags);
+ ioapic_write_entry_high(ioapic, id, cpu_apic_id(cpu) << 24);
+ ioapic_write_entry_low(ioapic, id, TRAP_INTR_FIRST + intr);
+ spinlock_unlock_intr_restore(&ioapic->lock, flags);
}
-int
-ioapic_enable(unsigned int intr, unsigned int cpu, unsigned int vector)
+static void
+ioapic_disable(void *priv, unsigned int intr)
{
struct ioapic *ioapic;
unsigned long flags;
- int error;
-
- spinlock_lock_intr_save(&ioapic_lock, &flags);
+ unsigned int id;
- ioapic = ioapic_lookup(intr);
+ ioapic = priv;
+ id = ioapic_compute_id(ioapic, intr);
- if (ioapic == NULL) {
- error = ERROR_NODEV;
- goto out;
- }
+ spinlock_lock_intr_save(&ioapic->lock, &flags);
+ ioapic_write_entry_low(ioapic, id, IOAPIC_ENTLOW_INTRMASK);
+ spinlock_unlock_intr_restore(&ioapic->lock, flags);
+}
- ioapic_enable_intr(ioapic, intr, cpu, vector);
- error = 0;
+static void
+ioapic_eoi(void *priv, unsigned int intr)
+{
+ (void)priv;
+ (void)intr;
-out:
- spinlock_unlock_intr_restore(&ioapic_lock, flags);
- return error;
+ lapic_eoi();
}
-void
-ioapic_disable(unsigned int intr)
+static const struct intr_ops ioapic_ops = {
+ .enable = ioapic_enable,
+ .disable = ioapic_disable,
+ .eoi = ioapic_eoi,
+};
+
+void __init
+ioapic_register(unsigned int id, uintptr_t addr, unsigned int intr_base)
{
- unsigned long flags;
+ struct ioapic *ioapic;
- spinlock_lock_intr_save(&ioapic_lock, &flags);
- ioapic_disable_intr(ioapic_lookup(intr), intr);
- spinlock_unlock_intr_restore(&ioapic_lock, flags);
+ ioapic = ioapic_create(id, addr, intr_base);
+ intr_register_ctl(&ioapic_ops, ioapic,
+ ioapic->first_intr, ioapic->last_intr);
}
+
diff --git a/arch/x86/machine/ioapic.h b/arch/x86/machine/ioapic.h
index daebdb1..4d22085 100644
--- a/arch/x86/machine/ioapic.h
+++ b/arch/x86/machine/ioapic.h
@@ -21,21 +21,8 @@
#include <stdint.h>
/*
- * Initialize the ioapic module.
- */
-void ioapic_setup(void);
-
-/*
* Register an I/O APIC controller.
*/
void ioapic_register(unsigned int id, uintptr_t addr, unsigned int gsi_base);
-/*
- * Enable/disable an interrupt line.
- *
- * The given interrupt is routed to the given (cpu, vector) destination.
- */
-int ioapic_enable(unsigned int intr, unsigned int cpu, unsigned int vector);
-void ioapic_disable(unsigned int intr);
-
#endif /* _KERN_IOAPIC_H */
diff --git a/arch/x86/machine/lapic.c b/arch/x86/machine/lapic.c
index a978da1..444e661 100644
--- a/arch/x86/machine/lapic.c
+++ b/arch/x86/machine/lapic.c
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011-2014 Richard Braun.
+ * Copyright (c) 2011-2017 Richard Braun.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,10 +15,12 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
+#include <kern/assert.h>
#include <kern/init.h>
#include <kern/macros.h>
#include <kern/panic.h>
@@ -182,6 +184,9 @@ static volatile struct lapic_map *lapic_map __read_mostly;
*/
static uint32_t lapic_bus_freq __read_mostly;
+static bool lapic_initialized __initdata;
+static bool lapic_is_unused __initdata;
+
static uint32_t
lapic_read(const volatile struct lapic_register *r)
{
@@ -237,6 +242,20 @@ lapic_setup_registers(void)
lapic_write(&lapic_map->timer_icr, lapic_bus_freq / HZ);
}
+bool __init
+lapic_unused(void)
+{
+ assert(lapic_initialized);
+ return lapic_is_unused;
+}
+
+void __init
+lapic_setup_unused(void)
+{
+ lapic_initialized = true;
+ lapic_is_unused = true;
+}
+
void __init
lapic_setup(uint32_t map_addr)
{
@@ -256,6 +275,8 @@ lapic_setup(uint32_t map_addr)
lapic_setup_registers();
lapic_setup_timer();
+
+ lapic_initialized = true;
}
void __init
diff --git a/arch/x86/machine/lapic.h b/arch/x86/machine/lapic.h
index 802d125..ff9c893 100644
--- a/arch/x86/machine/lapic.h
+++ b/arch/x86/machine/lapic.h
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2011, 2012 Richard Braun.
+ * Copyright (c) 2011-2017 Richard Braun.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -18,6 +18,7 @@
#ifndef _X86_LAPIC_H
#define _X86_LAPIC_H
+#include <stdbool.h>
#include <stdint.h>
#include <machine/trap.h>
@@ -28,6 +29,17 @@
void lapic_eoi(void);
/*
+ * Report whether the local APIC is actually used or not.
+ */
+bool lapic_unused(void);
+
+/*
+ * Initialize the lapic module for the sole purpose of reporting that
+ * it's actually not used.
+ */
+void lapic_setup_unused(void);
+
+/*
* Set up the lapic module.
*/
void lapic_setup(uint32_t map_addr);
diff --git a/arch/x86/machine/param.h b/arch/x86/machine/param.h
index e5e0f0a..6ee11cc 100644
--- a/arch/x86/machine/param.h
+++ b/arch/x86/machine/param.h
@@ -83,6 +83,11 @@
#define STACK_SIZE PAGE_SIZE
/*
+ * Maximum number of available interrupts.
+ */
+#define INTR_TABLE_SIZE 256
+
+/*
* Virtual memory properties.
*/
diff --git a/arch/x86/machine/pic.c b/arch/x86/machine/pic.c
index 412135c..fed8c97 100644
--- a/arch/x86/machine/pic.c
+++ b/arch/x86/machine/pic.c
@@ -19,9 +19,11 @@
#include <kern/assert.h>
#include <kern/init.h>
+#include <kern/intr.h>
#include <kern/panic.h>
-#include <machine/io.h>
#include <machine/cpu.h>
+#include <machine/io.h>
+#include <machine/lapic.h>
#include <machine/pic.h>
#include <machine/trap.h>
@@ -48,29 +50,43 @@
#define PIC_SLAVE_INTR 2
#define PIC_SPURIOUS_INTR 7
#define PIC_NR_INTRS 8
+#define PIC_MAX_INTR ((PIC_NR_INTRS * 2) - 1)
-void __init
-pic_setup(void)
+static unsigned int pic_nr_slave_intrs;
+
+static uint8_t pic_master_mask;
+static uint8_t pic_slave_mask;
+
+static bool
+pic_is_slave_intr(unsigned int intr)
{
- /* ICW 1 - State that ICW 4 will be sent */
- io_write_byte(PIC_MASTER_CMD, PIC_ICW1_INIT | PIC_ICW1_IC4);
- io_write_byte(PIC_SLAVE_CMD, PIC_ICW1_INIT | PIC_ICW1_IC4);
+ assert(intr <= PIC_MAX_INTR);
+ return (intr >= PIC_NR_INTRS);
+}
- /* ICW 2 */
- io_write_byte(PIC_MASTER_IMR, TRAP_INTR_FIRST);
- io_write_byte(PIC_SLAVE_IMR, TRAP_INTR_FIRST + PIC_NR_INTRS);
+static void
+pic_inc_slave_intrs(void)
+{
+ if (pic_nr_slave_intrs == 0) {
+ pic_master_mask |= 1 << PIC_SLAVE_INTR;
+ io_write_byte(PIC_MASTER_IMR, pic_master_mask);
+ }
- /* ICW 3 - Set up cascading */
- io_write_byte(PIC_MASTER_IMR, 1 << PIC_SLAVE_INTR);
- io_write_byte(PIC_SLAVE_IMR, PIC_SLAVE_INTR);
+ pic_nr_slave_intrs++;
+ assert(pic_nr_slave_intrs != 0);
+}
- /* ICW 4 - Set 8086 mode */
- io_write_byte(PIC_MASTER_IMR, PIC_ICW4_8086);
- io_write_byte(PIC_SLAVE_IMR, PIC_ICW4_8086);
+static void
+pic_dec_slave_intrs(void)
+{
+ assert(pic_nr_slave_intrs != 0);
- /* OCW 1 - Mask all interrupts */
- io_write_byte(PIC_MASTER_IMR, 0xff);
- io_write_byte(PIC_SLAVE_IMR, 0xff);
+ pic_nr_slave_intrs--;
+
+ if (pic_nr_slave_intrs == 0) {
+ pic_master_mask &= ~(1 << PIC_SLAVE_INTR);
+ io_write_byte(PIC_MASTER_IMR, pic_master_mask);
+ }
}
static void
@@ -90,6 +106,100 @@ pic_read_isr(uint16_t port)
return io_read_byte(port);
}
+static void
+pic_ops_enable(void *priv, unsigned int intr, unsigned int cpu)
+{
+ (void)priv;
+ (void)cpu;
+
+ if (pic_is_slave_intr(intr)) {
+ pic_slave_mask &= ~(1 << (intr - PIC_NR_INTRS));
+ io_write_byte(PIC_SLAVE_IMR, pic_slave_mask);
+ pic_inc_slave_intrs();
+ } else {
+ pic_master_mask &= ~(1 << intr);
+ io_write_byte(PIC_MASTER_IMR, pic_master_mask);
+ }
+}
+
+static void
+pic_ops_disable(void *priv, unsigned int intr)
+{
+ (void)priv;
+
+ if (pic_is_slave_intr(intr)) {
+ pic_dec_slave_intrs();
+ pic_slave_mask |= 1 << (intr - PIC_NR_INTRS);
+ io_write_byte(PIC_SLAVE_IMR, pic_slave_mask);
+ } else {
+ pic_master_mask |= 1 << intr;
+ io_write_byte(PIC_MASTER_IMR, pic_master_mask);
+ }
+}
+
+static void
+pic_ops_eoi(void *priv, unsigned int intr)
+{
+ (void)priv;
+ pic_eoi(intr);
+}
+
+static const struct intr_ops pic_ops = {
+ .enable = pic_ops_enable,
+ .disable = pic_ops_disable,
+ .eoi = pic_ops_eoi,
+};
+
+static void
+pic_intr(struct trap_frame *frame)
+{
+ intr_handle(frame->vector - TRAP_INTR_FIRST);
+}
+
+static void __init
+pic_register(void)
+{
+ unsigned int intr;
+
+ intr_register_ctl(&pic_ops, NULL, 0, PIC_MAX_INTR);
+
+ for (intr = 0; intr <= PIC_MAX_INTR; intr++) {
+ trap_register(TRAP_INTR_FIRST + intr, pic_intr);
+ }
+}
+
+void __init
+pic_setup(void)
+{
+ pic_nr_slave_intrs = 0;
+ pic_master_mask = 0xff;
+ pic_slave_mask = 0xff;
+
+ /* ICW 1 - State that ICW 4 will be sent */
+ io_write_byte(PIC_MASTER_CMD, PIC_ICW1_INIT | PIC_ICW1_IC4);
+ io_write_byte(PIC_SLAVE_CMD, PIC_ICW1_INIT | PIC_ICW1_IC4);
+
+ /* ICW 2 */
+ io_write_byte(PIC_MASTER_IMR, TRAP_INTR_FIRST);
+ io_write_byte(PIC_SLAVE_IMR, TRAP_INTR_FIRST + PIC_NR_INTRS);
+
+ /* ICW 3 - Set up cascading */
+ io_write_byte(PIC_MASTER_IMR, 1 << PIC_SLAVE_INTR);
+ io_write_byte(PIC_SLAVE_IMR, PIC_SLAVE_INTR);
+
+ /* ICW 4 - Set 8086 mode */
+ io_write_byte(PIC_MASTER_IMR, PIC_ICW4_8086);
+ io_write_byte(PIC_SLAVE_IMR, PIC_ICW4_8086);
+
+ /* OCW 1 - Mask all interrupts */
+ io_write_byte(PIC_MASTER_IMR, pic_master_mask);
+ io_write_byte(PIC_SLAVE_IMR, pic_slave_mask);
+
+ if (lapic_unused()) {
+ pic_register();
+ }
+}
+
void
pic_spurious_intr(struct trap_frame *frame)
{
diff --git a/arch/x86/machine/uart.c b/arch/x86/machine/uart.c
index d70cc41..86ef1e3 100644
--- a/arch/x86/machine/uart.c
+++ b/arch/x86/machine/uart.c
@@ -26,6 +26,7 @@
#include <kern/assert.h>
#include <kern/console.h>
#include <kern/init.h>
+#include <kern/intr.h>
#include <kern/macros.h>
#include <machine/biosmem.h>
#include <machine/io.h>
@@ -183,7 +184,7 @@ uart_init(struct uart *uart, uint16_t port, uint16_t intr)
}
void __init
-uart_setup(void)
+uart_bootstrap(void)
{
const uint16_t *ptr;
size_t i;
@@ -199,6 +200,42 @@ uart_setup(void)
}
}
+static int
+uart_intr(void *arg)
+{
+ struct uart *uart;
+ uint8_t byte;
+
+ uart = arg;
+ byte = uart_read(uart, UART_REG_DAT);
+ printf("uart: intr:%u byte:%hhu (%c)\n", uart->intr, byte, byte);
+ return 0;
+}
+
+void __init
+uart_setup(void)
+{
+ struct uart *uart;
+ int error;
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(uart_devs); i++) {
+ uart = uart_get_dev(i);
+
+ if (uart->port == 0) {
+ continue;
+ }
+
+ error = intr_register(uart->intr, uart_intr, uart);
+
+ if (error) {
+ printf("uart%zu: unable to register interrupt %u\n", i, uart->intr);
+ }
+
+ uart_write(uart, UART_REG_IER, 1);
+ }
+}
+
void __init
uart_info(void)
{
diff --git a/arch/x86/machine/uart.h b/arch/x86/machine/uart.h
index 9cb7cbf..60b195c 100644
--- a/arch/x86/machine/uart.h
+++ b/arch/x86/machine/uart.h
@@ -22,7 +22,17 @@
#define _X86_UART_H
/*
+ * Early initialization of the uart module.
+ *
+ * Devices may only be used to report diagnostics until initialization
+ * is completed.
+ */
+void uart_bootstrap(void);
+
+/*
* Initialize the uart module.
+ *
+ * On return, devices may be used for both input and output, using interrupts.
*/
void uart_setup(void);