# The only one that needs changing is the assembler
# rule, as we use nasm instead of GNU as.
+CONF=-DCONF_EARLY_KPRINTF=1
+
TGT=i686
-SOURCES=$(TGT)/entry.o $(TGT)/utils.o module/biosvga/module.o module/console.o common/kernel_main.o
+SOURCES= \
+ $(TGT)/entry.o \
+ $(TGT)/utils.o \
+ module/biosvga/module.o module/serial/module.o module/console.o \
+ common/stdc/memmove.o common/stdc/strlen.o common/stdc/memcmp.o common/stdc/memset.o common/stdc/memcpy.o \
+ common/kernel_main.o
+
LDSC=$(TGT)/link.ld
TGTNAM=$(TGT)-elf
$(LD) $(LDFLAGS) -o kernel $(LIBS) $(SOURCES)
%.o: %.c
- $(CC) $(CFLAGS) $(CPPFLAGS) $(ASFLAGS) -c -o $@ $<
+ $(CC) $(CFLAGS) $(CONF) $(CPPFLAGS) $(ASFLAGS) -c -o $@ $<
%.o: %.cxx
- $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(ASFLAGS) -c -o $@ $<
+ $(CXX) $(CXXFLAGS) $(CONF) $(CPPFLAGS) $(ASFLAGS) -c -o $@ $<
%.o: %.S
$(AS) $(ASFLAGS) -c -o $@ $<
{
console_init();
- kwrite("1\n");
- kwrite("2\n");
- kwrite("3\n");
- kwrite("4\n");
- kwrite("5\n");
- kwrite("6\n");
- kwrite("7\n");
- kwrite("8\n");
- kwrite("9\n");
- kwrite("10\n");
- kwrite("11\n");
- kwrite("12\n");
- kwrite("13\n");
- kwrite("14\n");
- kwrite("15\n");
- kwrite("16\n");
- kwrite("17\n");
- kwrite("18\n");
- kwrite("19\n");
- kwrite("20\n");
- kwrite("21\n");
- kwrite("22\n");
- kwrite("23\n");
- kwrite("24\n");
- kwrite("25\n");
- kwrite("26\n");
+ kprintf("1%c%% %s %X\n", 'c', "whoop", 0xDEADBEEF);
console_deinit();
+ asm volatile("int $4");
+ asm volatile("int $3");
+
+ while(1);
+
return 0xDEADBEEF;
}
--- /dev/null
+#include <string.h>
+
+int memcmp(const void* aptr, const void* bptr, size_t size) {
+ const unsigned char* a = (const unsigned char*) aptr;
+ const unsigned char* b = (const unsigned char*) bptr;
+ for (size_t i = 0; i < size; i++) {
+ if (a[i] < b[i])
+ return -1;
+ else if (b[i] < a[i])
+ return 1;
+ }
+ return 0;
+}
--- /dev/null
+#include <string.h>
+
+void* memcpy(void* restrict dstptr, const void* restrict srcptr, size_t size) {
+ unsigned char* dst = (unsigned char*) dstptr;
+ const unsigned char* src = (const unsigned char*) srcptr;
+ for (size_t i = 0; i < size; i++)
+ dst[i] = src[i];
+ return dstptr;
+}
--- /dev/null
+#include <string.h>
+
+void* memmove(void* dstptr, const void* srcptr, size_t size) {
+ unsigned char* dst = (unsigned char*) dstptr;
+ const unsigned char* src = (const unsigned char*) srcptr;
+ if (dst < src) {
+ for (size_t i = 0; i < size; i++)
+ dst[i] = src[i];
+ } else {
+ for (size_t i = size; i != 0; i--)
+ dst[i-1] = src[i-1];
+ }
+ return dstptr;
+}
--- /dev/null
+#include <string.h>
+
+void* memset(void* bufptr, int value, size_t size) {
+ unsigned char* buf = (unsigned char*) bufptr;
+ for (size_t i = 0; i < size; i++)
+ buf[i] = (unsigned char) value;
+ return bufptr;
+}
--- /dev/null
+#include <string.h>
+
+size_t strlen(const char* str) {
+ size_t len = 0;
+ while (str[len])
+ len++;
+ return len;
+}
+/* Multiboot garbage. */
+
.set ALIGN, 1<<0 # align loaded modules on page boundaries
.set MEMINFO, 1<<1 # provide memory map
.set FLAGS, ALIGN | MEMINFO # this is the Multiboot 'flag' field
.long FLAGS
.long CHECKSUM
-.section .bss
-.align 16
+/* Stack. */
+.section .bootstrap_stack, "aw", @nobits
stack_bottom:
-.skip 16384 # 16 KiB
+.skip 16384 // 16 KiB
stack_top:
+.set KERNEL_VBASE, 0xC0000000
+.set KERNEL_PNUM, (KERNEL_VBASE >> 22)
+
+.section .data
+.align 0x1000
+boot_pgdir:
+ .int 0x83
+ .fill (KERNEL_PNUM - 1), 4, 0
+ .int 0x83
+ .fill (1024 - KERNEL_PNUM - 1), 4, 0
+
+/* Our actual entrypoint. */
.section .text
.global _start
.type _start, @function
_start:
- mov $stack_top, %esp
+ // Map our kernel at KERNEL_VBASE and enable paging
+ mov $(boot_pgdir - KERNEL_VBASE), %ecx
+ mov %ecx,%cr3
+
+ mov %cr4,%ecx
+ or $0x10,%ecx
+ mov %ecx,%cr4
+
+ mov %cr0,%ecx
+ or $0x80000000,%ecx
+ mov %ecx,%cr0
+
+ lea .init, %ecx
+ jmp *%ecx
- push %ebx
+.init:
+ // Unmap page 0 (it's not needed now)
+ movl $0, boot_pgdir
+ invlpg 0
- call kernel_main
+ mov $stack_top, %esp
- cli
-.halt: hlt
- jmp .halt
+ push %ebx
+
+ call i686_init
+
+ call kernel_main
+
+ cli
+.halt:
+ hlt
+ jmp .halt
.size _start, . - _start
+
+.global gdt_flush
+.type gdt_flush, @function
+gdt_flush:
+ mov 0x4(%esp),%eax
+ lgdt (%eax)
+ mov $0x10,%ax
+ mov %eax,%ds
+ mov %eax,%es
+ mov %eax,%fs
+ mov %eax,%gs
+ mov %eax,%ss
+ lea .flush, %eax
+ jmp *%eax
+ .flush:
+ ret
+
+.size gdt_flush, . - gdt_flush
+
+.macro ISR index
+ .global isr_\index
+ .type isr_\index , @function
+ isr_\index :
+ push $0
+ push $\index
+ jmp isr_stub
+.endm
+
+.macro ISR_E index
+ .global isr_\index
+ .type isr_\index , @function
+ isr_\index :
+ push $\index
+ jmp isr_stub
+.endm
+
+ISR 0
+ISR 1
+ISR 2
+ISR 3
+ISR 4
+ISR 5
+ISR 6
+ISR 7
+ISR_E 8
+ISR 9
+ISR_E 10
+ISR_E 11
+ISR_E 12
+ISR_E 13
+ISR_E 14
+ISR 15
+ISR 16
+ISR_E 17
+ISR 18
+ISR 19
+ISR 20
+ISR 21
+ISR 22
+ISR 23
+ISR 24
+ISR 25
+ISR 26
+ISR 27
+ISR 28
+ISR 29
+ISR_E 30
+ISR 31
+
+isr_stub:
+ pusha
+
+ mov %ds, %ax
+ push %eax
+
+ mov $0x10,%ax
+ mov %ax,%ds
+ mov %ax,%es
+ mov %ax,%fs
+ mov %ax,%gs
+
+ push %esp
+
+ cld
+ call isr_handler
+
+ pop %eax
+
+ pop %eax
+ mov %ax,%ds
+ mov %ax,%es
+ mov %ax,%fs
+ mov %ax,%gs
+
+ popa
+ add $8, %esp
+ iret
+
+.global idt_flush
+.type idt_flush, @function
+idt_flush:
+ mov 0x4(%esp),%eax
+ lidt (%eax)
+ ret
kernel image. */
SECTIONS
{
- /* Begin putting sections at 1 MiB, a conventional place for kernels to be
- loaded at by the bootloader. */
- . = 1M;
-
- /* First put the multiboot header, as it is required to be put very early
- early in the image or the bootloader won't recognize the file format.
- Next we'll put the .text section. */
- .text BLOCK(4K) : ALIGN(4K)
+ /* Begin putting sections at 1 MiB (+3GB virt) */
+ . = 0xC0100000;
+
+ _kernel_start = .;
+
+ .text ALIGN (4K) : AT (ADDR (.text) - 0xC0000000)
{
*(.multiboot)
*(.text)
}
- /* Read-only data. */
- .rodata BLOCK(4K) : ALIGN(4K)
+ .rodata ALIGN (4K) : AT (ADDR (.rodata) - 0xC0000000)
{
*(.rodata)
}
- /* Read-write data (initialized) */
- .data BLOCK(4K) : ALIGN(4K)
+ .data ALIGN (4K) : AT (ADDR (.data) - 0xC0000000)
{
*(.data)
}
- /* Read-write data (uninitialized) and stack */
- .bss BLOCK(4K) : ALIGN(4K)
+ .bss ALIGN (4K) : AT (ADDR (.bss) - 0xC0000000)
{
+ _sbss = .;
*(COMMON)
*(.bss)
+ *(.bootstrap_stack)
+ _ebss = .;
}
/* The compiler may produce other sections, by default it will put them in
a segment with the same name. Simply add stuff here as needed. */
+
+ /* Add a symbol that indicates the end address of the kernel. */
+ _kernel_end = .;
}
#include <stdint.h>
+#include <stddef.h>
+
+#include <stdc/string.h>
void outb(uint16_t port, uint8_t val)
{
: "Nd"(port) );
return ret;
}
+
+
+// Lets us access our ASM functions from our C code.
+extern void gdt_flush(uint32_t);
+
+// This structure contains the value of one GDT entry.
+// We use the attribute 'packed' to tell GCC not to change
+// any of the alignment in the structure.
+struct gdt_entry_struct
+{
+ uint16_t limit_low; // The lower 16 bits of the limit.
+ uint16_t base_low; // The lower 16 bits of the base.
+ uint8_t base_middle; // The next 8 bits of the base.
+ uint8_t access; // Access flags, determine what ring this segment can be used in.
+ uint8_t granularity;
+ uint8_t base_high; // The last 8 bits of the base.
+} __attribute__((packed));
+typedef struct gdt_entry_struct gdt_entry_t;
+
+struct gdt_ptr_struct
+{
+ uint16_t limit; // The upper 16 bits of all selector limits.
+ uint32_t base; // The address of the first gdt_entry_t struct.
+}
+ __attribute__((packed));
+typedef struct gdt_ptr_struct gdt_ptr_t;
+
+gdt_entry_t gdt_entries[5];
+gdt_ptr_t gdt_ptr;
+
+// Set the value of one GDT entry.
+static void gdt_set_gate(int32_t num, uint32_t base, uint32_t limit, uint8_t access, uint8_t gran)
+{
+ gdt_entries[num].base_low = (base & 0xFFFF);
+ gdt_entries[num].base_middle = (base >> 16) & 0xFF;
+ gdt_entries[num].base_high = (base >> 24) & 0xFF;
+
+ gdt_entries[num].limit_low = (limit & 0xFFFF);
+ gdt_entries[num].granularity = (limit >> 16) & 0x0F;
+
+ gdt_entries[num].granularity |= gran & 0xF0;
+ gdt_entries[num].access = access;
+}
+
+static void init_gdt()
+{
+ gdt_ptr.limit = (sizeof(gdt_entry_t) * 5) - 1;
+ gdt_ptr.base = (uint32_t)&gdt_entries;
+
+ gdt_set_gate(0, 0, 0, 0, 0); // Null segment
+ gdt_set_gate(1, 0, 0xFFFFFFFF, 0x9A, 0xCF); // Code segment
+ gdt_set_gate(2, 0, 0xFFFFFFFF, 0x92, 0xCF); // Data segment
+ gdt_set_gate(3, 0, 0xFFFFFFFF, 0xFA, 0xCF); // User mode code segment
+ gdt_set_gate(4, 0, 0xFFFFFFFF, 0xF2, 0xCF); // User mode data segment
+
+ gdt_flush((uint32_t)&gdt_ptr);
+}
+
+// A struct describing an interrupt gate.
+struct idt_entry_struct
+{
+ uint16_t base_lo; // The lower 16 bits of the address to jump to when this interrupt fires.
+ uint16_t sel; // Kernel segment selector.
+ uint8_t always0; // This must always be zero.
+ uint8_t flags; // More flags. See documentation.
+ uint16_t base_hi; // The upper 16 bits of the address to jump to.
+} __attribute__((packed));
+typedef struct idt_entry_struct idt_entry_t;
+
+// A struct describing a pointer to an array of interrupt handlers.
+// This is in a format suitable for giving to 'lidt'.
+struct idt_ptr_struct
+{
+ uint16_t limit;
+ uint32_t base; // The address of the first element in our idt_entry_t array.
+} __attribute__((packed));
+typedef struct idt_ptr_struct idt_ptr_t;
+
+idt_entry_t idt_entries[256];
+idt_ptr_t idt_ptr;
+
+static void idt_set_gate(uint8_t num, uint32_t base, uint16_t sel, uint8_t flags)
+{
+ idt_entries[num].base_lo = base & 0xFFFF;
+ idt_entries[num].base_hi = (base >> 16) & 0xFFFF;
+
+ idt_entries[num].sel = sel;
+ idt_entries[num].always0 = 0;
+ // We must uncomment the OR below when we get to using user-mode.
+ // It sets the interrupt gate's privilege level to 3.
+ idt_entries[num].flags = flags /* | 0x60 */;
+}
+
+struct interrupt_frame
+{
+ uint32_t ds; // Data segment selector
+ uint32_t edi, esi, ebp, reserve, ebx, edx, ecx, eax; // Pushed by pusha.
+ uint32_t int_no, err_code; // Interrupt number and error code (if applicable)
+ uint32_t eip, cs, eflags, esp, ss; // Pushed by the processor automatically.
+};
+
+char *strs[] = {
+ "divide by zero",
+ "debug exception",
+ "non maskable interrupt",
+ "breakpoint",
+ "into detected overflow",
+ "out of bounds exception",
+ "invalid opcode exception",
+ "no coprocessor exception",
+ "double fault",
+ "coprocessor segment overrun",
+ "bad tss",
+ "segment not present",
+ "stack fault",
+ "general protection fault",
+ "page fault",
+ "unknown interrupt",
+ "coprocessor fault",
+ "alignment check exception",
+ "machine check exception"
+};
+
+void isr_handler(struct interrupt_frame* esp) {
+ kprintf("int %x: ", esp->int_no);
+ if (esp->int_no <= 18) {
+ kprintf("%s\n", strs[esp->int_no]);
+ } else {
+ kprintf("reserved\n");
+ }
+}
+
+extern void isr_0();
+extern void isr_1();
+extern void isr_2();
+extern void isr_3();
+extern void isr_4();
+extern void isr_5();
+extern void isr_6();
+extern void isr_7();
+extern void isr_8();
+extern void isr_9();
+extern void isr_10();
+extern void isr_11();
+extern void isr_12();
+extern void isr_13();
+extern void isr_14();
+extern void isr_15();
+extern void isr_16();
+extern void isr_17();
+extern void isr_18();
+extern void isr_19();
+extern void isr_20();
+extern void isr_21();
+extern void isr_22();
+extern void isr_23();
+extern void isr_24();
+extern void isr_25();
+extern void isr_26();
+extern void isr_27();
+extern void isr_28();
+extern void isr_29();
+extern void isr_30();
+extern void isr_31();
+
+#define ISR_CALL(N) \
+ idt_set_gate( N , (uint32_t) isr_##N , 0x08, 0x8E )
+
+static void init_idt()
+{
+ idt_ptr.limit = sizeof(idt_entry_t) * 256 - 1;
+ idt_ptr.base = (uint32_t)&idt_entries;
+
+ memset(&idt_entries, 0, sizeof(idt_entry_t)*256);
+
+ ISR_CALL(0);
+ ISR_CALL(1);
+ ISR_CALL(2);
+ ISR_CALL(3);
+ ISR_CALL(4);
+ ISR_CALL(5);
+ ISR_CALL(6);
+ ISR_CALL(7);
+ ISR_CALL(8);
+ ISR_CALL(9);
+ ISR_CALL(10);
+ ISR_CALL(11);
+ ISR_CALL(12);
+ ISR_CALL(13);
+ ISR_CALL(14);
+ ISR_CALL(15);
+ ISR_CALL(16);
+ ISR_CALL(17);
+ ISR_CALL(18);
+ ISR_CALL(19);
+ ISR_CALL(20);
+ ISR_CALL(21);
+ ISR_CALL(22);
+ ISR_CALL(23);
+ ISR_CALL(24);
+ ISR_CALL(25);
+ ISR_CALL(26);
+ ISR_CALL(27);
+ ISR_CALL(28);
+ ISR_CALL(29);
+ ISR_CALL(30);
+ ISR_CALL(31);
+
+ idt_flush((uint32_t)&idt_ptr);
+}
+
+void i686_init() {
+ init_gdt();
+ init_idt();
+}
--- /dev/null
+#define memcpy __builtin_memcpy
+#define memcmp __builtin_memcmp
+#define strcpy __builtin_strcpy
+#define strcmp __builtin_strcmp
+#include <stdarg.h>
+
typedef void (*console_put_f) (char, char, char, uint16_t, uint16_t);
typedef void (*console_get_f) (char*, char*, char*, uint16_t, uint16_t);
typedef void (*console_cursor_f) (uint16_t, uint16_t);
typedef uint16_t (*console_width_f) ();
typedef uint16_t (*console_height_f) ();
typedef void (*console_refresh_f)();
+typedef int (*console_tty_f) ();
struct console_module {
console_put_f put;
console_width_f width;
console_height_f height;
console_refresh_f refresh;
+ console_tty_f tty;
};
void console_register_module(struct console_module*);
void kputc(char);
void kwrite(char*);
+void kvsprintf(char *buffer, const char* fmt_con, va_list ap);
+void kprintf(const char* fmt, ...);
--- /dev/null
+#ifndef _SYS_CDEFS_H
+#define _SYS_CDEFS_H 1
+
+#define __eidos_kernel_stdc 1
+
+#endif
--- /dev/null
+#ifndef _STRING_H
+#define _STRING_H 1
+
+#include <sys/cdefs.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+int memcmp(const void*, const void*, size_t);
+void* memcpy(void* __restrict, const void* __restrict, size_t);
+void* memmove(void*, const void*, size_t);
+void* memset(void*, int, size_t);
+size_t strlen(const char*);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
#include <module.h>
#include <module_console.h>
-uint16_t *video_mem = (uint16_t*)0xB8000;
+uint16_t *video_mem = (uint16_t*)0xC00B8000;
void biosvga_put(char c, char color_fg, char color_bg, uint16_t x, uint16_t y) {
uint8_t attr = (color_bg << 4) | (color_fg & 0xF);
}
+int biosvga_tty() {
+ return 1;
+}
+
struct console_module biosvga_module = {
.put = biosvga_put,
.get = biosvga_get,
+ .tty = biosvga_tty,
.cursor = biosvga_cursor,
.width = biosvga_width,
.height = biosvga_height,
#include <stdint.h>
#include <stddef.h>
+#include <stdarg.h>
#include <module.h>
#include <module_console.h>
struct console_module *active;
+struct console_module *reg[2];
uint16_t x, y, tab_w;
void biosvga_init();
void biosvga_deinit();
+void console_mark_active(int idx) {
+ active = reg[idx];
+}
+
void console_register_module(struct console_module* module) {
- active = module;
+ reg[1] = reg[0];
+ reg[0] = module;
}
void console_unregister_module(struct console_module* module) {
- if (active == module)
- active = NULL;
+ if (reg[0] == module)
+ reg[0] = reg[1];
+ if (reg[1] == module)
+ reg[1] = NULL;
}
// TODO - Should this be part of the backend? Scrolling is slow.
void scroll() {
+ if (active->tty() == 0)
+ return;
+
if (y >= active->height()) {
for(int cy = 1; cy <= active->height(); cy++) {
for(int cx = 0; cx <= active->width(); cx++) {
// TODO - Should this be part of the backend? Scrolling is slow.
void clear() {
+ if (active->tty() == 0)
+ return;
+
x = y = 0;
for(int cy = 0; cy < active->height(); cy++) {
for(int cx = 0; cx < active->width(); cx++) {
}
scroll();
- active->cursor(x, y);
+ if (active->tty() == 1)
+ active->cursor(x, y);
}
void kwrite(char* str) {
}
}
+int strcatlen(char* a, char* b) {
+ char* o = b;
+ while(*b != 0) {
+ *a = *b;
+ a++;
+ b++;
+ }
+ return b - o;
+}
+
+int dumphex(char* buf, const char* index, unsigned int num) {
+ uint8_t *num_8 = (uint8_t *)#
+ char *obuf = buf;
+ for (int i = 3; i >= 0; i--) {
+ uint8_t high = (num_8[i] >> 4) & 0xf;
+ uint8_t low = num_8[i] & 0xf;
+
+ *buf = (index)[high]; buf++;
+ *buf = (index)[low]; buf++;
+ }
+
+ return buf - obuf;
+}
+
+void kvsprintf(char *buffer, const char* fmt_con, va_list ap) {
+ char* fmt = (char*)fmt_con;
+ char* buf = buffer;
+ while(*fmt != 0) {
+ if (*fmt == '%') {
+ // Format string.
+ ++fmt;
+ switch (*fmt) {
+ case 'c':
+ *buf = (char)va_arg(ap, int);
+ buf++;
+ fmt++;
+ break;
+ case '%':
+ *buf = '%';
+ buf++;
+ fmt++;
+ break;
+ case 's':
+ buf += strcatlen(buf, va_arg(ap, char*));
+ fmt++;
+ break;
+ case 'x':
+ buf += dumphex(buf, "0123456789abcdef", va_arg(ap, unsigned int));
+ ++fmt;
+ break;
+ case 'X':
+ buf += dumphex(buf, "0123456789ABCDEF", va_arg(ap, unsigned int));
+ ++fmt;
+ break;
+ case 'd':
+ case 'u':
+ default:
+ ++fmt;
+ break;
+ }
+ } else {
+ *buf = *fmt;
+ ++buf;
+ ++fmt;
+ }
+ }
+
+ *buf = 0;
+}
+
+char buf[4096];
+
+void kprintf(const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+
+ kvsprintf(buf, fmt, ap);
+
+ va_end(ap);
+
+ kwrite(buf);
+}
+
+void defer_kprintf(const char* fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+
+ kvsprintf(buf, fmt, ap);
+
+ va_end(ap);
+}
+
+void flush_defer_kprintf() {
+ kwrite(buf);
+}
+
void console_init() {
x = y = 0;
tab_w = 4;
// Temporary
+ serial_init();
biosvga_init();
+ console_mark_active(0);
+
clear();
}
void console_deinit() {
// Temporary
biosvga_deinit();
+ serial_deinit();
}
-
--- /dev/null
+#include <stdint.h>
+#include <module.h>
+#include <module_console.h>
+
+#define PORT 0x3f8 /* COM1 */
+
+int is_transmit_empty() {
+ return inb(PORT + 5) & 0x20;
+}
+
+void serial_put(char c, char color_fg, char color_bg, uint16_t x, uint16_t y) {
+ while (is_transmit_empty() == 0);
+ outb(PORT, c);
+}
+
+void serial_get(char* c, char* color_fg, char* color_bg, uint16_t x, uint16_t y) {
+}
+
+int serial_tty() {
+ return 0;
+}
+
+struct console_module serial_module = {
+ .put = serial_put,
+ .get = serial_get,
+ .tty = serial_tty
+};
+
+void serial_init() {
+ outb(PORT + 1, 0x00); // Disable all interrupts
+ outb(PORT + 3, 0x80); // Enable DLAB (set baud rate divisor)
+ outb(PORT + 0, 0x03); // Set divisor to 3 (lo byte) 38400 baud
+ outb(PORT + 1, 0x00); // (hi byte)
+ outb(PORT + 3, 0x03); // 8 bits, no parity, one stop bit
+ outb(PORT + 2, 0xC7); // Enable FIFO, clear them, with 14-byte threshold
+ outb(PORT + 4, 0x0B); // IRQs enabled, RTS/DSR set
+
+ console_register_module(&serial_module);
+}
+
+void serial_deinit() {
+ console_unregister_module(&serial_module);
+ // Stub.
+}