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