diff --git a/Kernel/Bus/USB/Drivers/HID/MouseDriver.cpp b/Kernel/Bus/USB/Drivers/HID/MouseDriver.cpp index 2ec950a848c1df..63152e7b62e6df 100644 --- a/Kernel/Bus/USB/Drivers/HID/MouseDriver.cpp +++ b/Kernel/Bus/USB/Drivers/HID/MouseDriver.cpp @@ -60,11 +60,8 @@ ErrorOr 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)); diff --git a/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp b/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp index 7fc61a6ddcbe70..5bddf93638f634 100644 --- a/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp +++ b/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp @@ -9,8 +9,10 @@ #include #include #include -#include -#include +#include +#include +#include +#include namespace Kernel::USB { @@ -24,24 +26,6 @@ void MassStorageDriver::init() USBManagement::register_driver(driver); } -ErrorOr 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 MassStorageDriver::probe(USB::Device& device) { // USB massbulk Table 4.1: @@ -54,24 +38,44 @@ ErrorOr 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 bot_interface; + Optional 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(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; @@ -80,14 +84,11 @@ ErrorOr MassStorageDriver::probe(USB::Device& device) ErrorOr 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( @@ -134,16 +135,132 @@ ErrorOr 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 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 command_pipe_endpoint_number; + u16 command_max_packet_size; + Optional status_pipe_endpoint_number; + u16 status_max_packet_size; + Optional in_pipe_endpoint_number; + u16 in_max_packet_size; + Optional 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 last_seen_endpoint_number; + u16 last_seen_max_packet_size; + + TRY(configuration.for_each_descriptor_in_interface(interface, + [&](ReadonlyBytes descriptor_data) -> ErrorOr { + auto const& descriptor_header = *bit_cast(descriptor_data.data()); + + if (descriptor_header.descriptor_type == DESCRIPTOR_TYPE_ENDPOINT) { + auto descriptor = bit_cast(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(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(); } } diff --git a/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.h b/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.h index b7e29260b5de8d..37ec525d14067b 100644 --- a/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.h +++ b/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.h @@ -9,7 +9,8 @@ #include #include #include -#include +#include +#include namespace Kernel::USB { @@ -28,11 +29,11 @@ class MassStorageDriver final : public Driver { virtual void detach(USB::Device&) override; private: - BulkSCSIInterface::List m_interfaces; - - ErrorOr checkout_interface(USB::Device&, USBInterface const&); + BulkSCSIInterface::List m_bot_interfaces; + UASInterface::List m_uas_interfaces; ErrorOr initialise_bulk_only_device(USB::Device&, USBInterface const&); + ErrorOr initialise_uas_device(USB::Device&, USBInterface const&); }; } diff --git a/Kernel/Bus/USB/USBConfiguration.cpp b/Kernel/Bus/USB/USBConfiguration.cpp index 72979e857acca4..034bc0ecde46b4 100644 --- a/Kernel/Bus/USB/USBConfiguration.cpp +++ b/Kernel/Bus/USB/USBConfiguration.cpp @@ -40,14 +40,14 @@ ErrorOr USBConfiguration::enumerate_interfaces() if (m_descriptor.total_length < sizeof(USBConfigurationDescriptor)) return EINVAL; - auto descriptor_hierarchy_buffer = TRY(FixedArray::create(m_descriptor.total_length)); // Buffer for us to store the entire hierarchy into + m_descriptor_hierarchy_buffer = TRY(FixedArray::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) @@ -83,6 +83,7 @@ ErrorOr USBConfiguration::enumerate_interfaces() switch (descriptor_header.descriptor_type) { case DESCRIPTOR_TYPE_INTERFACE: { + auto offset = stream.offset(); auto interface_descriptor = TRY(read_descriptor.operator()(descriptor_header)); if constexpr (USB_DEBUG) { @@ -96,9 +97,8 @@ ErrorOr 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; } diff --git a/Kernel/Bus/USB/USBConfiguration.h b/Kernel/Bus/USB/USBConfiguration.h index ca71a98c34a339..a7f61c7d6fba5e 100644 --- a/Kernel/Bus/USB/USBConfiguration.h +++ b/Kernel/Bus/USB/USBConfiguration.h @@ -6,6 +6,8 @@ #pragma once +#include +#include #include #include #include @@ -44,6 +46,31 @@ class USBConfiguration { Vector const& interfaces() const { return m_interfaces; } + template, ReadonlyBytes> Callback> + ErrorOr 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()); + + VERIFY(__builtin_memcmp(&interface_descriptor, &interface.descriptor(), sizeof(USBInterfaceDescriptor)) == 0); + + while (!stream.is_eof()) { + auto const descriptor_header = TRY(stream.read_value()); + + 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)); + } + + return {}; + } + ErrorOr enumerate_interfaces(); private: @@ -51,6 +78,8 @@ class USBConfiguration { USBConfigurationDescriptor const m_descriptor; // Descriptor that backs this configuration u8 m_descriptor_index; // Descriptor index for {GET,SET}_DESCRIPTOR Vector m_interfaces; // Interfaces for this device + + FixedArray m_descriptor_hierarchy_buffer; // Buffer for us to store the entire hierarchy into }; } diff --git a/Kernel/Bus/USB/USBDevice.cpp b/Kernel/Bus/USB/USBDevice.cpp index 05b626f397cd36..2551dcb21c9f99 100644 --- a/Kernel/Bus/USB/USBDevice.cpp +++ b/Kernel/Bus/USB/USBDevice.cpp @@ -97,4 +97,37 @@ ErrorOr Device::control_transfer(u8 request_type, u8 request, u16 value, return TRY(m_default_pipe->submit_control_transfer(request_type, request, value, index, length, data)); } +ErrorOr Device::set_configuration(USBConfiguration const& configuration) +{ + if (m_was_configured.was_set() && m_current_configuration != configuration.configuration_id()) + return EALREADY; + + if (!m_was_configured.was_set()) { + m_was_configured.set(); + m_current_configuration = configuration.configuration_id(); + + TRY(control_transfer(USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE | USB_REQUEST_TYPE_STANDARD | USB_REQUEST_RECIPIENT_DEVICE, USB_REQUEST_SET_CONFIGURATION, + m_current_configuration, 0, 0, nullptr)); + + // FIXME: On xHCI we should set up the all endpoints for the configuration here + // Currently we set them up on the first transfer, which works good enough for now + } + + return {}; +} + +ErrorOr Device::set_configuration_and_interface(USBInterface const& interface) +{ + auto const& configuration = interface.configuration(); + TRY(set_configuration(configuration)); + + // FIXME: When we use the default alternate_setting of interface/the current alternate setting, we don't need to SET_INTERFACE it + // but that gets a bit difficult to track + TRY(control_transfer(USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE | USB_REQUEST_TYPE_STANDARD | USB_REQUEST_RECIPIENT_INTERFACE, USB_REQUEST_SET_INTERFACE, + interface.descriptor().alternate_setting, interface.descriptor().interface_id, 0, nullptr)); + // FIXME: As in activate_configuration, we should set up changed endpoints on xHCI here + + return {}; +} + } diff --git a/Kernel/Bus/USB/USBDevice.h b/Kernel/Bus/USB/USBDevice.h index 5715e75c2984a2..014de85262bcfb 100644 --- a/Kernel/Bus/USB/USBDevice.h +++ b/Kernel/Bus/USB/USBDevice.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -76,6 +77,8 @@ class Device : public AtomicRefCounted { m_driver = nullptr; } + ErrorOr set_configuration_and_interface(USBInterface const& interface); + SpinlockProtected, LockRank::None>& sysfs_device_info_node(Badge) { return m_sysfs_device_info_node; } template Controller> @@ -105,6 +108,7 @@ class Device : public AtomicRefCounted { protected: void set_default_pipe(NonnullOwnPtr pipe); + ErrorOr set_configuration(USBConfiguration const& configuration); u8 m_device_port { 0 }; // What port is this device attached to. NOTE: This is 1-based. DeviceSpeed m_device_speed; // What speed is this device running at @@ -121,6 +125,12 @@ class Device : public AtomicRefCounted { Hub const* m_hub { nullptr }; OwnPtr m_default_pipe; // Default communication pipe (endpoint0) used during enumeration + // The current configuration is behind a SetOnce, this is the easiest way to + // guarantee that when a driver is attached, another driver cannot choose a different configuration + // using a different interface in the same configuration is fine, though + SetOnce m_was_configured; + u8 m_current_configuration { 0 }; + LockRefPtr m_driver; private: diff --git a/Kernel/Bus/USB/USBHub.cpp b/Kernel/Bus/USB/USBHub.cpp index 61796d4cf95023..eb5916739ccd5b 100644 --- a/Kernel/Bus/USB/USBHub.cpp +++ b/Kernel/Bus/USB/USBHub.cpp @@ -78,7 +78,7 @@ ErrorOr Hub::enumerate_and_power_on_hub() return EINVAL; // FIXME: Which configuration should we choose if there is more than one? - TRY(control_transfer(USB_REQUEST_TRANSFER_DIRECTION_HOST_TO_DEVICE | USB_REQUEST_TYPE_STANDARD | USB_REQUEST_RECIPIENT_DEVICE, USB_REQUEST_SET_CONFIGURATION, m_configurations[0].configuration_id(), 0, 0, nullptr)); + TRY(set_configuration(m_configurations[0])); } USBHubDescriptor descriptor {}; diff --git a/Kernel/Bus/USB/USBInterface.h b/Kernel/Bus/USB/USBInterface.h index 8382eb43329c43..3367bc4c62052c 100644 --- a/Kernel/Bus/USB/USBInterface.h +++ b/Kernel/Bus/USB/USBInterface.h @@ -17,9 +17,10 @@ class USBConfiguration; class USBInterface final { public: USBInterface() = delete; - USBInterface(USBConfiguration const& configuration, USBInterfaceDescriptor const descriptor) + USBInterface(USBConfiguration const& configuration, USBInterfaceDescriptor const descriptor, size_t descriptor_offset) : m_configuration(&configuration) , m_descriptor(descriptor) + , m_descriptor_offset(descriptor_offset) { } @@ -28,6 +29,8 @@ class USBInterface final { Vector const& endpoints() const { return m_endpoint_descriptors; } USBInterfaceDescriptor const& descriptor() const { return m_descriptor; } + size_t descriptor_offset(Badge) const { return m_descriptor_offset; } + USBConfiguration const& configuration() const { return *m_configuration; } void set_configuration(Badge, USBConfiguration const& configuration) { m_configuration = &configuration; } @@ -35,6 +38,7 @@ class USBInterface final { USBConfiguration const* m_configuration; // Configuration that this interface belongs to USBInterfaceDescriptor const m_descriptor; // Descriptor backing this interface Vector m_endpoint_descriptors; // Endpoint descriptors for this interface (that we can use to open an endpoint) + size_t m_descriptor_offset { 0 }; // Offset of the interface descriptor in the hierarchy }; } diff --git a/Kernel/Bus/USB/USBRequest.h b/Kernel/Bus/USB/USBRequest.h index 09c73f2b891b25..e22aff3405125f 100644 --- a/Kernel/Bus/USB/USBRequest.h +++ b/Kernel/Bus/USB/USBRequest.h @@ -41,6 +41,7 @@ static constexpr u8 USB_REQUEST_GET_DESCRIPTOR = 0x06; static constexpr u8 USB_REQUEST_SET_DESCRIPTOR = 0x07; static constexpr u8 USB_REQUEST_GET_CONFIGURATION = 0x08; static constexpr u8 USB_REQUEST_SET_CONFIGURATION = 0x09; +static constexpr u8 USB_REQUEST_SET_INTERFACE = 0x0B; // Table 9-6 static constexpr u16 USB_FEATURE_DEVICE_REMOTE_WAKEUP = 1; diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index 4b8451bf4b219a..59d57972708165 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -126,8 +126,10 @@ set(KERNEL_SOURCES Devices/Storage/SD/PCISDHostController.cpp Devices/Storage/SD/SDHostController.cpp Devices/Storage/SD/SDMemoryCard.cpp - Devices/Storage/USB/BulkSCSIInterface.cpp - Devices/Storage/USB/BulkSCSIStorageDevice.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 diff --git a/Kernel/Devices/Storage/USB/BulkSCSIInterface.cpp b/Kernel/Devices/Storage/USB/BOT/BulkSCSIInterface.cpp similarity index 98% rename from Kernel/Devices/Storage/USB/BulkSCSIInterface.cpp rename to Kernel/Devices/Storage/USB/BOT/BulkSCSIInterface.cpp index 166b64064871f1..cedee1f7598b7c 100644 --- a/Kernel/Devices/Storage/USB/BulkSCSIInterface.cpp +++ b/Kernel/Devices/Storage/USB/BOT/BulkSCSIInterface.cpp @@ -8,8 +8,8 @@ #include #include #include -#include -#include +#include +#include #include namespace Kernel::USB { diff --git a/Kernel/Devices/Storage/USB/BulkSCSIInterface.h b/Kernel/Devices/Storage/USB/BOT/BulkSCSIInterface.h similarity index 97% rename from Kernel/Devices/Storage/USB/BulkSCSIInterface.h rename to Kernel/Devices/Storage/USB/BOT/BulkSCSIInterface.h index 03735c48bcf238..057122eedd1c61 100644 --- a/Kernel/Devices/Storage/USB/BulkSCSIInterface.h +++ b/Kernel/Devices/Storage/USB/BOT/BulkSCSIInterface.h @@ -9,7 +9,8 @@ #include #include #include -#include +#include +#include namespace Kernel::USB { @@ -58,13 +59,7 @@ struct CommandStatusWrapper { }; static_assert(AssertSize()); -enum class SCSIDataDirection { - DataToTarget, - DataToInitiator, - NoData -}; - -class BulkSCSIInterface : public RefCounted { +class BulkSCSIInterface : public AK::RefCounted { // https://www.usb.org/sites/default/files/usbmassbulk_10.pdf public: static ErrorOr> initialize(USB::Device&, USBInterface const&, NonnullOwnPtr, NonnullOwnPtr); diff --git a/Kernel/Devices/Storage/USB/BulkSCSIStorageDevice.cpp b/Kernel/Devices/Storage/USB/BOT/BulkSCSIStorageDevice.cpp similarity index 98% rename from Kernel/Devices/Storage/USB/BulkSCSIStorageDevice.cpp rename to Kernel/Devices/Storage/USB/BOT/BulkSCSIStorageDevice.cpp index 354a8356981610..b8cee42bc093cd 100644 --- a/Kernel/Devices/Storage/USB/BulkSCSIStorageDevice.cpp +++ b/Kernel/Devices/Storage/USB/BOT/BulkSCSIStorageDevice.cpp @@ -5,8 +5,8 @@ * SPDX-License-Identifier: BSD-2-Clause */ -#include -#include +#include +#include #include namespace Kernel::USB { diff --git a/Kernel/Devices/Storage/USB/BulkSCSIStorageDevice.h b/Kernel/Devices/Storage/USB/BOT/BulkSCSIStorageDevice.h similarity index 100% rename from Kernel/Devices/Storage/USB/BulkSCSIStorageDevice.h rename to Kernel/Devices/Storage/USB/BOT/BulkSCSIStorageDevice.h diff --git a/Kernel/Devices/Storage/USB/Codes.h b/Kernel/Devices/Storage/USB/BOT/Codes.h similarity index 100% rename from Kernel/Devices/Storage/USB/Codes.h rename to Kernel/Devices/Storage/USB/BOT/Codes.h diff --git a/Kernel/Devices/Storage/USB/SCSICodes.h b/Kernel/Devices/Storage/USB/SCSICodes.h new file mode 100644 index 00000000000000..3732d158f2508f --- /dev/null +++ b/Kernel/Devices/Storage/USB/SCSICodes.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +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 : Formatter { + ErrorOr format(FormatBuilder& builder, Kernel::SCSI::StatusCode value) + { + return Formatter::format(builder, "{:02x}({})"sv, to_underlying(value), Kernel::SCSI::to_string(value)); + } +}; + +} diff --git a/Kernel/Devices/Storage/USB/SCSIComands.h b/Kernel/Devices/Storage/USB/SCSIComands.h index ca6a80d5a133fb..9b96564844bb22 100644 --- a/Kernel/Devices/Storage/USB/SCSIComands.h +++ b/Kernel/Devices/Storage/USB/SCSIComands.h @@ -189,6 +189,24 @@ struct ReadCapacity10Parameters { }; static_assert(AssertSize()); +// 3.33 +struct ReportLUNs { + u8 opcode { 0xA0 }; + u8 reserved { 0 }; + u8 select_report { 0 }; // FIXME: Support this + u8 reserved2[3] { 0 }; + BigEndian allocation_length; + u8 reserved3 { 0 }; + u8 control { 0 }; +}; +static_assert(AssertSize()); + +struct ReportLUNsParameterData { + BigEndian lun_list_length; + u8 reserved[4] { 0 }; + BigEndian lun_list[]; +}; + // 3.37 struct RequestSense { u8 opcode { 0x03 }; diff --git a/Kernel/Devices/Storage/USB/SCSIInterface.h b/Kernel/Devices/Storage/USB/SCSIInterface.h new file mode 100644 index 00000000000000..845ede676d0736 --- /dev/null +++ b/Kernel/Devices/Storage/USB/SCSIInterface.h @@ -0,0 +1,19 @@ +/* + * Copyright (c) 2024, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Kernel::USB { + +enum class SCSIDataDirection { + DataToTarget, + DataToInitiator, + NoData +}; + +} diff --git a/Kernel/Devices/Storage/USB/UAS/Structures.h b/Kernel/Devices/Storage/USB/UAS/Structures.h new file mode 100644 index 00000000000000..b85099e8ae6f68 --- /dev/null +++ b/Kernel/Devices/Storage/USB/UAS/Structures.h @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2024, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +namespace Kernel::USB { +// According to ISO/IEC 14776-251 or the t10 UAS specification + +// 5.3.3.5 Pipe Usage Descriptor +// Note: This should be 5.2.3.5, but the 2014 revision of the spec has a typo + +// Table 8 Pipe ID +enum class PipeID : u8 { + // Reserved: 0x00 + CommandPipe = 0x01, + StatusPipe = 0x02, + DataInPipe = 0x03, + DataOutPipe = 0x04, + // Reserved: 0x05 - 0xDF + // Vendor Specific: 0xE0 - 0xEF + // Reserved: 0xF0 - 0xFF +}; +// Table 7 +constexpr u8 UAS_PIPE_USAGE_DESCRIPTOR = 0x24; +struct PipeUsageDescriptor { + u8 descriptor_length; + u8 descriptor_type; + PipeID pipe_id; + u8 reserved; +}; + +// 6.2 IUs +// Table 9 +enum class IUID : u8 { + // Reserved: 0x00 + Command = 0x01, + // Reserved: 0x02 + Sense = 0x03, + Response = 0x04, + TaskManagement = 0x05, + ReadReady = 0x06, + WriteReady = 0x07, + // Reserved: 0x08 - 0x0F +}; + +// Table 10 +struct InformationUnitHeader { + IUID iu_id; + u8 reserved { 0 }; + BigEndian tag; +}; +static_assert(AssertSize()); + +// 6.2.2 Command IU +// Table 12 +enum class TaskAttribute : u8 { + Simple = 0b000, + HeadOfQueue = 0b001, + Ordered = 0b010, + // Reserved: 0b011 + ACA = 0b100, + // Reserved: 0b101 - 0b111 +}; + +// Table 11 +struct CommandIU { + InformationUnitHeader header; + struct { + TaskAttribute attribute : 3; + u8 priority : 4; + u8 reserved : 1; + } task_info; + u8 reserved_0 { 0 }; + u8 additional_cbd_length; // must be multiple of 4 + u8 reserved_1 { 0 }; + BigEndian lun; + u8 cdb[16]; + u8 additional_cbd_bytes[]; // indicated by additional_cbd_length -> multiple dwords + + template + void set_command(Command const& command) + { + // FIXME: + static_assert(sizeof(command) <= sizeof(cdb), "Command too large for CommandIU without additional_cbd_bytes"); + memcpy(cdb, &command, min(sizeof(command), sizeof(cdb))); + additional_cbd_length = 0; + } +}; +static_assert(AssertSize()); + +// 6.2.3 Read Ready IU +// Table 13 +struct ReadReadyIU { + InformationUnitHeader header; +}; +static_assert(AssertSize()); + +// 6.2.4 Write Ready IU +// Table 14 +struct WriteReadyIU { + InformationUnitHeader header; +}; +static_assert(AssertSize()); + +// 6.2.5 Sense IU +// Table 15 +struct SenseIU { + InformationUnitHeader header; + BigEndian status_qualifier; // See SAM-4 + SCSI::StatusCode status; + u8 reserved[7]; + BigEndian length; // FIXME: The spec does not actually state the endianness of this? + u8 sense_data[]; +}; +static_assert(AssertSize()); + +// 6.2.6 Response IU +// Table 17 +enum class ResponseCode : u8 { + // TM: On Task Management IUs + // Command: On Command IUs + TaskManagementFunctionComplete = 0x00, // TM + // Reserved: 0x01 + InvalidIU = 0x02, // TM, Command + // Reserved: 0x03 + TaskManagementFunctionNotSupported = 0x04, // TM + TaskManagementFunctionFailed = 0x05, // TM + // Reserved: 0x06 - 0x07 + TaskManagementFunctionSucceeded = 0x08, // TM + IncorrectLUN = 0x09, // TM + OverlappedTagAttempted = 0x0A, // TM, Command + // Reserved: 0x0B - 0x0F +}; + +// Table 16 +struct ResponseIU { + InformationUnitHeader header; + u8 additional_response_info[3]; + ResponseCode response_code; +}; +static_assert(AssertSize()); + +// 6.2.7 Task Management IU +// Table 19 +enum class TaskManagementFunction : u8 { + // LUN: lun field used + // TOTTBM: tag_of_task_to_be_managed field used + // Reserved: 0x00 + AbortTask = 0x01, // LUN, TOTTBM + AbortTaskSet = 0x02, // LUN + // Reserved: 0x03 + ClearTaskSet = 0x04, // LUN + // Reserved: 0x05 - 0x07 + LogicalUnitReset = 0x08, // LUN + // Reserved: 0x09 - 0x0F + ITNexusReset = 0x10, + // Reserved: 0x11 - 0x3F + ClearACA = 0x40, // LUN + // Reserved: 0x41 - 0x7F + QueryTask = 0x80, // LUN, TOTTBM + QueryTaskSet = 0x81, // LUN + QueryAsynchrounousEvent = 0x82, // LUN + // Reserved: 0x83 - 0xFF +}; + +// Table 18 +struct TaskManagementIU { + InformationUnitHeader header; + u8 function; + u8 reserved { 0 }; + BigEndian tag_of_task_to_be_managed; + BigEndian lun; +}; + +} diff --git a/Kernel/Devices/Storage/USB/UAS/UASInterface.cpp b/Kernel/Devices/Storage/USB/UAS/UASInterface.cpp new file mode 100644 index 00000000000000..f19205afac525a --- /dev/null +++ b/Kernel/Devices/Storage/USB/UAS/UASInterface.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2024, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Kernel::USB { + +UASInterface::UASInterface(USB::Device& device, USBInterface const& interface, NonnullOwnPtr command_pipe, NonnullOwnPtr status_pipe, NonnullOwnPtr data_in_pipe, NonnullOwnPtr data_out_pipe) + : m_device(device) + , m_interface(interface) + , m_command_pipe(move(command_pipe)) + , m_status_pipe(move(status_pipe)) + , m_in_pipe(move(data_in_pipe)) + , m_out_pipe(move(data_out_pipe)) +{ +} + +ErrorOr> UASInterface::initialize(USB::Device& device, USBInterface const& interface, NonnullOwnPtr command_pipe, NonnullOwnPtr status_pipe, NonnullOwnPtr data_in_pipe, NonnullOwnPtr data_out_pipe) +{ + auto uas_interface = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) UASInterface( + device, + interface, + move(command_pipe), + move(status_pipe), + move(data_in_pipe), + move(data_out_pipe)))); + + // FIXME: This has a lot of duplication with the BulkSCSIInterface::initialize function + + SCSI::Inquiry inquiry_command {}; + inquiry_command.allocation_length = sizeof(SCSI::StandardInquiryData); + + SCSI::StandardInquiryData inquiry_data; + + auto inquiry_response = TRY(uas_interface->send_scsi_command(inquiry_command, &inquiry_data, sizeof(inquiry_data))); + if (auto& sense = inquiry_response.as_sense(); sense.status != SCSI::StatusCode::Good) { + dmesgln("SCSI/UAS: Inquiry failed with code {}", sense.status); + return EIO; + } + + dmesgln(" Device Type: {}", inquiry_data.device_type_string()); + dmesgln(" Peripheral Qualifier: {:#03b}", (u8)inquiry_data.peripheral_info.qualifier); + dmesgln(" Removable: {}", (inquiry_data.removable & 0x80) == 0x80); + dmesgln(" Version: {:#02x}", inquiry_data.version); + dmesgln(" Vendor: {}", StringView { inquiry_data.vendor_id, 8 }); + dmesgln(" Product: {}", StringView { inquiry_data.product_id, 16 }); + dmesgln(" Revision: {}", StringView { inquiry_data.product_revision_level, 4 }); + if (inquiry_data.peripheral_info.device_type != SCSI::StandardInquiryData::DeviceType::DirectAccessBlockDevice) { + dmesgln("SCSI/UAS: Device is not a Direct Access Block device; Rejecting"); + return ENOTSUP; + } + if ((inquiry_data.version < 3 || inquiry_data.version > 7) && inquiry_data.version != 0) { + dmesgln("SCSI/UAS: Device SCSI version not supported ({:#02x}); Rejecting", inquiry_data.version); + return ENOTSUP; + } + if (inquiry_data.response_data.response_data_format != 2) { + // SCSI Commands Reference Manual, Rev. J states that only format 2 is valid, + // and that format 1 is obsolete, but does not actually specify what format 1 would have been + // so ENOTSUP to be safe + dmesgln("SCSI/UAS: Device does not support response data format 2 (got {} instead); Rejecting", (u8)inquiry_data.response_data.response_data_format); + return ENOTSUP; + } + + // FIXME: Re-query INQUIRY if the DRIVE SERIAL NUMBER field is present (see the ADDITIONAL LENGTH field), to record it + // (bytes 36-43 ~ 8 bytes) + + size_t tries = 0; + constexpr size_t max_tries = 5; + while (tries < max_tries) { + SCSI::TestUnitReady test_unit_ready_command {}; + auto test_unit_ready_response = TRY(uas_interface->send_scsi_command(test_unit_ready_command)); + + if (!test_unit_ready_response.is_sense()) { + dmesgln("SCSI/UAS: TestUnitReady did not return Sense IU, aborting"); + return EIO; + } + auto& test_unit_ready_sense = test_unit_ready_response.as_sense(); + + if (test_unit_ready_sense.status == SCSI::StatusCode::Good) + break; + + dmesgln("SCSI/UAS: TestUnitReady Failed:"); + dmesgln(" Status Code: {}", test_unit_ready_sense.status); + dmesgln(" Status Qualifier: {:04x}", test_unit_ready_sense.status_qualifier); + // FIXME: Check if we have additional sense data and print it + ++tries; + } + if (tries == max_tries) { + dmesgln("SCSI/UAS: TestUnitReady failed too many times"); + return EIO; + } + + // FIXME: Inquire Queue Depth and other capabilities + // FIXME: Configure auto sense + + do { + // Inquire LUNs + // Note: Even if this fails it should be fine in most cases + // as LUN 0 should always be available + // (Technically the spec says that LUN 0 isn't mandatory and devices can + // have a specific version of the REPORT LUNS command instead, but let's ignore that for now + // as you still would need to talk to LUN 0 to get all the info and we only support 1 LUN anyway) + SCSI::ReportLUNs report_luns_command {}; + alignas(SCSI::ReportLUNsParameterData) u8 report_luns_response_buffer[512]; + report_luns_command.allocation_length = sizeof(report_luns_response_buffer); + + auto report_luns_response = TRY(uas_interface->send_scsi_command(report_luns_command, report_luns_response_buffer, sizeof(report_luns_response_buffer))); + + if (!report_luns_response.is_sense()) { + dmesgln("SCSI/UAS: ReportLUNs did not return Sense IU; Assuming LUN 0 is available"); + break; + } + + if (auto& sense = report_luns_response.as_sense(); sense.status != SCSI::StatusCode::Good) { + dmesgln("SCSI/UAS: Failed to query LUNs: {}; Using LUN 0", sense.status); + break; + } + + bool has_lun_0 = false; + auto& report_luns_response_data = *bit_cast(&report_luns_response_buffer[0]); + dmesgln(" Found {} LUN(s):", report_luns_response_data.lun_list_length / 8); + for (size_t i = 0; i < report_luns_response_data.lun_list_length / 8; ++i) { + // FIXME: Properly parse the LUNs, and create a device for each + + dmesgln(" {:016x}", report_luns_response_data.lun_list[i]); + + if (report_luns_response_data.lun_list[i] == 0) + has_lun_0 = true; + } + + if (!has_lun_0) { + // FIXME?: See above about the availability of LUN 0 + dmesgln("SCSI/UAS: WARNING: LUN 0 not reported; Using LUN 0 anyway"); + break; + } + } while (false); + + SCSI::ReadCapacity10Parameters capacity; + auto status = TRY(uas_interface->send_scsi_command(SCSI::ReadCapacity10 {}, &capacity, sizeof(capacity))); + + // FIXME: BOT/BBB checks the data residue here, + // UAS does not seem to provide a similar field + // Should we do something similar? + + if (!status.is_sense()) { + dmesgln("SCSI/UAS: ReadCapacity returned non Sense IU {:02x}; Rejecting", to_underlying(status.response.header.iu_id)); + return EIO; + } + + if (auto& sense = status.as_sense(); sense.status != SCSI::StatusCode::Good) { + dmesgln("SCSI/UAS: Failed to query USB Drive capacity: {}; Rejecting", sense.status); + // FIXME: More error handling + return ENOTSUP; + } + + dmesgln(" Block Size: {}B", capacity.block_size); + dmesgln(" Block Count: {}", capacity.block_count); + dmesgln(" Total Size: {}MiB", (u64)capacity.block_size * capacity.block_count / MiB); + + // FIXME: UAS LUNs can be 64 bits, + // Possibly containing a Bus Number + // We only have space for 32 bits in the last field + // We also might want to re-evaluate our LUN layout/internal LUN handling + StorageDevice::LUNAddress lun = { + device.controller().storage_controller_id(), + device.address(), + // FIXME: Support multiple LUNs per device + 0 + }; + + auto storage_device = TRY(::Device::try_create_device( + *uas_interface, + lun, + device.address(), // FIXME: Figure out a better ID to put here + capacity.block_size, + capacity.block_count)); + + uas_interface->add_storage_device(storage_device); + + StorageManagement::the().add_device(storage_device); + + return uas_interface; +} + +UASInterface::~UASInterface() +{ + for (auto& storage_device : m_storage_devices) + StorageManagement::the().remove_device(storage_device); +} + +} diff --git a/Kernel/Devices/Storage/USB/UAS/UASInterface.h b/Kernel/Devices/Storage/USB/UAS/UASInterface.h new file mode 100644 index 00000000000000..40b4cd4bea329b --- /dev/null +++ b/Kernel/Devices/Storage/USB/UAS/UASInterface.h @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2024, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace Kernel::USB { + +class UASInterface : public RefCounted { + + union IU { + // Note: As the SenseIU is of flexible size we need to forcefully allocate some data here. + // The maximum size of the sense data the device may send is controlled by the + // MAXIMUM SENSE DATA LENGTH field in the Control extension mode page of the device. + // In theory the maximum size of the sense data is 252 bytes + // Meaning the maximum size of the IU is 252+16=268 bytes + // Just to be safe we allocate 512 bytes here, as the spec tells us that the + // SenseIU is to not share a USB packet with any other IU, so we can just use + // to the maximum packet size, which is 512 bytes (USB3 allows up to 1024 bytes) + u8 dummy[512] {}; + + InformationUnitHeader header; + CommandIU command; + ResponseIU response; + SenseIU sense; + TaskManagementIU task_management; + ReadReadyIU read_ready; + WriteReadyIU write_ready; + }; + + struct SendSCSICommandResult { + SenseIU& as_sense() + { + VERIFY(is_sense()); + return response.sense; + } + bool is_sense() const { return response.header.iu_id == IUID::Sense; } + + size_t transfer_size; + size_t response_size; + IU response; + }; + +public: + static ErrorOr> initialize(USB::Device&, USBInterface const&, NonnullOwnPtr command_pipe, NonnullOwnPtr status_pipe, NonnullOwnPtr data_in_pipe, NonnullOwnPtr data_out_pipe); + + ~UASInterface(); + + USB::Device const& device() const { return m_device; } + + template + requires(IsNullPointer + || IsPointer + || (Direction == SCSIDataDirection::DataToInitiator && IsSame) + || (Direction == SCSIDataDirection::DataToTarget && IsSameIgnoringCV)) + ErrorOr send_scsi_command(Command const& command, + Data data = nullptr, size_t data_size = 0) + { + // FIXME: + static_assert(sizeof(Command) <= sizeof(CommandIU::cdb), "Command too large for CommandIU without additional_cbd_bytes"); + // Note: Once we support USB3 streams, this should not exceed the maximum stream id + // Ideally this would then pull from a free-list of tags + u16 transfer_tag = m_next_tag++; + CommandIU command_iu {}; + command_iu.header.iu_id = IUID::Command; + command_iu.header.tag = transfer_tag; + // FIXME: Properly(/configurably) set the task_info + command_iu.task_info.attribute = TaskAttribute::Simple; + command_iu.task_info.priority = 0; + command_iu.set_command(command); + + dbgln_if(USB_MASS_STORAGE_DEBUG, "UAS: send_scsi_command (opcode {:#x}):", *bit_cast(&command)); + + dbgln_if(USB_MASS_STORAGE_DEBUG, "UAS: -> CIU: {:hex-dump}", ReadonlyBytes { &command_iu, sizeof(command_iu) }); + dbgln_if(USB_MASS_STORAGE_DEBUG, "UAS: CDB: {:hex-dump}", ReadonlyBytes { &command, sizeof(command) }); + + // FIXME: This should actually be done asynchronously and allow other commands to be sent in the meantime + // possibly allowing handling multiple commands to be processed in parallel + // Note: Different transactions are distinguished by the tag field in the IU header + // FIXME: I think we should do more error handling here in general? + // For example what if the command pipe is full? + // Note: The spec does say that there aren't any conditions resulting in a stall + auto command_stage_error = m_command_pipe->submit_bulk_out_transfer(sizeof(command_iu), &command_iu); + if (command_stage_error.is_error()) { + dmesgln("UAS: Command stage error: {}", command_stage_error.error()); + return command_stage_error.release_error(); + } + + // FIXME: On USB3 this is done through streams instead, so we would immediately wait on the data stream + size_t transfer_size = 0; + if constexpr (Direction != SCSIDataDirection::NoData) { + IU ready_response; + auto ready_response_size_or_error = m_status_pipe->submit_bulk_in_transfer(sizeof(IU), &ready_response); + if (ready_response_size_or_error.is_error()) { + dmesgln("UAS: Ready response error: {}", ready_response_size_or_error.error()); + return ready_response_size_or_error.release_error(); + } + + auto ready_response_size = ready_response_size_or_error.release_value(); + if constexpr (Direction == SCSIDataDirection::DataToInitiator) { + if (ready_response_size < sizeof(ReadReadyIU)) { + dmesgln("UAS: Response too short, expected at least {} bytes, got {}", sizeof(ReadReadyIU), ready_response_size); + return EIO; + } + if (ready_response.header.iu_id != IUID::ReadReady) { + dmesgln("UAS: Expected Read Ready IU, got {:02x}", static_cast(ready_response.header.iu_id)); + return EIO; + } + if (ready_response.read_ready.header.tag != transfer_tag) { + // Note: Once we support multiple commands in parallel, we should not return an error here + // but instead continue processing the responses and match them up with the commands + dmesgln("UAS: Response tag mismatch, expected {}, got {}", transfer_tag, ready_response.read_ready.header.tag); + return EIO; + } + + // Note: The ReadReady command does not contain any useful data other than the tag + + auto transfer_error = m_in_pipe->submit_bulk_in_transfer(data_size, data); + if (transfer_error.is_error()) { + dmesgln("UAS: Data transfer error: {}", transfer_error.error()); + return transfer_error.release_error(); + } + transfer_size = transfer_error.release_value(); + } else if constexpr (Direction == SCSIDataDirection::DataToTarget) { + if (ready_response_size < sizeof(WriteReadyIU)) { + dmesgln("UAS: Response too short, expected at least {} bytes, got {}", sizeof(WriteReadyIU), ready_response_size); + return EIO; + } + if (ready_response.header.iu_id != IUID::WriteReady) { + dmesgln("UAS: Expected Write Ready IU, got {:02x}", static_cast(ready_response.header.iu_id)); + return EIO; + } + if (ready_response.write_ready.header.tag != transfer_tag) { + // Note: Once we support multiple commands in parallel, we should not return an error here + // but instead continue processing the responses and match them up with the commands + dmesgln("UAS: Response tag mismatch, expected {}, got {}", transfer_tag, ready_response.write_ready.header.tag); + return EIO; + } + + // Note: The WriteReady command does not contain any useful data other than the tag + + auto transfer_error = m_out_pipe->submit_bulk_out_transfer(data_size, data); + if (transfer_error.is_error()) { + dmesgln("UAS: Data transfer error: {}", transfer_error.error()); + return transfer_error.release_error(); + } + transfer_size = transfer_error.release_value(); + } else { + VERIFY_NOT_REACHED(); + } + } + + IU sense; + auto sense_size = TRY(m_status_pipe->submit_bulk_in_transfer(sizeof(IU), &sense)); + + // FIXME: Should this check if this is a Sense IU and handle it accordingly? + // Or should we just return the sense data and let the caller handle it? + // Note: Unless the Queue is full we should always get a Sense IU, afaict + // In that case we would get a Response IU instead + + dbgln_if(USB_MASS_STORAGE_DEBUG, "UAS: <- SIU: {:hex-dump}", ReadonlyBytes { &sense, sense_size }); + + SendSCSICommandResult result; + result.transfer_size = transfer_size; + result.response_size = sense_size; + memcpy(&result.response, &sense, sense_size); + return result; + } + +private: + UASInterface(USB::Device&, USBInterface const&, NonnullOwnPtr command_pipe, NonnullOwnPtr status_pipe, NonnullOwnPtr data_in_pipe, NonnullOwnPtr data_out_pipe); + + void add_storage_device(UASStorageDevice& storage_device) { m_storage_devices.append(storage_device); } + + UASStorageDevice::List m_storage_devices; + + USB::Device& m_device; + USBInterface const& m_interface; + NonnullOwnPtr m_command_pipe; + NonnullOwnPtr m_status_pipe; + NonnullOwnPtr m_in_pipe; + NonnullOwnPtr m_out_pipe; + u16 m_next_tag { 1 }; + + IntrusiveListNode> m_list_node; + +public: + using List = IntrusiveList<&UASInterface::m_list_node>; +}; +} diff --git a/Kernel/Devices/Storage/USB/UAS/UASStorageDevice.cpp b/Kernel/Devices/Storage/USB/UAS/UASStorageDevice.cpp new file mode 100644 index 00000000000000..4b00bf636ed093 --- /dev/null +++ b/Kernel/Devices/Storage/USB/UAS/UASStorageDevice.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2023, Leon Albrecht + * Copyright (c) 2024, Sönke Holz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Kernel::USB { + +UASStorageDevice::UASStorageDevice(UASInterface& interface, LUNAddress logical_unit_number_address, u32 hardware_relative_controller_id, size_t sector_size, u64 max_addressable_block) + : StorageDevice(logical_unit_number_address, hardware_relative_controller_id, sector_size, max_addressable_block) + , m_interface(interface) +{ + // Note: If this fails, it only means that we may be inefficient in our way + // of talking to the device. + (void)query_characteristics(); +} + +ErrorOr UASStorageDevice::query_characteristics() +{ + SCSI::Inquiry inquiry_command {}; + inquiry_command.enable_vital_product_data = 1; + alignas(SCSI::SupportedVitalProductPages) u8 vital_product_page_buffer[0xfc]; + SCSI::SupportedVitalProductPages& vital_product_page = *reinterpret_cast(vital_product_page_buffer); + + inquiry_command.page_code = SCSI::VitalProductDataPageCode::SupportedVitalProductDataPages; + inquiry_command.allocation_length = sizeof(vital_product_page_buffer); + + auto status = TRY(m_interface.send_scsi_command(inquiry_command, &vital_product_page, sizeof(vital_product_page_buffer))); + + if (!status.is_sense()) { + dmesgln("SCSI/UAS: Expected Sense IU, got ID {:02x} instead", static_cast(status.response.header.iu_id)); + return EIO; + } + + if (auto& sense = status.as_sense(); sense.status != SCSI::StatusCode::Good) { + dbgln("SCSI/UAS: Inquiry failed to inquire supported vital product data pages with code {}", sense.status); + // FIXME: Maybe request sense here + // FIXME: Treating this as an error for now + // Some HW seems to stall this and/or send garbage... + return EIO; + } + + if (vital_product_page.page_code != SCSI::VitalProductDataPageCode::SupportedVitalProductDataPages) { + dmesgln("SCSI/UAS: Returned wrong page code for supported vital product data pages: {:#02x}", to_underlying(vital_product_page.page_code)); + return EIO; + } + + if ((vital_product_page.page_length + 4uz) > sizeof(vital_product_page_buffer)) { + // Note: This should not be possible, as there are less than 253 page codes allocated + dmesgln("SCSI/UAS: Warning: Returned page length for supported vital product data pages is bigger than the allocated buffer, we might be missing some supported pages"); + } + + // FIXME: Maybe check status.residual_data here + auto available_pages = min(vital_product_page.page_length, sizeof(vital_product_page_buffer) - sizeof(SCSI::VitalProductPage)); + bool found_block_limits = false; + for (size_t i = 0; i < available_pages; i++) { + if (vital_product_page.supported_pages[i] == SCSI::VitalProductDataPageCode::BlockLimits) { + found_block_limits = true; + break; + } + if (to_underlying(vital_product_page.supported_pages[i]) >= to_underlying(SCSI::VitalProductDataPageCode::BlockLimits)) { + // The available pages are (supposedly) sorted in ascending order + // so we can break here early + break; + } + } + + if (!found_block_limits) { + dmesgln("SCSI/UAS: Device does not support block limits page"); + // This is not an error, we just won't be able to optimize our transfers + return {}; + } + + inquiry_command.page_code = SCSI::VitalProductDataPageCode::BlockLimits; + SCSI::BlockLimitsPage block_limits_page {}; + inquiry_command.allocation_length = sizeof(SCSI::BlockLimitsPage); + status = TRY(m_interface.send_scsi_command(inquiry_command, &block_limits_page, sizeof(SCSI::BlockLimitsPage))); + + if (!status.is_sense()) { + dmesgln("SCSI/UAS: Expected Sense IU, got ID {:02x} instead", static_cast(status.response.header.iu_id)); + return EIO; + } + if (auto sense = status.as_sense(); sense.status != SCSI::StatusCode::Good) { + dbgln("SCSI/UAS: Inquiry failed to inquire block limits with code {}", sense.status); + // FIXME: Maybe request sense here + } + + if (block_limits_page.page_code != SCSI::VitalProductDataPageCode::BlockLimits) { + dmesgln("SCSI/UAS: Returned wrong page code for block limits {:#02x}", to_underlying(block_limits_page.page_code)); + return EIO; + } + + if (block_limits_page.page_length != sizeof(SCSI::BlockLimitsPage) - 4) { + dmesgln("SCSI/UAS: Returned wrong page length for block limits {}", block_limits_page.page_length); + return EIO; + } + + if (block_limits_page.maximum_transfer_length != 0) + m_maximum_transfer_length = block_limits_page.maximum_transfer_length; + if (block_limits_page.optimal_transfer_length != 0) + m_optimal_transfer_length = block_limits_page.optimal_transfer_length; + if (block_limits_page.optimal_transfer_length_granularity != 0) + m_optimal_transfer_length_granularity = block_limits_page.optimal_transfer_length_granularity; + + dbgln("SCSI/UAS: Maximum transfer length: {}", m_maximum_transfer_length); + dbgln("SCSI/UAS: Optimal transfer length: {}", m_optimal_transfer_length); + dbgln("SCSI/UAS: Optimal transfer length granularity: {}", m_optimal_transfer_length_granularity); + + return {}; +} + +u32 UASStorageDevice::optimal_block_count(u32 blocks) +{ + if (m_maximum_transfer_length.has_value() && blocks > m_maximum_transfer_length.value()) + return m_maximum_transfer_length.value(); + // quot. OPTIMAL TRANSFER LENGTH field: + // "[...] If a device server receives one of these commands with a transfer size greater than this value, + // then the device server may incur significant delays in processing the command." + if (m_optimal_transfer_length.has_value() && blocks > m_optimal_transfer_length.value()) + return m_optimal_transfer_length.value(); + + if (!m_optimal_transfer_length_granularity.has_value()) + return blocks; + + // quot. OPTIMAL TRANSFER LENGTH GRANULARITY field: + // "[...] If a device server receives one of these commands with a transfer size that + // is not equal to a multiple of this value, then the device server may incur significant + // delays in processing the command." + // FIXME: This sounds like it may be faster to align up to the granularity in some cases + // But that might be difficult to accomplish in some cases (Ie writing) + if (blocks < m_optimal_transfer_length_granularity.value()) + return blocks; + + return blocks - (blocks % m_optimal_transfer_length_granularity.value()); +} + +void UASStorageDevice::start_request(AsyncBlockDeviceRequest& request) +{ + if (request.request_type() == AsyncBlockDeviceRequest::RequestType::Read) { + if (do_read(request.block_index(), request.block_count(), request.buffer(), request.buffer_size()).is_error()) { + request.complete(AsyncDeviceRequest::RequestResult::Failure); + } else { + request.complete(AsyncDeviceRequest::RequestResult::Success); + } + } else { + if (do_write(request.block_index(), request.block_count(), request.buffer(), request.buffer_size()).is_error()) { + request.complete(AsyncDeviceRequest::RequestResult::Failure); + } else { + request.complete(AsyncDeviceRequest::RequestResult::Success); + } + } +} + +ErrorOr UASStorageDevice::do_read(u32 block_index, u32 block_count, UserOrKernelBuffer& buffer, size_t) +{ + // FIXME: Error Handling and proper device reset on exit + SCSI::Read10 read_command; + + u32 block_index_to_read = block_index; + u32 blocks_read = 0; + UserOrKernelBuffer destination_buffer = buffer; + while (blocks_read < block_count) { + read_command.logical_block_address = block_index_to_read; + + // FIXME: We only use READ(10) so we only ever read u16::max blocks at a time + auto blocks_to_transfer = optimal_block_count(block_count - blocks_read); + u16 transfer_length_bytes = min(blocks_to_transfer * block_size(), AK::NumericLimits::max()); + + read_command.transfer_length = transfer_length_bytes / block_size(); + + auto status = TRY(m_interface.send_scsi_command(read_command, destination_buffer, transfer_length_bytes)); + + if (!status.is_sense()) { + dmesgln("SCSI/UAS: Read did not return Sense IU, aborting"); + return EIO; + } + + if (auto sense = status.as_sense(); sense.status != SCSI::StatusCode::Good) { + // FIXME: Actually handle the error + dmesgln("SCSI/UAS: Read failed with status {}", sense.status); + return EIO; + } + + u32 bytes_transferred = status.transfer_size; + u32 blocks_read_in_transfer = bytes_transferred / block_size(); + + blocks_read += blocks_read_in_transfer; + block_index_to_read += blocks_read_in_transfer; + } + + return {}; +} + +ErrorOr UASStorageDevice::do_write(u32 block_index, u32 block_count, UserOrKernelBuffer& buffer, size_t) +{ + // FIXME: Error Handling and proper device reset on exit + SCSI::Write10 read_command; + + u32 block_index_to_read = block_index; + u32 blocks_read = 0; + UserOrKernelBuffer source_buffer = buffer; + while (blocks_read < block_count) { + read_command.logical_block_address = block_index_to_read; + + // FIXME: We only use WRITE(10) so we only ever write u16::max blocks at a time + auto blocks_to_transfer = optimal_block_count(block_count - blocks_read); + u16 transfer_length_bytes = min(blocks_to_transfer * block_size(), AK::NumericLimits::max()); + + read_command.transfer_length = transfer_length_bytes / block_size(); + + auto status = TRY(m_interface.send_scsi_command(read_command, source_buffer, transfer_length_bytes)); + + if (!status.is_sense()) { + dmesgln("SCSI/UAS: Write did not return Sense IU, aborting"); + return EIO; + } + + if (auto sense = status.as_sense(); sense.status != SCSI::StatusCode::Good) { + // FIXME: Actually handle the error + dmesgln("SCSI/UAS: Write failed with status {}", sense.status); + return EIO; + } + + u32 bytes_transferred = status.transfer_size; + u32 blocks_read_in_transfer = bytes_transferred / block_size(); + blocks_read += blocks_read_in_transfer; + block_index_to_read += blocks_read_in_transfer; + } + + return {}; +} + +} diff --git a/Kernel/Devices/Storage/USB/UAS/UASStorageDevice.h b/Kernel/Devices/Storage/USB/UAS/UASStorageDevice.h new file mode 100644 index 00000000000000..bc96f62a1fdf8f --- /dev/null +++ b/Kernel/Devices/Storage/USB/UAS/UASStorageDevice.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024, Leon Albrecht + * Copyright (c) 2024, Sönke Holz + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include + +namespace Kernel::USB { + +class UASInterface; + +class UASStorageDevice : public StorageDevice { +public: + UASStorageDevice(UASInterface&, LUNAddress logical_unit_number_address, u32 hardware_relative_controller_id, size_t sector_size, u64 max_addressable_block); + +private: + UASInterface& m_interface; + + virtual void start_request(AsyncBlockDeviceRequest&) override; + virtual CommandSet command_set() const override { return CommandSet::SCSI; } + + u32 optimal_block_count(u32 blocks); + + ErrorOr do_read(u32 block_index, u32 block_count, UserOrKernelBuffer& buffer, size_t buffer_size); + ErrorOr do_write(u32 block_index, u32 block_count, UserOrKernelBuffer& buffer, size_t buffer_size); + + ErrorOr query_characteristics(); + + Optional m_optimal_transfer_length; + Optional m_optimal_transfer_length_granularity; + Optional m_maximum_transfer_length; + + IntrusiveListNode> m_list_node; + +public: + using List = IntrusiveList<&UASStorageDevice::m_list_node>; +}; + +} diff --git a/Meta/run.py b/Meta/run.py index e5db2b358bf3cb..df6f11cd37aa27 100755 --- a/Meta/run.py +++ b/Meta/run.py @@ -92,6 +92,7 @@ class BootDriveType(Enum): PCI_SD = "pci-sd" USB_UHCI = "usb-uhci" USB_xHCI = "usb-xhci" + USB_UAS = "usb-uas" VirtIOBLK = "virtio" @@ -651,6 +652,12 @@ def set_up_boot_drive(config: Configuration): config.add_device("usb-storage,bus=boot-drive-xhci.0,drive=boot-drive") # FIXME: Find a better way to address the usb drive config.kernel_cmdline.append("root=block3:0") + elif config.boot_drive_type == BootDriveType.USB_UAS: + config.add_device("qemu-xhci,id=boot-drive-xhci,p3=0") + config.add_device("usb-uas,bus=boot-drive-xhci.0,id=boot-drive-uas,pcap=log.pcap") + config.add_device("scsi-hd,bus=boot-drive-uas.0,scsi-id=0,lun=0,drive=boot-drive") + # FIXME: Find a better way to address the usb drive + config.kernel_cmdline.append("root=block3:0") elif config.boot_drive_type == BootDriveType.VirtIOBLK: config.add_device("virtio-blk-pci,drive=boot-drive") config.kernel_cmdline.append("root=lun2:0:0")