blob: 8fa4b1d64df8a55cf221b80b35e6a300367041ff [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <endian.h>
#include <lib/scsi/controller.h>
#include <lib/scsi/disk.h>
#include <zircon/status.h>
#include <tuple>
#include <safemath/safe_math.h>
namespace scsi {
zx_status_t Controller::TestUnitReady(uint8_t target, uint16_t lun) {
scsi::TestUnitReadyCDB cdb = {};
cdb.opcode = scsi::Opcode::TEST_UNIT_READY;
zx_status_t status =
ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false, {nullptr, 0});
if (status != ZX_OK) {
FDF_LOGL(DEBUG, driver_logger(), "TEST_UNIT_READY failed for target %u, lun %u: %s", target,
lun, zx_status_get_string(status));
}
return status;
}
zx_status_t Controller::RequestSense(uint8_t target, uint16_t lun, iovec data) {
RequestSenseCDB cdb = {};
cdb.opcode = Opcode::REQUEST_SENSE;
cdb.allocation_length = static_cast<uint8_t>(data.iov_len);
zx_status_t status =
ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false, data);
if (status != ZX_OK) {
FDF_LOGL(DEBUG, driver_logger(), "REQUEST_SENSE failed for target %u, lun %u: %s", target, lun,
zx_status_get_string(status));
}
return status;
}
zx::result<InquiryData> Controller::Inquiry(uint8_t target, uint16_t lun) {
InquiryCDB cdb = {};
cdb.opcode = Opcode::INQUIRY;
InquiryData data = {};
cdb.allocation_length = htobe16(sizeof(data));
zx_status_t status = ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false,
{&data, sizeof(data)});
if (status != ZX_OK) {
FDF_LOGL(DEBUG, driver_logger(), "INQUIRY failed for target %u, lun %u: %s", target, lun,
zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(data);
}
zx::result<VPDBlockLimits> Controller::InquiryBlockLimits(uint8_t target, uint16_t lun) {
InquiryCDB cdb = {};
cdb.opcode = Opcode::INQUIRY;
// Query for all supported VPD pages.
cdb.reserved_and_evpd = 0x1;
cdb.page_code = 0x00;
VPDPageList vpd_pagelist = {};
cdb.allocation_length = htobe16(sizeof(vpd_pagelist));
zx_status_t status = ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false,
{&vpd_pagelist, sizeof(vpd_pagelist)});
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "INQUIRY failed for target %u, lun %u: %s", target, lun,
zx_status_get_string(status));
return zx::error(status);
}
uint8_t i;
for (i = 0; i < vpd_pagelist.page_length; ++i) {
if (vpd_pagelist.pages[i] == InquiryCDB::kBlockLimitsVpdPageCode) {
break;
}
}
if (i == vpd_pagelist.page_length) {
FDF_LOGL(INFO, driver_logger(),
"The Block Limits VPD page is not supported for target %u, lun %u.", target, lun);
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// The Block Limits VPD page is supported, fetch it.
cdb.page_code = InquiryCDB::kBlockLimitsVpdPageCode;
VPDBlockLimits block_limits = {};
cdb.allocation_length = htobe16(sizeof(block_limits));
status = ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false,
{&block_limits, sizeof(block_limits)});
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "INQUIRY failed for target %u, lun %u: %s", target, lun,
zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(block_limits);
}
zx::result<bool> Controller::InquirySupportUnmapCommand(uint8_t target, uint16_t lun) {
InquiryCDB cdb = {};
cdb.opcode = Opcode::INQUIRY;
// Query for all supported VPD pages.
cdb.reserved_and_evpd = 0x1;
cdb.page_code = 0x00;
VPDPageList vpd_pagelist = {};
cdb.allocation_length = htobe16(sizeof(vpd_pagelist));
zx_status_t status = ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false,
{&vpd_pagelist, sizeof(vpd_pagelist)});
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "INQUIRY failed for target %u, lun %u: %s", target, lun,
zx_status_get_string(status));
return zx::error(status);
}
uint8_t i;
for (i = 0; i < vpd_pagelist.page_length; ++i) {
if (vpd_pagelist.pages[i] == InquiryCDB::kLogicalBlockProvisioningVpdPageCode) {
break;
}
}
if (i == vpd_pagelist.page_length) {
FDF_LOGL(ERROR, driver_logger(),
"The Logical Block Provisioning VPD page is not supported for target %u, lun %u.",
target, lun);
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// The Block Limits VPD page is supported, fetch it.
cdb.page_code = InquiryCDB::kLogicalBlockProvisioningVpdPageCode;
VPDLogicalBlockProvisioning provisioning = {};
cdb.allocation_length = htobe16(sizeof(provisioning));
status = ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false,
{&provisioning, sizeof(provisioning)});
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "INQUIRY failed for target %u, lun %u: %s", target, lun,
zx_status_get_string(status));
return zx::error(status);
}
return zx::ok(provisioning.lbpu());
}
zx::result<> Controller::ModeSense(uint8_t target, uint16_t lun, PageCode page_code, iovec data,
bool use_mode_sense_6) {
// Allocate as much as the largest mode sense CDB.
uint8_t cdb[sizeof(ModeSense10CDB)] = {};
size_t cdb_size = 0;
if (use_mode_sense_6 && data.iov_len <= UINT8_MAX) { // MODE SENSE 6
if (data.iov_len < sizeof(Mode6ParameterHeader)) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
ModeSense6CDB* cdb_6 = reinterpret_cast<ModeSense6CDB*>(cdb);
cdb_6->opcode = Opcode::MODE_SENSE_6;
cdb_6->set_page_code(page_code);
cdb_6->allocation_length = safemath::checked_cast<uint8_t>(data.iov_len);
// Do not return any block descriptors.
cdb_6->set_disable_block_descriptors(true);
cdb_size = sizeof(ModeSense6CDB);
} else { // MODE SENSE 10
if (data.iov_len < sizeof(Mode10ParameterHeader) || data.iov_len > UINT16_MAX) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
ModeSense10CDB* cdb_10 = reinterpret_cast<ModeSense10CDB*>(cdb);
cdb_10->opcode = Opcode::MODE_SENSE_10;
cdb_10->set_page_code(page_code);
cdb_10->allocation_length = htobe16(safemath::checked_cast<uint16_t>(data.iov_len));
// Do not return any block descriptors.
cdb_10->set_disable_block_descriptors(true);
cdb_size = sizeof(ModeSense10CDB);
}
zx_status_t status = ExecuteCommandSync(target, lun, {&cdb, cdb_size},
/*is_write=*/false, data);
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "MODE_SENSE_%zu failed for target %u, lun %u: %s", cdb_size,
target, lun, zx_status_get_string(status));
return zx::error(status);
}
return zx::ok();
}
zx::result<std::tuple<bool, bool>> Controller::ModeSenseDpoFuaAndWriteProtectedEnabled(
uint8_t target, uint16_t lun, bool use_mode_sense_6) {
constexpr uint8_t header_size =
std::max(sizeof(Mode6ParameterHeader), sizeof(Mode10ParameterHeader));
uint8_t data[header_size];
zx::result result =
ModeSense(target, lun, PageCode::kAllPageCode, {data, sizeof(data)}, use_mode_sense_6);
if (result.is_error()) {
FDF_LOGL(ERROR, driver_logger(), "MODE_SENSE failed for target %u, lun %u: %s", target, lun,
result.status_string());
return result.take_error();
}
bool dpo_fua_available, write_protected;
if (use_mode_sense_6) {
Mode6ParameterHeader* parameter_header = reinterpret_cast<Mode6ParameterHeader*>(data);
dpo_fua_available = parameter_header->dpo_fua_available();
write_protected = parameter_header->write_protected();
} else {
Mode10ParameterHeader* parameter_header = reinterpret_cast<Mode10ParameterHeader*>(data);
dpo_fua_available = parameter_header->dpo_fua_available();
write_protected = parameter_header->write_protected();
}
return zx::ok(std::make_tuple(dpo_fua_available, write_protected));
}
zx::result<bool> Controller::ModeSenseWriteCacheEnabled(uint8_t target, uint16_t lun,
bool use_mode_sense_6) {
constexpr uint8_t header_size =
std::max(sizeof(Mode6ParameterHeader), sizeof(Mode10ParameterHeader));
uint8_t data[header_size + sizeof(CachingModePage)];
zx::result result =
ModeSense(target, lun, PageCode::kCachingPageCode, {data, sizeof(data)}, use_mode_sense_6);
if (result.is_error()) {
FDF_LOGL(ERROR, driver_logger(), "MODE_SENSE failed for target %u, lun %u: %s", target, lun,
result.status_string());
return result.take_error();
}
uint32_t mode_page_offset =
use_mode_sense_6 ? sizeof(Mode6ParameterHeader) : sizeof(Mode10ParameterHeader);
CachingModePage* mode_page = reinterpret_cast<CachingModePage*>(data + mode_page_offset);
if (mode_page->page_code() != static_cast<uint8_t>(PageCode::kCachingPageCode)) {
FDF_LOGL(ERROR, driver_logger(), "failed for target %u, lun %u to retrieve caching mode page",
target, lun);
return zx::error(ZX_ERR_INTERNAL);
}
return zx::ok(mode_page->write_cache_enabled());
}
zx_status_t Controller::ReadCapacity(uint8_t target, uint16_t lun, uint64_t* block_count,
uint32_t* block_size_bytes) {
ReadCapacity10CDB cdb10 = {};
cdb10.opcode = Opcode::READ_CAPACITY_10;
ReadCapacity10ParameterData data10 = {};
zx_status_t status = ExecuteCommandSync(target, lun, {&cdb10, sizeof(cdb10)}, /*is_write=*/false,
{&data10, sizeof(data10)});
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "READ_CAPACITY_10 failed for target %u, lun %u: %s", target,
lun, zx_status_get_string(status));
return status;
}
*block_count = betoh32(data10.returned_logical_block_address);
*block_size_bytes = betoh32(data10.block_length_in_bytes);
if (*block_count == UINT32_MAX) {
ReadCapacity16CDB cdb16 = {};
cdb16.opcode = Opcode::READ_CAPACITY_16;
cdb16.service_action = 0x10;
ReadCapacity16ParameterData data16 = {};
cdb16.allocation_length = htobe32(sizeof(data16));
status = ExecuteCommandSync(target, lun, {&cdb16, sizeof(cdb16)}, /*is_write=*/false,
{&data16, sizeof(data16)});
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "READ_CAPACITY_16 failed for target %u, lun %u: %s", target,
lun, zx_status_get_string(status));
return status;
}
*block_count = betoh64(data16.returned_logical_block_address);
*block_size_bytes = betoh32(data16.block_length_in_bytes);
}
// +1 because data.returned_logical_block_address returns the address of the final block, and
// blocks are zero indexed.
*block_count = *block_count + 1;
return ZX_OK;
}
zx::result<uint16_t> Controller::ReportLuns(uint8_t target) {
ReportLunsCDB cdb = {};
cdb.opcode = Opcode::REPORT_LUNS;
ReportLunsParameterDataHeader data = {};
cdb.allocation_length = htobe32(sizeof(data));
zx_status_t status =
ExecuteCommandSync(target, 0, {&cdb, sizeof(cdb)}, /*is_write=*/false, {&data, sizeof(data)});
if (status != ZX_OK) {
// Do not log the error, as it generates too many messages. Instead, log on success.
return zx::error(status);
} else {
FDF_LOGL(DEBUG, driver_logger(), "REPORT_LUNS succeeded for target %u.", target);
}
// data.lun_list_length is the number of bytes of LUN structures.
const uint32_t lun_count = betoh32(data.lun_list_length) / 8;
if (lun_count > UINT16_MAX) {
FDF_LOGL(ERROR, driver_logger(), "REPORT_LUNS returned unexpectedly large LUN count: %u",
lun_count);
return zx::error(ZX_ERR_OUT_OF_RANGE);
}
return zx::ok(static_cast<uint16_t>(lun_count));
}
zx_status_t Controller::StartStopUnit(uint8_t target, uint16_t lun, bool immed,
PowerCondition power_condition, uint8_t modifier,
std::optional<bool> load_or_unload) {
StartStopUnitCDB cdb = {};
cdb.opcode = Opcode::START_STOP_UNIT;
cdb.set_immed(immed);
cdb.set_power_condition(power_condition);
cdb.set_power_condition_modifier(modifier);
cdb.set_no_flush(false); // Currently, we only support flush.
if (load_or_unload.has_value()) {
if (power_condition != PowerCondition::kStartValid) {
FDF_LOGL(ERROR, driver_logger(),
"Power condition must be START_VALID to perform load/unload, power_condition=0x%x",
static_cast<uint8_t>(power_condition));
return ZX_ERR_INVALID_ARGS;
}
cdb.set_load_eject(true);
cdb.set_start(load_or_unload.value()); // Load = true, unload = false
}
cdb.set_start(0);
zx_status_t status =
ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false, {nullptr, 0});
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "START STOP UNIT failed for target %u, lun %u: %s", target,
lun, zx_status_get_string(status));
}
return status;
}
zx_status_t Controller::FormatUnit(uint8_t target, uint16_t lun) {
FormatUnitCDB cdb = {};
cdb.opcode = Opcode::FORMAT_UNIT;
cdb.set_fmtpinfo(0); // Currently, only supports type 0 protection.
cdb.set_fmtdata(false); // Currently, we do not send the parameter list.
cdb.set_longlist(false); // If the FMTDATA is set to zero, then the LONGLIST shall be ignored.
cdb.set_cmplst(false); // If the FMTDATA is set to zero, then the CMPLST shall be ignored.
// If the FMTDATA is set to zero, then the DEFECT_LIST_FORMAT is not available.
cdb.set_defect_list_format(0);
zx_status_t status =
ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false, {nullptr, 0});
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "FORMAT UNIT failed for target %u, lun %u: %s", target, lun,
zx_status_get_string(status));
}
return status;
}
zx_status_t Controller::SendDiagnostic(uint8_t target, uint16_t lun, SelfTestCode code) {
SendDiagnosticCDB cdb = {};
cdb.opcode = Opcode::SEND_DIAGNOSTIC;
cdb.set_self_test_code(code);
// We only supports the default self-test feature.
cdb.set_self_test(true);
cdb.set_pf(false);
cdb.parameter_list_length = 0;
cdb.set_dev_off_l(false);
cdb.set_unit_off_l(false);
zx_status_t status =
ExecuteCommandSync(target, lun, {&cdb, sizeof(cdb)}, /*is_write=*/false, {nullptr, 0});
if (status != ZX_OK) {
FDF_LOGL(ERROR, driver_logger(), "SEND DIAGNOSTIC failed for target %u, lun %u: %s", target,
lun, zx_status_get_string(status));
}
return status;
}
zx::result<uint32_t> Controller::ScanAndBindLogicalUnits(uint8_t target,
uint32_t max_transfer_bytes,
uint16_t max_lun, LuCallback lu_callback,
DiskOptions disk_options) {
zx::result<uint16_t> lun_count = ReportLuns(target);
if (lun_count.is_error()) {
return lun_count.take_error();
}
// TODO(b/317838849): We should only attempt to bind to the luns obtained by ReportLuns().
uint16_t luns_found = 0;
for (uint16_t lun = 0; lun < max_lun; ++lun) {
zx::result disk = Disk::Bind(this, target, lun, max_transfer_bytes, disk_options);
if (disk.is_ok()) {
scsi::Disk* dev = disk.value().get();
block_devs_[target][lun] = std::move(disk.value());
if (lu_callback) {
zx::result result = lu_callback(lun, dev->block_size_bytes(), dev->block_count());
if (result.is_error()) {
FDF_LOGL(ERROR, driver_logger(), "SCSI: lu_callback for block device failed: %s",
result.status_string());
return result.take_error();
}
}
++luns_found;
}
if (luns_found == lun_count.value()) {
break;
}
}
if (luns_found != lun_count.value()) {
FDF_LOGL(ERROR, driver_logger(),
"SCSI: Lun count(%d) and the number of luns found(%d) are different.",
lun_count.value(), luns_found);
return zx::error(ZX_ERR_BAD_STATE);
}
return zx::ok(lun_count.value());
}
} // namespace scsi