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 <erik.schilling@linaro.org>
This commit is contained in:
Erik Schilling 2023-05-05 15:16:22 +02:00 committed by Viresh Kumar
parent 3adff11c94
commit 07b103b4d6
3 changed files with 155 additions and 0 deletions

View File

@ -192,6 +192,20 @@ impl<T: BlockDeviceBackend> BlockDevice<T> {
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<T: BlockDeviceBackend> LogicalUnit for BlockDevice<T> {
}
}
}
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

View File

@ -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<u16>))] = &[
@ -222,6 +228,7 @@ pub(crate) const OPCODES: &[(CommandType, (u8, Option<u16>))] = &[
(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,

View File

@ -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();