/*
* Copyright (c) 2024, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <Kernel/Bus/USB/USBClasses.h>
#include <Kernel/Bus/USB/USBConstants.h>
#include <Kernel/Bus/USB/USBEndpoint.h>
#include <Kernel/Bus/USB/USBRequest.h>
#include <Kernel/Bus/USB/xHCI/xHCIController.h>
namespace Kernel::USB {
static USBDeviceDescriptor xhci_root_hub_device_descriptor = {
{
sizeof(USBDeviceDescriptor), // 18 bytes long
DESCRIPTOR_TYPE_DEVICE,
},
0x0300, // USB 3.0
(u8)USB_CLASS_HUB,
0, // Hubs use subclass 0
3, // Super Speed Hub
9, // Max packet size
0x0, // Vendor ID
0x0, // Product ID
0x0300, // Product version (can be anything, currently matching usb_spec_compliance_bcd)
0, // Index of manufacturer string.
0, // Index of product string.
0, // Index of serial string.
1, // One configuration descriptor
};
struct USBRootHubDescriptorChain {
USBConfigurationDescriptor configuration_descriptor;
USBInterfaceDescriptor interface_descriptor;
USBEndpointDescriptor endpoint_descriptor;
USBSuperSpeedEndpointCompanionDescriptor speed_endpoint_companion_descriptor;
USBHubDescriptor hub_descriptor;
};
static USBConfigurationDescriptor xhci_root_hub_configuration_descriptor = {
{
sizeof(USBConfigurationDescriptor), // 9 bytes long
DESCRIPTOR_TYPE_CONFIGURATION,
},
sizeof(USBRootHubDescriptorChain), // Combined length of configuration, interface, endpoint, endpoint companion and hub descriptors.
1, // One interface descriptor
1, // Configuration #1
0, // Index of configuration string.
(1 << 6), // Bit 6 is set to indicate that the root hub is self powered.
0, // 0 mA required from the bus (self-powered)
};
static USBInterfaceDescriptor xhci_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, // Root hub
0, // Index of interface string.
};
static USBEndpointDescriptor xhci_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
0xFF, // Max possible interval
};
static USBSuperSpeedEndpointCompanionDescriptor xhci_root_hub_superspeed_endpoint_companion_descriptor = {
{
sizeof(USBSuperSpeedEndpointCompanionDescriptor), // 5 bytes long
DESCRIPTOR_TYPE_USB_SUPERSPEED_ENDPOINT_COMPANION,
},
0,
{ 0 },
0,
};
static USBHubDescriptor xhci_root_hub_hub_descriptor = {
{
sizeof(USBHubDescriptor), // 7 bytes long.
DESCRIPTOR_TYPE_HUB,
},
0x0, // number of root ports (set dynamically by xHCI controller)
{ 0x0 }, // Ganged power switching, not a compound device, global over-current protection.
0x0, // xHCI ports are always powered, so there's no time from power on to power good.
0x0, // Self-powered
};
USBRootHubDescriptorChain xhci_root_hub_descriptor_chain {
xhci_root_hub_configuration_descriptor,
xhci_root_hub_interface_descriptor,
xhci_root_hub_endpoint_descriptor,
xhci_root_hub_superspeed_endpoint_companion_descriptor,
xhci_root_hub_hub_descriptor,
};
ErrorOr<NonnullOwnPtr<xHCIRootHub>> xHCIRootHub::try_create(NonnullLockRefPtr<xHCIController> controller)
{
return adopt_nonnull_own_or_enomem(new (nothrow) xHCIRootHub(move(controller)));
}
xHCIRootHub::xHCIRootHub(NonnullLockRefPtr<xHCIController> controller)
: m_controller(move(controller))
{
}
ErrorOr<void> xHCIRootHub::setup(Badge<xHCIController>)
{
m_hub = TRY(Hub::try_create_root_hub(m_controller, Device::DeviceSpeed::SuperSpeed, 1 /* Address 1 */, xhci_root_hub_device_descriptor));
return m_hub->enumerate_and_power_on_hub();
}
ErrorOr<size_t> xHCIRootHub::handle_control_transfer(Transfer& transfer)
{
auto const& request = transfer.request();
auto* request_data = transfer.buffer().as_ptr() + sizeof(USBRequestData);
if constexpr (XHCI_DEBUG) {
dbgln("xHCIRootHub: Received control transfer.");
dbgln("xHCIRootHub: Request Type: {:#02x}", request.request_type);
dbgln("xHCIRootHub: Request: {:#02x}", request.request);
dbgln("xHCIRootHub: Value: {:#04x}", request.value);
dbgln("xHCIRootHub: Index: {:#04x}", request.index);
dbgln("xHCIRootHub: Length: {:#04x}", request.length);
}
size_t length = 0;
switch (request.request) {
case HubRequest::GET_STATUS: {
length = min(transfer.transfer_data_size(), sizeof(HubStatus));
VERIFY(length <= sizeof(HubStatus));
if (request.index == 0) {
// If index == 0, the actual request is Get Hub Status
// xHCI does not provide "Local Power Source" or "Over-current" and their corresponding change flags.
memset(request_data, 0, length);
break;
}
// If index != 0, the actual request is Get Port Status
auto status = m_controller->get_port_status({}, request.index - 1);
memcpy(request_data, &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, &xhci_root_hub_device_descriptor, length);
break;
case DESCRIPTOR_TYPE_CONFIGURATION: {
length = min(transfer.transfer_data_size(), sizeof(USBRootHubDescriptorChain));
VERIFY(length <= sizeof(USBRootHubDescriptorChain));
memcpy(request_data, &xhci_root_hub_descriptor_chain, length);
auto constexpr ports_offset = __builtin_offsetof(USBRootHubDescriptorChain, hub_descriptor.number_of_downstream_ports);
if (ports_offset < length)
*(request_data + ports_offset) = m_controller->ports();
break;
}
case DESCRIPTOR_TYPE_INTERFACE:
length = min(transfer.transfer_data_size(), sizeof(USBInterfaceDescriptor));
VERIFY(length <= sizeof(USBInterfaceDescriptor));
memcpy(request_data, &xhci_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, &xhci_root_hub_endpoint_descriptor, length);
break;
case DESCRIPTOR_TYPE_USB_SUPERSPEED_ENDPOINT_COMPANION:
length = min(transfer.transfer_data_size(), sizeof(USBSuperSpeedEndpointCompanionDescriptor));
VERIFY(length <= sizeof(USBSuperSpeedEndpointCompanionDescriptor));
memcpy(request_data, &xhci_root_hub_superspeed_endpoint_companion_descriptor, length);
break;
case DESCRIPTOR_TYPE_HUB: {
length = min(transfer.transfer_data_size(), sizeof(USBHubDescriptor));
VERIFY(length <= sizeof(USBHubDescriptor));
memcpy(request_data, &xhci_root_hub_hub_descriptor, length);
auto constexpr ports_offset = __builtin_offsetof(USBHubDescriptor, number_of_downstream_ports);
if (ports_offset < length)
*(request_data + ports_offset) = m_controller->ports();
break;
}
default:
return EINVAL;
}
break;
}
case USB_REQUEST_SET_ADDRESS:
dbgln_if(XHCI_DEBUG, "xHCIRootHub: 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.
// xHCI 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.
auto feature_selector = (HubFeatureSelector)request.value;
TRY(m_controller->set_port_feature({}, request.index - 1, feature_selector));
break;
}
case HubRequest::CLEAR_FEATURE: {
if (request.index == 0) {
// If index == 0, the actual request is Clear Hub Feature.
// xHCI 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.
auto feature_selector = (HubFeatureSelector)request.value;
TRY(m_controller->clear_port_feature({}, request.index - 1, feature_selector));
break;
}
default:
return EINVAL;
}
transfer.set_complete();
return length;
}
}