summaryrefslogtreecommitdiff
path: root/hw
diff options
context:
space:
mode:
authorScott Shawcroft <[email protected]>2021-09-17 16:51:34 -0700
committerScott Shawcroft <[email protected]>2021-09-17 16:51:34 -0700
commit2499c9382d4ff0d67106f6dc6cd4a9dfbd0be20e (patch)
tree239a9d1bf09c47ae3594eb775b18613b2ab9ef8f /hw
parent6d3fb7eb98b6c0960be152b66b653a3062c7db5e (diff)
rpi start. doesn't work
Diffstat (limited to 'hw')
-rw-r--r--hw/bsp/raspberrypi4/boards/raspberrypi_cm4/board.h38
-rw-r--r--hw/bsp/raspberrypi4/boards/raspberrypi_cm4/board.mk0
-rw-r--r--hw/bsp/raspberrypi4/family.c151
-rw-r--r--hw/bsp/raspberrypi4/family.mk37
-rw-r--r--hw/mcu/broadcom/bcm2711/boot.s170
-rw-r--r--hw/mcu/broadcom/bcm2711/interrupts.c26
-rw-r--r--hw/mcu/broadcom/bcm2711/interrupts.h8
-rw-r--r--hw/mcu/broadcom/bcm2711/io.c168
-rw-r--r--hw/mcu/broadcom/bcm2711/io.h13
-rw-r--r--hw/mcu/broadcom/bcm2711/link.ld20
10 files changed, 631 insertions, 0 deletions
diff --git a/hw/bsp/raspberrypi4/boards/raspberrypi_cm4/board.h b/hw/bsp/raspberrypi4/boards/raspberrypi_cm4/board.h
new file mode 100644
index 000000000..1d3565d5c
--- /dev/null
+++ b/hw/bsp/raspberrypi4/boards/raspberrypi_cm4/board.h
@@ -0,0 +1,38 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020, Ha Thach (tinyusb.org)
+ *
+ * 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.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#ifndef BOARD_H_
+#define BOARD_H_
+
+#ifdef __cplusplus
+ extern "C" {
+#endif
+
+#ifdef __cplusplus
+ }
+#endif
+
+#endif /* BOARD_H_ */
diff --git a/hw/bsp/raspberrypi4/boards/raspberrypi_cm4/board.mk b/hw/bsp/raspberrypi4/boards/raspberrypi_cm4/board.mk
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/hw/bsp/raspberrypi4/boards/raspberrypi_cm4/board.mk
diff --git a/hw/bsp/raspberrypi4/family.c b/hw/bsp/raspberrypi4/family.c
new file mode 100644
index 000000000..b2665f42d
--- /dev/null
+++ b/hw/bsp/raspberrypi4/family.c
@@ -0,0 +1,151 @@
+/*
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2019 Ha Thach (tinyusb.org)
+ *
+ * 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.
+ *
+ * This file is part of the TinyUSB stack.
+ */
+
+#include "bsp/board.h"
+#include "board.h"
+
+#include "io.h"
+
+//--------------------------------------------------------------------+
+// Forward USB interrupt events to TinyUSB IRQ Handler
+//--------------------------------------------------------------------+
+void OTG_FS_IRQHandler(void)
+{
+ tud_int_handler(0);
+}
+
+//--------------------------------------------------------------------+
+// MACRO TYPEDEF CONSTANT ENUM
+//--------------------------------------------------------------------+
+
+//--------------------------------------------------------------------+
+// Board porting API
+//--------------------------------------------------------------------+
+
+void print(void) {
+ const char* greeting2 = "hello from cm100\r\n";
+ board_uart_write(greeting2, strlen(greeting2));
+}
+
+void board_init(void)
+{
+ gpio_initOutputPinWithPullNone(18);
+ gpio_setPinOutputBool(18, true);
+ gpio_initOutputPinWithPullNone(42);
+ // gpio_initOutputPinWithPullNone(23);
+ // gpio_initOutputPinWithPullNone(24);
+ // gpio_initOutputPinWithPullNone(25);
+ gpio_setPinOutputBool(18, false);
+ uart_init();
+ gpio_setPinOutputBool(18, true);
+ const char* greeting = "hello to gdb2\r\n";
+ gpio_setPinOutputBool(18, false);
+ for (size_t i = 0; i < 5; i++) {
+ // while (true) {
+ for (size_t j = 0; j < 1000000; j++) {
+ __asm__("nop");
+ }
+ gpio_setPinOutputBool(42, true);
+ gpio_setPinOutputBool(18, true);
+ for (size_t j = 0; j < 1000000; j++) {
+ __asm__("nop");
+ }
+ gpio_setPinOutputBool(42, false);
+ gpio_setPinOutputBool(18, false);
+ }
+ // uart_writeText("hello from io\n");
+ // gpio_setPinOutputBool(24, true);
+ board_uart_write(greeting, strlen(greeting));
+ // gpio_setPinOutputBool(24, false);
+ // gpio_setPinOutputBool(25, true);
+ print();
+ // gpio_setPinOutputBool(25, false);
+ while (true) {
+ // for (size_t i = 0; i < 5; i++) {
+ for (size_t j = 0; j < 10000000000; j++) {
+ __asm__("nop");
+ }
+ gpio_setPinOutputBool(42, true);
+ for (size_t j = 0; j < 10000000000; j++) {
+ __asm__("nop");
+ }
+ gpio_setPinOutputBool(42, false);
+ }
+ // while (1) uart_update();
+}
+
+void board_led_write(bool state)
+{
+ gpio_setPinOutputBool(42, state);
+}
+
+uint32_t board_button_read(void)
+{
+ return 0;
+}
+
+int board_uart_read(uint8_t* buf, int len)
+{
+ (void) buf; (void) len;
+ return 0;
+}
+
+int board_uart_write(void const * buf, int len)
+{
+ for (int i = 0; i < len; i++) {
+ const char* cbuf = buf;
+ if (cbuf[i] == '\n') {
+ uart_writeByteBlockingActual('\r');
+ }
+ uart_writeByteBlockingActual(cbuf[i]);
+ }
+ return len;
+}
+
+#if CFG_TUSB_OS == OPT_OS_NONE
+volatile uint32_t system_ticks = 0;
+void SysTick_Handler (void)
+{
+ system_ticks++;
+}
+
+uint32_t board_millis(void)
+{
+ return system_ticks;
+}
+#endif
+
+void HardFault_Handler (void)
+{
+ // asm("bkpt");
+}
+
+// Required by __libc_init_array in startup code if we are compiling using
+// -nostdlib/-nostartfiles.
+void _init(void)
+{
+
+}
diff --git a/hw/bsp/raspberrypi4/family.mk b/hw/bsp/raspberrypi4/family.mk
new file mode 100644
index 000000000..2d5c78d22
--- /dev/null
+++ b/hw/bsp/raspberrypi4/family.mk
@@ -0,0 +1,37 @@
+UF2_FAMILY_ID = 0x57755a57
+
+include $(TOP)/$(BOARD_PATH)/board.mk
+
+MCU_DIR = hw/mcu/broadcom/bcm2711
+
+CC = clang
+
+CFLAGS += \
+ -Wall \
+ -O0 \
+ -ffreestanding \
+ -nostdlib \
+ -nostartfiles \
+ -std=c17 \
+ -mgeneral-regs-only \
+ -DCFG_TUSB_MCU=OPT_MCU_BCM2711
+
+SRC_C += \
+ src/portable/broadcom/synopsys/dcd_synopsys.c \
+ $(MCU_DIR)/io.c \
+ $(MCU_DIR)/interrupts.c
+
+CROSS_COMPILE = aarch64-none-elf-
+
+SKIP_NANOLIB = 1
+
+LD_FILE = $(MCU_DIR)/link.ld
+
+INC += \
+ $(TOP)/$(BOARD_PATH) \
+ $(TOP)/$(MCU_DIR)
+
+SRC_S += $(MCU_DIR)/boot.S
+
+$(BUILD)/kernel8.img: $(BUILD)/$(PROJECT).elf
+ $(OBJCOPY) -O binary $^ $@
diff --git a/hw/mcu/broadcom/bcm2711/boot.s b/hw/mcu/broadcom/bcm2711/boot.s
new file mode 100644
index 000000000..076b3a76a
--- /dev/null
+++ b/hw/mcu/broadcom/bcm2711/boot.s
@@ -0,0 +1,170 @@
+.section ".text.boot" // Make sure the linker puts this at the start of the kernel image
+
+.global _start // Execution starts here
+
+_start:
+ // Check processor ID is zero (executing on main core), else hang
+ mrs x1, mpidr_el1
+ and x1, x1, #3
+ cbz x1, 2f
+ // We're not on the main core, so hang in an infinite wait loop
+1: wfe
+ b 1b
+2: // We're on the main core!
+
+ // Set stack to start below our code
+ ldr x1, =_start
+ mov sp, x1
+
+ adr x0, vectors // load VBAR_EL1 with virtual
+ // msr vbar_el3, x0 // vector table address
+ msr vbar_el1, x0 // vector table address
+ // msr vbar_el2, x0 // vector table address
+ isb
+
+ // Clean the BSS section
+ ldr x1, =__bss_start // Start address
+ ldr w2, =__bss_size // Size of the section
+3: cbz w2, 4f // Quit loop if zero
+ str xzr, [x1], #8
+ sub w2, w2, #1
+ cbnz w2, 3b // Loop if non-zero
+
+ // Jump to our main() routine in C (make sure it doesn't return)
+4: bl main
+ // In case it does return, halt the master core too
+ b 1b
+
+.macro ventry label
+.align 7
+b \label
+.endm
+
+.macro handle_invalid_entry type
+irq_entry
+mov x0, #\type
+mrs x1, esr_el1
+mrs x2, elr_el1
+b err_hang
+.endm
+
+.macro irq_entry
+sub sp, sp, #272
+stp x0, x1, [sp, #16 * 0]
+stp x2, x3, [sp, #16 * 1]
+stp x4, x5, [sp, #16 * 2]
+stp x6, x7, [sp, #16 * 3]
+stp x8, x9, [sp, #16 * 4]
+stp x10, x11, [sp, #16 * 5]
+stp x12, x13, [sp, #16 * 6]
+stp x14, x15, [sp, #16 * 7]
+stp x16, x17, [sp, #16 * 8]
+stp x18, x19, [sp, #16 * 9]
+stp x20, x21, [sp, #16 * 10]
+stp x22, x23, [sp, #16 * 11]
+stp x24, x25, [sp, #16 * 12]
+stp x26, x27, [sp, #16 * 13]
+stp x28, x29, [sp, #16 * 14]
+str x30, [sp, #16 * 15]
+.endm
+
+.macro irq_exit
+ldp x0, x1, [sp, #16 * 0]
+ldp x2, x3, [sp, #16 * 1]
+ldp x4, x5, [sp, #16 * 2]
+ldp x6, x7, [sp, #16 * 3]
+ldp x8, x9, [sp, #16 * 4]
+ldp x10, x11, [sp, #16 * 5]
+ldp x12, x13, [sp, #16 * 6]
+ldp x14, x15, [sp, #16 * 7]
+ldp x16, x17, [sp, #16 * 8]
+ldp x18, x19, [sp, #16 * 9]
+ldp x20, x21, [sp, #16 * 10]
+ldp x22, x23, [sp, #16 * 11]
+ldp x24, x25, [sp, #16 * 12]
+ldp x26, x27, [sp, #16 * 13]
+ldp x28, x29, [sp, #16 * 14]
+ldr x30, [sp, #16 * 15]
+add sp, sp, #272
+eret
+.endm
+
+
+/*
+ * Exception vectors.
+ */
+.align 11
+.globl vectors
+vectors:
+ ventry sync_invalid_el1t // Synchronous EL1t
+ ventry irq_invalid_el1t // IRQ EL1t
+ ventry fiq_invalid_el1t // FIQ EL1t
+ ventry error_invalid_el1t // Error EL1t
+
+ ventry sync_invalid_el1h // Synchronous EL1h
+ ventry el1_irq // IRQ EL1h
+ ventry fiq_invalid_el1h // FIQ EL1h
+ ventry error_invalid_el1h // Error EL1h
+
+ ventry sync_invalid_el0_64 // Synchronous 64-bit EL0
+ ventry irq_invalid_el0_64 // IRQ 64-bit EL0
+ ventry fiq_invalid_el0_64 // FIQ 64-bit EL0
+ ventry error_invalid_el0_64 // Error 64-bit EL0
+
+ ventry sync_invalid_el0_32 // Synchronous 32-bit EL0
+ ventry irq_invalid_el0_32 // IRQ 32-bit EL0
+ ventry fiq_invalid_el0_32 // FIQ 32-bit EL0
+ ventry error_invalid_el0_32 // Error 32-bit EL0
+
+sync_invalid_el1t:
+ handle_invalid_entry 0
+
+irq_invalid_el1t:
+ handle_invalid_entry 1
+
+fiq_invalid_el1t:
+ handle_invalid_entry 2
+
+error_invalid_el1t:
+ handle_invalid_entry 3
+
+sync_invalid_el1h:
+ handle_invalid_entry 4
+
+fiq_invalid_el1h:
+ handle_invalid_entry 5
+
+error_invalid_el1h:
+ handle_invalid_entry 6
+
+sync_invalid_el0_64:
+ handle_invalid_entry 7
+
+irq_invalid_el0_64:
+ handle_invalid_entry 8
+
+fiq_invalid_el0_64:
+ handle_invalid_entry 9
+
+error_invalid_el0_64:
+ handle_invalid_entry 10
+
+sync_invalid_el0_32:
+ handle_invalid_entry 11
+
+irq_invalid_el0_32:
+ handle_invalid_entry 12
+
+fiq_invalid_el0_32:
+ handle_invalid_entry 13
+
+error_invalid_el0_32:
+ handle_invalid_entry 14
+
+el1_irq:
+ irq_entry
+ bl handle_irq
+ irq_exit
+
+.globl err_hang
+err_hang: b err_hang
diff --git a/hw/mcu/broadcom/bcm2711/interrupts.c b/hw/mcu/broadcom/bcm2711/interrupts.c
new file mode 100644
index 000000000..cb5607526
--- /dev/null
+++ b/hw/mcu/broadcom/bcm2711/interrupts.c
@@ -0,0 +1,26 @@
+#include <stdbool.h>
+#include <stdint.h>
+
+void disable_async_interrupts(void) {
+
+}
+
+void enable_async_interrupts(void) {
+
+}
+
+void Default_Handler(void) {
+ while (true) {}
+}
+
+__attribute__((weak)) void handle_irq(void) {
+ unsigned int irq = *((volatile uint32_t*) 0x3F00B204);
+ switch (irq) {
+ // case (SYSTEM_TIMER_IRQ_1):
+ // handle_timer_irq();
+ // break;
+ default:
+ // printf("Unknown pending irq: %x\r\n", irq);
+ Default_Handler();
+ }
+} \ No newline at end of file
diff --git a/hw/mcu/broadcom/bcm2711/interrupts.h b/hw/mcu/broadcom/bcm2711/interrupts.h
new file mode 100644
index 000000000..5f36d6daa
--- /dev/null
+++ b/hw/mcu/broadcom/bcm2711/interrupts.h
@@ -0,0 +1,8 @@
+#pragma once
+
+void disable_async_interrupts(void);
+void enable_async_interrupts(void);
+
+#define GIC_BASE 0x4c0040000
+#define NUM_CPUS 4
+#define NUM_SPIS 192
diff --git a/hw/mcu/broadcom/bcm2711/io.c b/hw/mcu/broadcom/bcm2711/io.c
new file mode 100644
index 000000000..213d6f2fb
--- /dev/null
+++ b/hw/mcu/broadcom/bcm2711/io.c
@@ -0,0 +1,168 @@
+// From: https://github.com/isometimes/rpi4-osdev/blob/master/part4-miniuart/io.c
+// CC-0 License
+
+// GPIO
+
+enum {
+ PERIPHERAL_BASE = 0xFE000000,
+ GPFSEL0 = PERIPHERAL_BASE + 0x200000,
+ GPSET0 = PERIPHERAL_BASE + 0x20001C,
+ GPCLR0 = PERIPHERAL_BASE + 0x200028,
+ GPPUPPDN0 = PERIPHERAL_BASE + 0x2000E4
+};
+
+enum {
+ GPIO_MAX_PIN = 53,
+ GPIO_FUNCTION_OUT = 1,
+ GPIO_FUNCTION_ALT5 = 2,
+ GPIO_FUNCTION_ALT3 = 7
+};
+
+enum {
+ Pull_None = 0,
+ Pull_Down = 1, // Are down and up the right way around?
+ Pull_Up = 2
+};
+
+void mmio_write(long reg, unsigned int val) { *(volatile unsigned int *)reg = val; }
+unsigned int mmio_read(long reg) { return *(volatile unsigned int *)reg; }
+
+unsigned int gpio_call(unsigned int pin_number, unsigned int value, unsigned int base, unsigned int field_size, unsigned int field_max) {
+ unsigned int field_mask = (1 << field_size) - 1;
+
+ if (pin_number > field_max) return 0;
+ if (value > field_mask) return 0;
+
+ unsigned int num_fields = 32 / field_size;
+ unsigned int reg = base + ((pin_number / num_fields) * 4);
+ unsigned int shift = (pin_number % num_fields) * field_size;
+
+ unsigned int curval = mmio_read(reg);
+ curval &= ~(field_mask << shift);
+ curval |= value << shift;
+ mmio_write(reg, curval);
+
+ return 1;
+}
+
+unsigned int gpio_set (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPSET0, 1, GPIO_MAX_PIN); }
+unsigned int gpio_clear (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPCLR0, 1, GPIO_MAX_PIN); }
+unsigned int gpio_pull (unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPPUPPDN0, 2, GPIO_MAX_PIN); }
+unsigned int gpio_function(unsigned int pin_number, unsigned int value) { return gpio_call(pin_number, value, GPFSEL0, 3, GPIO_MAX_PIN); }
+
+void gpio_useAsAlt3(unsigned int pin_number) {
+ gpio_pull(pin_number, Pull_None);
+ gpio_function(pin_number, GPIO_FUNCTION_ALT3);
+}
+
+void gpio_useAsAlt5(unsigned int pin_number) {
+ gpio_pull(pin_number, Pull_None);
+ gpio_function(pin_number, GPIO_FUNCTION_ALT5);
+}
+
+void gpio_initOutputPinWithPullNone(unsigned int pin_number) {
+ gpio_pull(pin_number, Pull_None);
+ gpio_function(pin_number, GPIO_FUNCTION_OUT);
+}
+
+void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff) {
+ if (onOrOff) {
+ gpio_set(pin_number, 1);
+ } else {
+ gpio_clear(pin_number, 1);
+ }
+}
+
+// UART
+
+enum {
+ AUX_BASE = PERIPHERAL_BASE + 0x215000,
+ AUX_IRQ = AUX_BASE,
+ AUX_ENABLES = AUX_BASE + 4,
+ AUX_MU_IO_REG = AUX_BASE + 64,
+ AUX_MU_IER_REG = AUX_BASE + 68,
+ AUX_MU_IIR_REG = AUX_BASE + 72,
+ AUX_MU_LCR_REG = AUX_BASE + 76,
+ AUX_MU_MCR_REG = AUX_BASE + 80,
+ AUX_MU_LSR_REG = AUX_BASE + 84,
+ AUX_MU_MSR_REG = AUX_BASE + 88,
+ AUX_MU_SCRATCH = AUX_BASE + 92,
+ AUX_MU_CNTL_REG = AUX_BASE + 96,
+ AUX_MU_STAT_REG = AUX_BASE + 100,
+ AUX_MU_BAUD_REG = AUX_BASE + 104,
+ AUX_UART_CLOCK = 500000000,
+ UART_MAX_QUEUE = 16 * 1024
+};
+
+#define AUX_MU_BAUD(baud) ((AUX_UART_CLOCK/(baud*8))-1)
+
+unsigned char uart_output_queue[UART_MAX_QUEUE];
+unsigned int uart_output_queue_write = 0;
+unsigned int uart_output_queue_read = 0;
+
+void uart_init(void) {
+ mmio_write(AUX_ENABLES, 1); //enable UART1
+ mmio_write(AUX_MU_IER_REG, 0);
+ mmio_write(AUX_MU_CNTL_REG, 0);
+ mmio_write(AUX_MU_LCR_REG, 3); //8 bits
+ mmio_write(AUX_MU_MCR_REG, 0);
+ mmio_write(AUX_MU_IER_REG, 0);
+ mmio_write(AUX_MU_IIR_REG, 0xC6); //disable interrupts
+ mmio_write(AUX_MU_BAUD_REG, AUX_MU_BAUD(115200));
+ gpio_useAsAlt5(14);
+ gpio_useAsAlt5(15);
+ mmio_write(AUX_MU_CNTL_REG, 3); //enable RX/TX
+}
+
+unsigned int uart_isOutputQueueEmpty(void) {
+ return uart_output_queue_read == uart_output_queue_write;
+}
+
+unsigned int uart_isReadByteReady(void) { return mmio_read(AUX_MU_LSR_REG) & 0x01; }
+unsigned int uart_isWriteByteReady(void) { return mmio_read(AUX_MU_LSR_REG) & 0x20; }
+
+unsigned char uart_readByte(void) {
+ while (!uart_isReadByteReady());
+ return (unsigned char)mmio_read(AUX_MU_IO_REG);
+}
+
+void uart_writeByteBlockingActual(unsigned char ch) {
+ while (!uart_isWriteByteReady());
+ mmio_write(AUX_MU_IO_REG, (unsigned int)ch);
+}
+
+void uart_loadOutputFifo(void) {
+ while (!uart_isOutputQueueEmpty() && uart_isWriteByteReady()) {
+ uart_writeByteBlockingActual(uart_output_queue[uart_output_queue_read]);
+ uart_output_queue_read = (uart_output_queue_read + 1) & (UART_MAX_QUEUE - 1); // Don't overrun
+ }
+}
+
+void uart_writeByteBlocking(unsigned char ch) {
+ unsigned int next = (uart_output_queue_write + 1) & (UART_MAX_QUEUE - 1); // Don't overrun
+
+ while (next == uart_output_queue_read) uart_loadOutputFifo();
+
+ uart_output_queue[uart_output_queue_write] = ch;
+ uart_output_queue_write = next;
+}
+
+void uart_writeText(const char *buffer) {
+ while (*buffer) {
+ if (*buffer == '\n') uart_writeByteBlocking('\r');
+ uart_writeByteBlocking(*buffer++);
+ }
+}
+
+void uart_drainOutputQueue(void) {
+ while (!uart_isOutputQueueEmpty()) uart_loadOutputFifo();
+}
+
+void uart_update(void) {
+ uart_loadOutputFifo();
+
+ if (uart_isReadByteReady()) {
+ unsigned char ch = uart_readByte();
+ if (ch == '\r') uart_writeText("\n"); else uart_writeByteBlocking(ch);
+ }
+}
diff --git a/hw/mcu/broadcom/bcm2711/io.h b/hw/mcu/broadcom/bcm2711/io.h
new file mode 100644
index 000000000..9f70ce504
--- /dev/null
+++ b/hw/mcu/broadcom/bcm2711/io.h
@@ -0,0 +1,13 @@
+// From: https://github.com/isometimes/rpi4-osdev/blob/master/part4-miniuart/io.h
+// CC-0 License
+
+void uart_init(void);
+void uart_writeText(const char *buffer);
+void uart_loadOutputFifo(void);
+unsigned char uart_readByte(void);
+unsigned int uart_isReadByteReady(void);
+void uart_writeByteBlocking(unsigned char ch);
+void uart_update(void);
+void gpio_setPinOutputBool(unsigned int pin_number, unsigned int onOrOff);
+void uart_writeByteBlockingActual(unsigned char ch);
+void gpio_initOutputPinWithPullNone(unsigned int pin_number);
diff --git a/hw/mcu/broadcom/bcm2711/link.ld b/hw/mcu/broadcom/bcm2711/link.ld
new file mode 100644
index 000000000..0d5ebe2ab
--- /dev/null
+++ b/hw/mcu/broadcom/bcm2711/link.ld
@@ -0,0 +1,20 @@
+SECTIONS
+{
+ . = 0x80000; /* Kernel load address for AArch64 */
+ .text : { KEEP(*(.text.boot)) *(.text .text.* .gnu.linkonce.t*) }
+ .rodata : { *(.rodata .rodata.* .gnu.linkonce.r*) }
+ PROVIDE(_data = .);
+ .data : { *(.data .data.* .gnu.linkonce.d*) }
+ .bss (NOLOAD) : {
+ . = ALIGN(16);
+ __bss_start = .;
+ *(.bss .bss.*)
+ *(COMMON)
+ __bss_end = .;
+ }
+ _end = .;
+ end = .;
+
+ /DISCARD/ : { *(.comment) *(.gnu*) *(.note*) *(.eh_frame*) }
+}
+__bss_size = (__bss_end - __bss_start)>>3;