From 07b103b4d6cb6e6b5fcc82abaa2af27c276bf082 Mon Sep 17 00:00:00 2001 From: Erik Schilling Date: Fri, 5 May 2023 15:16:22 +0200 Subject: [PATCH] scsi: add support for WRITE SAME(16) WRITE SAME allows writing a block for a repeated number of times. Mostly, it can also be used to deallocate parts of the block device (the fstrim functionality uses this). We do not support that aspect yet. Instead, we will just stupidly repeat the pattern as many times as we are told. A future, smarter implementation could just punch a hole into the backend instead of filling it with zeros. Signed-off-by: Erik Schilling --- .../scsi/src/scsi/emulation/block_device.rs | 66 +++++++++++++++++++ crates/scsi/src/scsi/emulation/command.rs | 43 ++++++++++++ crates/scsi/src/scsi/emulation/tests/mod.rs | 46 +++++++++++++ 3 files changed, 155 insertions(+) diff --git a/crates/scsi/src/scsi/emulation/block_device.rs b/crates/scsi/src/scsi/emulation/block_device.rs index b21c833..117c66c 100644 --- a/crates/scsi/src/scsi/emulation/block_device.rs +++ b/crates/scsi/src/scsi/emulation/block_device.rs @@ -192,6 +192,20 @@ impl BlockDevice { Ok(()) } + fn write_same_block( + &mut self, + lba_start: BlockOffset, + block_count: BlockOffset, + buf: &[u8], + ) -> io::Result<()> { + let block_size = self.backend.block_size(); + for lba in u64::from(lba_start)..u64::from(lba_start + block_count) { + let lba = BlockOffset(lba); + self.backend.write_exact_at(buf, lba * block_size)?; + } + Ok(()) + } + pub fn set_write_protected(&mut self, wp: bool) { self.write_protected = wp; } @@ -485,6 +499,58 @@ impl LogicalUnit for BlockDevice { } } } + LunSpecificCommand::WriteSame16 { + lba, + number_of_logical_blocks, + anchor, + } => { + // We do not support block provisioning + if anchor { + return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB)); + } + + // This command can be used to unmap/discard a region of blocks... + // TODO: Do something smarter and punch holes into the backend, + // for now we will just write A LOT of zeros in a very inefficient way. + + let size = match self.backend.size_in_blocks() { + Ok(size) => size, + Err(e) => { + error!("Error getting image size for read: {}", e); + return Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR)); + } + }; + + let lba = BlockOffset(lba); + let number_of_logical_blocks = BlockOffset(number_of_logical_blocks.into()); + + if lba + number_of_logical_blocks > size { + return Ok(CmdOutput::check_condition( + sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE, + )); + } + + let mut buf = vec![ + 0; + usize::try_from(u32::from(self.backend.block_size())) + .expect("block_size should fit usize") + ]; + let read_result = data_out.read_exact(&mut buf); + if let Err(e) = read_result { + error!("Error reading from data_out: {}", e); + return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE)); + } + + let write_result = self.write_same_block(lba, number_of_logical_blocks, &buf); + + match write_result { + Ok(()) => Ok(CmdOutput::ok()), + Err(e) => { + error!("Error writing to block device: {}", e); + Ok(CmdOutput::check_condition(sense::TARGET_FAILURE)) + } + } + } LunSpecificCommand::Inquiry(page_code) => { // top 3 bits 0: peripheral device code = exists and ready // bottom 5 bits 0: device type = block device diff --git a/crates/scsi/src/scsi/emulation/command.rs b/crates/scsi/src/scsi/emulation/command.rs index bdfed33..39fed99 100644 --- a/crates/scsi/src/scsi/emulation/command.rs +++ b/crates/scsi/src/scsi/emulation/command.rs @@ -183,6 +183,11 @@ pub(crate) enum LunSpecificCommand { lba: u32, transfer_length: u16, }, + WriteSame16 { + lba: u64, + number_of_logical_blocks: u32, + anchor: bool, + }, ReadCapacity10, ReadCapacity16, ReportSupportedOperationCodes { @@ -212,6 +217,7 @@ pub(crate) enum CommandType { RequestSense, TestUnitReady, Write10, + WriteSame16, } pub(crate) const OPCODES: &[(CommandType, (u8, Option))] = &[ @@ -222,6 +228,7 @@ pub(crate) const OPCODES: &[(CommandType, (u8, Option))] = &[ (CommandType::ReadCapacity10, (0x25, None)), (CommandType::Read10, (0x28, None)), (CommandType::Write10, (0x2a, None)), + (CommandType::WriteSame16, (0x93, None)), (CommandType::ReadCapacity16, (0x9e, Some(0x10))), (CommandType::ReportLuns, (0xa0, None)), ( @@ -397,6 +404,24 @@ impl CommandType { 0b1111_1111, 0b0000_0100, ], + Self::WriteSame16 => &[ + 0x93, + 0b1111_1001, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b1111_1111, + 0b0011_1111, + 0b0000_0100, + ], Self::Inquiry => &[ 0x12, 0b0000_0001, @@ -555,6 +580,24 @@ impl Cdb { naca: (cdb[9] & 0b0000_0100) != 0, }) } + CommandType::WriteSame16 => { + if cdb[1] & 0b1110_0001 != 0 { + warn!("Unsupported field in WriteSame16"); + // We neither support protections nor logical block provisioning + return Err(ParseError::InvalidField); + } + Ok(Self { + command: Command::LunSpecificCommand(LunSpecificCommand::WriteSame16 { + lba: u64::from_be_bytes(cdb[2..10].try_into().expect("lba should fit u64")), + number_of_logical_blocks: u32::from_be_bytes( + cdb[10..14].try_into().expect("block count should fit u32"), + ), + anchor: (cdb[1] & 0b0001_0000) != 0, + }), + allocation_length: None, + naca: (cdb[15] & 0b0000_0100) != 0, + }) + } CommandType::ReadCapacity10 => Ok(Self { command: Command::LunSpecificCommand(LunSpecificCommand::ReadCapacity10), allocation_length: None, diff --git a/crates/scsi/src/scsi/emulation/tests/mod.rs b/crates/scsi/src/scsi/emulation/tests/mod.rs index f23e01a..8e8ffc1 100644 --- a/crates/scsi/src/scsi/emulation/tests/mod.rs +++ b/crates/scsi/src/scsi/emulation/tests/mod.rs @@ -327,6 +327,52 @@ fn test_write_10() { } } +#[test] +fn test_write_same_16() { + let mut target = EmulatedTarget::new(); + let mut backend = TestBackend::new(); + let dev = BlockDevice::new(backend.clone()); + target.add_lun(Box::new(dev)); + + // TODO: this test relies on the default logical block size of 512. We should + // make that explicit. + + backend + .write_exact_at(&[0xff; 512 * 6], BlockOffset::from(5) * block_size_512()) + .expect("Write should succeed"); + + let data_out = [0_u8; 512]; + + do_command_in( + &mut target, + &[ + 0x93, // WRITE SAME (16) + 0, // flags + 0, 0, 0, 0, 0, 0, 0, 5, // LBA: 5 + 0, 0, 0, 5, // tnumber of blocks: 5 + 0, // reserved, group # + 0, // control + ], + &data_out, + &[], + ); + + let mut buf = [0_u8; 512 * 5]; + backend + .read_exact_at(&mut buf, BlockOffset::from(5) * block_size_512()) + .expect("Reading should work"); + assert_eq!([0_u8; 512 * 5], buf, "5 sectors should have been zero'd"); + + let mut buf = [0_u8; 512]; + backend + .read_exact_at(&mut buf, BlockOffset::from(10) * block_size_512()) + .expect("Reading should work"); + assert_eq!( + [0xff_u8; 512], buf, + "sector after write should be left untouched" + ); +} + #[test] fn test_read_capacity_10() { let mut target = EmulatedTarget::new();