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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in new issue
Block a user