From e906741ee9e8af1abc27b3c4b0a9969bd222aa5d Mon Sep 17 00:00:00 2001 From: Yi-Ting Shih Date: Tue, 11 Mar 2025 05:17:34 +0800 Subject: [PATCH] initial commit --- Makefile | 72 ++++++++++++++++++++++++ bootloader/Start.S | 56 ++++++++++++++++++ bootloader/linker.ld | 50 +++++++++++++++++ bootloader/main.c | 21 +++++++ include/gpio.h | 46 +++++++++++++++ include/initrd.h | 46 +++++++++++++++ include/kmalloc.h | 7 +++ include/mbox.h | 48 ++++++++++++++++ include/random.h | 8 +++ include/shell.h | 9 +++ include/stddef.h | 9 +++ include/string.h | 7 +++ include/uart.h | 10 ++++ kernel/Start.S | 32 +++++++++++ kernel/linker.ld | 33 +++++++++++ kernel/main.c | 12 ++++ lib/initrd.c | 88 +++++++++++++++++++++++++++++ lib/kmalloc.c | 37 ++++++++++++ lib/mbox.c | 89 +++++++++++++++++++++++++++++ lib/random.c | 12 ++++ lib/shell.c | 75 +++++++++++++++++++++++++ lib/string.c | 35 ++++++++++++ lib/uart.c | 103 ++++++++++++++++++++++++++++++++++ misc/bcm2710-rpi-3-b-plus.dtb | Bin 0 -> 35322 bytes misc/config.txt | 3 + rootfs/poop.txt | 1 + scripts/upload.py | 15 +++++ 27 files changed, 924 insertions(+) create mode 100644 Makefile create mode 100644 bootloader/Start.S create mode 100644 bootloader/linker.ld create mode 100644 bootloader/main.c create mode 100644 include/gpio.h create mode 100644 include/initrd.h create mode 100644 include/kmalloc.h create mode 100644 include/mbox.h create mode 100644 include/random.h create mode 100644 include/shell.h create mode 100644 include/stddef.h create mode 100644 include/string.h create mode 100644 include/uart.h create mode 100644 kernel/Start.S create mode 100644 kernel/linker.ld create mode 100644 kernel/main.c create mode 100644 lib/initrd.c create mode 100644 lib/kmalloc.c create mode 100644 lib/mbox.c create mode 100644 lib/random.c create mode 100644 lib/shell.c create mode 100644 lib/string.c create mode 100644 lib/uart.c create mode 100644 misc/bcm2710-rpi-3-b-plus.dtb create mode 100644 misc/config.txt create mode 100644 rootfs/poop.txt create mode 100644 scripts/upload.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ff069fe --- /dev/null +++ b/Makefile @@ -0,0 +1,72 @@ +MAKE := make + +ARCH := aarch64 +GNU := $(ARCH)-linux-gnu + +QEMU := qemu-system-$(ARCH) +QEMUFLAGS += -M raspi3b -display none -serial null -serial pty + +CC := $(GNU)-gcc +CCFLAGS += -Wall -Wextra -O0 \ + -nostdinc -nostdlib -nostartfiles -nodefaultlibs \ + -Wno-unused-parameter -Wno-main -g +LD := $(GNU)-ld +LDFLAGS += -g -nostdlib +OBJCOPY := $(GNU)-objcopy + +#TARGET := kernel8 +#TARGET_DIR := ./kernel +ELF := $(TARGET).elf +IMG := $(TARGET).img + +LD_SCRIPT := $(TARGET_DIR)/linker.ld +INCLUDE += -Iinclude +LIB_DIR := ./lib +MISC_DIR := ./misc +#QEMUFLAGS += -dtb $(MISC_DIR)/bcm2710-rpi-3-b-plus.dtb + +BOOTLOADER_DIR := ./bootloader +ROOTFS_DIR := ./rootfs + +CPIO := initramfs.cpio +QEMUFLAGS += -initrd $(CPIO) + +SRCS := $(shell find $(TARGET_DIR) -name '*.[cS]') \ + $(shell find $(LIB_DIR) -name '*.c') +OBJS := $(SRCS:%=%.o) + +.PHONY: all build clean clean_target run + +all: + $(MAKE) build TARGET_DIR=./kernel TARGET=kernel8 + $(MAKE) build TARGET_DIR=./bootloader TARGET=bootloader + +build: $(IMG) + +$(CPIO): $(shell find $(ROOTFS_DIR)) + cd $(ROOTFS_DIR) && find . | cpio -o -H newc > ../$@ + +$(IMG): $(ELF) + $(OBJCOPY) -O binary $< $@ + +$(ELF): $(LD_SCRIPT) $(OBJS) + $(LD) -o $@ -T $^ $(LDFLAGS) + +%.S.o: %.S + mkdir -p $(dir $@) + $(CC) -c $< -o $@ $(INCLUDE) $(CCFLAGS) + +%.c.o: %.c + mkdir -p $(dir $@) + $(CC) -c $< -o $@ $(INCLUDE) $(CCFLAGS) + +clean: + $(MAKE) clean_target TARGET_DIR=./kernel TARGET=kernel8 + $(MAKE) clean_target TARGET_DIR=./bootloader TARGET=bootloader + +clean_target: + -rm $(OBJS) $(ELF) $(IMG) $(CPIO) + +run: all $(CPIO) + $(QEMU) -kernel bootloader.img $(QEMUFLAGS) + #$(QEMU) -kernel kernel8.img $(QEMUFLAGS) diff --git a/bootloader/Start.S b/bootloader/Start.S new file mode 100644 index 0000000..10dfa07 --- /dev/null +++ b/bootloader/Start.S @@ -0,0 +1,56 @@ +.section ".text.boot" + +.global _start + +_start: + mrs x1, mpidr_el1 + and x1, x1, #3 + cbz x1, run + +wait: + wfe + b wait + +run: + mov x20, x0 + adr x0, _start + mov sp, x0 + + adr x0, __text_start + ldr x1, =__new_text_start + cmp x0, x1 + b.gt relocate + + adr x0, __bss_start + adr x1, __bss_end + bl memzero + + mov x0, x20 + bl main + b wait + +relocate: + // move text section + ldr x0, =__new_text_start + adr x1, __text_start + adr x2, __text_end + sub x2, x2, x1 + bl memcpy + mov x19, x0 + + // move rodata section + ldr x0, =__new_ro_start + adr x1, __rodata_start + adr x2, __rodata_end + sub x2, x2, x1 + bl memcpy + + // move data section + ldr x0, =__new_data_start + adr x1, __data_start + adr x2, __data_end + sub x2, x2, x1 + bl memcpy + + br x19 + b wait diff --git a/bootloader/linker.ld b/bootloader/linker.ld new file mode 100644 index 0000000..190396a --- /dev/null +++ b/bootloader/linker.ld @@ -0,0 +1,50 @@ +ENTRY(_start) +MEMORY +{ + NEWTEXT (rx) : ORIGIN = 0x10000, LENGTH = 128K + NEWRO (r) : ORIGIN = 0x30000, LENGTH = 128K + NEWDATA (rw) : ORIGIN = 0x50000, LENGTH = 64K + NEWBSS (rw) : ORIGIN = 0x60000, LENGTH = 64K + + TEXT (rx) : ORIGIN = 0x80000, LENGTH = 128K + RO (r) : ORIGIN = 0xa0000, LENGTH = 128K + DATA (rw) : ORIGIN = 0xc0000, LENGTH = 64K + BSS (rw) : ORIGIN = 0xd0000, LENGTH = 64K + RAM (rw) : ORIGIN = 0xf0000, LENGTH = 8M +} + +SECTIONS +{ + .text : { + __text_start = .; + KEEP(*(.text.boot)) + *(.text) + __text_end = .; + } >TEXT + .rodata : { + __rodata_start = .; + *(.rodata) + __rodata_end = .; + } >RO + .data : { + __data_start = .; + *(.data) + __data_end = .; + } >DATA + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } >BSS + __stack_end = ORIGIN(RAM) + LENGTH(RAM); + + __new_text_start = ORIGIN(NEWTEXT); + __new_ro_start = ORIGIN(NEWRO); + __new_data_start = ORIGIN(NEWDATA); + __new_bss_start = ORIGIN(NEWBSS); +} + +__kernel = 0x80000; +__heap_start = ORIGIN(RAM); +__heap_end = ORIGIN(RAM) + LENGTH(RAM) - 2M; +__bss_size = (__bss_end - __bss_start)>>3; diff --git a/bootloader/main.c b/bootloader/main.c new file mode 100644 index 0000000..ba3441b --- /dev/null +++ b/bootloader/main.c @@ -0,0 +1,21 @@ +#include +#include +#include + +extern byte_t __kernel[]; +byte_t *kernel = __kernel; + +void main() +{ + uart_init(); + + uart_getc(); + uart_puts("loaded addr: "); + uart_hex((unsigned int)(unsigned long)main); + uart_puts(ENDL); + + int shell_cont = 1; + while (shell_cont) { + shell_cont = shell(); + } +} diff --git a/include/gpio.h b/include/gpio.h new file mode 100644 index 0000000..2f528ba --- /dev/null +++ b/include/gpio.h @@ -0,0 +1,46 @@ +#pragma once +/* + * Copyright (C) 2018 bzt (bztsrc@github) + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#define MMIO_BASE 0x3F000000 + +#define GPFSEL0 ((volatile unsigned int*)(MMIO_BASE+0x00200000)) +#define GPFSEL1 ((volatile unsigned int*)(MMIO_BASE+0x00200004)) +#define GPFSEL2 ((volatile unsigned int*)(MMIO_BASE+0x00200008)) +#define GPFSEL3 ((volatile unsigned int*)(MMIO_BASE+0x0020000C)) +#define GPFSEL4 ((volatile unsigned int*)(MMIO_BASE+0x00200010)) +#define GPFSEL5 ((volatile unsigned int*)(MMIO_BASE+0x00200014)) +#define GPSET0 ((volatile unsigned int*)(MMIO_BASE+0x0020001C)) +#define GPSET1 ((volatile unsigned int*)(MMIO_BASE+0x00200020)) +#define GPCLR0 ((volatile unsigned int*)(MMIO_BASE+0x00200028)) +#define GPLEV0 ((volatile unsigned int*)(MMIO_BASE+0x00200034)) +#define GPLEV1 ((volatile unsigned int*)(MMIO_BASE+0x00200038)) +#define GPEDS0 ((volatile unsigned int*)(MMIO_BASE+0x00200040)) +#define GPEDS1 ((volatile unsigned int*)(MMIO_BASE+0x00200044)) +#define GPHEN0 ((volatile unsigned int*)(MMIO_BASE+0x00200064)) +#define GPHEN1 ((volatile unsigned int*)(MMIO_BASE+0x00200068)) +#define GPPUD ((volatile unsigned int*)(MMIO_BASE+0x00200094)) +#define GPPUDCLK0 ((volatile unsigned int*)(MMIO_BASE+0x00200098)) +#define GPPUDCLK1 ((volatile unsigned int*)(MMIO_BASE+0x0020009C)) diff --git a/include/initrd.h b/include/initrd.h new file mode 100644 index 0000000..c72bf0d --- /dev/null +++ b/include/initrd.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +typedef struct { + char c_magic[6]; + char c_ino[8]; + char c_mode[8]; + char c_uid[8]; + char c_gid[8]; + char c_nlink[8]; + char c_mtime[8]; + char c_filesize[8]; + char c_devmajor[8]; + char c_devminor[8]; + char c_rdevmajor[8]; + char c_rdevminor[8]; + char c_namesize[8]; + char c_check[8]; +}__attribute__((packed)) cpio_newc_header_t; + +typedef struct file_node { + struct file_node *l, *r; + int rand; + int node_size; + + int ino; + int mode; + int uid; + int gid; + int nlink; + int mtime; + int filesize; + int devmajor; + int devminor; + int rdevmajor; + int rdevminor; + int namesize; + + char *filename; + byte_t *filecontent; +} file_node_t; + +file_node_t *initrd_init(void); +int initrd_ls(void); +file_node_t *initrd_get(file_node_t *root, const char *filename); diff --git a/include/kmalloc.h b/include/kmalloc.h new file mode 100644 index 0000000..feb86fa --- /dev/null +++ b/include/kmalloc.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +void *simple_alloc(size_t size); +void *kmalloc(size_t size); +void kfree(void *ptr); diff --git a/include/mbox.h b/include/mbox.h new file mode 100644 index 0000000..563c2d9 --- /dev/null +++ b/include/mbox.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2018 bzt (bztsrc@github) + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +/* a properly aligned buffer */ +extern volatile unsigned int mbox[36]; + +#define MBOX_REQUEST 0 + +/* channels */ +#define MBOX_CH_POWER 0 +#define MBOX_CH_FB 1 +#define MBOX_CH_VUART 2 +#define MBOX_CH_VCHIQ 3 +#define MBOX_CH_LEDS 4 +#define MBOX_CH_BTNS 5 +#define MBOX_CH_TOUCH 6 +#define MBOX_CH_COUNT 7 +#define MBOX_CH_PROP 8 + +/* tags */ +#define MBOX_TAG_GETSERIAL 0x10004 +#define MBOX_TAG_LAST 0 +#define MBOX_TAG_BOARD_REVISION 0x00010002 + +int mbox_call(unsigned char ch); +unsigned int get_board_revision(void); diff --git a/include/random.h b/include/random.h new file mode 100644 index 0000000..42c88c1 --- /dev/null +++ b/include/random.h @@ -0,0 +1,8 @@ +#pragma once + +extern const int _random_a; +extern const int _random_c; +extern const int _random_m; +extern int seed; + +int random(void); diff --git a/include/shell.h b/include/shell.h new file mode 100644 index 0000000..d24f816 --- /dev/null +++ b/include/shell.h @@ -0,0 +1,9 @@ +#pragma once + +void help(void); +void hello(void); +void hwinfo(void); +void reboot(void); + +int // is continue +shell(void); diff --git a/include/stddef.h b/include/stddef.h new file mode 100644 index 0000000..868939b --- /dev/null +++ b/include/stddef.h @@ -0,0 +1,9 @@ +#pragma once + +#define true 1 +#define false 0 +#define ENDL "\r\n" + +typedef long unsigned int size_t; +typedef unsigned char byte_t; +typedef unsigned long long int uint64_t; diff --git a/include/string.h b/include/string.h new file mode 100644 index 0000000..98addf6 --- /dev/null +++ b/include/string.h @@ -0,0 +1,7 @@ +#pragma once + +#include + +int strcmp(const char *lhs, const char *rhs); +void *memcpy(void *dest, const void *src, size_t count); +void *memzero(void *start, void *end); diff --git a/include/uart.h b/include/uart.h new file mode 100644 index 0000000..d2d64ee --- /dev/null +++ b/include/uart.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +void uart_init(); +void uart_send(unsigned int c); +byte_t uart_getb(); +char uart_getc(); +void uart_puts(char *s); +void uart_hex(unsigned int d); diff --git a/kernel/Start.S b/kernel/Start.S new file mode 100644 index 0000000..df2cb8f --- /dev/null +++ b/kernel/Start.S @@ -0,0 +1,32 @@ +.section ".text.boot" + +.global _start + +_start: + // read cpu id, stop slave cores + mrs x1, mpidr_el1 + and x1, x1, #3 + cbz x1, 2f + // cpu id > 0, stop +1: + wfe + b 1b +2:// cpu id == 0 + // set top of stack just before our code (stack grows to a lower address per AAPCS64) + ldr x1, =_start + mov sp, x1 + + // clear bss + ldr x1, =__bss_start + ldr w2, =__bss_size +3: + cbz w2, 4f + str xzr, [x1], #8 + sub w2, w2, #1 + cbnz w2, 3b + +4: + // jump to C code, should not return + bl main + // for failsafe, halt this core too + b 1b diff --git a/kernel/linker.ld b/kernel/linker.ld new file mode 100644 index 0000000..b9c3c02 --- /dev/null +++ b/kernel/linker.ld @@ -0,0 +1,33 @@ +ENTRY(_start) +MEMORY +{ + TEXT (rx) : ORIGIN = 0x80000, LENGTH = 128K + RO (r) : ORIGIN = 0xa0000, LENGTH = 128K + DATA (rw) : ORIGIN = 0x100000, LENGTH = 512K + RAM (rw) : ORIGIN = 0x180000, LENGTH = 8M +} + +SECTIONS +{ + .text : { + KEEP(*(.text.boot)) + *(.text) + } >TEXT + .rodata : { + *(.rodata) + } >RO + .data : { + *(.data) + } >DATA + .bss : { + __bss_start = .; + *(.bss) + __bss_end = .; + } >DATA + __stack_end = ORIGIN(RAM) + LENGTH(RAM); +} + +__kernel = 0x80000; +__heap_start = ORIGIN(RAM); +__heap_end = ORIGIN(RAM) + LENGTH(RAM) - 2M; +__bss_size = (__bss_end - __bss_start)>>3; diff --git a/kernel/main.c b/kernel/main.c new file mode 100644 index 0000000..dbb6e4e --- /dev/null +++ b/kernel/main.c @@ -0,0 +1,12 @@ +#include +#include + +void main() +{ + uart_init(); + + int shell_cont = 1; + while (shell_cont) { + shell_cont = shell(); + } +} diff --git a/lib/initrd.c b/lib/initrd.c new file mode 100644 index 0000000..5568ab1 --- /dev/null +++ b/lib/initrd.c @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include + +#define nullnode ((file_node_t *)0) + +void _init_node(file_node_t *node) { + node->l = nullnode; + node->r = nullnode; + node->rand = random(); + node->node_size = 1; +} + +void _pull_from(file_node_t *to, file_node_t *from) +{ + if (!from) + return; + + to->node_size += from->node_size; +} + +void _pull(file_node_t *node) +{ + node->node_size = 1; + _pull_from(node, node->l); + _pull_from(node, node->r); +} + +file_node_t *_merge(file_node_t *a, file_node_t *b) +{ + if (!a || !b) + return a ?: b; + + if (a->rand < b->rand) { + a->r = _merge(a->r, b); + _pull(a); + return a; + } + b->l = _merge(a, b->l); + _pull(b); + return b; +} + +void _split(file_node_t *rt, const char *s, file_node_t **a, file_node_t **b) +{ + if (!rt) { + *a = *b = nullnode; + return; + } + + if (strcmp(s, rt->filename) < 0) { + *a = rt; + _split((*a)->r, s, &(*a)->r, b); + _pull(*a); + } else { + *b = rt; + _split((*b)->l, s, a, &(*b)->l); + _pull(*b); + } +} + + +file_node_t *initrd_init(void) +{ + // TODO + + return nullnode; +} + +file_node_t *_node_bs(file_node_t *cur, const char *s) +{ + if (!cur) + return nullnode; + + int cmp = strcmp(s, cur->filename); + if (cmp < 0) + return _node_bs(cur->r, s); + if (cmp > 0) + return _node_bs(cur->l, s); + return cur; // cmp == 0 +} + +file_node_t *initrd_get(file_node_t *root, const char *filename) +{ + return _node_bs(root, filename); +} diff --git a/lib/kmalloc.c b/lib/kmalloc.c new file mode 100644 index 0000000..3d697b5 --- /dev/null +++ b/lib/kmalloc.c @@ -0,0 +1,37 @@ +#include +#include + +extern void *__heap_start; +extern void *__heap_end; + +void *_heap_top = (void *)0; + +// simple 8-byte aligned linear allocation +void *simple_alloc(size_t size) +{ + if (!_heap_top) { + _heap_top = __heap_start; + } + + if (size & 0xff) + size = (size & ~0xff) + 0x100; + + if ((uint64_t)_heap_top + size >= (uint64_t)__heap_end) + return (void *)0; + + void *ret = _heap_top; + _heap_top = (void *)((uint64_t)_heap_top + size); + + return ret; +} + +void *kmalloc(size_t size) +{ + return simple_alloc(size); +} + +void kfree(void *ptr) +{ + // not implemented for now + return; +} diff --git a/lib/mbox.c b/lib/mbox.c new file mode 100644 index 0000000..ac3f4fd --- /dev/null +++ b/lib/mbox.c @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2018 bzt (bztsrc@github) + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include + +/* mailbox message buffer */ +volatile unsigned int __attribute__((aligned(16))) mbox[36]; + +#define VIDEOCORE_MBOX (MMIO_BASE+0x0000B880) +#define MBOX_READ ((volatile unsigned int*)(VIDEOCORE_MBOX+0x0)) +#define MBOX_POLL ((volatile unsigned int*)(VIDEOCORE_MBOX+0x10)) +#define MBOX_SENDER ((volatile unsigned int*)(VIDEOCORE_MBOX+0x14)) +#define MBOX_STATUS ((volatile unsigned int*)(VIDEOCORE_MBOX+0x18)) +#define MBOX_CONFIG ((volatile unsigned int*)(VIDEOCORE_MBOX+0x1C)) +#define MBOX_WRITE ((volatile unsigned int*)(VIDEOCORE_MBOX+0x20)) +#define MBOX_RESPONSE 0x80000000 +#define MBOX_FULL 0x80000000 +#define MBOX_EMPTY 0x40000000 + +#define MAILBOX_BASE MMIO_BASE + 0xb880 + +#define REQUEST_CODE 0x00000000 +#define REQUEST_SUCCEED 0x80000000 +#define REQUEST_FAILED 0x80000001 +#define TAG_REQUEST_CODE 0x00000000 +#define END_TAG 0x00000000 + +/** + * Make a mailbox call. Returns 0 on failure, non-zero on success + */ +int mbox_call(unsigned char ch) +{ + unsigned int r = (((unsigned int)((unsigned long)&mbox)&~0xF) | (ch&0xF)); + /* wait until we can write to the mailbox */ + do{asm volatile("nop");}while(*MBOX_STATUS & MBOX_FULL); + /* write the address of our message to the mailbox with channel identifier */ + *MBOX_WRITE = r; + /* now wait for the response */ + while(1) { + /* is there a response? */ + do{asm volatile("nop");}while(*MBOX_STATUS & MBOX_EMPTY); + /* is it a response to our message? */ + if(r == *MBOX_READ) + /* is it a valid successful response? */ + return mbox[1]==MBOX_RESPONSE; + } + return 0; +} + +unsigned int get_board_revision(void) +{ + mbox[0] = 36 * 4; + mbox[1] = REQUEST_CODE; + + // tags + mbox[2] = MBOX_TAG_BOARD_REVISION; + mbox[3] = 4; + mbox[4] = TAG_REQUEST_CODE; + mbox[5] = 0; // value buffer + + mbox[6] = END_TAG; + + mbox_call(MBOX_CH_PROP); + + return mbox[5]; +} diff --git a/lib/random.c b/lib/random.c new file mode 100644 index 0000000..0192542 --- /dev/null +++ b/lib/random.c @@ -0,0 +1,12 @@ +#include + +const int _random_a = 100003; +const int _random_c = 114514 + 33; +const int _random_m = 1000000007; +int seed = 0; + +int random(void) +{ + seed = _random_a * (seed + _random_c) % _random_m; + return seed; +} diff --git a/lib/shell.c b/lib/shell.c new file mode 100644 index 0000000..ab7a4d9 --- /dev/null +++ b/lib/shell.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include + +#define INPUT_BUFLEN 1000 + +void help (void) +{ + uart_puts( + "help : print this help menu" ENDL + "hello : print Hello World!" ENDL + "hwinfo: print hardware info" ENDL + "reboot: reboot the device" ENDL + ); +} + +void hello (void) +{ + uart_puts("hello, world" ENDL); +} + +void hwinfo (void) +{ + unsigned int val = get_board_revision(); + uart_puts("hwinfo: "); + uart_hex(val); + uart_puts(ENDL); +} + +#define PM_PASSWORD 0x5a000000 +#define PM_RSTC 0x3F10001c +#define PM_WDOG 0x3F100024 + +void set(long addr, unsigned int value) { + volatile unsigned int* point = (unsigned int*)addr; + *point = value; +} + +void reset(int tick) { // reboot after watchdog timer expire + set(PM_RSTC, PM_PASSWORD | 0x20); // full reset + set(PM_WDOG, PM_PASSWORD | tick); // number of watchdog tick +} + +void reboot(void) +{ + reset(1 << 16); +} + +int shell (void) +{ + uart_puts("# "); + + char buf[INPUT_BUFLEN], ch; + int sz = 0; + while ((ch = uart_getc()) != '\n') { + buf[sz++] = ch; + uart_send(ch); + } + uart_puts(ENDL); + buf[sz] = '\0'; + + if (strcmp(buf, "help") == 0) { + help(); + } else if (strcmp(buf, "hello") == 0) { + hello(); + } else if (strcmp(buf, "hwinfo") == 0) { + hwinfo(); + } else if (strcmp(buf, "reboot") == 0) { + reboot(); + } + + return true; +} diff --git a/lib/string.c b/lib/string.c new file mode 100644 index 0000000..6f2fedc --- /dev/null +++ b/lib/string.c @@ -0,0 +1,35 @@ +#include +#include + +int strcmp(const char *a, const char *b) +{ + while (*a != '\0' && *b != '\0') { + if (*a != *b) + return (*a > *b) - (*a < *b); + a++, b++; + } + if (*a == '\0' && *b == '\0') + return 0; + + if (*a == '\0') + return -1; + return 1; +} + +void *memcpy(void *dest, const void *src, size_t count) +{ + void *ret = dest; + for (const byte_t *p = src; count > 0; --count, ++p, ++dest) + *(byte_t *)dest = *p; + + return ret; +} + +void *memzero(void *start, void *end) +{ + void *ret = start; + for (byte_t *p = start; p < (byte_t *)end; ++p) + *p = 0; + + return ret; +} diff --git a/lib/uart.c b/lib/uart.c new file mode 100644 index 0000000..f103162 --- /dev/null +++ b/lib/uart.c @@ -0,0 +1,103 @@ +#include +#include + +/* Auxilary mini UART registers */ +#define AUX_ENABLE ((volatile unsigned int*)(MMIO_BASE+0x00215004)) +#define AUX_MU_IO ((volatile unsigned int*)(MMIO_BASE+0x00215040)) +#define AUX_MU_IER ((volatile unsigned int*)(MMIO_BASE+0x00215044)) +#define AUX_MU_IIR ((volatile unsigned int*)(MMIO_BASE+0x00215048)) +#define AUX_MU_LCR ((volatile unsigned int*)(MMIO_BASE+0x0021504C)) +#define AUX_MU_MCR ((volatile unsigned int*)(MMIO_BASE+0x00215050)) +#define AUX_MU_LSR ((volatile unsigned int*)(MMIO_BASE+0x00215054)) +#define AUX_MU_MSR ((volatile unsigned int*)(MMIO_BASE+0x00215058)) +#define AUX_MU_SCRATCH ((volatile unsigned int*)(MMIO_BASE+0x0021505C)) +#define AUX_MU_CNTL ((volatile unsigned int*)(MMIO_BASE+0x00215060)) +#define AUX_MU_STAT ((volatile unsigned int*)(MMIO_BASE+0x00215064)) +#define AUX_MU_BAUD ((volatile unsigned int*)(MMIO_BASE+0x00215068)) + +/** + * Set baud rate and characteristics (115200 8N1) and map to GPIO + */ +void uart_init() +{ + register unsigned int r; + + /* initialize UART */ + *AUX_ENABLE |=1; // enable UART1, AUX mini uart + *AUX_MU_IER = 0; + *AUX_MU_CNTL = 0; + *AUX_MU_LCR = 3; // 8 bits + *AUX_MU_MCR = 0; + *AUX_MU_IER = 0; + *AUX_MU_IIR = 0xc6; // disable interrupts + *AUX_MU_BAUD = 270; // 115200 baud + /* map UART1 to GPIO pins */ + r=*GPFSEL1; + r&=~((7<<12)|(7<<15)); // gpio14, gpio15 + r|=(2<<12)|(2<<15); // alt5 + *GPFSEL1 = r; + *GPPUD = 0; // enable pins 14 and 15 + r=150; while(r--) { asm volatile("nop"); } + *GPPUDCLK0 = (1<<14)|(1<<15); + r=150; while(r--) { asm volatile("nop"); } + *GPPUDCLK0 = 0; // flush GPIO setup + *AUX_MU_CNTL = 3; // enable Tx, Rx +} + +/** + * Send a character + */ +void uart_send(unsigned int c) { + /* wait until we can send */ + do{asm volatile("nop");}while(!(*AUX_MU_LSR&0x20)); + /* write the character to the buffer */ + *AUX_MU_IO=c; +} + +byte_t uart_getb() +{ + byte_t r; + /* wait until something is in the buffer */ + do{asm volatile("nop");}while(!(*AUX_MU_LSR&0x01)); + /* read it and return */ + r=(byte_t)(*AUX_MU_IO); + + return r; +} + +/** + * Receive a character + */ +char uart_getc() { + char r = (char)uart_getb(); + + /* convert carrige return to newline */ + return r=='\r'?'\n':r; +} + +/** + * Display a string + */ +void uart_puts(char *s) { + while(*s) { + /* convert newline to carrige return + newline */ + if(*s=='\n') + uart_send('\r'); + uart_send(*s++); + } +} + +/** + * Display a binary value in hexadecimal + */ +void uart_hex(unsigned int d) { + unsigned int n; + int c; + for(c=28;c>=0;c-=4) { + // get highest tetrad + n=(d>>c)&0xF; + // 0-9 => '0'-'9', 10-15 => 'A'-'F' + n+=n>9?0x37:0x30; + uart_send(n); + } +} diff --git a/misc/bcm2710-rpi-3-b-plus.dtb b/misc/bcm2710-rpi-3-b-plus.dtb new file mode 100644 index 0000000000000000000000000000000000000000..ffc07ccd834d659ab2b4c1ccf73908e6aa0909c7 GIT binary patch literal 35322 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