1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-25 06:47:35 +00:00

Kernel/USB: Add Hubs and the UHCI Root Hub

This commit is contained in:
Luke 2021-08-13 20:47:13 +01:00 committed by Andreas Kling
parent 9dcd146ab4
commit da0a1068e9
11 changed files with 1009 additions and 100 deletions

View file

@ -62,6 +62,7 @@ static constexpr u16 UHCI_PORTSC_RESUME_DETECT = 0x40;
static constexpr u16 UHCI_PORTSC_LOW_SPEED_DEVICE = 0x0100;
static constexpr u16 UHCI_PORTSC_PORT_RESET = 0x0200;
static constexpr u16 UHCI_PORTSC_SUSPEND = 0x1000;
static constexpr u16 UCHI_PORTSC_NON_WRITE_CLEAR_BIT_MASK = 0x1FF5; // This is used to mask out the Write Clear bits making sure we don't accidentally clear them.
// *BSD and a few other drivers seem to use this number
static constexpr u8 UHCI_NUMBER_OF_ISOCHRONOUS_TDS = 128;
@ -486,6 +487,16 @@ KResult UHCIController::start()
break;
}
dbgln("UHCI: Started");
auto root_hub_or_error = UHCIRootHub::try_create(*this);
if (root_hub_or_error.is_error())
return root_hub_or_error.error();
m_root_hub = root_hub_or_error.release_value();
auto result = m_root_hub->setup({});
if (result.is_error())
return result;
return KSuccess;
}
@ -581,6 +592,12 @@ KResultOr<size_t> UHCIController::submit_control_transfer(Transfer& transfer)
Pipe& pipe = transfer.pipe(); // Short circuit the pipe related to this transfer
bool direction_in = (transfer.request().request_type & USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST) == USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST;
dbgln_if(UHCI_DEBUG, "UHCI: Received control transfer for address {}. Root Hub is at address {}.", pipe.device_address(), m_root_hub->device_address());
// Short-circuit the root hub.
if (pipe.device_address() == m_root_hub->device_address())
return m_root_hub->handle_control_transfer(transfer);
TransferDescriptor* setup_td = create_transfer_descriptor(pipe, PacketID::SETUP, sizeof(USBRequestData));
if (!setup_td)
return ENOMEM;
@ -683,88 +700,9 @@ void UHCIController::spawn_port_proc()
Process::create_kernel_process(usb_hotplug_thread, "UHCIHotplug", [&] {
for (;;) {
for (int port = 0; port < UHCI_ROOT_PORT_COUNT; port++) {
u16 port_data = 0;
if (m_root_hub)
m_root_hub->check_for_port_updates();
if (port == 1) {
// Let's see what's happening on port 1
// Current status
port_data = read_portsc1();
if (port_data & UHCI_PORTSC_CONNECT_STATUS_CHANGED) {
if (port_data & UHCI_PORTSC_CURRRENT_CONNECT_STATUS) {
dmesgln("UHCI: Device attach detected on Root Port 1!");
// Reset the port
port_data = read_portsc1();
write_portsc1(port_data | UHCI_PORTSC_PORT_RESET);
IO::delay(500);
write_portsc1(port_data & ~UHCI_PORTSC_PORT_RESET);
IO::delay(500);
write_portsc1(port_data & (~UHCI_PORTSC_PORT_ENABLE_CHANGED | ~UHCI_PORTSC_CONNECT_STATUS_CHANGED));
port_data = read_portsc1();
write_portsc1(port_data | UHCI_PORTSC_PORT_ENABLED);
dbgln("port should be enabled now: {:#04x}\n", read_portsc1());
USB::Device::DeviceSpeed speed = (port_data & UHCI_PORTSC_LOW_SPEED_DEVICE) ? USB::Device::DeviceSpeed::LowSpeed : USB::Device::DeviceSpeed::FullSpeed;
auto device = USB::Device::try_create(*this, USB::Device::PortNumber::Port1, speed);
if (device.is_error())
dmesgln("UHCI: Device creation failed on port 1 ({})", device.error());
m_devices.at(0) = device.value();
VERIFY(s_procfs_usb_bus_directory);
s_procfs_usb_bus_directory->plug(device.value());
} else {
// FIXME: Clean up (and properly) the RefPtr to the device in m_devices
VERIFY(s_procfs_usb_bus_directory);
VERIFY(m_devices.at(0));
dmesgln("UHCI: Device detach detected on Root Port 1");
s_procfs_usb_bus_directory->unplug(*m_devices.at(0));
}
}
} else {
port_data = read_portsc2();
if (port_data & UHCI_PORTSC_CONNECT_STATUS_CHANGED) {
if (port_data & UHCI_PORTSC_CURRRENT_CONNECT_STATUS) {
dmesgln("UHCI: Device attach detected on Root Port 2");
// Reset the port
port_data = read_portsc2();
write_portsc2(port_data | UHCI_PORTSC_PORT_RESET);
for (size_t i = 0; i < 50000; ++i)
IO::in8(0x80);
write_portsc2(port_data & ~UHCI_PORTSC_PORT_RESET);
for (size_t i = 0; i < 100000; ++i)
IO::in8(0x80);
write_portsc2(port_data & (~UHCI_PORTSC_PORT_ENABLE_CHANGED | ~UHCI_PORTSC_CONNECT_STATUS_CHANGED));
port_data = read_portsc2();
write_portsc2(port_data | UHCI_PORTSC_PORT_ENABLED);
dbgln("port should be enabled now: {:#04x}\n", read_portsc2());
USB::Device::DeviceSpeed speed = (port_data & UHCI_PORTSC_LOW_SPEED_DEVICE) ? USB::Device::DeviceSpeed::LowSpeed : USB::Device::DeviceSpeed::FullSpeed;
auto device = USB::Device::try_create(*this, USB::Device::PortNumber::Port2, speed);
if (device.is_error())
dmesgln("UHCI: Device creation failed on port 2 ({})", device.error());
m_devices.at(1) = device.value();
VERIFY(s_procfs_usb_bus_directory);
s_procfs_usb_bus_directory->plug(device.value());
} else {
// FIXME: Clean up (and properly) the RefPtr to the device in m_devices
VERIFY(s_procfs_usb_bus_directory);
VERIFY(m_devices.at(1));
dmesgln("UHCI: Device detach detected on Root Port 2");
s_procfs_usb_bus_directory->unplug(*m_devices.at(1));
}
}
}
}
(void)Thread::current()->sleep(Time::from_seconds(1));
}
});
@ -788,4 +726,173 @@ bool UHCIController::handle_irq(const RegisterState&)
return true;
}
void UHCIController::get_port_status(Badge<UHCIRootHub>, u8 port, HubStatus& hub_port_status)
{
// The check is done by UHCIRootHub.
VERIFY(port < NUMBER_OF_ROOT_PORTS);
u16 status = port == 0 ? read_portsc1() : read_portsc2();
if (status & UHCI_PORTSC_CURRRENT_CONNECT_STATUS)
hub_port_status.status |= PORT_STATUS_CURRENT_CONNECT_STATUS;
if (status & UHCI_PORTSC_CONNECT_STATUS_CHANGED)
hub_port_status.change |= PORT_STATUS_CONNECT_STATUS_CHANGED;
if (status & UHCI_PORTSC_PORT_ENABLED)
hub_port_status.status |= PORT_STATUS_PORT_ENABLED;
if (status & UHCI_PORTSC_PORT_ENABLE_CHANGED)
hub_port_status.change |= PORT_STATUS_PORT_ENABLED_CHANGED;
if (status & UHCI_PORTSC_LOW_SPEED_DEVICE)
hub_port_status.status |= PORT_STATUS_LOW_SPEED_DEVICE_ATTACHED;
if (status & UHCI_PORTSC_PORT_RESET)
hub_port_status.status |= PORT_STATUS_RESET;
if (m_port_reset_change_statuses & (1 << port))
hub_port_status.change |= PORT_STATUS_RESET_CHANGED;
if (status & UHCI_PORTSC_SUSPEND)
hub_port_status.status |= PORT_STATUS_SUSPEND;
if (m_port_suspend_change_statuses & (1 << port))
hub_port_status.change |= PORT_STATUS_SUSPEND_CHANGED;
// UHCI ports are always powered.
hub_port_status.status |= PORT_STATUS_PORT_POWER;
dbgln_if(UHCI_DEBUG, "UHCI: get_port_status status=0x{:04x} change=0x{:04x}", hub_port_status.status, hub_port_status.change);
}
void UHCIController::reset_port(u8 port)
{
// We still have to reset the port manually because UHCI does not automatically enable the port after reset.
// Additionally, the USB 2.0 specification says the SetPortFeature(PORT_ENABLE) request is not specified and that the _ideal_ behaviour is to return a Request Error.
// Source: USB 2.0 Specification Section 11.24.2.7.1.2
// This means the hub code cannot rely on using it.
// The check is done by UHCIRootHub and set_port_feature.
VERIFY(port < NUMBER_OF_ROOT_PORTS);
u16 port_data = port == 0 ? read_portsc1() : read_portsc2();
port_data &= UCHI_PORTSC_NON_WRITE_CLEAR_BIT_MASK;
port_data |= UHCI_PORTSC_PORT_RESET;
if (port == 0)
write_portsc1(port_data);
else
write_portsc2(port_data);
// Wait at least 50 ms for the port to reset.
// This is T DRSTR in the USB 2.0 Specification Page 186 Table 7-13.
constexpr u16 reset_delay = 50 * 1000;
IO::delay(reset_delay);
port_data &= ~UHCI_PORTSC_PORT_RESET;
if (port == 0)
write_portsc1(port_data);
else
write_portsc2(port_data);
// Wait 10 ms for the port to recover.
// This is T RSTRCY in the USB 2.0 Specification Page 188 Table 7-14.
constexpr u16 reset_recovery_delay = 10 * 1000;
IO::delay(reset_recovery_delay);
port_data = port == 0 ? read_portsc1() : read_portsc2();
port_data |= UHCI_PORTSC_PORT_ENABLED;
if (port == 0)
write_portsc1(port_data);
else
write_portsc2(port_data);
dbgln_if(UHCI_DEBUG, "UHCI: Port should be enabled now: {:#04x}", port == 0 ? read_portsc1() : read_portsc2());
m_port_reset_change_statuses |= (1 << port);
}
KResult UHCIController::set_port_feature(Badge<UHCIRootHub>, u8 port, HubFeatureSelector feature_selector)
{
// The check is done by UHCIRootHub.
VERIFY(port < NUMBER_OF_ROOT_PORTS);
dbgln_if(UHCI_DEBUG, "UHCI: set_port_feature: port={} feature_selector={}", port, (u8)feature_selector);
switch (feature_selector) {
case HubFeatureSelector::PORT_POWER:
// Ignore the request. UHCI ports are always powered.
break;
case HubFeatureSelector::PORT_RESET:
reset_port(port);
break;
case HubFeatureSelector::PORT_SUSPEND: {
u16 port_data = port == 0 ? read_portsc1() : read_portsc2();
port_data &= UCHI_PORTSC_NON_WRITE_CLEAR_BIT_MASK;
port_data |= UHCI_PORTSC_SUSPEND;
if (port == 0)
write_portsc1(port_data);
else
write_portsc2(port_data);
m_port_suspend_change_statuses |= (1 << port);
break;
}
default:
dbgln("UHCI: Unknown feature selector in set_port_feature: {}", (u8)feature_selector);
return EINVAL;
}
return KSuccess;
}
KResult UHCIController::clear_port_feature(Badge<UHCIRootHub>, u8 port, HubFeatureSelector feature_selector)
{
// The check is done by UHCIRootHub.
VERIFY(port < NUMBER_OF_ROOT_PORTS);
dbgln_if(UHCI_DEBUG, "UHCI: clear_port_feature: port={} feature_selector={}", port, (u8)feature_selector);
u16 port_data = port == 0 ? read_portsc1() : read_portsc2();
port_data &= UCHI_PORTSC_NON_WRITE_CLEAR_BIT_MASK;
switch (feature_selector) {
case HubFeatureSelector::PORT_ENABLE:
port_data &= ~UHCI_PORTSC_PORT_ENABLED;
break;
case HubFeatureSelector::PORT_SUSPEND:
port_data &= ~UHCI_PORTSC_SUSPEND;
break;
case HubFeatureSelector::PORT_POWER:
// Ignore the request. UHCI ports are always powered.
break;
case HubFeatureSelector::C_PORT_CONNECTION:
// This field is Write Clear.
port_data |= UHCI_PORTSC_CONNECT_STATUS_CHANGED;
break;
case HubFeatureSelector::C_PORT_RESET:
m_port_reset_change_statuses &= ~(1 << port);
break;
case HubFeatureSelector::C_PORT_ENABLE:
// This field is Write Clear.
port_data |= UHCI_PORTSC_PORT_ENABLE_CHANGED;
break;
case HubFeatureSelector::C_PORT_SUSPEND:
m_port_suspend_change_statuses &= ~(1 << port);
break;
default:
dbgln("UHCI: Unknown feature selector in clear_port_feature: {}", (u8)feature_selector);
return EINVAL;
}
dbgln_if(UHCI_DEBUG, "UHCI: clear_port_feature: writing 0x{:04x} to portsc{}.", port_data, port + 1);
if (port == 0)
write_portsc1(port_data);
else
write_portsc2(port_data);
return KSuccess;
}
}