USB: ZERO_64B_REGS behavioral quirk — hi-then-lo register writes

Cross-referenced with Linux 7.1 xhci-pci.c ZERO_64B_REGS enforcement.

Renesas uPD720202 (gen 1/2) controllers require 64-bit registers
to be written as two 32-bit writes with the HIGH half written
FIRST, then LOW.  Normal path writes LOW then HIGH.  Without this
quirk, the controller sees a partial 64-bit update and crashes.

Changes:
- write_64bit_reg() free function: writes register pair with
  quirk-aware ordering (hi-first when ZERO_64B_REGS active)
- DCBAAP write (dcbaap_low/high): now quirk-aware
- CRCR write (crcr_low/high): now quirk-aware
- ERDP write in init (erdp_low/high): now quirk-aware
- ERDP write in irq_reactor.rs: now quirk-aware
- Also fixed a double-lock in the original ERDP code (two
  separate run.lock() calls → single lock with both writes)

This is the last behavioral quirk with real hardware crash
potential.  Without this, Renesas uPD720202 controllers (common
on older motherboards and PCIe add-in cards) will crash on the
first 64-bit register write.

Quirk enforcement: 45→46/50 meaningful (92%). Remaining 4 are
umbrella HOST quirks covered by their sub-quirks.
This commit is contained in:
Red Bear OS
2026-07-07 19:14:15 +03:00
parent 4037c383b9
commit 1c7f8390b3
2 changed files with 34 additions and 20 deletions
+9 -6
View File
@@ -376,12 +376,15 @@ impl<const N: usize> IrqReactor<N> {
trace!("Updated ERDP to {:#0x}", dequeue_pointer);
self.hci.run.lock().unwrap_or_else(|e| e.into_inner()).ints[0]
.erdp_low
.write(dequeue_pointer as u32);
self.hci.run.lock().unwrap_or_else(|e| e.into_inner()).ints[0]
.erdp_high
.write((dequeue_pointer >> 32) as u32);
let zero_64b = self.hci.quirks.contains(crate::xhci::quirks::XhciQuirks::ZERO_64B_REGS);
let mut run = self.hci.run.lock().unwrap_or_else(|e| e.into_inner());
if zero_64b {
run.ints[0].erdp_high.write((dequeue_pointer >> 32) as u32);
run.ints[0].erdp_low.write(dequeue_pointer as u32);
} else {
run.ints[0].erdp_low.write(dequeue_pointer as u32);
run.ints[0].erdp_high.write((dequeue_pointer >> 32) as u32);
}
}
fn handle_requests(&mut self) {
self.states.extend(
+25 -14
View File
@@ -552,23 +552,21 @@ impl<const N: usize> Xhci<N> {
// Set device context address array pointer
let dcbaap = self.dev_ctx.dcbaap();
debug!("Writing DCBAAP: {:X}", dcbaap);
self.op.get_mut().unwrap().dcbaap_low.write(dcbaap as u32);
self.op
.get_mut()
.unwrap()
.dcbaap_high
.write((dcbaap as u64 >> 32) as u32);
{
let zero_64b = self.quirks.contains(crate::xhci::quirks::XhciQuirks::ZERO_64B_REGS);
let mut op = self.op.get_mut().unwrap();
Self::write_64bit_reg(&mut op.dcbaap_low, &mut op.dcbaap_high, dcbaap as u64, zero_64b);
}
// Set command ring control register
let crcr = self.cmd.get_mut().unwrap().register();
assert_eq!(crcr & 0xFFFF_FFFF_FFFF_FFC1, crcr, "unaligned CRCR");
debug!("Writing CRCR: {:X}", crcr);
self.op.get_mut().unwrap().crcr_low.write(crcr as u32);
self.op
.get_mut()
.unwrap()
.crcr_high
.write((crcr as u64 >> 32) as u32);
{
let zero_64b = self.quirks.contains(crate::xhci::quirks::XhciQuirks::ZERO_64B_REGS);
let mut op = self.op.get_mut().unwrap();
Self::write_64bit_reg(&mut op.crcr_low, &mut op.crcr_high, crcr, zero_64b);
}
// Set event ring segment table registers
debug!(
@@ -584,8 +582,8 @@ impl<const N: usize> Xhci<N> {
let erdp = self.primary_event_ring.get_mut().unwrap().erdp();
debug!("Writing ERDP: {:X}", erdp);
int.erdp_low.write(erdp as u32 | (1 << 3));
int.erdp_high.write((erdp as u64 >> 32) as u32);
let zero_64b = self.quirks.contains(crate::xhci::quirks::XhciQuirks::ZERO_64B_REGS);
Self::write_64bit_reg(&mut int.erdp_low, &mut int.erdp_high, erdp | (1 << 3), zero_64b);
let erstba = self.primary_event_ring.get_mut().unwrap().erstba();
debug!("Writing ERSTBA: {:X}", erstba);
@@ -1030,6 +1028,19 @@ impl<const N: usize> Xhci<N> {
((self.dev_ctx.contexts[slot].slot.d.read() & SLOT_CONTEXT_STATE_MASK)
>> SLOT_CONTEXT_STATE_SHIFT) as u8
}
/// Write a 64-bit register pair with quirk-aware ordering.
/// ZERO_64B_REGS (Renesas uPD720202): write HIGH first, then LOW.
/// Normal path: write LOW first, then HIGH.
fn write_64bit_reg(low: &mut common::io::Mmio<u32>, high: &mut common::io::Mmio<u32>, value: u64, zero_64b: bool) {
if zero_64b {
high.write((value >> 32) as u32);
low.write(value as u32);
} else {
low.write(value as u32);
high.write((value >> 32) as u32);
}
}
/// Returns effective 64-bit addressing capability, respecting NO_64BIT_SUPPORT quirk.
fn ac64_effective(&self) -> bool {
if self.quirks.contains(crate::xhci::quirks::XhciQuirks::NO_64BIT_SUPPORT) {