blob: 7086faf35fd3ffa6938510787b5678d8597eb198 [file] [log] [blame]
// Copyright 2019 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 <ddk/binding.h>
#include <lib/fake_ddk/fake_ddk.h>
#include <lib/scsi/scsilib.h>
#include <lib/scsi/scsilib_controller.h>
#include <lib/fit/function.h>
#include <zxtest/zxtest.h>
namespace {
class Binder : public fake_ddk::Bind {
public:
zx_status_t DeviceAdd(zx_driver_t* drv, zx_device_t* parent, device_add_args_t* args,
zx_device_t** out) {
device_ = reinterpret_cast<scsi::Disk*>(args->ctx);
return Base::DeviceAdd(drv, parent, args, out);
}
scsi::Disk* device() const { return device_; }
private:
using Base = fake_ddk::Bind;
scsi::Disk* device_;
};
class ScsiControllerForTest : public scsi::Controller {
public:
using IOCallbackType = fit::function<zx_status_t(uint8_t, uint16_t, struct iovec, struct iovec,
struct iovec)>;
~ScsiControllerForTest() {
ASSERT_EQ(times_, 0);
}
zx_status_t ExecuteCommandSync(uint8_t target, uint16_t lun, struct iovec cdb,
struct iovec data_out, struct iovec data_in) override {
EXPECT_TRUE(do_io_);
EXPECT_GT(times_, 0);
if (!do_io_ || times_ == 0) {
return ZX_ERR_INTERNAL;
}
auto status = do_io_(target, lun, cdb, data_out, data_in);
if (--times_ == 0) {
decltype(do_io_) empty;
do_io_.swap(empty);
}
return status;
}
void ExpectCall(IOCallbackType do_io, int times) {
do_io_.swap(do_io);
times_ = times;
}
private:
IOCallbackType do_io_;
int times_ = 0;
};
// Test that we can create a disk when the underlying controller successfully executes CDBs.
TEST(ScsilibDiskTest, TestCreateDestroy) {
static constexpr uint8_t kTarget = 5;
static constexpr uint16_t kLun = 1;
static constexpr int kTransferSize = 32 * 1024;
static constexpr uint64_t kFakeBlocks = 128000ul;
static constexpr uint32_t kBlockSize = 512;
ScsiControllerForTest controller;
int seq = 0;
controller.ExpectCall([&seq](uint8_t target, uint16_t lun, struct iovec cdb,
struct iovec data_out, struct iovec data_in) -> auto {
EXPECT_EQ(target, kTarget);
EXPECT_EQ(lun, kLun);
if (seq == 0) {
// INQUIRY is expected first.
EXPECT_EQ(cdb.iov_len, 6);
scsi::InquiryCDB decoded_cdb = {};
memcpy(reinterpret_cast<scsi::InquiryCDB*>(&decoded_cdb), cdb.iov_base, cdb.iov_len);
EXPECT_EQ(decoded_cdb.opcode, scsi::Opcode::INQUIRY);
} else {
// Then READ CAPACITY (16).
EXPECT_EQ(cdb.iov_len, 16);
scsi::ReadCapacity16CDB decoded_cdb = {};
memcpy(reinterpret_cast<scsi::ReadCapacity16CDB*>(&decoded_cdb), cdb.iov_base,
cdb.iov_len);
EXPECT_EQ(decoded_cdb.opcode, scsi::Opcode::READ_CAPACITY_16);
EXPECT_EQ(decoded_cdb.service_action, 0x10);
scsi::ReadCapacity16ParameterData response = {};
response.returned_logical_block_address = htobe64(kFakeBlocks - 1);
response.block_length_in_bytes = htobe32(kBlockSize);
memcpy(data_in.iov_base, reinterpret_cast<char*>(&response), sizeof(response));
}
seq++;
return ZX_OK;
}, /*times=*/2);
Binder bind;
EXPECT_EQ(scsi::Disk::Create(&controller, fake_ddk::kFakeParent, kTarget, kLun, kTransferSize),
ZX_OK);
EXPECT_EQ(bind.device()->DdkGetSize(), kFakeBlocks * kBlockSize);
bind.device()->DdkRemove();
EXPECT_TRUE(bind.Ok());
}
} // namespace