initial versioin

This commit is contained in:
Wang Chen
2021-03-31 14:32:02 +08:00
committed by unicornx
parent 8ad78e0e0a
commit ad15280f3a
277 changed files with 15119 additions and 1 deletions

View File

@@ -0,0 +1,61 @@
CROSS_COMPILE = riscv64-unknown-elf-
CFLAGS = -nostdlib -fno-builtin -mcmodel=medany -march=rv32ima -mabi=ilp32 -g -Wall
QEMU = qemu-system-riscv32
QFLAGS = -nographic -smp 1 -machine virt -bios none
GDB = ${CROSS_COMPILE}gdb
CC = ${CROSS_COMPILE}gcc
OBJCOPY = ${CROSS_COMPILE}objcopy
OBJDUMP = ${CROSS_COMPILE}objdump
SRCS_ASM = \
start.S \
mem.S \
entry.S \
SRCS_C = \
kernel.c \
uart.c \
printf.c \
page.c \
sched.c \
OBJS = $(SRCS_ASM:.S=.o)
OBJS += $(SRCS_C:.c=.o)
.DEFAULT_GOAL := all
all: os.elf
# start.o must be the first in dependency!
os.elf: ${OBJS}
${CC} $(CFLAGS) -T os.ld -o os.elf $^
${OBJCOPY} -O binary os.elf os.bin
%.o : %.c
${CC} ${CFLAGS} -c -o $@ $<
%.o : %.S
${CC} ${CFLAGS} -c -o $@ $<
run: all
@${QEMU} -M ? | grep virt >/dev/null || exit
@echo "Press Ctrl-A and then X to exit QEMU"
@echo "------------------------------------"
@${QEMU} ${QFLAGS} -kernel os.elf
.PHONY : debug
debug: all
@echo "Press Ctrl-C and then input 'quit' to exit GDB and QEMU"
@echo "-------------------------------------------------------"
@${QEMU} ${QFLAGS} -kernel os.elf -s -S &
@${GDB} os.elf -q -x ../gdbinit
.PHONY : code
code: all
@${OBJDUMP} -S os.elf | less
.PHONY : clean
clean:
rm -rf *.o *.bin *.elf

View File

@@ -0,0 +1,106 @@
# save all General-Purpose(GP) registers to context
# struct context *base = &ctx_task;
# base->ra = ra;
# ......
.macro reg_save base
sw ra, 0(\base)
sw sp, 4(\base)
sw gp, 8(\base)
sw tp, 12(\base)
sw t0, 16(\base)
sw t1, 20(\base)
sw t2, 24(\base)
sw s0, 28(\base)
sw s1, 32(\base)
sw a0, 36(\base)
sw a1, 40(\base)
sw a2, 44(\base)
sw a3, 48(\base)
sw a4, 52(\base)
sw a5, 56(\base)
sw a6, 60(\base)
sw a7, 64(\base)
sw s2, 68(\base)
sw s3, 72(\base)
sw s4, 76(\base)
sw s5, 80(\base)
sw s6, 84(\base)
sw s7, 88(\base)
sw s8, 92(\base)
sw s9, 96(\base)
sw s10, 100(\base)
sw s11, 104(\base)
sw t3, 108(\base)
sw t4, 112(\base)
sw t5, 116(\base)
sw t6, 120(\base)
.endm
# restore all General-Purpose(GP) registers from the context
# struct context *base = &ctx_task;
# ra = base->ra;
# ......
.macro reg_restore base
lw ra, 0(\base)
lw sp, 4(\base)
lw gp, 8(\base)
lw tp, 12(\base)
lw t0, 16(\base)
lw t1, 20(\base)
lw t2, 24(\base)
lw s0, 28(\base)
lw s1, 32(\base)
lw a0, 36(\base)
lw a1, 40(\base)
lw a2, 44(\base)
lw a3, 48(\base)
lw a4, 52(\base)
lw a5, 56(\base)
lw a6, 60(\base)
lw a7, 64(\base)
lw s2, 68(\base)
lw s3, 72(\base)
lw s4, 76(\base)
lw s5, 80(\base)
lw s6, 84(\base)
lw s7, 88(\base)
lw s8, 92(\base)
lw s9, 96(\base)
lw s10, 100(\base)
lw s11, 104(\base)
lw t3, 108(\base)
lw t4, 112(\base)
lw t5, 116(\base)
lw t6, 120(\base)
.endm
# Something to note about save/restore:
# - We use mscratch to hold a pointer to context of previous task
# - We use t6 as the 'base' for reg_save/reg_restore, because it is the
# very bottom register (x31) and would not be overwritten during loading.
.text
# void switch_to(struct context *next);
# a0: pointer to the context of the next task
.globl switch_to
.align 4
switch_to:
csrrw t6, mscratch, t6 # swap t6 and mscratch
beqz t6, 1f # Notice: previous task may be NULL
reg_save t6 # save context of prev task
1:
# switch mscratch to point to the context of the next task
csrw mscratch, a0
# Restore all GP registers
# Use t6 to point to the context of the new task
mv t6, a0
reg_restore t6
# Do actual context switching.
ret
.end

View File

@@ -0,0 +1,26 @@
#include "os.h"
/*
* Following functions SHOULD be called ONLY ONE time here,
* so just declared here ONCE and NOT included in file os.h.
*/
extern void uart_init(void);
extern void page_init(void);
extern void sched_init(void);
extern void schedule(void);
void start_kernel(void)
{
uart_init();
uart_puts("Hello, RVOS!\n");
page_init();
sched_init();
schedule();
uart_puts("Would not go here!\n");
while (1) {}; // stop here!
}

View File

@@ -0,0 +1,30 @@
.section .rodata
.global HEAP_START
HEAP_START: .word _heap_start
.global HEAP_SIZE
HEAP_SIZE: .word _heap_size
.global TEXT_START
TEXT_START: .word _text_start
.global TEXT_END
TEXT_END: .word _text_end
.global DATA_START
DATA_START: .word _data_start
.global DATA_END
DATA_END: .word _data_end
.global RODATA_START
RODATA_START: .word _rodata_start
.global RODATA_END
RODATA_END: .word _rodata_end
.global BSS_START
BSS_START: .word _bss_start
.global BSS_END
BSS_END: .word _bss_end

View File

@@ -0,0 +1,61 @@
#ifndef __OS_H__
#define __OS_H__
#include "types.h"
#include "platform.h"
#include <stddef.h>
#include <stdarg.h>
/* uart */
extern int uart_putc(char ch);
extern void uart_puts(char *s);
/* printf */
extern int printf(const char* s, ...);
extern void panic(char *s);
/* memory management */
extern void *page_alloc(int npages);
extern void page_free(void *p);
/* task management */
struct context {
/* ignore x0 */
reg_t ra;
reg_t sp;
reg_t gp;
reg_t tp;
reg_t t0;
reg_t t1;
reg_t t2;
reg_t s0;
reg_t s1;
reg_t a0;
reg_t a1;
reg_t a2;
reg_t a3;
reg_t a4;
reg_t a5;
reg_t a6;
reg_t a7;
reg_t s2;
reg_t s3;
reg_t s4;
reg_t s5;
reg_t s6;
reg_t s7;
reg_t s8;
reg_t s9;
reg_t s10;
reg_t s11;
reg_t t3;
reg_t t4;
reg_t t5;
reg_t t6;
};
extern int task_create(void (*task)(void));
extern void task_delay(volatile int count);
#endif /* __OS_H__ */

View File

@@ -0,0 +1,136 @@
/*
* rvos.ld
* Linker script for outputting to RVOS
*/
/*
* https://sourceware.org/binutils/docs/ld/Miscellaneous-Commands.html
* OUTPUT_ARCH command specifies a particular output machine architecture.
* "riscv" is the name of the architecture for both 64-bit and 32-bit
* RISC-V target. We will further refine this by using -march=rv32ima
* and -mabi=ilp32 when calling gcc.
*/
OUTPUT_ARCH( "riscv" )
/*
* https://sourceware.org/binutils/docs/ld/Entry-Point.html
* ENTRY command is used to set the "entry point", which is the first instruction
* to execute in a program.
* The argument of ENTRY command is a symbol name, here is "_start" which is
* defined in start.S.
*/
ENTRY( _start )
/*
* https://sourceware.org/binutils/docs/ld/MEMORY.html
* The MEMORY command describes the location and size of blocks of memory in
* the target.
* The syntax for MEMORY is:
* MEMORY
* {
* name [(attr)] : ORIGIN = origin, LENGTH = len
* ......
* }
* Each line defines a memory region.
* Each memory region must have a distinct name within the MEMORY command. Here
* we only define one region named as "ram".
* The "attr" string is an optional list of attributes that specify whether to
* use a particular memory region for an input section which is not explicitly
* mapped in the linker script. Here we assign 'w' (writeable), 'x' (executable),
* and 'a' (allocatable). We use '!' to invert 'r' (read-only) and
* 'i' (initialized).
* The "ORIGIN" is used to set the start address of the memory region. Here we
* place it right at the beginning of 0x8000_0000 because this is where the
* QEMU-virt machine will start executing.
* Finally LENGTH = 128M tells the linker that we have 128 megabyte of RAM.
* The linker will double check this to make sure everything can fit.
*/
MEMORY
{
ram (wxa!ri) : ORIGIN = 0x80000000, LENGTH = 128M
}
/*
* https://sourceware.org/binutils/docs/ld/SECTIONS.html
* The SECTIONS command tells the linker how to map input sections into output
* sections, and how to place the output sections in memory.
* The format of the SECTIONS command is:
* SECTIONS
* {
* sections-command
* sections-command
* ......
* }
*
* Each sections-command may of be one of the following:
* (1) an ENTRY command
* (2) a symbol assignment
* (3) an output section description
* (4) an overlay description
* We here only demo (2) & (3).
*
* We use PROVIDE command to define symbols.
* https://sourceware.org/binutils/docs/ld/PROVIDE.html
* The PROVIDE keyword may be used to define a symbol.
* The syntax is PROVIDE(symbol = expression).
* Such symbols as "_text_start", "_text_end" ... will be used in mem.S.
* Notice the period '.' tells the linker to set symbol(e.g. _text_start) to
* the CURRENT location ('.' = current memory location). This current memory
* location moves as we add things.
*/
SECTIONS
{
/*
* We are going to layout all text sections in .text output section,
* starting with .text. The asterisk("*") in front of the
* parentheses means to match the .text section of ANY object file.
*/
.text : {
PROVIDE(_text_start = .);
*(.text .text.*)
PROVIDE(_text_end = .);
} >ram
.rodata : {
PROVIDE(_rodata_start = .);
*(.rodata .rodata.*)
PROVIDE(_rodata_end = .);
} >ram
.data : {
/*
* . = ALIGN(4096) tells the linker to align the current memory
* location to 4096 bytes. This will insert padding bytes until
* current location becomes aligned on 4096-byte boundary.
* This is because our paging system's resolution is 4,096 bytes.
*/
. = ALIGN(4096);
PROVIDE(_data_start = .);
/*
* sdata and data are essentially the same thing. We do not need
* to distinguish sdata from data.
*/
*(.sdata .sdata.*)
*(.data .data.*)
PROVIDE(_data_end = .);
} >ram
.bss :{
/*
* https://sourceware.org/binutils/docs/ld/Input-Section-Common.html
* In most cases, common symbols in input files will be placed
* in the .bss section in the output file.
*/
PROVIDE(_bss_start = .);
*(.sbss .sbss.*)
*(.bss .bss.*)
*(COMMON)
PROVIDE(_bss_end = .);
} >ram
PROVIDE(_memory_start = ORIGIN(ram));
PROVIDE(_memory_end = ORIGIN(ram) + LENGTH(ram));
PROVIDE(_heap_start = _bss_end);
PROVIDE(_heap_size = _memory_end - _heap_start);
}

View File

@@ -0,0 +1,189 @@
#include "os.h"
/*
* Following global vars are defined in mem.S
*/
extern uint32_t TEXT_START;
extern uint32_t TEXT_END;
extern uint32_t DATA_START;
extern uint32_t DATA_END;
extern uint32_t RODATA_START;
extern uint32_t RODATA_END;
extern uint32_t BSS_START;
extern uint32_t BSS_END;
extern uint32_t HEAP_START;
extern uint32_t HEAP_SIZE;
/*
* _alloc_start points to the actual start address of heap pool
* _alloc_end points to the actual end address of heap pool
* _num_pages holds the actual max number of pages we can allocate.
*/
static uint32_t _alloc_start = 0;
static uint32_t _alloc_end = 0;
static uint32_t _num_pages = 0;
#define PAGE_SIZE 4096
#define PAGE_ORDER 12
#define PAGE_TAKEN (uint8_t)(1 << 0)
#define PAGE_LAST (uint8_t)(1 << 1)
/*
* Page Descriptor
* flags:
* - bit 0: flag if this page is taken(allocated)
* - bit 1: flag if this page is the last page of the memory block allocated
*/
struct Page {
uint8_t flags;
};
static inline void _clear(struct Page *page)
{
page->flags = 0;
}
static inline int _is_free(struct Page *page)
{
if (page->flags & PAGE_TAKEN) {
return 0;
} else {
return 1;
}
}
static inline void _set_flag(struct Page *page, uint8_t flags)
{
page->flags |= flags;
}
static inline int _is_last(struct Page *page)
{
if (page->flags & PAGE_LAST) {
return 1;
} else {
return 0;
}
}
/*
* align the address to the border of page(4K)
*/
static inline uint32_t _align_page(uint32_t address)
{
uint32_t order = (1 << PAGE_ORDER) - 1;
return (address + order) & (~order);
}
void page_init()
{
/*
* We reserved 8 Page (8 x 4096) to hold the Page structures.
* It should be enough to manage at most 128 MB (8 x 4096 x 4096)
*/
_num_pages = (HEAP_SIZE / PAGE_SIZE) - 8;
printf("HEAP_START = %x, HEAP_SIZE = %x, num of pages = %d\n", HEAP_START, HEAP_SIZE, _num_pages);
struct Page *page = (struct Page *)HEAP_START;
for (int i = 0; i < _num_pages; i++) {
_clear(page);
page++;
}
_alloc_start = _align_page(HEAP_START + 8 * PAGE_SIZE);
_alloc_end = _alloc_start + (PAGE_SIZE * _num_pages);
printf("TEXT: 0x%x -> 0x%x\n", TEXT_START, TEXT_END);
printf("RODATA: 0x%x -> 0x%x\n", RODATA_START, RODATA_END);
printf("DATA: 0x%x -> 0x%x\n", DATA_START, DATA_END);
printf("BSS: 0x%x -> 0x%x\n", BSS_START, BSS_END);
printf("HEAP: 0x%x -> 0x%x\n", _alloc_start, _alloc_end);
}
/*
* Allocate a memory block which is composed of contiguous physical pages
* - npages: the number of PAGE_SIZE pages to allocate
*/
void *page_alloc(int npages)
{
/* Note we are searching the page descriptor bitmaps. */
int found = 0;
struct Page *page_i = (struct Page *)HEAP_START;
for (int i = 0; i < (_num_pages - npages); i++) {
if (_is_free(page_i)) {
found = 1;
/*
* meet a free page, continue to check if following
* (npages - 1) pages are also unallocated.
*/
struct Page *page_j = page_i;
for (int j = i; j < (i + npages); j++) {
if (!_is_free(page_j)) {
found = 0;
break;
}
page_j++;
}
/*
* get a memory block which is good enough for us,
* take housekeeping, then return the actual start
* address of the first page of this memory block
*/
if (found) {
struct Page *page_k = page_i;
for (int k = i; k < (i + npages); k++) {
_set_flag(page_k, PAGE_TAKEN);
page_k++;
}
page_k--;
_set_flag(page_k, PAGE_LAST);
return (void *)(_alloc_start + i * PAGE_SIZE);
}
}
page_i++;
}
return NULL;
}
/*
* Free the memory block
* - p: start address of the memory block
*/
void page_free(void *p)
{
/*
* Assert (TBD) if p is invalid
*/
if (!p || (uint32_t)p >= _alloc_end) {
return;
}
/* get the first page descriptor of this memory block */
struct Page *page = (struct Page *)HEAP_START;
page += ((uint32_t)p - _alloc_start)/ PAGE_SIZE;
/* loop and clear all the page descriptors of the memory block */
while (!_is_free(page)) {
if (_is_last(page)) {
_clear(page);
break;
} else {
_clear(page);
page++;;
}
}
}
void page_test()
{
void *p = page_alloc(2);
printf("p = 0x%x\n", p);
//page_free(p);
void *p2 = page_alloc(7);
printf("p2 = 0x%x\n", p2);
page_free(p2);
void *p3 = page_alloc(4);
printf("p3 = 0x%x\n", p3);
}

View File

@@ -0,0 +1,29 @@
#ifndef __PLATFORM_H__
#define __PLATFORM_H__
/*
* QEMU RISC-V Virt machine with 16550a UART and VirtIO MMIO
*/
/*
* maximum number of CPUs
* see https://github.com/qemu/qemu/blob/master/include/hw/riscv/virt.h
* #define VIRT_CPUS_MAX 8
*/
#define MAXNUM_CPU 8
/*
* MemoryMap
* see https://github.com/qemu/qemu/blob/master/hw/riscv/virt.c, virt_memmap[]
* 0x00001000 -- boot ROM, provided by qemu
* 0x02000000 -- CLINT
* 0x0C000000 -- PLIC
* 0x10000000 -- UART0
* 0x10001000 -- virtio disk
* 0x80000000 -- boot ROM jumps here in machine mode, where we load our kernel
*/
/* This machine puts UART registers here in physical memory. */
#define UART0 0x10000000L
#endif /* __PLATFORM_H__ */

View File

@@ -0,0 +1,138 @@
#include "os.h"
/*
* ref: https://github.com/cccriscv/mini-riscv-os/blob/master/05-Preemptive/lib.c
*/
static int _vsnprintf(char * out, size_t n, const char* s, va_list vl)
{
int format = 0;
int longarg = 0;
size_t pos = 0;
for (; *s; s++) {
if (format) {
switch(*s) {
case 'l': {
longarg = 1;
break;
}
case 'p': {
longarg = 1;
if (out && pos < n) {
out[pos] = '0';
}
pos++;
if (out && pos < n) {
out[pos] = 'x';
}
pos++;
}
case 'x': {
long num = longarg ? va_arg(vl, long) : va_arg(vl, int);
int hexdigits = 2*(longarg ? sizeof(long) : sizeof(int))-1;
for(int i = hexdigits; i >= 0; i--) {
int d = (num >> (4*i)) & 0xF;
if (out && pos < n) {
out[pos] = (d < 10 ? '0'+d : 'a'+d-10);
}
pos++;
}
longarg = 0;
format = 0;
break;
}
case 'd': {
long num = longarg ? va_arg(vl, long) : va_arg(vl, int);
if (num < 0) {
num = -num;
if (out && pos < n) {
out[pos] = '-';
}
pos++;
}
long digits = 1;
for (long nn = num; nn /= 10; digits++);
for (int i = digits-1; i >= 0; i--) {
if (out && pos + i < n) {
out[pos + i] = '0' + (num % 10);
}
num /= 10;
}
pos += digits;
longarg = 0;
format = 0;
break;
}
case 's': {
const char* s2 = va_arg(vl, const char*);
while (*s2) {
if (out && pos < n) {
out[pos] = *s2;
}
pos++;
s2++;
}
longarg = 0;
format = 0;
break;
}
case 'c': {
if (out && pos < n) {
out[pos] = (char)va_arg(vl,int);
}
pos++;
longarg = 0;
format = 0;
break;
}
default:
break;
}
} else if (*s == '%') {
format = 1;
} else {
if (out && pos < n) {
out[pos] = *s;
}
pos++;
}
}
if (out && pos < n) {
out[pos] = 0;
} else if (out && n) {
out[n-1] = 0;
}
return pos;
}
static char out_buf[1000]; // buffer for _vprintf()
static int _vprintf(const char* s, va_list vl)
{
int res = _vsnprintf(NULL, -1, s, vl);
if (res+1 >= sizeof(out_buf)) {
uart_puts("error: output string size overflow\n");
while(1) {}
}
_vsnprintf(out_buf, res + 1, s, vl);
uart_puts(out_buf);
return res;
}
int printf(const char* s, ...)
{
int res = 0;
va_list vl;
va_start(vl, s);
res = _vprintf(s, vl);
va_end(vl);
return res;
}
void panic(char *s)
{
printf("panic: ");
printf(s);
printf("\n");
while(1){};
}

View File

@@ -0,0 +1,48 @@
#include "os.h"
/* defined in entry.S */
extern void switch_to(struct context *next);
#define STACK_SIZE 1024
uint8_t task_stack[STACK_SIZE];
struct context ctx_task;
static void w_mscratch(reg_t x)
{
asm volatile("csrw mscratch, %0" : : "r" (x));
}
void user_task0(void);
void sched_init()
{
w_mscratch(0);
ctx_task.sp = (reg_t) &task_stack[STACK_SIZE - 1];
ctx_task.ra = (reg_t) user_task0;
}
void schedule()
{
struct context *next = &ctx_task;
switch_to(next);
}
/*
* a very rough implementaion, just to consume the cpu
*/
void task_delay(volatile int count)
{
count *= 50000;
while (count--);
}
void user_task0(void)
{
uart_puts("Task 0: Created!\n");
while (1) {
uart_puts("Task 0: Running...\n");
task_delay(1000);
}
}

View File

@@ -0,0 +1,41 @@
#include "platform.h"
.equ STACK_SIZE, 8192
.global _start
.text
_start:
# park harts with id != 0
csrr t0, mhartid # read current hart id
mv tp, t0 # keep CPU's hartid in its tp for later usage.
bnez t0, park # if we're not on the hart 0
# we park the hart
# Set all bytes in the BSS section to zero.
la a0, _bss_start
la a1, _bss_end
bgeu a0, a1, 2f
1:
sw zero, (a0)
addi a0, a0, 4
bltu a0, a1, 1b
2:
# Setup stacks, the stack grows from bottom to top, so we put the
# stack pointer to the very end of the stack range.
slli t0, t0, 10 # shift left the hart id by 1024
la sp, stacks + STACK_SIZE # set the initial stack pointer
# to the end of the stack space
add sp, sp, t0 # move the current hart stack pointer
# to its place in the stack space
j start_kernel # hart 0 jump to c
park:
wfi
j park
stacks:
.skip STACK_SIZE * MAXNUM_CPU # allocate space for the harts stacks
.end # End of file

View File

@@ -0,0 +1,14 @@
#ifndef __TYPES_H__
#define __TYPES_H__
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
/*
* RISCV32: register is 32bits width
*/
typedef uint32_t reg_t;
#endif /* __TYPES_H__ */

View File

@@ -0,0 +1,119 @@
#include "os.h"
/*
* The UART control registers are memory-mapped at address UART0.
* This macro returns the address of one of the registers.
*/
#define UART_REG(reg) ((volatile uint8_t *)(UART0 + reg))
/*
* Reference
* [1]: TECHNICAL DATA ON 16550, http://byterunner.com/16550.html
*/
/*
* UART control registers map. see [1] "PROGRAMMING TABLE"
* note some are reused by multiple functions
* 0 (write mode): THR/DLL
* 1 (write mode): IER/DLM
*/
#define RHR 0 // Receive Holding Register (read mode)
#define THR 0 // Transmit Holding Register (write mode)
#define DLL 0 // LSB of Divisor Latch (write mode)
#define IER 1 // Interrupt Enable Register (write mode)
#define DLM 1 // MSB of Divisor Latch (write mode)
#define FCR 2 // FIFO Control Register (write mode)
#define ISR 2 // Interrupt Status Register (read mode)
#define LCR 3 // Line Control Register
#define MCR 4 // Modem Control Register
#define LSR 5 // Line Status Register
#define MSR 6 // Modem Status Register
#define SPR 7 // ScratchPad Register
/*
* POWER UP DEFAULTS
* IER = 0: TX/RX holding register interrupts are bith disabled
* ISR = 1: no interrupt penting
* LCR = 0
* MCR = 0
* LSR = 60 HEX
* MSR = BITS 0-3 = 0, BITS 4-7 = inputs
* FCR = 0
* TX = High
* OP1 = High
* OP2 = High
* RTS = High
* DTR = High
* RXRDY = High
* TXRDY = Low
* INT = Low
*/
/*
* LINE STATUS REGISTER (LSR)
* LSR BIT 0:
* 0 = no data in receive holding register or FIFO.
* 1 = data has been receive and saved in the receive holding register or FIFO.
* ......
* LSR BIT 5:
* 0 = transmit holding register is full. 16550 will not accept any data for transmission.
* 1 = transmitter hold register (or FIFO) is empty. CPU can load the next character.
* ......
*/
#define LSR_RX_READY (1 << 0)
#define LSR_TX_IDLE (1 << 5)
#define uart_read_reg(reg) (*(UART_REG(reg)))
#define uart_write_reg(reg, v) (*(UART_REG(reg)) = (v))
void uart_init()
{
/* disable interrupts. */
uart_write_reg(IER, 0x00);
/*
* Setting baud rate. Just a demo here if we care about the divisor,
* but for our purpose [QEMU-virt], this doesn't really do anything.
*
* Notice that the divisor register DLL (divisor latch least) and DLM (divisor
* latch most) have the same base address as the receiver/transmitter and the
* interrupt enable register. To change what the base address points to, we
* open the "divisor latch" by writing 1 into the Divisor Latch Access Bit
* (DLAB), which is bit index 7 of the Line Control Register (LCR).
*
* Regarding the baud rate value, see [1] "BAUD RATE GENERATOR PROGRAMMING TABLE".
* We use 38.4K when 1.8432 MHZ crystal, so the corresponding value is 3.
* And due to the divisor register is two bytes (16 bits), so we need to
* split the value of 3(0x0003) into two bytes, DLL stores the low byte,
* DLM stores the high byte.
*/
uint8_t lcr = uart_read_reg(LCR);
uart_write_reg(LCR, lcr | (1 << 7));
uart_write_reg(DLL, 0x03);
uart_write_reg(DLM, 0x00);
/*
* Continue setting the asynchronous data communication format.
* - number of the word length: 8 bits
* - number of stop bits1 bit when word length is 8 bits
* - no parity
* - no break control
* - disabled baud latch
*/
lcr = 0;
uart_write_reg(LCR, lcr | (3 << 0));
}
int uart_putc(char ch)
{
while ((uart_read_reg(LSR) & LSR_TX_IDLE) == 0);
return uart_write_reg(THR, ch);
}
void uart_puts(char *s)
{
while (*s) {
uart_putc(*s++);
}
}