xHCI: drive Port Reset on port reuse; WIREBIND: kill the console VM too (FABRIC-3.md §XI.5)
Two independent bugs that together caused a reliable hotplug wedge: reusing an xHCI port for a second identity right after an unclean detach of a first would leave no further hotplug events reaching the guest at all. Bug 1 (xhci.c/xhci_driver.h): the xHCI driver never drove PORTSC.PR -- a known, named gap since Milestone 2e (the code's own comment flagged it, PORTSC_PR/PRC were defined but never referenced). A port's first connect each boot reads PED already set, so skipping the reset happened to work; a second device on the same port after a prior disconnect reads PED clear, and Address Device reliably failed without an explicit reset cycle. New XHCI_CONN_AWAIT_PORT_RESET state drives PR and waits for PED to read set before proceeding to Enable Slot. Bug 2 (capsule_wirebind.c): capsule_wirebind_eject()/unclean_detach() compared g_repl_active_vm against the *user* VM's pointer (g_wirebind_attached_vm_id tracks that one, not the console VM) -- never equal, since USE/g_repl_active_vm always points at the console VM. The guard never fired and the console VM was never killed at all, only orphaned -- paired to a dead user VM but still the REPL's active session. New wirebind_teardown_console() helper resolves and tears down the console VM by its own tracked bare username. Verified live on amd64: the exact repro (identity 01 on port 2, unclean detach, identity 02 on the same port immediately after) -- previously wedged with "xhci: address device failed" and no further hotplug activity; now attaches cleanly and fast, both VMs' KILL messages appear, console is immediately interactive on the new identity. Three-arch clean qemu acceptance passed, all clean on the first attempt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014Ec88YKxxhZGG1RNnune78
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
9ea5580ace
commit
b301317902
+79
-8
@@ -1907,11 +1907,82 @@ noted below): fast attach, `USE`, a standard word (`1 2 + .` / `9 9 * .`) comput
|
|||||||
and `VM-EXEC` (ACL-denied for this restricted personality) recovering gracefully — "VM-EXEC:
|
and `VM-EXEC` (ACL-denied for this restricted personality) recovering gracefully — "VM-EXEC:
|
||||||
ERROR in `NN~user`", console stays interactive, no halt — for every one of the 7.
|
ERROR in `NN~user`", console stays interactive, no halt — for every one of the 7.
|
||||||
|
|
||||||
**Noted but out of scope, not investigated further:** attaching a second identity's drive to
|
**Noted but out of scope, not investigated further at the time:** attaching a second identity's
|
||||||
the same xHCI port within the same boot, immediately after an unclean (`device_del`, no `EJECT`
|
drive to the same xHCI port within the same boot, immediately after an unclean (`device_del`, no
|
||||||
word) detach of a first identity, reliably wedged that port — no further hotplug events reached
|
`EJECT` word) detach of a first identity, reliably wedged that port — no further hotplug events
|
||||||
the guest at all for any identity attached there afterward, console stuck on the dead first
|
reached the guest at all for any identity attached there afterward, console stuck on the dead
|
||||||
identity's own prompt. Working around it (fresh port per identity, or a fresh boot per identity)
|
first identity's own prompt. Working around it (fresh port per identity, or a fresh boot per
|
||||||
avoided it entirely and was sufficient for this verification pass; root cause (xHCI port-state
|
identity) avoided it entirely and was sufficient for that verification pass. Root-caused and
|
||||||
settling, `g_wirebind_attached_*`/`g_repl_active_vm` single-active-binding assumptions colliding
|
fixed the same day — see §XI.5.
|
||||||
with a genuinely unclean detach, or both) is unexamined.
|
|
||||||
|
### XI.5 — The reused-port hotplug wedge: two real, independent bugs, both CLOSED
|
||||||
|
|
||||||
|
Investigated on request after §XI.4 flagged it. Two separate bugs, one in the xHCI driver, one
|
||||||
|
in WIREBIND's own teardown bookkeeping — both had to be present for the wedge to look as total
|
||||||
|
as it did; fixing either alone would have helped, fixing both closes it.
|
||||||
|
|
||||||
|
**Bug 1 — the xHCI driver never drove Port Reset (PORTSC.PR).** `xhci_handle_port_connected()`
|
||||||
|
(`src/starkernel/usb/xhci.c`) went straight from detecting a port's Current Connect Status to
|
||||||
|
Enable Slot -> Address Device, with no reset cycle in between. This was a known, named gap, not
|
||||||
|
a new regression -- the function's own comment (since Milestone 2e) already said "USB2 needs
|
||||||
|
software to drive PORTSC.PR and wait for PRC/PED before the device will respond to addressing --
|
||||||
|
not yet known which this driver's ports need." It logged whether `PED` (Port Enabled/Disabled)
|
||||||
|
was set but never acted on it. `XHCI_PORTSC_PR`/`XHCI_PORTSC_PRC` were defined in
|
||||||
|
`include/starkernel/xhci.h` from the start and never referenced anywhere in `xhci.c` -- the gap
|
||||||
|
was real and literally unused code sitting right there.
|
||||||
|
|
||||||
|
Why it only bit on port *reuse*: a device's first connect to a port each boot reads `PED` already
|
||||||
|
set (QEMU's emulated root hub presents it that way), so skipping the reset happened to work. After
|
||||||
|
a device disconnects, a *new* device on that same port reads `PED` clear -- per the xHCI/USB spec,
|
||||||
|
`Address Device` requires the port to be in the Enabled state, which only a completed Port Reset
|
||||||
|
produces. Without driving one, `Address Device` reliably failed -- confirmed live before the fix,
|
||||||
|
matching this exact log line every time:
|
||||||
|
|
||||||
|
```
|
||||||
|
[HADES][ERROR] xhci: address device failed
|
||||||
|
```
|
||||||
|
|
||||||
|
Fix: a new `XHCI_CONN_AWAIT_PORT_RESET` connect_state (`include/starkernel/xhci_driver.h`).
|
||||||
|
`xhci_handle_port_connected()` now checks `PED` on every connect: already set -> straight to
|
||||||
|
Enable Slot as before (the common, first-connect case, unchanged); clear -> write `PORTSC.PR`
|
||||||
|
(preserving `PP`, same discipline the existing CSC-ack write already used) and wait. The reset
|
||||||
|
lands as a fresh port-status-change event with `CCS` still set (device never left) -- the same
|
||||||
|
function handles that continuation: `PED` now set means the reset completed, proceed to Enable
|
||||||
|
Slot; still clear means keep waiting for the next event (no explicit timeout, matching this
|
||||||
|
driver's existing convention of relying on the poll cadence rather than hard deadlines). Both
|
||||||
|
`xhci_poll_events()`'s and `xhci_scan_ports_for_already_connected()`'s trailing PORTSC ack writes
|
||||||
|
now also acknowledge `PRC`, not just `CSC` (RW1C, unconditional like the existing `CSC` ack --
|
||||||
|
writing 1 to an already-clear bit is a no-op).
|
||||||
|
|
||||||
|
**Bug 2 — `capsule_wirebind_eject()`/`capsule_wirebind_unclean_detach()` compared against the
|
||||||
|
wrong VM, so the console VM was never cleaned up.** Both functions resolve their `entry` via
|
||||||
|
`wirebind_resolve_attached()`, which tracks `g_wirebind_attached_vm_id` -- documented, correctly,
|
||||||
|
as *the user VM's id* ("the user VM, not the console VM" -- `capsule_wirebind_try_attach()`'s own
|
||||||
|
comment). But `USE`/`g_repl_active_vm` always points at the *console* VM (`capsule_console_birth`,
|
||||||
|
registered under the bare username, e.g. `"01"`), never the user VM (`"01~user"`). So both
|
||||||
|
functions' `sk_repl_get_active_vm() == entry.vm_ptr` guard compared the console VM against the
|
||||||
|
user VM's pointer -- never equal, so the guard never fired, `g_repl_active_vm` was never cleared,
|
||||||
|
and -- worse -- the console VM was never killed at all (only `entry.name`, the user VM, was --
|
||||||
|
`capsule_vm_kill(entry.name)`). The console VM was left alive and orphaned, paired to a now-dead
|
||||||
|
user VM, and (because the guard never cleared it) still the REPL's own active session. This is
|
||||||
|
why a fresh WIREBIND attach for a second identity, even once Bug 1 stopped blocking the hotplug
|
||||||
|
itself, would have kept showing the first identity's dead-paired prompt rather than switching to
|
||||||
|
the new one.
|
||||||
|
|
||||||
|
Fix (`capsule_wirebind.c`): a new shared `wirebind_teardown_console()` helper, called from both
|
||||||
|
`capsule_wirebind_eject()` and `capsule_wirebind_unclean_detach()` before they clear
|
||||||
|
`g_wirebind_attached_username`. It resolves the console VM by that bare username (the field these
|
||||||
|
functions already track for exactly this purpose, kept separate from the user-VM-keyed
|
||||||
|
`g_wirebind_attached_vm_id` from the start), clears `g_repl_active_vm` if it currently points at
|
||||||
|
that console VM, and kills it -- the guard now compares like against like, and the console VM is
|
||||||
|
actually torn down, not just orphaned.
|
||||||
|
|
||||||
|
**Verified live, amd64:** the exact repro from §XI.4's note -- identity `01` attached on xHCI
|
||||||
|
port 2, unclean-detached (`device_del`, no `EJECT`), identity `02` attached on the *same* port 2
|
||||||
|
immediately after. Before this fix: wedged, no further hotplug events, "address device failed"
|
||||||
|
in the log. After: `02` attached cleanly and fast, log shows both `KILL: 01 dead` (the console
|
||||||
|
VM, new) and `KILL: 01~user dead` (the user VM, already worked) on `01`'s teardown, no "address
|
||||||
|
device failed" anywhere, and the console was immediately `USE`-able and fully interactive on
|
||||||
|
`02` (arithmetic computed correctly, `VM-EXEC` denial recovered gracefully per §XI.4's fix,
|
||||||
|
console stayed responsive). Three-arch `clean qemu` acceptance (amd64/aarch64/riscv64) passed,
|
||||||
|
all three clean on the first attempt.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# Capsule Block Manifest — Auto-generated
|
# Capsule Block Manifest — Auto-generated
|
||||||
<!-- Generated by mkcapsule --manifest 2026-09-10T00:53:54Z -->
|
<!-- Generated by mkcapsule --manifest 2026-09-10T02:09:05Z -->
|
||||||
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
|
||||||
<!-- Hand-written justifications and immutability notes live -->
|
<!-- Hand-written justifications and immutability notes live -->
|
||||||
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
<!-- in MANIFEST.md alongside this auto-generated index. -->
|
||||||
|
|||||||
Binary file not shown.
@@ -229,6 +229,7 @@ typedef struct {
|
|||||||
* second connect while this is busy is now handled. */
|
* second connect while this is busy is now handled. */
|
||||||
enum {
|
enum {
|
||||||
XHCI_CONN_IDLE = 0,
|
XHCI_CONN_IDLE = 0,
|
||||||
|
XHCI_CONN_AWAIT_PORT_RESET,
|
||||||
XHCI_CONN_AWAIT_ENABLE_SLOT,
|
XHCI_CONN_AWAIT_ENABLE_SLOT,
|
||||||
XHCI_CONN_AWAIT_ADDRESS_DEVICE,
|
XHCI_CONN_AWAIT_ADDRESS_DEVICE,
|
||||||
XHCI_CONN_AWAIT_DISABLE_SLOT,
|
XHCI_CONN_AWAIT_DISABLE_SLOT,
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -262,6 +262,43 @@ static int wirebind_resolve_attached(VMRegistryEntry *out) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* FABRIC-3.md, 2026-09-09: shared by capsule_wirebind_eject() and
|
||||||
|
* capsule_wirebind_unclean_detach() -- tears down the *console* VM
|
||||||
|
* (registered under the bare username, e.g. "00" -- capsule_console_
|
||||||
|
* birth() in capsule_wirebind_try_attach() above) paired with the user
|
||||||
|
* VM ("<username>~user") those two functions already kill.
|
||||||
|
*
|
||||||
|
* Real bug found live, 2026-09-09: both functions used to compare
|
||||||
|
* sk_repl_get_active_vm() against entry.vm_ptr, where `entry` comes
|
||||||
|
* from wirebind_resolve_attached() -- which resolves g_wirebind_
|
||||||
|
* attached_vm_id, the *user* VM's id (see that field's own doc comment:
|
||||||
|
* "the user VM, not the console VM"). But USE/g_repl_active_vm always
|
||||||
|
* points at the *console* VM, never the user VM -- so that comparison
|
||||||
|
* was never true, the dangling-pointer guard never fired, and the
|
||||||
|
* console VM was never killed at all here, just silently orphaned
|
||||||
|
* (paired to a now-dead user VM, but still live and still whatever the
|
||||||
|
* active REPL session was). Confirmed live: reusing an xHCI port for a
|
||||||
|
* second identity right after an unclean detach of a first left the
|
||||||
|
* console stuck showing the first identity's own dead-paired prompt.
|
||||||
|
*
|
||||||
|
* Uses g_wirebind_attached_username (the bare username, captured at
|
||||||
|
* attach time) rather than deriving it from entry.name (which is
|
||||||
|
* already the "<user>~user" registry name) -- see capsule_wirebind_
|
||||||
|
* try_attach()'s own comment on why the two names are tracked
|
||||||
|
* separately. Called before either caller clears that field. */
|
||||||
|
static void wirebind_teardown_console(void) {
|
||||||
|
if (g_wirebind_attached_username[0] == '\0') return;
|
||||||
|
VMRegistryEntry console_entry;
|
||||||
|
if (capsule_vm_find_by_name(g_wirebind_attached_username, &console_entry) != 0 ||
|
||||||
|
console_entry.state != VM_STATE_LIVE) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (sk_repl_get_active_vm() == (VM *)console_entry.vm_ptr) {
|
||||||
|
sk_repl_set_active_vm((VM *)0);
|
||||||
|
}
|
||||||
|
capsule_vm_kill(console_entry.name);
|
||||||
|
}
|
||||||
|
|
||||||
int capsule_wirebind_eject(void) {
|
int capsule_wirebind_eject(void) {
|
||||||
VMRegistryEntry entry;
|
VMRegistryEntry entry;
|
||||||
if (wirebind_resolve_attached(&entry) != 0) {
|
if (wirebind_resolve_attached(&entry) != 0) {
|
||||||
@@ -273,10 +310,7 @@ int capsule_wirebind_eject(void) {
|
|||||||
* remove (§F.10 decision 1). */
|
* remove (§F.10 decision 1). */
|
||||||
blk_vm_flush_all((VM *)entry.vm_ptr);
|
blk_vm_flush_all((VM *)entry.vm_ptr);
|
||||||
|
|
||||||
if (sk_repl_get_active_vm() == (VM *)entry.vm_ptr) {
|
wirebind_teardown_console();
|
||||||
sk_repl_set_active_vm((VM *)0);
|
|
||||||
}
|
|
||||||
|
|
||||||
capsule_vm_kill(entry.name);
|
capsule_vm_kill(entry.name);
|
||||||
g_wirebind_attached_valid = 0;
|
g_wirebind_attached_valid = 0;
|
||||||
g_wirebind_attached_dev = (struct blkio_dev *) 0;
|
g_wirebind_attached_dev = (struct blkio_dev *) 0;
|
||||||
@@ -306,10 +340,7 @@ void capsule_wirebind_unclean_detach(struct blkio_dev *dev) {
|
|||||||
/* Device is already gone (§F.10 decision 2) -- no flush attempted;
|
/* Device is already gone (§F.10 decision 2) -- no flush attempted;
|
||||||
* data since the last flush is lost, matching real-OS unclean-removal
|
* data since the last flush is lost, matching real-OS unclean-removal
|
||||||
* semantics. */
|
* semantics. */
|
||||||
if (sk_repl_get_active_vm() == (VM *)entry.vm_ptr) {
|
wirebind_teardown_console();
|
||||||
sk_repl_set_active_vm((VM *)0);
|
|
||||||
}
|
|
||||||
|
|
||||||
capsule_vm_kill(entry.name);
|
capsule_vm_kill(entry.name);
|
||||||
g_wirebind_attached_valid = 0;
|
g_wirebind_attached_valid = 0;
|
||||||
g_wirebind_attached_dev = (struct blkio_dev *) 0;
|
g_wirebind_attached_dev = (struct blkio_dev *) 0;
|
||||||
|
|||||||
+81
-21
@@ -1358,27 +1358,82 @@ static void xhci_handle_port_connected(xhci_dev_t *dev, uint32_t port_id, uint32
|
|||||||
{
|
{
|
||||||
log_message(LOG_DEBUG, "xhci: port status change -- device connected");
|
log_message(LOG_DEBUG, "xhci: port status change -- device connected");
|
||||||
/* Milestone 2e prep: Address Device requires the port in Default
|
/* Milestone 2e prep: Address Device requires the port in Default
|
||||||
* state. USB3 links train and enable themselves; USB2 needs software
|
* state (PED set). USB3 links train and enable themselves; USB2
|
||||||
* to drive PORTSC.PR and wait for PRC/PED before the device will
|
* needs software to drive PORTSC.PR and wait for PRC/PED before the
|
||||||
* respond to addressing -- not yet known which this driver's ports
|
* device will respond to addressing. */
|
||||||
* need, so log raw PORTSC and PED rather than assume. */
|
|
||||||
xhci_log_hex32("xhci: portsc=", portsc);
|
xhci_log_hex32("xhci: portsc=", portsc);
|
||||||
log_message(LOG_DEBUG, (portsc & XHCI_PORTSC_PED)
|
|
||||||
? "xhci: port enabled (PED set)"
|
if (port_id > XHCI_MAX_TRACKED_PORTS) {
|
||||||
: "xhci: port not yet enabled (PED clear)");
|
log_message(LOG_WARN, "xhci: port beyond tracked range -- enable slot skipped");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* FABRIC-3.md, 2026-09-09: a reset this driver itself kicked off
|
||||||
|
* (below) has landed as a fresh port-status-change event -- CCS is
|
||||||
|
* still set (device never left), so this is the *same* connect
|
||||||
|
* continuing, not a new one. Proceed to Enable Slot now that PED
|
||||||
|
* reads set; if it still doesn't, the reset is still in progress
|
||||||
|
* (real hardware/QEMU can take more than one event to settle it) --
|
||||||
|
* just wait for the next port-status-change event, same as every
|
||||||
|
* other outstanding-command wait in this driver (no explicit
|
||||||
|
* timeout, matching this file's existing convention). */
|
||||||
|
if (dev->connect_state == XHCI_CONN_AWAIT_PORT_RESET &&
|
||||||
|
dev->pending_connect_port_id == port_id) {
|
||||||
|
if (portsc & XHCI_PORTSC_PED) {
|
||||||
|
log_message(LOG_DEBUG, "xhci: port reset complete (PED set)");
|
||||||
|
dev->pending_connect_speed = XHCI_PORTSC_SPEED(portsc);
|
||||||
|
dev->connect_state = XHCI_CONN_AWAIT_ENABLE_SLOT;
|
||||||
|
xhci_cmd_enable_slot(dev);
|
||||||
|
} else {
|
||||||
|
log_message(LOG_DEBUG, "xhci: port reset still in progress (PED clear)");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Only one Enable Slot in flight at a time (real xHCI Command Ring
|
/* Only one Enable Slot in flight at a time (real xHCI Command Ring
|
||||||
* semantics, one outstanding command) -- if another connect's slot
|
* semantics, one outstanding command) -- if another connect's slot
|
||||||
* request is still outstanding, this one is queued (FABRIC-3.md §VII
|
* request is still outstanding, this one is queued (FABRIC-3.md §VII
|
||||||
* item 3, 2026-09-05 -- used to be dropped here) rather than lost. */
|
* item 3, 2026-09-05 -- used to be dropped here) rather than lost. */
|
||||||
if (port_id > XHCI_MAX_TRACKED_PORTS) {
|
if (dev->connect_state != XHCI_CONN_IDLE) {
|
||||||
log_message(LOG_WARN, "xhci: port beyond tracked range -- enable slot skipped");
|
xhci_queue_pending_event(dev, port_id, 1, portsc, 0);
|
||||||
} else if (dev->connect_state == XHCI_CONN_IDLE) {
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (portsc & XHCI_PORTSC_PED) {
|
||||||
|
/* Common case: the port already reads enabled -- either this is
|
||||||
|
* the very first connect on this port this boot (QEMU/most real
|
||||||
|
* USB3 root hubs present it already-enabled), or a USB3 link
|
||||||
|
* that trained/enabled itself. Go straight to Enable Slot, same
|
||||||
|
* as this driver has always done for that case. */
|
||||||
|
log_message(LOG_DEBUG, "xhci: port enabled (PED set)");
|
||||||
dev->pending_connect_port_id = port_id;
|
dev->pending_connect_port_id = port_id;
|
||||||
dev->pending_connect_speed = XHCI_PORTSC_SPEED(portsc);
|
dev->pending_connect_speed = XHCI_PORTSC_SPEED(portsc);
|
||||||
dev->connect_state = XHCI_CONN_AWAIT_ENABLE_SLOT;
|
dev->connect_state = XHCI_CONN_AWAIT_ENABLE_SLOT;
|
||||||
xhci_cmd_enable_slot(dev);
|
xhci_cmd_enable_slot(dev);
|
||||||
} else {
|
} else {
|
||||||
xhci_queue_pending_event(dev, port_id, 1, portsc, 0);
|
/* FABRIC-3.md, 2026-09-09: closes the "reused-port hotplug
|
||||||
|
* wedge" this function's own comment used to flag as an open
|
||||||
|
* question ("not yet known which this driver's ports need").
|
||||||
|
* Confirmed live: a second device attached to a port whose
|
||||||
|
* previous occupant just disconnected reads PED clear here, and
|
||||||
|
* without a reset cycle Address Device reliably fails
|
||||||
|
* ("xhci: address device failed") -- the port needs an explicit
|
||||||
|
* Port Reset before it will respond to addressing again, exactly
|
||||||
|
* as USB2 devices always have. Same preserve-PP-only write
|
||||||
|
* discipline xhci_poll_events()'s own CSC ack already uses;
|
||||||
|
* PORTSC.PR is RW1S and self-clears in hardware once the reset
|
||||||
|
* completes (PRC set, observed above via the PED check on the
|
||||||
|
* event this produces), so no matching "clear" write is needed. */
|
||||||
|
log_message(LOG_DEBUG, "xhci: port not yet enabled (PED clear) -- driving port reset");
|
||||||
|
dev->pending_connect_port_id = port_id;
|
||||||
|
dev->connect_state = XHCI_CONN_AWAIT_PORT_RESET;
|
||||||
|
xhci_port_regs_t *port = xhci_port_regs(dev, port_id);
|
||||||
|
if (port) {
|
||||||
|
port->portsc = (portsc & XHCI_PORTSC_PP) | XHCI_PORTSC_PR;
|
||||||
|
} else {
|
||||||
|
dev->connect_state = XHCI_CONN_IDLE;
|
||||||
|
dev->pending_connect_port_id = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1441,10 +1496,10 @@ static void xhci_scan_ports_for_already_connected(xhci_dev_t *dev)
|
|||||||
if (!(portsc & XHCI_PORTSC_CCS)) continue;
|
if (!(portsc & XHCI_PORTSC_CCS)) continue;
|
||||||
|
|
||||||
xhci_handle_port_connected(dev, port_id, portsc);
|
xhci_handle_port_connected(dev, port_id, portsc);
|
||||||
/* Acknowledge CSC the same way xhci_poll_events() does, in case
|
/* Acknowledge CSC and PRC the same way xhci_poll_events() does, in
|
||||||
* the controller latched it during reset -- harmless if it was
|
* case the controller latched either during reset -- harmless if
|
||||||
* never set. */
|
* neither was actually set. */
|
||||||
port->portsc = (portsc & XHCI_PORTSC_PP) | XHCI_PORTSC_CSC;
|
port->portsc = (portsc & XHCI_PORTSC_PP) | XHCI_PORTSC_CSC | XHCI_PORTSC_PRC;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1634,12 +1689,17 @@ void xhci_poll_events(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Acknowledge only CSC (RW1CS): preserve PP, write 0 for
|
/* Acknowledge CSC and PRC (both RW1C): preserve PP, write 0
|
||||||
* PED/PR (writing 1 there disables the port / starts a new
|
* for PED/PR (writing 1 there disables the port / starts a
|
||||||
* reset -- not intended here) and for every other _C bit
|
* new reset -- not intended here) and for every other _C
|
||||||
* (writing 0 leaves them untouched, not cleared) -- the
|
* bit (writing 0 leaves them untouched, not cleared) -- the
|
||||||
* same discipline this driver already applies to ERDP.EHB. */
|
* same discipline this driver already applies to ERDP.EHB.
|
||||||
port->portsc = (portsc & XHCI_PORTSC_PP) | XHCI_PORTSC_CSC;
|
* PRC added FABRIC-3.md 2026-09-09 alongside
|
||||||
|
* xhci_handle_port_connected()'s new port-reset drive --
|
||||||
|
* unconditional like CSC always was (RW1C: writing 1 to an
|
||||||
|
* already-clear bit is a no-op, harmless when this event
|
||||||
|
* wasn't reset-related). */
|
||||||
|
port->portsc = (portsc & XHCI_PORTSC_PP) | XHCI_PORTSC_CSC | XHCI_PORTSC_PRC;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case XHCI_TRB_TYPE_COMMAND_COMPLETION_EVT: {
|
case XHCI_TRB_TYPE_COMMAND_COMPLETION_EVT: {
|
||||||
|
|||||||
Reference in New Issue
Block a user