commit 8f27b1a5764f00ad0ec5437bdfd6dad25b8bc054 Author: Alessandro Mauri Date: Thu Jun 8 13:14:16 2023 +0200 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a6d6ddf --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +**/.cross_build +cross +*.dtb +*.dts \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e2040bf --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# ROVO + +An hobby operating system for RISC-V platforms \ No newline at end of file diff --git a/ROADMAP b/ROADMAP new file mode 100644 index 0000000..57fc473 --- /dev/null +++ b/ROADMAP @@ -0,0 +1,6 @@ +[ ] parse the dtb + [ ] Fin the name of the board + [ ] Find out how many cores the cpu has + [ ] Find where the ram starts and how much memory is available + [ ] Find the UART address +[ ] Print the previous information to the serial console \ No newline at end of file diff --git a/build_cross_toolchain.sh b/build_cross_toolchain.sh new file mode 100755 index 0000000..1c9b48f --- /dev/null +++ b/build_cross_toolchain.sh @@ -0,0 +1,283 @@ +#!/bin/sh + + +BUILD_GCC=true +BUILD_BINUTILS=true +BUILD_GDB=true +START_SHELL=false + +PROJECT_ROOT="$(pwd)" +CROSS_ROOT="$PROJECT_ROOT/cross" +PREFIX="$CROSS_ROOT" +CROSS_BUILD_DIR="$PROJECT_ROOT/.cross_build" + +TARGET='' +CPUS=1 + +# Get the target arch +if [ -z "$ARCH" ]; then + ARCH="$1" + shift + if [ -z "$ARCH" ]; then + echo "Must define an arch" + exit 1 + fi +fi + +case "$ARCH" in + 'x86_64-elf'|'x86_64'|'x64'|'amd64') + TARGET='x86_64-elf' + CONFIG_EXTRA_GCC="$CONFIG_EXTRA_GCC --enable-multilib" + ;; + 'i686-elf'|'i386'|'x32'|'386') + TARGET='i686-elf' + CONFIG_EXTRA_GCC="$CONFIG_EXTRA_GCC --enable-multilib" + ;; + 'riscv-64-elf'|'rv64'|'riscv64') + TARGET='riscv-64-elf' + ;; + *) + echo "arch=$ARCH not implemented" + exit 1 + ;; +esac + +# Get the build target +while [ "${1:-}" != "" ]; do + case "$1" in + "--no-gcc") + BUILD_GCC=false + ;; + "--no-binutils") + BUILD_BINUTILS=false + ;; + "--no-gdb") + BUILD_GDB=false + ;; + "--start-shell") + START_SHELL=true + BUILD_GCC=false + BUILD_BINUTILS=false + BUILD_GDB=false + ;; + esac + shift +done + +# TODO: add some more options to speed up the build / disable unnecessary features +# https://gitlab.archlinux.org/archlinux/packaging/packages/riscv64-linux-gnu-binutils/-/blob/main/PKGBUILD +CONFIG_EXTRA_BINUTILS="\ + --without-zstd \ + --with-sysroot \ + --enable-languages=c \ + --disable-nls \ + --disable-werror" + +# TODO: add some more options to speed up the build / disable unnecessary features +# https://gitlab.archlinux.org/archlinux/packaging/packages/riscv64-linux-gnu-gcc/-/blob/main/PKGBUILD +CONFIG_EXTRA_GCC="\ + --without-zstd \ + --disable-nls \ + --enable-languages=c \ + --without-headers" + +CONFIG_EXTRA_GDB="\ + --with-sysroot \ + --disable-nls \ + --disable-werror \ + --without-headers \ + --enable-languages=c \ + --enable-tui=yes \ + --with-expat" + +#CONFIG_HOST="$(gcc -dumpmachine)" + +FTP_MIRROR="https://ftpmirror.gnu.org" + +gdb_version='13.1' +gcc_version='12.2.0' +binutils_version='2.37' + +gcc_url="$FTP_MIRROR/gcc/gcc-$gcc_version" +binutils_url="$FTP_MIRROR/binutils" +gdb_url="$FTP_MIRROR/gdb" + +gdb_build_log=gdb-log-"$(date +%F_%H-%M)".log +gcc_build_log=gcc-log-"$(date +%F_%H-%M)".log +binutils_build_log=binutils-log-"$(date +%F_%H-%M)".log + +# check if the user wants a particular version +if [ -n "$GDB_VERSION" ]; then + gdb_version="$GDB_VERSION" +fi + +# sanity check +if [ -z "$TARGET" ]; then + echo "Target not specified, how'd you get this far?" + exit 1 +fi + +# get the right number of cpus +# and some extra fixes +case $(uname) in + 'OpenBSD') + CPUS="$(sysctl -n hw.ncpu)" + CPUS=$((CPUS + 1)) + CONFIG_EXTRA_GCC="$CONFIG_EXTRA_GCC --with-gmp=/usr/local" + # use GNU make + alias make=gmake + ;; + 'Linux') + CPUS="$(nproc)" + CPUS=$((CPUS + 1)) + ;; + *) + ;; +esac + + +gpg_verify() { + keyring="$1" + pkg="$2" + sig="$3" + gpg --verify --keyring "$keyring" "$sig" "$pkg" + return $? +} + + +download_sources() { + pkg="$1" + sig="$2" + url="$3" + wget -nv -O "$pkg" "$url/$pkg" || return $? + wget -nv -O "$sig" "$url/$sig" || return $? +} + + +download_and_build() { + + pkg_name="$1" + pkg_version="$2" + pkg_config="$3" + pkg_url="$4" + pkg_ark="$pkg_name-$pkg_version.tar.xz" + pkg_sig="$pkg_name-$pkg_version.tar.xz.sig" + + # Get the archives and signature if they are not already present + if ! [ -e "$pkg_ark" ] || ! [ -e "$pkg_sig" ]; then + download_sources "$pkg_ark" "$pkg_sig" "$pkg_url" || return 1 + fi + + # Verify the archive + if ! gpg_verify ./gnu-keyring.gpg "$pkg_ark" "$pkg_sig"; then + echo "Package signature was incorrect, please remove the donwloaded sources and try again" + return 1 + fi + + # Extract the archive + xz -k -d "$pkg_ark" + tar -xf "$pkg_name-$pkg_version.tar" && rm -f "$pkg_name-$pkg_version.tar" + + # FIXME: this forces a comlete rebuild + #if [ -d "build-$pkg_name-$TARGET" ]; then + # rm -rf "build-$pkg_name-$TARGET" + #fi + mkdir -p "build-$pkg_name-$TARGET" + cd "build-$pkg_name-$TARGET" || exit 1 + + oldpath="$PATH" + export PATH="$PREFIX/bin:$PATH" +# shellcheck disable=SC2086 + ../"$pkg_name-$pkg_version"/configure \ + --target="$TARGET" \ + $pkg_config \ + --prefix="$PREFIX" || return 1 + + case "$pkg_name" in + 'gcc') + make all-gcc || return 1 + make all-target-libgcc || return 1 + make install-gcc || return 1 + make install-target-libgcc || return 1 + ;; + 'binutils') + make -j$CPUS || return 1 + make install || return 1 + ;; + 'gdb') + make all-gdb || return 1 + make install-gdb || return 1 + ;; + esac + + export PATH="$oldpath" + cd .. + return 0 +} + + +#set -x + +mkdir -p "$PREFIX" +mkdir -p "$CROSS_BUILD_DIR/$TARGET" + +cd "$CROSS_BUILD_DIR/$TARGET" || exit 1 + +# get the gnu keyring, used to verify the archives +if [ $BUILD_GCC = true ] || [ $BUILD_BINUTILS = true ] || [ $BUILD_GDB = true ]; then + if ! [ -e gnu-keyring.gpg ]; then + wget -O gnu-keyring.gpg https://ftp.gnu.org/gnu/gnu-keyring.gpg + fi +fi + +if [ $BUILD_BINUTILS = true ]; then + echo "### BUILDING BINUTILS ###" + + download_and_build binutils "$binutils_version" "$CONFIG_EXTRA_BINUTILS" \ + "$binutils_url" > ../"$binutils_build_log" 2>&1 + res=$? + + if ! [ $res -eq 0 ]; then + echo "Error building binutils, check $CROSS_BUILD_DIR/$binutils_build_log for more details" + exit 1 + fi +fi + + +if [ $BUILD_GCC = true ]; then + echo "### BUILDING GCC ###" + + download_and_build gcc "$gcc_version" "$CONFIG_EXTRA_GCC" \ + "$gcc_url" > ../"$gcc_build_log" 2>&1 + res=$? + + if ! [ $res -eq 0 ]; then + echo "Error building gcc, check $CROSS_BUILD_DIR/$gcc_build_log for more details" + exit 1 + fi +fi + + +if [ $BUILD_GDB = true ]; then + echo "### BUILDING GDB ###" + + download_and_build gdb "$gdb_version" "$CONFIG_EXTRA_GDB" \ + "$gdb_url" > ../"$gdb_build_log" 2>&1 + res=$? + + if ! [ $res -eq 0 ]; then + echo "Error building gdb, check $CROSS_BUILD_DIR/$gdb_build_log for more details" + exit 1 + fi +fi + +cd "$PROJECT_ROOT" || exit 1 + +if [ $START_SHELL = true ]; then + PATH="$CROSS_ROOT/bin:$PATH" + export PATH TARGET ARCH + echo "### Starting $SHELL with correct PATH and TARGET ###" + exec "$SHELL" || exit 1 +fi + +# TODO: validate that all things are where they should diff --git a/kernel/-kernel b/kernel/-kernel new file mode 100644 index 0000000..e69de29 diff --git a/kernel/.gitignore b/kernel/.gitignore new file mode 100644 index 0000000..e134a4e --- /dev/null +++ b/kernel/.gitignore @@ -0,0 +1,4 @@ +**/*.o +*.img +*.elf +compile_commands.json \ No newline at end of file diff --git a/kernel/Makefile b/kernel/Makefile new file mode 100644 index 0000000..ad66788 --- /dev/null +++ b/kernel/Makefile @@ -0,0 +1,39 @@ +CC = $(TARGET)-gcc +LD = $(TARGET)-ld +AS = $(TARGET)-gcc + +KERN_WARNINGS = -Wall -Wextra -Wpedantic -Wuninitialized -Wunused-result +KERN_FLAGS = -ffreestanding -nostartfiles -nostdlib -nodefaultlibs --std=gnu2x +MACHINE_FLAGS = -march=rv64gc -mabi=lp64 -mcmodel=medany +PATH_FLAGS = -I./ + +CFLAGS = ${MACHINE_FLAGS} -g -O0 ${KERN_WARNINGS} ${KERN_FLAGS} ${PATH_FLAGS} +LDFLAGS = -m elf64lriscv --gc-sections -T rv64-sifive_u.ld +ASFLAGS = ${CFLAGS} + +.SUFFIXES: .c .o .S + +.c.o: + ${CC} ${CFLAGS} -o $@ -c $< + +.S.o: + ${AS} ${ASFLAGS} -o $@ -c $< + +kernel.elf: kernel.o crt0.o platform/FU740/uart.o platform/FU740/clock.o + ${LD} ${LDFLAGS} -o kernel.elf kernel.o crt0.o platform/FU740/uart.o platform/FU740/clock.o + +kernel.o: kernel.c + +platform/FU740/uart.o: platform/FU740/uart.c + +platform/FU740/clock.o: platform/FU740/clock.c + +crt0.o: crt0.S + +.PHONY: all test clean +all: kernel.img + +test: + +clean: + rm -f kernel.elf kernel.o crt0.o platform/FU740/uart.o platform/FU740/clock.o diff --git a/kernel/clock.h b/kernel/clock.h new file mode 100644 index 0000000..bdb8d3e --- /dev/null +++ b/kernel/clock.h @@ -0,0 +1,190 @@ +#ifndef _PLATFORM_CLOCK_H +#define _PLATFORM_CLOCK_H + + +#include +#include + +/* Clocking and reset is managed by the PRCI (Power Reset Clocking Interrupt) + * block (Figure 24) [77] + * + * FU740-C000 generates all internal clocks from 26 MHz hfclk driven from an + * external oscillator (HFCLKIN) or crystal (HFOSCIN) input, selected by input + * HFXSEL. All harts operate in a single clock domain (coreclk) supplied by + * either corepll or dvfscorepll, which can be selected using the corepllsel + * register. These PLLs step 26 MHz hfclk up to higher frequencies. + * The recommended frequency of coreclk is 1.0GHz, however operation at up to + * 1.5GHz is possible. + * tlclk is a divided version of the coreclk and generates the clock for the L2 + * cache. + * The hfpclkpll generates the clock for peripherals such as SPI, UART, GPIO, + * I2C, and PWM. + * dvfs_core_pll enables the user to change the CPU frequency without dropping + * down to the lower frequency hfclk. + * The DDR, Ethernet and PCIe Subsystems operate asynchronously. + * The PRCI contains two dedicated PLLs used to step 26 MHz hfclk up to the DDR + * and Ethernet operating frequencies. + * The PCIe Subsystem contains its own clock generation. + * The PRCI contains memory-mapped registers that control the clock selection + * and configuration of the PLLs. + * On power-on, the default PRCI register settings start the harts running + * directly from hfclk. + * All additional clock management, for instance initializing the DDR PLL or + * stepping the coreclk frequency, is performed through software reads and + * writes to the memory-mapped PRCI control registers. + * The CPU real time clock (rtcclk) runs at 1 MHz and is driven from input pin + * RTCCLKIN. This should be connected to an external oscillator. + * JTAG debug logic runs off of JTAG TCK as described in Chapter 26. + * + * Reset + * The FU740-C000 has two external reset pins. + * PORESET_N is an asynchonous active low power-on reset that should be + * connected to an external power sequencing/supervisory circuit. ERESET_N is + * an asynchonous active low reset that can be connected to a reset button. + * There is internal debounce and stretch logic. The PRCI also contains hardware + * to generate internal synchronous resets for coreclk, tlclk, and hfpclk + * domains and handle reset to and from the debug module. Resets for the DDR, + * Eth- ernet and PCIE Subsystems are performed through software reads and + * writes to memory-mapped PRCI control registers. These registers are outlined + * in Table 34 below. + */ + +/* Table 21: PRCI Memory Map + * Offset Name Description + * 0x00 hfxosccfg Crystal Oscillator Configuration and Status + * 0x04 core_pllcfg PLL Configuration and Status + * 0x08 core_plloutdiv PLL Final Divide Configuration + * 0x0C ddr_pllcfg PLL Configuration and Status + * 0x10 ddr_plloutdiv PLL Final Divide Configuration + * 0x1C gemgxl_pllcfg PLL Configuration and Status + * 0x20 gemgxl_plloutdiv PLL Final Divide Configuration + * 0x24 core_clk_sel_reg Select core clock source. 0: coreclkpll 1: external hfclk + * 0x28 devices_reset_n Software controlled resets (active low) + * 0x2C clk_mux_status Current selection of each clock mux + * 0x38 dvfs_core_pllcfg PLL Configuration and Status + * 0x3C dvfs_core_plloutdiv PLL Final Divide Configuration + * 0x40 corepllsel Select which PLL output to use for core clock. 0: corepll 1: dvfscorepll + * 0x50 hfpclk_pllcfg PLL Configuration and Status + * 0x54 hfpclk_plloutdiv PLL Final Divide Configuration + * 0x58 hfpclkpllsel Select source for Periphery Clock (pclk). 0: hfpclkpll 1: external hfclk + * 0x5C hfpclk_div_reg HFPCLK PLL divider value + * 0xE0 prci_plls Indicates presence of each PLL + */ + +#define HFCLK_FREQ_HZ 26000000L +#define PRCI_MEMORY_BLOCK 0x10000000L + + +struct S_PACKED prci_pllcfg { + uint32_t pllr:6; // [RW, 0x1] PLL R Value + uint32_t pllf:9; // [RW, 0x1F] PLL F Value + uint32_t pllq:3; // [RW, 0x3] PLL Q Value + uint32_t pllrange:3; // [RW, 0x0] PLL Range Value + uint32_t reserved0:3; + uint32_t pllbypass:1; // [RW, 0x1] PLL Bypass + uint32_t pllfsebypass:1; // [RW, 0x1] PLL FSE Bypass + uint32_t reserved1:5; + uint32_t plllock:1; // [RO, X] PLL Lock +}; + + +struct S_PACKED prci_plloutdiv { + uint32_t reserved:31; + uint32_t pllcke:1; // [RW, 0x0] PLL Clock Enable +}; + +struct S_PACKED prci_mem_map { + // Offset 0x0 + struct prci_hfxosccfg { + uint32_t reserved:30; + uint32_t hfxoscen:1; // [RW, 0x1] Crystal Oscillator Enable + uint32_t hfxoscrdy:1; // [RO, X] Crystal Oscillator Ready + } hfxosccfg; + + // Offset 0x4 + struct prci_pllcfg core_pllcfg; + + // Offset 0x8 + struct { uint32_t reserved; } core_plloutdiv; + + // Offset 0xC + struct prci_pllcfg ddr_pllcfg; + + // Offset 0x10 + struct prci_plloutdiv ddr_plloutdiv; + uint8_t off0[8]; + + // Offset 0x1C + struct prci_pllcfg gemgxl_pllcfg; + + // Offset 0x20 + struct prci_plloutdiv gemgxl_plloutdiv; + + // Offset 0x24 + uint32_t core_clk_sel_reg; + + // Offset 0x28 + struct prci_devices_reset_n { + uint32_t ddrctrl_reset_n:1; // [RW, 0x0] Active-Low ddrctrl reset + uint32_t ddraxi_reset_n:1; // [RW, 0x0] Active-Low ddraxi reset + uint32_t ddrahb_reset_n:1; // [RW, 0x0] Active-Low ddrahb reset + uint32_t ddrphy_reset_n:1; // [RW, 0x0] Active-Low ddrphy reset + uint32_t pcieaux_reset_n:1; // [RW, 0x0] Active-Low pcieaux reset + uint32_t gemgxl_reset_n:1; // [RW, 0x0] Active-Low gemgxl reset + uint32_t reserved:26; + + } devices_reset_n; + + // Offset 0x2C + struct prci_clk_mux_status { + uint32_t coreclkpllsel:1; // [RO, X] Current setting of coreclkpllsel mux + uint32_t tlclksel:1; // [RO, X] Current setting of tlclksel mux + uint32_t rtcxsel:1; // [RO, X] Current setting of rtcxsel mux + uint32_t ddrctrlclksel:1; // [RO, X] Current setting of ddrctrlclksel mux + uint32_t ddrphyclksel:1; // [RO, X] Current setting of ddrphyclksel mux + uint32_t reserved0:1; // [RO, X] Current setting of reserved0 mux + uint32_t gemgxlclksel:1; // [RO, X] Current setting of gemgxlclksel mux + uint32_t mainmemclksel:1; // [RO, X] Current setting of mainmemclksel mux + uint32_t reserved:24; + } clk_mux_status; + uint8_t off1[8]; + + // Offset 0x38 + struct prci_pllcfg dvfs_core_pllcfg; + + // Offset 0x3C + struct prci_plloutdiv dvfs_core_plloutdiv; + + // Offset 0x40 + uint32_t corepllsel; + uint8_t off2[12]; + + // Offset 0x50 + struct prci_pllcfg hfpclk_pllcfg; + + // Offset 0x54 + struct prci_plloutdiv hfpclk_plloutdiv; + + // Offset 0x58 + uint32_t hfpclkpllsel; + + // Offset 0x5C + uint32_t hfpclk_div_reg; // [RW, 0x0] HFPCLK PLL divider value + uint8_t off3[128]; + + // Offset 0xE0 + struct prci_prci_plls { + uint32_t cltxpll:1; // [RO, X] Indicates presence of cltxpll + uint32_t gemgxlpll:1; // [RO, X] Indicates presence of gemgxlpll + uint32_t ddrpll:1; // [RO, X] Indicates presence of ddrpll + uint32_t hfpclkpll:1; // [RO, X] Indicates presence of hfpclkpll + uint32_t dvfscorepll:1; // [RO, X] Indicates presence of dvfscorepll + uint32_t corepll:1; // [RO, X] Indicates presence of corepll + uint32_t reserved:26; + } prci_plls; +}; +static_assert((sizeof(struct prci_mem_map) == 0xE4), + "prci_mem_map is not the right size"); + + +#endif \ No newline at end of file diff --git a/kernel/crt0.S b/kernel/crt0.S new file mode 100644 index 0000000..bb4277a --- /dev/null +++ b/kernel/crt0.S @@ -0,0 +1,27 @@ +# C runtime, modified from +# https://twilco.github.io/riscv-from-scratch/2019/04/27/riscv-from-scratch-2.html#stop--hammertime-runtime + +# in the init section which is "allocatable" and "executable" +.section .init, "ax" + +# entry point for the kernel +.global _start +_start: +.cfi_startproc +.cfi_undefined ra + # initialize the global pointer register + .option push + .option norelax + la gp, __global_pointer$ + .option pop + # initialize the stack pointer and frame pointer registers + # FIXME: when putting the stack at the end of RAM with system memory >2G + # it fails to link with the error: relocation truncated to fit: + # R_RISCV_HI20 against '__stack_top' + la sp, __stack_top + add s0, sp, zero + # jump to main + jal zero, main +.cfi_endproc + +.end \ No newline at end of file diff --git a/kernel/kernel b/kernel/kernel new file mode 100755 index 0000000..189f928 Binary files /dev/null and b/kernel/kernel differ diff --git a/kernel/kernel.c b/kernel/kernel.c new file mode 100644 index 0000000..8223d71 --- /dev/null +++ b/kernel/kernel.c @@ -0,0 +1,12 @@ +#include "macro.h" +#include "platform/FU740/uart.h" + +int F_NAKED main(void) +{ + (void)uart_init(UART0, DEFAULT_BAUD, 0); + + static char str[] = "Hello RISC-V!\n"; + uart_tx_buf(UART0, str, sizeof(str)); + + while(1) asm volatile("nop"); // we should not be here +} diff --git a/kernel/macro.h b/kernel/macro.h new file mode 100644 index 0000000..edb7405 --- /dev/null +++ b/kernel/macro.h @@ -0,0 +1,10 @@ +#ifndef _COMMON_MACROS_H +#define _COMMON_MACROS_H + +#define F_NAKED __attribute__((noreturn)) +#define F_INTERRUPT __attribute__((interrupt)) +#define S_PACKED __attribute__((packed)) + +#define static_assert _Static_assert + +#endif diff --git a/kernel/platform/FU740/clock.c b/kernel/platform/FU740/clock.c new file mode 100644 index 0000000..865474a --- /dev/null +++ b/kernel/platform/FU740/clock.c @@ -0,0 +1,26 @@ +#include + +#include "clock.h" + + +static struct prci_mem_map *const prci_mm = (struct prci_mem_map *const)PRCI_MEMORY_BLOCK; + + +// Sets the hfpclkpll register to the appropriate value for the freqency, returns +// -1 on error +// TODO: for higher frequencies use the internal PLLs +int set_hfp_frequency(uint32_t f) +{ + // test if the correct register is present + if (!prci_mm->prci_plls.hfpclkpll) + return -1; + + // make sure that the clock source is set to external + prci_mm->hfpclkpllsel = 1; + + // the external clock source is 26MHz, the formula for the baud rate is: + // f_baud = f_hfclk / (1 + hfpclk_div_reg) + prci_mm->hfpclk_div_reg = (HFCLK_FREQ_HZ/f + 1); + + return 0; +} diff --git a/kernel/platform/FU740/doc/FU740_errata_20210205.pdf b/kernel/platform/FU740/doc/FU740_errata_20210205.pdf new file mode 100644 index 0000000..a98a988 Binary files /dev/null and b/kernel/platform/FU740/doc/FU740_errata_20210205.pdf differ diff --git a/kernel/platform/FU740/doc/fu740-c000-manual-v1p6.pdf b/kernel/platform/FU740/doc/fu740-c000-manual-v1p6.pdf new file mode 100644 index 0000000..f246874 Binary files /dev/null and b/kernel/platform/FU740/doc/fu740-c000-manual-v1p6.pdf differ diff --git a/kernel/platform/FU740/uart.c b/kernel/platform/FU740/uart.c new file mode 100644 index 0000000..9990584 --- /dev/null +++ b/kernel/platform/FU740/uart.c @@ -0,0 +1,62 @@ +#include + +#include "uart.h" + + +static struct uart_memory_map *const uart_mm[] = { + (struct uart_memory_map *const)UART_0_ADDR, + (struct uart_memory_map *const)UART_1_ADDR, +}; + + +// set the baudrate on the chosen uart, if the baud rate is incorrect or invalid +// then set it to the default value, return the set baudrate value +int uart_set_baudrate(uart_id id, int baud) +{ + // FIXME: actually set the baud rate, for now leave it as the default + (void)baud; + (void)id; + return 115200; +} + + +// initialize the uart registers for transmit and receive +// TODO: also initialize interrupts and thesholds +int uart_init(uart_id id, uint32_t baud, int stop_bits) +{ + // FIXME: check baud rate error and propagate it + (void)uart_set_baudrate(id, baud); + + // change the number of stop bits only if stop_bits > 0 + if (stop_bits > 0) + uart_mm[id]->txctrl.nstop = !!stop_bits; + + uart_mm[id]->txctrl.txen = 1; + uart_mm[id]->rxctrl.rxen = 1; + + return 0; +} + + +// transmit a byte of data through a uart line +void uart_tx_byte(uart_id id, unsigned char b) +{ + uart_mm[id]->txdata.data = b; +} + + +// busy-wait until the uart buffer is cleared +void uart_tx_full(uart_id id) +{ + while (uart_mm[id]->txdata.full); +} + + +// send a buffer through uart +void uart_tx_buf(uart_id id, unsigned char *buf, unsigned int size) +{ + for (unsigned int i = 0; i < size; i++) { + uart_tx_byte(id, buf[i]); + uart_tx_full(id); + } +} diff --git a/kernel/platform/FU740/uart.h b/kernel/platform/FU740/uart.h new file mode 100644 index 0000000..16a2c9a --- /dev/null +++ b/kernel/platform/FU740/uart.h @@ -0,0 +1,170 @@ +#ifndef _PLATFORM_UART_H +#define _PLATFORM_UART_H + + +#include +#include + + +// <[Doc Page]> + +// UART Base Addesses [141] +#define UART_0_ADDR 0x10010000L +#define UART_1_ADDR 0x10010000L + +// UART Control Register Offsets [142] +#define UART_TXDATA_OFF 0x00 // Transmit data register +#define UART_RXDATA_OFF 0x04 // Receive data register +#define UART_TXCTRL_OFF 0x08 // Transmit control register +#define UART_RXCTRL_OFF 0x0C // Receive control register +#define UART_IE_OFF 0x10 // UART interrupt enable +#define UART_IP_OFF 0x14 // UART interrupt pending +#define UART_DIV_OFF 0x18 // Baud rate divisor + +#define UART_NUMBER 2 +#define DEFAULT_BAUD 115200 + + +// UART ids +typedef enum { + UART0 = 0, + UART1 = 1, +} uart_id; + + +/* Transmit Data Register (txdata) [142] + * Writing to the txdata register enqueues the character contained in the data + * field to the transmit FIFO if the FIFO is able to accept new entries. Reading + * from txdata returns the current value of the full flag and zero in the data + * field. The full flag indicates whether the transmit FIFO is able to accept + * new entries; when set, writes to data are ignored. A RISC‐V amoor.w + * instruction can be used to both read the full status and attempt to enqueue + * data, with a non-zero return value indicating the character was not accepted. + */ +struct S_PACKED uart_txdata_reg { + uint32_t data:8; + uint32_t reserved:23; + uint32_t full:1; +}; +static_assert((sizeof(struct uart_txdata_reg) == sizeof(uint32_t)), + "uart_txdata_reg is not the right size"); + +/* Receive Data Register (rxdata) [143] + * Reading the rxdata register dequeues a character from the receive FIFO and + * returns the value in the data field. The empty flag indicates if the receive + * FIFO was empty; when set, the data field does not contain a valid character. + * Writes to rxdata are ignored. + */ +struct S_PACKED uart_rxdata_reg { + uint32_t data:8; + uint32_t reserved:23; + uint32_t empty:1; +}; +static_assert((sizeof(struct uart_rxdata_reg) == sizeof(uint32_t)), + "uart_rxdata_reg is not the right size"); + +/* Transmit Control Register (txctrl) [143] + * The read-write txctrl register controls the operation of the transmit channel. + * The txen bit con- trols whether the Tx channel is active. When cleared, + * transmission of Tx FIFO contents is suppressed, and the txd pin is driven + * high. The nstop field specifies the number of stop bits: 0 for one stop bit + * and 1 for two stop bits. The txcnt field specifies the threshold at which the + * Tx FIFO watermark interrupt triggers. The txctrl register is reset to 0. + */ +struct S_PACKED uart_txctrl_reg { + uint32_t txen:1; + uint32_t nstop:1; + uint32_t reserved0:14; + uint32_t txcnt:3; + uint32_t reserved1:13; +}; +static_assert((sizeof(struct uart_txctrl_reg) == sizeof(uint32_t)), + "uart_txctrl_reg is not the right size"); + +/* Receive Control Register (rxctrl) [144] + * The read-write rxctrl register controls the operation of the receive channel. + * The rxen bit controls whether the Rx channel is active. When cleared, the + * state of the rxd pin is ignored, and no characters will be enqueued into the + * Rx FIFO. The rxcnt field specifies the threshold at which the Rx FIFO + * watermark interrupt triggers. The rxctrl register is reset to 0. Characters + * are enqueued when a zero (low) start bit is seen. + */ +struct S_PACKED uart_rxctrl_reg { + uint32_t rxen:1; + uint32_t reserved0:15; + uint32_t rxcnt:3; + uint32_t reserved1:13; +}; +static_assert((sizeof(struct uart_rxctrl_reg) == sizeof(uint32_t)), + "uart_rxctrl_reg is not the right size"); + +/* Interrupt Registers (ip and ie) [144] + * The ip register is a read-only register indicating the pending interrupt + * conditions, and the read- write ie register controls which UART interrupts + * are enabled. ie is reset to 0. The txwm condition becomes raised when the + * number of entries in the transmit FIFO is strictly less than the count + * specified by the txcnt field of the txctrl register. The pending bit is + * cleared when sufficient entries have been enqueued to exceed the watermark. + * The rxwm condition becomes raised when the number of entries in the receive + * FIFO is strictly greater than the count specified by the rxcnt field of the + * rxctrl register. The pending bit is cleared when sufficient entries have been + * dequeued to fall below the watermark. + */ +struct S_PACKED uart_ie_reg { + uint32_t txwm:1; + uint32_t rxwm:1; + uint32_t reserved:30; +}; +static_assert((sizeof(struct uart_ie_reg) == sizeof(uint32_t)), + "uart_ie_reg is not the right size"); + +struct S_PACKED uart_ip_reg { + uint32_t txwm:1; + uint32_t rxwm:1; + uint32_t reserved:30; +}; +static_assert((sizeof(struct uart_ip_reg) == sizeof(uint32_t)), + "uart_ip_reg is not the right size"); + +/* Baud Rate Divisor Register (div) [145] + * The read-write, div_width-bit div register specifies the divisor used by baud + * rate generation for both Tx and Rx channels. The relationship between the + * input clock and baud rate is given by the following formula: The input clock + * is the bus clock pclk. The reset value of the register is set to div_init, + * which is tuned to provide a 115200 baud output out of reset given the + * expected frequency of pclk. Table 85 shows divisors for some common core + * clock rates and commonly used baud rates. Note that the table shows the + * divide ratios, which are one greater than the value stored in the div + * register. The receive channel is sampled at 16× the baud rate, and a majority + * vote over 3 neighboring bits is used to determine the received value. For + * this reason, the divisor must be ≥16 for a receive channel. + */ +struct S_PACKED uart_div_reg { + uint32_t div:16; + uint32_t reserved:16; +}; +static_assert((sizeof(struct uart_div_reg) == sizeof(uint32_t)), + "uart_div_reg is not the right size"); + + +struct S_PACKED uart_memory_map { + struct uart_txdata_reg txdata; + struct uart_rxdata_reg rxdata; + struct uart_txctrl_reg txctrl; + struct uart_rxctrl_reg rxctrl; + struct uart_ie_reg ie; + struct uart_ip_reg ip; + struct uart_div_reg div; +}; +static_assert((sizeof(struct uart_memory_map) == sizeof(uint32_t[7])), + "uart_memory_map is not the right size"); + + +int uart_set_baudrate(uart_id id, int baud); +int uart_init(uart_id id, uint32_t baud, int stop_bits); +void uart_tx_byte(uart_id id, unsigned char b); +void uart_tx_full(uart_id id); +void uart_tx_buf(uart_id id, unsigned char *buf, unsigned int size); + + +#endif diff --git a/kernel/rv64-sifive_u.ld b/kernel/rv64-sifive_u.ld new file mode 100644 index 0000000..a8c70d9 --- /dev/null +++ b/kernel/rv64-sifive_u.ld @@ -0,0 +1,263 @@ +OUTPUT_FORMAT("elf64-littleriscv", "elf64-littleriscv", "elf64-littleriscv") +OUTPUT_ARCH(riscv) + + +SEARCH_DIR("=/home/ale/Documents/Projects/OS/rovo/cross/riscv-64-elf/lib") +SEARCH_DIR("=/usr/local/lib") +SEARCH_DIR("=/lib") +SEARCH_DIR("=/usr/lib") + + +MEMORY +{ + /* qemu-system-risc64 sifive_u machine */ + RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 1024M +} + + +ENTRY(_start) + + +SECTIONS +{ + /* Read-only sections, merged into text segment: */ + PROVIDE (__executable_start = SEGMENT_START("text-segment", 0x10000)); + . = SEGMENT_START("text-segment", 0x10000) + SIZEOF_HEADERS; + + /* Provide the stack top positioned at the bottom of RAM */ + PROVIDE (__stack_top = ORIGIN(RAM) + LENGTH(RAM)); + + .interp : { *(.interp) } + .note.gnu.build-id : { *(.note.gnu.build-id) } + .hash : { *(.hash) } + .gnu.hash : { *(.gnu.hash) } + .dynsym : { *(.dynsym) } + .dynstr : { *(.dynstr) } + .gnu.version : { *(.gnu.version) } + .gnu.version_d : { *(.gnu.version_d) } + .gnu.version_r : { *(.gnu.version_r) } + .rela.dyn : + { + *(.rela.init) + *(.rela.text .rela.text.* .rela.gnu.linkonce.t.*) + *(.rela.fini) + *(.rela.rodata .rela.rodata.* .rela.gnu.linkonce.r.*) + *(.rela.data .rela.data.* .rela.gnu.linkonce.d.*) + *(.rela.tdata .rela.tdata.* .rela.gnu.linkonce.td.*) + *(.rela.tbss .rela.tbss.* .rela.gnu.linkonce.tb.*) + *(.rela.ctors) + *(.rela.dtors) + *(.rela.got) + *(.rela.sdata .rela.sdata.* .rela.gnu.linkonce.s.*) + *(.rela.sbss .rela.sbss.* .rela.gnu.linkonce.sb.*) + *(.rela.sdata2 .rela.sdata2.* .rela.gnu.linkonce.s2.*) + *(.rela.sbss2 .rela.sbss2.* .rela.gnu.linkonce.sb2.*) + *(.rela.bss .rela.bss.* .rela.gnu.linkonce.b.*) + *(.rela.ifunc) + } + .rela.plt : + { + *(.rela.plt) + PROVIDE_HIDDEN (__rela_iplt_start = .); + *(.rela.iplt) + PROVIDE_HIDDEN (__rela_iplt_end = .); + } + .init : + { + KEEP (*(SORT_NONE(.init))) + } + .plt : { *(.plt) *(.iplt) } + .text : + { + *(.text.unlikely .text.*_unlikely .text.unlikely.*) + *(.text.exit .text.exit.*) + *(.text.startup .text.startup.*) + *(.text.hot .text.hot.*) + *(SORT(.text.sorted.*)) + *(.text .stub .text.* .gnu.linkonce.t.*) + /* .gnu.warning sections are handled specially by elf.em. */ + *(.gnu.warning) + } + .fini : + { + KEEP (*(SORT_NONE(.fini))) + } + PROVIDE (__etext = .); + PROVIDE (_etext = .); + PROVIDE (etext = .); + .rodata : { *(.rodata .rodata.* .gnu.linkonce.r.*) } + .rodata1 : { *(.rodata1) } + .sdata2 : + { + *(.sdata2 .sdata2.* .gnu.linkonce.s2.*) + } + .sbss2 : { *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) } + .eh_frame_hdr : { *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) } + .eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } + .gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } + /* These sections are generated by the Sun/Oracle C++ compiler. */ + .exception_ranges : ONLY_IF_RO { *(.exception_ranges*) } + /* Adjust the address for the data segment. We want to adjust up to + the same address within the page on the next page up. */ + . = DATA_SEGMENT_ALIGN (CONSTANT (MAXPAGESIZE), CONSTANT (COMMONPAGESIZE)); + /* Exception handling */ + .eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } + .gnu_extab : ONLY_IF_RW { *(.gnu_extab) } + .gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } + .exception_ranges : ONLY_IF_RW { *(.exception_ranges*) } + /* Thread Local Storage sections */ + .tdata : + { + PROVIDE_HIDDEN (__tdata_start = .); + *(.tdata .tdata.* .gnu.linkonce.td.*) + } + .tbss : { *(.tbss .tbss.* .gnu.linkonce.tb.*) *(.tcommon) } + .preinit_array : + { + PROVIDE_HIDDEN (__preinit_array_start = .); + KEEP (*(.preinit_array)) + PROVIDE_HIDDEN (__preinit_array_end = .); + } + .init_array : + { + PROVIDE_HIDDEN (__init_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.init_array.*) SORT_BY_INIT_PRIORITY(.ctors.*))) + KEEP (*(.init_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .ctors)) + PROVIDE_HIDDEN (__init_array_end = .); + } + .fini_array : + { + PROVIDE_HIDDEN (__fini_array_start = .); + KEEP (*(SORT_BY_INIT_PRIORITY(.fini_array.*) SORT_BY_INIT_PRIORITY(.dtors.*))) + KEEP (*(.fini_array EXCLUDE_FILE (*crtbegin.o *crtbegin?.o *crtend.o *crtend?.o ) .dtors)) + PROVIDE_HIDDEN (__fini_array_end = .); + } + .ctors : + { + /* gcc uses crtbegin.o to find the start of + the constructors, so we make sure it is + first. Because this is a wildcard, it + doesn't matter if the user does not + actually link against crtbegin.o; the + linker won't look for a file to match a + wildcard. The wildcard also means that it + doesn't matter which directory crtbegin.o + is in. */ + KEEP (*crtbegin.o(.ctors)) + KEEP (*crtbegin?.o(.ctors)) + /* We don't want to include the .ctor section from + the crtend.o file until after the sorted ctors. + The .ctor section from the crtend file contains the + end of ctors marker and it must be last */ + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .ctors)) + KEEP (*(SORT(.ctors.*))) + KEEP (*(.ctors)) + } + .dtors : + { + KEEP (*crtbegin.o(.dtors)) + KEEP (*crtbegin?.o(.dtors)) + KEEP (*(EXCLUDE_FILE (*crtend.o *crtend?.o ) .dtors)) + KEEP (*(SORT(.dtors.*))) + KEEP (*(.dtors)) + } + .jcr : { KEEP (*(.jcr)) } + .data.rel.ro : { *(.data.rel.ro.local* .gnu.linkonce.d.rel.ro.local.*) *(.data.rel.ro .data.rel.ro.* .gnu.linkonce.d.rel.ro.*) } + .dynamic : { *(.dynamic) } + . = DATA_SEGMENT_RELRO_END (0, .); + .data : + { + __DATA_BEGIN__ = .; + *(.data .data.* .gnu.linkonce.d.*) + SORT(CONSTRUCTORS) + } + .data1 : { *(.data1) } + .got : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) } + /* We want the small data sections together, so single-instruction offsets + can access them all, and initialized data all before uninitialized, so + we can shorten the on-disk segment size. */ + .sdata : + { + __SDATA_BEGIN__ = .; + *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*) + *(.sdata .sdata.* .gnu.linkonce.s.*) + } + _edata = .; PROVIDE (edata = .); + . = .; + __bss_start = .; + .sbss : + { + *(.dynsbss) + *(.sbss .sbss.* .gnu.linkonce.sb.*) + *(.scommon) + } + .bss : + { + *(.dynbss) + *(.bss .bss.* .gnu.linkonce.b.*) + *(COMMON) + /* Align here to ensure that the .bss section occupies space up to + _end. Align after .bss to ensure correct alignment even if the + .bss section disappears because there are no input sections. + FIXME: Why do we need it? When there is no .bss section, we do not + pad the .data section. */ + . = ALIGN(. != 0 ? 64 / 8 : 1); + } + . = ALIGN(64 / 8); + . = SEGMENT_START("ldata-segment", .); + . = ALIGN(64 / 8); + __BSS_END__ = .; + __global_pointer$ = MIN(__SDATA_BEGIN__ + 0x800, + MAX(__DATA_BEGIN__ + 0x800, __BSS_END__ - 0x800)); + _end = .; PROVIDE (end = .); + . = DATA_SEGMENT_END (.); + /* Stabs debugging sections. */ + .stab 0 : { *(.stab) } + .stabstr 0 : { *(.stabstr) } + .stab.excl 0 : { *(.stab.excl) } + .stab.exclstr 0 : { *(.stab.exclstr) } + .stab.index 0 : { *(.stab.index) } + .stab.indexstr 0 : { *(.stab.indexstr) } + .comment 0 : { *(.comment) } + .gnu.build.attributes : { *(.gnu.build.attributes .gnu.build.attributes.*) } + /* DWARF debug sections. + Symbols in the DWARF debugging sections are relative to the beginning + of the section so we begin them at 0. */ + /* DWARF 1. */ + .debug 0 : { *(.debug) } + .line 0 : { *(.line) } + /* GNU DWARF 1 extensions. */ + .debug_srcinfo 0 : { *(.debug_srcinfo) } + .debug_sfnames 0 : { *(.debug_sfnames) } + /* DWARF 1.1 and DWARF 2. */ + .debug_aranges 0 : { *(.debug_aranges) } + .debug_pubnames 0 : { *(.debug_pubnames) } + /* DWARF 2. */ + .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) } + .debug_abbrev 0 : { *(.debug_abbrev) } + .debug_line 0 : { *(.debug_line .debug_line.* .debug_line_end) } + .debug_frame 0 : { *(.debug_frame) } + .debug_str 0 : { *(.debug_str) } + .debug_loc 0 : { *(.debug_loc) } + .debug_macinfo 0 : { *(.debug_macinfo) } + /* SGI/MIPS DWARF 2 extensions. */ + .debug_weaknames 0 : { *(.debug_weaknames) } + .debug_funcnames 0 : { *(.debug_funcnames) } + .debug_typenames 0 : { *(.debug_typenames) } + .debug_varnames 0 : { *(.debug_varnames) } + /* DWARF 3. */ + .debug_pubtypes 0 : { *(.debug_pubtypes) } + .debug_ranges 0 : { *(.debug_ranges) } + /* DWARF 5. */ + .debug_addr 0 : { *(.debug_addr) } + .debug_line_str 0 : { *(.debug_line_str) } + .debug_loclists 0 : { *(.debug_loclists) } + .debug_macro 0 : { *(.debug_macro) } + .debug_names 0 : { *(.debug_names) } + .debug_rnglists 0 : { *(.debug_rnglists) } + .debug_str_offsets 0 : { *(.debug_str_offsets) } + .debug_sup 0 : { *(.debug_sup) } + .gnu.attributes 0 : { KEEP (*(.gnu.attributes)) } + /DISCARD/ : { *(.note.GNU-stack) *(.gnu_debuglink) *(.gnu.lto_*) } +} diff --git a/qemu_run.sh b/qemu_run.sh new file mode 100755 index 0000000..163bc94 --- /dev/null +++ b/qemu_run.sh @@ -0,0 +1,44 @@ +#!/bin/sh + +# https://www.qemu.org/docs/master/system/target-riscv.html#board-specific-documentation +# This is similar to the JH7110 used in the PineTab-V +EMU_MACHINE='sifive_u' +EMU_MEMORY='1G' +EMU_BIOS='none' +EMU_PLATFORM="-machine $EMU_MACHINE -smp 5 -m $EMU_MEMORY" +EMU_OPTIONS="-bios $EMU_BIOS -display none -serial stdio -gdb tcp::1234" +EMU_EXTRA='' +QEMU_BIN='qemu-system-riscv64' + +kern_img='' + +while [ "${1:-}" != "" ]; do + case "$1" in + "--debug") + EMU_EXTRA='-D' + ;; + "--dump-dts"|"--dump") + # shellcheck disable=SC2086 + QEMU_CMD="$QEMU_BIN $EMU_PLATFORM $EMU_OPTIONS" + $QEMU_CMD -machine dumpdtb=riscv64-"$EMU_MACHINE".dtb + dtc riscv64-"$EMU_MACHINE".dtb > riscv64-"$EMU_MACHINE".dts + exit 0 + ;; + *) + kern_img="$1" + ;; + esac + shift +done + + +if ! [ "$kern_img" ]; then + echo "Must provide kernel binary ($kern_img)" + exit 1 +fi + +QEMU_CMD="$QEMU_BIN $EMU_PLATFORM $EMU_OPTIONS $EMU_EXTRA" + +echo "starting $QEMU_CMD" +# shellcheck disable=SC2086 +exec $QEMU_CMD -kernel "$kern_img"