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