From 44739bf35df120cdf88bcbdf85d9bc7c6ef2d13c Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Mon, 26 Oct 2026 07:46:17 +0100 Subject: [PATCH] fix: Pi 400 Bluetooth - hold RTS deasserted across BT_ON power-on, require Read Local Version to sync, mini UART no flow control first --- kernel/src/Api/BuildNo.hpp | 2 +- kernel/src/Arch/aarch64/Paging.cpp | 8 ++ kernel/src/Arch/aarch64/PanicFrame.cpp | 8 ++ kernel/src/Drivers/Bluetooth/A2dp.cpp | 31 ++++++ kernel/src/Drivers/Bluetooth/Broadcom.cpp | 106 ++++++++++++++++---- kernel/src/Drivers/Bluetooth/Hci.cpp | 4 + kernel/src/Drivers/Bluetooth/Hci.hpp | 5 + kernel/src/Drivers/Platform/RpiFirmware.cpp | 43 ++++++-- kernel/src/Drivers/Platform/RpiFirmware.hpp | 2 + kernel/src/Drivers/Serial/BtUart.cpp | 28 +++++- kernel/src/Drivers/Serial/BtUart.hpp | 11 +- 11 files changed, 210 insertions(+), 38 deletions(-) diff --git a/kernel/src/Api/BuildNo.hpp b/kernel/src/Api/BuildNo.hpp index 8f90946..5b504e9 100644 --- a/kernel/src/Api/BuildNo.hpp +++ b/kernel/src/Api/BuildNo.hpp @@ -12,4 +12,4 @@ #pragma once -#define MONTAUK_BUILD_NUMBER 86 +#define MONTAUK_BUILD_NUMBER 99 diff --git a/kernel/src/Arch/aarch64/Paging.cpp b/kernel/src/Arch/aarch64/Paging.cpp index cf13362..e64fe5e 100644 --- a/kernel/src/Arch/aarch64/Paging.cpp +++ b/kernel/src/Arch/aarch64/Paging.cpp @@ -330,6 +330,14 @@ namespace Memory::VMM { } } + // The bootloader wrote the image through the data cache. Push it to + // the point of unification and drop every instruction-cache line + // before the kernel relies on fetching code it has not run yet: a + // line still dirty in L1D is invisible to instruction fetch, which + // then reads stale DRAM (an "unknown reason" exception on a valid + // instruction). + Arch::SyncInstructionCache((const void*)kernelBaseVirt, kernelSize); + Arch::ActivateKernelTranslation(); Kt::KernelLogStream(Kt::OK, "VMM") << "Switched to kernel translation tables (HHDM limit 0x" diff --git a/kernel/src/Arch/aarch64/PanicFrame.cpp b/kernel/src/Arch/aarch64/PanicFrame.cpp index 7b19ca9..d542fb7 100644 --- a/kernel/src/Arch/aarch64/PanicFrame.cpp +++ b/kernel/src/Arch/aarch64/PanicFrame.cpp @@ -24,5 +24,13 @@ namespace Arch { PrintBoxedHex(kerr, "PSTATE (SPSR)", frame->spsr, BoxWidth); PrintBoxedHex(kerr, "Stack Pointer", frame->sp, BoxWidth); PrintBoxedHex(kerr, "Link Register (x30)", frame->x[30], BoxWidth); + + // An "unknown reason" exception on a kernel PC: show the word the + // data side sees there. A valid instruction means instruction fetch + // saw something else (cache maintenance); garbage means the code + // itself was overwritten. The fetch succeeded, so the page is mapped. + if ((frame->esr >> 26) == 0 && frame->elr >= 0xFFFF000000000000ULL && (frame->elr & 3) == 0) { + PrintBoxedHex(kerr, "Instruction at PC", *(volatile uint32_t*)frame->elr, BoxWidth); + } } } diff --git a/kernel/src/Drivers/Bluetooth/A2dp.cpp b/kernel/src/Drivers/Bluetooth/A2dp.cpp index 6a114e9..64f2670 100644 --- a/kernel/src/Drivers/Bluetooth/A2dp.cpp +++ b/kernel/src/Drivers/Bluetooth/A2dp.cpp @@ -1837,6 +1837,36 @@ namespace Drivers::Bluetooth::A2dp { // ConfigureStream // ========================================================================= + // A UART transport has a hard byte budget: at 460800 baud it carries + // 46 KB/s, and SBC at bitpool 53 / 48 kHz needs ~46 KB/s with its + // headers, so the stream fell behind whenever anything else crossed the + // link and the sink played concealment. Keep the stream within three + // quarters of the line rate by lowering the bitpool (any value in the + // negotiated range is valid per frame). Over USB nothing changes. + static void FitBitpoolToTransport(uint32_t sampleRate) { + uint32_t budget = Hci::TransportBytesPerSecond() / 4 * 3; + if (budget == 0) return; + uint32_t rate = sampleRate ? sampleRate : 48000; + uint8_t minBitpool = g_cfgSbc[2] < 2 ? 2 : g_cfgSbc[2]; + uint8_t original = g_sbcEncoder.Bitpool; + auto bytesPerSecond = [&]() { + // H4 + ACL + L2CAP + RTP + SCMS-T + SBC headers (23 bytes) per + // packet, spread over the ~4-5 frames bundled into each. + uint32_t frameCost = Sbc::GetFrameSize(&g_sbcEncoder) + 6; + return (uint64_t)frameCost * rate / Sbc::GetSamplesPerFrame(&g_sbcEncoder); + }; + while (g_sbcEncoder.Bitpool > minBitpool && bytesPerSecond() > budget) { + Sbc::Configure(&g_sbcEncoder, g_cfgSbc[0], g_cfgSbc[1], + (uint8_t)(g_sbcEncoder.Bitpool - 1)); + } + if (g_sbcEncoder.Bitpool != original) { + KernelLogStream(INFO, "BT-A2DP") << "Bitpool " << (uint64_t)original << " -> " + << (uint64_t)g_sbcEncoder.Bitpool << " to fit the transport (" + << bytesPerSecond() << " of " << (uint64_t)Hci::TransportBytesPerSecond() + << " bytes/s)"; + } + } + bool ConfigureStream(uint32_t sampleRate, uint8_t channels, uint8_t bitsPerSample) { // Encoder configuration and PumpMedia both mutate the SBC encoder. // Device switching normally configures an Open stream, but explicitly @@ -1851,6 +1881,7 @@ namespace Drivers::Bluetooth::A2dp { // agreed to (Init's defaults may differ from the negotiated subset). if (g_cfgSbc[0] || g_cfgSbc[1]) { Sbc::Configure(&g_sbcEncoder, g_cfgSbc[0], g_cfgSbc[1], g_cfgSbc[3]); + FitBitpoolToTransport(sampleRate); } g_sbcInitialized = true; g_seqNum = 0; diff --git a/kernel/src/Drivers/Bluetooth/Broadcom.cpp b/kernel/src/Drivers/Bluetooth/Broadcom.cpp index 0f779d2..d4e25fa 100644 --- a/kernel/src/Drivers/Bluetooth/Broadcom.cpp +++ b/kernel/src/Drivers/Bluetooth/Broadcom.cpp @@ -38,9 +38,15 @@ // 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. +// control on the mini UART and at 3 Mbaud with it on the PL011. 460800 is +// too slow for A2DP here: 8N1 carries 46 KB/s and SBC at bitpool 53 needs +// ~46 KB/s with its headers, so music broke up whenever anything else used +// the link. The mini UART's auto RTS/CTS allows 921600, so it is tried +// first; without flow control (460800) is the fallback, and A2DP lowers its +// bitpool to fit (Hci::TransportBytesPerSecond). +// +// Our RTS must be deasserted while the controller powers on: with it +// asserted the controller boots into autobaud mode (see PowerCycle). using namespace Kt; @@ -189,14 +195,19 @@ namespace Drivers::Bluetooth::Broadcom { // did not). The PL011's reference clock is fixed (CLCK, 48 MHz). uint64_t tableClock = 0; uint32_t clockHz = 0; + const char* clockSource = "firmware"; if (mini) { if (!RpiFirmware::GetClockRate(RpiFirmware::CLOCK_CORE, clockHz)) { - if (ReadInteger(uart.NodeIndex, "MUCR", tableClock) && tableClock > 0x1000000) + if (ReadInteger(uart.NodeIndex, "MUCR", tableClock) && tableClock > 0x1000000) { clockHz = (uint32_t)tableClock; - else + clockSource = "ACPI MUCR"; + } else { clockHz = 500000000; + clockSource = "assumed"; + } } } else { + clockSource = "PL011 reference"; clockHz = ReadInteger(uart.NodeIndex, "CLCK", tableClock) && tableClock != 0 ? (uint32_t)tableClock : 48000000; } @@ -221,7 +232,7 @@ namespace Drivers::Bluetooth::Broadcom { Hci::InitializeUart(); Hci::SetVendor(Hci::Vendor::Broadcom); KernelLogStream(OK, "BT-BCM") << "Bluetooth controller " << bt.Path << " on " - << uart.Path << " (GSI " << (uint64_t)config.gsi << ")"; + << uart.Path << " (GSI " << (uint64_t)config.gsi << ", clock " << clockSource << ")"; return true; } @@ -229,11 +240,31 @@ namespace Drivers::Bluetooth::Broadcom { // Controller bring-up // ========================================================================= + // BT_ON through the firmware's expander. The line has to be an output + // first (hci_bcm's GPIOD_OUT_LOW request does that on Linux); plain + // SET_GPIO_STATE is the fallback for firmware without the config tag. + static bool SetBtOn(bool on) { + return RpiFirmware::SetGpioOutput(RpiFirmware::EXPGPIO_BT_ON, on) + || RpiFirmware::SetGpioState(RpiFirmware::EXPGPIO_BT_ON, on); + } + + // The controller samples its CTS (our RTS) as it leaves reset. Asserted, + // it boots into autobaud mode, where it answers HCI Reset and silently + // drops nearly everything else (on a Pi 400: Read Local Version, Read + // PIN Type, Write PIN Type, at any baud rate). hci_bcm deasserts it + // around power-on for the same reason; SyncAtInitialBaud asserts it + // again once the controller is up. static void PowerCycle() { if (g_powerSwitch) { - if (RpiFirmware::SetGpioState(RpiFirmware::EXPGPIO_BT_ON, false)) { + BtUart::DeassertRts(); + if (SetBtOn(false)) { Timekeeping::BusyWaitMs(20); - RpiFirmware::SetGpioState(RpiFirmware::EXPGPIO_BT_ON, true); + bool on = SetBtOn(true); + bool state = false; + bool readBack = RpiFirmware::GetGpioState(RpiFirmware::EXPGPIO_BT_ON, state); + KernelLogStream(on ? INFO : WARNING, "BT-BCM") << "BT_ON " + << (on ? "switched on" : "did not switch on") << " (reads back " + << (readBack ? (state ? "high" : "low") : "unknown") << ")"; // hci_bcm allows 100-120 ms to power on and leave reset. Timekeeping::BusyWaitMs(150); } else { @@ -245,25 +276,58 @@ namespace Drivers::Bluetooth::Broadcom { 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); + // Whether received bytes move by interrupt and whether any were lost: + // the first thing to know when the controller seems not to answer. + static void LogUartStats(const char* when) { + BtUart::Stats stats{}; + BtUart::GetStats(stats); + KernelLogStream(INFO, "BT-BCM") << when << ": UART rx " << stats.rxBytes << " bytes, tx " + << stats.txBytes << ", " << stats.interrupts << " interrupts, " << stats.overruns + << " overruns, " << stats.rxDropped << " dropped, " << (uint64_t)BtUart::GetBaud() + << " baud, " << (BtUart::FlowControl() ? "RTS flow control" : "no flow control"); } - // 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() { + static bool ResetController(uint32_t timeoutMs) { + if (!Hci::SendCommand(Hci::OP_RESET, nullptr, 0)) return false; + if (!Hci::WaitCommandComplete(Hci::OP_RESET, nullptr, 0, timeoutMs)) return false; + // The controller acknowledges the reset before it has finished it + // and ignores commands until then; btbcm_reset waits 100 ms. + Timekeeping::BusyWaitMs(100); + return true; + } + + // Reach the controller at its power-on 115200 baud and read its ROM + // version. A flow-control mode counts only once a second command has + // been answered too: a controller in autobaud mode answers HCI Reset + // alone, and a flow-control polarity that holds either side back fails + // here rather than later. + static bool SyncAtInitialBaud(Hci::LocalVersion& rom) { BtUart::SetBaud(InitialBaud); static constexpr bool attempts[] = { true, true, false, false }; for (bool flow : attempts) { BtUart::SetFlowControl(flow); + // Events the controller held back while it saw RTS deasserted + // pour out once it is asserted: let them arrive and drop them, + // so they neither overrun the FIFO under the next reply nor + // leave the deframer mid-packet. + Timekeeping::BusyWaitMs(50); Hci::ResetUartReceiver(); - if (ResetController(1000)) { + if (!ResetController(1000)) { + LogUartStats(flow ? "No reset reply with flow control" : "No reset reply"); + continue; + } + // A controller slow to finish its reset gets a second chance. + bool answered = Hci::ReadLocalVersion(&rom); + if (!answered) { + Timekeeping::BusyWaitMs(200); + answered = Hci::ReadLocalVersion(&rom); + } + if (answered) { g_flowControl = flow; return true; } + LogUartStats(flow ? "Reset answered but not Read Local Version, with flow control" + : "Reset answered but not Read Local Version"); } return false; } @@ -402,14 +466,12 @@ namespace Drivers::Bluetooth::Broadcom { // 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" + Hci::LocalVersion rom{}; + if (!SyncAtInitialBaud(rom)) { + KernelLogStream(ERROR, "BT-BCM") << "Controller does not answer HCI commands" << (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 diff --git a/kernel/src/Drivers/Bluetooth/Hci.cpp b/kernel/src/Drivers/Bluetooth/Hci.cpp index 437c2ed..2ca93c7 100644 --- a/kernel/src/Drivers/Bluetooth/Hci.cpp +++ b/kernel/src/Drivers/Bluetooth/Hci.cpp @@ -695,6 +695,10 @@ namespace Drivers::Bluetooth::Hci { } Transport GetTransport() { return g_transport; } + + uint32_t TransportBytesPerSecond() { + return g_transport == Transport::Uart ? BtUart::GetBaud() / 10 : 0; + } void SetVendor(Vendor vendor) { g_vendor = vendor; } Vendor GetVendor() { return g_vendor; } diff --git a/kernel/src/Drivers/Bluetooth/Hci.hpp b/kernel/src/Drivers/Bluetooth/Hci.hpp index 09c7974..28282fa 100644 --- a/kernel/src/Drivers/Bluetooth/Hci.hpp +++ b/kernel/src/Drivers/Bluetooth/Hci.hpp @@ -199,6 +199,11 @@ namespace Drivers::Bluetooth::Hci { void InitializeUart(); Transport GetTransport(); + + // Bytes per second the host-to-controller link can carry: the UART's + // line rate (8N1), or 0 when the transport is not the bottleneck (USB). + uint32_t TransportBytesPerSecond(); + void SetVendor(Vendor vendor); Vendor GetVendor(); diff --git a/kernel/src/Drivers/Platform/RpiFirmware.cpp b/kernel/src/Drivers/Platform/RpiFirmware.cpp index 2cdca99..026b6fb 100644 --- a/kernel/src/Drivers/Platform/RpiFirmware.cpp +++ b/kernel/src/Drivers/Platform/RpiFirmware.cpp @@ -48,6 +48,8 @@ namespace Drivers::Platform::RpiFirmware { 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; + static constexpr uint32_t TAG_GET_GPIO_CONFIG = 0x00030043; + static constexpr uint32_t TAG_SET_GPIO_CONFIG = 0x00038043; // The VideoCore sees the first GiB of SDRAM at bus address 0xC0000000 // (the uncached alias), and nothing above it, so the message buffer @@ -199,18 +201,28 @@ namespace Drivers::Platform::RpiFirmware { g_buffer[words++] = 0; // end tag g_buffer[0] = words * 4; + // The firmware does not set the response bit for every tag: the + // expander GPIO tags answer through value[0] alone and leave the + // status word 0. Linux checks only the message code, so do the same + // and take the whole value buffer back when the bit is missing; each + // caller validates the values it gets. 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; - } + bool answered = Call(); + uint32_t status = g_buffer[valueStart - 1]; + if (answered) { + uint32_t length = (status & TAG_RESPONSE) ? status & ~TAG_RESPONSE : valueWords * 4; + 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; } + uint32_t code = g_buffer[1]; g_lock.Release(); + + if (result < 0) { + KernelLogStream(WARNING, "RpiFw") << "Property tag 0x" << base::hex << (uint64_t)tag + << " failed: message code 0x" << (uint64_t)code << base::dec; + } return result; } @@ -228,6 +240,19 @@ namespace Drivers::Platform::RpiFirmware { return PropertyCall(TAG_SET_GPIO_STATE, v, 2) >= 4 && v[0] == 0; } + // As Linux's rpi_exp_gpio_dir_out: keep the line's polarity, make it an + // output driving `high`. A line the firmware still has as an input + // refuses SET_GPIO_STATE. + bool SetGpioOutput(uint32_t gpio, bool high) { + uint32_t polarity = 0; + uint32_t cfg[5] = { gpio, 0, 0, 0, 0 }; // gpio, direction, polarity, term_en, term_pull_up + if (PropertyCall(TAG_GET_GPIO_CONFIG, cfg, 5) >= 12 && cfg[0] == 0) polarity = cfg[2]; + + uint32_t v[6] = { gpio, 1 /* output */, polarity, 0, 0, high ? 1u : 0u }; + if (PropertyCall(TAG_SET_GPIO_CONFIG, v, 6) < 4 || v[0] != 0) return false; + return true; + } + 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; diff --git a/kernel/src/Drivers/Platform/RpiFirmware.hpp b/kernel/src/Drivers/Platform/RpiFirmware.hpp index e88fa49..2043939 100644 --- a/kernel/src/Drivers/Platform/RpiFirmware.hpp +++ b/kernel/src/Drivers/Platform/RpiFirmware.hpp @@ -38,6 +38,8 @@ namespace Drivers::Platform::RpiFirmware { constexpr uint32_t EXPGPIO_WL_ON = EXPGPIO_BASE + 1; // Wi-Fi power (high = on) bool SetGpioState(uint32_t gpio, bool high); + // Configure the line as an output, then drive it (SET_GPIO_CONFIG). + bool SetGpioOutput(uint32_t gpio, bool high); bool GetGpioState(uint32_t gpio, bool& high); // The board's Ethernet MAC address from OTP, in transmission order diff --git a/kernel/src/Drivers/Serial/BtUart.cpp b/kernel/src/Drivers/Serial/BtUart.cpp index 6eba5a0..4142c4b 100644 --- a/kernel/src/Drivers/Serial/BtUart.cpp +++ b/kernel/src/Drivers/Serial/BtUart.cpp @@ -52,9 +52,16 @@ namespace Drivers::Serial::BtUart { 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). + // Auto RTS at the default level (deassert with 3 FIFO spaces left). + // Bit 6 picks the level auto RTS asserts: set = low, clear (the reset + // value) = high. A UART's RTS is active low, so it must be set: left + // clear, RTS reads "stop" to the controller the moment auto RTS is on. + // Bits 3 and 7 are the same for CTS: on a Pi 400, with bit 7 clear the + // transmitter held its bytes while the controller drove CTS low (ready). static constexpr uint32_t MU_CNTL_AUTORTS = 1u << 2; + static constexpr uint32_t MU_CNTL_AUTOCTS = 1u << 3; + static constexpr uint32_t MU_CNTL_RTS_LOW = 1u << 6; + static constexpr uint32_t MU_CNTL_CTS_LOW = 1u << 7; // PL011 (ARM PrimeCell UART TRM) static constexpr uint32_t PL_DR = 0x00; @@ -359,7 +366,9 @@ namespace Drivers::Serial::BtUart { 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); + uint32_t cntl = MU_CNTL_RXEN | MU_CNTL_TXEN + | (on ? MU_CNTL_AUTORTS | MU_CNTL_RTS_LOW + | MU_CNTL_AUTOCTS | MU_CNTL_CTS_LOW : 0); Write32(MU_CNTL, cntl); // With auto RTS off, hold RTS asserted by hand. Write32(MU_MCR, MU_MCR_RTS); @@ -372,6 +381,19 @@ namespace Drivers::Serial::BtUart { g_hwLock.Release(); } + void DeassertRts() { + if (!g_initialized) return; + g_hwLock.Acquire(); + if (g_kind == Kind::MiniUart) { + Write32(MU_CNTL, MU_CNTL_RXEN | MU_CNTL_TXEN); + Write32(MU_MCR, 0); + } else { + Write32(PL_CR, Read32(PL_CR) & ~(PL_CR_RTSEN | PL_CR_CTSEN | PL_CR_RTS)); + } + g_flowControl = false; + 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); diff --git a/kernel/src/Drivers/Serial/BtUart.hpp b/kernel/src/Drivers/Serial/BtUart.hpp index 2830c07..31c4b4b 100644 --- a/kernel/src/Drivers/Serial/BtUart.hpp +++ b/kernel/src/Drivers/Serial/BtUart.hpp @@ -52,12 +52,17 @@ namespace Drivers::Serial::BtUart { 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). + // Hardware flow control: deassert RTS while the receive FIFO is nearly + // full so the peer pauses instead of overrunning it, and hold our + // transmitter while the peer deasserts CTS. void SetFlowControl(bool on); bool FlowControl(); + // Drive RTS deasserted (and stop auto RTS) until the next + // SetFlowControl. The peer may sample it at power-on: a Broadcom + // controller that sees its CTS asserted then boots into autobaud mode. + void DeassertRts(); + // Receive ring (single consumer). Read returns the number of bytes copied. int Read(uint8_t* buffer, int maxLength); bool HasRxData();