blob: bf064dcb10f867b74f3f15ddfb1ef98b58795690 [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/block-device.h>
#include <lib/scsi/controller.h>
#include <zircon/status.h>
#include <cstdint>
#include <tuple>
#include <safemath/safe_math.h>
#include "src/devices/block/lib/common/include/common.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,
DeviceOptions device_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 block_device =
BlockDevice::Bind(this, target, lun, max_transfer_bytes, device_options);
if (block_device.is_ok()) {
scsi::BlockDevice* dev = block_device.value().get();
block_devs_[target][lun] = std::move(block_device.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());
}
zx::result<PostProcess> Controller::CheckSenseData(const FixedFormatSenseDataHeader& sense_data) {
// Currently, we only support fixed format sense data.
if (sense_data.response_code() != SenseDataResponseCodes::kFixedCurrentInformation) {
FDF_LOGL(WARNING, driver_logger(),
"SCSI: It only supports FixedFormatSenseData, response_code=0x%x",
static_cast<uint8_t>(sense_data.response_code()));
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
if (sense_data.filemark() || sense_data.eom() || sense_data.ili()) {
FDF_LOGL(WARNING, driver_logger(), "SCSI: Invalid flags, filemark=%d, EOM=%d, ILI=%d",
sense_data.filemark(), sense_data.eom(), sense_data.ili());
return zx::error(ZX_ERR_INVALID_ARGS);
}
PostProcess post_process = PostProcess::kNone;
switch (sense_data.sense_key()) {
case SenseKey::NO_SENSE:
case SenseKey::RECOVERED_ERROR:
break;
case SenseKey::ABORTED_COMMAND: {
if (sense_data.additional_sense_code == 0x10) { // DIF(Data Integrity Field)
// If aborted due to a DIF error, there is no reason to retry.
return zx::error(ZX_ERR_IO_DATA_INTEGRITY);
}
// Check if the abort is due to a command timeout.
// - ASC=0x2e, ASCQ=0x01: COMMAND TIMEOUT BEFORE PROCESSING
// - ASC=0x2e, ASCQ=0x02: COMMAND TIMEOUT DURING PROCESSING
// - ASC=0x2e, ASCQ=0x03: COMMAND TIMEOUT DURING PROCESSING DUE TO ERROR RECOVERY
if (sense_data.additional_sense_code == 0x2e &&
sense_data.additional_sense_code_qualifier >= 0x01 &&
sense_data.additional_sense_code_qualifier <= 0x03) {
return zx::error(ZX_ERR_TIMED_OUT);
}
post_process = PostProcess::kNeedsRetry;
break;
}
case SenseKey::NOT_READY:
case SenseKey::UNIT_ATTENTION:
// Expecting a CHECK_CONDITION/UNIT_ATTENTION because of a bus reset. In this case, we
// need to retry.
// - ASC=0x28, ASCQ=0x00: NOT READY TO READY CHANGE, MEDIUM MAY HAVE CHANGED
if (expect_check_condition_or_unit_attention_ &&
(sense_data.additional_sense_code != 0x28 ||
sense_data.additional_sense_code_qualifier != 0x00)) {
expect_check_condition_or_unit_attention_ = false;
post_process = PostProcess::kNeedsRetry;
break;
}
// TODO(b/317838849): ASC=0x3f, ASCQ=0x0e: REPORTED LUN DATA HAS CHANGED
// TODO(b/317838849): ASC=0x04, ASCQ=0x02: LOGICAL UNIT NOT READY, INITIALIZING COMMAND
// REQUIRED
// If the device is preparing, we should retry.
// - ASC=0x04, ASCQ=0x01: LOGICAL UNIT IS IN PROCESS OF BECOMING READY
if (sense_data.additional_sense_code == 0x04 &&
sense_data.additional_sense_code_qualifier == 0x01) {
post_process = PostProcess::kNeedsRetry;
break;
}
return zx::error(ZX_ERR_BAD_STATE);
default:
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
return zx::ok(post_process);
}
zx::result<PostProcess> Controller::CheckScsiStatus(StatusCode status_code,
const FixedFormatSenseDataHeader& sense_data) {
PostProcess post_process = PostProcess::kNone;
switch (status_code) {
case StatusCode::GOOD:
case StatusCode::TASK_ABORTED:
break;
case StatusCode::CHECK_CONDITION:
return CheckSenseData(sense_data);
case StatusCode::TASK_SET_FULL:
case StatusCode::BUSY:
post_process = PostProcess::kNeedsRetry;
break;
case StatusCode::CONDITION_MET:
case StatusCode::INTERMEDIATE:
case StatusCode::INTERMEDIATE_CONDITION_MET:
case StatusCode::ACA_ACTIVE:
case StatusCode::RESERVATION_CONFILCT:
return zx::error(ZX_ERR_NOT_SUPPORTED);
default:
return zx::error(ZX_ERR_INVALID_ARGS);
}
return zx::ok(post_process);
}
zx::result<> Controller::ScsiComplete(
StatusMessage status_message,
std::optional<std::reference_wrapper<FixedFormatSenseDataHeader>> sense_data) {
zx::result<PostProcess> post_process = zx::ok(PostProcess::kNone);
switch (status_message.host_status_code) {
case HostStatusCode::kOk: {
if (!sense_data.has_value()) {
return zx::error(ZX_ERR_INVALID_ARGS);
}
post_process = CheckScsiStatus(status_message.scsi_status_code, sense_data.value());
break;
}
case HostStatusCode::kTimeout:
post_process = zx::ok(PostProcess::kNeedsErrorHandling);
break;
case HostStatusCode::kRequeue:
case HostStatusCode::kError:
post_process = zx::ok(PostProcess::kNeedsRetry);
break;
case HostStatusCode::kAbort:
post_process = zx::error(ZX_ERR_IO_REFUSED);
break;
default:
FDF_LOGL(WARNING, driver_logger(), "SCSI: Unexpected host status value(%d)",
static_cast<uint8_t>(status_message.host_status_code));
post_process = zx::error(ZX_ERR_BAD_STATE);
break;
}
if (post_process.is_ok() && post_process == PostProcess::kNeedsErrorHandling) {
// Always returns ZX_ERR_TIMED_OUT until we implement an error handler.
post_process = zx::error(ZX_ERR_TIMED_OUT);
}
if (post_process.is_ok() && post_process == PostProcess::kNeedsRetry) {
// Before retry is implemented, UNIT_ATTENTION is ignored by returning ZX_ERR_UNAVAILABLE.
if (sense_data.has_value() && (sense_data->get().sense_key() == SenseKey::UNIT_ATTENTION)) {
return zx::error(ZX_ERR_UNAVAILABLE);
}
// TODO(b/317838849): We need to implement the retry behavior.
post_process = zx::error(ZX_ERR_BAD_STATE);
}
return zx::make_result(post_process.status_value());
}
zx_status_t Controller::ReadBuffer(uint8_t target, uint16_t lun, uint8_t mod, uint8_t buffer_id,
uint32_t buffer_offset, iovec data) {
ReadBufferCDB cdb = {};
cdb.opcode = Opcode::READ_BUFFER;
cdb.mod = mod;
cdb.buffer_id = buffer_id;
block::WriteToBigEndian24(buffer_offset, cdb.buffer_offset);
if (data.iov_len > UINT32_MAX) {
return ZX_ERR_OUT_OF_RANGE;
}
block::WriteToBigEndian24(static_cast<uint32_t>(data.iov_len), cdb.allocation_length);
zx_status_t status = ExecuteCommandSync(target, lun, {.iov_base = &cdb, .iov_len = sizeof(cdb)},
/*is_write=*/false, data);
if (status != ZX_OK) {
FDF_LOGL(DEBUG, driver_logger(), "READ BUFFER failed for target %u, lun %u: %s", target, lun,
zx_status_get_string(status));
}
return status;
}
zx_status_t Controller::WriteBuffer(uint8_t target, uint16_t lun, uint8_t mod, uint8_t buffer_id,
uint32_t buffer_offset, iovec data) {
WriteBufferCDB cdb = {};
cdb.opcode = Opcode::WRITE_BUFFER;
cdb.mod = mod;
cdb.buffer_id = buffer_id;
block::WriteToBigEndian24(buffer_offset, cdb.buffer_offset);
if (data.iov_len > UINT32_MAX) {
return ZX_ERR_OUT_OF_RANGE;
}
block::WriteToBigEndian24(static_cast<uint32_t>(data.iov_len), cdb.parameter_list_length);
zx_status_t status = ExecuteCommandSync(target, lun, {.iov_base = &cdb, .iov_len = sizeof(cdb)},
/*is_write=*/true, data);
if (status != ZX_OK) {
FDF_LOGL(DEBUG, driver_logger(), "WRITE BUFFER failed for target %u, lun %u: %s", target, lun,
zx_status_get_string(status));
}
return status;
}
} // namespace scsi