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