1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-05 17:17:36 +00:00
serenity/Kernel/Bus/USB/UHCI/UHCIRootHub.cpp
Jesse Buhagiar 4abf399a74 Kernel/USB: Move UHCI related structures to subdirectory
The number of UHCI related files is starting to expand to the point
where it's best if we move this into their own subdirectory. It'll
also make it easier to manage when we decide to add some more
controller types (whenever that may be)
2021-08-19 18:42:07 +02:00

262 lines
11 KiB
C++

/*
* Copyright (c) 2021, Luke Wilde <lukew@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Bus/USB/UHCI/UHCIController.h>
#include <Kernel/Bus/USB/UHCI/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;
}
}