diff --git a/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp b/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp new file mode 100644 index 0000000000..2e3def05b1 --- /dev/null +++ b/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2023, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Kernel::USB { + +using namespace MassStorage; + +USB_DEVICE_DRIVER(MassStorageDriver); + +void MassStorageDriver::init() +{ + auto driver = MUST(adopt_nonnull_lock_ref_or_enomem(new MassStorageDriver())); + USBManagement::the().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 {}:{} 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: + if (device.device_descriptor().device_class != USB_CLASS_DEVICE + || device.device_descriptor().device_sub_class != 0x00 + || device.device_descriptor().device_protocol != 0x00) + return ENOTSUP; + + for (auto const& config : device.configurations()) { + // 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; + 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()) + continue; + + has_accepted_an_interface = true; + } + + if (has_accepted_an_interface) + return {}; + } + + return ENOTSUP; +} + +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)); + + u8 max_luns; + TRY(device.control_transfer( + USB_REQUEST_TYPE_CLASS | USB_REQUEST_RECIPIENT_INTERFACE | USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST, + to_underlying(MassStorage::RequestCodes::GetMaxLun), 0, interface.descriptor().interface_id, 1, &max_luns)); + // FIXME: Devices that do not support multiple LUNs may STALL this command + // FIXME: Support multiple LUNs + if (max_luns != 0) + dmesgln("SCSI/BBB: WARNING: USB Mass Storage Device supports multiple LUNs ({}) only targetting first LUN", max_luns); + + u8 in_pipe_address = 0xff; + u8 in_max_packet_size; + u8 out_pipe_address = 0xff; + u8 out_max_packet_size; + + if (interface.descriptor().number_of_endpoints < 2) { + dmesgln("SCSI/BBB: Interface does not provide enough endpoints for advertised Bulk-only transfer protocol; Rejecting"); + return ENOTSUP; + } + + for (auto const& endpoint : interface.endpoints()) { + if (endpoint.endpoint_attributes_bitmap != USBEndpoint::ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_BULK) + continue; + // The upper bit of the Endpoint address is set to 1, iff it is the Bulk-In Endpoint + if (endpoint.endpoint_address & 0x80) { + in_pipe_address = endpoint.endpoint_address & 0b1111; + in_max_packet_size = endpoint.max_packet_size; + } else { + out_pipe_address = endpoint.endpoint_address & 0b1111; + out_max_packet_size = endpoint.max_packet_size; + } + } + if (in_pipe_address == 0xff || out_pipe_address == 0xff) { + dmesgln("SCSI/BBB: Interface did not advertise two Bulk Endpoints; Rejecting"); + return ENOTSUP; + } + + auto in_pipe = TRY(BulkInPipe::create(device.controller(), in_pipe_address, in_max_packet_size, device.address())); + auto out_pipe = TRY(BulkOutPipe::create(device.controller(), out_pipe_address, out_max_packet_size, device.address())); + + CommandBlockWrapper command_block {}; + command_block.set_command(SCSI::ReadCapacity10 {}); + command_block.transfer_length = sizeof(SCSI::ReadCapacity10Parameters); + command_block.direction = CBWDirection::DataIn; + TRY(out_pipe->submit_bulk_out_transfer(31, &command_block)); + + SCSI::ReadCapacity10Parameters capacity; + TRY(in_pipe->submit_bulk_in_transfer(sizeof(capacity), &capacity)); + // FIXME: Handle Stalling + CommandStatusWrapper status; + TRY(in_pipe->submit_bulk_in_transfer(sizeof(status), &status)); + if (status.status != CSWStatus::Passed) { + dmesgln("SCSI/BBB: Failed to query USB Drive capacity; Rejecting"); + 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); + + StorageDevice::LUNAddress lun = { + device.controller().storage_controller_id(), + device.address(), + // FIXME: Again, support multiple LUNs per device + 0 + }; + + auto bulk_scsi_interface = TRY(DeviceManagement::try_create_device( + lun, + device.address(), // FIXME: Figure out a better ID to put here + capacity.block_size, + capacity.block_count, + device, + move(in_pipe), + move(out_pipe))); + + m_interfaces.append(bulk_scsi_interface); + StorageManagement::the().add_device(bulk_scsi_interface); + + return {}; +} + +void MassStorageDriver::detach(USB::Device& device) +{ + auto&& interface = AK::find_if(m_interfaces.begin(), m_interfaces.end(), [&device](auto& interface) { return &interface.device() == &device; }); + + StorageManagement::the().remove_device(*interface); + m_interfaces.remove(*interface); +} + +} diff --git a/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.h b/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.h new file mode 100644 index 0000000000..b7e29260b5 --- /dev/null +++ b/Kernel/Bus/USB/Drivers/MassStorage/MassStorageDriver.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2023, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Kernel::USB { + +class MassStorageDriver final : public Driver { +public: + MassStorageDriver() + : Driver("USB MassStorage"sv) + { + } + + static void init(); + + virtual ~MassStorageDriver() override = default; + + virtual ErrorOr probe(USB::Device&) override; + virtual void detach(USB::Device&) override; + +private: + BulkSCSIInterface::List m_interfaces; + + ErrorOr checkout_interface(USB::Device&, USBInterface const&); + + ErrorOr initialise_bulk_only_device(USB::Device&, USBInterface const&); +}; + +} diff --git a/Kernel/Bus/USB/USBClasses.h b/Kernel/Bus/USB/USBClasses.h index e9eba78d55..a7ee20a3bf 100644 --- a/Kernel/Bus/USB/USBClasses.h +++ b/Kernel/Bus/USB/USBClasses.h @@ -11,6 +11,7 @@ namespace Kernel::USB { // https://www.usb.org/defined-class-codes +static constexpr u8 USB_CLASS_DEVICE = 0x00; static constexpr u8 USB_CLASS_AUDIO = 0x01; static constexpr u8 USB_CLASS_COMMUNICATIONS_AND_CDC_CONTROL = 0x02; static constexpr u8 USB_CLASS_HID = 0x03; diff --git a/Kernel/CMakeLists.txt b/Kernel/CMakeLists.txt index d95a4a9a68..74ffebc48d 100644 --- a/Kernel/CMakeLists.txt +++ b/Kernel/CMakeLists.txt @@ -31,6 +31,7 @@ set(KERNEL_SOURCES Bus/PCI/DeviceIdentifier.cpp Bus/USB/UHCI/UHCIController.cpp Bus/USB/UHCI/UHCIRootHub.cpp + Bus/USB/Drivers/MassStorage/MassStorageDriver.cpp Bus/USB/USBConfiguration.cpp Bus/USB/USBController.cpp Bus/USB/USBDevice.cpp @@ -120,6 +121,7 @@ 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/DiskPartition.cpp Devices/Storage/StorageController.cpp Devices/Storage/StorageDevice.cpp diff --git a/Kernel/Devices/Storage/USB/BulkSCSIInterface.cpp b/Kernel/Devices/Storage/USB/BulkSCSIInterface.cpp new file mode 100644 index 0000000000..c87efc339b --- /dev/null +++ b/Kernel/Devices/Storage/USB/BulkSCSIInterface.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2023, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include + +namespace Kernel::USB { + +BulkSCSIInterface::BulkSCSIInterface(LUNAddress logical_unit_number_address, u32 hardware_relative_controller_id, size_t sector_size, u64 max_addressable_block, USB::Device& device, NonnullOwnPtr in_pipe, NonnullOwnPtr out_pipe) + : StorageDevice(logical_unit_number_address, hardware_relative_controller_id, sector_size, max_addressable_block) + , m_device(device) + , m_in_pipe(move(in_pipe)) + , m_out_pipe(move(out_pipe)) +{ +} + +void BulkSCSIInterface::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 BulkSCSIInterface::do_read(u32 block_index, u32 block_count, UserOrKernelBuffer& buffer, size_t) +{ + // FIXME: Error Handling and proper device reset on exit + CommandBlockWrapper command; + 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; + + u16 transfer_length_bytes = min((block_count - blocks_read) * block_size(), AK::NumericLimits::max()); + + read_command.transfer_length = transfer_length_bytes / block_size(); + + command.transfer_length = transfer_length_bytes; + command.direction = CBWDirection::DataIn; + command.set_command(read_command); + + TRY(m_out_pipe->submit_bulk_out_transfer(sizeof(command), &command)); + + TRY(m_in_pipe->submit_bulk_in_transfer(transfer_length_bytes, destination_buffer)); + + CommandStatusWrapper status; + TRY(m_in_pipe->submit_bulk_in_transfer(sizeof(status), &status)); + + if (status.status != CSWStatus::Passed) { + // FIXME: Actually handle the error + // See usbmassbulk 5.3, 6.4 and 6.5 + dmesgln("SCSI/BBB: Read failed with code {}", to_underlying(status.status)); + return EIO; + } + + u32 bytes_transferred = transfer_length_bytes - status.data_residue; + 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 BulkSCSIInterface::do_write(u32 block_index, u32 block_count, UserOrKernelBuffer& buffer, size_t) +{ + // FIXME: Error Handling and proper device reset on exit + + CommandBlockWrapper command; + SCSI::Write10 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; + + u16 transfer_length_bytes = min((block_count - blocks_read) * block_size(), AK::NumericLimits::max()); + + read_command.transfer_length = transfer_length_bytes / block_size(); + + command.transfer_length = transfer_length_bytes; + command.direction = CBWDirection::DataOut; + command.set_command(read_command); + + TRY(m_out_pipe->submit_bulk_out_transfer(sizeof(command), &command)); + + TRY(m_out_pipe->submit_bulk_out_transfer(transfer_length_bytes, destination_buffer)); + + CommandStatusWrapper status; + TRY(m_in_pipe->submit_bulk_in_transfer(sizeof(status), &status)); + + if (status.status != CSWStatus::Passed) { + // FIXME: Actually handle the error + // See usbmassbulk 5.3, 6.4 and 6.5 + dmesgln("SCSI/BBB: Write failed with code {}", to_underlying(status.status)); + return EIO; + } + + u32 bytes_transferred = transfer_length_bytes - status.data_residue; + 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/BulkSCSIInterface.h b/Kernel/Devices/Storage/USB/BulkSCSIInterface.h new file mode 100644 index 0000000000..78cba89f03 --- /dev/null +++ b/Kernel/Devices/Storage/USB/BulkSCSIInterface.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2023, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Kernel::USB { + +enum class CBWDirection : u8 { + DataOut = 0, + DataIn = 1 +}; + +struct CommandBlockWrapper { + LittleEndian signature { 0x43425355 }; + LittleEndian tag { 0 }; + LittleEndian transfer_length { 0 }; + union { + u8 flags { 0 }; + struct { + u8 flag_reserved : 6; + u8 flag_obsolete : 1; + CBWDirection direction : 1; + }; + }; + u8 lun { 0 }; // only 4 bits + u8 command_length { 0 }; // 5 bits, range 1-16 + u8 command_block[16] { 0 }; + + template + requires(sizeof(T) <= 16) + void set_command(T const& command) + { + command_length = sizeof(command); + memcpy(&command_block, &command, sizeof(command)); + } +}; +static_assert(AssertSize()); + +enum class CSWStatus : u8 { + Passed = 0x00, + Failed = 0x01, + PhaseError = 0x02 +}; + +struct CommandStatusWrapper { + LittleEndian signature; + LittleEndian tag; + LittleEndian data_residue; + CSWStatus status; +}; +static_assert(AssertSize()); + +class BulkSCSIInterface : public StorageDevice { + // https://www.usb.org/sites/default/files/usbmassbulk_10.pdf +public: + BulkSCSIInterface(LUNAddress logical_unit_number_address, u32 hardware_relative_controller_id, size_t sector_size, u64 max_addressable_block, USB::Device& device, NonnullOwnPtr in_pipe, NonnullOwnPtr out_pipe); + + USB::Device const& device() const { return m_device; } + + virtual void start_request(AsyncBlockDeviceRequest&) override; + virtual CommandSet command_set() const override { return CommandSet::SCSI; } + +private: + USB::Device& m_device; + NonnullOwnPtr m_in_pipe; + NonnullOwnPtr m_out_pipe; + + 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); + + IntrusiveListNode> m_list_node; + +public: + using List = IntrusiveList<&BulkSCSIInterface::m_list_node>; +}; + +} diff --git a/Kernel/Devices/Storage/USB/Codes.h b/Kernel/Devices/Storage/USB/Codes.h new file mode 100644 index 0000000000..3215aa4443 --- /dev/null +++ b/Kernel/Devices/Storage/USB/Codes.h @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2023, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace Kernel::USB::MassStorage { +// https://www.usb.org/sites/default/files/Mass_Storage_Specification_Overview_v1.4_2-19-2010.pdf +// 2 +enum class SubclassCode : u8 { + NotReported = 0x00, + RBC = 0x01, + MMC5 = 0x02, // ATAPI + Obsolete_QIC157 = 0x03, + UFI = 0x04, // Floppy + Obsolete_SFF8070i = 0x05, + SCSI_transparent = 0x06, + LSD_FS = 0x07, + IEEE1667 = 0x08, + // Reserved: 0x09 - 0xFE + VendorSpecific = 0xFF +}; + +constexpr StringView subclass_string(SubclassCode code) +{ + switch (code) { + case SubclassCode::NotReported: + return "Not Reported"sv; + case SubclassCode::RBC: + return "RBC"sv; + case SubclassCode::MMC5: + return "MMC-5 (ATAPI)"sv; + case SubclassCode::Obsolete_QIC157: + return "QIC157 (Obsolete)"sv; + case SubclassCode::UFI: + return "UFI"sv; + case SubclassCode::Obsolete_SFF8070i: + return "SFF8070i (Obsolete)"sv; + case SubclassCode::SCSI_transparent: + return "SCSI-transparent"sv; + case SubclassCode::LSD_FS: + return "LSD FS"sv; + case SubclassCode::IEEE1667: + return "IEEE1667"sv; + case SubclassCode::VendorSpecific: + return "Vendor Specific"sv; + } + + return "Reserved"sv; +} + +// 3 +enum class TransportProtocol : u8 { + CBI_completion_interrupt = 0x00, // Control/Bulk/Interrupt + CBI_no_completion_interrupt = 0x01, // Control/Bulk/Interrupt + Obsolete = 0x02, + // Reserved: 0x03 - 0x4F + BBB = 0x50, // Bulk-only + // Reserved: 0x51 - 0x61 + UAS = 0x62, + // Reserved: 0x63 - 0xFE + VendorSpecific = 0xFF +}; + +constexpr StringView transport_protocol_string(TransportProtocol protocol) +{ + switch (protocol) { + case TransportProtocol::CBI_completion_interrupt: + return "Control/Bulk/Interrupt with completion interrupt"sv; + case TransportProtocol::CBI_no_completion_interrupt: + return "Control/Bulk/Interrupt without completion interrupt"sv; + case TransportProtocol::Obsolete: + return "Obsolete"sv; + case TransportProtocol::BBB: + return "Bulk only"sv; + case TransportProtocol::UAS: + return "UAS"sv; + case TransportProtocol::VendorSpecific: + return "Vendor Specific"sv; + } + + return "Reserved"sv; +} + +// 4 +enum class RequestCodes : u8 { + ADSC = 0x00, // Accept Device Specific Command (CBI) - also alias USB-request 00h Get Status + // Reserved/alias USB-bRequest: 0x01 - 0x0D + // Reserved: 0x0E - 0XFB + GetRequest = 0xFC, + PutRequest = 0xFD, + GetMaxLun = 0xFE, // GML (BBB) + BulkOnlyMassStorageReset = 0xFF // BOMSR (BBB) +}; + +// 5 +enum class ClassSpecificDescriptorCodes : u8 { + // Undefined by Mass Storage: 0x00 - 0x23 + PipeUsageClassSpecific = 0x24 // UAS + // Undefined by Mass Storage: 0x25 - 0xFF +}; + +} diff --git a/Kernel/Devices/Storage/USB/SCSIComands.h b/Kernel/Devices/Storage/USB/SCSIComands.h new file mode 100644 index 0000000000..68d01da0cb --- /dev/null +++ b/Kernel/Devices/Storage/USB/SCSIComands.h @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2023, Leon Albrecht + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include + +// https://www.seagate.com/files/staticfiles/support/docs/manual/Interface%20manuals/100293068j.pdf + +namespace Kernel::SCSI { +// 3.22.1 +struct ReadCapacity10 { + u8 opcode { 0x25 }; + u8 reserved1 { 0 }; + BigEndian oboslete_logical_block_address { 0 }; + u16 reserved2 { 0 }; + u8 reserved3 { 0 }; + u8 control { 0 }; +}; +static_assert(AssertSize()); +// 3.22.2 +struct ReadCapacity10Parameters { + BigEndian block_count; + BigEndian block_size; +}; +static_assert(AssertSize()); + +// 3.16 +struct Read10 { + u8 operation_code { 0x28 }; + union { + u8 settings { 0 }; + struct { + u8 obsolete : 2; + u8 rarc : 1; + u8 fua : 1; + u8 dpo : 1; + u8 rdprotect : 3; + }; + }; + BigEndian logical_block_address; + u8 group_number { 0 }; // only bottom 5 bits + BigEndian transfer_length; + u8 control { 0 }; +}; +static_assert(AssertSize()); + +// 3.60 +struct Write10 { + u8 operation_code { 0x2A }; + union { + u8 settings { 0 }; + struct { + u8 obsolete : 2; + u8 reserved : 1; + u8 fua : 1; + u8 dpo : 1; + u8 wrprotect : 3; + }; + }; + BigEndian logical_block_address; + u8 group_number { 0 }; // only bottom 5 bits + BigEndian transfer_length; + u8 control { 0 }; +}; +static_assert(AssertSize()); +}