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;
}
}

View file

@ -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)
};
}

View file

@ -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;
}
}

View file

@ -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;
};
}

View file

@ -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;
}

View file

@ -39,6 +39,7 @@ struct [[gnu::packed]] USBDeviceDescriptor {
u8 serial_number_descriptor_index;
u8 num_configurations;
};
static_assert(sizeof(USBDeviceDescriptor) == 18);
//
// Configuration Descriptor

View file

@ -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()
{
}
}

View file

@ -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>;
};
}

317
Kernel/Bus/USB/USBHub.cpp Normal file
View file

@ -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();
}
}
}
}

107
Kernel/Bus/USB/USBHub.h Normal file
View file

@ -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;
};
}

View file

@ -32,8 +32,10 @@ set(KERNEL_SOURCES
Bus/PCI/Initializer.cpp
Bus/PCI/WindowedMMIOAccess.cpp
Bus/USB/UHCIController.cpp
Bus/USB/UHCIRootHub.cpp
Bus/USB/USBController.cpp
Bus/USB/USBDevice.cpp
Bus/USB/USBHub.cpp
Bus/USB/USBManagement.cpp
Bus/USB/USBPipe.cpp
Bus/USB/USBTransfer.cpp