Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kernel: Add a basic USB Attached SCSI (UAS) driver #25067

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions Kernel/Bus/USB/Drivers/HID/MouseDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,8 @@ ErrorOr<void> MouseDriver::initialize_device(USB::Device& device, USBInterface c
{
if (interface.endpoints().size() != 1)
return ENOTSUP;
auto const& configuration = interface.configuration();
// FIXME: Should we check other configurations?
TRY(device.control_transfer(
USB_REQUEST_RECIPIENT_DEVICE | USB_REQUEST_TYPE_STANDARD | USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE,
USB_REQUEST_SET_CONFIGURATION, configuration.configuration_id(), 0, 0, nullptr));
TRY(device.set_configuration_and_interface(interface));

auto const& endpoint_descriptor = interface.endpoints()[0];
auto interrupt_in_pipe = TRY(USB::InterruptInPipe::create(device.controller(), device, endpoint_descriptor.endpoint_address & 0xf, endpoint_descriptor.max_packet_size, 10));
Expand Down
197 changes: 157 additions & 40 deletions Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@
#include <Kernel/Bus/USB/USBClasses.h>
#include <Kernel/Bus/USB/USBEndpoint.h>
#include <Kernel/Bus/USB/USBRequest.h>
#include <Kernel/Devices/Storage/USB/BulkSCSIInterface.h>
#include <Kernel/Devices/Storage/USB/Codes.h>
#include <Kernel/Devices/Storage/USB/BOT/BulkSCSIInterface.h>
#include <Kernel/Devices/Storage/USB/BOT/Codes.h>
#include <Kernel/Devices/Storage/USB/UAS/Structures.h>
#include <Kernel/Devices/Storage/USB/UAS/UASInterface.h>

namespace Kernel::USB {

Expand All @@ -24,24 +26,6 @@ void MassStorageDriver::init()
USBManagement::register_driver(driver);
}

ErrorOr<void> MassStorageDriver::checkout_interface(USB::Device& device, USBInterface const& interface)
{
auto const& descriptor = interface.descriptor();

if (descriptor.interface_class_code != USB_CLASS_MASS_STORAGE)
return ENOTSUP;

dmesgln("USB MassStorage Interface for device {:04x}:{:04x} found:", device.device_descriptor().vendor_id, device.device_descriptor().product_id);
dmesgln(" Subclass: {} [{:#02x}]", MassStorage::subclass_string((SubclassCode)descriptor.interface_sub_class_code), descriptor.interface_sub_class_code);
dmesgln(" Protocol: {} [{:#02x}]", MassStorage::transport_protocol_string((TransportProtocol)descriptor.interface_protocol), descriptor.interface_protocol);

// FIXME: Find a nice way of handling multiple device subclasses and protocols
if (descriptor.interface_protocol == to_underlying(TransportProtocol::BBB))
return initialise_bulk_only_device(device, interface);

return ENOTSUP;
}

ErrorOr<void> MassStorageDriver::probe(USB::Device& device)
{
// USB massbulk Table 4.1:
Expand All @@ -54,24 +38,44 @@ ErrorOr<void> MassStorageDriver::probe(USB::Device& device)
// FIXME: There might be multiple MassStorage configs present,
// figure out how to decide which one to take,
// although that's very unlikely
bool has_accepted_an_interface = false;
Optional<USBInterface const&> bot_interface;
Optional<USBInterface const&> uas_interface;

for (auto const& interface : config.interfaces()) {
// FIXME: Handle multiple interfaces
// Interface may coexist at the same time,
// but having multiple handles on the same data storage seems like a bad idea,
// so:
// FIXME: Choose the best supported interface
// UAS for example is supposed to be better than BBB, but BBB will always
// be the first listed interface of them, when both are supported
auto result = checkout_interface(device, interface);
if (result.is_error())
if (interface.descriptor().interface_class_code != USB_CLASS_MASS_STORAGE)
continue;

has_accepted_an_interface = true;
if (interface.descriptor().interface_protocol == to_underlying(TransportProtocol::UAS))
uas_interface = interface;
else if (interface.descriptor().interface_protocol == to_underlying(TransportProtocol::BBB))
bot_interface = interface;
else
dmesgln("USB MassStorage Interface for device {:04x}:{:04x} has unsupported protocol {}", device.device_descriptor().vendor_id, device.device_descriptor().product_id,
transport_protocol_string(static_cast<TransportProtocol>(interface.descriptor().interface_protocol)));
}

if (!bot_interface.has_value() && !uas_interface.has_value())
continue;

dmesgln("USB MassStorage Interfaces for device {:04x}:{:04x} found:", device.device_descriptor().vendor_id, device.device_descriptor().product_id);
dmesgln(" Configuration: {}", config.configuration_id());
dmesgln(" BOT Interface: {}", bot_interface.has_value());
dmesgln(" UAS Interface: {}", uas_interface.has_value());

if (uas_interface.has_value() && device.speed() != USB::Device::DeviceSpeed::SuperSpeed) {
// FIXME: We only support UAS on version < 3.0 devices
// as we don't support streams, which are mandatory for UAS on USB 3.0 devices,
// as they replace the Read/WriteReady signals to and leverage stream IDs instead
dmesgln(" Using UAS interface");
TRY(initialise_uas_device(device, *uas_interface));
return {};
}

if (has_accepted_an_interface)
if (bot_interface.has_value()) {
dmesgln(" Using BOT interface");
TRY(initialise_bulk_only_device(device, *bot_interface));
return {};
}
}

return ENOTSUP;
Expand All @@ -80,14 +84,11 @@ ErrorOr<void> MassStorageDriver::probe(USB::Device& device)
ErrorOr<void> MassStorageDriver::initialise_bulk_only_device(USB::Device& device, USBInterface const& interface)
{
auto const& descriptor = interface.descriptor();
auto const& configuration = interface.configuration();

if (descriptor.interface_sub_class_code != to_underlying(MassStorage::SubclassCode::SCSI_transparent))
return ENOTSUP;

TRY(device.control_transfer(
USB_REQUEST_RECIPIENT_DEVICE | USB_REQUEST_TYPE_STANDARD | USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE,
USB_REQUEST_SET_CONFIGURATION, configuration.configuration_id(), 0, 0, nullptr));
TRY(device.set_configuration_and_interface(interface));

u8 max_luns;
TRY(device.control_transfer(
Expand Down Expand Up @@ -134,16 +135,132 @@ ErrorOr<void> MassStorageDriver::initialise_bulk_only_device(USB::Device& device
move(in_pipe),
move(out_pipe)));

m_interfaces.append(move(bulk_scsi_interface));
m_bot_interfaces.append(move(bulk_scsi_interface));

return {};
}

void MassStorageDriver::detach(USB::Device& device)
ErrorOr<void> MassStorageDriver::initialise_uas_device(USB::Device& device, USBInterface const& interface)
{
auto&& interface = AK::find_if(m_interfaces.begin(), m_interfaces.end(), [&device](auto& interface) { return &interface.device() == &device; });
auto const& descriptor = interface.descriptor();
auto const& configuration = interface.configuration();

if (descriptor.interface_sub_class_code != to_underlying(MassStorage::SubclassCode::SCSI_transparent))
return ENOTSUP;

TRY(device.set_configuration_and_interface(interface));

Optional<u8> command_pipe_endpoint_number;
u16 command_max_packet_size;
Optional<u8> status_pipe_endpoint_number;
u16 status_max_packet_size;
Optional<u8> in_pipe_endpoint_number;
u16 in_max_packet_size;
Optional<u8> out_pipe_endpoint_number;
u16 out_max_packet_size;

m_interfaces.remove(*interface);
if (interface.descriptor().number_of_endpoints < 4) {
dmesgln("SCSI/UAS: Interface does not provide enough endpoints for advertised UAS transfer protocol; Rejecting");
return EIO;
}

Optional<u8> last_seen_endpoint_number;
u16 last_seen_max_packet_size;

TRY(configuration.for_each_descriptor_in_interface(interface,
[&](ReadonlyBytes descriptor_data) -> ErrorOr<void> {
auto const& descriptor_header = *bit_cast<USBDescriptorCommon const*>(descriptor_data.data());

if (descriptor_header.descriptor_type == DESCRIPTOR_TYPE_ENDPOINT) {
auto descriptor = bit_cast<USBEndpointDescriptor const*>(descriptor_data.data());
if ((descriptor->endpoint_attributes_bitmap & USBEndpoint::ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_MASK) != USBEndpoint::ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_BULK)
return {};
last_seen_endpoint_number = descriptor->endpoint_address & 0b1111;
last_seen_max_packet_size = descriptor->max_packet_size;
return {};
}

// Note: The spec says that the Pipe Usage Descriptor should be the first descriptor after the Endpoint Descriptor,
// but we don't enforce that here
// As other descriptors, like the SuperSpeed Endpoint Companion Descriptor, may be present in between
if (descriptor_header.descriptor_type != UAS_PIPE_USAGE_DESCRIPTOR)
return {};

if (descriptor_data.size() < sizeof(PipeUsageDescriptor)) {
dmesgln("SCSI/UAS: Provided Pipe Usage Descriptor is too small; Rejecting");
return EIO;
}

auto descriptor = *bit_cast<PipeUsageDescriptor const*>(descriptor_data.data());

if (!last_seen_endpoint_number.has_value()) {
dmesgln("SCSI/UAS: Found Pipe Usage Descriptor without preceding Endpoint Descriptor; Rejecting");
return EIO;
}

using enum PipeID;
switch (descriptor.pipe_id) {
case CommandPipe:
command_pipe_endpoint_number = last_seen_endpoint_number;
command_max_packet_size = last_seen_max_packet_size;
break;
case StatusPipe:
status_pipe_endpoint_number = last_seen_endpoint_number;
status_max_packet_size = last_seen_max_packet_size;
break;
case DataInPipe:
in_pipe_endpoint_number = last_seen_endpoint_number;
in_max_packet_size = last_seen_max_packet_size;
break;
case DataOutPipe:
out_pipe_endpoint_number = last_seen_endpoint_number;
out_max_packet_size = last_seen_max_packet_size;
break;
}

last_seen_endpoint_number.clear();
last_seen_max_packet_size = 0;

return {};
}));

if (!in_pipe_endpoint_number.has_value()
|| !out_pipe_endpoint_number.has_value()
|| !command_pipe_endpoint_number.has_value()
|| !status_pipe_endpoint_number.has_value()) {
dmesgln("SCSI/UAS: Interface did not advertise all required Bulk Endpoints; Rejecting");
return EIO;
}

auto command_pipe = TRY(BulkOutPipe::create(device.controller(), device, *command_pipe_endpoint_number, command_max_packet_size));
auto status_pipe = TRY(BulkInPipe::create(device.controller(), device, *status_pipe_endpoint_number, status_max_packet_size));
auto in_pipe = TRY(BulkInPipe::create(device.controller(), device, *in_pipe_endpoint_number, in_max_packet_size));
auto out_pipe = TRY(BulkOutPipe::create(device.controller(), device, *out_pipe_endpoint_number, out_max_packet_size));

auto uas_interface = TRY(UASInterface::initialize(
device,
interface,
move(command_pipe),
move(status_pipe),
move(in_pipe),
move(out_pipe)));

m_uas_interfaces.append(move(uas_interface));

return {};
}

void MassStorageDriver::detach(USB::Device& device)
{
if (auto&& interface = AK::find_if(m_bot_interfaces.begin(), m_bot_interfaces.end(), [&device](auto& interface) { return &interface.device() == &device; }); interface != m_bot_interfaces.end()) {
m_bot_interfaces.remove(*interface);
return;
}
if (auto&& interface = AK::find_if(m_uas_interfaces.begin(), m_uas_interfaces.end(), [&device](auto& interface) { return &interface.device() == &device; }); interface != m_uas_interfaces.end()) {
m_uas_interfaces.remove(*interface);
return;
}
VERIFY_NOT_REACHED();
}

}
9 changes: 5 additions & 4 deletions Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
#include <Kernel/Bus/USB/Drivers/USBDriver.h>
#include <Kernel/Bus/USB/USBInterface.h>
#include <Kernel/Bus/USB/USBManagement.h>
#include <Kernel/Devices/Storage/USB/BulkSCSIInterface.h>
#include <Kernel/Devices/Storage/USB/BOT/BulkSCSIInterface.h>
#include <Kernel/Devices/Storage/USB/UAS/UASInterface.h>

namespace Kernel::USB {

Expand All @@ -28,11 +29,11 @@ class MassStorageDriver final : public Driver {
virtual void detach(USB::Device&) override;

private:
BulkSCSIInterface::List m_interfaces;

ErrorOr<void> checkout_interface(USB::Device&, USBInterface const&);
BulkSCSIInterface::List m_bot_interfaces;
UASInterface::List m_uas_interfaces;
Hendiadyoin1 marked this conversation as resolved.
Show resolved Hide resolved

ErrorOr<void> initialise_bulk_only_device(USB::Device&, USBInterface const&);
ErrorOr<void> initialise_uas_device(USB::Device&, USBInterface const&);
};

}
10 changes: 5 additions & 5 deletions Kernel/Bus/USB/USBConfiguration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ ErrorOr<void> USBConfiguration::enumerate_interfaces()
if (m_descriptor.total_length < sizeof(USBConfigurationDescriptor))
return EINVAL;

auto descriptor_hierarchy_buffer = TRY(FixedArray<u8>::create(m_descriptor.total_length)); // Buffer for us to store the entire hierarchy into
m_descriptor_hierarchy_buffer = TRY(FixedArray<u8>::create(m_descriptor.total_length)); // Buffer for us to store the entire hierarchy into

// The USB spec is a little bit janky here... Interface and Endpoint descriptors aren't fetched
// through a `GET_DESCRIPTOR` request to the device. Instead, the _entire_ hierarchy is returned
// to us in one go.
auto transfer_length = TRY(m_device->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST, USB_REQUEST_GET_DESCRIPTOR, (DESCRIPTOR_TYPE_CONFIGURATION << 8) | m_descriptor_index, 0, m_descriptor.total_length, descriptor_hierarchy_buffer.data()));
auto transfer_length = TRY(m_device->control_transfer(USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST, USB_REQUEST_GET_DESCRIPTOR, (DESCRIPTOR_TYPE_CONFIGURATION << 8) | m_descriptor_index, 0, m_descriptor.total_length, m_descriptor_hierarchy_buffer.data()));

FixedMemoryStream stream { descriptor_hierarchy_buffer.span() };
FixedMemoryStream stream { m_descriptor_hierarchy_buffer.span() };

// FIXME: Why does transfer length return the actual size +8 bytes?
if (transfer_length < m_descriptor.total_length)
Expand Down Expand Up @@ -83,6 +83,7 @@ ErrorOr<void> USBConfiguration::enumerate_interfaces()

switch (descriptor_header.descriptor_type) {
case DESCRIPTOR_TYPE_INTERFACE: {
auto offset = stream.offset();
auto interface_descriptor = TRY(read_descriptor.operator()<USBInterfaceDescriptor>(descriptor_header));

if constexpr (USB_DEBUG) {
Expand All @@ -96,9 +97,8 @@ ErrorOr<void> USBConfiguration::enumerate_interfaces()
dbgln(" interface_string_descriptor_index: {}", interface_descriptor.interface_string_descriptor_index);
}

TRY(m_interfaces.try_empend(*this, interface_descriptor));
TRY(m_interfaces.try_empend(*this, interface_descriptor, offset));
current_interface = &m_interfaces.last();

break;
}

Expand Down
29 changes: 29 additions & 0 deletions Kernel/Bus/USB/USBConfiguration.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

#pragma once

#include <AK/FixedArray.h>
#include <AK/MemoryStream.h>
#include <AK/Vector.h>
#include <Kernel/Bus/USB/USBDescriptors.h>
#include <Kernel/Bus/USB/USBDevice.h>
Expand Down Expand Up @@ -44,13 +46,40 @@ class USBConfiguration {

Vector<USBInterface> const& interfaces() const { return m_interfaces; }

template<CallableAs<ErrorOr<void>, ReadonlyBytes> Callback>
ErrorOr<void> for_each_descriptor_in_interface(USBInterface const& interface, Callback&& callback) const
{
auto stream = FixedMemoryStream(m_descriptor_hierarchy_buffer.span());
TRY(stream.seek(interface.descriptor_offset({}), SeekMode::SetPosition));

auto const interface_descriptor = TRY(stream.read_value<USBInterfaceDescriptor>());

VERIFY(__builtin_memcmp(&interface_descriptor, &interface.descriptor(), sizeof(USBInterfaceDescriptor)) == 0);

while (!stream.is_eof()) {
auto const descriptor_header = TRY(stream.read_value<USBDescriptorCommon>());

if (descriptor_header.descriptor_type == DESCRIPTOR_TYPE_INTERFACE)
break;

ReadonlyBytes descriptor_data { m_descriptor_hierarchy_buffer.span().slice(stream.offset() - sizeof(USBDescriptorCommon), descriptor_header.length) };
TRY(callback(descriptor_data));

TRY(stream.seek(descriptor_header.length - sizeof(USBDescriptorCommon), SeekMode::FromCurrentPosition));
Hendiadyoin1 marked this conversation as resolved.
Show resolved Hide resolved
}

return {};
}

ErrorOr<void> enumerate_interfaces();

private:
Device* m_device; // Reference to the device linked to this configuration
USBConfigurationDescriptor const m_descriptor; // Descriptor that backs this configuration
u8 m_descriptor_index; // Descriptor index for {GET,SET}_DESCRIPTOR
Vector<USBInterface> m_interfaces; // Interfaces for this device

FixedArray<u8> m_descriptor_hierarchy_buffer; // Buffer for us to store the entire hierarchy into
};

}
Loading
Loading