mirror of
https://github.com/rust-vmm/vhost-device.git
synced 2025-12-28 08:01:04 +00:00
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:
parent
3adff11c94
commit
07b103b4d6
@ -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
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user