Skip to content

Commit

Permalink
Kernel/USBMS: Add a basic UAS driver
Browse files Browse the repository at this point in the history
For now we only support USB <3.0 devices, as we don't lissten for
ERDY transaction packets.
We also don't leverage the benefits of UAS, as we pretend to have a
queue depth of 1, ie are single threaded.

To test this driver, you can use the following command:
```
SERENITY_BOOT_DRIVE=usb-uas Meta/serenity.sh run x86_64 Clang
```
  • Loading branch information
Hendiadyoin1 committed Oct 3, 2024
1 parent 7f9d791 commit 1c466ec
Show file tree
Hide file tree
Showing 12 changed files with 1,126 additions and 37 deletions.
189 changes: 155 additions & 34 deletions Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include <Kernel/Bus/USB/USBRequest.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,43 @@ 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 {:02x}", 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 waiting ERDY transactions yet
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 Down Expand Up @@ -134,16 +137,134 @@ 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.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));

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;

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_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 PipeUsage;
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));

m_interfaces.remove(*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();
}

}
7 changes: 4 additions & 3 deletions Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <Kernel/Bus/USB/USBInterface.h>
#include <Kernel/Bus/USB/USBManagement.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;

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

}
2 changes: 2 additions & 0 deletions Kernel/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ set(KERNEL_SOURCES
Devices/Storage/SD/SDMemoryCard.cpp
Devices/Storage/USB/BOT/BulkSCSIInterface.cpp
Devices/Storage/USB/BOT/BulkSCSIStorageDevice.cpp
Devices/Storage/USB/UAS/UASInterface.cpp
Devices/Storage/USB/UAS/UASStorageDevice.cpp
Devices/Storage/VirtIO/VirtIOBlockController.cpp
Devices/Storage/VirtIO/VirtIOBlockDevice.cpp
Devices/Storage/StorageController.cpp
Expand Down
66 changes: 66 additions & 0 deletions Kernel/Devices/Storage/USB/SCSICodes.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* Copyright (c) 2024, Leon Albrecht <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/Format.h>
#include <AK/StringView.h>
#include <AK/Types.h>

namespace Kernel::SCSI {

// SAM 4: 5.3.1 Status codes
enum class StatusCode : u8 {
Good = 0x00,
CheckCondition = 0x02,
ConditionMet = 0x04,
Busy = 0x08,
// Obsolete = 0x10,
// Obsolete = 0x14,
ReservationConflict = 0x18,
// Obsolete = 0x22, was Command Terminated
TaskSetFull = 0x28,
ACAActive = 0x30,
TaskAborted = 0x40,
};
constexpr StringView to_string(StatusCode status)
{
using enum StatusCode;
switch (status) {
case Good:
return "Good"sv;
case CheckCondition:
return "Check Condition"sv;
case ConditionMet:
return "Condition Met"sv;
case Busy:
return "Busy"sv;
case ReservationConflict:
return "Reservation Conflict"sv;
case TaskSetFull:
return "Task Set Full"sv;
case ACAActive:
return "ACA Active"sv;
case TaskAborted:
return "Task Aborted"sv;
default:
return "Unknown"sv;
}
}

}

namespace AK {

template<>
struct Formatter<Kernel::SCSI::StatusCode> : Formatter<FormatString> {
ErrorOr<void> format(FormatBuilder& builder, Kernel::SCSI::StatusCode value)
{
return Formatter<FormatString>::format(builder, "{:02x}({})"sv, to_underlying(value), Kernel::SCSI::to_string(value));
}
};

}
18 changes: 18 additions & 0 deletions Kernel/Devices/Storage/USB/SCSIComands.h
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,24 @@ struct ReadCapacity10Parameters {
};
static_assert(AssertSize<ReadCapacity10Parameters, 8>());

// 3.33
struct ReportLUNs {
u8 opcode { 0xA0 };
u8 reserved { 0 };
u8 select_report { 0 }; // FIXME: Support this
u8 reserved2[3] { 0 };
BigEndian<u32> allocation_length;
u8 reserved3 { 0 };
u8 control { 0 };
};
static_assert(AssertSize<ReportLUNs, 12>());

struct ReportLUNsParameterData {
BigEndian<u32> lun_list_length;
u8 reserved[4] { 0 };
BigEndian<u64> lun_list[];
};

// 3.37
struct RequestSense {
u8 opcode { 0x03 };
Expand Down
19 changes: 19 additions & 0 deletions Kernel/Devices/Storage/USB/SCSIInterface.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2024, Leon Albrecht <[email protected]>
*
* SPDX-License-Identifier: BSD-2-Clause
*/

#pragma once

#include <AK/Types.h>

namespace Kernel::USB {

enum class SCSIDataDirection {
DataToTarget,
DataToInitiator,
NoData
};

}
Loading

0 comments on commit 1c466ec

Please sign in to comment.