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