| // 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. |
| |
| #pragma once |
| |
| #include "scsilib_controller.h" |
| #include <ddk/device.h> |
| #include <ddk/driver.h> |
| #include <ddk/protocol/block.h> |
| #include <ddktl/device.h> |
| #include <ddktl/protocol/block.h> |
| #include <stdint.h> |
| |
| namespace scsi { |
| |
| enum class Opcode : uint8_t { |
| TEST_UNIT_READY = 0x00, |
| INQUIRY = 0x12, |
| MODE_SENSE_6 = 0x1A, |
| READ_16 = 0x88, |
| WRITE_16 = 0x8A, |
| READ_CAPACITY_16 = 0x9E, |
| REPORT_LUNS = 0xA0, |
| }; |
| |
| // SCSI command structures (CDBs) |
| |
| struct TestUnitReadyCDB { |
| Opcode opcode; |
| uint8_t reserved[4]; |
| uint8_t control; |
| } __PACKED; |
| |
| static_assert(sizeof(TestUnitReadyCDB) == 6, "TestUnitReady CDB must be 6 bytes"); |
| |
| struct InquiryCDB { |
| Opcode opcode; |
| // reserved_and_evpd(0) is 'Enable Vital Product Data' |
| uint8_t reserved_and_evpd; |
| uint8_t page_code; |
| // allocation_length is in network-byte-order. |
| uint16_t allocation_length; |
| uint8_t control; |
| } __PACKED; |
| |
| static_assert(sizeof(InquiryCDB) == 6, "Inquiry CDB must be 6 bytes"); |
| |
| // Standard INQUIRY Data Header. |
| struct InquiryData { |
| // Peripheral Device Type Header and qualifier. |
| uint8_t peripheral_device_type; |
| // removable(7) is the 'Removable' bit. |
| uint8_t removable; |
| uint8_t version; |
| // response_data_format_and_control(3 downto 0) is Response Data Format |
| // response_data_format_and_control(4) is HiSup |
| // response_data_format_and_control(5) is NormACA |
| uint8_t response_data_format_and_control; |
| uint8_t additional_length; |
| // Various control bits, unused currently. |
| uint8_t control[3]; |
| uint8_t t10_vendor_id[8]; |
| uint8_t product_id[16]; |
| uint8_t product_revision[4]; |
| uint8_t drive_serial_number[8]; |
| } __PACKED; |
| |
| static_assert(offsetof(InquiryData, t10_vendor_id) == 8, "T10 Vendor ID is at offset 8"); |
| static_assert(offsetof(InquiryData, product_id) == 16, "Product ID is at offset 16"); |
| |
| struct ModeSense6CDB { |
| Opcode opcode; |
| // If disable_block_descriptors(4) is '1', device will not return Block Descriptors. |
| uint8_t disable_block_descriptors; |
| // page_code(7 downto 6) is 'page control'. Should be 00h for current devices. |
| uint8_t page_code; |
| uint8_t subpage_code; |
| uint8_t allocation_length; |
| uint8_t control; |
| } __PACKED; |
| |
| static_assert(sizeof(ModeSense6CDB) == 6, "Mode Sense 6 CDB must be 6 bytes"); |
| |
| struct ModeSense6ParameterHeader { |
| uint8_t mode_data_length; |
| // 00h is 'Direct Access Block Device' |
| uint8_t medium_type; |
| // For Direct Access Block Devices: |
| // device_specific_parameter(7) is write-protected bit |
| // device_specific_parameter(4) is disable page out/force unit access available |
| uint8_t device_specific_parameter; |
| uint8_t block_descriptor_length; |
| } __PACKED; |
| |
| static_assert(sizeof(ModeSense6ParameterHeader) == 4, "Mode sense 6 parameters must be 4 bytes"); |
| |
| struct ReadCapacity16CDB { |
| Opcode opcode; |
| uint8_t service_action; |
| uint64_t reserved; |
| uint32_t allocation_length; |
| uint8_t pmi; |
| uint8_t control; |
| } __PACKED; |
| |
| static_assert(sizeof(ReadCapacity16CDB) == 16, "Read Capacity 16 CDB must be 16 bytes"); |
| |
| struct ReadCapacity16ParameterData { |
| uint64_t returned_logical_block_address; |
| uint32_t block_length_in_bytes; |
| uint8_t prot_info; |
| uint8_t logical_blocks_exponent_info; |
| uint16_t lowest_aligned_logical_block; |
| uint8_t reserved[16]; |
| } __PACKED; |
| |
| static_assert(sizeof(ReadCapacity16ParameterData) == 32, "Read Capacity 16 Params are 32 bytes"); |
| |
| struct ReportLunsCDB { |
| Opcode opcode; |
| uint8_t reserved0; |
| uint8_t select_report; |
| uint8_t reserved1[3]; |
| uint32_t allocation_length; |
| uint8_t reserved2; |
| uint8_t control; |
| } __PACKED; |
| |
| static_assert(sizeof(ReportLunsCDB) == 12, "Report LUNs CDB must be 12 bytes"); |
| |
| struct ReportLunsParameterDataHeader { |
| uint32_t lun_list_length; |
| uint32_t reserved; |
| uint64_t lun; // Need space for at least one LUN. |
| // Followed by 8-byte LUN structures. |
| } __PACKED; |
| |
| static_assert(sizeof(ReportLunsParameterDataHeader) == 16, "Report LUNs Header must be 16 bytes"); |
| |
| // Count the number of addressable LUNs attached to a target. |
| uint32_t CountLuns(Controller* controller, uint8_t target); |
| |
| struct Read16CDB { |
| Opcode opcode; |
| // dpo_fua(4) - DPO - Disable Page Out |
| // dpo_fua(3) - FUA - Force Unit Access |
| uint8_t dpo_fua; |
| // Network byte order |
| uint64_t logical_block_address; |
| uint32_t transfer_length; |
| uint8_t reserved; |
| uint8_t control; |
| } __PACKED; |
| |
| static_assert(sizeof(Read16CDB) == 16, "Read 16 CDB must be 16 bytes"); |
| |
| struct Write16CDB { |
| Opcode opcode; |
| // dpo_fua(4) - DPO - Disable Page Out |
| // dpo_fua(3) - FUA - Write to medium |
| // dpo_fua(1) - FUA_NV - If NV_SUP is 1, prefer write to nonvolatile cache. |
| uint8_t dpo_fua; |
| // Network byte order |
| uint64_t logical_block_address; |
| uint32_t transfer_length; |
| uint8_t reserved; |
| uint8_t control; |
| } __PACKED; |
| |
| static_assert(sizeof(Write16CDB) == 16, "Write 16 CDB must be 16 bytes"); |
| |
| class Disk; |
| using DeviceType = ddk::Device<Disk, ddk::GetSizable, ddk::Unbindable>; |
| |
| // |Disk| represents a single SCSI direct access block device. |
| // |Disk| bridges between the Zircon block protocol and SCSI commands/responses. |
| class Disk : public DeviceType, public ddk::BlockImplProtocol<Disk, ddk::base_protocol> { |
| public: |
| // Public so that we can use make_unique. |
| // Clients should use Disk::Create(). |
| Disk(Controller* controller, zx_device_t* parent, uint8_t target, uint16_t lun); |
| |
| // Create a Disk at a specific target/lun. |
| // |controller| is a pointer to the scsi::Controller this disk is attached to. |
| // |controller| must outlast Disk. |
| // This disk does not take ownership of or any references on |controller|. |
| static zx_status_t Create(Controller* controller, zx_device_t* parent, uint8_t target, |
| uint16_t lun); |
| |
| const char* tag() const { return tag_; } |
| |
| // DeviceType functions. |
| void DdkUnbind() { DdkRemove(); } |
| void DdkRelease() { delete this; } |
| |
| // ddk::GetSizable functions. |
| zx_off_t DdkGetSize() { return blocks_ * block_size_; } |
| |
| // ddk::BlockImplProtocol functions. |
| void BlockImplQuery(block_info_t* info_out, size_t* block_op_size_out) { |
| info_out->block_size = block_size_; |
| info_out->block_count = blocks_; |
| info_out->max_transfer_size = block_size_ * 32; // TODO(ZX-2314): Correct this size. |
| info_out->flags = (removable_) ? BLOCK_FLAG_REMOVABLE : 0; |
| *block_op_size_out = sizeof(block_op_t); |
| } |
| void BlockImplQueue(block_op_t* operation, block_impl_queue_callback completion_cb, |
| void* cookie); |
| |
| Disk(const Disk&) = delete; |
| Disk& operator=(const Disk&) = delete; |
| |
| private: |
| zx_status_t Bind(); |
| |
| Controller* const controller_; |
| char tag_[24]; |
| const uint8_t target_; |
| const uint16_t lun_; |
| |
| bool removable_; |
| uint64_t blocks_; |
| uint32_t block_size_; |
| }; |
| |
| } // namespace scsi |