feat: Raspberry Pi 4/400 Bluetooth over mini UART (H4, Broadcom patchram, firmware mailbox BT_ON); move BT stack to Drivers/Bluetooth
This commit is contained in:
1 parent
808e1d66fd
commit
ffee9002a6
38 files changed
+2176
-141
No files matched your search
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* RpiFirmware.cpp
|
||||
* Raspberry Pi VideoCore firmware mailbox (property interface)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "RpiFirmware.hpp"
|
||||
#include <ACPI/AcpiDevices.hpp>
|
||||
#include <Arch/Cpu.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Boot/Boot.hpp>
|
||||
#include <Memory/Memmap.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <Timekeeping/Timer.hpp>
|
||||
|
||||
// Property interface (raspberrypi/firmware wiki, "Mailbox property
|
||||
// interface"): the ARM writes a message buffer's bus address, tagged with
|
||||
// channel 8, to mailbox 1 and reads the same address back from mailbox 0
|
||||
// once the firmware has written its responses into the buffer.
|
||||
//
|
||||
// u32 size, u32 code (0 = request; 0x80000000 = success),
|
||||
// tags: { u32 id, u32 value buffer size, u32 req/resp (bit 31 + length),
|
||||
// value buffer ... }, u32 0 (end tag)
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::Platform::RpiFirmware {
|
||||
|
||||
// Mailbox registers (offsets from the block ACPI describes, 0x7E00B880)
|
||||
static constexpr uint32_t MBOX_READ = 0x00; // mailbox 0: VC -> ARM
|
||||
static constexpr uint32_t MBOX0_STATUS = 0x18;
|
||||
static constexpr uint32_t MBOX_WRITE = 0x20; // mailbox 1: ARM -> VC
|
||||
static constexpr uint32_t MBOX1_STATUS = 0x38;
|
||||
static constexpr uint32_t STATUS_FULL = 1u << 31;
|
||||
static constexpr uint32_t STATUS_EMPTY = 1u << 30;
|
||||
|
||||
static constexpr uint32_t CHANNEL_PROPERTY = 8;
|
||||
|
||||
static constexpr uint32_t CODE_REQUEST = 0x00000000;
|
||||
static constexpr uint32_t CODE_SUCCESS = 0x80000000;
|
||||
static constexpr uint32_t TAG_RESPONSE = 0x80000000;
|
||||
|
||||
static constexpr uint32_t TAG_GET_BOARD_MAC = 0x00010003;
|
||||
static constexpr uint32_t TAG_GET_CLOCK_RATE = 0x00030002;
|
||||
static constexpr uint32_t TAG_GET_GPIO_STATE = 0x00030041;
|
||||
static constexpr uint32_t TAG_SET_GPIO_STATE = 0x00038041;
|
||||
|
||||
// The VideoCore sees the first GiB of SDRAM at bus address 0xC0000000
|
||||
// (the uncached alias), and nothing above it, so the message buffer
|
||||
// must sit below 1 GiB.
|
||||
static constexpr uint64_t VC_BUS_ALIAS = 0xC0000000;
|
||||
static constexpr uint64_t VC_DMA_LIMIT = 0x40000000;
|
||||
|
||||
static constexpr uint64_t TimeoutMs = 200;
|
||||
|
||||
static volatile uint8_t* g_mmio = nullptr;
|
||||
static volatile uint32_t* g_buffer = nullptr; // uncached, below 1 GiB
|
||||
static uint64_t g_bufferPhys = 0;
|
||||
static bool g_probed = false;
|
||||
static kcp::Mutex g_lock;
|
||||
|
||||
static inline uint32_t Read32(uint32_t offset) {
|
||||
return *(volatile uint32_t*)(g_mmio + offset);
|
||||
}
|
||||
|
||||
static inline void Write32(uint32_t offset, uint32_t value) {
|
||||
*(volatile uint32_t*)(g_mmio + offset) = value;
|
||||
}
|
||||
|
||||
// A page the VideoCore can reach. The page-frame allocator only manages
|
||||
// the largest run of usable RAM, which on a Pi 4 with the 3 GB limit is
|
||||
// 0x40000000 and up, so it never has one. The usable regions below 1 GiB
|
||||
// are otherwise untouched: take the last page of the highest one.
|
||||
static void* AllocateLowPage(uint64_t& phys) {
|
||||
const auto& map = montauk::boot::Info().memoryMap;
|
||||
Memory::LargestSection pool = Memory::Scan(map);
|
||||
|
||||
uint64_t best = 0;
|
||||
for (uint64_t i = 0; i < map.count; i++) {
|
||||
const auto& r = map.regions[i];
|
||||
if (r.kind != montauk::boot::MemoryKind::Usable || r.base == 0) continue;
|
||||
if (r.base == pool.address) continue; // the allocator's
|
||||
uint64_t end = r.base + r.length;
|
||||
if (end > VC_DMA_LIMIT) end = VC_DMA_LIMIT;
|
||||
if (end < r.base + 0x1000) continue;
|
||||
uint64_t page = (end - 0x1000) & ~0xFFFULL;
|
||||
if (page >= r.base && page > best) best = page;
|
||||
}
|
||||
if (best == 0) return nullptr;
|
||||
|
||||
// Zero it through its cacheable HHDM alias, push the lines out, then
|
||||
// make the alias uncached so neither side needs cache maintenance.
|
||||
void* virt = (void*)Memory::HHDM(best);
|
||||
memset(virt, 0, 0x1000);
|
||||
Arch::InvalidateDataCache(virt, 0x1000);
|
||||
Memory::VMM::g_paging->MapWC(best, (uint64_t)virt);
|
||||
phys = best;
|
||||
return virt;
|
||||
}
|
||||
|
||||
bool Initialize() {
|
||||
if (g_probed) return g_buffer != nullptr;
|
||||
g_probed = true;
|
||||
|
||||
Hal::AcpiDevices::DeviceInfo info{};
|
||||
if (!Hal::AcpiDevices::FindDeviceByHid("BCM2849", info)) return false;
|
||||
|
||||
Hal::AML::ResourceList resources;
|
||||
uint64_t base = 0, length = 0;
|
||||
if (!Hal::AcpiDevices::EvaluateCrs(info.NodeIndex, resources) ||
|
||||
!resources.GetMemory(base, length) || base == 0) {
|
||||
KernelLogStream(WARNING, "RpiFw") << "Mailbox " << info.Path << ": no register block in _CRS";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t page = base & ~0xFFFULL;
|
||||
Memory::VMM::g_paging->MapMMIO(page, Memory::HHDM(page));
|
||||
if (((base + 0x40) & ~0xFFFULL) != page) {
|
||||
Memory::VMM::g_paging->MapMMIO(page + 0x1000, Memory::HHDM(page + 0x1000));
|
||||
}
|
||||
g_mmio = (volatile uint8_t*)Memory::HHDM(base);
|
||||
|
||||
uint64_t phys = 0;
|
||||
void* buffer = AllocateLowPage(phys);
|
||||
if (buffer == nullptr) {
|
||||
KernelLogStream(ERROR, "RpiFw") << "No message buffer below 1 GiB";
|
||||
g_mmio = nullptr;
|
||||
return false;
|
||||
}
|
||||
g_buffer = (volatile uint32_t*)buffer;
|
||||
g_bufferPhys = phys;
|
||||
|
||||
KernelLogStream(OK, "RpiFw") << "VideoCore mailbox at 0x" << base::hex << base
|
||||
<< ", buffer 0x" << phys << base::dec;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPresent() {
|
||||
return Initialize();
|
||||
}
|
||||
|
||||
// Send the message in g_buffer and wait for the firmware's reply.
|
||||
// Caller holds g_lock.
|
||||
static bool Call() {
|
||||
uint32_t message = (uint32_t)((g_bufferPhys | VC_BUS_ALIAS) & ~0xFULL) | CHANNEL_PROPERTY;
|
||||
|
||||
// Drop any stale replies (a previous call that timed out).
|
||||
for (int i = 0; i < 16 && !(Read32(MBOX0_STATUS) & STATUS_EMPTY); i++) {
|
||||
(void)Read32(MBOX_READ);
|
||||
}
|
||||
|
||||
uint64_t start = Timekeeping::GetMilliseconds();
|
||||
while (Read32(MBOX1_STATUS) & STATUS_FULL) {
|
||||
if (Timekeeping::GetMilliseconds() - start > TimeoutMs) return false;
|
||||
Arch::Pause();
|
||||
}
|
||||
|
||||
// The buffer is uncached; the fence orders its stores before the
|
||||
// doorbell write the firmware reacts to.
|
||||
Arch::MemoryFence();
|
||||
Write32(MBOX_WRITE, message);
|
||||
|
||||
start = Timekeeping::GetMilliseconds();
|
||||
for (;;) {
|
||||
while (Read32(MBOX0_STATUS) & STATUS_EMPTY) {
|
||||
if (Timekeeping::GetMilliseconds() - start > TimeoutMs) {
|
||||
KernelLogStream(WARNING, "RpiFw") << "Mailbox call timed out";
|
||||
return false;
|
||||
}
|
||||
Arch::Pause();
|
||||
}
|
||||
uint32_t reply = Read32(MBOX_READ);
|
||||
if (reply == message) break;
|
||||
}
|
||||
Arch::MemoryFence();
|
||||
return g_buffer[1] == CODE_SUCCESS;
|
||||
}
|
||||
|
||||
// One-tag property call. `values` holds valueWords request words and
|
||||
// receives up to valueWords response words. Returns the response length
|
||||
// in bytes, or -1 on failure.
|
||||
static int PropertyCall(uint32_t tag, uint32_t* values, uint32_t valueWords) {
|
||||
if (!Initialize()) return -1;
|
||||
if (valueWords > 16) return -1;
|
||||
|
||||
g_lock.Acquire();
|
||||
uint32_t words = 0;
|
||||
g_buffer[words++] = 0; // size, filled in below
|
||||
g_buffer[words++] = CODE_REQUEST;
|
||||
g_buffer[words++] = tag;
|
||||
g_buffer[words++] = valueWords * 4; // value buffer size
|
||||
g_buffer[words++] = 0; // request
|
||||
uint32_t valueStart = words;
|
||||
for (uint32_t i = 0; i < valueWords; i++) g_buffer[words++] = values[i];
|
||||
g_buffer[words++] = 0; // end tag
|
||||
g_buffer[0] = words * 4;
|
||||
|
||||
int result = -1;
|
||||
if (Call()) {
|
||||
uint32_t status = g_buffer[valueStart - 1];
|
||||
if (status & TAG_RESPONSE) {
|
||||
uint32_t length = status & ~TAG_RESPONSE;
|
||||
uint32_t copy = (length + 3) / 4;
|
||||
if (copy > valueWords) copy = valueWords;
|
||||
for (uint32_t i = 0; i < copy; i++) values[i] = g_buffer[valueStart + i];
|
||||
result = (int)length;
|
||||
}
|
||||
}
|
||||
g_lock.Release();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool GetClockRate(uint32_t clockId, uint32_t& hz) {
|
||||
uint32_t v[2] = { clockId, 0 };
|
||||
if (PropertyCall(TAG_GET_CLOCK_RATE, v, 2) < 8 || v[0] != clockId) return false;
|
||||
hz = v[1];
|
||||
return hz != 0;
|
||||
}
|
||||
|
||||
// The GPIO tags answer in place of the GPIO number with a status word,
|
||||
// 0 for success (as Linux's raspberrypi-exp-gpio driver checks).
|
||||
bool SetGpioState(uint32_t gpio, bool high) {
|
||||
uint32_t v[2] = { gpio, high ? 1u : 0u };
|
||||
return PropertyCall(TAG_SET_GPIO_STATE, v, 2) >= 4 && v[0] == 0;
|
||||
}
|
||||
|
||||
bool GetGpioState(uint32_t gpio, bool& high) {
|
||||
uint32_t v[2] = { gpio, 0 };
|
||||
if (PropertyCall(TAG_GET_GPIO_STATE, v, 2) < 8 || v[0] != 0) return false;
|
||||
high = v[1] != 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GetBoardMacAddress(uint8_t mac[6]) {
|
||||
uint32_t v[2] = {};
|
||||
if (PropertyCall(TAG_GET_BOARD_MAC, v, 2) < 6) return false;
|
||||
const uint8_t* bytes = (const uint8_t*)v;
|
||||
bool allZero = true;
|
||||
for (int i = 0; i < 6; i++) {
|
||||
mac[i] = bytes[i];
|
||||
if (bytes[i] != 0) allZero = false;
|
||||
}
|
||||
return !allZero;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* RpiFirmware.hpp
|
||||
* Raspberry Pi VideoCore firmware mailbox (property interface)
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// The VideoCore firmware owns parts of the board the ARM cores cannot reach
|
||||
// directly: the GPIO expander that powers the Wi-Fi/Bluetooth radio, the
|
||||
// clock tree, the board's OTP (serial number, MAC address). The ARM side asks
|
||||
// for them through the property channel of the VideoCore mailbox, which
|
||||
// ACPI describes as BCM2849 (\_SB.GDV0.RPIQ) on the Pi Firmware Task Force
|
||||
// UEFI firmware.
|
||||
//
|
||||
// All calls are synchronous, take a process-context lock, and must not be
|
||||
// made from interrupt context. They return false when there is no mailbox
|
||||
// (any machine but a Pi) or the firmware rejects the request.
|
||||
|
||||
namespace Drivers::Platform::RpiFirmware {
|
||||
|
||||
// Find the mailbox through ACPI and allocate its message buffer.
|
||||
// Idempotent; the other calls initialize on first use.
|
||||
bool Initialize();
|
||||
|
||||
bool IsPresent();
|
||||
|
||||
// Clock identifiers (the firmware's numbering).
|
||||
constexpr uint32_t CLOCK_CORE = 4; // VPU core clock, feeds the mini UART
|
||||
|
||||
// Current rate of a firmware clock, in Hz.
|
||||
bool GetClockRate(uint32_t clockId, uint32_t& hz);
|
||||
|
||||
// The firmware's GPIO expander lines are numbered from 128.
|
||||
constexpr uint32_t EXPGPIO_BASE = 128;
|
||||
constexpr uint32_t EXPGPIO_BT_ON = EXPGPIO_BASE + 0; // Bluetooth power (high = on)
|
||||
constexpr uint32_t EXPGPIO_WL_ON = EXPGPIO_BASE + 1; // Wi-Fi power (high = on)
|
||||
|
||||
bool SetGpioState(uint32_t gpio, bool high);
|
||||
bool GetGpioState(uint32_t gpio, bool& high);
|
||||
|
||||
// The board's Ethernet MAC address from OTP, in transmission order
|
||||
// (mac[0] is the first octet of the OUI).
|
||||
bool GetBoardMacAddress(uint8_t mac[6]);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* RpiGpio.cpp
|
||||
* BCM2711 GPIO pin function and pull control
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "RpiGpio.hpp"
|
||||
#include <ACPI/AcpiDevices.hpp>
|
||||
#include <CppLib/Spinlock.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
|
||||
using namespace Kt;
|
||||
|
||||
namespace Drivers::Platform::RpiGpio {
|
||||
|
||||
// BCM2711 ARM Peripherals, chapter 5
|
||||
static constexpr uint32_t REG_GPFSEL0 = 0x00; // 10 pins per register, 3 bits each
|
||||
static constexpr uint32_t REG_PUP_PDN_CNTRL0 = 0xE4; // 16 pins per register, 2 bits each
|
||||
static constexpr uint32_t PinCount = 58;
|
||||
|
||||
static volatile uint8_t* g_mmio = nullptr;
|
||||
static bool g_probed = false;
|
||||
static kcp::Spinlock g_lock;
|
||||
|
||||
static bool Initialize() {
|
||||
if (g_probed) return g_mmio != nullptr;
|
||||
g_probed = true;
|
||||
|
||||
Hal::AcpiDevices::DeviceInfo info{};
|
||||
if (!Hal::AcpiDevices::FindDeviceByHid("BCM2845", info)) return false;
|
||||
|
||||
Hal::AML::ResourceList resources;
|
||||
uint64_t base = 0, length = 0;
|
||||
if (!Hal::AcpiDevices::EvaluateCrs(info.NodeIndex, resources) ||
|
||||
!resources.GetMemory(base, length) || base == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint64_t page = base & ~0xFFFULL;
|
||||
Memory::VMM::g_paging->MapMMIO(page, Memory::HHDM(page));
|
||||
g_mmio = (volatile uint8_t*)Memory::HHDM(base);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void Modify(uint32_t offset, uint32_t mask, uint32_t value) {
|
||||
g_lock.Acquire();
|
||||
volatile uint32_t* reg = (volatile uint32_t*)(g_mmio + offset);
|
||||
*reg = (*reg & ~mask) | (value & mask);
|
||||
g_lock.Release();
|
||||
}
|
||||
|
||||
bool SetFunction(uint32_t pin, Function function) {
|
||||
if (pin >= PinCount || !Initialize()) return false;
|
||||
uint32_t shift = (pin % 10) * 3;
|
||||
Modify(REG_GPFSEL0 + (pin / 10) * 4, 7u << shift, (uint32_t)function << shift);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetPull(uint32_t pin, Pull pull) {
|
||||
if (pin >= PinCount || !Initialize()) return false;
|
||||
uint32_t shift = (pin % 16) * 2;
|
||||
Modify(REG_PUP_PDN_CNTRL0 + (pin / 16) * 4, 3u << shift, (uint32_t)pull << shift);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* RpiGpio.hpp
|
||||
* BCM2711 GPIO pin function and pull control
|
||||
* Copyright (c) 2026 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
// The SoC's own GPIO block (ACPI BCM2845, \_SB.GDV0.GPI0), used to route
|
||||
// pins to the peripheral that drives them. On Linux the device tree's
|
||||
// pinctrl does this; booted through ACPI, nothing does it for us. Only the
|
||||
// BCM2711 (Raspberry Pi 4/400) pull-control layout is implemented.
|
||||
|
||||
namespace Drivers::Platform::RpiGpio {
|
||||
|
||||
enum class Function : uint8_t {
|
||||
Input = 0b000,
|
||||
Output = 0b001,
|
||||
Alt0 = 0b100,
|
||||
Alt1 = 0b101,
|
||||
Alt2 = 0b110,
|
||||
Alt3 = 0b111,
|
||||
Alt4 = 0b011,
|
||||
Alt5 = 0b010,
|
||||
};
|
||||
|
||||
enum class Pull : uint8_t {
|
||||
None = 0b00,
|
||||
Up = 0b01,
|
||||
Down = 0b10,
|
||||
};
|
||||
|
||||
bool SetFunction(uint32_t pin, Function function);
|
||||
bool SetPull(uint32_t pin, Pull pull);
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user