Kernel/USB: Add Hubs and the UHCI Root Hub
This commit is contained in:
parent
9dcd146ab4
commit
da0a1068e9
Notes:
sideshowbarker
2024-07-18 05:43:49 +09:00
Author: https://github.com/Lubrsi Commit: https://github.com/SerenityOS/serenity/commit/da0a1068e94 Pull-request: https://github.com/SerenityOS/serenity/pull/9394 Reviewed-by: https://github.com/supercomputer7
11 changed files with 1009 additions and 100 deletions
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
||||
}
|
||||
|
|
252
Kernel/Bus/USB/UHCIRootHub.cpp
Normal file
252
Kernel/Bus/USB/UHCIRootHub.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
39
Kernel/Bus/USB/UHCIRootHub.h
Normal file
39
Kernel/Bus/USB/UHCIRootHub.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
16
Kernel/Bus/USB/USBConstants.h
Normal file
16
Kernel/Bus/USB/USBConstants.h
Normal 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;
|
||||
|
||||
}
|
|
@ -39,6 +39,7 @@ struct [[gnu::packed]] USBDeviceDescriptor {
|
|||
u8 serial_number_descriptor_index;
|
||||
u8 num_configurations;
|
||||
};
|
||||
static_assert(sizeof(USBDeviceDescriptor) == 18);
|
||||
|
||||
//
|
||||
// Configuration Descriptor
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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
317
Kernel/Bus/USB/USBHub.cpp
Normal 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
107
Kernel/Bus/USB/USBHub.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue