summaryrefslogtreecommitdiff
path: root/Kernel/Bus/USB
diff options
context:
space:
mode:
authorLuke <luke.wilde@live.co.uk>2021-08-13 20:47:13 +0100
committerAndreas Kling <kling@serenityos.org>2021-08-14 21:22:44 +0200
commitda0a1068e94bad11967de15b9bd36864acb5ac9a (patch)
tree7682ffaff09bcef6907c1f82c33fa84760170822 /Kernel/Bus/USB
parent9dcd146ab4a30bf72439854568fc464afc63db01 (diff)
downloadserenity-da0a1068e94bad11967de15b9bd36864acb5ac9a.zip
Kernel/USB: Add Hubs and the UHCI Root Hub
Diffstat (limited to 'Kernel/Bus/USB')
-rw-r--r--Kernel/Bus/USB/UHCIController.cpp271
-rw-r--r--Kernel/Bus/USB/UHCIController.h18
-rw-r--r--Kernel/Bus/USB/UHCIRootHub.cpp252
-rw-r--r--Kernel/Bus/USB/UHCIRootHub.h39
-rw-r--r--Kernel/Bus/USB/USBConstants.h16
-rw-r--r--Kernel/Bus/USB/USBDescriptors.h1
-rw-r--r--Kernel/Bus/USB/USBDevice.cpp70
-rw-r--r--Kernel/Bus/USB/USBDevice.h18
-rw-r--r--Kernel/Bus/USB/USBHub.cpp317
-rw-r--r--Kernel/Bus/USB/USBHub.h107
10 files changed, 1008 insertions, 101 deletions
diff --git a/Kernel/Bus/USB/UHCIController.cpp b/Kernel/Bus/USB/UHCIController.cpp
index bb197432f2..bbcd2d680f 100644
--- a/Kernel/Bus/USB/UHCIController.cpp
+++ b/Kernel/Bus/USB/UHCIController.cpp
@@ -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 (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));
- }
- }
- }
- }
+ if (m_root_hub)
+ m_root_hub->check_for_port_updates();
+
(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;
+}
+
}
diff --git a/Kernel/Bus/USB/UHCIController.h b/Kernel/Bus/USB/UHCIController.h
index 2dfc5235a4..975fdf0454 100644
--- a/Kernel/Bus/USB/UHCIController.h
+++ b/Kernel/Bus/USB/UHCIController.h
@@ -12,6 +12,7 @@
#include <AK/NonnullOwnPtr.h>
#include <Kernel/Bus/PCI/Device.h>
#include <Kernel/Bus/USB/UHCIDescriptorTypes.h>
+#include <Kernel/Bus/USB/UHCIRootHub.h>
#include <Kernel/Bus/USB/USBController.h>
#include <Kernel/IO.h>
#include <Kernel/Memory/AnonymousVMObject.h>
@@ -25,6 +26,7 @@ class UHCIController final
, public PCI::Device {
public:
+ static constexpr u8 NUMBER_OF_ROOT_PORTS = 2;
static KResultOr<NonnullRefPtr<UHCIController>> try_to_initialize(PCI::Address address);
virtual ~UHCIController() override;
@@ -43,6 +45,10 @@ public:
virtual RefPtr<USB::Device> const get_device_at_port(USB::Device::PortNumber) override;
virtual RefPtr<USB::Device> const get_device_from_address(u8 device_address) override;
+ void get_port_status(Badge<UHCIRootHub>, u8, HubStatus&);
+ KResult set_port_feature(Badge<UHCIRootHub>, u8, HubFeatureSelector);
+ KResult clear_port_feature(Badge<UHCIRootHub>, u8, HubFeatureSelector);
+
private:
explicit UHCIController(PCI::Address);
@@ -77,9 +83,13 @@ private:
QueueHead* allocate_queue_head() const;
TransferDescriptor* allocate_transfer_descriptor() const;
+ void reset_port(u8);
+
private:
IOAddress m_io_base;
+ OwnPtr<UHCIRootHub> m_root_hub;
+
Vector<QueueHead*> m_free_qh_pool;
Vector<TransferDescriptor*> m_free_td_pool;
Vector<TransferDescriptor*> m_iso_td_list;
@@ -94,7 +104,13 @@ private:
OwnPtr<Memory::Region> m_qh_pool;
OwnPtr<Memory::Region> m_td_pool;
- Array<RefPtr<USB::Device>, 2> m_devices; // Devices connected to the root ports (of which there are two)
+ // Bitfield containing whether a given port should signal a change in reset or not.
+ u8 m_port_reset_change_statuses { 0 };
+
+ // Bitfield containing whether a given port should signal a change in suspend or not.
+ u8 m_port_suspend_change_statuses { 0 };
+
+ Array<RefPtr<USB::Device>, NUMBER_OF_ROOT_PORTS> m_devices; // Devices connected to the root ports (of which there are two)
};
}
diff --git a/Kernel/Bus/USB/UHCIRootHub.cpp b/Kernel/Bus/USB/UHCIRootHub.cpp
new file mode 100644
index 0000000000..4b2fb34441
--- /dev/null
+++ b/Kernel/Bus/USB/UHCIRootHub.cpp
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <Kernel/Bus/USB/UHCIController.h>
+#include <Kernel/Bus/USB/UHCIRootHub.h>
+#include <Kernel/Bus/USB/USBClasses.h>
+#include <Kernel/Bus/USB/USBConstants.h>
+#include <Kernel/Bus/USB/USBEndpoint.h>
+#include <Kernel/Bus/USB/USBHub.h>
+#include <Kernel/Bus/USB/USBRequest.h>
+
+namespace Kernel::USB {
+
+static USBDeviceDescriptor uhci_root_hub_device_descriptor = {
+ sizeof(USBDeviceDescriptor), // 18 bytes long
+ DESCRIPTOR_TYPE_DEVICE,
+ 0x0110, // USB 1.1
+ (u8)USB_CLASS_HUB,
+ 0, // Hubs use subclass 0
+ 0, // Full Speed Hub
+ 64, // Max packet size
+ 0x0, // Vendor ID
+ 0x0, // Product ID
+ 0x0110, // Product version (can be anything, currently matching usb_spec_compliance_bcd)
+ 0, // Index of manufacturer string. FIXME: There is currently no support for string descriptors.
+ 0, // Index of product string. FIXME: There is currently no support for string descriptors.
+ 0, // Index of serial string. FIXME: There is currently no support for string descriptors.
+ 1, // One configuration descriptor
+};
+
+static USBConfigurationDescriptor uhci_root_hub_configuration_descriptor = {
+ sizeof(USBConfigurationDescriptor), // 9 bytes long
+ DESCRIPTOR_TYPE_CONFIGURATION,
+ sizeof(USBConfigurationDescriptor) + sizeof(USBInterfaceDescriptor) + sizeof(USBEndpointDescriptor) + sizeof(USBHubDescriptor), // Combined length of configuration, interface, endpoint and hub descriptors.
+ 1, // One interface descriptor
+ 1, // Configuration #1
+ 0, // Index of configuration string. FIXME: There is currently no support for string descriptors.
+ (1 << 7) | (1 << 6), // Bit 6 is set to indicate that the root hub is self powered. Bit 7 must always be 1.
+ 0, // 0 mA required from the bus (self-powered)
+};
+
+static USBInterfaceDescriptor uhci_root_hub_interface_descriptor = {
+ sizeof(USBInterfaceDescriptor), // 9 bytes long
+ DESCRIPTOR_TYPE_INTERFACE,
+ 0, // Interface #0
+ 0, // Alternate setting
+ 1, // One endpoint
+ (u8)USB_CLASS_HUB,
+ 0, // Hubs use subclass 0
+ 0, // Full Speed Hub
+ 0, // Index of interface string. FIXME: There is currently no support for string descriptors
+};
+
+static USBEndpointDescriptor uhci_root_hub_endpoint_descriptor = {
+ sizeof(USBEndpointDescriptor), // 7 bytes long
+ DESCRIPTOR_TYPE_ENDPOINT,
+ USBEndpoint::ENDPOINT_ADDRESS_DIRECTION_IN | 1, // IN Endpoint #1
+ USBEndpoint::ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_INTERRUPT, // Interrupt endpoint
+ 2, // Max Packet Size FIXME: I'm not sure what this is supposed to be as it is implementation defined. 2 is the number of bytes Get Port Status returns.
+ 0xFF, // Max possible interval
+};
+
+// NOTE: UHCI does not provide us anything for the Root Hub's Hub Descriptor.
+static USBHubDescriptor uhci_root_hub_hub_descriptor = {
+ sizeof(USBHubDescriptor), // 7 bytes long. FIXME: Add the size of the VLAs at the end once they're supported.
+ DESCRIPTOR_TYPE_HUB,
+ UHCIController::NUMBER_OF_ROOT_PORTS, // 2 ports
+ 0x0, // Ganged power switching, not a compound device, global over-current protection.
+ 0x0, // UHCI ports are always powered, so there's no time from power on to power good.
+ 0x0, // Self-powered
+};
+
+KResultOr<NonnullOwnPtr<UHCIRootHub>> UHCIRootHub::try_create(NonnullRefPtr<UHCIController> uhci_controller)
+{
+ auto root_hub = adopt_own_if_nonnull(new (nothrow) UHCIRootHub(uhci_controller));
+ if (!root_hub)
+ return ENOMEM;
+
+ return root_hub.release_nonnull();
+}
+
+UHCIRootHub::UHCIRootHub(NonnullRefPtr<UHCIController> uhci_controller)
+ : m_uhci_controller(uhci_controller)
+{
+}
+
+KResult UHCIRootHub::setup(Badge<UHCIController>)
+{
+ auto hub_or_error = Hub::try_create_root_hub(m_uhci_controller, Device::DeviceSpeed::FullSpeed);
+ if (hub_or_error.is_error())
+ return hub_or_error.error();
+
+ m_hub = hub_or_error.release_value();
+
+ // NOTE: The root hub will be on the default address at this point.
+ // The root hub must be the first device to be created, otherwise the HCD will intercept all default address transfers as though they're targeted at the root hub.
+ auto result = m_hub->enumerate_device();
+ if (result.is_error())
+ return result;
+
+ // NOTE: The root hub is no longer on the default address.
+ result = m_hub->enumerate_and_power_on_hub();
+ if (result.is_error())
+ return result;
+
+ return KSuccess;
+}
+
+KResultOr<size_t> UHCIRootHub::handle_control_transfer(Transfer& transfer)
+{
+ auto& request = transfer.request();
+ auto* request_data = transfer.buffer().as_ptr() + sizeof(USBRequestData);
+
+ if constexpr (UHCI_DEBUG) {
+ dbgln("UHCIRootHub: Received control transfer.");
+ dbgln("UHCIRootHub: Request Type: 0x{:02x}", request.request_type);
+ dbgln("UHCIRootHub: Request: 0x{:02x}", request.request);
+ dbgln("UHCIRootHub: Value: 0x{:04x}", request.value);
+ dbgln("UHCIRootHub: Index: 0x{:04x}", request.index);
+ dbgln("UHCIRootHub: Length: 0x{:04x}", request.length);
+ }
+
+ size_t length = 0;
+
+ switch (request.request) {
+ case HubRequest::GET_STATUS: {
+ if (request.index > UHCIController::NUMBER_OF_ROOT_PORTS)
+ return EINVAL;
+
+ length = min(transfer.transfer_data_size(), sizeof(HubStatus));
+ VERIFY(length <= sizeof(HubStatus));
+ HubStatus hub_status {};
+
+ if (request.index == 0) {
+ // If index == 0, the actual request is Get Hub Status
+ // UHCI does not provide "Local Power Source" or "Over-current" and their corresponding change flags.
+ // The members of hub_status are initialized to 0, so we can memcpy it straight away.
+ memcpy(request_data, (void*)&hub_status, length);
+ break;
+ }
+
+ // If index != 0, the actual request is Get Port Status
+ m_uhci_controller->get_port_status({}, request.index - 1, hub_status);
+ memcpy(request_data, (void*)&hub_status, length);
+ break;
+ }
+ case HubRequest::GET_DESCRIPTOR: {
+ u8 descriptor_type = request.value >> 8;
+ switch (descriptor_type) {
+ case DESCRIPTOR_TYPE_DEVICE:
+ length = min(transfer.transfer_data_size(), sizeof(USBDeviceDescriptor));
+ VERIFY(length <= sizeof(USBDeviceDescriptor));
+ memcpy(request_data, (void*)&uhci_root_hub_device_descriptor, length);
+ break;
+ case DESCRIPTOR_TYPE_CONFIGURATION:
+ length = min(transfer.transfer_data_size(), sizeof(USBConfigurationDescriptor));
+ VERIFY(length <= sizeof(USBConfigurationDescriptor));
+ memcpy(request_data, (void*)&uhci_root_hub_configuration_descriptor, length);
+ break;
+ case DESCRIPTOR_TYPE_INTERFACE:
+ length = min(transfer.transfer_data_size(), sizeof(USBInterfaceDescriptor));
+ VERIFY(length <= sizeof(USBInterfaceDescriptor));
+ memcpy(request_data, (void*)&uhci_root_hub_interface_descriptor, length);
+ break;
+ case DESCRIPTOR_TYPE_ENDPOINT:
+ length = min(transfer.transfer_data_size(), sizeof(USBEndpointDescriptor));
+ VERIFY(length <= sizeof(USBEndpointDescriptor));
+ memcpy(request_data, (void*)&uhci_root_hub_endpoint_descriptor, length);
+ break;
+ case DESCRIPTOR_TYPE_HUB:
+ length = min(transfer.transfer_data_size(), sizeof(USBHubDescriptor));
+ VERIFY(length <= sizeof(USBHubDescriptor));
+ memcpy(request_data, (void*)&uhci_root_hub_hub_descriptor, length);
+ break;
+ default:
+ return EINVAL;
+ }
+ break;
+ }
+ case USB_REQUEST_SET_ADDRESS:
+ dbgln_if(UHCI_DEBUG, "UHCIRootHub: Attempt to set address to {}, ignoring.", request.value);
+ if (request.value > USB_MAX_ADDRESS)
+ return EINVAL;
+ // Ignore SET_ADDRESS requests. USBDevice sets its internal address to the new allocated address that it just sent to us.
+ // The internal address is used to check if the request is directed at the root hub or not.
+ break;
+ case HubRequest::SET_FEATURE: {
+ if (request.index == 0) {
+ // If index == 0, the actual request is Set Hub Feature.
+ // UHCI does not provide "Local Power Source" or "Over-current" and their corresponding change flags.
+ // Therefore, ignore the request, but return an error if the value is not "Local Power Source" or "Over-current"
+ switch (request.value) {
+ case HubFeatureSelector::C_HUB_LOCAL_POWER:
+ case HubFeatureSelector::C_HUB_OVER_CURRENT:
+ break;
+ default:
+ return EINVAL;
+ }
+
+ break;
+ }
+
+ // If index != 0, the actual request is Set Port Feature.
+ u8 port = request.index & 0xFF;
+ if (port > UHCIController::NUMBER_OF_ROOT_PORTS)
+ return EINVAL;
+
+ auto feature_selector = (HubFeatureSelector)request.value;
+ auto result = m_uhci_controller->set_port_feature({}, port - 1, feature_selector);
+ if (result.is_error())
+ return result.error();
+ break;
+ }
+ case HubRequest::CLEAR_FEATURE: {
+ if (request.index == 0) {
+ // If index == 0, the actual request is Clear Hub Feature.
+ // UHCI does not provide "Local Power Source" or "Over-current" and their corresponding change flags.
+ // Therefore, ignore the request, but return an error if the value is not "Local Power Source" or "Over-current"
+ switch (request.value) {
+ case HubFeatureSelector::C_HUB_LOCAL_POWER:
+ case HubFeatureSelector::C_HUB_OVER_CURRENT:
+ break;
+ default:
+ return EINVAL;
+ }
+
+ break;
+ }
+
+ // If index != 0, the actual request is Clear Port Feature.
+ u8 port = request.index & 0xFF;
+ if (port > UHCIController::NUMBER_OF_ROOT_PORTS)
+ return EINVAL;
+
+ auto feature_selector = (HubFeatureSelector)request.value;
+ auto result = m_uhci_controller->clear_port_feature({}, port - 1, feature_selector);
+ if (result.is_error())
+ return result.error();
+ break;
+ }
+ default:
+ return EINVAL;
+ }
+
+ transfer.set_complete();
+ return length;
+}
+
+}
diff --git a/Kernel/Bus/USB/UHCIRootHub.h b/Kernel/Bus/USB/UHCIRootHub.h
new file mode 100644
index 0000000000..964b4be736
--- /dev/null
+++ b/Kernel/Bus/USB/UHCIRootHub.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/NonnullOwnPtr.h>
+#include <AK/NonnullRefPtr.h>
+#include <Kernel/Bus/USB/USBHub.h>
+#include <Kernel/Bus/USB/USBTransfer.h>
+#include <Kernel/KResult.h>
+
+namespace Kernel::USB {
+
+class UHCIController;
+
+class UHCIRootHub {
+public:
+ static KResultOr<NonnullOwnPtr<UHCIRootHub>> try_create(NonnullRefPtr<UHCIController>);
+
+ UHCIRootHub(NonnullRefPtr<UHCIController>);
+ ~UHCIRootHub() = default;
+
+ KResult setup(Badge<UHCIController>);
+
+ u8 device_address() const { return m_hub->address(); }
+
+ KResultOr<size_t> handle_control_transfer(Transfer& transfer);
+
+ void check_for_port_updates() { m_hub->check_for_port_updates(); }
+
+private:
+ NonnullRefPtr<UHCIController> m_uhci_controller;
+ RefPtr<Hub> m_hub;
+};
+
+}
diff --git a/Kernel/Bus/USB/USBConstants.h b/Kernel/Bus/USB/USBConstants.h
new file mode 100644
index 0000000000..98747c315c
--- /dev/null
+++ b/Kernel/Bus/USB/USBConstants.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Types.h>
+
+namespace Kernel::USB {
+
+// USB 2.0 Specification Section 9.4.6
+static constexpr u8 USB_MAX_ADDRESS = 127;
+
+}
diff --git a/Kernel/Bus/USB/USBDescriptors.h b/Kernel/Bus/USB/USBDescriptors.h
index b806bfa34d..b703261283 100644
--- a/Kernel/Bus/USB/USBDescriptors.h
+++ b/Kernel/Bus/USB/USBDescriptors.h
@@ -39,6 +39,7 @@ struct [[gnu::packed]] USBDeviceDescriptor {
u8 serial_number_descriptor_index;
u8 num_configurations;
};
+static_assert(sizeof(USBDeviceDescriptor) == 18);
//
// Configuration Descriptor
diff --git a/Kernel/Bus/USB/USBDevice.cpp b/Kernel/Bus/USB/USBDevice.cpp
index b4866ca96d..69586c3129 100644
--- a/Kernel/Bus/USB/USBDevice.cpp
+++ b/Kernel/Bus/USB/USBDevice.cpp
@@ -26,7 +26,7 @@ KResultOr<NonnullRefPtr<Device>> Device::try_create(USBController const& control
if (!device)
return ENOMEM;
- auto enumerate_result = device->enumerate();
+ auto enumerate_result = device->enumerate_device();
if (enumerate_result.is_error())
return enumerate_result;
@@ -42,20 +42,58 @@ Device::Device(USBController const& controller, PortNumber port, DeviceSpeed spe
{
}
-KResult Device::enumerate()
+Device::Device(NonnullRefPtr<USBController> controller, u8 address, PortNumber port, DeviceSpeed speed, NonnullOwnPtr<Pipe> default_pipe)
+ : m_device_port(port)
+ , m_device_speed(speed)
+ , m_address(address)
+ , m_controller(controller)
+ , m_default_pipe(move(default_pipe))
+{
+}
+
+Device::Device(Device const& device, NonnullOwnPtr<Pipe> default_pipe)
+ : m_device_port(device.port())
+ , m_device_speed(device.speed())
+ , m_address(device.address())
+ , m_device_descriptor(device.device_descriptor())
+ , m_controller(device.controller())
+ , m_default_pipe(move(default_pipe))
+{
+}
+
+Device::~Device()
+{
+}
+
+KResult Device::enumerate_device()
{
USBDeviceDescriptor dev_descriptor {};
// Send 8-bytes to get at least the `max_packet_size` from the device
- auto transfer_length_or_error = m_default_pipe->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST, USB_REQUEST_GET_DESCRIPTOR, (DESCRIPTOR_TYPE_DEVICE << 8), 0, 8, &dev_descriptor);
+ constexpr u8 short_device_descriptor_length = 8;
+ auto transfer_length_or_error = m_default_pipe->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST, USB_REQUEST_GET_DESCRIPTOR, (DESCRIPTOR_TYPE_DEVICE << 8), 0, short_device_descriptor_length, &dev_descriptor);
if (transfer_length_or_error.is_error())
return transfer_length_or_error.error();
auto transfer_length = transfer_length_or_error.release_value();
- // FIXME: This shouldn't crash! Do some correct error handling on me please!
- VERIFY(transfer_length > 0);
+ // FIXME: This be "not equal to" instead of "less than", but control transfers report a higher transfer length than expected.
+ if (transfer_length < short_device_descriptor_length) {
+ dbgln("USB Device: Not enough bytes for short device descriptor. Expected {}, got {}.", short_device_descriptor_length, transfer_length);
+ return EIO;
+ }
+
+ if constexpr (USB_DEBUG) {
+ dbgln("USB Short Device Descriptor:");
+ dbgln("Descriptor length: {}", dev_descriptor.descriptor_header.length);
+ dbgln("Descriptor type: {}", dev_descriptor.descriptor_header.descriptor_type);
+
+ dbgln("Device Class: {:02x}", dev_descriptor.device_class);
+ dbgln("Device Sub-Class: {:02x}", dev_descriptor.device_sub_class);
+ dbgln("Device Protocol: {:02x}", dev_descriptor.device_protocol);
+ dbgln("Max Packet Size: {:02x} bytes", dev_descriptor.max_packet_size);
+ }
// Ensure that this is actually a valid device descriptor...
VERIFY(dev_descriptor.descriptor_header.descriptor_type == DESCRIPTOR_TYPE_DEVICE);
@@ -68,8 +106,11 @@ KResult Device::enumerate()
transfer_length = transfer_length_or_error.release_value();
- // FIXME: This shouldn't crash! Do some correct error handling on me please!
- VERIFY(transfer_length > 0);
+ // FIXME: This be "not equal to" instead of "less than", but control transfers report a higher transfer length than expected.
+ if (transfer_length < sizeof(USBDeviceDescriptor)) {
+ dbgln("USB Device: Unexpected device descriptor length. Expected {}, got {}.", sizeof(USBDeviceDescriptor), transfer_length);
+ return EIO;
+ }
// Ensure that this is actually a valid device descriptor...
VERIFY(dev_descriptor.descriptor_header.descriptor_type == DESCRIPTOR_TYPE_DEVICE);
@@ -83,24 +124,23 @@ KResult Device::enumerate()
dbgln("Number of configurations: {:02x}", dev_descriptor.num_configurations);
}
- m_address = m_controller->allocate_address();
+ auto new_address = m_controller->allocate_address();
// Attempt to set devices address on the bus
- transfer_length_or_error = m_default_pipe->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE, USB_REQUEST_SET_ADDRESS, m_address, 0, 0, nullptr);
+ transfer_length_or_error = m_default_pipe->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE, USB_REQUEST_SET_ADDRESS, new_address, 0, 0, nullptr);
if (transfer_length_or_error.is_error())
return transfer_length_or_error.error();
- transfer_length = transfer_length_or_error.release_value();
+ // This has to be set after we send out the "Set Address" request because it might be sent to the root hub.
+ // The root hub uses the address to intercept requests to itself.
+ m_address = new_address;
+ m_default_pipe->set_device_address(new_address);
- VERIFY(transfer_length > 0);
+ dbgln_if(USB_DEBUG, "USB Device: Set address to {}", m_address);
memcpy(&m_device_descriptor, &dev_descriptor, sizeof(USBDeviceDescriptor));
return KSuccess;
}
-Device::~Device()
-{
-}
-
}
diff --git a/Kernel/Bus/USB/USBDevice.h b/Kernel/Bus/USB/USBDevice.h
index e31f517957..4d23b54310 100644
--- a/Kernel/Bus/USB/USBDevice.h
+++ b/Kernel/Bus/USB/USBDevice.h
@@ -35,9 +35,10 @@ public:
static KResultOr<NonnullRefPtr<Device>> try_create(USBController const&, PortNumber, DeviceSpeed);
Device(USBController const&, PortNumber, DeviceSpeed, NonnullOwnPtr<Pipe> default_pipe);
- ~Device();
+ Device(Device const& device, NonnullOwnPtr<Pipe> default_pipe);
+ virtual ~Device();
- KResult enumerate();
+ KResult enumerate_device();
PortNumber port() const { return m_device_port; }
DeviceSpeed speed() const { return m_device_speed; }
@@ -46,7 +47,12 @@ public:
const USBDeviceDescriptor& device_descriptor() const { return m_device_descriptor; }
-private:
+ USBController& controller() { return *m_controller; }
+ USBController const& controller() const { return *m_controller; }
+
+protected:
+ Device(NonnullRefPtr<USBController> controller, u8 address, PortNumber port, DeviceSpeed speed, NonnullOwnPtr<Pipe> default_pipe);
+
PortNumber m_device_port; // What port is this device attached to
DeviceSpeed m_device_speed; // What speed is this device running at
u8 m_address { 0 }; // USB address assigned to this device
@@ -58,5 +64,11 @@ private:
NonnullRefPtr<USBController> m_controller;
NonnullOwnPtr<Pipe> m_default_pipe; // Default communication pipe (endpoint0) used during enumeration
+
+private:
+ IntrusiveListNode<Device, NonnullRefPtr<Device>> m_hub_child_node;
+
+public:
+ using List = IntrusiveList<Device, NonnullRefPtr<Device>, &Device::m_hub_child_node>;
};
}
diff --git a/Kernel/Bus/USB/USBHub.cpp b/Kernel/Bus/USB/USBHub.cpp
new file mode 100644
index 0000000000..1b35a05b47
--- /dev/null
+++ b/Kernel/Bus/USB/USBHub.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <Kernel/Bus/USB/USBClasses.h>
+#include <Kernel/Bus/USB/USBController.h>
+#include <Kernel/Bus/USB/USBHub.h>
+#include <Kernel/Bus/USB/USBRequest.h>
+#include <Kernel/StdLib.h>
+
+namespace Kernel::USB {
+
+KResultOr<NonnullRefPtr<Hub>> Hub::try_create_root_hub(NonnullRefPtr<USBController> controller, DeviceSpeed device_speed)
+{
+ auto pipe_or_error = Pipe::try_create_pipe(controller, Pipe::Type::Control, Pipe::Direction::Bidirectional, 0, 8, 0);
+ if (pipe_or_error.is_error())
+ return pipe_or_error.error();
+
+ auto hub = AK::try_create<Hub>(controller, device_speed, pipe_or_error.release_value());
+ if (!hub)
+ return ENOMEM;
+
+ // NOTE: Enumeration does not happen here, as the controller must know what the device address is at all times during enumeration to intercept requests.
+
+ return hub.release_nonnull();
+}
+
+KResultOr<NonnullRefPtr<Hub>> Hub::try_create_from_device(Device const& device)
+{
+ auto pipe_or_error = Pipe::try_create_pipe(device.controller(), Pipe::Type::Control, Pipe::Direction::Bidirectional, 0, device.device_descriptor().max_packet_size, device.address());
+ if (pipe_or_error.is_error())
+ return pipe_or_error.error();
+
+ auto hub = AK::try_create<Hub>(device, pipe_or_error.release_value());
+ if (!hub)
+ return ENOMEM;
+
+ auto result = hub->enumerate_and_power_on_hub();
+ if (result.is_error())
+ return result;
+
+ return hub.release_nonnull();
+}
+
+Hub::Hub(NonnullRefPtr<USBController> controller, DeviceSpeed device_speed, NonnullOwnPtr<Pipe> default_pipe)
+ : Device(move(controller), PortNumber::Port1, device_speed, move(default_pipe))
+{
+}
+
+Hub::Hub(Device const& device, NonnullOwnPtr<Pipe> default_pipe)
+ : Device(device, move(default_pipe))
+{
+}
+
+KResult Hub::enumerate_and_power_on_hub()
+{
+ // USBDevice::enumerate_device must be called before this.
+ VERIFY(m_address > 0);
+
+ if (m_device_descriptor.device_class != USB_CLASS_HUB) {
+ dbgln("USB Hub: Trying to enumerate and power on a device that says it isn't a hub.");
+ return EINVAL;
+ }
+
+ dbgln_if(USB_DEBUG, "USB Hub: Enumerating and powering on for address {}", m_address);
+
+ USBHubDescriptor descriptor {};
+
+ // Get the first hub descriptor. All hubs are required to have a hub descriptor at index 0. USB 2.0 Specification Section 11.24.2.5.
+ auto transfer_length_or_error = m_default_pipe->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST | USB_REQUEST_TYPE_CLASS, HubRequest::GET_DESCRIPTOR, (DESCRIPTOR_TYPE_HUB << 8), 0, sizeof(USBHubDescriptor), &descriptor);
+ if (transfer_length_or_error.is_error())
+ return transfer_length_or_error.error();
+
+ // FIXME: This be "not equal to" instead of "less than", but control transfers report a higher transfer length than expected.
+ if (transfer_length_or_error.value() < sizeof(USBHubDescriptor)) {
+ dbgln("USB Hub: Unexpected hub descriptor size. Expected {}, got {}", sizeof(USBHubDescriptor), transfer_length_or_error.value());
+ return EIO;
+ }
+
+ if constexpr (USB_DEBUG) {
+ dbgln("USB Hub Descriptor for {:04x}:{:04x}", m_vendor_id, m_product_id);
+ dbgln("Number of Downstream Ports: {}", descriptor.number_of_downstream_ports);
+ dbgln("Hub Characteristics: 0x{:04x}", descriptor.hub_characteristics);
+ dbgln("Power On to Power Good Time: {} ms ({} * 2ms)", descriptor.power_on_to_power_good_time * 2, descriptor.power_on_to_power_good_time);
+ dbgln("Hub Controller Current: {} mA", descriptor.hub_controller_current);
+ }
+
+ // FIXME: Queue the status change interrupt
+
+ // Enable all the ports
+ for (u8 port_index = 0; port_index < descriptor.number_of_downstream_ports; ++port_index) {
+ auto result = m_default_pipe->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE | USB_REQUEST_TYPE_CLASS | USB_REQUEST_RECIPIENT_OTHER, HubRequest::SET_FEATURE, HubFeatureSelector::PORT_POWER, port_index + 1, 0, nullptr);
+ if (result.is_error())
+ dbgln("USB: Failed to power on port {} on hub at address {}.", port_index + 1, m_address);
+ }
+
+ // Wait for the ports to power up. power_on_to_power_good_time is in units of 2 ms and we want in us, so multiply by 2000.
+ IO::delay(descriptor.power_on_to_power_good_time * 2000);
+
+ memcpy(&m_hub_descriptor, &descriptor, sizeof(USBHubDescriptor));
+
+ return KSuccess;
+}
+
+// USB 2.0 Specification Section 11.24.2.7
+KResult Hub::get_port_status(u8 port, HubStatus& hub_status)
+{
+ // Ports are 1-based.
+ if (port == 0 || port > m_hub_descriptor.number_of_downstream_ports)
+ return EINVAL;
+
+ auto transfer_length_or_error = m_default_pipe->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST | USB_REQUEST_TYPE_CLASS | USB_REQUEST_RECIPIENT_OTHER, HubRequest::GET_STATUS, 0, port, sizeof(HubStatus), &hub_status);
+ if (transfer_length_or_error.is_error())
+ return transfer_length_or_error.error();
+
+ // FIXME: This be "not equal to" instead of "less than", but control transfers report a higher transfer length than expected.
+ if (transfer_length_or_error.value() < sizeof(HubStatus)) {
+ dbgln("USB Hub: Unexpected hub status size. Expected {}, got {}.", sizeof(HubStatus), transfer_length_or_error.value());
+ return EIO;
+ }
+
+ return KSuccess;
+}
+
+// USB 2.0 Specification Section 11.24.2.2
+KResult Hub::clear_port_feature(u8 port, HubFeatureSelector feature_selector)
+{
+ // Ports are 1-based.
+ if (port == 0 || port > m_hub_descriptor.number_of_downstream_ports)
+ return EINVAL;
+
+ auto result = m_default_pipe->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE | USB_REQUEST_TYPE_CLASS | USB_REQUEST_RECIPIENT_OTHER, HubRequest::CLEAR_FEATURE, feature_selector, port, 0, nullptr);
+ if (result.is_error())
+ return result.error();
+
+ return KSuccess;
+}
+
+// USB 2.0 Specification Section 11.24.2.13
+KResult Hub::set_port_feature(u8 port, HubFeatureSelector feature_selector)
+{
+ // Ports are 1-based.
+ if (port == 0 || port > m_hub_descriptor.number_of_downstream_ports)
+ return EINVAL;
+
+ auto result = m_default_pipe->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE | USB_REQUEST_TYPE_CLASS | USB_REQUEST_RECIPIENT_OTHER, HubRequest::SET_FEATURE, feature_selector, port, 0, nullptr);
+ if (result.is_error())
+ return result.error();
+
+ return KSuccess;
+}
+
+void Hub::check_for_port_updates()
+{
+ for (u8 port_number = 1; port_number < m_hub_descriptor.number_of_downstream_ports + 1; ++port_number) {
+ dbgln_if(USB_DEBUG, "USB Hub: Checking for port updates on port {}...", port_number);
+
+ HubStatus port_status {};
+ auto result = get_port_status(port_number, port_status);
+ if (result.is_error()) {
+ dbgln("USB Hub: Error occurred when getting status for port {}: {}. Checking next port instead.", port_number, result.error());
+ continue;
+ }
+
+ if (port_status.change & PORT_STATUS_CONNECT_STATUS_CHANGED) {
+ // Clear the connection status change notification.
+ result = clear_port_feature(port_number, HubFeatureSelector::C_PORT_CONNECTION);
+ if (result.is_error()) {
+ dbgln("USB Hub: Error occurred when clearing port connection change for port {}: {}.", port_number, result.error());
+ return;
+ }
+
+ if (port_status.status & PORT_STATUS_CURRENT_CONNECT_STATUS) {
+ dbgln("USB Hub: Device attached to port {}!", port_number);
+
+ // Debounce the port. USB 2.0 Specification Page 150
+ // Debounce interval is 100 ms (100000 us). USB 2.0 Specification Page 188 Table 7-14.
+ constexpr u32 debounce_interval = 100 * 1000;
+
+ // We must check if the device disconnected every so often. If it disconnects, we must reset the debounce timer.
+ // This doesn't seem to be specified. Let's check every 10ms (10000 us).
+ constexpr u32 debounce_disconnect_check_interval = 10 * 1000;
+
+ u32 debounce_timer = 0;
+
+ dbgln_if(USB_DEBUG, "USB Hub: Debouncing...");
+
+ // FIXME: Timeout
+ while (debounce_timer < debounce_interval) {
+ IO::delay(debounce_disconnect_check_interval);
+ debounce_timer += debounce_disconnect_check_interval;
+
+ result = get_port_status(port_number, port_status);
+ if (result.is_error()) {
+ dbgln("USB Hub: Error occurred when getting status while debouncing port {}: {}.", port_number, result.error());
+ return;
+ }
+
+ if (!(port_status.change & PORT_STATUS_CONNECT_STATUS_CHANGED))
+ continue;
+
+ dbgln_if(USB_DEBUG, "USB Hub: Connection status changed while debouncing, resetting debounce timer.");
+ debounce_timer = 0;
+ result = clear_port_feature(port_number, HubFeatureSelector::C_PORT_CONNECTION);
+ if (result.is_error()) {
+ dbgln("USB Hub: Error occurred when clearing port connection change while debouncing port {}: {}.", port_number, result.error());
+ return;
+ }
+ }
+
+ // Reset the port
+ dbgln_if(USB_DEBUG, "USB Hub: Debounce finished. Driving reset...");
+ result = set_port_feature(port_number, HubFeatureSelector::PORT_RESET);
+ if (result.is_error()) {
+ dbgln("USB Hub: Error occurred when resetting port {}: {}.", port_number, result.error());
+ return;
+ }
+
+ // FIXME: Timeout
+ for (;;) {
+ // Wait at least 10 ms for the port to reset.
+ // This is T DRST in the USB 2.0 Specification Page 186 Table 7-13.
+ constexpr u16 reset_delay = 10 * 1000;
+ IO::delay(reset_delay);
+
+ result = get_port_status(port_number, port_status);
+ if (result.is_error()) {
+ dbgln("USB Hub: Error occurred when getting status while resetting port {}: {}.", port_number, result.error());
+ return;
+ }
+
+ if (port_status.change & PORT_STATUS_RESET_CHANGED)
+ break;
+ }
+
+ // Stop asserting reset. This also causes the port to become enabled.
+ result = clear_port_feature(port_number, HubFeatureSelector::C_PORT_RESET);
+ if (result.is_error()) {
+ dbgln("USB Hub: Error occurred when resetting port {}: {}.", port_number, result.error());
+ return;
+ }
+
+ // 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);
+
+ dbgln_if(USB_DEBUG, "USB Hub: Reset complete!");
+
+ // The port is ready to go. This is where we start communicating with the device to set up a driver for it.
+
+ result = get_port_status(port_number, port_status);
+ if (result.is_error()) {
+ dbgln("USB Hub: Error occurred when getting status for port {} after reset: {}.", port_number, result.error());
+ return;
+ }
+
+ // FIXME: Check for high speed.
+ auto speed = port_status.status & PORT_STATUS_LOW_SPEED_DEVICE_ATTACHED ? USB::Device::DeviceSpeed::LowSpeed : USB::Device::DeviceSpeed::FullSpeed;
+
+ // FIXME: This only assumes two ports.
+ auto device_or_error = USB::Device::try_create(m_controller, port_number == 1 ? PortNumber::Port1 : PortNumber::Port2, speed);
+ if (device_or_error.is_error()) {
+ dbgln("USB Hub: Failed to create device for port {}: {}", port_number, device_or_error.error());
+ return;
+ }
+
+ auto device = device_or_error.release_value();
+
+ dbgln_if(USB_DEBUG, "USB Hub: Created device with address {}!", device->address());
+
+ if (device->device_descriptor().device_class == USB_CLASS_HUB) {
+ auto hub_or_error = Hub::try_create_from_device(*device);
+ if (hub_or_error.is_error()) {
+ dbgln("USB Hub: Failed to upgrade device to hub for port {}: {}", port_number, device_or_error.error());
+ return;
+ }
+
+ dbgln_if(USB_DEBUG, "USB Hub: Upgraded device at address {} to hub!", device->address());
+
+ m_children.append(hub_or_error.release_value());
+ } else {
+ m_children.append(device);
+ }
+
+ } else {
+ dbgln("USB Hub: Device detached on port {}!", port_number);
+
+ Device* device_to_remove = nullptr;
+ for (auto& child : m_children) {
+ // FIXME: This kinda sucks.
+ if (port_number - 1 == (u8)child.port()) {
+ device_to_remove = &child;
+ break;
+ }
+ }
+
+ if (device_to_remove)
+ m_children.remove(*device_to_remove);
+ else
+ dbgln_if(USB_DEBUG, "USB Hub: No child set up on port {}, ignoring detachment.", port_number);
+ }
+ }
+ }
+
+ for (auto& child : m_children) {
+ if (child.device_descriptor().device_class == USB_CLASS_HUB) {
+ auto& hub_child = static_cast<Hub&>(child);
+ dbgln_if(USB_DEBUG, "USB Hub: Checking for port updates on child hub at address {}...", child.address());
+ hub_child.check_for_port_updates();
+ }
+ }
+}
+
+}
diff --git a/Kernel/Bus/USB/USBHub.h b/Kernel/Bus/USB/USBHub.h
new file mode 100644
index 0000000000..5ba10c9b0c
--- /dev/null
+++ b/Kernel/Bus/USB/USBHub.h
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/RefCounted.h>
+#include <AK/Types.h>
+#include <Kernel/Bus/USB/USBDevice.h>
+
+namespace Kernel::USB {
+
+// USB 2.0 Specification Page 421 Table 11-16
+enum HubRequest : u8 {
+ GET_STATUS = 0,
+ CLEAR_FEATURE = 1,
+ // 2 is reserved.
+ SET_FEATURE = 3,
+ // 4-5 are reserved.
+ GET_DESCRIPTOR = 6,
+ SET_DESCRIPTOR = 7,
+ CLEAR_TT_BUFFER = 8,
+ RESET_TT = 9,
+ GET_TT_STATE = 10,
+ STOP_TT = 11,
+};
+
+// USB 2.0 Specification Pages 421-422 Table 11-17
+enum HubFeatureSelector : u8 {
+ C_HUB_LOCAL_POWER = 0,
+ C_HUB_OVER_CURRENT = 1,
+ PORT_CONNECTION = 0,
+ PORT_ENABLE = 1,
+ PORT_SUSPEND = 2,
+ PORT_OVER_CURRENT = 3,
+ PORT_RESET = 4,
+ PORT_POWER = 8,
+ PORT_LOW_SPEED = 9,
+ C_PORT_CONNECTION = 16,
+ C_PORT_ENABLE = 17,
+ C_PORT_SUSPEND = 18,
+ C_PORT_OVER_CURRENT = 19,
+ C_PORT_RESET = 20,
+ PORT_TEST = 21,
+ PORT_INDICATOR = 22,
+};
+
+// USB 2.0 Specification Section 11.24.2.{6,7}
+// This is used to store both the hub status and port status, as they have the same layout.
+struct [[gnu::packed]] HubStatus {
+ u16 status { 0 };
+ u16 change { 0 };
+};
+static_assert(sizeof(HubStatus) == 4);
+
+static constexpr u16 HUB_STATUS_LOCAL_POWER_SOURCE = (1 << 0);
+static constexpr u16 HUB_STATUS_OVER_CURRENT = (1 << 1);
+
+static constexpr u16 HUB_STATUS_LOCAL_POWER_SOURCE_CHANGED = (1 << 0);
+static constexpr u16 HUB_STATUS_OVER_CURRENT_CHANGED = (1 << 1);
+
+static constexpr u16 PORT_STATUS_CURRENT_CONNECT_STATUS = (1 << 0);
+static constexpr u16 PORT_STATUS_PORT_ENABLED = (1 << 1);
+static constexpr u16 PORT_STATUS_SUSPEND = (1 << 2);
+static constexpr u16 PORT_STATUS_OVER_CURRENT = (1 << 3);
+static constexpr u16 PORT_STATUS_RESET = (1 << 4);
+static constexpr u16 PORT_STATUS_PORT_POWER = (1 << 8);
+static constexpr u16 PORT_STATUS_LOW_SPEED_DEVICE_ATTACHED = (1 << 9);
+static constexpr u16 PORT_STATUS_HIGH_SPEED_DEVICE_ATTACHED = (1 << 10);
+static constexpr u16 PORT_STATUS_PORT_STATUS_MODE = (1 << 11);
+static constexpr u16 PORT_STATUS_PORT_INDICATOR_CONTROL = (1 << 12);
+
+static constexpr u16 PORT_STATUS_CONNECT_STATUS_CHANGED = (1 << 0);
+static constexpr u16 PORT_STATUS_PORT_ENABLED_CHANGED = (1 << 1);
+static constexpr u16 PORT_STATUS_SUSPEND_CHANGED = (1 << 2);
+static constexpr u16 PORT_STATUS_OVER_CURRENT_INDICATOR_CHANGED = (1 << 3);
+static constexpr u16 PORT_STATUS_RESET_CHANGED = (1 << 4);
+
+class Hub : public Device {
+public:
+ static KResultOr<NonnullRefPtr<Hub>> try_create_root_hub(NonnullRefPtr<USBController>, DeviceSpeed);
+ static KResultOr<NonnullRefPtr<Hub>> try_create_from_device(Device const&);
+
+ // Root Hub constructor
+ Hub(NonnullRefPtr<USBController>, DeviceSpeed, NonnullOwnPtr<Pipe> default_pipe);
+ Hub(Device const&, NonnullOwnPtr<Pipe> default_pipe);
+ virtual ~Hub() override = default;
+
+ KResult enumerate_and_power_on_hub();
+
+ KResult get_port_status(u8, HubStatus&);
+ KResult clear_port_feature(u8, HubFeatureSelector);
+ KResult set_port_feature(u8, HubFeatureSelector);
+
+ KResult reset_port(u8);
+
+ void check_for_port_updates();
+
+private:
+ USBHubDescriptor m_hub_descriptor;
+
+ Device::List m_children;
+};
+
+}