hocclk: add bpmpfw loading

This commit is contained in:
souldbminersmwc committed 2026-08-24 21:21:50 -04:00
1 parent 7fea98a72d
commit bd468f087d
14 files changed
+522 -3

No files matched your search

+44
View File
@@ -0,0 +1,44 @@
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
include $(DEVKITARM)/base_rules
TARGET := bpmpfw
BUILDDIR := build
OUTPUTDIR := out
SOURCEDIR := source
OBJS := $(BUILDDIR)/start.o $(BUILDDIR)/main.o
ARCH := -march=armv4t -mtune=arm7tdmi -mthumb-interwork
CXXFLAGS := $(ARCH) -O2 -g -ffreestanding -nostdlib -fno-exceptions -fno-rtti \
-fno-unwind-tables -fno-threadsafe-statics -ffunction-sections -fdata-sections \
-fomit-frame-pointer -std=gnu++20 -Wall -Wextra $(CUSTOMDEFINES)
ASFLAGS := $(ARCH) -g
LDFLAGS := $(ARCH) -nostartfiles -nodefaultlibs -Wl,--nmagic,--gc-sections \
-T $(SOURCEDIR)/../bpmpfw.ld -lgcc
BIN_TARGET := $(OUTPUTDIR)/$(TARGET).bin
.PHONY: all clean
all: $(BIN_TARGET)
$(BIN_TARGET): $(BUILDDIR)/$(TARGET).elf
@mkdir -p $(OUTPUTDIR)
$(OBJCOPY) -S -O binary $< $@
$(BUILDDIR)/$(TARGET).elf: $(OBJS)
@$(CXX) $(LDFLAGS) $(OBJS) -o $@
$(BUILDDIR)/%.o: $(SOURCEDIR)/%.cpp | $(BUILDDIR)
@$(CXX) $(CXXFLAGS) -I$(SOURCEDIR) -c $< -o $@
$(BUILDDIR)/%.o: $(SOURCEDIR)/%.s | $(BUILDDIR)
@$(CXX) $(ASFLAGS) -I$(SOURCEDIR) -c $< -o $@
$(BUILDDIR):
@mkdir -p $(BUILDDIR)
clean:
@rm -rf $(BUILDDIR) $(OUTPUTDIR)
+151
View File
@@ -0,0 +1,151 @@
OUTPUT_ARCH(arm)
ENTRY(reset)
MEMORY
{
bpmpfw : ORIGIN = 0x40004000, LENGTH = 32K
}
SECTIONS
{
PROVIDE(__start__ = ORIGIN(bpmpfw));
. = __start__;
__code_start = . ;
.vectors :
{
KEEP (*(.vectors .vectors.*))
. = ALIGN(8);
} >bpmpfw
.text :
{
*(.text.unlikely .text.*_unlikely .text.unlikely.*)
*(.text.exit .text.exit.*)
*(.text.startup .text.startup.*)
*(.text.hot .text.hot.*)
*(.text .stub .text.* .gnu.linkonce.t.*)
. = ALIGN(8);
} >bpmpfw
.init :
{
KEEP( *(.init) )
. = ALIGN(8);
} >bpmpfw
.plt :
{
*(.plt)
*(.iplt)
. = ALIGN(8);
} >bpmpfw
.fini :
{
KEEP( *(.fini) )
. = ALIGN(8);
} >bpmpfw
. = ALIGN(8);
__rodata_start = . ;
.rodata :
{
*(.rodata .rodata.* .gnu.linkonce.r.*)
. = ALIGN(8);
} >bpmpfw
.eh_frame_hdr : { __eh_frame_hdr_start = .; *(.eh_frame_hdr) *(.eh_frame_entry .eh_frame_entry.*) __eh_frame_hdr_end = .; } >bpmpfw
.eh_frame : ONLY_IF_RO { KEEP (*(.eh_frame)) *(.eh_frame.*) } >bpmpfw
.gcc_except_table : ONLY_IF_RO { *(.gcc_except_table .gcc_except_table.*) } >bpmpfw
.gnu_extab : ONLY_IF_RO { *(.gnu_extab*) } >bpmpfw
.hash : { *(.hash) } >bpmpfw
. = ALIGN(8);
__data_start = . ;
.eh_frame : ONLY_IF_RW { KEEP (*(.eh_frame)) *(.eh_frame.*) } >bpmpfw
.gcc_except_table : ONLY_IF_RW { *(.gcc_except_table .gcc_except_table.*) } >bpmpfw
.gnu_extab : ONLY_IF_RW { *(.gnu_extab*) } >bpmpfw
.exception_ranges : ONLY_IF_RW { *(.exception_ranges .exception_ranges*) } >bpmpfw
.preinit_array ALIGN(8) :
{
PROVIDE (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE (__preinit_array_end = .);
} >bpmpfw
.init_array ALIGN(8) :
{
PROVIDE (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE (__init_array_end = .);
} >bpmpfw
.fini_array ALIGN(8) :
{
PROVIDE (__fini_array_start = .);
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
PROVIDE (__fini_array_end = .);
} >bpmpfw
.ctors ALIGN(8) :
{
KEEP (*crtbegin.o(.ctors)) /* MUST be first -- GCC requires it */
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*(.ctors))
} >bpmpfw
.dtors ALIGN(8) :
{
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*(.dtors))
} >bpmpfw
__got_start__ = .;
.got : { *(.got) *(.igot) } >bpmpfw
.got.plt : { *(.got.plt) *(.igot.plt) } >bpmpfw
__got_end__ = .;
.data ALIGN(8) :
{
*(.data .data.* .gnu.linkonce.d.*)
SORT(CONSTRUCTORS)
} >bpmpfw
__bss_start__ = .;
.bss ALIGN(8) :
{
*(.dynbss)
*(.bss .bss.* .gnu.linkonce.b.*)
*(COMMON)
. = ALIGN(16);
} >bpmpfw
__bss_end__ = .;
__end__ = ABSOLUTE(.) ;
__stack_top = ORIGIN(bpmpfw) + LENGTH(bpmpfw) - 0x10;
/DISCARD/ : { *(.group .comment .note .interp .ARM.attributes) }
.stab 0 : { *(.stab) }
.stabstr 0 : { *(.stabstr) }
.debug_info 0 : { *(.debug_info) }
.debug_abbrev 0 : { *(.debug_abbrev) }
.debug_line 0 : { *(.debug_line) }
.debug_frame 0 : { *(.debug_frame) }
.debug_str 0 : { *(.debug_str) }
.debug_loc 0 : { *(.debug_loc) }
.debug_macinfo 0 : { *(.debug_macinfo) }
}
Binary file not shown.
+9
View File
@@ -0,0 +1,9 @@
#include "regs.hpp"
extern "C" void main() {
UartPuts("[bpmpfw]: Hello from BPMP-FW\n");
for (;;) {
FlowCtlrHaltUsec(200);
}
}
+42
View File
@@ -0,0 +1,42 @@
#pragma once
#include <cstdint>
using u8 = std::uint8_t;
using u16 = std::uint16_t;
using u32 = std::uint32_t;
#define MMIO32(addr) (*reinterpret_cast<volatile u32 *>(addr))
constexpr u32 FlowCtlrBase = 0x60007000;
constexpr u32 FlowCtlrHaltCopEvents = 0x004;
constexpr u32 HaltModeWaitEvent = (2u << 29);
constexpr u32 HaltUsec = (0u << 24);
inline void FlowCtlrHaltUsec(u32 usec) {
MMIO32(FlowCtlrBase + FlowCtlrHaltCopEvents) = HaltModeWaitEvent | HaltUsec | (usec & 0xFFFFFFu);
}
constexpr u32 UartBBase = 0x70006040;
constexpr u32 UartThr = 0x00;
constexpr u32 UartLsr = 0x14;
constexpr u32 UartLsrThre = (1u << 5);
inline void UartPutc(char c) {
while (!(MMIO32(UartBBase + UartLsr) & UartLsrThre))
;
MMIO32(UartBBase + UartThr) = static_cast<u32>(static_cast<u8>(c));
}
inline void UartPuts(const char *s) {
while (*s) {
UartPutc(*s++);
}
}
inline void UartPutHex32(u32 v) {
static const char digits[] = "0123456789ABCDEF";
for (int i = 28; i >= 0; i -= 4) {
UartPutc(digits[(v >> i) & 0xF]);
}
}
+65
View File
@@ -0,0 +1,65 @@
.section .vectors, "ax", %progbits
.align 3
.global reset
reset:
b _start
b _panic
.section .text.start, "ax", %progbits
.align 3
.global _start
.extern main
_start:
/* Mask IRQ + FIQ, enter SVC mode. */
msr cpsr_f, #0xC0
msr cpsr_cf, #0xD3
/* Set the stack pointer. */
ldr sp, =__stack_top
/* Zero .bss. */
ldr r0, =__bss_start__
ldr r1, =__bss_end__
mov r2, #0
1:
cmp r0, r1
bge 2f
str r2, [r0], #4
b 1b
2:
/* Run global constructors. */
ldr r4, =__init_array_start
ldr r5, =__init_array_end
3:
cmp r4, r5
bge 4f
ldr r0, [r4], #4
mov lr, pc
bx r0
b 3b
4:
bl main
.global _panic
_panic:
ldr r0, =0x70006040 /* UART_B_BASE */
adr r3, _panic_msg
7:
ldrb r1, [r3], #1
cmp r1, #0
beq _panic_hang
8:
ldr r2, [r0, #0x14] /* UART_LSR */
tst r2, #0x20 /* UART_LSR_THRE */
beq 8b
str r1, [r0]
b 7b
_panic_msg:
.asciz "[bpmpfw] PANIC\n"
.align 2
_panic_hang:
b _panic_hang
+9
View File
@@ -42,6 +42,15 @@ mkdir -p "$DIST_DIR/switch/.overlays"
cp -vf "$ROOT_DIR/overlay/out/horizon-oc-overlay.ovl" "$DIST_DIR/switch/.overlays/horizon-oc-overlay.ovl"
echo
echo "*** Compiling bpmpfw ***"
pushd "$ROOT_DIR/bpmpfw"
make -j$CORES
popd > /dev/null
mkdir -p "$DIST_DIR/config/horizon-oc"
cp -vf "$ROOT_DIR/bpmpfw/out/bpmpfw.bin" "$DIST_DIR/config/horizon-oc/bpmpfw.bin"
echo
echo "*** Copying assets ***"
mkdir -p "$DIST_DIR/config/horizon-oc"
cp -vf "$ROOT_DIR/config.ini.template" "$DIST_DIR/config/horizon-oc/config.ini.template"
+1 -1
View File
@@ -22,7 +22,7 @@ CONFIG_DIR := horizon-oc
BUILD := build
OUTDIR := out
RESOURCES := res
SOURCES := src src/hos src/i2c src/ipc src/board src/display src/tsensor src/mgr src/file src/soc src/mapping src/pwr src/util
SOURCES := src src/hos src/i2c src/ipc src/board src/bpmp src/display src/tsensor src/mgr src/file src/soc src/mapping src/pwr src/util
DATA := data
INCLUDES := ../common/include src/hos src/soc src/i2c src/util src/pwr src/ipc
EXEFS_SRC := exefs_src
+11 -1
View File
@@ -35,6 +35,7 @@
#include <t210.h>
#include <tmp451.h>
#include "../bpmp/bpmp.hpp"
#include "../display/display_refresh_rate.hpp"
#include "../file/file_utils.hpp"
#include "../hos/apm_ext.h"
@@ -52,7 +53,7 @@
namespace board {
u64 clkVirtAddr, dsiVirtAddr, apbVirtAddr, fuseVirtAddr;
u64 clkVirtAddr, dsiVirtAddr, apbVirtAddr, fuseVirtAddr, sysVirtAddr, actmonVirtAddr;
HocClkSocType gSocType;
u8 gDramID;
@@ -153,6 +154,10 @@ namespace board {
rc = QueryMemoryMapping(&fuseVirtAddr, 0x7000F000, 0x1000);
ASSERT_RESULT_OK(rc, "QueryMemoryMapping (fuse)");
rc = QueryMemoryMapping(&sysVirtAddr, 0x6000C000, 0x1000);
ASSERT_RESULT_OK(rc, "QueryMemoryMapping (sys)");
actmonVirtAddr = sysVirtAddr + 0x800;
FetchHardwareInfos();
Result nvCheck = 1;
@@ -173,6 +178,11 @@ namespace board {
tsensor::InitializeSoctherm(); // SOCTHERM must be init before AOTAG
Result bpmpfwRc = bpmp::StartBpmfwExecution();
if (R_FAILED(bpmpfwRc)) {
fileUtils::LogLine("[bpmp] StartBpmfwExecution failed: 0x%x", bpmpfwRc);
}
// PMC exosphere check
SecmonArgs args = {};
args.X[0] = 0xF0000002;
+1 -1
View File
@@ -42,7 +42,7 @@
#define HOSSVC_HAS_TC (hosversionAtLeast(5, 0, 0))
namespace board {
extern u64 clkVirtAddr, dsiVirtAddr, apbVirtAddr, fuseVirtAddr;
extern u64 clkVirtAddr, dsiVirtAddr, apbVirtAddr, fuseVirtAddr, sysVirtAddr, actmonVirtAddr;
extern HocClkSocType gSocType;
extern u8 gDramID;
extern HocClkConsoleType gConsoleType;
+140
View File
@@ -0,0 +1,140 @@
/*
* Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <cstdio>
#include <cstring>
#include <switch.h>
#include "../board/board.hpp"
#include "../file/file_utils.hpp"
#include "../mapping/mem_map.hpp"
#include "bpmp.hpp"
namespace bpmp {
namespace {
constexpr u64 FlowCtlrPhysBase = 0x60007000;
constexpr u64 EvpPhysBase = 0x6000F000;
constexpr u32 ClkRstRstDevLSet = 0x300;
constexpr u32 ClkRstRstDevLClr = 0x304;
constexpr u32 RstDevLCopRstBit = (1u << 1); /* SWR_COP_RST */
constexpr u32 ApbMiscSlaveSecurityReg0 = 0xC00;
constexpr u32 ApbMiscSlaveSecurityReg1 = 0xC04;
constexpr u32 ApbMiscSlaveSecurityReg2 = 0xC08;
/* Relative to actmonVirtAddr */
constexpr u32 ActmonCopCtrl = 0xC0;
constexpr u32 ActmonCopIntrStatus = 0xE4;
constexpr u32 EvpCopResetVector = 0x200;
constexpr u32 EvpCopUndefVector = 0x204;
constexpr u32 EvpCopSwiVector = 0x208;
constexpr u32 EvpCopPrefetchAbortVector = 0x20C;
constexpr u32 EvpCopDataAbortVector = 0x210;
constexpr u32 EvpCopRsvdVector = 0x214;
constexpr u32 EvpCopIrqVector = 0x218;
constexpr u32 EvpCopFiqVector = 0x21C;
constexpr u32 FlowCtlrHaltCopEvents = 0x004;
constexpr u32 FlowModeNone = 0x00000000;
constexpr u64 FwIramPhysBase = 0x40004000;
constexpr u32 FwStagingSize = 0x8000;
alignas(4096) u8 s_fwStageBuf[FwStagingSize];
inline volatile u32 &Mmio(u64 base_va, u32 offset) {
return *reinterpret_cast<volatile u32 *>(base_va + offset);
}
} // namespace
Result StartBpmfwExecution() {
if (!IsPatchedExosphere()) {
fileUtils::LogLine("[bpmp] Cannot start without patched exosphere");
return (Result)1;
}
std::memset(s_fwStageBuf, 0, sizeof(s_fwStageBuf));
FILE *f = fopen("sdmc:/config/horizon-oc/bpmpfw.bin", "rb");
if (!f) {
fileUtils::LogLine("[bpmp] StartBpmfwExecution: bpmpfw.bin not found");
return (Result)1;
}
const size_t fw_size = fread(s_fwStageBuf, 1, sizeof(s_fwStageBuf), f);
fclose(f);
if (fw_size == 0) {
fileUtils::LogLine("[bpmp] StartBpmfwExecution: bpmpfw.bin is empty");
return (Result)1;
}
fileUtils::LogLine("[bpmp] StartBpmfwExecution: loaded %zu bytes, loading at 0x%llx",
fw_size, static_cast<unsigned long long>(FwIramPhysBase));
/* Set BPMP reset */
Mmio(board::clkVirtAddr, ClkRstRstDevLSet) = RstDevLCopRstBit;
/* Grant BPMP access to all a bunch of registers that are useful */
Mmio(board::apbVirtAddr, ApbMiscSlaveSecurityReg0) = 0;
Mmio(board::apbVirtAddr, ApbMiscSlaveSecurityReg1) = 0;
Mmio(board::apbVirtAddr, ApbMiscSlaveSecurityReg2) = 0;
/* Set up exeption vectors for bpmp */
const u32 fw_reset_vector = static_cast<u32>(FwIramPhysBase + 0x0);
const u32 fw_panic_vector = static_cast<u32>(FwIramPhysBase + 0x4);
SmcReadWriteRegister(EvpPhysBase + EvpCopResetVector, 0xFFFFFFFF, fw_reset_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopUndefVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopSwiVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopPrefetchAbortVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopDataAbortVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopRsvdVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopIrqVector, 0xFFFFFFFF, fw_panic_vector);
SmcReadWriteRegister(EvpPhysBase + EvpCopFiqVector, 0xFFFFFFFF, fw_panic_vector);
/* Disable actmon monitoring to avoid hang on FW >4.0.0 */
Mmio(board::actmonVirtAddr, ActmonCopCtrl) = 0;
Mmio(board::actmonVirtAddr, ActmonCopIntrStatus) = Mmio(board::actmonVirtAddr, ActmonCopIntrStatus);
/* Load the BPMPFW into iram per page. TODO: determine if this is nessesary */
constexpr size_t PageSize = 4096;
for (size_t ofs = 0; ofs < fw_size; ofs += PageSize) {
const size_t chunk = (fw_size - ofs) < PageSize ? (fw_size - ofs) : PageSize;
Result copy_rc = SmcCopyToIram(FwIramPhysBase + ofs, s_fwStageBuf + ofs, static_cast<u32>(chunk));
if (R_FAILED(copy_rc)) {
fileUtils::LogLine("[bpmp] SmcCopyToIram failed at +0x%zx: 0x%x", ofs, copy_rc);
return copy_rc;
}
}
/* Clear BPMP reset. */
Mmio(board::clkVirtAddr, ClkRstRstDevLClr) = RstDevLCopRstBit;
/* Clear the halt written by bootloader. */
SmcReadWriteRegister(FlowCtlrPhysBase + FlowCtlrHaltCopEvents, 0xFFFFFFFF, FlowModeNone);
return 0;
}
} // namespace bpmp
@@ -0,0 +1,23 @@
/*
* Copyright (c) Souldbminer, Lightos and Horizon OC Contributors
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#pragma once
#include <switch.h>
namespace bpmp {
Result StartBpmfwExecution();
}
@@ -65,4 +65,28 @@ Result SmcCopyToIram(uintptr_t dest, const void *src, u32 size) {
rc = (26u | ((u32)args.X[0] << 9u));
}
return rc;
}
bool IsPatchedExosphere() {
constexpr u64 EvpPhysBase = 0x6000F000;
constexpr u64 EvpCopResetVector = 0x200;
SecmonArgs args = {};
args.X[0] = 0xF0000002;
args.X[1] = EvpPhysBase + EvpCopResetVector;
args.X[2] = 0;
args.X[3] = 0;
svcCallSecureMonitor(&args);
return args.X[0] == 0;
}
u32 SmcReadWriteRegister(u64 phys_addr, u32 mask, u32 value) {
SecmonArgs args = {};
args.X[0] = 0xF0000002;
args.X[1] = phys_addr;
args.X[2] = mask;
args.X[3] = value;
svcCallSecureMonitor(&args);
return (u32)args.X[1];
}
@@ -22,3 +22,5 @@ Result QueryMemoryMapping(u64 *virtaddr, u64 physaddr, u64 size);
Result MapAddress(u64 &va, const u64 &physAddr, const char *name);
Result SmcCopyFromIram(void *dest, uintptr_t src, u32 size);
Result SmcCopyToIram(uintptr_t dest, const void *src, u32 size);
bool IsPatchedExosphere();
u32 SmcReadWriteRegister(u64 phys_addr, u32 mask, u32 value);