From 50ef7583ffefab8cb6b3be17cff2a165782d8f2c Mon Sep 17 00:00:00 2001 From: alloncm Date: Fri, 19 May 2023 01:03:44 +0300 Subject: [PATCH] Add baremetal target with support for 32bit RPI4 --- Cargo.lock | 9 + Cargo.toml | 3 +- baremetal/.cargo/config.toml | 6 + baremetal/Cargo.toml | 17 ++ baremetal/build.bat | 2 + baremetal/build.rs | 5 + baremetal/build.sh | 3 + baremetal/config.txt | 7 + baremetal/link.ld | 60 +++++ baremetal/src/boot.rs | 18 ++ baremetal/src/boot_armv7a.s | 241 +++++++++++++++++ baremetal/src/configuration.rs | 37 +++ baremetal/src/drivers/gpio_joypad.rs | 42 +++ baremetal/src/drivers/ili9341_gfx_device.rs | 227 ++++++++++++++++ baremetal/src/drivers/mod.rs | 5 + baremetal/src/logging.rs | 52 ++++ baremetal/src/main.rs | 50 ++++ baremetal/src/peripherals/dma.rs | 273 ++++++++++++++++++++ baremetal/src/peripherals/gpio.rs | 128 +++++++++ baremetal/src/peripherals/gpu.rs | 64 +++++ baremetal/src/peripherals/mailbox.rs | 128 +++++++++ baremetal/src/peripherals/mini_uart.rs | 93 +++++++ baremetal/src/peripherals/mod.rs | 66 +++++ baremetal/src/peripherals/spi.rs | 148 +++++++++++ baremetal/src/peripherals/timer.rs | 57 ++++ baremetal/src/peripherals/utils.rs | 53 ++++ baremetal/src/syncronization.rs | 26 ++ 27 files changed, 1819 insertions(+), 1 deletion(-) create mode 100644 baremetal/.cargo/config.toml create mode 100644 baremetal/Cargo.toml create mode 100644 baremetal/build.bat create mode 100644 baremetal/build.rs create mode 100644 baremetal/build.sh create mode 100644 baremetal/config.txt create mode 100644 baremetal/link.ld create mode 100644 baremetal/src/boot.rs create mode 100644 baremetal/src/boot_armv7a.s create mode 100644 baremetal/src/configuration.rs create mode 100644 baremetal/src/drivers/gpio_joypad.rs create mode 100644 baremetal/src/drivers/ili9341_gfx_device.rs create mode 100644 baremetal/src/drivers/mod.rs create mode 100644 baremetal/src/logging.rs create mode 100644 baremetal/src/main.rs create mode 100644 baremetal/src/peripherals/dma.rs create mode 100644 baremetal/src/peripherals/gpio.rs create mode 100644 baremetal/src/peripherals/gpu.rs create mode 100644 baremetal/src/peripherals/mailbox.rs create mode 100644 baremetal/src/peripherals/mini_uart.rs create mode 100644 baremetal/src/peripherals/mod.rs create mode 100644 baremetal/src/peripherals/spi.rs create mode 100644 baremetal/src/peripherals/timer.rs create mode 100644 baremetal/src/peripherals/utils.rs create mode 100644 baremetal/src/syncronization.rs diff --git a/Cargo.lock b/Cargo.lock index 59e45b39..d011b478 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -25,6 +25,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "baremetal" +version = "3.0.2" +dependencies = [ + "image_inter", + "lib_gb", + "log", +] + [[package]] name = "base64" version = "0.21.0" diff --git a/Cargo.toml b/Cargo.toml index 6f47a208..c17f6ce9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,8 @@ members = [ "gb", "lib_gb", "image_inter", - "bcm_host" + "bcm_host", + "baremetal" ] [workspace.package] diff --git a/baremetal/.cargo/config.toml b/baremetal/.cargo/config.toml new file mode 100644 index 00000000..ba6123a2 --- /dev/null +++ b/baremetal/.cargo/config.toml @@ -0,0 +1,6 @@ +[build] +target = "armv7a-none-eabihf" +rustflags = [ + "-Clink-arg=--script=./baremetal/link.ld", + "-Ctarget-feature=+virtualization" +] \ No newline at end of file diff --git a/baremetal/Cargo.toml b/baremetal/Cargo.toml new file mode 100644 index 00000000..7fd40e9f --- /dev/null +++ b/baremetal/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "baremetal" +version.workspace = true +authors.workspace = true +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +log = "0.4" +lib_gb = {path = "../lib_gb", features = ["u16pixel"]} +image_inter = {path = "../image_inter"} + +[features] +default = ["rpi4"] +rpi4 = [] +rpi2 = [] \ No newline at end of file diff --git a/baremetal/build.bat b/baremetal/build.bat new file mode 100644 index 00000000..9119f64b --- /dev/null +++ b/baremetal/build.bat @@ -0,0 +1,2 @@ +cargo +nightly b -r -Z build-std=core +rust-objcopy ../target/armv7a-none-eabihf/release/baremetal -O binary kernel7.img \ No newline at end of file diff --git a/baremetal/build.rs b/baremetal/build.rs new file mode 100644 index 00000000..1b848f40 --- /dev/null +++ b/baremetal/build.rs @@ -0,0 +1,5 @@ +const LD_SCRIPT_PATH:&str = "link.ld"; + +fn main(){ + println!("cargo:rerun-if-changed={}", LD_SCRIPT_PATH); +} \ No newline at end of file diff --git a/baremetal/build.sh b/baremetal/build.sh new file mode 100644 index 00000000..9faf2e4b --- /dev/null +++ b/baremetal/build.sh @@ -0,0 +1,3 @@ +# armv7a-none-eabihf is not supported automacticaly by rust so the nightly toolchain is neccessary to build the core library +cargo +nightly b -r -Z build-std=core +rust-objcopy ../target/armv7a-none-eabihf/release/baremetal -O binary kernel7.img \ No newline at end of file diff --git a/baremetal/config.txt b/baremetal/config.txt new file mode 100644 index 00000000..e4b6be5e --- /dev/null +++ b/baremetal/config.txt @@ -0,0 +1,7 @@ +# configuration for the RPI +arm_64bit=0 # boot to 32 bit mode + +# fast boot +boot_delay=0 +disable_poe_fan=1 +disable_splash=1 \ No newline at end of file diff --git a/baremetal/link.ld b/baremetal/link.ld new file mode 100644 index 00000000..64aeea77 --- /dev/null +++ b/baremetal/link.ld @@ -0,0 +1,60 @@ +/* Place _start procedure at the entry address for RPI */ +__rpi_32_phys_binary_load_addr = 0x8000; +__isr_table_addr = 0; +__stack_size = 0x100000; /* 1MB stack */ +ENTRY(__rpi_32_phys_binary_load_addr) /* enry point */ + +SECTIONS +{ + .isr_table (NOLOAD) : + { + . = __isr_table_addr; + /* allocate space for the table */ + . = . + 0x40; + } + . = __rpi_32_phys_binary_load_addr; + .text : + { + KEEP(*(.text._start)) /*put _start first, `KEEP` disables linker optimizations*/ + *(.text*) + } + /*readonly data - readonly global variables*/ + .rodata : + { + *(.rodata*) + } + /*global variables*/ + .data : + { + *(.data*) + } + .stack (NOLOAD) : ALIGN(16) + { + . = . + __stack_size; + __cpu0_stack_start = .; /* stack grows from high address to low address */ + } + /*bss must be at the end of the linker script in order to keep the image small (otherwise objcopy will gap the space between bss and the next section)*/ + /*uninitialized global variables*/ + .bss (NOLOAD) : ALIGN(16) + { + __bss_start = .; + *(.bss*) + __bss_end = .; + } + /* uncached memory - used by drivers and devices */ + /* Since L1 memory map entry is 1MB, make sure it's alligned correctly*/ + .uncached (NOLOAD) : ALIGN(0x100000) + { + __uncached_data_start = .; + __cached_data_map_size = __uncached_data_start / 0x100000; + *(.uncached*) + } + + /* Remove those sections from the final binary */ + /DISCARD/ : + { + *(.ARM.attributes) + *(.ARM.exidx) /* Used for stack unwinding - not relevant for now */ + *(.comment) /* comments about the compiler and linker - not intresting */ + } +} \ No newline at end of file diff --git a/baremetal/src/boot.rs b/baremetal/src/boot.rs new file mode 100644 index 00000000..09a96c72 --- /dev/null +++ b/baremetal/src/boot.rs @@ -0,0 +1,18 @@ +use core::arch::{global_asm, asm}; + +#[no_mangle] +static PERIPHERALS_BASE_ADDRESS:u32 = crate::peripherals::PERIPHERALS_BASE_ADDRESS as u32; + +global_asm!(include_str!("boot_armv7a.s")); + +extern "C"{ + // declared at startup assembly file + pub fn hang_led()->!; +} + +pub fn get_cpu_execution_mode()->u32{ + let mut mode:u32; + unsafe{asm!("mrs {r}, cpsr", r = out(reg) mode)}; + // only the first 5 bits are relevant for the mode + return mode & 0b1_1111; +} \ No newline at end of file diff --git a/baremetal/src/boot_armv7a.s b/baremetal/src/boot_armv7a.s new file mode 100644 index 00000000..0c2f41bf --- /dev/null +++ b/baremetal/src/boot_armv7a.s @@ -0,0 +1,241 @@ +// alias the PERIPHERALS_BASE_ADDRESS symbol at pointer +// since it is a static global variable from the rust side +.equ PERIPHERALS_BASE_ADDRESS_PTR, PERIPHERALS_BASE_ADDRESS + +.macro ldsctlr reg + mrc p15, 0, \reg, c1, c0, 0 // Read SCTLR +.endm +.macro stsctlr reg + mcr p15, 0, \reg, c1, c0, 0 // Write SCTLR +.endm + +// source - https://developer.arm.com/documentation/ddi0406/c/System-Level-Architecture/System-Control-Registers-in-a-VMSA-implementation/VMSA-System-control-registers-descriptions--in-register-order/SCTLR--System-Control-Register--VMSA?lang=en +.equ SCTLR_MMU_ENABLE, (1<<0) +.equ SCTLR_ENABLE_DATA_CACHE, (1<<2) +.equ SCTLR_ENABLE_BRANCH_PREDICTION, (1<<11) // Does not always exist, if implementation does not support BP or does not support disabling it +.equ SCTLR_ENABLE_INSTRUCTION_CACHE, (1<<12) +.equ SCTLR_ENABLE_AFE, (1<<29) + +// source - https://developer.arm.com/documentation/ddi0488/d/system-control/aarch64-register-descriptions/cpu-extended-control-register--el1 +.equ CPUECTLR_SMPEN, (1<<6) + +// source - https://developer.arm.com/documentation/ddi0406/c/System-Level-Architecture/The-System-Level-Programmers--Model/ARM-processor-modes-and-ARM-core-registers/ARM-processor-modes?lang=en#CIHGHDGI +.equ ARM_SUPERVISOR_MODE, 0x13 +.equ ARM_HYPERVISOR_MODE, 0x1A + +// source - https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/Advanced-SIMD-and-floating-point-support/The-Floating-Point-Exception-Register--FPEXC-?lang=en +.equ FPEXC_VFP_SIMD_ENABLE, (1<<30) + +// source - https://developer.arm.com/documentation/ddi0406/b/System-Level-Architecture/The-System-Level-Programmers--Model/Advanced-SIMD-and-floating-point-support/Enabling-Advanced-SIMD-and-floating-point-support +.equ CPACR_CP10_CP11_ENABLE, (0xF<<20) // Enable floating point and SIMD + +// source - https://developer.arm.com/documentation/den0013/d/The-Memory-Management-Unit/First-level-address-translation +.equ TTB_SECTION_NORMAL, 0x15C06 // normal memory TTB config +.equ TTB_SECTION_DEVICE, 0x10C02 // device memory TTB config +.equ L1_MEMORY_MAP_SIZE, 0x1000 // 4096 u32's +.equ NORMAL_MAP_SIZE, __cached_data_map_size // __uncached_data must by aligned to 1MB + +.section .text._start +.global _start +_start: + mrc p15, 0, r0, c0, c0, 5 // read MPIDR, + and r0, r0, #0b11 // on coretx a72 those bits are the CPU core id + cmp r0, #0 // check for cpu 0 + beq .main_core +.park_loop: + wfe + b .park_loop +.main_core: +// Setup ISR table + ldr r0, =isr_table + ldr r1, =__isr_table_addr + mcr p15, 0, r1, c12, c0, 0 // Write VBAR + // copy the table + ldmia r0!, {{r2,r3,r4,r5,r6,r7,r8,r9}} + stmia r1!, {{r2,r3,r4,r5,r6,r7,r8,r9}} + ldmia r0!, {{r2,r3,r4,r5,r6,r7,r8,r9}} + stmia r1!, {{r2,r3,r4,r5,r6,r7,r8,r9}} + +// Enable HW floating point + mrc p15, 0, r0, c1, c0, 2 // Read CPACR + orr r0, r0, #CPACR_CP10_CP11_ENABLE + mcr p15, 0, r0, c1, c0, 2 // Write CPACR + vmrs r0, fpexc // Read FPEXC + orr r0, r0, #FPEXC_VFP_SIMD_ENABLE + vmsr fpexc, r0 // Write FPEXC + +// Switch to supervisor mode + mrs r0, cpsr // read current status register + mov r4, #0x1F // CPSR mode mask + and r1, r0, r4 // get mode bits + cmp r1, #ARM_HYPERVISOR_MODE + bne .finis_mode_switch + // On cortex a-72 you must enable CPUECTLR.SMPEN bit before touching the mmu and its accesible only in HYPERVISOR mdoe + mrrc p15, 1, r2, r3, c15 // Read CPUECTLR + orr r2, r2, #CPUECTLR_SMPEN + mcrr p15, 1, r2, r3, c15 // Write CPUECTLR + // Switch to Supervisor mode + bic r0, r0, r4 // clear mode bits + orr r0, r0, #ARM_SUPERVISOR_MODE + msr spsr_cxsf, r0 // see msr docs - https://developer.arm.com/documentation/ddi0406/c/System-Level-Architecture/System-Instructions/Alphabetical-list-of-instructions/MSR--register-?lang=en + // Also see this SO - https://stackoverflow.com/questions/15641149/current-program-status-register-exception-modes + ldr r0, =.finis_mode_switch // hold (in ELR_hyp) the address to return to (to make 'eret' working right) + msr ELR_hyp, r0 // save the address in ELR_hyp + eret // apply the mode change (Exception return) + +.finis_mode_switch: +// Invalidate L1 cache + // Disable cache and MMU + ldsctlr r0 + bic r0, r0, #SCTLR_ENABLE_DATA_CACHE + bic r0, r0, #SCTLR_ENABLE_INSTRUCTION_CACHE + bic r0, r0, #SCTLR_MMU_ENABLE + stsctlr r0 + + // invalidate I cache, TLB, branch predictor + mov r0, #0 + mcr p15, 0, r0, c7, c5, 0 // Invalidate I cache + mcr p15, 0, r0, c7, c5, 6 // Invalidate branch prediction + mcr p15, 0, r0, c8, c7, 0 // Invalidate TLB + + // invalidate D cache + // This is a machine idependent code to invalidate the D cache, + // On most of the new armv7a and armv8a there is not need for this + // and the CPU will boot just fine without it, but I want this code to portable so Im including this + // and trying to document the hell out of it + mov r0, #0 // Select L1 cache + mcr p15, 2, r0, c0, c0, 0 // Write CSSELR + mrc p15, 1, r0, c0, c0, 0 // Read CCSIDR + and r1, r0, #0x7 // Extract line size (3bit), value is (log2(number_of_words)) - 4 (Some old docs are wrong and says its 2) + add r1, r1, #4 // add 4 to make r1 the number of bits to shift to get the number of bytes in line, + // thats how we calculate the offset of set om tje DCCISW command + mov r2, #0x7FFF // mask to get the number of lines in cache + and r3, r2, r0, lsr #13 // r3 = r2 & (r0 << 13) -> lines_in_cache - 1 + mov r2, #0x3FF // mask to get the cache associativity + and r2, r2, r0, lsr #3 // r2 &= r0 << 3 -> associativity - 1 + clz r4, r2 // calc the leading zeros in u32 bits, this will indicate where to place the way param in the DCCISW command + mov r5, #0 // way loop counter +.way_loop: + mov r6, #0 // set loop counter +.set_loop: + orr r7, r0, r5, lsl r4 // set way + orr r7, r7, r6, lsl r1 // set set + mcr p15, 0, r7, c7, c6, 2 // Write DCCISW + add r6, r6, #1 // inc set + cmp r6, r3 // check for last set + ble .set_loop // if not iter set_loop + add r5, r5, #1 // else next way + cmp r5, r2 // check last way + ble .way_loop // if not iter way_loop + + // sync barriers after the invalidations and clearing + dsb + isb + +// initialize translation tables + mov r0, 0 // use short descriptor, base address is 16kb alligned + mcr p15, 0, r0, c2, c0, 2 // Write TTBCR + + ldr r0, =0x55555555 // set all domains as clients (accesses are checked againt the translation table) + mcr p15, 0, r0, c3, c0, 0 // Set DACR + + ldsctlr r0 + bic r0, r0, #SCTLR_ENABLE_AFE + stsctlr r0 + + ldr r0, =l1_memory_map_1t1_table + mov r1, #0x2B // rest of the config for TTBR0 + orr r1, r0, r1 // combine the 16KB alligned address with the rest of the config + mcr p15, 0, r1, c2, c0, 0 // Write TTBR0 + + //set up memory map + mov r1, #0 // map start index + ldr r2, =TTB_SECTION_NORMAL + ldr r3, =NORMAL_MAP_SIZE + bl init_mmu_map_section // init normal memory + ldr r2, =TTB_SECTION_DEVICE + ldr r3, =L1_MEMORY_MAP_SIZE // the rest of the map is for device memory + bl init_mmu_map_section // init device memory + +// enable MMU and caches + ldsctlr r0 + // the cortex a72 does not have an enable BP, but other cpus might have + orr r0, r0, #SCTLR_ENABLE_DATA_CACHE + orr r0, r0, #SCTLR_ENABLE_INSTRUCTION_CACHE + orr r0, r0, #SCTLR_MMU_ENABLE + stsctlr r0 + dsb + isb + +// clear bss + ldr r0, =__bss_start + ldr r1, =__bss_end + mov r2, #0 +.init_bss_loop: + cmp r0, r1 + beq .finish_bss + str r2, [r0], #4 // increment r0 by 4 (dword) + b .init_bss_loop +.finish_bss: +// setting up the stack + ldr sp, =__cpu0_stack_start + b main + +// Corrupts r4, r1 = end index +// r0 - L1 map ptr +// r1 - map start index +// r2 - ttb section configuration +// r3 - map end index +init_mmu_map_section: + mov r4, r1, lsl #20 // move r1 shifted left 20 to r3 + orr r4, r4, r2 // r4 = map section + str r4, [r0, r1, lsl #2] // store r2 to r0 + (r1 * 4) + add r1, r1, #1 // increment current index + cmp r1, r3 // test current index vs end index + blt init_mmu_map_section // loop if current index < end index + bx lr // return to caller + +.global hang_led +hang_led: + ldr r3, =PERIPHERALS_BASE_ADDRESS_PTR + ldr r0, [r3] // Deref the ptr to get the pperipherals address + ldr r2, =0x200008 // GPFSEL2 MMIO register offset from base address + add r2, r2, r0 // add both to create GPFSEL2 address + mov r1, #1<<3 // Pin 21 as output mode + str r1, [r2] // write to register + ldr r2, =0x20001C // GPSET0 MMIO register offset from base address + add r2, r2, r0 // add both to create GPSET0 address + mov r1, #1<<21 // Pin 21 offset + str r1, [r2] // Write to register +.hang_loop: + wfe // easy on the busy waiting + b .hang_loop // verify no return + +// interrupts jump table (ISR TABLE) +isr_table: + ldr pc, _reset_h + ldr pc, _undefined_instruction_vector_h + ldr pc, _software_interrupt_vector_h + ldr pc, _prefetch_abort_vector_h + ldr pc, _data_abort_vector_h + ldr pc, _unused_handler_h + ldr pc, _interrupt_vector_h + ldr pc, _fast_interrupt_vector_h + +_reset_h: .word hang_led +_undefined_instruction_vector_h: .word hang_led +_software_interrupt_vector_h: .word hang_led +_prefetch_abort_vector_h: .word hang_led +_data_abort_vector_h: .word hang_led +_unused_handler_h: .word hang_led +_interrupt_vector_h: .word hang_led +_fast_interrupt_vector_h: .word hang_led + + +.section .data +// This table must aligned for 16k address +.balign 16384 +l1_memory_map_1t1_table: + .rept L1_MEMORY_MAP_SIZE + .word 0 + .endr \ No newline at end of file diff --git a/baremetal/src/configuration.rs b/baremetal/src/configuration.rs new file mode 100644 index 00000000..765200a6 --- /dev/null +++ b/baremetal/src/configuration.rs @@ -0,0 +1,37 @@ +pub mod joypad{ + use lib_gb::keypad::button::Button; + + pub const fn button_to_bcm_pin(button:Button)->u8{ + match button{ + Button::A => 0, + Button::B => 7, + Button::Start => 6, + Button::Select => 5, + Button::Up => 22, + Button::Down => 17, + Button::Right => 4, + Button::Left => 27, + } + } +} + +pub mod display{ + pub const RESET_PIN_BCM:u8 = 13; + pub const LED_PIN_BCM:u8 = 25; +} + +pub mod peripherals{ + pub const CORE_FREQ:u32 = 400_000_000; + pub const MINI_UART_BAUDRATE:u32 = 115200; + pub const SPI0_DC_BCM_PIN:u8 = 12; + pub const DMA_RX_CHANNEL_NUMBER:u8 = 7; + pub const DMA_TX_CHANNEL_NUMBER:u8 = 1; + pub const FAST_SPI_CLOCK_DIVISOR:u32 = 6; // the smaller the faster + pub const INIT_SPI_CLOCK_DIVISOR:u32 = 34; // slow clock for verifiying the initialization goes smooth with no corruptions +} + +pub mod emulation{ + pub const ROM:&'static [u8] = include_bytes!("../../Dependencies/TetrisDX.gbc"); // Path is relative to the build + pub const TURBO:u8 = 1; // Will speed up the emulation * X + pub const FRAME_LIMITER:u32 = 0; // Will filter every frame X frames +} \ No newline at end of file diff --git a/baremetal/src/drivers/gpio_joypad.rs b/baremetal/src/drivers/gpio_joypad.rs new file mode 100644 index 00000000..0aa33eaa --- /dev/null +++ b/baremetal/src/drivers/gpio_joypad.rs @@ -0,0 +1,42 @@ +use lib_gb::keypad::{joypad::{Joypad, NUM_OF_KEYS},joypad_provider::JoypadProvider, button::Button}; + +use crate::peripherals::{PERIPHERALS, GpioPin, Mode, GpioPull}; + +const READ_THRESHOLD:u32 = 0x1000; + +pub struct GpioJoypadProvider{ + input_pins: [GpioPin; NUM_OF_KEYS], + read_threshold_counter: u32 +} + +impl GpioJoypadProvider{ + pub fn new(mapper:impl Fn(Button)->u8)->Self{ + let gpio = unsafe{PERIPHERALS.get_gpio()}; + let mut input_pins = [ + gpio.take_pin(mapper(Button::A), Mode::Input), + gpio.take_pin(mapper(Button::B), Mode::Input), + gpio.take_pin(mapper(Button::Start), Mode::Input), + gpio.take_pin(mapper(Button::Select), Mode::Input), + gpio.take_pin(mapper(Button::Up), Mode::Input), + gpio.take_pin(mapper(Button::Down), Mode::Input), + gpio.take_pin(mapper(Button::Right), Mode::Input), + gpio.take_pin(mapper(Button::Left), Mode::Input), + ]; + for i in &mut input_pins{ + i.set_pull(GpioPull::None); + } + return Self { input_pins, read_threshold_counter: 0 }; + } +} + +impl JoypadProvider for GpioJoypadProvider{ + fn provide(&mut self, joypad:&mut Joypad){ + self.read_threshold_counter = (self.read_threshold_counter + 1) % READ_THRESHOLD; + if self.read_threshold_counter != 0 { + return; + } + for i in 0..joypad.buttons.len(){ + joypad.buttons[i] = self.input_pins[i].read_state(); + } + } +} \ No newline at end of file diff --git a/baremetal/src/drivers/ili9341_gfx_device.rs b/baremetal/src/drivers/ili9341_gfx_device.rs new file mode 100644 index 00000000..876d2ce9 --- /dev/null +++ b/baremetal/src/drivers/ili9341_gfx_device.rs @@ -0,0 +1,227 @@ +use lib_gb::ppu::{gb_ppu::{SCREEN_WIDTH, SCREEN_HEIGHT}, gfx_device::{GfxDevice, Pixel}}; + +use crate::peripherals::{GpioPin, Mode, Timer, Spi0, PERIPHERALS}; + +const ILI9341_SCREEN_WIDTH:usize = 320; +const ILI9341_SCREEN_HEIGHT:usize = 240; +const SCALE:f32 = 5.0 / 3.0; // maximum scale to fit the ili9341 screen +pub(super) const TARGET_SCREEN_WIDTH:usize = (SCREEN_WIDTH as f32 * SCALE) as usize; +pub(super) const TARGET_SCREEN_HEIGHT:usize = (SCREEN_HEIGHT as f32 * SCALE) as usize; +const FRAME_BUFFER_X_OFFSET:usize = (ILI9341_SCREEN_WIDTH - TARGET_SCREEN_WIDTH) / 2; + +pub const SPI_BUFFER_SIZE:usize = TARGET_SCREEN_HEIGHT * TARGET_SCREEN_WIDTH * core::mem::size_of::(); + +#[repr(u8)] +enum Ili9341Command{ + SoftwareReset = 0x01, + SleepOut = 0x11, + GammaSet = 0x26, + DisplayOff = 0x28, + DisplayOn = 0x29, + ColumnAddressSet = 0x2A, // Set curosr X value + PageAddressSet = 0x2B, // Set cursor Y value + MemoryWrite = 0x2C, + MemoryAccessControl = 0x36, + PixelFormatSet = 0x3A, + FrameRateControl = 0xB1, + DisplayFunctionControl = 0xB6, + PowerControl1 = 0xC0, + PowerControl2 = 0xC1, + VcomControl1 = 0xC5, + VcomControl2 = 0xC7, + PowerControlA = 0xCB, + PowerControlB = 0xCF, + PossitiveGammaCorrection = 0xE0, + NegativeGammaCorrection = 0xE1, + DriverTimingControlA = 0xE8, + DriverTimingControlB = 0xEA, + PowerOnSequenceControl = 0xED, + Enable3G = 0xF2, +} + +struct Ili9341Contoller{ + spi:Spi0, + timer: Timer, + led_pin: GpioPin, + reset_pin: GpioPin +} + +impl Ili9341Contoller{ + const CLEAN_BUFFER:[u8;ILI9341_SCREEN_HEIGHT * ILI9341_SCREEN_WIDTH * core::mem::size_of::()] = [0; ILI9341_SCREEN_HEIGHT * ILI9341_SCREEN_WIDTH * core::mem::size_of::()]; + + pub fn new(reset_pin_bcm:u8, led_pin_bcm:u8)->Self{ + log::info!("Initalizing with screen size width: {}, hight: {}", TARGET_SCREEN_WIDTH, TARGET_SCREEN_HEIGHT); + + let gpio = unsafe{PERIPHERALS.get_gpio()}; + let reset_pin = gpio.take_pin(reset_pin_bcm, Mode::Output); + let led_pin = gpio.take_pin(led_pin_bcm, Mode::Output); + let spi = unsafe{PERIPHERALS.take_spi0()}; + + let mut controller = Ili9341Contoller { spi, led_pin, reset_pin, timer: unsafe{PERIPHERALS.take_timer()}}; + + // toggling the reset pin to initalize the lcd + controller.reset_pin.set_high(); + controller.sleep_ms(120); + controller.reset_pin.set_low(); + controller.sleep_ms(120); + controller.reset_pin.set_high(); + controller.sleep_ms(120); + + + // This code snippets is ofcourse wrriten by me but took heavy insperation from fbcp-ili9341 (https://github.com/juj/fbcp-ili9341) + // I used the ili9341 application notes and the fbcp-ili9341 implementation in order to write it all down + // And later I twicked some params specific to my display (http://www.lcdwiki.com/3.2inch_SPI_Module_ILI9341_SKU:MSP3218) + + // There is another implementation in rust for an ili9341 controller which is much simpler and uses those commands: + // Sleepms(5), SoftwareReset, Sleepms(120), MemoryAccessControl, PixelFormatSet, SleepOut, Sleepms(5), DisplayOn + // minimal config based on rust ili9341 lib (https://github.com/yuri91/ili9341-rs) + + // fbcp-ili9341 inspired implementation: + /*---------------------------------------------------------------------------------------------------------------------- */ + // Reset the screen + controller.spi.write(Ili9341Command::SoftwareReset as u8,&[]); + controller.sleep_ms(5); + controller.spi.write(Ili9341Command::DisplayOff as u8,&[]); + + // Some power stuff, probably uneccessary but just for sure + controller.spi.write(Ili9341Command::PowerControlA as u8, &[0x39, 0x2C, 0x0, 0x34, 0x2]); + controller.spi.write(Ili9341Command::PowerControlB as u8, &[0x0, 0xC1, 0x30]); + controller.spi.write(Ili9341Command::DriverTimingControlA as u8, &[0x85, 0x0, 0x78]); + controller.spi.write(Ili9341Command::DriverTimingControlB as u8, &[0x0, 0x0]); + controller.spi.write(Ili9341Command::PowerOnSequenceControl as u8, &[0x64, 0x3, 0x12, 0x81]); + controller.spi.write(Ili9341Command::PowerControl1 as u8, &[0x23]); + controller.spi.write(Ili9341Command::PowerControl2 as u8,&[0x10]); + controller.spi.write(Ili9341Command::VcomControl1 as u8, &[0xE3, 0x28]); + controller.spi.write(Ili9341Command::VcomControl2 as u8, &[0x86]); + + // Configuring the screen + controller.spi.write(Ili9341Command::MemoryAccessControl as u8, &[0x28]); // This command tlit the screen 90 degree and set pixel to BGR order + controller.spi.write(Ili9341Command::PixelFormatSet as u8, &[0x55]); // set pixel format to 16 bit per pixel; + controller.spi.write(Ili9341Command::FrameRateControl as u8, &[0x0, 0x10 /*According to the docs this is 119 hrz, setting this option in order to avoid screen tearing on rpi zero2 */]); + controller.spi.write(Ili9341Command::DisplayFunctionControl as u8, &[0x8, 0x82, 0x27]); + + // Gamma values - pretty sure its redundant + controller.spi.write(Ili9341Command::Enable3G as u8, &[0x2]); + controller.spi.write(Ili9341Command::GammaSet as u8, &[0x1]); + controller.spi.write(Ili9341Command::PossitiveGammaCorrection as u8,&[0x0F, 0x31, 0x2B, 0x0C, 0x0E, 0x08, 0x4E, 0xF1, 0x37, 0x07, 0x10, 0x03, 0x0E, 0x09, 0x00]); + controller.spi.write(Ili9341Command::NegativeGammaCorrection as u8, &[0x00, 0x0E, 0x14, 0x03, 0x11, 0x07, 0x31, 0xC1, 0x48, 0x08, 0x0F, 0x0C, 0x31, 0x36, 0x0F]); + + // Turn screen on + controller.spi.write(Ili9341Command::SleepOut as u8,&[]); + controller.sleep_ms(120); + controller.spi.write(Ili9341Command::DisplayOn as u8,&[]); + /*---------------------------------------------------------------------------------------------------------------------- */ + //End of fbcp-ili9341 inpired implementation + + log::info!("Finish configuring ili9341"); + + // turn backlight on + controller.led_pin.set_high(); + + // Clear screen + controller.spi.write(Ili9341Command::ColumnAddressSet as u8, &[0,0,((ILI9341_SCREEN_WIDTH -1) >> 8) as u8, ((ILI9341_SCREEN_WIDTH -1) & 0xFF) as u8]); + controller.spi.write(Ili9341Command::PageAddressSet as u8, &[0,0,((ILI9341_SCREEN_HEIGHT -1) >> 8) as u8, ((ILI9341_SCREEN_HEIGHT -1) & 0xFF) as u8]); + // using write and not write buffer since this is not the correct size + controller.spi.write(Ili9341Command::MemoryWrite as u8, &Self::CLEAN_BUFFER); + + // need to sleep before changing the clock after transferring pixels to the lcd + controller.sleep_ms(120); + + controller.spi.fast_mode(); + + log::info!("finish ili9341 device init"); + + return controller; + } + + + pub fn write_frame_buffer(&mut self, buffer:&[u16;SCREEN_HEIGHT*SCREEN_WIDTH]){ + let mut scaled_buffer: [u8;TARGET_SCREEN_HEIGHT * TARGET_SCREEN_WIDTH * 2] = [0;TARGET_SCREEN_HEIGHT * TARGET_SCREEN_WIDTH * 2]; + unsafe{image_inter::scale_bilinear::(buffer.as_ptr(), scaled_buffer.as_mut_ptr())}; + + let end_x_index = TARGET_SCREEN_WIDTH + FRAME_BUFFER_X_OFFSET - 1; + self.spi.write(Ili9341Command::ColumnAddressSet as u8, &[ + (FRAME_BUFFER_X_OFFSET >> 8) as u8, + (FRAME_BUFFER_X_OFFSET & 0xFF) as u8, + (end_x_index >> 8) as u8, + (end_x_index & 0xFF) as u8 + ]); + self.spi.write(Ili9341Command::PageAddressSet as u8, &[ + 0x0, 0x0, + ((TARGET_SCREEN_HEIGHT - 1) >> 8) as u8, + ((TARGET_SCREEN_HEIGHT - 1) & 0xFF) as u8 + ]); + + self.spi.write_dma(Ili9341Command::MemoryWrite as u8, &scaled_buffer); + } + + fn sleep_ms(&mut self, milliseconds_to_sleep:u64){ + let target_wait_time = core::time::Duration::from_millis(milliseconds_to_sleep); + self.timer.wait(target_wait_time); + } +} + +impl Drop for Ili9341Contoller{ + fn drop(&mut self) { + self.spi.write(Ili9341Command::DisplayOff as u8, &[]); + self.led_pin.set_low(); + self.reset_pin.set_high(); + self.sleep_ms(1); + self.reset_pin.set_low(); + } +} + +pub struct Ili9341GfxDevice{ + ili9341_controller:Ili9341Contoller, + turbo_mul:u8, + turbo_frame_counter:u8, + + frame_limiter:u32, + frames_counter: u32, + time_counter:core::time::Duration, +} + +impl Ili9341GfxDevice{ + pub fn new(reset_pin_bcm:u8, led_pin_bcm:u8, turbo_mul:u8, frame_limiter:u32)->Self{ + let mut ili9341_controller = Ili9341Contoller::new(reset_pin_bcm, led_pin_bcm); + // reset the timer + let _ = ili9341_controller.timer.tick(); + + Ili9341GfxDevice { + ili9341_controller,frames_counter:0, + time_counter: core::time::Duration::ZERO, + turbo_mul, turbo_frame_counter:0, frame_limiter + } + } +} + + +const EXPECTED_FRAME_DURATION: f64 = 1.0f64/60.0f64; +impl GfxDevice for Ili9341GfxDevice{ + fn swap_buffer(&mut self, buffer:&[Pixel; SCREEN_HEIGHT * SCREEN_WIDTH]) { + self.turbo_frame_counter = (self.turbo_frame_counter + 1) % self.turbo_mul; + if self.turbo_frame_counter != 0{ + return; + } + + if self.frames_counter & self.frame_limiter == 0{ + self.ili9341_controller.write_frame_buffer(&buffer); + } + + // measure fps + self.frames_counter += 1; + let mut duration = self.ili9341_controller.timer.tick().as_secs_f64(); + + // block for the frame duration + while duration < EXPECTED_FRAME_DURATION{ + duration += self.ili9341_controller.timer.tick().as_secs_f64(); + } + + self.time_counter += core::time::Duration::from_secs_f64(duration); + if self.time_counter.as_millis() > 1000{ + log::debug!("FPS: {}", self.frames_counter); + self.frames_counter = 0; + self.time_counter = core::time::Duration::ZERO; + } + } +} \ No newline at end of file diff --git a/baremetal/src/drivers/mod.rs b/baremetal/src/drivers/mod.rs new file mode 100644 index 00000000..8afcf7cb --- /dev/null +++ b/baremetal/src/drivers/mod.rs @@ -0,0 +1,5 @@ +mod gpio_joypad; +mod ili9341_gfx_device; + +pub use gpio_joypad::*; +pub use ili9341_gfx_device::*; \ No newline at end of file diff --git a/baremetal/src/logging.rs b/baremetal/src/logging.rs new file mode 100644 index 00000000..e89ed733 --- /dev/null +++ b/baremetal/src/logging.rs @@ -0,0 +1,52 @@ +use core::fmt::Write; + +use crate::{peripherals::{MiniUart, PERIPHERALS}, syncronization::Mutex}; + +use log::{Record, Metadata, Log, LevelFilter}; + +struct UartDevice(MiniUart); +impl Write for UartDevice{ + fn write_str(&mut self, s: &str) -> core::fmt::Result { + self.0.send(s.as_bytes()); + return core::fmt::Result::Ok(()); + } +} + +pub struct UartLogger{ + uart_mutex:Mutex +} + +static mut LOGGER:Option = None; + +impl UartLogger{ + pub fn init(max_log_level:LevelFilter){ + let uart = unsafe{PERIPHERALS.take_mini_uart()}; + let logger:&'static UartLogger = unsafe{ + LOGGER = Some(UartLogger{uart_mutex:Mutex::new(UartDevice(uart))}); + LOGGER.as_ref().unwrap() + }; + + // On armv7a calling set_logger corrupt the program and make it unresponsive (not even panicking) + // I dont know why but I believe its due to the MMU not being turned on + // Now that I turned it on I believe this should work, in case it hangs again use log::set_logger_racy + if let Err(error_message) = log::set_logger(logger){ + logger.uart_mutex.lock(|u|u.write_fmt(format_args!("{}", error_message)).unwrap()); + core::panic!("Error initializng logger"); + } + log::set_max_level(max_log_level); + } +} + +impl Log for UartLogger{ + fn enabled(&self, metadata: &Metadata) -> bool { + unsafe{LOGGER.is_some() && metadata.level() >= log::max_level()} + } + + fn log(&self, record: &Record) { + let level = record.level(); + let log_message = *record.args(); + self.uart_mutex.lock(|uart|uart.write_fmt(format_args!("{} - {}\n\r", level, log_message)).unwrap()); + } + + fn flush(&self) {} +} \ No newline at end of file diff --git a/baremetal/src/main.rs b/baremetal/src/main.rs new file mode 100644 index 00000000..b26745b0 --- /dev/null +++ b/baremetal/src/main.rs @@ -0,0 +1,50 @@ +#![no_main] +#![no_std] + +mod boot; +mod syncronization; +mod peripherals; +mod logging; +mod drivers; +mod configuration; + +use core::panic::PanicInfo; + +use lib_gb::{apu::audio_device::AudioDevice, machine::{gameboy::GameBoy, mbc_initializer::initialize_mbc}, mmu::external_memory_bus::Bootrom}; + +use crate::{drivers::{GpioJoypadProvider, Ili9341GfxDevice}, peripherals::PERIPHERALS, configuration::{display::*, joypad::button_to_bcm_pin, emulation::*}}; + +struct BlankAudioDevice; +impl AudioDevice for BlankAudioDevice{ + fn push_buffer(&mut self, _buffer:&[lib_gb::apu::audio_device::StereoSample; lib_gb::apu::audio_device::BUFFER_SIZE]) {} +} + +// This function is no regular main. +// It will not return and will be jumped to from the _start proc in the boot code +// it is unmangled and exposed as a "C" function in order for the _start proc to call it +#[no_mangle] +pub extern "C" fn main()->!{ + unsafe{PERIPHERALS.set_core_clock()}; + logging::UartLogger::init(log::LevelFilter::Debug); + log::info!("Initialized logger"); + log::info!("running at exec mode: {:#X}", boot::get_cpu_execution_mode()); + + let mbc = initialize_mbc(ROM, None, None); + let joypad_provider = GpioJoypadProvider::new(button_to_bcm_pin); + + let gfx = Ili9341GfxDevice::new(RESET_PIN_BCM, LED_PIN_BCM, TURBO, FRAME_LIMITER); + log::info!("Init joypad"); + let mut gameboy = GameBoy::new(mbc, joypad_provider, BlankAudioDevice, gfx, Bootrom::None, None); + log::info!("Initialized gameboy!"); + loop{ + gameboy.cycle_frame(); + } +} + +#[panic_handler] +fn panic(info:&PanicInfo)->!{ + log::error!("An error has occoured!"); + log::error!("{}", info); + + unsafe{boot::hang_led()}; +} \ No newline at end of file diff --git a/baremetal/src/peripherals/dma.rs b/baremetal/src/peripherals/dma.rs new file mode 100644 index 00000000..47bc8a68 --- /dev/null +++ b/baremetal/src/peripherals/dma.rs @@ -0,0 +1,273 @@ +use core::ptr::write_volatile; + +use super::{utils::{MmioReg32, compile_time_size_assert, memory_barrier}, gpu::GpuMemory, PERIPHERALS_BASE_ADDRESS, spi::SPI_BUFFER_SIZE}; + +// The DMA control block registers are in a 32 byte alligned addresses so the stracture mapping them needs to be as well +// in order for me to cast some bytes to this stuct (at least I think so) +// Not valid for DMA4 channels +#[repr(C, align(32))] +struct DmaControlBlock{ + transfer_information:MmioReg32, + source_address:MmioReg32, + destination_address:MmioReg32, + transfer_length:MmioReg32, + _stride:MmioReg32, // Not avalibale on the lite channels + next_control_block_address:MmioReg32, + _reserved:[u32;2] +} +compile_time_size_assert!(DmaControlBlock, 0x20); + +// Since Im casting an arbitary pointer to this struct it must be alligned by 4 bytes (with no gaps as well) +#[repr(C, align(4))] +struct DmaRegistersAccess{ + control_status:MmioReg32, + control_block_address:MmioReg32, + transfer_information:MmioReg32, + source_address:MmioReg32, + destination_address:MmioReg32, + transfer_length:MmioReg32, + _stride:MmioReg32, // Not avalibale on the lite channels + next_control_block_address:MmioReg32, + debug:MmioReg32 +} +compile_time_size_assert!(DmaRegistersAccess, 0x24); + +pub struct DmaSpiTransferer{ + tx_dma:&'static mut DmaRegistersAccess, + rx_dma:&'static mut DmaRegistersAccess, + tx_control_block_memory:GpuMemory, + rx_control_block_memory:GpuMemory, + source_buffer_memory:GpuMemory, + dma_dynamic_memory:GpuMemory, + dma_constant_memory:GpuMemory, + dma_enable_register:&'static mut MmioReg32, +} + +impl DmaSpiTransferer{ + const BCM_DMA0_ADDRESS:usize = PERIPHERALS_BASE_ADDRESS + 0x7_000; + const BCM_DMA_ENABLE_REGISTER_ADDRESS:usize = Self::BCM_DMA0_ADDRESS + 0xFF0; + const DMA_CHANNEL_REGISTERS_SIZE:usize = 0x100; + + const DMA_CS_RESET:u32 = 1 << 31; + const DMA_CS_END:u32 = 1 << 1; + const DMA_CS_ACTIVE:u32 = 1; + + const DMA_TI_SRC_DREQ:u32 = 1 << 10; + const DMA_TI_SRC_INC:u32 = 1 << 8; + const DMA_TI_DEST_IGNORE:u32 = 1 << 7; + const DMA_TI_DEST_DREQ:u32 = 1 << 6; + const DMA_TI_DEST_INC:u32 = 1 << 4; + const DMA_TI_WAIT_RESP:u32 = 1 << 3; + + const DMA_DMA0_CS_PHYS_ADDRESS:u32 = 0x7E00_7000; + const DMA_DMA0_CONBLK_AD_PHYS_ADDRESS:u32 = 0x7E00_7004; + const fn dma_ti_permap(peripherial_mapping:u8)->u32{(peripherial_mapping as u32) << 16} + + const DMA_CONTROL_BLOCKS_PER_TRANSFER:u32 = 4; + const DMA_CONSTANT_MEMORY_SIZE:u32 = (core::mem::size_of::() * 2) as u32; + const DMA_SPI_HEADER_SIZE:u32 = core::mem::size_of::() as u32; + const RX_CHANNEL_NUMBER:u8 = crate::configuration::peripherals::DMA_RX_CHANNEL_NUMBER; + const TX_CHANNEL_NUMBER:u8 = crate::configuration::peripherals::DMA_TX_CHANNEL_NUMBER; + + const MAX_DMA_SPI_TRANSFER:usize = 0xFFE0; // must be smaller than max u16 and better be alligned for 32 bytes + const DMA_SPI_NUM_CHUNKS:usize = (SPI_BUFFER_SIZE / Self::MAX_DMA_SPI_TRANSFER) + ((SPI_BUFFER_SIZE % Self::MAX_DMA_SPI_TRANSFER) != 0) as usize; + const DMA_SPI_CHUNK_SIZE:usize = (SPI_BUFFER_SIZE / Self::DMA_SPI_NUM_CHUNKS) + 4; + const DMA_SPI_TRANSFER_SIZE:usize = Self::DMA_SPI_CHUNK_SIZE * Self::DMA_SPI_NUM_CHUNKS; + const DMA_TI_PERMAP_SPI_TX:u8 = 6; + const DMA_TI_PERMAP_SPI_RX:u8 = 7; + + const DMA_SPI0_CS_PHYS_ADDRESS:u32 = 0x7E20_4000; + const DMA_SPI0_FIFO_PHYS_ADDRESS:u32 = 0x7E20_4004; + + pub fn new(spi_enable_dma_flag:u32)->Self{ + let tx_registers = unsafe{&mut *((Self::BCM_DMA0_ADDRESS + (Self::TX_CHANNEL_NUMBER as usize * Self::DMA_CHANNEL_REGISTERS_SIZE)) as *mut DmaRegistersAccess)}; + let rx_registers = unsafe{&mut *((Self::BCM_DMA0_ADDRESS + (Self::RX_CHANNEL_NUMBER as usize * Self::DMA_CHANNEL_REGISTERS_SIZE)) as *mut DmaRegistersAccess)}; + + let dma_tx_control_block_memory = GpuMemory::allocate( + core::mem::size_of::() as u32 * Self::DMA_CONTROL_BLOCKS_PER_TRANSFER * Self::DMA_SPI_CHUNK_SIZE as u32 + ); + let dma_rx_control_block_memory = GpuMemory::allocate( + core::mem::size_of::() as u32 * Self::DMA_SPI_NUM_CHUNKS as u32 + ); + let dma_source_buffer_memory = GpuMemory::allocate((Self::DMA_SPI_TRANSFER_SIZE) as u32); + let dma_dynamic_memory = GpuMemory::allocate((core::mem::size_of::() * Self::DMA_SPI_NUM_CHUNKS) as u32); + let dma_constant_memory = GpuMemory::allocate(Self::DMA_CONSTANT_MEMORY_SIZE); + + log::info!("Finish allocate gpu mem"); + let dma_enable_register = unsafe{&mut *(Self::BCM_DMA_ENABLE_REGISTER_ADDRESS as *mut MmioReg32)}; + + unsafe{ + // setup constant data + let ptr = dma_constant_memory.virtual_address_ptr as *mut u32; + write_volatile(ptr, spi_enable_dma_flag); // this int enable spi with dma + write_volatile(ptr.add(1), Self::DMA_CS_ACTIVE | Self::DMA_CS_END); // this int starts the dma (set active and wrtie to end to reset it) + + // enable the rx & tx dma channels + dma_enable_register.write(dma_enable_register.read() | 1 << Self::TX_CHANNEL_NUMBER | 1 << Self::RX_CHANNEL_NUMBER); + + //reset the dma channels + tx_registers.control_status.write(Self::DMA_CS_RESET); + rx_registers.control_status.write(Self::DMA_CS_RESET); + + // memset the memory + core::ptr::write_bytes(dma_rx_control_block_memory.virtual_address_ptr as *mut u8, 0, dma_rx_control_block_memory.size as usize); + core::ptr::write_bytes(dma_tx_control_block_memory.virtual_address_ptr as *mut u8, 0, dma_tx_control_block_memory.size as usize); + core::ptr::write_bytes(dma_source_buffer_memory.virtual_address_ptr as *mut u8, 0, dma_source_buffer_memory.size as usize); + core::ptr::write_bytes(dma_dynamic_memory.virtual_address_ptr as *mut u8, 0, dma_dynamic_memory.size as usize); + } + + log::info!("Finish init gpu mem"); + + let mut dma_controller = Self { + tx_dma: tx_registers, + rx_dma: rx_registers, + rx_control_block_memory:dma_rx_control_block_memory, + tx_control_block_memory:dma_tx_control_block_memory, + source_buffer_memory:dma_source_buffer_memory, + dma_dynamic_memory, + dma_constant_memory, + dma_enable_register + }; + + unsafe{dma_controller.init_dma_control_blocks()}; + + log::info!("Initialized dma contorller"); + + return dma_controller; + } + + unsafe fn init_dma_control_blocks(&mut self) { + let mut rx_control_block = &mut *(self.rx_control_block_memory.virtual_address_ptr as *mut DmaControlBlock); + rx_control_block.transfer_information.write(Self::dma_ti_permap(Self::DMA_TI_PERMAP_SPI_RX) | Self::DMA_TI_SRC_DREQ | Self::DMA_TI_DEST_IGNORE); + rx_control_block.source_address.write(Self::DMA_SPI0_FIFO_PHYS_ADDRESS); + rx_control_block.destination_address.write(0); + rx_control_block.transfer_length.write(Self::DMA_SPI_CHUNK_SIZE as u32 - Self::DMA_SPI_HEADER_SIZE); // without the 4 byte header + rx_control_block.next_control_block_address.write(0); + + let tx_control_block = &mut *(self.tx_control_block_memory.virtual_address_ptr as *mut DmaControlBlock); + tx_control_block.transfer_information.write(Self::dma_ti_permap(Self::DMA_TI_PERMAP_SPI_TX) | Self::DMA_TI_DEST_DREQ | Self::DMA_TI_SRC_INC | Self::DMA_TI_WAIT_RESP); + tx_control_block.source_address.write(self.source_buffer_memory.bus_address); + tx_control_block.destination_address.write(Self::DMA_SPI0_FIFO_PHYS_ADDRESS); + tx_control_block.transfer_length.write(Self::DMA_SPI_CHUNK_SIZE as u32); + tx_control_block.next_control_block_address.write(0); + for i in 1..Self::DMA_SPI_NUM_CHUNKS{ + let tx_cb_index = i * Self::DMA_CONTROL_BLOCKS_PER_TRANSFER as usize; + let set_tx_cb_index = tx_cb_index + 1; + let disable_tx_cb_index = set_tx_cb_index + 1; + let start_tx_cb_index = disable_tx_cb_index + 1; + + let tx_control_block = &mut *((self.tx_control_block_memory.virtual_address_ptr as *mut DmaControlBlock).add(tx_cb_index)); + tx_control_block.transfer_information.write(Self::dma_ti_permap(Self::DMA_TI_PERMAP_SPI_TX) | Self::DMA_TI_DEST_DREQ | Self::DMA_TI_SRC_INC | Self::DMA_TI_WAIT_RESP); + tx_control_block.source_address.write(self.source_buffer_memory.bus_address + (i * Self::DMA_SPI_CHUNK_SIZE) as u32); + tx_control_block.destination_address.write(Self::DMA_SPI0_FIFO_PHYS_ADDRESS); + tx_control_block.transfer_length.write(Self::DMA_SPI_CHUNK_SIZE as u32); + tx_control_block.next_control_block_address.write(0); + + let set_dma_tx_address = &mut *((self.tx_control_block_memory.virtual_address_ptr as *mut DmaControlBlock).add(set_tx_cb_index)); + let disable_dma_tx_address = &mut *((self.tx_control_block_memory.virtual_address_ptr as *mut DmaControlBlock).add(disable_tx_cb_index)); + let start_dma_tx_address = &mut *((self.tx_control_block_memory.virtual_address_ptr as *mut DmaControlBlock).add(start_tx_cb_index)); + + rx_control_block.next_control_block_address.write(self.tx_control_block_memory.bus_address + (set_tx_cb_index * core::mem::size_of::()) as u32); + + write_volatile((self.dma_dynamic_memory.virtual_address_ptr as *mut u32).add(i), self.tx_control_block_memory.bus_address + (tx_cb_index * core::mem::size_of::()) as u32); + + set_dma_tx_address.transfer_information.write(Self::DMA_TI_SRC_INC | Self::DMA_TI_DEST_INC | Self::DMA_TI_WAIT_RESP); + set_dma_tx_address.source_address.write(self.dma_dynamic_memory.bus_address + (i as u32 * Self::DMA_CONTROL_BLOCKS_PER_TRANSFER)); + set_dma_tx_address.destination_address.write(Self::DMA_DMA0_CONBLK_AD_PHYS_ADDRESS + (Self::TX_CHANNEL_NUMBER as u32 * Self::DMA_CHANNEL_REGISTERS_SIZE as u32)); // channel control block address register + set_dma_tx_address.transfer_length.write(core::mem::size_of::() as u32); + set_dma_tx_address.next_control_block_address.write(self.tx_control_block_memory.bus_address + (disable_tx_cb_index * core::mem::size_of::()) as u32); + + + disable_dma_tx_address.transfer_information.write(Self::DMA_TI_SRC_INC | Self::DMA_TI_DEST_INC | Self::DMA_TI_WAIT_RESP); + disable_dma_tx_address.source_address.write(self.dma_constant_memory.bus_address); + disable_dma_tx_address.destination_address.write(Self::DMA_SPI0_CS_PHYS_ADDRESS); + disable_dma_tx_address.transfer_length.write(core::mem::size_of::() as u32); + disable_dma_tx_address.next_control_block_address.write(self.tx_control_block_memory.bus_address + (start_tx_cb_index * core::mem::size_of::()) as u32); + + + start_dma_tx_address.transfer_information.write(Self::DMA_TI_SRC_INC | Self::DMA_TI_DEST_INC | Self::DMA_TI_WAIT_RESP); + start_dma_tx_address.source_address.write(self.dma_constant_memory.bus_address + (i * core::mem::size_of::()) as u32); + start_dma_tx_address.destination_address.write(Self::DMA_DMA0_CS_PHYS_ADDRESS + (Self::TX_CHANNEL_NUMBER as u32 * Self::DMA_CHANNEL_REGISTERS_SIZE as u32)); + start_dma_tx_address.transfer_length.write(core::mem::size_of::() as u32); + start_dma_tx_address.next_control_block_address.write(self.rx_control_block_memory.bus_address + (i * core::mem::size_of::()) as u32); + + + rx_control_block = &mut *((self.rx_control_block_memory.virtual_address_ptr as *mut DmaControlBlock).add(i)); + rx_control_block.transfer_information.write(Self::dma_ti_permap(Self::DMA_TI_PERMAP_SPI_RX) | Self::DMA_TI_SRC_DREQ | Self::DMA_TI_DEST_IGNORE); + rx_control_block.source_address.write(Self::DMA_SPI0_FIFO_PHYS_ADDRESS); + rx_control_block.destination_address.write(0); + rx_control_block.transfer_length.write(Self::DMA_SPI_CHUNK_SIZE as u32 - Self::DMA_SPI_HEADER_SIZE); // without the 4 byte header + rx_control_block.next_control_block_address.write(0); + } + } + + pub fn start_dma_transfer(&mut self, data:&[u8; SPI_BUFFER_SIZE], transfer_active_flag:u8){ + unsafe{ + if self.tx_dma.control_status.read() & 0x100 != 0{ + log::error!("Error in the tx dma"); + } + + let data_len = Self::DMA_SPI_CHUNK_SIZE - Self::DMA_SPI_HEADER_SIZE as usize; // Removing the first 4 bytes from this length param + let header = [transfer_active_flag, 0, (data_len & 0xFF) as u8, /*making sure this is little endian order*/ (data_len >> 8) as u8]; + + let chunks = data.chunks_exact(Self::DMA_SPI_CHUNK_SIZE - Self::DMA_SPI_HEADER_SIZE as usize); + let mut array:[u8;Self::DMA_SPI_TRANSFER_SIZE] = [0;Self::DMA_SPI_TRANSFER_SIZE]; + let mut i = 0; + for chunk in chunks{ + core::ptr::copy_nonoverlapping(header.as_ptr(), array.as_mut_ptr().add(i * Self::DMA_SPI_CHUNK_SIZE), 4); + core::ptr::copy_nonoverlapping(chunk.as_ptr(), array.as_mut_ptr().add(4 + (i * Self::DMA_SPI_CHUNK_SIZE)), Self::DMA_SPI_CHUNK_SIZE - 4); + i += 1; + } + + core::ptr::copy_nonoverlapping(array.as_ptr(), self.source_buffer_memory.virtual_address_ptr as *mut u8, array.len()); + + self.tx_dma.control_block_address.write(self.tx_control_block_memory.bus_address); + self.rx_dma.control_block_address.write(self.rx_control_block_memory.bus_address); + + memory_barrier(); // Sync all the memory operations happened in this function + // Starting the dma transfer + self.tx_dma.control_status.write(Self::DMA_CS_ACTIVE | Self::DMA_CS_END); + self.rx_dma.control_status.write(Self::DMA_CS_ACTIVE | Self::DMA_CS_END); + // Since the DMA controller writes to the SPI registers adding a barrier (even though it wrties afterwards to the DMA registers) + memory_barrier(); // Change DMA to SPI + } + } + + pub fn end_dma_transfer(&self){ + const TIME_TO_ABORT_AS_MICRO:i32 = 1_000_000; + // Wait for the last trasfer to end + let mut counter = 0; + while self.tx_dma.control_status.read() & Self::DMA_CS_ACTIVE != 0 { + // Self::sleep_us(1); + counter += 1; + if counter > TIME_TO_ABORT_AS_MICRO{ + core::panic!("ERROR! tx dma channel is not responding, a reboot is suggested"); + } + } + while self.rx_dma.control_status.read() & Self::DMA_CS_ACTIVE != 0 { + // Self::sleep_us(1); + counter += 1; + if counter > TIME_TO_ABORT_AS_MICRO{ + core::panic!("ERROR! rx dma channel is not responding, a reboot is suggested"); + } + } + } +} + +impl Drop for DmaSpiTransferer{ + fn drop(&mut self) { + // Finish current dma operation + self.end_dma_transfer(); + + // reset the dma channels before releasing the memory + // reset the dma channels + self.tx_dma.control_status.write(Self::DMA_CS_RESET); + self.rx_dma.control_status.write(Self::DMA_CS_RESET); + // clear the permaps for the channels + self.tx_dma.transfer_information.write(0); + self.rx_dma.transfer_information.write(0); + // disable the channels I used + let mask = !((1 << Self::TX_CHANNEL_NUMBER) | (1 << Self::RX_CHANNEL_NUMBER)); + self.dma_enable_register.write(self.dma_enable_register.read() & mask); + } +} \ No newline at end of file diff --git a/baremetal/src/peripherals/gpio.rs b/baremetal/src/peripherals/gpio.rs new file mode 100644 index 00000000..21e11e03 --- /dev/null +++ b/baremetal/src/peripherals/gpio.rs @@ -0,0 +1,128 @@ +use crate::syncronization::Mutex; + +use super::{PERIPHERALS_BASE_ADDRESS, utils::{MmioReg32, compile_time_size_assert, memory_barrier}}; + +#[repr(C,align(4))] +struct GpioRegisters{ + gpfsel:[MmioReg32;6], + _pad0:u32, + gpset:[MmioReg32;2], + _pad1:u32, + gpclr:[MmioReg32;2], + _pad2:u32, + pglev:[MmioReg32;2], + _pad3:[u32;42], + gpio_pup_pdn_cntrl:[MmioReg32;4] +} +compile_time_size_assert!(GpioRegisters, 0xF4); + +#[repr(u8)] +#[derive(Clone, Copy, PartialEq)] +pub enum Mode{ + Input = 0, + Output = 1, + Alt0 = 4, + Alt5 = 2 +} + +pub enum GpioPull{ + None = 0, + _PullUp = 0b01, +} + + +const RPI4_GPIO_PINS_COUNT:usize = 58; +const BASE_GPIO_ADDRESS: usize = PERIPHERALS_BASE_ADDRESS + 0x20_0000; +static mut GPIO_REGISTERS:Option> = None; + +pub struct GpioManager{ + pins_availability:[bool;RPI4_GPIO_PINS_COUNT] +} + +impl GpioManager{ + pub(super) fn new()->GpioManager{ + unsafe{GPIO_REGISTERS = Some(Mutex::new(&mut *(BASE_GPIO_ADDRESS as *mut GpioRegisters)));} + GpioManager { pins_availability: [true;RPI4_GPIO_PINS_COUNT]} + } + + pub fn take_pin(&mut self, bcm_pin_number:u8, mode:Mode)->GpioPin{ + if self.pins_availability[bcm_pin_number as usize]{ + self.pins_availability[bcm_pin_number as usize] = false; + return GpioPin::new(bcm_pin_number, mode); + } + core::panic!("Pin {} is already taken", bcm_pin_number); + } +} + +pub struct GpioPin{ + mode:Mode, + registers:&'static mut Mutex<&'static mut GpioRegisters>, + bcm_pin_number:u8, +} + +impl GpioPin{ + pub(super) fn new(bcm_pin_number:u8, mode:Mode)->Self{ + let registers = unsafe{GPIO_REGISTERS.as_mut().unwrap()}; + let mut pin = Self{mode:Mode::Input, registers, bcm_pin_number}; + pin.set_mode(mode); + return pin; + } + + pub fn set_mode(&mut self, mode:Mode){ + let gpfsel_register = (self.bcm_pin_number / 10) as usize; // each registers contains 10 pins + let gpfsel_register_offset = (self.bcm_pin_number % 10) * 3; // each pin take 3 bits in the register + memory_barrier(); + self.registers.lock(|r|{ + let mut register_value = r.gpfsel[gpfsel_register].read(); + register_value &= !(0b111 << gpfsel_register_offset); // clear the specific bits + r.gpfsel[gpfsel_register].write(register_value | (mode as u32) << gpfsel_register_offset) + }); + memory_barrier(); + self.mode = mode; + } + + pub fn set_state(&mut self, state:bool){ + debug_assert!(self.mode == Mode::Output); + let register = self.bcm_pin_number / 32; // since each registers contains 32 pins + let value = 1 << (self.bcm_pin_number % 32); // get the position in the register + memory_barrier(); + if state{ + self.registers.lock(|r|r.gpset[register as usize].write(value)); + } + else{ + self.registers.lock(|r|r.gpclr[register as usize].write(value)); + } + memory_barrier(); + } + + pub fn read_state(&self)->bool{ + debug_assert!(self.mode == Mode::Input); + + let gplev_register = self.bcm_pin_number / 32; // since each registers contains 32 pins + let mask = 1 << (self.bcm_pin_number % 32); // get the position in the register + memory_barrier(); + let value = self.registers.lock(|r|r.pglev[gplev_register as usize].read()); + memory_barrier(); + return value & mask != 0; + } + + pub fn set_pull(&mut self, pull_mode:GpioPull){ + let register_index = self.bcm_pin_number / 16; + let offset = (self.bcm_pin_number % 16) * 2; + let mask:u32 = 0b11 << offset; + memory_barrier(); + let register_value = self.registers.lock(|r|r.gpio_pup_pdn_cntrl[register_index as usize].read()); + let new_value = (register_value & !mask) | ((pull_mode as u32) << offset as u32); + self.registers.lock(|r|r.gpio_pup_pdn_cntrl[register_index as usize].write(new_value)); + memory_barrier(); + } + + // Sugar syntax functions + pub fn set_high(&mut self){ + self.set_state(true) + } + + pub fn set_low(&mut self){ + self.set_state(false); + } +} \ No newline at end of file diff --git a/baremetal/src/peripherals/gpu.rs b/baremetal/src/peripherals/gpu.rs new file mode 100644 index 00000000..1e83d050 --- /dev/null +++ b/baremetal/src/peripherals/gpu.rs @@ -0,0 +1,64 @@ +use crate::peripherals::PERIPHERALS; + +// using GpuMemory cause I need a memory that is not cached by the cpu caches (L1, L2) +pub(super) struct GpuMemory{ + pub virtual_address_ptr:usize, + pub bus_address:u32, + mailbox_memory_handle:u32, + pub size:u32 +} + +impl GpuMemory{ + const MEM_ALLOC_FLAG_DIRECT:usize = 1 << 2; + const MEM_ALLOC_FLAG_COHERENT:usize = 1 << 3; + const ALLOCATE_MEMORY_TAG:u32 = 0x3000C; + const LOCK_MEMORY_TAG:u32 = 0x3000D; + const UNLOCK_MEMORY_TAG:u32 = 0x3000E; + const RELEASE_MEMORY_TAG:u32 = 0x3000F; + const PAGE_SIZE:u32 = 4096; + + // This function converts the from the bus address of the SDRAM uncached memory to the arm physical address + // Notice that supposed to work only for this type of memory + const fn bus_to_phys(bus_address:u32)->u32{bus_address & !0xC000_0000} + + // Using the Mailbox interface to allocate memory on the gpu + pub(super) fn allocate(size:u32)->GpuMemory{ + let mbox = unsafe{PERIPHERALS.get_mailbox()}; + let flags = (Self::MEM_ALLOC_FLAG_COHERENT | Self::MEM_ALLOC_FLAG_DIRECT) as u32; + + log::debug!("Trying to allocate: {} memory", size); + // Result for alloc memory call is in the first u32 of the buffer + let handle = mbox.call(Self::ALLOCATE_MEMORY_TAG, [size, Self::PAGE_SIZE, flags])[0]; + // This is not documented well but after testing - on out of Gpu memory mailbox returns handle = 0 + if handle == 0{ + core::panic!("Error allocating Gpu memory! perhaps there is not enough free Gpu memory"); + } + + // The result for lock memory call is in the first u32 of the buffer + let bus_address = mbox.call(Self::LOCK_MEMORY_TAG, [handle])[0]; + // This is not documented well but after testing - on invalid handle mailbox returns bus_address = 0 + if bus_address == 0{ + core::panic!("Error locking Gpu memory!"); + } + + let address = Self::bus_to_phys(bus_address); + + return GpuMemory { virtual_address_ptr: address as usize, bus_address, mailbox_memory_handle:handle, size } + } + + fn release(&self){ + let mbox = unsafe{PERIPHERALS.get_mailbox()}; + if mbox.call(Self::UNLOCK_MEMORY_TAG, [self.mailbox_memory_handle])[0] != 0{ + core::panic!("Error while trying to unlock gpu memory using mailbox"); + } + if mbox.call(Self::RELEASE_MEMORY_TAG, [self.mailbox_memory_handle])[0] != 0{ + core::panic!("Error while trying to release gpu memory using mailbox"); + } + } +} + +impl Drop for GpuMemory{ + fn drop(&mut self) { + self.release(); + } +} \ No newline at end of file diff --git a/baremetal/src/peripherals/mailbox.rs b/baremetal/src/peripherals/mailbox.rs new file mode 100644 index 00000000..17f0bd51 --- /dev/null +++ b/baremetal/src/peripherals/mailbox.rs @@ -0,0 +1,128 @@ +// This module is wrriten using the following sources +// Linux kernel for the rpi version 4.9 as a referrence - https://github.com/raspberrypi/linux/blob/2aae4cc63e30f99dd152fc63cc4a67ca29e4647b/drivers/firmware/raspberrypi.c +// This tutorial - https://www.rpi4os.com/part5-framebuffer/ +// The official mailbox docs - https://github.com/raspberrypi/firmware/wiki/Mailboxes + +//Turns out this peripheral needs a non cached memory + +use core::mem::size_of; + +use super::{PERIPHERALS_BASE_ADDRESS, utils::{compile_time_size_assert, MmioReg32, memory_barrier}}; + +const MBOX_BASE_ADDRESS:usize = PERIPHERALS_BASE_ADDRESS + 0xB880; +const MBOX_CHANNEL:u32 = 8; // free channel for communication from the cpu to the core + +#[repr(C, align(4))] +struct PropertyTagHeader{ + tag:u32, + buffer_size:u32, + + // On submit, the length of the request (though it doesn't appear to be currently used by the firmware). + // On return, the length of the response (always 4 byte aligned), with the low bit set. + request_response_size:u32 +} + +#[repr(u32)] +#[derive(Clone, Copy, PartialEq)] +enum PropertyStatus{ + Request = 0, + Success = 0x8000_0000, + _Error = 0x8000_0001 +} +compile_time_size_assert!(PropertyStatus, 4); + +// The 28 first bits are the address but the last 4 bits represnt the mailbox channel +// by aligning to 16 the last 4 bit are always 0 +#[repr(C, align(16))] +struct Message{ + size:u32, + status:PropertyStatus, + tag_header:PropertyTagHeader, + data:[u32;DATA_LEN], + property_end_marker:u32 +} + +impl Message{ + const MBOX_PROPERTY_END:u32 = 0; + + fn new(tag:u32, data:[u32;DATA_LEN])->Self{ + Self{ + size: size_of::() as u32, + status: PropertyStatus::Request, + tag_header: PropertyTagHeader { + tag, + buffer_size: (size_of::() * DATA_LEN) as u32, + request_response_size: 0 // zero since unused by the firmare + }, + data, + property_end_marker: Self::MBOX_PROPERTY_END + } + } +} + +#[repr(C, align(4))] +struct MailboxRegisters{ + read_reg:MmioReg32, + _res:[u32;5], + status:MmioReg32, + _res1:u32, + write_reg:MmioReg32, +} +compile_time_size_assert!(MailboxRegisters, 0x24); + +const MAILBOX_BUFFER_SIZE:usize = 0x100; +#[repr(align(16))] +struct MailboxBuffer([u8;MAILBOX_BUFFER_SIZE]); +#[no_mangle] +#[link_section = ".uncached"] +static mut MAILBOX_UNCACHED_BUFFER:MailboxBuffer = MailboxBuffer([0;MAILBOX_BUFFER_SIZE]); + +pub struct Mailbox{ + registers:&'static mut MailboxRegisters, + uncached_buffer:&'static mut [u8] +} + +impl Mailbox{ + const STATUS_EMPTY:u32 = 0x4000_0000; + const STATUS_FULL:u32 = 0x8000_0000; + + pub(super) fn new()->Mailbox{ + let registers = unsafe{&mut *(MBOX_BASE_ADDRESS as *mut MailboxRegisters)}; + let uncached_buffer:&'static mut [u8] = unsafe{&mut MAILBOX_UNCACHED_BUFFER.0}; + return Mailbox { registers, uncached_buffer }; + } + + pub fn call(&mut self, tag:u32, data:[u32;DATA_LEN])->[u32;DATA_LEN]{ + if size_of::>() > self.uncached_buffer.len() { + core::panic!("Error, Message with data len of {} bytes is too large ({}) and cant fit a {} bytes buffer", + DATA_LEN, size_of::>(), self.uncached_buffer.len()); + } + + let uncached_message = unsafe{ + let message = Message::new(tag, data); + core::ptr::copy_nonoverlapping(&message as *const Message as *const u8, self.uncached_buffer.as_mut_ptr(), size_of::>()); + &*(self.uncached_buffer.as_ptr() as *const Message) + }; + let mut message_address = (self.uncached_buffer.as_ptr()) as *const Message as u32; + if message_address & 0xF != 0{ + core::panic!("Error! mbox message is not alligned for 16 bytes") + } + message_address += MBOX_CHANNEL; + + memory_barrier(); + while self.registers.status.read() & Self::STATUS_FULL != 0{} // blocks untill mbox is avaliable + self.registers.write_reg.write(message_address); + + loop{ + while self.registers.status.read() & Self::STATUS_EMPTY != 0{} // block untill there is a response (non empty mailbox) + if self.registers.read_reg.read() == message_address{ + memory_barrier(); + if uncached_message.status == PropertyStatus::Success{ + return uncached_message.data; + } + core::panic!("Error in mbox call! tag: {:#X}, req_data: {:?}, status: {:#X}, res_data: {:?}", + tag, data, uncached_message.status as u32, uncached_message.data); + } + } + } +} \ No newline at end of file diff --git a/baremetal/src/peripherals/mini_uart.rs b/baremetal/src/peripherals/mini_uart.rs new file mode 100644 index 00000000..c224bed7 --- /dev/null +++ b/baremetal/src/peripherals/mini_uart.rs @@ -0,0 +1,93 @@ +use super::{Mode, GpioPull, CORE_FREQ, PERIPHERALS_BASE_ADDRESS, utils::{MmioReg32, compile_time_size_assert, memory_barrier}, PERIPHERALS}; + +const UART_RX_PIN_BCM: u8 = 15; +const UART_TX_PIN_BCM: u8 = 14; +const AUX_BASE_ADDRESS:usize = PERIPHERALS_BASE_ADDRESS + 0x21_5000; +const AUX_MINI_UART_ADDRESS:usize = AUX_BASE_ADDRESS + 0x40; + +#[repr(C, align(4))] +struct AuxControlRegisters{ + aux_irq:MmioReg32, + aux_enables:MmioReg32, +} + +#[repr(C, align(4))] +struct MiniUartRegisters{ + io:MmioReg32, // data + ier:MmioReg32, // interrupt enable + iir:MmioReg32, // imterrupt identity + lcr:MmioReg32, // line control + mcr:MmioReg32, // modem control + lsr:MmioReg32, // line status + _msr:MmioReg32, // modem status + _scratch:MmioReg32, + cntl:MmioReg32, // extra control + _stat:MmioReg32, // extra status + baud:MmioReg32 +} +compile_time_size_assert!(MiniUartRegisters, 0x2C); + +/* The docs are wrong and this is a 3 bit parameter: +According the Linux kernel header - https://git.kernel.org/pub/scm/linux/kernel/git/next/linux-next.git/tree/include/uapi/linux/serial_reg.h#n110 +5bit - 0 +6bit - 1 +7bit - 2 +8bit - 3 */ +const AUX_MU_LCR_8BIT_DATA_SIZE:u32 = 0b11; +const AUX_MU_IIR_CLEAR_FIFO:u32 = 0b11 << 1; +const AUX_MU_CNTL_ENABLE_RX:u32 = 1; +const AUX_MU_CNTL_ENABLE_TX:u32 = 1 << 1; +const AUX_MU_LSR_TX_EMPTY:u32 = 1 << 5; +const AUX_ENABLES_ENABLE_UART:u32 = 1; + +pub struct MiniUart{ + registers:&'static mut MiniUartRegisters, + _aux_control_registers:&'static AuxControlRegisters +} + +impl MiniUart{ + pub(super) fn new(baudrate:u32)->MiniUart{ + let gpio = unsafe{PERIPHERALS.get_gpio()}; + // alt5 is the uart1 which the mini uart uses + let mut rx_pin = gpio.take_pin(UART_RX_PIN_BCM, Mode::Alt5); + let mut tx_pin = gpio.take_pin(UART_TX_PIN_BCM, Mode::Alt5); + + // set pull to none for the pins + rx_pin.set_pull(GpioPull::None); + tx_pin.set_pull(GpioPull::None); + + // the docs for bcm2835 says I might need to sleep here for 150 cycles, the bcm2711 omitted this and changed the registers + + let control_regs = unsafe{&mut *(AUX_BASE_ADDRESS as *mut AuxControlRegisters)}; + let mini_uart_regs = unsafe{&mut *(AUX_MINI_UART_ADDRESS as *mut MiniUartRegisters)}; + + // setup uart + memory_barrier(); + control_regs.aux_enables.write(AUX_ENABLES_ENABLE_UART); // enables uart + mini_uart_regs.ier.write(0); // turn off interrupts + mini_uart_regs.cntl.write(0); // turn off io for init + mini_uart_regs.lcr.write(AUX_MU_LCR_8BIT_DATA_SIZE); + mini_uart_regs.mcr.write(0); // set rts to high + mini_uart_regs.iir.write(AUX_MU_IIR_CLEAR_FIFO); + mini_uart_regs.baud.write(Self::aux_mu_baud(baudrate)); + mini_uart_regs.cntl.write(AUX_MU_CNTL_ENABLE_RX | AUX_MU_CNTL_ENABLE_TX); + memory_barrier(); + + // the pins are leaking right now, but I dont care + return MiniUart{registers:mini_uart_regs, _aux_control_registers:control_regs}; + } + + pub fn send(&mut self, data:&[u8]){ + memory_barrier(); + for ch in data{ + // block untill we can write + while (self.registers.lsr.read() & AUX_MU_LSR_TX_EMPTY) == 0 {} + // TX is empty writing to the fifo register + self.registers.io.write(*ch as u32); + } + memory_barrier(); + } + + // baudrate = core_freq / (8(baudrate_reg + 1)) + const fn aux_mu_baud(baudrate:u32)->u32{(CORE_FREQ / (8*baudrate)) - 1} +} \ No newline at end of file diff --git a/baremetal/src/peripherals/mod.rs b/baremetal/src/peripherals/mod.rs new file mode 100644 index 00000000..da40318b --- /dev/null +++ b/baremetal/src/peripherals/mod.rs @@ -0,0 +1,66 @@ +mod gpio; +mod mini_uart; +mod mailbox; +mod utils; +mod gpu; +mod spi; +mod dma; +mod timer; + +pub use gpio::*; +pub use mini_uart::MiniUart; +pub use mailbox::*; +pub use timer::*; +pub use spi::Spi0; +use utils::Peripheral; + +use crate::configuration::peripherals::*; + +#[cfg(feature = "rpi4")] +pub const PERIPHERALS_BASE_ADDRESS:usize = 0xFE00_0000; +#[cfg(feature = "rpi2")] +pub const PERIPHERALS_BASE_ADDRESS:usize = 0x3F00_0000; + +pub struct Peripherals{ + gpio_manager: Peripheral, + mini_uart: Peripheral, + mailbox: Peripheral, + timer: Peripheral, + spi0: Peripheral, +} + +impl Peripherals{ + const SET_CLOCK_RATE_TAG: u32 = 0x38002; + + pub fn set_core_clock(&mut self){ + const CORE_CLOCK_ID:u32 = 4; + let mbox = self.get_mailbox(); + let result = mbox.call(Self::SET_CLOCK_RATE_TAG, [CORE_CLOCK_ID, CORE_FREQ, 0]); + if result[1] != CORE_FREQ{ + core::panic!("Error, set core clock failed"); + } + } + pub fn take_mini_uart(&mut self)->mini_uart::MiniUart{ + self.mini_uart.take(||mini_uart::MiniUart::new(MINI_UART_BAUDRATE)) + } + pub fn get_gpio(&mut self)->&mut gpio::GpioManager{ + self.gpio_manager.get(||GpioManager::new()) + } + pub fn get_mailbox(&mut self)->&mut mailbox::Mailbox{ + self.mailbox.get(||mailbox::Mailbox::new()) + } + pub fn take_timer(&mut self)->timer::Timer{ + self.timer.take(||Timer::new()) + } + pub fn take_spi0(&mut self)->Spi0{ + self.spi0.take(||spi::Spi0::new(SPI0_DC_BCM_PIN)) + } +} + +pub static mut PERIPHERALS: Peripherals = Peripherals{ + gpio_manager: Peripheral::Uninit, + mini_uart: Peripheral::Uninit, + mailbox: Peripheral::Uninit, + timer: Peripheral::Uninit, + spi0: Peripheral::Uninit +}; \ No newline at end of file diff --git a/baremetal/src/peripherals/spi.rs b/baremetal/src/peripherals/spi.rs new file mode 100644 index 00000000..5eb2dfe1 --- /dev/null +++ b/baremetal/src/peripherals/spi.rs @@ -0,0 +1,148 @@ +use crate::{peripherals::{Mode, dma::DmaSpiTransferer, utils::memory_barrier, PERIPHERALS}, drivers, configuration::peripherals::*}; + +use super::{utils::{MmioReg32, compile_time_size_assert}, GpioPin, PERIPHERALS_BASE_ADDRESS}; + +const SPI0_BASE_ADDRESS:usize = PERIPHERALS_BASE_ADDRESS + 0x20_4000; + +// Fix this usafe of the driver module here +pub(super)const SPI_BUFFER_SIZE:usize = drivers::SPI_BUFFER_SIZE; + +// The register are 4 bytes each so making sure the allignment and padding are correct +#[repr(C, align(4))] +struct SpiRegisters{ + control_status:MmioReg32, + fifo:MmioReg32, + clock:MmioReg32, + data_len:MmioReg32 +} +compile_time_size_assert!(SpiRegisters, 0x10); + +pub struct Spi0{ + spi_registers: &'static mut SpiRegisters, + dc_pin:GpioPin, + last_transfer_was_dma:bool, + dma:DmaSpiTransferer, + + // holding those pins in order to make sure they are configured correctly + // the state resets upon drop + _spi_pins:[GpioPin;2], + _spi_cs0:GpioPin, +} + +impl Spi0{ + const SPI0_CE0_N_BCM_PIN:u8 = 8; + const SPI0_MOSI_BCM_PIN:u8 = 10; + const SPI0_SCLK_BCM_PIN:u8 = 11; + + const SPI_CS_RXF:u32 = 1 << 20; + const SPI_CS_RXR:u32 = 1 << 19; + const SPI_CS_TXD:u32 = 1 << 18; + const SPI_CS_DONE:u32 = 1 << 16; + const SPI_CS_DMAEN:u32 = 1 << 8; + const SPI_CS_TA:u32 = 1 << 7; + const SPI_CS_CLEAR:u32 = 3<<4; + const SPI_CS_CLEAR_RX:u32 = 1 << 5; + + pub(super) fn new (dc_pin:u8)->Self{ + let gpio = unsafe{PERIPHERALS.get_gpio()}; + let mut spi_cs0 = gpio.take_pin(Self::SPI0_CE0_N_BCM_PIN, Mode::Output); + let dc_pin = gpio.take_pin(dc_pin, Mode::Output); + let spi_pins = [ + gpio.take_pin(Self::SPI0_MOSI_BCM_PIN, Mode::Alt0), + gpio.take_pin(Self::SPI0_SCLK_BCM_PIN, Mode::Alt0) + ]; + + let spi_registers = unsafe{&mut *(SPI0_BASE_ADDRESS as *mut SpiRegisters)}; + + // ChipSelect = 0, ClockPhase = 0, ClockPolarity = 0 + spi_cs0.set_low(); + Self::setup_poll_fast_transfer(&mut *spi_registers); + spi_registers.clock.write(INIT_SPI_CLOCK_DIVISOR); + + memory_barrier(); // Change SPI to DMA + let dma_transferer = DmaSpiTransferer::new(Self::SPI_CS_DMAEN); + memory_barrier(); // Change DMA to SPI + + log::info!("Finish initializing spi mmio interface"); + Spi0 { + spi_registers, dc_pin, _spi_pins: spi_pins, _spi_cs0: spi_cs0, last_transfer_was_dma: false, + dma:dma_transferer + } + } + + pub fn write(&mut self, command:u8, data:&[u8;SIZE]){ + self.prepare_for_transfer(); + + self.dc_pin.set_low(); + self.write_raw(&[command]); + self.dc_pin.set_high(); + self.write_raw(data); + self.last_transfer_was_dma = false; + } + + fn prepare_for_transfer(&mut self) { + if self.last_transfer_was_dma{ + memory_barrier(); // Change SPI to DMA + self.dma.end_dma_transfer(); + memory_barrier(); // Change DMA to SPI + Self::setup_poll_fast_transfer(self.spi_registers); + } + } + + fn setup_poll_fast_transfer(spi_registers:&mut SpiRegisters){ + spi_registers.control_status.write(Self::SPI_CS_TA | Self::SPI_CS_CLEAR); + + // poll mode speed up according to this forum post - https://forums.raspberrypi.com/viewtopic.php?f=44&t=181154 + spi_registers.data_len.write(2); + } + + fn write_raw(&mut self, data:&[u8;SIZE]){ + let mut current_index = 0; + while current_index < SIZE { + let cs:u32 = self.spi_registers.control_status.read(); + if cs & Self::SPI_CS_TXD != 0{ + self.spi_registers.fifo.write(data[current_index] as u32); + current_index += 1; + } + if (cs & (Self::SPI_CS_RXR | Self::SPI_CS_RXF)) != 0 { + self.spi_registers.control_status.write(Self::SPI_CS_TA | Self::SPI_CS_CLEAR_RX); + } + } + + // wait for the last trasfer to finish + while (self.spi_registers.control_status.read() & Self::SPI_CS_DONE) == 0 { + if (self.spi_registers.control_status.read() & (Self::SPI_CS_RXR | Self::SPI_CS_RXF)) != 0{ + self.spi_registers.control_status.write(Self::SPI_CS_TA | Self::SPI_CS_CLEAR_RX); + } + } + } + + pub fn write_dma(&mut self, command:u8, data:&[u8;SPI_BUFFER_SIZE]){ + self.prepare_for_transfer(); + + self.dc_pin.set_low(); + self.write_raw(&[command]); + self.dc_pin.set_high(); + self.write_dma_raw(&data); + self.last_transfer_was_dma = true; + } + + + // Since generic_const_exprs is not stable yet Im reserving the first 4 bytes of the data variable for internal use + fn write_dma_raw(&mut self, data:&[u8;SPI_BUFFER_SIZE]){ + self.spi_registers.control_status.write(Self::SPI_CS_DMAEN | Self::SPI_CS_CLEAR); + memory_barrier(); // Change SPI to DMA + self.dma.start_dma_transfer(data, Self::SPI_CS_TA as u8); + memory_barrier(); // Change DMA to SPI + } + + pub fn fast_mode(&mut self) { + self.spi_registers.clock.write(FAST_SPI_CLOCK_DIVISOR); + } +} + +impl Drop for Spi0{ + fn drop(&mut self) { + self.spi_registers.control_status.write(Self::SPI_CS_CLEAR); + } +} \ No newline at end of file diff --git a/baremetal/src/peripherals/timer.rs b/baremetal/src/peripherals/timer.rs new file mode 100644 index 00000000..e16b386f --- /dev/null +++ b/baremetal/src/peripherals/timer.rs @@ -0,0 +1,57 @@ +use core::time::Duration; + +use super::{utils::{MmioReg32, compile_time_size_assert, memory_barrier}, PERIPHERALS_BASE_ADDRESS}; + +const TIMER_BASE_ADDRESS:usize = PERIPHERALS_BASE_ADDRESS + 0x3000; + +// timer frequency is 1_000_000 hz +// This number is based on this - https://www.youtube.com/watch?v=2dlBZoLCMSc + +#[repr(C, align(4))] +struct TimerRegisters{ + _contorl_status:MmioReg32, + counter_low:MmioReg32, + counter_high:MmioReg32 +} +compile_time_size_assert!(TimerRegisters, 0xC); + +pub struct Timer{ + registers:&'static mut TimerRegisters, + current_tick:u64 +} + +impl Timer{ + pub(super) fn new()->Self{ + let registers = unsafe{&mut *(TIMER_BASE_ADDRESS as *mut TimerRegisters)}; + let mut timer = Self { registers, current_tick:0 }; + // init current tick with valid value + timer.current_tick = timer.get_timer_counter(); + return timer; + } + + pub fn tick(&mut self)->Duration{ + let last_tick = self.current_tick; + self.current_tick = self.get_timer_counter(); + let diff = self.current_tick - last_tick; + // Since freq is 1_000_000 hz + return Duration::from_micros(diff); + } + + pub fn wait(&mut self, duration:Duration){ + let mut counter = core::time::Duration::ZERO; + let _ = self.tick(); // reset the timer ticking and disard the result + while counter < duration{ + let time_from_last_tick = self.tick(); + counter += time_from_last_tick; + } + } + + fn get_timer_counter(&self)->u64{ + memory_barrier(); + let low = self.registers.counter_low.read(); + let high = self.registers.counter_high.read(); + memory_barrier(); + + return low as u64 | ((high as u64) << 32); + } +} \ No newline at end of file diff --git a/baremetal/src/peripherals/utils.rs b/baremetal/src/peripherals/utils.rs new file mode 100644 index 00000000..f9e3eff0 --- /dev/null +++ b/baremetal/src/peripherals/utils.rs @@ -0,0 +1,53 @@ +macro_rules! compile_time_size_assert { + ($t:ty, $size:literal) => { + const _:[u8;$size] = [0;core::mem::size_of::<$t>()]; + }; +} +pub(super) use compile_time_size_assert; + +#[repr(transparent)] +pub(super) struct MmioReg32(u32); +impl MmioReg32 { + #[inline] + pub fn read(&self)->u32{ + unsafe{core::ptr::read_volatile(&self.0)} + } + #[inline] + pub fn write(&mut self, value:u32){ + unsafe{core::ptr::write_volatile(&mut self.0, value)} + } +} + +// According to the docs the raspberrypi requires memory barrier between reads and writes to differnet peripherals +#[inline] +pub(super) fn memory_barrier(){ + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); +} + +pub enum Peripheral{ + Uninit, + Init(T), + Taken +} + +impl Peripheral{ + pub(super) fn get(&mut self, init_callback: impl FnOnce()->T)->&mut T{ + if let Self::Uninit = self { + *self = Self::Init(init_callback()); + } + return match self{ + Self::Init(t) => t, + Self::Taken => core::panic!("Peripheral is unavaliable, its been taken "), + Self::Uninit => core::unreachable!("At this point the peripheral must be initialized"), + }; + } + + pub(super) fn take(&mut self, init_callback: impl FnOnce()->T)->T{ + let s = core::mem::replace(self, Self::Taken); + return match s{ + Self::Uninit => init_callback(), + Self::Init(t) => t, + Self::Taken => core::panic!("Peripheral is unavaliable, its been taken"), + }; + } +} \ No newline at end of file diff --git a/baremetal/src/syncronization.rs b/baremetal/src/syncronization.rs new file mode 100644 index 00000000..72b6ef7e --- /dev/null +++ b/baremetal/src/syncronization.rs @@ -0,0 +1,26 @@ +use core::{sync::atomic::{AtomicBool, Ordering}, cell::UnsafeCell}; + +pub struct Mutex { + lock:AtomicBool, + data:UnsafeCell, +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} + +impl Mutex{ + pub const fn new(data:TData)->Mutex{ + Mutex { data:UnsafeCell::new(data), lock:AtomicBool::new(false) } + } + + pub fn lock<'a, TReturn>(&'a self, callback: impl FnOnce(&'a mut TData)->TReturn)->TReturn{ + // block untill given access to lock the mutex + while self.lock.compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst).is_ok(){} + let data = unsafe{&mut *self.data.get()}; + let res = callback(data); + // free the mutex + self.lock.store(false, Ordering::SeqCst); + + return res; + } +} \ No newline at end of file