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