• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2019 The ChromiumOS Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 use std::sync::Arc;
6 use std::sync::MutexGuard;
7 
8 use remain::sorted;
9 use sync::Mutex;
10 use thiserror::Error;
11 
12 use super::interrupter::Error as InterrupterError;
13 use super::interrupter::Interrupter;
14 use super::xhci_backend_device::BackendType;
15 use super::xhci_backend_device::XhciBackendDevice;
16 use super::xhci_regs::XhciRegs;
17 use super::xhci_regs::MAX_PORTS;
18 use super::xhci_regs::PORTSC_CONNECT_STATUS_CHANGE;
19 use super::xhci_regs::PORTSC_CURRENT_CONNECT_STATUS;
20 use super::xhci_regs::PORTSC_PORT_ENABLED;
21 use super::xhci_regs::PORTSC_PORT_ENABLED_DISABLED_CHANGE;
22 use super::xhci_regs::PORTSC_PORT_SPEED_MASK;
23 use super::xhci_regs::PORTSC_PORT_SPEED_SHIFT;
24 use super::xhci_regs::USB2_PORTS_END;
25 use super::xhci_regs::USB2_PORTS_START;
26 use super::xhci_regs::USB3_PORTS_END;
27 use super::xhci_regs::USB3_PORTS_START;
28 use super::xhci_regs::USB_STS_PORT_CHANGE_DETECT;
29 use crate::register_space::Register;
30 use usb_util::DeviceSpeed;
31 
32 #[sorted]
33 #[derive(Error, Debug)]
34 pub enum Error {
35     #[error("all suitable ports already attached")]
36     AllPortsAttached,
37     #[error("device already detached from port {0}")]
38     AlreadyDetached(u8),
39     #[error("failed to attach device to port {port_id}: {reason}")]
40     Attach {
41         port_id: u8,
42         reason: InterrupterError,
43     },
44     #[error("failed to detach device from port {port_id}: {reason}")]
45     Detach {
46         port_id: u8,
47         reason: InterrupterError,
48     },
49     #[error("device {bus}:{addr}:{vid:04x}:{pid:04x} is not attached")]
50     NoSuchDevice {
51         bus: u8,
52         addr: u8,
53         vid: u16,
54         pid: u16,
55     },
56     #[error("port {0} does not exist")]
57     NoSuchPort(u8),
58 }
59 
60 type Result<T> = std::result::Result<T, Error>;
61 
62 /// A port on usb hub. It could have a device connected to it.
63 pub struct UsbPort {
64     ty: BackendType,
65     port_id: u8,
66     portsc: Register<u32>,
67     usbsts: Register<u32>,
68     interrupter: Arc<Mutex<Interrupter>>,
69     backend_device: Mutex<Option<Box<dyn XhciBackendDevice>>>,
70 }
71 
72 impl UsbPort {
73     /// Create a new usb port that has nothing connected to it.
new( ty: BackendType, port_id: u8, portsc: Register<u32>, usbsts: Register<u32>, interrupter: Arc<Mutex<Interrupter>>, ) -> UsbPort74     pub fn new(
75         ty: BackendType,
76         port_id: u8,
77         portsc: Register<u32>,
78         usbsts: Register<u32>,
79         interrupter: Arc<Mutex<Interrupter>>,
80     ) -> UsbPort {
81         UsbPort {
82             ty,
83             port_id,
84             portsc,
85             usbsts,
86             interrupter,
87             backend_device: Mutex::new(None),
88         }
89     }
90 
port_id(&self) -> u891     fn port_id(&self) -> u8 {
92         self.port_id
93     }
94 
95     /// Detach current connected backend. Returns false when there is no backend connected.
detach(&self) -> Result<()>96     pub fn detach(&self) -> Result<()> {
97         let mut locked = self.backend_device.lock();
98         if locked.is_none() {
99             return Err(Error::AlreadyDetached(self.port_id));
100         }
101         usb_debug!("device detached from port {}", self.port_id);
102         *locked = None;
103         self.portsc.clear_bits(PORTSC_PORT_SPEED_MASK);
104         self.send_device_disconnected_event()
105             .map_err(|reason| Error::Detach {
106                 port_id: self.port_id,
107                 reason,
108             })
109     }
110 
111     /// Get current connected backend.
get_backend_device(&self) -> MutexGuard<Option<Box<dyn XhciBackendDevice>>>112     pub fn get_backend_device(&self) -> MutexGuard<Option<Box<dyn XhciBackendDevice>>> {
113         self.backend_device.lock()
114     }
115 
is_attached(&self) -> bool116     fn is_attached(&self) -> bool {
117         self.backend_device.lock().is_some()
118     }
119 
reset(&self) -> std::result::Result<(), InterrupterError>120     fn reset(&self) -> std::result::Result<(), InterrupterError> {
121         if self.is_attached() {
122             self.send_device_connected_event()?;
123         }
124         Ok(())
125     }
126 
attach( &self, device: Box<dyn XhciBackendDevice>, ) -> std::result::Result<(), InterrupterError>127     fn attach(
128         &self,
129         device: Box<dyn XhciBackendDevice>,
130     ) -> std::result::Result<(), InterrupterError> {
131         usb_debug!("A backend is connected to port {}", self.port_id);
132         let speed = device.get_speed();
133         let mut locked = self.backend_device.lock();
134         assert!(locked.is_none());
135         *locked = Some(device);
136         self.portsc.clear_bits(PORTSC_PORT_SPEED_MASK);
137         // Speed mappings from xHCI spec 7.2.2.1.1 ("Default Speed ID Mapping")
138         let speed_id: u32 = match speed {
139             None => 0,
140             Some(DeviceSpeed::Full) => 1,
141             Some(DeviceSpeed::Low) => 2,
142             Some(DeviceSpeed::High) => 3,
143             Some(DeviceSpeed::Super) => 4,
144             Some(DeviceSpeed::SuperPlus) => 5,
145         };
146         self.portsc.set_bits(speed_id << PORTSC_PORT_SPEED_SHIFT);
147         self.send_device_connected_event()
148     }
149 
150     /// Inform the guest kernel there is device connected to this port. It combines first few steps
151     /// of USB device initialization process in xHCI spec 4.3.
send_device_connected_event(&self) -> std::result::Result<(), InterrupterError>152     pub fn send_device_connected_event(&self) -> std::result::Result<(), InterrupterError> {
153         // xHCI spec 4.3.
154         self.portsc.set_bits(
155             PORTSC_CURRENT_CONNECT_STATUS
156                 | PORTSC_PORT_ENABLED
157                 | PORTSC_CONNECT_STATUS_CHANGE
158                 | PORTSC_PORT_ENABLED_DISABLED_CHANGE,
159         );
160         self.usbsts.set_bits(USB_STS_PORT_CHANGE_DETECT);
161         self.interrupter
162             .lock()
163             .send_port_status_change_trb(self.port_id)
164     }
165 
166     /// Inform the guest kernel that device has been detached.
send_device_disconnected_event(&self) -> std::result::Result<(), InterrupterError>167     pub fn send_device_disconnected_event(&self) -> std::result::Result<(), InterrupterError> {
168         // xHCI spec 4.3.
169         self.portsc
170             .set_bits(PORTSC_CONNECT_STATUS_CHANGE | PORTSC_PORT_ENABLED_DISABLED_CHANGE);
171         self.portsc.clear_bits(PORTSC_CURRENT_CONNECT_STATUS);
172         self.usbsts.set_bits(USB_STS_PORT_CHANGE_DETECT);
173         self.interrupter
174             .lock()
175             .send_port_status_change_trb(self.port_id)
176     }
177 }
178 
179 /// UsbHub is a set of usb ports.
180 pub struct UsbHub {
181     ports: Vec<Arc<UsbPort>>,
182 }
183 
184 impl UsbHub {
185     /// Create usb hub with no device attached.
new(regs: &XhciRegs, interrupter: Arc<Mutex<Interrupter>>) -> UsbHub186     pub fn new(regs: &XhciRegs, interrupter: Arc<Mutex<Interrupter>>) -> UsbHub {
187         let mut ports = Vec::new();
188         // Each port should have a portsc register.
189         assert_eq!(MAX_PORTS as usize, regs.portsc.len());
190 
191         for i in USB2_PORTS_START..USB2_PORTS_END {
192             ports.push(Arc::new(UsbPort::new(
193                 BackendType::Usb2,
194                 i + 1,
195                 regs.portsc[i as usize].clone(),
196                 regs.usbsts.clone(),
197                 interrupter.clone(),
198             )));
199         }
200 
201         for i in USB3_PORTS_START..USB3_PORTS_END {
202             ports.push(Arc::new(UsbPort::new(
203                 BackendType::Usb3,
204                 i + 1,
205                 regs.portsc[i as usize].clone(),
206                 regs.usbsts.clone(),
207                 interrupter.clone(),
208             )));
209         }
210         UsbHub { ports }
211     }
212 
213     /// Reset all ports.
reset(&self) -> Result<()>214     pub fn reset(&self) -> Result<()> {
215         usb_debug!("reseting usb hub");
216         for p in &self.ports {
217             p.reset().map_err(|reason| Error::Detach {
218                 port_id: p.port_id(),
219                 reason,
220             })?;
221         }
222         Ok(())
223     }
224 
225     /// Get a specific port of the hub.
get_port(&self, port_id: u8) -> Option<Arc<UsbPort>>226     pub fn get_port(&self, port_id: u8) -> Option<Arc<UsbPort>> {
227         if port_id == 0 || port_id > MAX_PORTS {
228             return None;
229         }
230         let port_index = (port_id - 1) as usize;
231         Some(self.ports.get(port_index)?.clone())
232     }
233 
234     /// Connect backend to next empty port.
connect_backend(&self, backend: Box<dyn XhciBackendDevice>) -> Result<u8>235     pub fn connect_backend(&self, backend: Box<dyn XhciBackendDevice>) -> Result<u8> {
236         usb_debug!("Trying to connect backend to hub");
237         for port in &self.ports {
238             if port.is_attached() {
239                 continue;
240             }
241             if port.ty != backend.get_backend_type() {
242                 continue;
243             }
244             let port_id = port.port_id();
245             port.attach(backend)
246                 .map_err(|reason| Error::Attach { port_id, reason })?;
247             return Ok(port_id);
248         }
249         Err(Error::AllPortsAttached)
250     }
251 
252     /// Disconnect device from port. Returns false if port id is not valid or could not be
253     /// disonnected.
disconnect_port(&self, port_id: u8) -> Result<()>254     pub fn disconnect_port(&self, port_id: u8) -> Result<()> {
255         match self.get_port(port_id) {
256             Some(port) => port.detach(),
257             None => Err(Error::NoSuchPort(port_id)),
258         }
259     }
260 }
261