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:
daniel committed 2026-10-25 15:59:33 +01:00
1 parent 808e1d66fd
commit ffee9002a6
38 files changed
+2176 -141

No files matched your search

+43 -2
View File
@@ -95,6 +95,9 @@ PL011 on GPIO 14/15: header pins 8 (TX) and 10 (RX), ground on pin 6, 115200
| Keyboard (built in), USB ports | VL805 xHCI as ACPI `\_SB.SCB0.XHC0` (`PNP0D10`) | `Drivers/USB/Xhci.cpp` platform probe | | Keyboard (built in), USB ports | VL805 xHCI as ACPI `\_SB.SCB0.XHC0` (`PNP0D10`) | `Drivers/USB/Xhci.cpp` platform probe |
| USB 2 devices (built-in keyboard too) | VIA hub on the VL805's USB 2 root port | `Drivers/USB/Hub.cpp` | | USB 2 devices (built-in keyboard too) | VIA hub on the VL805's USB 2 root port | `Drivers/USB/Hub.cpp` |
| Reset, power-off | PSCI | `Arch::Reboot`, `Arch::PowerOff` | | Reset, power-off | PSCI | `Arch::Reboot`, `Arch::PowerOff` |
| Bluetooth (CYW43455 on the 4B, CYW43456 on the 400) | `BCM2EA6` (`\_SB.GDV0.BTH0`) on the mini UART (`BCM2836`) | `Drivers/Bluetooth/Broadcom.cpp`, `Drivers/Serial/BtUart.cpp` |
| VideoCore firmware mailbox (radio power, clocks, board MAC) | `BCM2849` (`RPIQ`) | `Drivers/Platform/RpiFirmware.cpp` |
| GPIO pin functions | `BCM2845` (`GPI0`) | `Drivers/Platform/RpiGpio.cpp` |
The VL805 sits behind the BCM2711's PCIe, whose config space is not ECAM The VL805 sits behind the BCM2711's PCIe, whose config space is not ECAM
(the root bridge's `_DSD` says `linux-ecam-quirk-id = "bcm2711"`), so the (the root bridge's `_DSD` says `linux-ecam-quirk-id = "bcm2711"`), so the
@@ -114,18 +117,56 @@ socket, and the Pi 400's internal keyboard (`04d9:0007`), hang off it: a
keyboard or mouse is always a low/full-speed device behind a high-speed hub, keyboard or mouse is always a low/full-speed device behind a high-speed hub,
reached through that hub's transaction translator. reached through that hub's transaction translator.
## Bluetooth
The radio's Bluetooth half speaks HCI over a UART (H4) on GPIO 30-33 (CTS,
RTS, TXD, RXD); its power switch, `BT_ON`, is line 0 of the VideoCore
firmware's GPIO expander, reached through the mailbox. The firmware tables
describe it as `BTH0`, whose `UARTSerialBus` names the UART it uses: with
the `miniuart-bt` overlay the PL011 is the console, so Bluetooth has the mini
UART. The kernel refuses to take a UART that is the SPCR console.
At boot the driver probe routes the pins (`ALT5` for the mini UART, `ALT3`
for the PL011), starts the UART (interrupt driven, 16 KiB receive and
transmit rings) and the H4 transport under the shared HCI layer. Once the
root is mounted the idle loop finishes, as for USB adapters:
1. power-cycle the controller through `BT_ON`, HCI Reset at 115200 baud;
2. Read Local Version: the ROM's LMP subversion picks the patch file,
`0:/os/firmware/brcm/BCM4345C0.hcd` (0x6119, 4B) or `BCM4345C5.hcd`
(0x6606, 400), shipped unmodified from Raspberry Pi OS's bluez-firmware;
3. raise both ends to the operating speed (vendor `0xFC18`);
4. Download Minidriver (`0xFC2E`), then every record of the `.hcd` file
(Write RAM, then Launch RAM), as Linux's `btbcm` does;
5. the patched firmware restarts at 115200: reset, raise the speed again.
The mini UART's FIFO is 8 bytes deep. Its hardware RTS is tried first; if
the controller answers with it on, the link runs at 921600 baud, otherwise
at 460800 without flow control (what Raspberry Pi OS uses), and at 115200
if the UART has no interrupt. The baud rate derives from the VPU core
clock, so `mk-rpi4-sd.sh` pins it (`core_freq=500`, `core_freq_min=500` in
`config.txt`), as the overlay's documentation requires. A failed fast
bring-up is retried entirely at 115200.
The CYW43456 reports a chip-wide default address (`43:45:C5:00:1F:AC`);
the driver replaces it with the board's Ethernet MAC from the firmware.
`0:/config/bluetooth.toml` still overrides both (Broadcom `0xFC01`).
## Not supported yet ## Not supported yet
- Ethernet (BCM54213 via GENET v5, `BCM6E4E`). - Ethernet (BCM54213 via GENET v5, `BCM6E4E`).
- SD high-speed mode: the driver transfers by polled PIO at 25 MHz on a - SD high-speed mode: the driver transfers by polled PIO at 25 MHz on a
4-bit bus (multi-block CMD18/CMD25 with Auto CMD12), at most 12.5 MB/s; 4-bit bus (multi-block CMD18/CMD25 with Auto CMD12), at most 12.5 MB/s;
switching the card to 50 MHz (CMD6) would double that. switching the card to 50 MHz (CMD6) would double that.
- Wi-Fi and Bluetooth (CYW43455 over SDIO/UART). - Wi-Fi (the CYW4345x's SDIO half, on the Arasan controller).
- Audio (HDMI via VideoCore, the 3.5 mm jack via PWM). - Audio (HDMI via VideoCore, the 3.5 mm jack via PWM).
## Status ## Status
Everything above is verified on QEMU only, including the Pi-specific path: Bluetooth has not run yet: QEMU has no model of the radio, so it is
untested on any machine.
Everything else above is verified on QEMU only, including the Pi-specific path:
the firmware's own XHC0 table, retargeted at QEMU's xHCI and loaded with the the firmware's own XHC0 table, retargeted at QEMU's xHCI and loaded with the
`bcm2711` quirk on QEMU's root bridge, boots to the desktop with non-coherent `bcm2711` quirk on QEMU's root bridge, boots to the desktop with non-coherent
DMA. Real hardware has not been tried yet. The ext2 root on the SD card boots on QEMU (`run-rpi4-sd`); the DMA. Real hardware has not been tried yet. The ext2 root on the SD card boots on QEMU (`run-rpi4-sd`); the
+6 -6
View File
@@ -11,8 +11,8 @@
#include <Drivers/Audio/IntelHda.hpp> #include <Drivers/Audio/IntelHda.hpp>
#include <Drivers/Audio/Mixer.hpp> #include <Drivers/Audio/Mixer.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <Drivers/USB/Bluetooth/Bluetooth.hpp> #include <Drivers/Bluetooth/Bluetooth.hpp>
#include <Drivers/USB/Bluetooth/A2dp.hpp> #include <Drivers/Bluetooth/A2dp.hpp>
#include <Sched/Scheduler.hpp> #include <Sched/Scheduler.hpp>
#include "Syscall.hpp" #include "Syscall.hpp"
@@ -55,10 +55,10 @@ namespace montauk::abi {
return Drivers::Audio::Mixer::SetOutput( return Drivers::Audio::Mixer::SetOutput(
(Drivers::Audio::Mixer::Output)value); (Drivers::Audio::Mixer::Output)value);
if (cmd == AUDIO_CTL_BT_STATUS) { if (cmd == AUDIO_CTL_BT_STATUS) {
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0; if (!Drivers::Bluetooth::IsInitialized()) return 0;
if (Drivers::USB::Bluetooth::A2dp::IsReady()) return 2; if (Drivers::Bluetooth::A2dp::IsReady()) return 2;
return Drivers::USB::Bluetooth::A2dp::GetState() return Drivers::Bluetooth::A2dp::GetState()
== Drivers::USB::Bluetooth::A2dp::State::Idle == Drivers::Bluetooth::A2dp::State::Idle
? 0 : 1; ? 0 : 1;
} }
+22 -22
View File
@@ -6,8 +6,8 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
#include <Drivers/USB/Bluetooth/Bluetooth.hpp> #include <Drivers/Bluetooth/Bluetooth.hpp>
#include <Drivers/USB/Bluetooth/Hci.hpp> #include <Drivers/Bluetooth/Hci.hpp>
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
#include <Sched/Scheduler.hpp> #include <Sched/Scheduler.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
@@ -19,14 +19,14 @@ namespace montauk::abi {
static int64_t Sys_BtScan(BtScanResult* buf, int maxCount, uint32_t timeoutMs) { static int64_t Sys_BtScan(BtScanResult* buf, int maxCount, uint32_t timeoutMs) {
if (!buf || maxCount <= 0) return InvalidArgument; if (!buf || maxCount <= 0) return InvalidArgument;
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice; if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
// Use a stack buffer for kernel-side results (max 16) // Use a stack buffer for kernel-side results (max 16)
Drivers::USB::Bluetooth::Hci::InquiryDevice tmpBuf[16]; Drivers::Bluetooth::Hci::InquiryDevice tmpBuf[16];
int kMax = maxCount; int kMax = maxCount;
if (kMax > 16) kMax = 16; if (kMax > 16) kMax = 16;
int found = Drivers::USB::Bluetooth::Scan(tmpBuf, kMax, timeoutMs); int found = Drivers::Bluetooth::Scan(tmpBuf, kMax, timeoutMs);
if (found < 0) return found; if (found < 0) return found;
// Convert kernel InquiryDevice to userspace BtScanResult // Convert kernel InquiryDevice to userspace BtScanResult
@@ -47,38 +47,38 @@ namespace montauk::abi {
static int64_t Sys_BtConnect(const uint8_t* bdAddr) { static int64_t Sys_BtConnect(const uint8_t* bdAddr) {
if (!bdAddr) return InvalidArgument; if (!bdAddr) return InvalidArgument;
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice; if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
return (int64_t)Drivers::USB::Bluetooth::Connect(bdAddr); return (int64_t)Drivers::Bluetooth::Connect(bdAddr);
} }
static int64_t Sys_BtDisconnect(const uint8_t* bdAddr) { static int64_t Sys_BtDisconnect(const uint8_t* bdAddr) {
if (!bdAddr) return InvalidArgument; if (!bdAddr) return InvalidArgument;
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice; if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
Kt::KernelLogStream(Kt::INFO, "Power") << "pid " << kcp::dec Kt::KernelLogStream(Kt::INFO, "Power") << "pid " << kcp::dec
<< (uint64_t)Sched::GetCurrentPid() << (uint64_t)Sched::GetCurrentPid()
<< " entered Bluetooth disconnect stage"; << " entered Bluetooth disconnect stage";
return (int64_t)Drivers::USB::Bluetooth::Disconnect(bdAddr); return (int64_t)Drivers::Bluetooth::Disconnect(bdAddr);
} }
static int64_t Sys_BtSetAddr(const uint8_t* bdAddr) { static int64_t Sys_BtSetAddr(const uint8_t* bdAddr) {
if (!bdAddr) return InvalidArgument; if (!bdAddr) return InvalidArgument;
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice; if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
// bdAddr is a 6-byte buffer, addr[0] = least-significant octet. // bdAddr is a 6-byte buffer, addr[0] = least-significant octet.
return Drivers::USB::Bluetooth::SetAddress(bdAddr) ? Success : GenericFailure; return Drivers::Bluetooth::SetAddress(bdAddr) ? Success : GenericFailure;
} }
static int64_t Sys_BtBonds(BtBondInfo* buf, int maxCount) { static int64_t Sys_BtBonds(BtBondInfo* buf, int maxCount) {
if (!buf || maxCount <= 0) return 0; if (!buf || maxCount <= 0) return 0;
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0; if (!Drivers::Bluetooth::IsInitialized()) return 0;
Drivers::USB::Bluetooth::Hci::BondInfo tmpBuf[8]; Drivers::Bluetooth::Hci::BondInfo tmpBuf[8];
int kMax = maxCount; int kMax = maxCount;
if (kMax > 8) kMax = 8; if (kMax > 8) kMax = 8;
int count = Drivers::USB::Bluetooth::ListBonded(tmpBuf, kMax); int count = Drivers::Bluetooth::ListBonded(tmpBuf, kMax);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
memcpy(buf[i].bdAddr, tmpBuf[i].Addr, 6); memcpy(buf[i].bdAddr, tmpBuf[i].Addr, 6);
@@ -91,20 +91,20 @@ namespace montauk::abi {
static int64_t Sys_BtForget(const uint8_t* bdAddr) { static int64_t Sys_BtForget(const uint8_t* bdAddr) {
if (!bdAddr) return InvalidArgument; if (!bdAddr) return InvalidArgument;
if (!Drivers::USB::Bluetooth::IsInitialized()) return NoDevice; if (!Drivers::Bluetooth::IsInitialized()) return NoDevice;
return (int64_t)Drivers::USB::Bluetooth::ForgetDevice(bdAddr); return (int64_t)Drivers::Bluetooth::ForgetDevice(bdAddr);
} }
static int64_t Sys_BtList(BtDevInfo* buf, int maxCount) { static int64_t Sys_BtList(BtDevInfo* buf, int maxCount) {
if (!buf || maxCount <= 0) return 0; if (!buf || maxCount <= 0) return 0;
if (!Drivers::USB::Bluetooth::IsInitialized()) return 0; if (!Drivers::Bluetooth::IsInitialized()) return 0;
Drivers::USB::Bluetooth::Hci::ConnectionInfo tmpBuf[4]; Drivers::Bluetooth::Hci::ConnectionInfo tmpBuf[4];
int kMax = maxCount; int kMax = maxCount;
if (kMax > 4) kMax = 4; if (kMax > 4) kMax = 4;
int count = Drivers::USB::Bluetooth::ListConnected(tmpBuf, kMax); int count = Drivers::Bluetooth::ListConnected(tmpBuf, kMax);
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {
memcpy(buf[i].bdAddr, tmpBuf[i].BdAddr, 6); memcpy(buf[i].bdAddr, tmpBuf[i].BdAddr, 6);
@@ -123,14 +123,14 @@ namespace montauk::abi {
memset(buf, 0, sizeof(BtAdapterInfo)); memset(buf, 0, sizeof(BtAdapterInfo));
if (!Drivers::USB::Bluetooth::IsInitialized()) { if (!Drivers::Bluetooth::IsInitialized()) {
buf->initialized = 0; buf->initialized = 0;
return NoDevice; return NoDevice;
} }
buf->initialized = 1; buf->initialized = 1;
memcpy(buf->bdAddr, Drivers::USB::Bluetooth::GetBdAddr(), 6); memcpy(buf->bdAddr, Drivers::Bluetooth::GetBdAddr(), 6);
buf->scanning = Drivers::USB::Bluetooth::Hci::IsInquiryActive() ? 1 : 0; buf->scanning = Drivers::Bluetooth::Hci::IsInquiryActive() ? 1 : 0;
// Copy adapter name // Copy adapter name
const char* name = "MontaukOS"; const char* name = "MontaukOS";
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 73 #define MONTAUK_BUILD_NUMBER 80
+8 -2
View File
@@ -8,7 +8,7 @@
#include <cstdint> #include <cstdint>
#include <Drivers/USB/Xhci.hpp> #include <Drivers/USB/Xhci.hpp>
#include <Drivers/USB/UsbDevice.hpp> #include <Drivers/USB/UsbDevice.hpp>
#include <Drivers/USB/Bluetooth/Bluetooth.hpp> #include <Drivers/Bluetooth/Bluetooth.hpp>
#include <Drivers/Net/E1000.hpp> #include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp> #include <Drivers/Net/E1000E.hpp>
#include <Drivers/Net/Wifi/Wifi.hpp> #include <Drivers/Net/Wifi/Wifi.hpp>
@@ -127,6 +127,12 @@ namespace montauk::abi {
? "802.11 wireless (connected)" ? "802.11 wireless (connected)"
: "802.11 wireless"); : "802.11 wireless");
} }
// An on-board UART Bluetooth controller (Raspberry Pi) is no USB
// device, so it is listed here; USB adapters appear above.
if (Drivers::Bluetooth::IsInitialized() &&
Drivers::Bluetooth::Hci::GetTransport() == Drivers::Bluetooth::Hci::Transport::Uart) {
add(5, "Bluetooth Adapter", "Broadcom, HCI over UART");
}
// Display (category 6) // Display (category 6)
if (Drivers::Graphics::IntelGPU::IsInitialized()) { if (Drivers::Graphics::IntelGPU::IsInitialized()) {
@@ -148,7 +154,7 @@ namespace montauk::abi {
add(9, "Intel HDA", detail); add(9, "Intel HDA", detail);
} }
if (Drivers::USB::Bluetooth::IsInitialized()) { if (Drivers::Bluetooth::IsInitialized()) {
add(9, "Bluetooth Audio", "A2DP (SBC)"); add(9, "Bluetooth Audio", "A2DP (SBC)");
} }
+20 -20
View File
@@ -7,9 +7,9 @@
#include "Mixer.hpp" #include "Mixer.hpp"
#include "IntelHda.hpp" #include "IntelHda.hpp"
#include <Drivers/USB/Bluetooth/Bluetooth.hpp> #include <Drivers/Bluetooth/Bluetooth.hpp>
#include <Drivers/USB/Bluetooth/A2dp.hpp> #include <Drivers/Bluetooth/A2dp.hpp>
#include <Drivers/USB/Bluetooth/Avrcp.hpp> #include <Drivers/Bluetooth/Avrcp.hpp>
#include <Memory/PageFrameAllocator.hpp> #include <Memory/PageFrameAllocator.hpp>
#include <Memory/HHDM.hpp> #include <Memory/HHDM.hpp>
#include <Sched/Scheduler.hpp> #include <Sched/Scheduler.hpp>
@@ -146,8 +146,8 @@ namespace Drivers::Audio::Mixer {
} }
static bool BluetoothReady() { static bool BluetoothReady() {
if (!Drivers::USB::Bluetooth::IsInitialized()) return false; if (!Drivers::Bluetooth::IsInitialized()) return false;
return Drivers::USB::Bluetooth::A2dp::IsReady(); return Drivers::Bluetooth::A2dp::IsReady();
} }
static void SyncHdaMasterMute() { static void SyncHdaMasterMute() {
@@ -236,7 +236,7 @@ namespace Drivers::Audio::Mixer {
static uint32_t BackendWriteSpace() { static uint32_t BackendWriteSpace() {
if (g_output == Output::Bluetooth) { if (g_output == Output::Bluetooth) {
return g_btOpened return g_btOpened
? Drivers::USB::Bluetooth::A2dp::GetWriteSpace() : 0; ? Drivers::Bluetooth::A2dp::GetWriteSpace() : 0;
} }
return g_hdaOpened ? IntelHda::GetWriteSpace(g_hdaHandle) : 0; return g_hdaOpened ? IntelHda::GetWriteSpace(g_hdaHandle) : 0;
} }
@@ -244,7 +244,7 @@ namespace Drivers::Audio::Mixer {
static int BackendWrite(const uint8_t* data, uint32_t size) { static int BackendWrite(const uint8_t* data, uint32_t size) {
if (g_output == Output::Bluetooth) { if (g_output == Output::Bluetooth) {
return g_btOpened return g_btOpened
? Drivers::USB::Bluetooth::A2dp::WriteAudio(data, size) : -1; ? Drivers::Bluetooth::A2dp::WriteAudio(data, size) : -1;
} }
return g_hdaOpened ? IntelHda::Write(g_hdaHandle, data, size) : -1; return g_hdaOpened ? IntelHda::Write(g_hdaHandle, data, size) : -1;
} }
@@ -403,14 +403,14 @@ namespace Drivers::Audio::Mixer {
if (g_output == Output::Bluetooth && !g_btOpened && BluetoothReady()) { if (g_output == Output::Bluetooth && !g_btOpened && BluetoothReady()) {
g_switchingOutput = true; g_switchingOutput = true;
g_lock.Release(); g_lock.Release();
bool btOk = Drivers::USB::Bluetooth::A2dp::ConfigureStream( bool btOk = Drivers::Bluetooth::A2dp::ConfigureStream(
MIX_RATE, MIX_CHANNELS, MIX_BITS) && MIX_RATE, MIX_CHANNELS, MIX_BITS) &&
Drivers::USB::Bluetooth::A2dp::StartStream(); Drivers::Bluetooth::A2dp::StartStream();
g_lock.Acquire(); g_lock.Acquire();
g_btOpened = btOk; g_btOpened = btOk;
if (!btOk) g_output = Output::Hda; if (!btOk) g_output = Output::Hda;
if (btOk) if (btOk)
Drivers::USB::Bluetooth::A2dp::SetMuted( Drivers::Bluetooth::A2dp::SetMuted(
g_masterMute.load(std::memory_order_acquire)); g_masterMute.load(std::memory_order_acquire));
g_switchingOutput = false; g_switchingOutput = false;
wakeSwitchWaiters = true; wakeSwitchWaiters = true;
@@ -515,7 +515,7 @@ namespace Drivers::Audio::Mixer {
if (closeBt) g_btOpened = false; if (closeBt) g_btOpened = false;
g_lock.Release(); g_lock.Release();
if (closeHda) IntelHda::Close(hdaHandle); if (closeHda) IntelHda::Close(hdaHandle);
if (closeBt) Drivers::USB::Bluetooth::A2dp::StopStream(true); if (closeBt) Drivers::Bluetooth::A2dp::StopStream(true);
if (ringToFree) FreeRing(ringToFree); if (ringToFree) FreeRing(ringToFree);
if (changed) Sched::WakeObjectWaiters((void*)&g_serial); if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
} }
@@ -535,7 +535,7 @@ namespace Drivers::Audio::Mixer {
bool serviceBluetooth = g_output == Output::Bluetooth; bool serviceBluetooth = g_output == Output::Bluetooth;
g_lock.Release(); g_lock.Release();
if (serviceBluetooth) if (serviceBluetooth)
Drivers::USB::Bluetooth::A2dp::ServiceMedia(); Drivers::Bluetooth::A2dp::ServiceMedia();
return (int)written; return (int)written;
} }
@@ -651,7 +651,7 @@ namespace Drivers::Audio::Mixer {
if (changed) BumpSerialLocked(); if (changed) BumpSerialLocked();
g_lock.Release(); g_lock.Release();
if (closeHda) IntelHda::Close(hdaHandle); if (closeHda) IntelHda::Close(hdaHandle);
if (closeBt) Drivers::USB::Bluetooth::A2dp::StopStream(true); if (closeBt) Drivers::Bluetooth::A2dp::StopStream(true);
for (int i = 0; i < ringCount; i++) FreeRing(ringsToFree[i]); for (int i = 0; i < ringCount; i++) FreeRing(ringsToFree[i]);
if (changed) Sched::WakeObjectWaiters((void*)&g_serial); if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
} }
@@ -668,7 +668,7 @@ namespace Drivers::Audio::Mixer {
g_lock.Release(); g_lock.Release();
if (changed) if (changed)
Drivers::USB::Bluetooth::Avrcp::NotifyVolumeChanged(percent); Drivers::Bluetooth::Avrcp::NotifyVolumeChanged(percent);
if (changed) Sched::WakeObjectWaiters((void*)&g_serial); if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
} }
@@ -688,7 +688,7 @@ namespace Drivers::Audio::Mixer {
if (changed) SyncHdaMasterMute(); if (changed) SyncHdaMasterMute();
if (changed) if (changed)
Drivers::USB::Bluetooth::A2dp::SetMuted(muted); Drivers::Bluetooth::A2dp::SetMuted(muted);
if (changed) Sched::WakeObjectWaiters((void*)&g_serial); if (changed) Sched::WakeObjectWaiters((void*)&g_serial);
} }
@@ -733,11 +733,11 @@ namespace Drivers::Audio::Mixer {
} }
} else if (needBackend && output == Output::Bluetooth) { } else if (needBackend && output == Output::Bluetooth) {
prepared = BluetoothReady() && prepared = BluetoothReady() &&
Drivers::USB::Bluetooth::A2dp::ConfigureStream( Drivers::Bluetooth::A2dp::ConfigureStream(
MIX_RATE, MIX_CHANNELS, MIX_BITS) && MIX_RATE, MIX_CHANNELS, MIX_BITS) &&
Drivers::USB::Bluetooth::A2dp::StartStream(); Drivers::Bluetooth::A2dp::StartStream();
if (prepared) if (prepared)
Drivers::USB::Bluetooth::A2dp::SetMuted( Drivers::Bluetooth::A2dp::SetMuted(
g_masterMute.load(std::memory_order_acquire)); g_masterMute.load(std::memory_order_acquire));
} else if (output == Output::Hda) { } else if (output == Output::Hda) {
prepared = IntelHda::IsInitialized(); prepared = IntelHda::IsInitialized();
@@ -779,7 +779,7 @@ namespace Drivers::Audio::Mixer {
Sched::WakeObjectWaiters((void*)&g_switchingOutput); Sched::WakeObjectWaiters((void*)&g_switchingOutput);
if (closeHda) IntelHda::Close(oldHdaHandle); if (closeHda) IntelHda::Close(oldHdaHandle);
if (closeBt) Drivers::USB::Bluetooth::A2dp::StopStream(true); if (closeBt) Drivers::Bluetooth::A2dp::StopStream(true);
Sched::WakeObjectWaiters((void*)&g_serial); Sched::WakeObjectWaiters((void*)&g_serial);
return 0; return 0;
} }
@@ -815,7 +815,7 @@ namespace Drivers::Audio::Mixer {
wake = true; wake = true;
} }
g_lock.Release(); g_lock.Release();
if (stopBt) Drivers::USB::Bluetooth::A2dp::StopStream(true); if (stopBt) Drivers::Bluetooth::A2dp::StopStream(true);
if (wake) Sched::WakeObjectWaiters((void*)&g_serial); if (wake) Sched::WakeObjectWaiters((void*)&g_serial);
} }
@@ -9,7 +9,6 @@
#include "Sbc.hpp" #include "Sbc.hpp"
#include "L2cap.hpp" #include "L2cap.hpp"
#include "Hci.hpp" #include "Hci.hpp"
#include <Drivers/USB/Xhci.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
@@ -18,7 +17,7 @@
using namespace Kt; using namespace Kt;
namespace Drivers::USB::Bluetooth::A2dp { namespace Drivers::Bluetooth::A2dp {
// ========================================================================= // =========================================================================
// AVDTP constants // AVDTP constants
@@ -222,7 +221,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) { while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); // AVDTP responses arrive as ACL data Hci::DrainEvents(); // AVDTP responses arrive as ACL data
if (g_avdtpResponseReady.load(std::memory_order_acquire)) { if (g_avdtpResponseReady.load(std::memory_order_acquire)) {
// Freeze this mailbox before returning to the caller. A late // Freeze this mailbox before returning to the caller. A late
@@ -557,7 +556,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
void OnChannelReady(uint16_t l2capCid) { void OnChannelReady(uint16_t l2capCid) {
// Invoked from L2CAP inside the ACL receive path -- i.e. NESTED under // Invoked from L2CAP inside the ACL receive path -- i.e. NESTED under
// Xhci::PollEvents. PollEvents is non-reentrant (re-entry guard), so we // Hci::DrainEvents. That is non-reentrant (re-entry guard), so we
// must NOT run the blocking AVDTP signaling chain here; a nested poll // must NOT run the blocking AVDTP signaling chain here; a nested poll
// would stall and every WaitAvdtpResponse() would time out. Just record // would stall and every WaitAvdtpResponse() would time out. Just record
// the channel; StartSource() drives the handshakes from top-level // the channel; StartSource() drives the handshakes from top-level
@@ -1161,7 +1160,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
bool answered = false; bool answered = false;
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) { while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
if (g_sdpRspReady.load(std::memory_order_acquire)) { if (g_sdpRspReady.load(std::memory_order_acquire)) {
answered = true; answered = true;
@@ -1196,7 +1195,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
static bool WaitEncrypted(uint32_t timeoutMs) { static bool WaitEncrypted(uint32_t timeoutMs) {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) { while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
Hci::ProcessPendingCommands(); Hci::ProcessPendingCommands();
auto* c = Hci::GetConnection(g_aclHandle.load(std::memory_order_acquire)); auto* c = Hci::GetConnection(g_aclHandle.load(std::memory_order_acquire));
@@ -1235,7 +1234,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
uint64_t waitStart = Timekeeping::GetMilliseconds(); uint64_t waitStart = Timekeeping::GetMilliseconds();
while (g_sourceSetupActive.load(std::memory_order_acquire) while (g_sourceSetupActive.load(std::memory_order_acquire)
&& Timekeeping::GetMilliseconds() - waitStart < timeoutMs) { && Timekeeping::GetMilliseconds() - waitStart < timeoutMs) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
for (int j = 0; j < 100; j++) Arch::Pause(); for (int j = 0; j < 100; j++) Arch::Pause();
} }
@@ -1265,7 +1264,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
if (g_sigCid.load(std::memory_order_acquire) == 0) { if (g_sigCid.load(std::memory_order_acquire) == 0) {
uint64_t lStart = Timekeeping::GetMilliseconds(); uint64_t lStart = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - lStart < 1000) { while (Timekeeping::GetMilliseconds() - lStart < 1000) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
Hci::ProcessPendingCommands(); Hci::ProcessPendingCommands();
if (g_sigCid.load(std::memory_order_acquire) != 0) break; if (g_sigCid.load(std::memory_order_acquire) != 0) break;
@@ -1319,7 +1318,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
uint32_t attemptWaitMs = timeoutMs < 1500 ? timeoutMs : 1500; uint32_t attemptWaitMs = timeoutMs < 1500 ? timeoutMs : 1500;
uint32_t waitBudgetMs = attemptWaitMs; uint32_t waitBudgetMs = attemptWaitMs;
while (Timekeeping::GetMilliseconds() - sigStart < waitBudgetMs) { while (Timekeeping::GetMilliseconds() - sigStart < waitBudgetMs) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
if (g_sigCid.load(std::memory_order_acquire) != 0) break; if (g_sigCid.load(std::memory_order_acquire) != 0) break;
// PENDING is explicitly non-final. A peer performing service // PENDING is explicitly non-final. A peer performing service
@@ -1363,7 +1362,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
if (sig) L2cap::FreeChannel(sig); if (sig) L2cap::FreeChannel(sig);
uint64_t st = Timekeeping::GetMilliseconds(); uint64_t st = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - st < 400) { while (Timekeeping::GetMilliseconds() - st < 400) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
for (int j = 0; j < 200; j++) Arch::Pause(); for (int j = 0; j < 200; j++) Arch::Pause();
} }
@@ -1384,7 +1383,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
if (g_peerConfigured.load(std::memory_order_acquire)) { if (g_peerConfigured.load(std::memory_order_acquire)) {
uint64_t peerStart = Timekeeping::GetMilliseconds(); uint64_t peerStart = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - peerStart < timeoutMs) { while (Timekeeping::GetMilliseconds() - peerStart < timeoutMs) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
State s = g_state.load(std::memory_order_acquire); State s = g_state.load(std::memory_order_acquire);
if (s == State::Open || s == State::Streaming) break; if (s == State::Open || s == State::Streaming) break;
@@ -1483,7 +1482,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
uint64_t mStart = Timekeeping::GetMilliseconds(); uint64_t mStart = Timekeeping::GetMilliseconds();
while (g_mediaCid.load(std::memory_order_acquire) == 0 while (g_mediaCid.load(std::memory_order_acquire) == 0
&& Timekeeping::GetMilliseconds() - mStart < kMediaWaitMs) { && Timekeeping::GetMilliseconds() - mStart < kMediaWaitMs) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
auto* ch = media ? L2cap::GetChannel(media) : nullptr; auto* ch = media ? L2cap::GetChannel(media) : nullptr;
if (ch && ch->Configured) { if (ch && ch->Configured) {
@@ -2136,7 +2135,7 @@ namespace Drivers::USB::Bluetooth::A2dp {
void ServiceMedia() { void ServiceMedia() {
if (!AcquireMediaService()) return; if (!AcquireMediaService()) return;
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
PumpMedia(); PumpMedia();
g_serviceActive.store(false, std::memory_order_release); g_serviceActive.store(false, std::memory_order_release);
@@ -8,7 +8,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
namespace Drivers::USB::Bluetooth::A2dp { namespace Drivers::Bluetooth::A2dp {
// ========================================================================= // =========================================================================
// A2DP stream states // A2DP stream states
@@ -15,7 +15,7 @@
using namespace Kt; using namespace Kt;
namespace Drivers::USB::Bluetooth::Avrcp { namespace Drivers::Bluetooth::Avrcp {
// ========================================================================= // =========================================================================
// Constants // Constants
@@ -7,7 +7,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
namespace Drivers::USB::Bluetooth::Avrcp { namespace Drivers::Bluetooth::Avrcp {
// Process an AVCTP packet arriving on an AVRCP control channel (PSM 0x0017). // Process an AVCTP packet arriving on an AVRCP control channel (PSM 0x0017).
// Implements a minimal AVRCP Target: unit/subunit info, honest rejection of // Implements a minimal AVRCP Target: unit/subunit info, honest rejection of
@@ -1,6 +1,6 @@
/* /*
* Bluetooth.cpp * Bluetooth.cpp
* Top-level Bluetooth subsystem — adapter registration and Intel BT initialization * Top-level Bluetooth subsystem — adapter registration and controller bring-up
* Copyright (c) 2026 Daniel Hammer * Copyright (c) 2026 Daniel Hammer
*/ */
@@ -9,6 +9,7 @@
#include "Hci.hpp" #include "Hci.hpp"
#include "A2dp.hpp" #include "A2dp.hpp"
#include "IntelFirmware.hpp" #include "IntelFirmware.hpp"
#include "Broadcom.hpp"
#include <Drivers/Audio/Mixer.hpp> #include <Drivers/Audio/Mixer.hpp>
#include <Drivers/USB/Xhci.hpp> #include <Drivers/USB/Xhci.hpp>
#include <Drivers/USB/UsbDevice.hpp> #include <Drivers/USB/UsbDevice.hpp>
@@ -22,7 +23,9 @@
using namespace Kt; using namespace Kt;
namespace Drivers::USB::Bluetooth { namespace Drivers::Bluetooth {
namespace Xhci = Drivers::USB::Xhci;
// ========================================================================= // =========================================================================
// State // State
@@ -205,7 +208,9 @@ namespace Drivers::USB::Bluetooth {
// ========================================================================= // =========================================================================
void RegisterAdapter(uint8_t slotId) { void RegisterAdapter(uint8_t slotId) {
if (g_initialized) { // One adapter at a time, whichever came first (an on-board UART
// controller may still be finishing its deferred init).
if (g_initialized || Hci::GetTransport() != Hci::Transport::None) {
KernelLogStream(WARNING, "BT") << "Bluetooth adapter already registered"; KernelLogStream(WARNING, "BT") << "Bluetooth adapter already registered";
return; return;
} }
@@ -249,17 +254,47 @@ namespace Drivers::USB::Bluetooth {
CompleteInit(); CompleteInit();
} }
// =========================================================================
// ProbePlatformAdapter — an on-board UART controller ACPI describes
// =========================================================================
bool ProbePlatformAdapter() {
if (g_initialized || Hci::GetTransport() != Hci::Transport::None) return false;
if (!Broadcom::ProbePlatform()) return false;
// The firmware patch lives on the root filesystem, which the boot
// driver probe precedes; finish from the idle loop like USB adapters.
if (!Fs::Vfs::IsDriveRegistered(0)) {
g_initPending.store(true, std::memory_order_release);
KernelLogStream(INFO, "BT") << "Transport up; deferring init until the root is mounted";
return true;
}
CompleteInit();
return true;
}
// ========================================================================= // =========================================================================
// CompleteInit — firmware-dependent HCI bring-up (needs VFS/ramdisk) // CompleteInit — firmware-dependent HCI bring-up (needs VFS/ramdisk)
// ========================================================================= // =========================================================================
static void CompleteInit() { static void CompleteInit() {
auto* dev = Xhci::GetDevice(g_slotId); bool didReset = false;
if (!dev) return;
if (Hci::GetTransport() == Hci::Transport::Uart) {
// Power cycle, firmware patch and operating baud; ends with a reset.
if (!Broadcom::Initialize()) {
KernelLogStream(ERROR, "BT") << "Broadcom BT initialization failed";
return;
}
didReset = true;
}
auto* dev = Hci::GetTransport() == Hci::Transport::Usb ? Xhci::GetDevice(g_slotId) : nullptr;
if (Hci::GetTransport() == Hci::Transport::Usb && !dev) return;
// Intel-specific initialization (firmware download + HCI Reset) // Intel-specific initialization (firmware download + HCI Reset)
bool didReset = false; if (dev && IsIntelBt(dev->VendorId, dev->ProductId)) {
if (IsIntelBt(dev->VendorId, dev->ProductId)) { Hci::SetVendor(Hci::Vendor::Intel);
if (InitIntelBluetooth()) { if (InitIntelBluetooth()) {
didReset = true; // InitIntelBluetooth already sent HCI Reset didReset = true; // InitIntelBluetooth already sent HCI Reset
} else { } else {
@@ -280,9 +315,11 @@ namespace Drivers::USB::Bluetooth {
} }
} }
// Apply a persisted BD_ADDR override (0:/config/bluetooth.toml) now, // A Broadcom part still on its chip-wide default address gets the
// after the last reset and before the address is read back, so the // board's, then a persisted BD_ADDR override (0:/config/bluetooth.toml)
// whole bring-up below uses the overridden address. // wins over both. Both after the last reset and before the address
// is read back, so the whole bring-up below uses the final address.
if (Hci::GetVendor() == Hci::Vendor::Broadcom) Broadcom::FixDefaultAddress();
ApplyConfiguredAddress(); ApplyConfiguredAddress();
// Read BD_ADDR // Read BD_ADDR
@@ -387,7 +424,7 @@ namespace Drivers::USB::Bluetooth {
void ServiceDeferredInit() { void ServiceDeferredInit() {
if (!g_initPending.load(std::memory_order_relaxed) || g_initialized) return; if (!g_initPending.load(std::memory_order_relaxed) || g_initialized) return;
if (!Fs::Vfs::IsDriveRegistered(0)) return; // ramdisk still not mounted if (!Fs::Vfs::IsDriveRegistered(0)) return; // ramdisk still not mounted
if (Xhci::InPollContext()) return; // never nest under PollEvents if (Hci::InPumpContext()) return; // never nest under the transport pump
// Claim the pending init (this runs from the idle loop; make sure only // Claim the pending init (this runs from the idle loop; make sure only
// one pass performs the bring-up). The firmware download inside takes // one pass performs the bring-up). The firmware download inside takes
@@ -425,12 +462,12 @@ namespace Drivers::USB::Bluetooth {
!g_initialized && Fs::Vfs::IsDriveRegistered(0)) { !g_initialized && Fs::Vfs::IsDriveRegistered(0)) {
return true; return true;
} }
return g_initialized && Hci::HasPendingCommands(); return g_initialized && (Hci::HasPendingCommands() || Hci::TransportHasRxData());
} }
void ServiceEvents() { void ServiceEvents() {
if (!g_initialized) return; if (!g_initialized) return;
if (Xhci::InPollContext()) return; // never nest under PollEvents if (Hci::InPumpContext()) return; // never nest under the transport pump
A2dp::ServiceMedia(); // reap events and feed queued media A2dp::ServiceMedia(); // reap events and feed queued media
// DrainEvents may just have queued an accept/auth/encryption reply. // DrainEvents may just have queued an accept/auth/encryption reply.
// Send it in this same service pass instead of adding a scheduler-turn // Send it in this same service pass instead of adding a scheduler-turn
@@ -492,7 +529,7 @@ namespace Drivers::USB::Bluetooth {
uint64_t t0 = Timekeeping::GetMilliseconds(); uint64_t t0 = Timekeeping::GetMilliseconds();
bool anyActive = true; bool anyActive = true;
while (anyActive && Timekeeping::GetMilliseconds() - t0 < 2000) { while (anyActive && Timekeeping::GetMilliseconds() - t0 < 2000) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
anyActive = false; anyActive = false;
for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) { for (int i = 0; i < Hci::MAX_CONNECTIONS; i++) {
@@ -548,7 +585,7 @@ namespace Drivers::USB::Bluetooth {
// Poll until inquiry completes or timeout // Poll until inquiry completes or timeout
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Hci::IsInquiryActive() && (Timekeeping::GetMilliseconds() - start < timeoutMs)) { while (Hci::IsInquiryActive() && (Timekeeping::GetMilliseconds() - start < timeoutMs)) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
for (int j = 0; j < 200; j++) { for (int j = 0; j < 200; j++) {
@@ -566,7 +603,7 @@ namespace Drivers::USB::Bluetooth {
uint64_t cancelStart = Timekeeping::GetMilliseconds(); uint64_t cancelStart = Timekeeping::GetMilliseconds();
while (Hci::IsInquiryActive() while (Hci::IsInquiryActive()
&& Timekeeping::GetMilliseconds() - cancelStart < 250) { && Timekeeping::GetMilliseconds() - cancelStart < 250) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
for (int j = 0; j < 100; j++) for (int j = 0; j < 100; j++)
Arch::Pause(); Arch::Pause();
@@ -612,7 +649,7 @@ namespace Drivers::USB::Bluetooth {
// Wait for Connection Complete event // Wait for Connection Complete event
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (!target && Timekeeping::GetMilliseconds() - start < timeoutMs) { while (!target && Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
target = FindAclConnection(bdAddr); target = FindAclConnection(bdAddr);
for (int j = 0; j < 200; j++) { for (int j = 0; j < 200; j++) {
@@ -631,7 +668,7 @@ namespace Drivers::USB::Bluetooth {
if (reusedAcl) Hci::AuthenticateLink(target->Handle); if (reusedAcl) Hci::AuthenticateLink(target->Handle);
uint64_t t0 = Timekeeping::GetMilliseconds(); uint64_t t0 = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - t0 < 5000) { while (Timekeeping::GetMilliseconds() - t0 < 5000) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); Hci::DrainEvents();
Hci::ProcessPendingCommands(); Hci::ProcessPendingCommands();
if (!target->Active || !SameAddress(target->BdAddr, bdAddr)) break; if (!target->Active || !SameAddress(target->BdAddr, bdAddr)) break;
@@ -8,28 +8,35 @@
#include <cstdint> #include <cstdint>
#include "Hci.hpp" #include "Hci.hpp"
namespace Drivers::USB::Bluetooth { namespace Drivers::Bluetooth {
// Called by USB enumeration when a Bluetooth adapter is detected // Called by USB enumeration when a Bluetooth adapter is detected
void RegisterAdapter(uint8_t slotId); void RegisterAdapter(uint8_t slotId);
// Called by the boot driver probe: bring up an on-board UART controller
// ACPI describes (the Raspberry Pi 4/400's Broadcom radio). Returns true
// if one was found; its firmware-dependent init is deferred like USB's.
bool ProbePlatformAdapter();
// Complete any firmware-dependent bring-up that was deferred because the // Complete any firmware-dependent bring-up that was deferred because the
// adapter enumerated during the boot port scan, before the ramdisk (which // adapter enumerated during the boot port scan, before the ramdisk (which
// holds the firmware images) was mounted. Safe to call unconditionally // holds the firmware images) was mounted. Safe to call unconditionally
// once the boot filesystems are up; a no-op if nothing is pending. // once the boot filesystems are up; a no-op if nothing is pending.
void ServiceDeferredInit(); void ServiceDeferredInit();
// Steady-state event service: drains the xHCI event ring + Bluetooth ACL // Steady-state event service: drains the transport (xHCI event ring or
// RX ring and flushes queued HCI replies. Called from the idle loop so // UART receive ring) + Bluetooth ACL RX ring and flushes queued HCI
// replies. Called from the idle loop so
// inbound traffic (the headset's SDP/AVRCP queries, AVDTP commands, ACL // inbound traffic (the headset's SDP/AVRCP queries, AVDTP commands, ACL
// credits) is processed even when no syscall happens to be pumping a BT // credits) is processed even when no syscall happens to be pumping a BT
// wait loop. Cheap no-op when the adapter is down; safe from any core // wait loop. Cheap no-op when the adapter is down; safe from any core
// (PollEvents/DrainEvents/ProcessPendingCommands all self-serialize). // (Pump/DrainEvents/ProcessPendingCommands all self-serialize).
void ServiceEvents(); void ServiceEvents();
// True when boot-deferred initialization or queued HCI control work needs // True when boot-deferred initialization, queued HCI control work or
// an idle-context service pass. USB receive events are signaled separately // received UART bytes need an idle-context service pass. USB receive
// by xHCI and cause the dispatcher to service Bluetooth in the same pass. // events are signaled separately by xHCI and cause the dispatcher to
// service Bluetooth in the same pass.
bool HasDeferredWork(); bool HasDeferredWork();
// Query adapter state // Query adapter state
@@ -40,8 +47,8 @@ namespace Drivers::USB::Bluetooth {
const uint8_t* GetBdAddr(); const uint8_t* GetBdAddr();
// Change the adapter's BD_ADDR at runtime. Drops any active links (their // Change the adapter's BD_ADDR at runtime. Drops any active links (their
// pairing/crypto was bound to the old address), issues the Intel vendor // pairing/crypto was bound to the old address), issues the vendor write
// write (0xFC31), re-reads the controller's address into the cache and // (Intel 0xFC31, Broadcom 0xFC01), re-reads the controller's address into the cache and
// re-asserts scan. Returns true only if the controller reports the new // re-asserts scan. Returns true only if the controller reports the new
// address. Persisting the choice to 0:/config/bluetooth.toml is the // address. Persisting the choice to 0:/config/bluetooth.toml is the
// caller's job (userspace); the override is re-applied on boot by // caller's job (userspace); the override is re-applied on boot by
+493
View File
@@ -0,0 +1,493 @@
/*
* Broadcom.cpp
* Broadcom/Cypress UART Bluetooth controller bring-up (Raspberry Pi 4/400)
* Copyright (c) 2026 Daniel Hammer
*/
#include "Broadcom.hpp"
#include "Hci.hpp"
#include <ACPI/AcpiDevices.hpp>
#include <ACPI/AML/AmlInterpreter.hpp>
#include <Arch/Cpu.hpp>
#include <CppLib/Stream.hpp>
#include <Drivers/Platform/RpiFirmware.hpp>
#include <Drivers/Platform/RpiGpio.hpp>
#include <Drivers/Serial/BtUart.hpp>
#include <Drivers/Serial/Uart.hpp>
#include <Fs/Vfs.hpp>
#include <Libraries/Memory.hpp>
#include <Memory/Heap.hpp>
#include <Terminal/Terminal.hpp>
#include <Timekeeping/Timer.hpp>
// The Raspberry Pi 4B (CYW43455) and 400 (CYW43456) carry a Cypress radio
// whose Bluetooth half speaks HCI over a UART (H4) on GPIO 30-33: CTS, RTS,
// TXD, RXD. Its power switch, BT_ON, is line 0 of the VideoCore firmware's
// GPIO expander.
//
// Bring-up follows Linux's hci_bcm + btbcm:
// 1. power on, wait ~100 ms, HCI Reset at the controller's 115200 baud;
// 2. Read Local Version: the LMP subversion names the chip and so the
// patch file (BCM4345C0.hcd, BCM4345C5.hcd);
// 3. raise both ends to the operating speed (vendor 0xFC18, answered at
// the old speed);
// 4. Download Minidriver (0xFC2E), wait 50 ms, then send every record of
// the .hcd file -- each one is a complete HCI command, Write RAM
// (0xFC4C) and finally Launch RAM (0xFC4E) -- and wait 250 ms;
// 5. the patched firmware restarts at 115200: HCI Reset there, and raise
// the speed again.
//
// Raspberry Pi OS's btuart runs the controller at 460800 without flow
// control on the mini UART and at 3 Mbaud with it on the PL011. The mini
// UART's FIFO is 8 bytes deep, so here its hardware RTS is used whenever
// the controller is still reachable with it on, which allows 921600.
using namespace Kt;
namespace Drivers::Bluetooth::Broadcom {
namespace BtUart = Drivers::Serial::BtUart;
namespace RpiFirmware = Drivers::Platform::RpiFirmware;
namespace RpiGpio = Drivers::Platform::RpiGpio;
// GPIO 30-33 carry CTS, RTS, TXD, RXD: alternate function 3 routes them
// to the PL011, 5 to the mini UART. Pulls as Linux's bcm2711 pinctrl
// sets them (uart0_ctsrts_gpio30, uart0_gpio32).
static constexpr uint32_t PIN_CTS = 30;
static constexpr uint32_t PIN_RTS = 31;
static constexpr uint32_t PIN_TXD = 32;
static constexpr uint32_t PIN_RXD = 33;
static constexpr uint32_t InitialBaud = 115200;
// LMP subversions of the ROM firmware -> patch file (Linux btbcm's
// bcm_uart_subver_table).
struct ChipName {
uint16_t subversion;
const char* name;
};
static constexpr ChipName g_chips[] = {
{ 0x6119, "BCM4345C0" }, // CYW43455: Raspberry Pi 3B+, 4B
{ 0x6606, "BCM4345C5" }, // CYW43456: Raspberry Pi 400, CM4
};
static bool g_flowControl = false;
static bool g_powerSwitch = true;
// =========================================================================
// Platform probe
// =========================================================================
static bool EndsWith(const char* s, const char* suffix) {
int sl = 0, xl = 0;
while (s[sl]) sl++;
while (suffix[xl]) xl++;
if (xl > sl) return false;
for (int i = 0; i < xl; i++) {
if (s[sl - xl + i] != suffix[i]) return false;
}
return true;
}
// The controller's UartSerialBusV2 descriptor (ACPI 6.4, 6.4.3.8.2.3)
// names the UART it hangs off in its ResourceSource string.
static bool ReadUartSource(int32_t node, char* source, int maxLength) {
Hal::AML::Object crs;
if (!Hal::AML::GetInterpreter().EvaluateChild(node, "_CRS", nullptr, 0, crs) ||
crs.Type != Hal::AML::ObjectType::Buffer) {
return false;
}
const uint8_t* data = crs.Buffer.Data;
uint32_t length = crs.Buffer.Length;
uint32_t pos = 0;
while (pos < length) {
uint8_t tag = data[pos];
if (!(tag & 0x80)) {
if ((tag & 0x78) == 0x78) break; // end tag
pos += 1 + (tag & 0x07);
continue;
}
if (pos + 3 > length) break;
uint32_t descLength = (uint32_t)data[pos + 1] | ((uint32_t)data[pos + 2] << 8);
uint32_t end = pos + 3 + descLength;
if (end > length) break;
static constexpr uint8_t SerialBusTag = 0x8E, SerialBusUart = 3;
if (tag == SerialBusTag && descLength >= 9 && data[pos + 5] == SerialBusUart) {
uint32_t typeDataLength = (uint32_t)data[pos + 10] | ((uint32_t)data[pos + 11] << 8);
uint32_t s = pos + 12 + typeDataLength;
int n = 0;
while (s < end && data[s] != 0 && n < maxLength - 1) source[n++] = (char)data[s++];
source[n] = '\0';
return n > 0;
}
pos = end;
}
return false;
}
static bool ReadInteger(int32_t node, const char* name, uint64_t& value) {
Hal::AML::Object obj;
if (!Hal::AML::GetInterpreter().EvaluateChild(node, name, nullptr, 0, obj) ||
obj.Type != Hal::AML::ObjectType::Integer) {
return false;
}
value = obj.Integer;
return true;
}
bool ProbePlatform() {
Hal::AcpiDevices::DeviceInfo bt{};
if (!Hal::AcpiDevices::FindDeviceByHid("BCM2EA6", bt)) return false;
// Which UART: the descriptor's source, else the firmware's "UART in
// use" flag (0: the PL011 is the console, Bluetooth has the mini UART).
BtUart::Kind kind = BtUart::Kind::MiniUart;
char source[64] = {};
uint64_t uartInUse = 0;
if (ReadUartSource(bt.NodeIndex, source, sizeof(source))) {
kind = EndsWith(source, "URT0") ? BtUart::Kind::Pl011 : BtUart::Kind::MiniUart;
} else if (ReadInteger(bt.NodeIndex, "URIU", uartInUse)) {
kind = uartInUse == 0 ? BtUart::Kind::MiniUart : BtUart::Kind::Pl011;
}
bool mini = kind == BtUart::Kind::MiniUart;
Hal::AcpiDevices::DeviceInfo uart{};
if (!Hal::AcpiDevices::FindDeviceByHid(mini ? "BCM2836" : "BCM2837", uart)) {
KernelLogStream(WARNING, "BT-BCM") << bt.Path << ": its UART "
<< (source[0] ? source : (mini ? "(mini UART)" : "(PL011)")) << " is not in ACPI";
return false;
}
Hal::AML::ResourceList resources;
uint64_t base = 0, length = 0;
if (!Hal::AcpiDevices::EvaluateCrs(uart.NodeIndex, resources) ||
!resources.GetMemory(base, length) || base == 0) {
KernelLogStream(WARNING, "BT-BCM") << uart.Path << ": no register block in _CRS";
return false;
}
uint64_t console = Drivers::Serial::ConsoleAddress();
if (console != 0 && (console & ~0xFFFULL) == (base & ~0xFFFULL)) {
KernelLogStream(WARNING, "BT-BCM") << "Bluetooth's UART " << uart.Path
<< " is the serial console; leaving Bluetooth off";
return false;
}
BtUart::Config config{};
config.kind = kind;
config.mmioPhys = base;
config.levelTriggered = true;
if (auto* irq = resources.FindFirst(Hal::AML::ResourceType::ExtendedIrq)) {
config.gsi = irq->ExtendedIrq.Interrupt;
config.levelTriggered = (irq->ExtendedIrq.Flags & 1) == 0;
config.activeLow = (irq->ExtendedIrq.Flags & 2) != 0;
}
// The mini UART's baud generator runs off the VPU core clock: ask the
// firmware for its current rate, else take the rate the firmware
// patched into the table (MUCR; the placeholder 0x1000000 means it
// did not). The PL011's reference clock is fixed (CLCK, 48 MHz).
uint64_t tableClock = 0;
uint32_t clockHz = 0;
if (mini) {
if (!RpiFirmware::GetClockRate(RpiFirmware::CLOCK_CORE, clockHz)) {
if (ReadInteger(uart.NodeIndex, "MUCR", tableClock) && tableClock > 0x1000000)
clockHz = (uint32_t)tableClock;
else
clockHz = 500000000;
}
} else {
clockHz = ReadInteger(uart.NodeIndex, "CLCK", tableClock) && tableClock != 0
? (uint32_t)tableClock : 48000000;
}
config.clockHz = clockHz;
RpiGpio::Function function = mini ? RpiGpio::Function::Alt5 : RpiGpio::Function::Alt3;
bool pinsOk = RpiGpio::SetFunction(PIN_CTS, function)
&& RpiGpio::SetFunction(PIN_RTS, function)
&& RpiGpio::SetFunction(PIN_TXD, function)
&& RpiGpio::SetFunction(PIN_RXD, function)
&& RpiGpio::SetPull(PIN_CTS, RpiGpio::Pull::Up)
&& RpiGpio::SetPull(PIN_RTS, RpiGpio::Pull::None)
&& RpiGpio::SetPull(PIN_TXD, RpiGpio::Pull::None)
&& RpiGpio::SetPull(PIN_RXD, RpiGpio::Pull::Up);
if (!pinsOk) {
KernelLogStream(WARNING, "BT-BCM")
<< "No GPIO controller (BCM2845): UART pins left as the firmware set them";
}
if (!BtUart::Initialize(config)) return false;
Hci::InitializeUart();
Hci::SetVendor(Hci::Vendor::Broadcom);
KernelLogStream(OK, "BT-BCM") << "Bluetooth controller " << bt.Path << " on "
<< uart.Path << " (GSI " << (uint64_t)config.gsi << ")";
return true;
}
// =========================================================================
// Controller bring-up
// =========================================================================
static void PowerCycle() {
if (g_powerSwitch) {
if (RpiFirmware::SetGpioState(RpiFirmware::EXPGPIO_BT_ON, false)) {
Timekeeping::BusyWaitMs(20);
RpiFirmware::SetGpioState(RpiFirmware::EXPGPIO_BT_ON, true);
// hci_bcm allows 100-120 ms to power on and leave reset.
Timekeeping::BusyWaitMs(150);
} else {
g_powerSwitch = false;
KernelLogStream(WARNING, "BT-BCM")
<< "Cannot switch BT_ON through the firmware; assuming the controller is powered";
}
}
Hci::ResetUartReceiver();
}
static bool ResetController(uint32_t timeoutMs) {
if (!Hci::SendCommand(Hci::OP_RESET, nullptr, 0)) return false;
return Hci::WaitCommandComplete(Hci::OP_RESET, nullptr, 0, timeoutMs);
}
// Reach the controller at its power-on 115200 baud. Hardware RTS is tried
// first: if the RTS line is wired the wrong way round for it, the
// controller sees RTS deasserted and never answers, and the retry without
// it tells the two cases apart.
static bool SyncAtInitialBaud() {
BtUart::SetBaud(InitialBaud);
static constexpr bool attempts[] = { true, true, false, false };
for (bool flow : attempts) {
BtUart::SetFlowControl(flow);
Hci::ResetUartReceiver();
if (ResetController(1000)) {
g_flowControl = flow;
return true;
}
}
return false;
}
static uint32_t PickOperatingBaud() {
uint32_t wanted;
if (BtUart::GetKind() == BtUart::Kind::Pl011) {
wanted = g_flowControl ? 3000000 : 921600;
} else if (!BtUart::HasInterrupt()) {
// Polled 8-byte FIFO: stay where nothing overruns.
return InitialBaud;
} else {
wanted = g_flowControl ? 921600 : 460800;
}
static constexpr uint32_t rates[] = { 3000000, 2000000, 921600, 460800, 230400 };
for (uint32_t rate : rates) {
if (rate <= wanted && BtUart::BaudReachable(rate)) return rate;
}
return InitialBaud;
}
// Both ends to `baud`. The controller answers 0xFC18 at the old speed,
// then switches. False leaves the host at its old speed; the controller
// may or may not have switched (the caller power-cycles to be sure).
static bool SwitchBaud(uint32_t baud) {
uint32_t oldBaud = BtUart::GetBaud();
uint8_t params[6] = {
0x00, 0x00,
(uint8_t)baud, (uint8_t)(baud >> 8), (uint8_t)(baud >> 16), (uint8_t)(baud >> 24),
};
if (!Hci::SendCommand(Hci::OP_BCM_UPDATE_BAUD_RATE, params, sizeof(params)) ||
!Hci::WaitCommandComplete(Hci::OP_BCM_UPDATE_BAUD_RATE, nullptr, 0, 1000)) {
KernelLogStream(WARNING, "BT-BCM") << "Controller refused " << (uint64_t)baud << " baud";
return false;
}
if (!BtUart::SetBaud(baud)) return false;
Timekeeping::BusyWaitMs(10);
Hci::ResetUartReceiver();
Hci::LocalVersion ver{};
if (Hci::ReadLocalVersion(&ver)) return true;
KernelLogStream(WARNING, "BT-BCM") << "No answer at " << (uint64_t)baud << " baud";
BtUart::SetBaud(oldBaud);
Hci::ResetUartReceiver();
return false;
}
static const char* ChipFor(uint16_t subversion) {
for (const auto& chip : g_chips) {
if (chip.subversion == subversion) return chip.name;
}
return nullptr;
}
// Load 0:/os/firmware/brcm/<chip>.hcd. Returns nullptr (and logs) when
// the file is missing; the caller frees the image.
static uint8_t* LoadPatch(const char* chip, uint64_t& size) {
char path[64];
const char* prefix = "0:/os/firmware/brcm/";
int n = 0;
for (const char* p = prefix; *p; p++) path[n++] = *p;
for (const char* p = chip; *p && n < 56; p++) path[n++] = *p;
for (const char* p = ".hcd"; *p; p++) path[n++] = *p;
path[n] = '\0';
Fs::Vfs::BackendFile file;
if (Fs::Vfs::OpenBackendFile(path, file) < 0) {
KernelLogStream(WARNING, "BT-BCM") << "Firmware patch not found: " << path;
return nullptr;
}
size = Fs::Vfs::GetBackendFileSize(file);
uint8_t* image = size ? (uint8_t*)Memory::g_heap->Request(size) : nullptr;
if (image == nullptr) {
Fs::Vfs::CloseBackendFile(file);
return nullptr;
}
int got = Fs::Vfs::ReadBackendFile(file, image, 0, size);
Fs::Vfs::CloseBackendFile(file);
if (got != (int)size) {
Memory::g_heap->Free(image);
KernelLogStream(ERROR, "BT-BCM") << "Short read of " << path;
return nullptr;
}
KernelLogStream(INFO, "BT-BCM") << "Loading " << path << " (" << size << " bytes)";
return image;
}
static bool DownloadPatch(const uint8_t* image, uint64_t size) {
if (!Hci::SendCommand(Hci::OP_BCM_DOWNLOAD_MINIDRV, nullptr, 0) ||
!Hci::WaitCommandComplete(Hci::OP_BCM_DOWNLOAD_MINIDRV, nullptr, 0, 2000)) {
KernelLogStream(ERROR, "BT-BCM") << "Download Minidriver failed";
return false;
}
Timekeeping::BusyWaitMs(50);
uint64_t start = Timekeeping::GetMilliseconds();
uint64_t off = 0;
uint32_t records = 0;
while (off + 3 <= size) {
uint16_t opcode = (uint16_t)image[off] | ((uint16_t)image[off + 1] << 8);
uint8_t paramLength = image[off + 2];
off += 3;
if (off + paramLength > size) {
KernelLogStream(ERROR, "BT-BCM") << "Truncated patch record " << (uint64_t)records;
return false;
}
if (!Hci::SendCommand(opcode, image + off, paramLength)) return false;
bool acked = Hci::WaitCommandComplete(opcode, nullptr, 0, 2000);
if (!acked && opcode != Hci::OP_BCM_LAUNCH_RAM) {
KernelLogStream(ERROR, "BT-BCM") << "Patch record " << (uint64_t)records
<< " (opcode " << base::hex << (uint64_t)opcode << base::dec << ") failed";
return false;
}
off += paramLength;
records++;
}
// The patched firmware starts after Launch RAM; btbcm waits 250 ms.
Timekeeping::BusyWaitMs(250);
KernelLogStream(OK, "BT-BCM") << "Patch downloaded: " << (uint64_t)records << " records in "
<< (Timekeeping::GetMilliseconds() - start) << " ms at "
<< (uint64_t)BtUart::GetBaud() << " baud";
return true;
}
static void LogVersion(const char* chip, const Hci::LocalVersion& ver) {
uint16_t sub = ver.LmpSubversion;
KernelLogStream(OK, "BT-BCM") << (chip ? chip : "Broadcom controller") << " ("
<< (uint64_t)((sub & 0xE000) >> 13) << "." << (uint64_t)((sub & 0x1F00) >> 8)
<< "." << (uint64_t)(sub & 0x00FF) << ") build " << (uint64_t)(ver.HciRevision & 0x0FFF)
<< ", " << (uint64_t)BtUart::GetBaud() << " baud, "
<< (g_flowControl ? "RTS flow control" : "no flow control");
}
// One complete bring-up. `fast` raises the speed for the download and
// afterwards; without it everything stays at 115200.
static bool Attempt(bool fast) {
PowerCycle();
if (!SyncAtInitialBaud()) {
KernelLogStream(ERROR, "BT-BCM") << "Controller does not answer HCI Reset"
<< (g_powerSwitch ? "" : " (BT_ON was never switched on: it is likely unpowered)");
return false;
}
Hci::LocalVersion rom{};
if (!Hci::ReadLocalVersion(&rom)) return false;
const char* chip = ChipFor(rom.LmpSubversion);
KernelLogStream(INFO, "BT-BCM") << "ROM firmware: LMP subversion " << base::hex
<< (uint64_t)rom.LmpSubversion << ", HCI revision " << (uint64_t)rom.HciRevision
<< base::dec << (chip ? " -> " : " (unknown chip)") << (chip ? chip : "");
uint32_t operating = fast ? PickOperatingBaud() : InitialBaud;
if (operating != InitialBaud && !SwitchBaud(operating)) return false;
uint64_t size = 0;
uint8_t* image = chip ? LoadPatch(chip, size) : nullptr;
if (image != nullptr) {
bool ok = DownloadPatch(image, size);
Memory::g_heap->Free(image);
if (!ok) return false;
// The patched firmware comes up at 115200 again.
BtUart::SetBaud(InitialBaud);
Hci::ResetUartReceiver();
bool reset = false;
for (int i = 0; i < 3 && !reset; i++) reset = ResetController(1000);
if (!reset) {
KernelLogStream(ERROR, "BT-BCM") << "Patched firmware does not answer HCI Reset";
return false;
}
if (operating != InitialBaud && !SwitchBaud(operating)) return false;
} else {
KernelLogStream(WARNING, "BT-BCM") << "Running the controller's ROM firmware";
}
Hci::LocalVersion ver{};
if (Hci::ReadLocalVersion(&ver)) LogVersion(chip, ver);
return true;
}
bool Initialize() {
if (Hci::GetTransport() != Hci::Transport::Uart) return false;
if (Attempt(true)) return true;
KernelLogStream(WARNING, "BT-BCM") << "Retrying bring-up at 115200 baud";
return Attempt(false);
}
// =========================================================================
// BD_ADDR
// =========================================================================
// Addresses are in HCI order (addr[0] = least significant octet).
static bool IsDefaultAddress(const uint8_t* addr) {
bool allZero = true, allOnes = true;
for (int i = 0; i < 6; i++) {
if (addr[i] != 0x00) allZero = false;
if (addr[i] != 0xFF) allOnes = false;
}
if (allZero || allOnes) return true;
// The chip-wide defaults Linux's btbcm lists for these parts (the
// BCM4345C5's is 43:45:C5:00:1F:AC) start with 0x43, which has the
// IEEE group bit set: no assigned device address does.
return (addr[5] & 0x01) != 0;
}
void FixDefaultAddress() {
uint8_t addr[6];
if (!Hci::ReadBdAddr(addr) || !IsDefaultAddress(addr)) return;
uint8_t mac[6];
if (!RpiFirmware::GetBoardMacAddress(mac)) {
KernelLogStream(WARNING, "BT-BCM")
<< "Controller has its chip-wide default BD_ADDR and no board MAC is available";
return;
}
// The board's Ethernet MAC is unique to it and in the Raspberry Pi's
// own address block.
uint8_t board[6];
for (int i = 0; i < 6; i++) board[i] = mac[5 - i];
if (Hci::SetBdAddr(board)) {
KernelLogStream(OK, "BT-BCM") << "Replaced the chip-wide default BD_ADDR with the board's";
} else {
KernelLogStream(WARNING, "BT-BCM") << "Controller rejected the board BD_ADDR";
}
}
}
+27
View File
@@ -0,0 +1,27 @@
/*
* Broadcom.hpp
* Broadcom/Cypress UART Bluetooth controller bring-up (Raspberry Pi 4/400)
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
namespace Drivers::Bluetooth::Broadcom {
// Find the on-board controller ACPI describes (BCM2EA6, \_SB.GDV0.BTH0),
// route its UART to GPIO 30-33 and start the UART and the H4 HCI
// transport. Needs no firmware files, so it runs during the boot driver
// probe. Returns false when there is no such controller.
bool ProbePlatform();
// Power-cycle the controller, load its firmware patch from
// 0:/os/firmware/brcm/ and raise the UART to its operating speed.
// Leaves the controller freshly reset and ready for standard HCI.
bool Initialize();
// After the last HCI Reset: give a controller still on its chip-wide
// default BD_ADDR (the CYW43456 ships with one) a board-unique address
// from the Pi's Ethernet MAC.
void FixDefaultAddress();
}
@@ -1,6 +1,6 @@
/* /*
* Hci.cpp * Hci.cpp
* Bluetooth HCI transport over USB * Bluetooth HCI transport over USB or a UART (H4)
* Copyright (c) 2026 Daniel Hammer * Copyright (c) 2026 Daniel Hammer
*/ */
@@ -10,6 +10,7 @@
#include "A2dp.hpp" #include "A2dp.hpp"
#include <atomic> #include <atomic>
#include <Fs/Vfs.hpp> #include <Fs/Vfs.hpp>
#include <Drivers/Serial/BtUart.hpp>
#include <Drivers/USB/Xhci.hpp> #include <Drivers/USB/Xhci.hpp>
#include <Drivers/USB/UsbDevice.hpp> #include <Drivers/USB/UsbDevice.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
@@ -22,12 +23,17 @@
using namespace Kt; using namespace Kt;
namespace Drivers::USB::Bluetooth::Hci { namespace Drivers::Bluetooth::Hci {
namespace Xhci = Drivers::USB::Xhci;
namespace BtUart = Drivers::Serial::BtUart;
// ========================================================================= // =========================================================================
// State // State
// ========================================================================= // =========================================================================
static Transport g_transport = Transport::None;
static Vendor g_vendor = Vendor::Unknown;
static uint8_t g_slotId = 0; static uint8_t g_slotId = 0;
static bool g_initialized = false; static bool g_initialized = false;
@@ -541,6 +547,157 @@ namespace Drivers::USB::Bluetooth::Hci {
} }
} }
// =========================================================================
// UART (H4) receive deframing
// =========================================================================
// The UART delivers one byte stream: [type][header][payload]... Pump()
// feeds it here in process context, so complete packets go straight to
// the same command mailbox / async event ring / ACL ring the USB
// callback fills. A byte that cannot start a packet is dropped, which
// resynchronizes after line noise (e.g. while the controller powers up).
enum class H4State : uint8_t { Type, Header, Payload, Discard };
static H4State g_h4State = H4State::Type;
static uint8_t g_h4Type = 0;
static uint8_t g_h4Packet[ACL_RX_SLOT_SIZE] = {};
static uint16_t g_h4Len = 0; // bytes in g_h4Packet
static uint16_t g_h4HeaderLen = 0;
static uint16_t g_h4Expected = 0; // header + payload
static uint32_t g_h4Remaining = 0; // Discard: bytes left to skip
static uint32_t g_h4Dropped = 0; // bytes that could not start a packet
// The UART receive ring and the deframer have one consumer at a time:
// whoever holds this (Pump, or a flush).
static std::atomic<bool> g_uartConsumer{false};
void ResetUartReceiver() {
bool expected = false;
while (!g_uartConsumer.compare_exchange_weak(expected, true,
std::memory_order_acquire)) {
expected = false;
Arch::Pause();
}
BtUart::FlushRx();
g_h4State = H4State::Type;
g_h4Len = 0;
g_h4Remaining = 0;
g_uartConsumer.store(false, std::memory_order_release);
}
static void CompleteH4Packet() {
if (g_h4Type == H4_EVENT) {
QueueCompleteHciEvent(g_h4Packet, g_h4Len);
} else if (g_h4Type == H4_ACL) {
QueueCompleteAclPacket(g_h4Packet, g_h4Len);
}
}
static void FeedH4Bytes(const uint8_t* data, uint32_t length) {
uint32_t off = 0;
while (off < length) {
switch (g_h4State) {
case H4State::Type: {
uint8_t type = data[off++];
g_h4Type = type;
g_h4Len = 0;
if (type == H4_EVENT) g_h4HeaderLen = sizeof(EventHeader);
else if (type == H4_ACL) g_h4HeaderLen = sizeof(AclHeader);
else if (type == H4_SCO) g_h4HeaderLen = 3;
else { g_h4Dropped++; break; }
g_h4State = H4State::Header;
break;
}
case H4State::Header: {
g_h4Packet[g_h4Len++] = data[off++];
if (g_h4Len < g_h4HeaderLen) break;
uint32_t payload;
if (g_h4Type == H4_EVENT) payload = g_h4Packet[1];
else if (g_h4Type == H4_ACL) payload = (uint32_t)g_h4Packet[2]
| ((uint32_t)g_h4Packet[3] << 8);
else payload = g_h4Packet[2];
// SCO is not used; an ACL packet beyond the ring slot
// cannot be a valid one from a controller that reported
// its buffer size. Skip either without losing framing.
if (g_h4Type == H4_SCO
|| g_h4HeaderLen + payload > sizeof(g_h4Packet)) {
g_h4Remaining = payload;
g_h4State = payload ? H4State::Discard : H4State::Type;
break;
}
g_h4Expected = (uint16_t)(g_h4HeaderLen + payload);
if (payload == 0) {
CompleteH4Packet();
g_h4State = H4State::Type;
} else {
g_h4State = H4State::Payload;
}
break;
}
case H4State::Payload: {
uint32_t take = g_h4Expected - g_h4Len;
if (take > length - off) take = length - off;
memcpy(&g_h4Packet[g_h4Len], &data[off], take);
g_h4Len = (uint16_t)(g_h4Len + take);
off += take;
if (g_h4Len == g_h4Expected) {
CompleteH4Packet();
g_h4State = H4State::Type;
}
break;
}
case H4State::Discard: {
uint32_t take = length - off;
if (take > g_h4Remaining) take = g_h4Remaining;
off += take;
g_h4Remaining -= take;
if (g_h4Remaining == 0) g_h4State = H4State::Type;
break;
}
}
}
}
// =========================================================================
// Transport pump
// =========================================================================
void Pump() {
if (g_transport == Transport::Usb) {
Xhci::PollEvents();
return;
}
if (g_transport != Transport::Uart) return;
bool expected = false;
if (!g_uartConsumer.compare_exchange_strong(expected, true,
std::memory_order_acquire)) return;
// Also moves bytes by hand, in case the UART interrupt never fires.
BtUart::Service();
uint8_t chunk[256];
int n;
while ((n = BtUart::Read(chunk, sizeof(chunk))) > 0) {
FeedH4Bytes(chunk, (uint32_t)n);
}
g_uartConsumer.store(false, std::memory_order_release);
}
bool InPumpContext() {
// UART packets are only queued by Pump, never handled inside it.
return g_transport == Transport::Usb && Xhci::InPollContext();
}
bool TransportHasRxData() {
return g_transport == Transport::Uart && BtUart::HasRxData();
}
Transport GetTransport() { return g_transport; }
void SetVendor(Vendor vendor) { g_vendor = vendor; }
Vendor GetVendor() { return g_vendor; }
// ========================================================================= // =========================================================================
// USB transfer callback // USB transfer callback
// ========================================================================= // =========================================================================
@@ -641,7 +798,7 @@ namespace Drivers::USB::Bluetooth::Hci {
static void PollWait(uint32_t ms) { static void PollWait(uint32_t ms) {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < ms) { while (Timekeeping::GetMilliseconds() - start < ms) {
Xhci::PollEvents(); Pump();
DrainEvents(); DrainEvents();
for (int j = 0; j < 100; j++) { for (int j = 0; j < 100; j++) {
Arch::CompilerBarrier(); Arch::CompilerBarrier();
@@ -672,10 +829,20 @@ namespace Drivers::USB::Bluetooth::Hci {
// the configured USB function its settling delay. StartEventPipe then // the configured USB function its settling delay. StartEventPipe then
// arms both receive endpoints before the first bootloader command. // arms both receive endpoints before the first bootloader command.
g_transport = Transport::Usb;
g_initialized = true; g_initialized = true;
KernelLogStream(OK, "BT-HCI") << "HCI transport initialized on slot " << (uint64_t)slotId; KernelLogStream(OK, "BT-HCI") << "HCI transport initialized on slot " << (uint64_t)slotId;
} }
void InitializeUart() {
// No DMA buffers: SendCommand/SendAcl copy each H4 packet straight
// into the UART's transmit ring.
ResetUartReceiver();
g_transport = Transport::Uart;
g_initialized = true;
KernelLogStream(OK, "BT-HCI") << "HCI transport initialized on UART";
}
// Start receiving HCI events and ACL data — call after HCI init sequence. // Start receiving HCI events and ACL data — call after HCI init sequence.
// Arms BOTH the interrupt IN (events) and the bulk IN (ACL). The bulk IN // Arms BOTH the interrupt IN (events) and the bulk IN (ACL). The bulk IN
// MUST stay armed across the firmware download: on this controller the device // MUST stay armed across the firmware download: on this controller the device
@@ -712,7 +879,8 @@ namespace Drivers::USB::Bluetooth::Hci {
// ========================================================================= // =========================================================================
bool SendCommand(uint16_t opcode, const uint8_t* params, uint8_t paramLen) { bool SendCommand(uint16_t opcode, const uint8_t* params, uint8_t paramLen) {
if (!g_initialized || !g_cmdDmaBuf) return false; if (!g_initialized) return false;
if (g_transport == Transport::Usb && !g_cmdDmaBuf) return false;
uint16_t expected = 0; uint16_t expected = 0;
if (!g_commandOpcode.compare_exchange_strong(expected, opcode, if (!g_commandOpcode.compare_exchange_strong(expected, opcode,
@@ -745,6 +913,27 @@ namespace Drivers::USB::Bluetooth::Hci {
<< base::hex << (uint64_t)opcode << base::dec; << base::hex << (uint64_t)opcode << base::dec;
} }
if (g_transport == Transport::Uart) {
// H4: [0x01][opcode][paramLen][params]. The transmit ring is
// shared with ACL traffic; wait briefly for room behind it.
uint8_t header[4] = {
H4_COMMAND, (uint8_t)(opcode & 0xFF), (uint8_t)(opcode >> 8), paramLen
};
uint64_t start = Timekeeping::GetMilliseconds();
while (!BtUart::Write(header, sizeof(header), params, paramLen)) {
if (Timekeeping::GetMilliseconds() - start > 1000) {
FinishCommand(opcode);
KernelLogStream(WARNING, "BT-HCI") << "SendCommand: UART transmit stalled, opcode="
<< base::hex << (uint64_t)opcode << base::dec;
return false;
}
BtUart::Service();
Arch::Pause();
}
return true;
}
// Use DMA-allocated buffer (not stack) for the command data. // Use DMA-allocated buffer (not stack) for the command data.
// xHCI reads from this buffer via DMA for OUT transfers. // xHCI reads from this buffer via DMA for OUT transfers.
memset(g_cmdDmaBuf, 0, 512); memset(g_cmdDmaBuf, 0, 512);
@@ -791,7 +980,7 @@ namespace Drivers::USB::Bluetooth::Hci {
// cannot wait -- a nested PollEvents is a no-op, so the Command Complete // cannot wait -- a nested PollEvents is a no-op, so the Command Complete
// is reaped by the active PollEvents after we return. The command was // is reaped by the active PollEvents after we return. The command was
// already submitted (fire-and-forget); report success. // already submitted (fire-and-forget); report success.
if (Xhci::InPollContext()) { if (InPumpContext()) {
FinishCommand(opcode); FinishCommand(opcode);
return true; return true;
} }
@@ -799,7 +988,7 @@ namespace Drivers::USB::Bluetooth::Hci {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) { while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents(); Pump();
DrainEvents(); DrainEvents();
uint8_t event[sizeof(g_eventBuf)]; uint8_t event[sizeof(g_eventBuf)];
@@ -852,7 +1041,7 @@ namespace Drivers::USB::Bluetooth::Hci {
bool WaitCommandStatus(uint16_t opcode, uint32_t timeoutMs) { bool WaitCommandStatus(uint16_t opcode, uint32_t timeoutMs) {
// See WaitCommandComplete: cannot wait when nested under PollEvents. // See WaitCommandComplete: cannot wait when nested under PollEvents.
if (Xhci::InPollContext()) { if (InPumpContext()) {
FinishCommand(opcode); FinishCommand(opcode);
return true; return true;
} }
@@ -860,7 +1049,7 @@ namespace Drivers::USB::Bluetooth::Hci {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) { while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents(); Pump();
DrainEvents(); DrainEvents();
uint8_t event[sizeof(g_eventBuf)]; uint8_t event[sizeof(g_eventBuf)];
@@ -897,10 +1086,40 @@ namespace Drivers::USB::Bluetooth::Hci {
// SendAcl — via USB bulk OUT // SendAcl — via USB bulk OUT
// ========================================================================= // =========================================================================
// Room the UART transmit ring must keep for one maximum-size ACL packet.
static uint32_t UartAclSlotBytes() {
return 1u + sizeof(AclHeader) + (g_aclMaxLen ? g_aclMaxLen : 1021);
}
bool SendAcl(uint16_t handle, uint16_t pbFlag, const uint8_t* data, uint16_t len) { bool SendAcl(uint16_t handle, uint16_t pbFlag, const uint8_t* data, uint16_t len) {
if (!g_initialized || !g_aclTxRing[0] || !GetConnection(handle)) return false; if (!g_initialized || !GetConnection(handle)) return false;
if (len + sizeof(AclHeader) > 4096) return false; // Single page DMA buffer if (len + sizeof(AclHeader) > 4096) return false; // Single page DMA buffer
if (g_transport == Transport::Uart) {
g_aclTxLock.Acquire();
if (g_aclMaxNum != 0 && AclPendingCount() >= g_aclMaxNum) {
g_aclTxLock.Release();
return false;
}
uint16_t handleFlags = (uint16_t)((handle & 0x0FFF) | pbFlag);
uint8_t header[1 + sizeof(AclHeader)] = {
H4_ACL,
(uint8_t)handleFlags, (uint8_t)(handleFlags >> 8),
(uint8_t)len, (uint8_t)(len >> 8)
};
// All or nothing: a partial H4 packet would desynchronize the
// controller's deframer. A full ring drops the packet, as a
// full xHCI TX ring does.
bool queued = BtUart::Write(header, sizeof(header), data, len);
if (queued) {
g_aclPendingCount.fetch_add(1, std::memory_order_relaxed);
g_aclTxCount.fetch_add(1, std::memory_order_relaxed);
}
g_aclTxLock.Release();
return queued;
}
if (!g_aclTxRing[0]) return false;
g_aclTxLock.Acquire(); g_aclTxLock.Acquire();
// HCI host flow control is a hard limit, not merely a media-pacing // HCI host flow control is a hard limit, not merely a media-pacing
@@ -951,6 +1170,11 @@ namespace Drivers::USB::Bluetooth::Hci {
uint16_t AclMaxPackets() { return g_aclMaxNum; } uint16_t AclMaxPackets() { return g_aclMaxNum; }
uint32_t AclTxInFlight() { uint32_t AclTxInFlight() {
if (g_transport == Transport::Uart) {
uint32_t pending = BtUart::TxPending();
uint32_t slot = UartAclSlotBytes();
return (pending + slot - 1) / slot;
}
uint32_t tx = g_aclTxCount.load(std::memory_order_relaxed); uint32_t tx = g_aclTxCount.load(std::memory_order_relaxed);
uint32_t done = g_aclTxDoneCount.load(std::memory_order_relaxed); uint32_t done = g_aclTxDoneCount.load(std::memory_order_relaxed);
return (tx >= done) ? (tx - done) : 0; return (tx >= done) ? (tx - done) : 0;
@@ -966,6 +1190,10 @@ namespace Drivers::USB::Bluetooth::Hci {
uint16_t maxOut = g_aclMaxNum ? g_aclMaxNum : 4; uint16_t maxOut = g_aclMaxNum ? g_aclMaxNum : 4;
uint16_t mediaLimit = maxOut > 1 ? (uint16_t)(maxOut - 1) : maxOut; uint16_t mediaLimit = maxOut > 1 ? (uint16_t)(maxOut - 1) : maxOut;
if (AclPendingCount() >= mediaLimit) return false; if (AclPendingCount() >= mediaLimit) return false;
if (g_transport == Transport::Uart) {
// Room for this packet plus a signaling reply behind it.
return BtUart::TxFree() >= 2 * UartAclSlotBytes();
}
if (AclTxInFlight() >= (uint32_t)(ACL_TX_SLOTS - 1)) return false; if (AclTxInFlight() >= (uint32_t)(ACL_TX_SLOTS - 1)) return false;
return true; return true;
} }
@@ -1420,6 +1648,7 @@ namespace Drivers::USB::Bluetooth::Hci {
} }
case EVT_VENDOR_SPECIFIC: { case EVT_VENDOR_SPECIFIC: {
if (g_vendor != Vendor::Intel) break;
// Intel vendor events carry a sub-opcode in the first byte. // Intel vendor events carry a sub-opcode in the first byte.
// 0x02 = "bootup" (operational firmware booted after 0xFC01) // 0x02 = "bootup" (operational firmware booted after 0xFC01)
// 0x06 = "secure send result": result(1) opcode(2) status(1) // 0x06 = "secure send result": result(1) opcode(2) status(1)
@@ -1625,7 +1854,7 @@ namespace Drivers::USB::Bluetooth::Hci {
// copying past what the controller delivered. // copying past what the controller delivered.
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < 2000) { while (Timekeeping::GetMilliseconds() - start < 2000) {
Xhci::PollEvents(); Pump();
DrainEvents(); DrainEvents();
uint8_t event[sizeof(g_eventBuf)]; uint8_t event[sizeof(g_eventBuf)];
@@ -1676,7 +1905,7 @@ namespace Drivers::USB::Bluetooth::Hci {
void ProcessPendingCommands() { void ProcessPendingCommands() {
// Top-level only: drains queued pairing replies with real, confirmed // Top-level only: drains queued pairing replies with real, confirmed
// control transfers. Must NOT be called from inside PollEvents. // control transfers. Must NOT be called from inside PollEvents.
if (Xhci::InPollContext()) return; if (InPumpContext()) return;
// One consumer at a time: this runs from the Connect wait loop AND the // One consumer at a time: this runs from the Connect wait loop AND the
// idle-core service pump, potentially on different cores; two consumers // idle-core service pump, potentially on different cores; two consumers
// on the lockless queue would race the tail and interleave commands. // on the lockless queue would race the tail and interleave commands.
@@ -1728,7 +1957,7 @@ namespace Drivers::USB::Bluetooth::Hci {
bool WaitSecureSendResult(uint32_t timeoutMs, uint8_t* outResult, uint8_t* outStatus) { bool WaitSecureSendResult(uint32_t timeoutMs, uint8_t* outResult, uint8_t* outStatus) {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) { while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents(); Pump();
DrainEvents(); DrainEvents();
if (g_secureResultValid) { if (g_secureResultValid) {
if (outResult) *outResult = g_secureResult; if (outResult) *outResult = g_secureResult;
@@ -1763,7 +1992,7 @@ namespace Drivers::USB::Bluetooth::Hci {
<< " byte " << g_ssBytesSent; << " byte " << g_ssBytesSent;
return false; return false;
} }
Xhci::PollEvents(); Pump();
DrainEvents(); DrainEvents();
g_ssBytesSent += frag; g_ssBytesSent += frag;
@@ -1784,7 +2013,7 @@ namespace Drivers::USB::Bluetooth::Hci {
// header is deliberately absent, and the NOCP credit counter is NOT // header is deliberately absent, and the NOCP credit counter is NOT
// touched: these are commands, not ACL data). // touched: these are commands, not ACL data).
bool IntelSecureSendBulk(uint8_t fragmentType, const uint8_t* data, uint32_t len) { bool IntelSecureSendBulk(uint8_t fragmentType, const uint8_t* data, uint32_t len) {
if (!g_initialized || !g_aclTxRing[0]) return false; if (!g_initialized || g_transport != Transport::Usb || !g_aclTxRing[0]) return false;
if (g_aclTxTransportError.load(std::memory_order_acquire)) return false; if (g_aclTxTransportError.load(std::memory_order_acquire)) return false;
uint32_t off = 0; uint32_t off = 0;
@@ -1796,7 +2025,7 @@ namespace Drivers::USB::Bluetooth::Hci {
// controller from freezing the boot path. // controller from freezing the boot path.
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (AclTxInFlight() >= (uint32_t)(ACL_TX_SLOTS - 1)) { while (AclTxInFlight() >= (uint32_t)(ACL_TX_SLOTS - 1)) {
Xhci::PollEvents(); Pump();
DrainEvents(); DrainEvents();
if (g_aclTxTransportError.load(std::memory_order_acquire)) { if (g_aclTxTransportError.load(std::memory_order_acquire)) {
KernelLogStream(ERROR, "BT-HCI") KernelLogStream(ERROR, "BT-HCI")
@@ -1843,7 +2072,7 @@ namespace Drivers::USB::Bluetooth::Hci {
bool DrainBulkTx(uint32_t timeoutMs) { bool DrainBulkTx(uint32_t timeoutMs) {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (AclTxInFlight() > 0) { while (AclTxInFlight() > 0) {
Xhci::PollEvents(); Pump();
DrainEvents(); DrainEvents();
if (g_aclTxTransportError.load(std::memory_order_acquire)) return false; if (g_aclTxTransportError.load(std::memory_order_acquire)) return false;
if (Timekeeping::GetMilliseconds() - start > timeoutMs) return false; if (Timekeeping::GetMilliseconds() - start > timeoutMs) return false;
@@ -1872,7 +2101,7 @@ namespace Drivers::USB::Bluetooth::Hci {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) { while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents(); Pump();
DrainEvents(); DrainEvents();
if (g_intelBootup) return true; if (g_intelBootup) return true;
for (int j = 0; j < 100; j++) Arch::CompilerBarrier(); for (int j = 0; j < 100; j++) Arch::CompilerBarrier();
@@ -1953,9 +2182,14 @@ namespace Drivers::USB::Bluetooth::Hci {
} }
bool SetBdAddr(const uint8_t* addr) { bool SetBdAddr(const uint8_t* addr) {
// Intel 0xFC31: 6-byte BD_ADDR, little-endian (same order as ReadBdAddr). // Intel 0xFC31 / Broadcom 0xFC01: 6-byte BD_ADDR, little-endian (same
if (!SendCommand(OP_INTEL_WRITE_BD_ADDR, addr, 6)) return false; // order as ReadBdAddr).
return WaitCommandComplete(OP_INTEL_WRITE_BD_ADDR, nullptr, 0, 2000); uint16_t opcode;
if (g_vendor == Vendor::Intel) opcode = OP_INTEL_WRITE_BD_ADDR;
else if (g_vendor == Vendor::Broadcom) opcode = OP_BCM_WRITE_BD_ADDR;
else return false;
if (!SendCommand(opcode, addr, 6)) return false;
return WaitCommandComplete(opcode, nullptr, 0, 2000);
} }
bool Disconnect(uint16_t handle, uint8_t reason) { bool Disconnect(uint16_t handle, uint8_t reason) {
@@ -1,23 +1,35 @@
/* /*
* Hci.hpp * Hci.hpp
* Bluetooth HCI (Host Controller Interface) layer * Bluetooth HCI (Host Controller Interface) layer
* HCI transport over USB bulk/interrupt/control endpoints * HCI transport over USB (bulk/interrupt/control endpoints) or a UART (H4)
* Copyright (c) 2026 Daniel Hammer * Copyright (c) 2026 Daniel Hammer
*/ */
#pragma once #pragma once
#include <cstdint> #include <cstdint>
namespace Drivers::USB::Bluetooth::Hci { namespace Drivers::Bluetooth::Hci {
// ========================================================================= // =========================================================================
// HCI packet types (for USB transport) // HCI transports
// ========================================================================= // =========================================================================
// USB transport uses different endpoints for each packet type: // USB transport uses different endpoints for each packet type:
// Commands -> Control EP0 (class request) // Commands -> Control EP0 (class request)
// ACL data -> Bulk OUT / Bulk IN // ACL data -> Bulk OUT / Bulk IN
// Events -> Interrupt IN // Events -> Interrupt IN
//
// The UART transport (H4, Bluetooth Core spec Vol 4 Part A) carries all
// of them on one byte stream, each packet preceded by its type.
constexpr uint8_t H4_COMMAND = 0x01;
constexpr uint8_t H4_ACL = 0x02;
constexpr uint8_t H4_SCO = 0x03;
constexpr uint8_t H4_EVENT = 0x04;
enum class Transport : uint8_t { None, Usb, Uart };
// Controller vendor, for the vendor-specific commands (BD_ADDR write).
enum class Vendor : uint8_t { Unknown, Intel, Broadcom };
// ========================================================================= // =========================================================================
// HCI command opcodes (OGF << 10 | OCF) // HCI command opcodes (OGF << 10 | OCF)
@@ -61,6 +73,13 @@ namespace Drivers::USB::Bluetooth::Hci {
constexpr uint16_t OP_READ_LOCAL_FEATURES = 0x1003; constexpr uint16_t OP_READ_LOCAL_FEATURES = 0x1003;
constexpr uint16_t OP_READ_BUFFER_SIZE = 0x1005; constexpr uint16_t OP_READ_BUFFER_SIZE = 0x1005;
// Broadcom vendor commands (OGF 0x3F), from Linux btbcm/hci_bcm
constexpr uint16_t OP_BCM_WRITE_BD_ADDR = 0xFC01;
constexpr uint16_t OP_BCM_UPDATE_BAUD_RATE = 0xFC18;
constexpr uint16_t OP_BCM_DOWNLOAD_MINIDRV = 0xFC2E;
constexpr uint16_t OP_BCM_WRITE_RAM = 0xFC4C;
constexpr uint16_t OP_BCM_LAUNCH_RAM = 0xFC4E;
// Intel vendor commands (OGF 0x3F) // Intel vendor commands (OGF 0x3F)
constexpr uint16_t OP_INTEL_READ_VERSION = 0xFC05; constexpr uint16_t OP_INTEL_READ_VERSION = 0xFC05;
constexpr uint16_t OP_INTEL_RESET = 0xFC01; constexpr uint16_t OP_INTEL_RESET = 0xFC01;
@@ -175,6 +194,32 @@ namespace Drivers::USB::Bluetooth::Hci {
// Initialize HCI transport over USB for the given slot // Initialize HCI transport over USB for the given slot
void Initialize(uint8_t slotId); void Initialize(uint8_t slotId);
// Initialize HCI transport over the Bluetooth UART (Serial::BtUart,
// already initialized by the caller).
void InitializeUart();
Transport GetTransport();
void SetVendor(Vendor vendor);
Vendor GetVendor();
// Move received bytes from the transport into the HCI queues: the xHCI
// event ring for USB, the UART receive ring (H4 deframing) for UART.
// Every wait loop calls this before DrainEvents. Non-reentrant; a
// nested or concurrent call returns at once.
void Pump();
// True while running nested under Pump (USB transfer callbacks), where
// a command cannot be waited for.
bool InPumpContext();
// Received transport bytes are waiting for Pump (UART only; USB signals
// through the xHCI deferred-work flag).
bool TransportHasRxData();
// Discard all received UART input, queued bytes and any partial packet
// (after a power cycle or a baud-rate change).
void ResetUartReceiver();
// Start receiving HCI events and ACL data (call after HCI init sequence). // Start receiving HCI events and ACL data (call after HCI init sequence).
// Arms both the interrupt IN and the bulk IN; the bulk IN must stay armed // Arms both the interrupt IN and the bulk IN; the bulk IN must stay armed
// through the firmware download (it absorbs the device's ~635 KB cc=4 glitch // through the firmware download (it absorbs the device's ~635 KB cc=4 glitch
@@ -186,7 +231,7 @@ namespace Drivers::USB::Bluetooth::Hci {
// are intentionally ignored and any endpoint error is recovered here. // are intentionally ignored and any endpoint error is recovered here.
void EnableAclDataReception(); void EnableAclDataReception();
// Send an HCI command via USB control transfer (EP0) // Send an HCI command (USB: control transfer on EP0; UART: H4 packet)
bool SendCommand(uint16_t opcode, const uint8_t* params, uint8_t paramLen); bool SendCommand(uint16_t opcode, const uint8_t* params, uint8_t paramLen);
// Wait for a Command Complete event matching the given opcode // Wait for a Command Complete event matching the given opcode
@@ -197,7 +242,7 @@ namespace Drivers::USB::Bluetooth::Hci {
// Wait for a Command Status event matching the given opcode // Wait for a Command Status event matching the given opcode
bool WaitCommandStatus(uint16_t opcode, uint32_t timeoutMs = 2000); bool WaitCommandStatus(uint16_t opcode, uint32_t timeoutMs = 2000);
// Send ACL data via USB bulk OUT // Send ACL data (USB: bulk OUT; UART: H4 packet)
bool SendAcl(uint16_t handle, uint16_t pbFlag, const uint8_t* data, uint16_t len); bool SendAcl(uint16_t handle, uint16_t pbFlag, const uint8_t* data, uint16_t len);
// Process an HCI event received on the interrupt IN endpoint // Process an HCI event received on the interrupt IN endpoint
@@ -323,8 +368,10 @@ namespace Drivers::USB::Bluetooth::Hci {
// Drives Link Key Request -> pairing; needed for bonded-device reconnects. // Drives Link Key Request -> pairing; needed for bonded-device reconnects.
bool AuthenticateLink(uint16_t handle); bool AuthenticateLink(uint16_t handle);
// Set the adapter's BD_ADDR (Intel vendor command 0xFC31). Used to dodge a // Set the adapter's BD_ADDR (vendor command: Intel 0xFC31, Broadcom
// remote that holds a stale, un-clearable bond to our real address. // 0xFC01). Used to dodge a remote that holds a stale, un-clearable bond
// to our real address, and to give Broadcom parts without a programmed
// address a unique one.
bool SetBdAddr(const uint8_t* addr); bool SetBdAddr(const uint8_t* addr);
// Send any queued pairing replies (IO-cap / user-confirm / link-key) with // Send any queued pairing replies (IO-cap / user-confirm / link-key) with
@@ -340,7 +387,8 @@ namespace Drivers::USB::Bluetooth::Hci {
uint16_t AclMaxPackets(); uint16_t AclMaxPackets();
// ACL packets handed to the xHCI whose bulk OUT completion has not been // ACL packets handed to the xHCI whose bulk OUT completion has not been
// reaped yet (each one still owns a TX DMA ring slot). // reaped yet (each one still owns a TX DMA ring slot). On UART, the
// transmit ring's backlog in maximum-size packets (0 once drained).
uint32_t AclTxInFlight(); uint32_t AclTxInFlight();
// True when SendAcl can be called without overrunning either the // True when SendAcl can be called without overrunning either the
@@ -23,7 +23,7 @@
using namespace Kt; using namespace Kt;
namespace Drivers::USB::Bluetooth { namespace Drivers::Bluetooth {
// ========================================================================= // =========================================================================
// .sfi / TLV format constants (from drivers/bluetooth/btintel.{c,h}) // .sfi / TLV format constants (from drivers/bluetooth/btintel.{c,h})
@@ -7,7 +7,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
namespace Drivers::USB::Bluetooth { namespace Drivers::Bluetooth {
// Drive the Intel bootloader firmware download sequence for the currently // Drive the Intel bootloader firmware download sequence for the currently
// registered adapter: // registered adapter:
@@ -9,7 +9,6 @@
#include "Hci.hpp" #include "Hci.hpp"
#include "A2dp.hpp" #include "A2dp.hpp"
#include "Avrcp.hpp" #include "Avrcp.hpp"
#include <Drivers/USB/Xhci.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
@@ -17,7 +16,7 @@
using namespace Kt; using namespace Kt;
namespace Drivers::USB::Bluetooth::L2cap { namespace Drivers::Bluetooth::L2cap {
// ========================================================================= // =========================================================================
// State // State
@@ -548,7 +547,7 @@ namespace Drivers::USB::Bluetooth::L2cap {
uint64_t start = Timekeeping::GetMilliseconds(); uint64_t start = Timekeeping::GetMilliseconds();
while (Timekeeping::GetMilliseconds() - start < timeoutMs) { while (Timekeeping::GetMilliseconds() - start < timeoutMs) {
Xhci::PollEvents(); Hci::Pump();
Hci::DrainEvents(); // process incoming ACL (the Connection Response!) Hci::DrainEvents(); // process incoming ACL (the Connection Response!)
auto* ch = GetChannel(localCid); auto* ch = GetChannel(localCid);
@@ -7,7 +7,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
namespace Drivers::USB::Bluetooth::L2cap { namespace Drivers::Bluetooth::L2cap {
// ========================================================================= // =========================================================================
// L2CAP CIDs (Channel Identifiers) // L2CAP CIDs (Channel Identifiers)
@@ -9,7 +9,7 @@
#include "Sbc.hpp" #include "Sbc.hpp"
#include <Libraries/Memory.hpp> #include <Libraries/Memory.hpp>
namespace Drivers::USB::Bluetooth::Sbc { namespace Drivers::Bluetooth::Sbc {
// ========================================================================= // =========================================================================
// Analysis filterbank tables // Analysis filterbank tables
@@ -8,7 +8,7 @@
#pragma once #pragma once
#include <cstdint> #include <cstdint>
namespace Drivers::USB::Bluetooth::Sbc { namespace Drivers::Bluetooth::Sbc {
// ========================================================================= // =========================================================================
// SBC configuration // SBC configuration
+5 -2
View File
@@ -17,6 +17,7 @@
#include <Drivers/Storage/Sdhci.hpp> #include <Drivers/Storage/Sdhci.hpp>
#include <Drivers/Storage/Gpt.hpp> #include <Drivers/Storage/Gpt.hpp>
#include <Drivers/Audio/IntelHda.hpp> #include <Drivers/Audio/IntelHda.hpp>
#include <Drivers/Bluetooth/Bluetooth.hpp>
#include <Graphics/Framebuffer.hpp> #include <Graphics/Framebuffer.hpp>
#include <Net/Net.hpp> #include <Net/Net.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
@@ -359,6 +360,7 @@ namespace Drivers {
Pci::ProbeAll(g_driverTable, g_driverTableCount, Pci::ProbePhase::Normal); Pci::ProbeAll(g_driverTable, g_driverTableCount, Pci::ProbePhase::Normal);
ProbePlatformXhci(); ProbePlatformXhci();
ProbePlatformSdhci(); ProbePlatformSdhci();
Bluetooth::ProbePlatformAdapter();
} }
void InitializeNetwork() { void InitializeNetwork() {
@@ -373,8 +375,9 @@ namespace Drivers {
void InitializeAudio() { void InitializeAudio() {
// HDA driver initializes during ProbeNormal(). // HDA driver initializes during ProbeNormal().
// Bluetooth audio (A2DP) initializes automatically during USB enumeration // Bluetooth audio (A2DP) initializes with its adapter: during USB
// when a Bluetooth adapter is detected by the xHCI driver. // enumeration, or for an on-board UART controller (Raspberry Pi) from
// the platform probe in ProbeNormal().
} }
} }
+250
View File
@@ -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]);
}
+69
View File
@@ -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;
}
}
+37
View File
@@ -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);
}
+466
View File
@@ -0,0 +1,466 @@
/*
* BtUart.cpp
* Interrupt-driven UART to an on-board Bluetooth controller
* Copyright (c) 2026 Daniel Hammer
*/
#include "BtUart.hpp"
#include <ACPI/GsiRouting.hpp>
#include <Arch/Cpu.hpp>
#include <Arch/Interrupts.hpp>
#include <CppLib/Spinlock.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
#include <Sched/Scheduler.hpp>
#include <Terminal/Terminal.hpp>
#include <Timekeeping/Timer.hpp>
#include <atomic>
using namespace Kt;
namespace Drivers::Serial::BtUart {
// =========================================================================
// Registers
// =========================================================================
// Mini UART, offsets from the AUX block (BCM2711 ARM Peripherals 2.2,
// with the elinux.org BCM2835 datasheet errata: IER bit 0 enables the
// receive interrupt and bit 1 the transmit one, IER bits 3:2 must be set
// for interrupts to be raised, LCR needs 0b11 for 8 data bits).
static constexpr uint32_t AUX_ENABLES = 0x04;
static constexpr uint32_t MU_IO = 0x40;
static constexpr uint32_t MU_IER = 0x44;
static constexpr uint32_t MU_IIR = 0x48;
static constexpr uint32_t MU_LCR = 0x4C;
static constexpr uint32_t MU_MCR = 0x50;
static constexpr uint32_t MU_LSR = 0x54;
static constexpr uint32_t MU_CNTL = 0x60;
static constexpr uint32_t MU_BAUD = 0x68;
static constexpr uint32_t AUX_ENABLE_MU = 1u << 0;
static constexpr uint32_t MU_IER_RX = 1u << 0;
static constexpr uint32_t MU_IER_TX = 1u << 1;
static constexpr uint32_t MU_IER_REQUIRED = 3u << 2;
static constexpr uint32_t MU_IIR_FLUSH = 0xC6; // clear both FIFOs, FIFOs on
static constexpr uint32_t MU_LCR_8BIT = 0x03;
static constexpr uint32_t MU_MCR_RTS = 1u << 1; // RTS pin low (asserted)
static constexpr uint32_t MU_LSR_DATA_READY = 1u << 0;
static constexpr uint32_t MU_LSR_OVERRUN = 1u << 1;
static constexpr uint32_t MU_LSR_TX_SPACE = 1u << 5; // FIFO accepts a byte
static constexpr uint32_t MU_LSR_TX_IDLE = 1u << 6; // FIFO empty, shifter idle
static constexpr uint32_t MU_CNTL_RXEN = 1u << 0;
static constexpr uint32_t MU_CNTL_TXEN = 1u << 1;
// Auto RTS at the default level (deassert with 3 FIFO spaces left) and
// the default, active-low polarity (CNTL bit 6 would invert it).
static constexpr uint32_t MU_CNTL_AUTORTS = 1u << 2;
// PL011 (ARM PrimeCell UART TRM)
static constexpr uint32_t PL_DR = 0x00;
static constexpr uint32_t PL_FR = 0x18;
static constexpr uint32_t PL_IBRD = 0x24;
static constexpr uint32_t PL_FBRD = 0x28;
static constexpr uint32_t PL_LCRH = 0x2C;
static constexpr uint32_t PL_CR = 0x30;
static constexpr uint32_t PL_IFLS = 0x34;
static constexpr uint32_t PL_IMSC = 0x38;
static constexpr uint32_t PL_ICR = 0x44;
static constexpr uint32_t PL_DR_OE = 1u << 11;
static constexpr uint32_t PL_FR_BUSY = 1u << 3;
static constexpr uint32_t PL_FR_RXFE = 1u << 4;
static constexpr uint32_t PL_FR_TXFF = 1u << 5;
static constexpr uint32_t PL_FR_TXFE = 1u << 7;
static constexpr uint32_t PL_LCRH_FEN = 1u << 4;
static constexpr uint32_t PL_LCRH_8BIT = 3u << 5;
static constexpr uint32_t PL_CR_UARTEN = 1u << 0;
static constexpr uint32_t PL_CR_TXE = 1u << 8;
static constexpr uint32_t PL_CR_RXE = 1u << 9;
static constexpr uint32_t PL_CR_RTS = 1u << 11;
static constexpr uint32_t PL_CR_RTSEN = 1u << 14;
static constexpr uint32_t PL_CR_CTSEN = 1u << 15;
static constexpr uint32_t PL_INT_RX = 1u << 4;
static constexpr uint32_t PL_INT_TX = 1u << 5;
static constexpr uint32_t PL_INT_RT = 1u << 6; // receive timeout
static constexpr uint32_t PL_INT_OE = 1u << 10;
static constexpr uint32_t PL_IFLS_RX_HALF = 2u << 3;
static constexpr uint32_t PL_IFLS_TX_EIGHTH = 0;
// =========================================================================
// State
// =========================================================================
static constexpr uint32_t RX_RING_SIZE = 16384; // powers of two
static constexpr uint32_t TX_RING_SIZE = 16384;
static Kind g_kind = Kind::MiniUart;
static volatile uint8_t* g_mmio = nullptr;
static uint32_t g_clockHz = 0;
static uint32_t g_baud = 0;
static bool g_flowControl = false;
static bool g_interrupt = false;
static bool g_initialized = false;
// Guards the hardware and the transmit ring: taken by the IRQ handler,
// Service() and every producer, with interrupts off.
static kcp::Spinlock g_hwLock;
// Receive ring: produced under g_hwLock, consumed by Read() (one reader).
static uint8_t g_rxRing[RX_RING_SIZE];
static std::atomic<uint32_t> g_rxHead{0};
static std::atomic<uint32_t> g_rxTail{0};
// Transmit ring: entirely under g_hwLock. g_txCount mirrors the fill for
// lock-free readers.
static uint8_t g_txRing[TX_RING_SIZE];
static uint32_t g_txHead = 0;
static uint32_t g_txTail = 0;
static std::atomic<uint32_t> g_txCount{0};
static bool g_txIrqEnabled = false;
static Stats g_stats = {};
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;
}
// =========================================================================
// FIFO service (caller holds g_hwLock)
// =========================================================================
static inline void PushRx(uint8_t byte) {
uint32_t head = g_rxHead.load(std::memory_order_relaxed);
if (head - g_rxTail.load(std::memory_order_acquire) >= RX_RING_SIZE) {
g_stats.rxDropped++;
return;
}
g_rxRing[head & (RX_RING_SIZE - 1)] = byte;
g_rxHead.store(head + 1, std::memory_order_release);
g_stats.rxBytes++;
}
static bool DrainRxLocked() {
bool any = false;
if (g_kind == Kind::MiniUart) {
for (;;) {
uint32_t lsr = Read32(MU_LSR);
if (lsr & MU_LSR_OVERRUN) g_stats.overruns++;
if (!(lsr & MU_LSR_DATA_READY)) break;
PushRx((uint8_t)Read32(MU_IO));
any = true;
}
} else {
while (!(Read32(PL_FR) & PL_FR_RXFE)) {
uint32_t data = Read32(PL_DR);
if (data & PL_DR_OE) g_stats.overruns++;
PushRx((uint8_t)data);
any = true;
}
Write32(PL_ICR, PL_INT_RX | PL_INT_RT | PL_INT_OE);
}
return any;
}
static void SetTxIrqLocked(bool on) {
if (on == g_txIrqEnabled || !g_interrupt) return;
g_txIrqEnabled = on;
if (g_kind == Kind::MiniUart) {
Write32(MU_IER, MU_IER_RX | MU_IER_REQUIRED | (on ? MU_IER_TX : 0));
} else {
Write32(PL_IMSC, PL_INT_RX | PL_INT_RT | PL_INT_OE | (on ? PL_INT_TX : 0));
}
}
// Move queued bytes into the transmit FIFO. The transmit interrupt stays
// enabled exactly while bytes remain queued: the PL011's only fires on
// crossing its FIFO threshold, so the FIFO must be refilled here first.
static void FillTxLocked() {
while (g_txHead != g_txTail) {
bool space = (g_kind == Kind::MiniUart)
? (Read32(MU_LSR) & MU_LSR_TX_SPACE) != 0
: (Read32(PL_FR) & PL_FR_TXFF) == 0;
if (!space) break;
uint8_t byte = g_txRing[g_txTail & (TX_RING_SIZE - 1)];
g_txTail++;
Write32(g_kind == Kind::MiniUart ? MU_IO : PL_DR, byte);
g_stats.txBytes++;
}
g_txCount.store(g_txHead - g_txTail, std::memory_order_release);
if (g_kind == Kind::Pl011) Write32(PL_ICR, PL_INT_TX);
SetTxIrqLocked(g_txHead != g_txTail);
}
static bool TransmitterIdleLocked() {
if (g_txHead != g_txTail) return false;
if (g_kind == Kind::MiniUart) return (Read32(MU_LSR) & MU_LSR_TX_IDLE) != 0;
uint32_t fr = Read32(PL_FR);
return (fr & PL_FR_TXFE) && !(fr & PL_FR_BUSY);
}
static void HandleInterrupt(uint8_t, bool fromUser) {
g_hwLock.Acquire();
g_stats.interrupts++;
bool received = DrainRxLocked();
FillTxLocked();
g_hwLock.Release();
// Received bytes are parsed in process context. When this interrupt
// landed in user mode, ask for an idle-context pass at once (as the
// timer does for USB) instead of waiting for the core to go idle.
if (received && fromUser) Sched::RequestKernelWork();
}
// =========================================================================
// Baud rate (caller holds g_hwLock, transmitter idle)
// =========================================================================
static bool WithinTolerance(uint64_t actual, uint32_t wanted) {
uint64_t diff = actual > wanted ? actual - wanted : wanted - actual;
return diff * 40 <= wanted; // 2.5%
}
// The divisor for `baud`: mini UART, baud = clock / (8 * divisor) (the
// register holds divisor - 1); PL011, clock / (16 * baud) in 16.6 fixed
// point (IBRD = divisor >> 6, FBRD = divisor & 63).
static bool ComputeDivisor(uint32_t baud, uint64_t& divisor) {
if (baud == 0 || g_clockHz == 0) return false;
if (g_kind == Kind::MiniUart) {
divisor = ((uint64_t)g_clockHz + 4ull * baud) / (8ull * baud);
if (divisor < 1 || divisor > 0x10000) return false;
return WithinTolerance((uint64_t)g_clockHz / (8 * divisor), baud);
}
divisor = ((uint64_t)g_clockHz * 8 / baud + 1) / 2;
if ((divisor >> 6) < 1 || (divisor >> 6) > 0xFFFF) return false;
return WithinTolerance((uint64_t)g_clockHz * 4 / divisor, baud);
}
static bool ProgramBaudLocked(uint32_t baud) {
uint64_t divisor = 0;
if (!ComputeDivisor(baud, divisor)) return false;
if (g_kind == Kind::MiniUart) {
uint32_t cntl = Read32(MU_CNTL);
Write32(MU_CNTL, 0);
Write32(MU_BAUD, (uint32_t)(divisor - 1));
Write32(MU_CNTL, cntl);
} else {
uint32_t ibrd = (uint32_t)(divisor >> 6);
uint32_t fbrd = (uint32_t)(divisor & 63);
uint32_t cr = Read32(PL_CR);
Write32(PL_CR, 0);
for (int i = 0; i < 100000 && (Read32(PL_FR) & PL_FR_BUSY); i++) Arch::Pause();
Write32(PL_LCRH, 0); // flush the FIFOs
Write32(PL_IBRD, ibrd);
Write32(PL_FBRD, fbrd);
Write32(PL_LCRH, PL_LCRH_8BIT | PL_LCRH_FEN); // latches IBRD/FBRD
Write32(PL_CR, cr);
}
g_baud = baud;
return true;
}
// =========================================================================
// Public API
// =========================================================================
bool Initialize(const Config& config) {
if (g_initialized) return true;
if (config.mmioPhys == 0 || config.clockHz == 0) return false;
g_kind = config.kind;
g_clockHz = config.clockHz;
// The mini UART lives 0x40 into the AUX block; accept either base.
uint64_t base = config.mmioPhys;
if (g_kind == Kind::MiniUart && (base & 0xFF) == MU_IO) base -= MU_IO;
uint64_t page = base & ~0xFFFULL;
Memory::VMM::g_paging->MapMMIO(page, Memory::HHDM(page));
g_mmio = (volatile uint8_t*)Memory::HHDM(base);
g_hwLock.Acquire();
if (g_kind == Kind::MiniUart) {
// AUX_ENABLES also gates SPI1/SPI2; leave their bits alone.
Write32(AUX_ENABLES, Read32(AUX_ENABLES) | AUX_ENABLE_MU);
Write32(MU_CNTL, 0);
Write32(MU_IER, 0);
Write32(MU_LCR, MU_LCR_8BIT);
Write32(MU_MCR, MU_MCR_RTS);
Write32(MU_IIR, MU_IIR_FLUSH);
Write32(MU_CNTL, MU_CNTL_RXEN | MU_CNTL_TXEN);
} else {
Write32(PL_CR, 0);
Write32(PL_IMSC, 0);
Write32(PL_ICR, 0x7FF);
Write32(PL_IFLS, PL_IFLS_RX_HALF | PL_IFLS_TX_EIGHTH);
Write32(PL_LCRH, PL_LCRH_8BIT | PL_LCRH_FEN);
Write32(PL_CR, PL_CR_UARTEN | PL_CR_TXE | PL_CR_RXE | PL_CR_RTS);
}
bool baudOk = ProgramBaudLocked(115200);
while (DrainRxLocked()) {}
g_rxTail.store(g_rxHead.load(std::memory_order_relaxed), std::memory_order_release);
g_hwLock.Release();
if (!baudOk) {
KernelLogStream(ERROR, "BtUart") << "115200 baud unreachable from a "
<< (uint64_t)g_clockHz << " Hz clock";
return false;
}
if (config.gsi != 0) {
uint8_t line = Hal::GsiRouting::LineForGsi(config.gsi, config.levelTriggered,
config.activeLow);
if (line != 0) {
Arch::RegisterIrqHandler(line, HandleInterrupt);
g_interrupt = true;
g_hwLock.Acquire();
if (g_kind == Kind::MiniUart) {
Write32(MU_IER, MU_IER_RX | MU_IER_REQUIRED);
} else {
Write32(PL_IMSC, PL_INT_RX | PL_INT_RT | PL_INT_OE);
}
g_hwLock.Release();
Arch::EnableLegacyIrq(line);
}
}
g_initialized = true;
KernelLogStream(OK, "BtUart") << (g_kind == Kind::MiniUart ? "Mini UART" : "PL011")
<< " at 0x" << base::hex << base << base::dec << ", " << (uint64_t)g_clockHz
<< " Hz clock, " << (g_interrupt ? "interrupt driven" : "polled (no interrupt)");
return true;
}
bool IsInitialized() { return g_initialized; }
bool HasInterrupt() { return g_interrupt; }
uint32_t GetBaud() { return g_baud; }
Kind GetKind() { return g_kind; }
bool FlowControl() { return g_flowControl; }
bool BaudReachable(uint32_t baud) {
uint64_t divisor = 0;
return g_initialized && ComputeDivisor(baud, divisor);
}
bool SetBaud(uint32_t baud) {
if (!g_initialized) return false;
WaitTxIdle(1000);
g_hwLock.Acquire();
bool ok = ProgramBaudLocked(baud);
g_hwLock.Release();
return ok;
}
void SetFlowControl(bool on) {
if (!g_initialized) return;
g_hwLock.Acquire();
if (g_kind == Kind::MiniUart) {
uint32_t cntl = MU_CNTL_RXEN | MU_CNTL_TXEN | (on ? MU_CNTL_AUTORTS : 0);
Write32(MU_CNTL, cntl);
// With auto RTS off, hold RTS asserted by hand.
Write32(MU_MCR, MU_MCR_RTS);
} else {
uint32_t cr = Read32(PL_CR) & ~(PL_CR_RTSEN | PL_CR_CTSEN);
if (on) cr |= PL_CR_RTSEN | PL_CR_CTSEN;
Write32(PL_CR, cr | PL_CR_RTS);
}
g_flowControl = on;
g_hwLock.Release();
}
int Read(uint8_t* buffer, int maxLength) {
if (buffer == nullptr || maxLength <= 0) return 0;
uint32_t tail = g_rxTail.load(std::memory_order_relaxed);
uint32_t head = g_rxHead.load(std::memory_order_acquire);
uint32_t available = head - tail;
uint32_t n = available < (uint32_t)maxLength ? available : (uint32_t)maxLength;
for (uint32_t i = 0; i < n; i++) {
buffer[i] = g_rxRing[(tail + i) & (RX_RING_SIZE - 1)];
}
g_rxTail.store(tail + n, std::memory_order_release);
return (int)n;
}
bool HasRxData() {
if (!g_initialized) return false;
if (g_rxHead.load(std::memory_order_acquire) != g_rxTail.load(std::memory_order_relaxed))
return true;
// Polled: nothing fills the ring until Service() runs, so look at
// the FIFO itself.
if (g_interrupt) return false;
return g_kind == Kind::MiniUart ? (Read32(MU_LSR) & MU_LSR_DATA_READY) != 0
: (Read32(PL_FR) & PL_FR_RXFE) == 0;
}
void FlushRx() {
if (!g_initialized) return;
g_hwLock.Acquire();
while (DrainRxLocked()) {}
g_rxTail.store(g_rxHead.load(std::memory_order_relaxed), std::memory_order_release);
g_hwLock.Release();
}
bool Write(const uint8_t* head, uint32_t headLength,
const uint8_t* body, uint32_t bodyLength) {
if (body == nullptr) bodyLength = 0;
uint32_t length = headLength + bodyLength;
if (!g_initialized || length == 0) return length == 0;
if (length > TX_RING_SIZE) return false;
g_hwLock.Acquire();
if (TX_RING_SIZE - (g_txHead - g_txTail) < length) {
g_hwLock.Release();
return false;
}
for (uint32_t i = 0; i < headLength; i++) {
g_txRing[(g_txHead + i) & (TX_RING_SIZE - 1)] = head[i];
}
for (uint32_t i = 0; i < bodyLength; i++) {
g_txRing[(g_txHead + headLength + i) & (TX_RING_SIZE - 1)] = body[i];
}
g_txHead += length;
FillTxLocked();
g_hwLock.Release();
return true;
}
uint32_t TxFree() {
return TX_RING_SIZE - g_txCount.load(std::memory_order_acquire);
}
uint32_t TxPending() {
return g_txCount.load(std::memory_order_acquire);
}
bool WaitTxIdle(uint32_t timeoutMs) {
if (!g_initialized) return false;
uint64_t start = Timekeeping::GetMilliseconds();
for (;;) {
g_hwLock.Acquire();
FillTxLocked();
bool idle = TransmitterIdleLocked();
g_hwLock.Release();
if (idle) return true;
if (Timekeeping::GetMilliseconds() - start > timeoutMs) return false;
Arch::Pause();
}
}
void Service() {
if (!g_initialized) return;
g_hwLock.Acquire();
DrainRxLocked();
FillTxLocked();
g_hwLock.Release();
}
void GetStats(Stats& out) {
g_hwLock.Acquire();
out = g_stats;
g_hwLock.Release();
}
}
+91
View File
@@ -0,0 +1,91 @@
/*
* BtUart.hpp
* Interrupt-driven UART to an on-board Bluetooth controller
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
// A byte pipe to a peripheral wired to one of the Broadcom SoC's UARTs --
// on the Raspberry Pi 4/400 the Bluetooth half of the CYW4345x radio.
// Unlike the console (Uart.cpp: polled, transmit only) this one receives,
// so it is interrupt driven: the hard IRQ moves bytes between the FIFOs and
// two software rings, and everything else runs in process context.
//
// Two UARTs can carry it:
// - the mini UART (BCM2835 AUX, 8-byte FIFOs, baud from the VPU core
// clock), which is where Bluetooth sits with the miniuart-bt overlay
// the Pi firmware task force's UEFI ships;
// - the PL011 (32-byte FIFOs, fixed 48 MHz reference clock).
// Pin routing (GPIO 30-33) is the caller's job.
namespace Drivers::Serial::BtUart {
enum class Kind : uint8_t {
MiniUart,
Pl011,
};
struct Config {
Kind kind;
uint64_t mmioPhys; // the block ACPI describes (mini UART: the AUX block)
uint32_t clockHz; // UART reference clock
uint32_t gsi; // 0 = no interrupt, poll from Service()
bool levelTriggered;
bool activeLow;
};
// Program the UART to 115200 8N1 with RTS asserted and start receiving.
bool Initialize(const Config& config);
bool IsInitialized();
// Whether the receive path is interrupt driven. Without it, bytes only
// move when Service() runs, so a caller should keep the baud rate low.
bool HasInterrupt();
// Change the baud rate. Waits for queued output to drain first, since
// the peer is about to switch too. Returns false if the rate is
// unreachable from the reference clock (more than 2.5% off).
bool SetBaud(uint32_t baud);
bool BaudReachable(uint32_t baud);
uint32_t GetBaud();
Kind GetKind();
// Hardware RTS: deassert RTS while the receive FIFO is nearly full so
// the peer pauses instead of overrunning it. On the PL011 this also
// enables CTS (the peer's RTS gating our transmitter).
void SetFlowControl(bool on);
bool FlowControl();
// Receive ring (single consumer). Read returns the number of bytes copied.
int Read(uint8_t* buffer, int maxLength);
bool HasRxData();
void FlushRx();
// Queue bytes for transmission: all of them, or none (false) when the
// transmit ring lacks room. Safe from several producers at once; each
// call's bytes (head, then body) stay contiguous on the wire.
bool Write(const uint8_t* head, uint32_t headLength,
const uint8_t* body = nullptr, uint32_t bodyLength = 0);
uint32_t TxFree();
uint32_t TxPending();
// Wait until everything queued has left the transmitter.
bool WaitTxIdle(uint32_t timeoutMs);
// Move bytes between the FIFOs and the rings from process context: the
// whole receive path when there is no interrupt, and a safety net when
// there is one.
void Service();
struct Stats {
uint64_t rxBytes;
uint64_t txBytes;
uint64_t rxDropped; // receive ring full
uint64_t overruns; // receive FIFO overflowed in hardware
uint64_t interrupts;
};
void GetStats(Stats& out);
}
+6
View File
@@ -64,6 +64,7 @@ namespace Drivers::Serial {
static volatile uint8_t* g_mmio = nullptr; static volatile uint8_t* g_mmio = nullptr;
static uint16_t g_port = 0; static uint16_t g_port = 0;
static uint32_t g_stride = 1; static uint32_t g_stride = 1;
static uint64_t g_consoleAddress = 0;
static inline uint32_t Read32(uint64_t offset) { static inline uint32_t Read32(uint64_t offset) {
return *(volatile uint32_t*)(g_mmio + offset); return *(volatile uint32_t*)(g_mmio + offset);
@@ -173,9 +174,14 @@ namespace Drivers::Serial {
return; return;
} }
g_consoleAddress = reg.Address;
AttachSerialConsole(sink); AttachSerialConsole(sink);
KernelLogStream(OK, "Serial") << "Console on " << name << " at " << (isIo ? "port " : "") KernelLogStream(OK, "Serial") << "Console on " << name << " at " << (isIo ? "port " : "")
<< "0x" << base::hex << reg.Address; << "0x" << base::hex << reg.Address;
} }
uint64_t ConsoleAddress() {
return g_consoleAddress;
}
} }
+4
View File
@@ -15,4 +15,8 @@ namespace Drivers::Serial {
// tables (the registers are mapped uncached through the HHDM). // tables (the registers are mapped uncached through the HHDM).
void InitializeConsole(Hal::ACPI::CommonSDTHeader* xsdt); void InitializeConsole(Hal::ACPI::CommonSDTHeader* xsdt);
// Physical address of the console UART's registers, or 0 when there is
// none (so another driver does not take over the console's UART).
uint64_t ConsoleAddress();
} }
+1 -1
View File
@@ -11,7 +11,7 @@
#include "HidMouse.hpp" #include "HidMouse.hpp"
#include "MassStorage.hpp" #include "MassStorage.hpp"
#include "Hub.hpp" #include "Hub.hpp"
#include "Bluetooth/Bluetooth.hpp" #include <Drivers/Bluetooth/Bluetooth.hpp>
#include <Terminal/Terminal.hpp> #include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp> #include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp> #include <Memory/HHDM.hpp>
+9 -5
View File
@@ -19,7 +19,8 @@
#include <Drivers/Net/Wifi/Wifi.hpp> #include <Drivers/Net/Wifi/Wifi.hpp>
#include <Drivers/Audio/IntelHda.hpp> #include <Drivers/Audio/IntelHda.hpp>
#include <Drivers/USB/Xhci.hpp> #include <Drivers/USB/Xhci.hpp>
#include <Drivers/USB/Bluetooth/Bluetooth.hpp> #include <Drivers/Bluetooth/Bluetooth.hpp>
#include <Drivers/Serial/BtUart.hpp>
#include <Drivers/USB/HidKeyboard.hpp> #include <Drivers/USB/HidKeyboard.hpp>
using namespace Kt; using namespace Kt;
@@ -58,7 +59,10 @@ namespace Timekeeping {
// desktop may never enter idle. Force one bounded idle-context // desktop may never enter idle. Force one bounded idle-context
// pass for USB immediately, and for network RX at most every // pass for USB immediately, and for network RX at most every
// 10 ms (important for E1000E's polling fallback). // 10 ms (important for E1000E's polling fallback).
bool usbWork = Drivers::USB::Xhci::HasDeferredWork(); // UART Bluetooth (Raspberry Pi) has no xHCI behind it: its
// received bytes wait in the UART ring for the same pass.
bool usbWork = Drivers::USB::Xhci::HasDeferredWork() ||
Drivers::Serial::BtUart::HasRxData();
bool audioWork = Drivers::Audio::IntelHda::HasDeferredWork(); bool audioWork = Drivers::Audio::IntelHda::HasDeferredWork();
bool netWork = Drivers::Net::E1000::HasDeferredWork() || bool netWork = Drivers::Net::E1000::HasDeferredWork() ||
Drivers::Net::E1000E::HasDeferredWork(); Drivers::Net::E1000E::HasDeferredWork();
@@ -185,9 +189,9 @@ namespace Timekeeping {
// Cheap no-op unless an adapter is waiting; self-claiming, and safe // Cheap no-op unless an adapter is waiting; self-claiming, and safe
// to preempt (the scheduler saves/resumes the idle context). // to preempt (the scheduler saves/resumes the idle context).
bool bluetoothWork = usbWork || bool bluetoothWork = usbWork ||
Drivers::USB::Bluetooth::HasDeferredWork(); Drivers::Bluetooth::HasDeferredWork();
if (bluetoothWork) if (bluetoothWork)
Drivers::USB::Bluetooth::ServiceDeferredInit(); Drivers::Bluetooth::ServiceDeferredInit();
// Service Bluetooth inbound traffic (the headset's SDP/AVRCP queries, // Service Bluetooth inbound traffic (the headset's SDP/AVRCP queries,
// AVDTP commands, ACL flow-control credits) whenever a core idles. // AVDTP commands, ACL flow-control credits) whenever a core idles.
@@ -197,7 +201,7 @@ namespace Timekeeping {
// queries never answered). Self-serializing and a cheap no-op when // queries never answered). Self-serializing and a cheap no-op when
// the adapter is down. // the adapter is down.
if (bluetoothWork) if (bluetoothWork)
Drivers::USB::Bluetooth::ServiceEvents(); Drivers::Bluetooth::ServiceEvents();
// Wi-Fi mirrors the Bluetooth split: the firmware load needs the // Wi-Fi mirrors the Bluetooth split: the firmware load needs the
// ramdisk, and the RX/notification ring must be drained outside hard // ramdisk, and the RX/notification ring must be drained outside hard
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,139 @@
### CYPRESS WIRELESS CONNECTIVITY DEVICES
### DRIVER END USER LICENSE AGREEMENT (SOURCE AND BINARY DISTRIBUTION)
PLEASE READ THIS END USER LICENSE AGREEMENT ("Agreement") CAREFULLY BEFORE
DOWNLOADING, INSTALLING, OR USING THIS SOFTWARE, ANY ACCOMPANYING
DOCUMENTATION, OR ANY UPDATES PROVIDED BY CYPRESS ("Software"). BY
DOWNLOADING, INSTALLING, OR USING THE SOFTWARE, YOU ARE AGREEING TO BE BOUND
BY THIS AGREEMENT. IF YOU DO NOT AGREE TO ALL OF THE TERMS OF THIS
AGREEMENT, PROMPTLY RETURN AND DO NOT USE THE SOFTWARE. IF YOU HAVE
PURCHASED THE SOFTWARE, YOUR RIGHT TO RETURN THE SOFTWARE EXPIRES 30 DAYS
AFTER YOUR PURCHASE AND APPLIES ONLY TO THE ORIGINAL PURCHASER.
Software Provided in Binary Code Form. This paragraph applies to any Software
provided in binary code form. Subject to the terms and conditions of this
Agreement, Cypress Semiconductor Corporation ("Cypress") grants you a
non-exclusive, non-transferable license under its copyright rights in the
Software to reproduce and distribute the Software in object code form only,
solely for use in connection with Cypress integrated circuit products
("Purpose").
Software Provided in Source Code Form. This paragraph applies to any Software
provided in source code form ("Cypress Source Code"). Subject to the terms and
conditions of this Agreement, Cypress grants you a non-exclusive,
non-transferable license under its copyright rights in the Cypress Source Code
to reproduce, modify, compile, and distribute the Cypress Source Code (whether
in source code form or as compiled into binary code form) solely for the
Purpose. Cypress retains ownership of the Cypress Source Code and any compiled
version thereof. Subject to Cypress' ownership of the underlying Cypress
Source Code, you retain ownership of any modifications you make to the
Cypress Source Code. You agree not to remove any Cypress copyright or other
notices from the Cypress Source Code and any modifications thereof. Any
reproduction, modification, translation, compilation, or representation of
the Cypress Source Code except as permitted in this paragraph is prohibited
without the express written permission of Cypress.
Free and Open Source Software. Portions of the Software may be licensed under
free and/or open source licenses such as the GNU General Public License
("FOSS"). FOSS is subject to the applicable license agreement and not this
Agreement. If you are entitled to receive the source code from Cypress for any
FOSS included with the Software, either the source code will be included with
the Software or you may obtain the source code at no charge from
<http://www.cypress.com/go/opensource>. The applicable license terms will
accompany each source code package. To review the license terms applicable to
any FOSS for which Cypress is not required to provide you with source code,
please see the Software's installation directory on your computer.
Proprietary Rights. The Software, including all intellectual property rights
therein, is and will remain the sole and exclusive property of Cypress or its
suppliers. Except as otherwise expressly provided in this Agreement, you may
not: (i) modify, adapt, or create derivative works based upon the Software;
(ii) copy the Software; (iii) except and only to the extent explicitly
permitted by applicable law despite this limitation, decompile, translate,
reverse engineer, disassemble or otherwise reduce the Software to
human-readable form; or (iv) use the Software other than for the Purpose.
No Support. Cypress may, but is not required to, provide technical support for
the Software.
Term and Termination. This Agreement is effective until terminated. This
Agreement and Your license rights will terminate immediately without notice
from Cypress if you fail to comply with any provision of this Agreement. Upon
termination, you must destroy all copies of Software in your possession or
control. Termination of this Agreement will not affect any licenses validly
granted as of the termination date to any end users of the Software. The
following paragraphs shall survive any termination of this Agreement: "Free and
Open Source Software," "Proprietary Rights," "Compliance With Law,"
"Disclaimer," "Limitation of Liability," and "General."
Compliance With Law. Each party agrees to comply with all applicable laws,
rules and regulations in connection with its activities under this Agreement.
Without limiting the foregoing, the Software may be subject to export control
laws and regulations of the United States and other countries. You agree to
comply strictly with all such laws and regulations and acknowledge that you
have the responsibility to obtain licenses to export, re-export, or import
the Software.
Disclaimer. TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, CYPRESS MAKES
NO WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, WITH REGARD TO THE SOFTWARE,
INCLUDING, BUT NOT LIMITED TO, INFRINGEMENT AND THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. Cypress reserves the
right to make changes to the Software without notice. Cypress does not assume
any liability arising out of the application or use of Software or any
product or circuit described in the Software. Cypress does not authorize its
products for use as critical components in life-support systems where a
malfunction or failure may reasonably be expected to result in significant
injury to the user. The inclusion of Cypress' product in a life-support
system or application implies that the manufacturer of such system or
application assumes all risk of such use and in doing so indemnifies Cypress
against all charges.
Limitation of Liability. IN NO EVENT WILL CYPRESS OR ITS SUPPLIERS,
RESELLERS, OR DISTRIBUTORS BE LIABLE FOR ANY LOST REVENUE, PROFIT, OR DATA,
OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL, OR PUNITIVE DAMAGES
HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE
USE OF OR INABILITY TO USE THE SOFTWARE EVEN IF CYPRESS OR ITS SUPPLIERS,
RESELLERS, OR DISTRIBUTORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES. IN NO EVENT SHALL CYPRESS' OR ITS SUPPLIERS' RESELLERS', OR
DISTRIBUTORS' TOTAL LIABILITY TO YOU, WHETHER IN CONTRACT, TORT (INCLUDING
NEGLIGENCE), OR OTHERWISE, EXCEED THE PRICE PAID BY YOU FOR THE SOFTWARE.
THE FOREGOING LIMITATIONS SHALL APPLY EVEN IF THE ABOVE-STATED WARRANTY FAILS
OF ITS ESSENTIAL PURPOSE. BECAUSE SOME STATES OR JURISDICTIONS DO NOT ALLOW
LIMITATION OR EXCLUSION OF CONSEQUENTIAL OR INCIDENTAL DAMAGES, THE ABOVE
LIMITATION MAY NOT APPLY TO YOU.
Restricted Rights. The Software under this Agreement is commercial computer
software as that term is described in 48 C.F.R. 252.227-7014(a)(1). If
acquired by or on behalf of a civilian agency, the U.S. Government acquires
this commercial computer software and/or commercial computer software
documentation subject to the terms of this Agreement as specified in 48
C.F.R. 12.212 (Computer Software) and 12.211 (Technical Data) of the Federal
Acquisition Regulations ("FAR") and its successors. If acquired by or on
behalf of any agency within the Department of Defense ("DOD"), the U.S.
Government acquires this commercial computer software and/or commercial
computer software documentation subject to the terms of this Agreement as
specified in 48 C.F.R. 227.7202-3 of the DOD FAR Supplement ("DFAR") and its
successors.
General. This Agreement will bind and inure to the benefit of each party's
successors and assigns, provided that you may not assign or transfer this
Agreement, in whole or in part, without Cypress' written consent. This
Agreement shall be governed by and construed in accordance with the laws of
the State of California, United States of America, as if performed wholly
within the state and without giving effect to the principles of conflict of
law. The parties consent to personal and exclusive jurisdiction of and venue
in, the state and federal courts within Santa Clara County, California;
provided however, that nothing in this Agreement will limit Cypress' right to
bring legal action in any venue in order to protect or enforce its
intellectual property rights. No failure of either party to exercise or
enforce any of its rights under this Agreement will act as a waiver of such
rights. If any portion hereof is found to be void or unenforceable, the
remaining provisions of this Agreement shall remain in full force and
effect. This Agreement is the complete and exclusive agreement between the
parties with respect to the subject matter hereof, superseding and replacing
any and all prior agreements, communications, and understandings (both
written and oral) regarding such subject matter. Any notice to Cypress will
be deemed effective when actually received and must be sent to Cypress
Semiconductor Corporation, ATTN: Chief Legal Officer, 198 Champion Court, San
Jose, CA 95134 USA.
+20
View File
@@ -655,6 +655,26 @@ License: zlib License
Jean-loup Gailly Mark Adler Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu jloup@gzip.org madler@alumni.caltech.edu
================================================================================
23. Cypress (Broadcom) Bluetooth firmware
================================================================================
Shipped as binary firmware patch files (0:/os/firmware/brcm/BCM4345C0.hcd and
BCM4345C5.hcd), loaded at runtime into the CYW43455 / CYW43456 Bluetooth
controller of the Raspberry Pi 4 and 400 by the MontaukOS Bluetooth driver.
The files are taken unmodified from Raspberry Pi OS's bluez-firmware package.
Copyright (c) Cypress Semiconductor Corporation (an Infineon company)
Upstream: https://github.com/RPi-Distro/bluez-firmware
(debian/firmware/broadcom/)
License: Cypress Wireless Connectivity Devices Driver End User License
Agreement (source and binary distribution)
Reproduction and distribution in object code form are permitted solely for
use with Cypress integrated circuit products. The full license text is
included at 0:/os/licenses/Cypress-bt-firmware.txt.
================================================================================ ================================================================================
For licensing inquiries regarding MontaukOS itself, contact: licensing@montaukos.org For licensing inquiries regarding MontaukOS itself, contact: licensing@montaukos.org
================================================================================ ================================================================================
+9 -1
View File
@@ -165,10 +165,18 @@ IMG="$WORK/esp.fat"
dd if=/dev/zero of="$IMG" bs=1M count=0 seek=$esp_mib status=none dd if=/dev/zero of="$IMG" bs=1M count=0 seek=$esp_mib status=none
mformat -i "$IMG" -F -v MONTAUK :: mformat -i "$IMG" -F -v MONTAUK ::
# Firmware: everything in the release except its readme. # Firmware: everything in the release except its readme. Its config.txt
# gets the VPU core clock pinned: the miniuart-bt overlay leaves Bluetooth on
# the mini UART, whose baud rate is derived from that clock, and the
# overlay's documentation requires core_freq and core_freq_min to match or
# the mini UART breaks when the firmware scales the clock (500 MHz is the
# Pi 4/400 default).
for f in "$FW_DIR"/*; do for f in "$FW_DIR"/*; do
case "$(basename "$f")" in case "$(basename "$f")" in
Readme.md) ;; Readme.md) ;;
config.txt)
{ cat "$f"; printf '\ncore_freq=500\ncore_freq_min=500\n'; } > "$WORK/config.txt"
mcopy -i "$IMG" "$WORK/config.txt" :: ;;
*) mcopy -s -i "$IMG" "$f" :: ;; *) mcopy -s -i "$IMG" "$f" :: ;;
esac esac
done done