-
Notifications
You must be signed in to change notification settings - Fork 5
Controller hardware reset #268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
cfa3d50
86bb12e
23f886c
30d9823
f44698a
2f0c330
f96fc07
3987355
337d7a0
9f23810
3122929
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| use tokio::{runtime, sync::oneshot}; | ||
| use tracing::{error, info}; | ||
|
|
||
| use crate::device::{pci::constants::xhci::operational::usbcmd, xhci::registers::UsbcmdRegister}; | ||
|
|
||
| /// Sends reset requests to a component and reports when the reset finished. | ||
| /// | ||
| /// The completion notifier must be triggered by the worker after it has applied | ||
| /// its reset side effects. | ||
| pub trait ResetSender: Send + Sync { | ||
| fn send_reset(&self, completion_notifier: oneshot::Sender<()>) -> anyhow::Result<()>; | ||
|
|
||
| fn reset(&self) -> anyhow::Result<oneshot::Receiver<()>> { | ||
| let (send, recv) = oneshot::channel(); | ||
| self.send_reset(send)?; | ||
|
|
||
| Ok(recv) | ||
| } | ||
| } | ||
|
|
||
| /// Coordinates host controller reset across all resettable xHCI components. | ||
| /// | ||
| /// The coordinator waits for `USBCMD.HCRST`, resets all registered components, | ||
| /// and clears `HCRST` after all reset completion signals were received. | ||
| pub struct ResetCoordinator { | ||
| usbcmd: UsbcmdRegister, | ||
| reset_senders: [Box<dyn ResetSender>; 3], | ||
| } | ||
|
|
||
| impl ResetCoordinator { | ||
| pub fn start( | ||
| usbcmd: UsbcmdRegister, | ||
| reset_senders: [Box<dyn ResetSender>; 3], | ||
| async_runtime: &runtime::Handle, | ||
| ) { | ||
| let coordinator = Self { | ||
| usbcmd, | ||
| reset_senders, | ||
| }; | ||
|
|
||
| async_runtime.spawn(coordinator.run_loop()); | ||
| } | ||
|
|
||
| async fn run_loop(self) { | ||
| loop { | ||
| self.usbcmd.hcrst_notification().await; | ||
|
|
||
| if self.usbcmd.read() & usbcmd::HCRST == 0 { | ||
| continue; | ||
| } | ||
|
|
||
| self.reset() | ||
| .await | ||
| .map_err(|err| error!("failed to reset host controller: {err}")) | ||
| .ok(); | ||
| } | ||
| } | ||
|
|
||
| async fn reset(&self) -> anyhow::Result<()> { | ||
| info!("Host Controller Reset requested"); | ||
|
|
||
| for reset_sender in &self.reset_senders { | ||
| reset_sender.reset()?.await?; | ||
| } | ||
| self.usbcmd.clear_hcrst(); | ||
|
|
||
| Ok(()) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,23 +1,46 @@ | ||
| use anyhow::{anyhow, Context}; | ||
| use tokio::sync::mpsc; | ||
| use tokio::sync::{mpsc, oneshot}; | ||
| use tokio::{runtime, select}; | ||
| use tracing::{debug, info}; | ||
|
|
||
| use crate::device::bus::BusDeviceRef; | ||
| use crate::device::interrupt_line::{DummyInterruptLine, InterruptLine}; | ||
| use crate::device::pci::constants::xhci::runtime::IMOD_DEFAULT; | ||
| use crate::device::xhci::controller_reset::ResetSender; | ||
| use crate::device::xhci::event_ring::EventRing; | ||
| use crate::device::xhci::registers::{ErstbaRegister, GenericRwRegister}; | ||
| use crate::device::xhci::trb::EventTrb; | ||
| use std::sync::Arc; | ||
|
|
||
| #[derive(Debug)] | ||
| #[derive(Debug, Clone)] | ||
| pub struct Interrupter { | ||
| pub registers: InterrupterRegisters, | ||
| /// Transmits events to send to the worker | ||
| msg_sender: mpsc::UnboundedSender<InterrupterMessage>, | ||
| } | ||
|
|
||
| #[derive(Debug, Clone)] | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is to create an |
||
| pub struct InterrupterResetSender { | ||
| registers: InterrupterRegisters, | ||
| msg_sender: mpsc::UnboundedSender<InterrupterMessage>, | ||
| } | ||
|
|
||
| impl ResetSender for InterrupterResetSender { | ||
| fn send_reset(&self, completion_notifier: oneshot::Sender<()>) -> anyhow::Result<()> { | ||
| self.registers.interrupt_management.write(0); | ||
| self.registers | ||
| .interrupt_moderation_interval | ||
| .write(IMOD_DEFAULT); | ||
| self.registers.erst_base_address.reset(); | ||
| self.registers.erst_size.write(0); | ||
| self.registers.eventring_dequeue_pointer.write(0); | ||
|
Comment on lines
+30
to
+36
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The interrupter worker should execute all this code once it receives the message. |
||
| self.msg_sender | ||
| .send(InterrupterMessage::Reset(completion_notifier))?; | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
|
|
||
| #[derive(Debug, Clone)] | ||
| pub struct InterrupterRegisters { | ||
| /// IMAN: Interrupt management register | ||
|
|
@@ -58,6 +81,7 @@ struct EventWorker { | |
| enum InterrupterMessage { | ||
| SendEvent(EventTrb), | ||
| UpdateInterruptLine(Arc<dyn InterruptLine>), | ||
| Reset(oneshot::Sender<()>), | ||
| } | ||
|
|
||
| #[derive(Debug, Clone)] | ||
|
|
@@ -115,6 +139,13 @@ impl Interrupter { | |
| sender: self.msg_sender.clone(), | ||
| } | ||
| } | ||
|
|
||
| pub fn reset_sender(&self) -> InterrupterResetSender { | ||
| InterrupterResetSender { | ||
| registers: self.registers.clone(), | ||
| msg_sender: self.msg_sender.clone(), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl EventWorker { | ||
|
|
@@ -134,29 +165,44 @@ impl EventWorker { | |
| } | ||
| } | ||
|
|
||
| // function only returns on error, but cannot use ! in Result | ||
| async fn run_loop(&mut self) -> anyhow::Result<()> { | ||
| // first ERSTBA write starts the event ring. | ||
| // drop all events that happen before. | ||
| // interrupt line updates should be processed. | ||
| // Each ERSTBA write configures a new event ring. A host controller reset | ||
| // clears that configuration and sends us back to waiting for ERSTBA. | ||
| loop { | ||
| self.wait_for_event_ring_configuration().await?; | ||
| self.event_ring.configure( | ||
| self.registers.erst_base_address.erstba(), | ||
| self.registers.erst_size.read() as u32, | ||
| ); | ||
|
|
||
| self.run_configured().await?; | ||
| } | ||
|
ziyifu225 marked this conversation as resolved.
|
||
| } | ||
|
|
||
| // The first ERSTBA write starts the event ring. Drop events that happen | ||
| // before configuration, but keep processing control messages. | ||
| async fn wait_for_event_ring_configuration(&mut self) -> anyhow::Result<()> { | ||
| loop { | ||
| select! { | ||
| _ = self.registers.erst_base_address.write_notification() => break, | ||
| _ = self.registers.erst_base_address.write_notification() => return Ok(()), | ||
| // we cannot use self.next_msg() here because it borrows self mutable, clashing | ||
| // with the borrow of self.registers above | ||
| msg = self.msg_recv.recv() => match msg.ok_or_else(|| anyhow!("event channel closed"))? { | ||
| InterrupterMessage::SendEvent(_) => {} | ||
| InterrupterMessage::UpdateInterruptLine(interrupt_line) => self.interrupt_line = interrupt_line, | ||
| InterrupterMessage::Reset(completion) => { | ||
| self.event_ring.reset(); | ||
| completion.send(()).ok(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
| } | ||
| }, | ||
| } | ||
| } | ||
| self.event_ring.configure( | ||
| self.registers.erst_base_address.erstba(), | ||
| self.registers.erst_size.read() as u32, | ||
| ); | ||
| } | ||
|
|
||
| // Process messages while the event ring is configured. A reset clears the | ||
| // current configuration and returns to the outer loop to wait for ERSTBA. | ||
| async fn run_configured(&mut self) -> anyhow::Result<()> { | ||
| loop { | ||
| // process TRB | ||
| match self.next_msg().await? { | ||
| InterrupterMessage::SendEvent(event_trb) => { | ||
| self.event_ring.enqueue( | ||
|
|
@@ -172,7 +218,14 @@ impl EventWorker { | |
| self.interrupt_line = interrupt_line; | ||
| debug!("Updated interrupt line"); | ||
| } | ||
| InterrupterMessage::Reset(completion) => { | ||
| self.event_ring.reset(); | ||
| completion.send(()).ok(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please use |
||
| break; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change? You never use the clone functionality, the code works perfectly without Clone.