Newer
Older
minerva / Kernel / Devices / Storage / USB / UAS / UASStorageDevice.cpp
@minerva minerva on 13 Jul 10 KB Initial commit
/*
 * 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 {};
}

}