mirror of
https://github.com/SerenityOS/serenity
synced 2026-05-12 18:06:56 +02:00
For now we only support USB <3.0 devices, as we don't support streams. 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 ```
239 lines
11 KiB
C++
239 lines
11 KiB
C++
/*
|
|
* Copyright (c) 2023, Leon Albrecht <leon.a@serenityos.org>
|
|
* Copyright (c) 2024, Sönke Holz <sholz8530@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <Kernel/Devices/Storage/USB/SCSIComands.h>
|
|
#include <Kernel/Devices/Storage/USB/UAS/UASInterface.h>
|
|
#include <Kernel/Devices/Storage/USB/UAS/UASStorageDevice.h>
|
|
|
|
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<void> 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<SCSI::SupportedVitalProductPages*>(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<SCSIDataDirection::DataToInitiator>(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<u8>(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<SCSIDataDirection::DataToInitiator>(inquiry_command, &block_limits_page, sizeof(SCSI::BlockLimitsPage)));
|
|
|
|
if (!status.is_sense()) {
|
|
dmesgln("SCSI/UAS: Expected Sense IU, got ID {:02x} instead", static_cast<u8>(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<void> 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<u16>::max());
|
|
|
|
read_command.transfer_length = transfer_length_bytes / block_size();
|
|
|
|
auto status = TRY(m_interface.send_scsi_command<SCSIDataDirection::DataToInitiator>(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<void> 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<u16>::max());
|
|
|
|
read_command.transfer_length = transfer_length_bytes / block_size();
|
|
|
|
auto status = TRY(m_interface.send_scsi_command<SCSIDataDirection::DataToTarget>(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 {};
|
|
}
|
|
|
|
}
|