Skip to content

Commit c55bc71

Browse files
author
Erik Schilling
committed
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 <[email protected]>
1 parent f30f342 commit c55bc71

File tree

4 files changed

+145
-0
lines changed

4 files changed

+145
-0
lines changed

crates/scsi/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,5 @@ vmm-sys-util = "0.11"
2828

2929
[dev-dependencies]
3030
tempfile = "3.2.0"
31+
virtio-queue = { version = "0.7", features = ["test-utils"] }
3132

crates/scsi/src/scsi/emulation/block_device.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::{
44
convert::{TryFrom, TryInto},
55
fs::File,
66
io::{self, Read, Write},
7+
ops::Range,
78
os::unix::prelude::*,
89
};
910

@@ -107,6 +108,14 @@ impl<T: BlockDeviceBackend> BlockDevice<T> {
107108
Ok(())
108109
}
109110

111+
fn write_same_block(&mut self, lba_range: Range<u64>, buf: &[u8]) -> io::Result<()> {
112+
let block_size = u64::from(self.backend.block_size());
113+
for lba in lba_range {
114+
self.backend.write_exact_at(buf, lba * block_size)?;
115+
}
116+
Ok(())
117+
}
118+
110119
pub fn set_write_protected(&mut self, wp: bool) {
111120
self.write_protected = wp;
112121
}
@@ -393,6 +402,52 @@ impl<T: BlockDeviceBackend> LogicalUnit for BlockDevice<T> {
393402
}
394403
}
395404
}
405+
LunSpecificCommand::WriteSame16 {
406+
lba,
407+
number_of_logical_blocks,
408+
anchor,
409+
} => {
410+
// We do not support block provisioning
411+
if anchor {
412+
return Ok(CmdOutput::check_condition(sense::INVALID_FIELD_IN_CDB));
413+
}
414+
415+
// This command can be used to unmap/discard a region of blocks...
416+
// TODO: Do something smarter and punch holes into the backend,
417+
// for now we will just write A LOT of zeros in a very inefficient way.
418+
419+
let size = match self.backend.size_in_blocks() {
420+
Ok(size) => size,
421+
Err(e) => {
422+
error!("Error getting image size for read: {}", e);
423+
return Ok(CmdOutput::check_condition(sense::UNRECOVERED_READ_ERROR));
424+
}
425+
};
426+
427+
if lba + u64::from(number_of_logical_blocks) > size {
428+
return Ok(CmdOutput::check_condition(
429+
sense::LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE,
430+
));
431+
}
432+
433+
let mut buf = vec![0; self.backend.block_size() as usize];
434+
let read_result = data_out.read_exact(&mut buf);
435+
if let Err(e) = read_result {
436+
error!("Error reading from data_out: {}", e);
437+
return Ok(CmdOutput::check_condition(sense::TARGET_FAILURE));
438+
}
439+
440+
let write_result =
441+
self.write_same_block(lba..(lba + u64::from(number_of_logical_blocks)), &buf);
442+
443+
match write_result {
444+
Ok(()) => Ok(CmdOutput::ok()),
445+
Err(e) => {
446+
error!("Error writing to block device: {}", e);
447+
Ok(CmdOutput::check_condition(sense::TARGET_FAILURE))
448+
}
449+
}
450+
}
396451
LunSpecificCommand::Inquiry(page_code) => {
397452
// top 3 bits 0: peripheral device code = exists and ready
398453
// bottom 5 bits 0: device type = block device

crates/scsi/src/scsi/emulation/command.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,11 @@ pub(crate) enum LunSpecificCommand {
183183
lba: u32,
184184
transfer_length: u16,
185185
},
186+
WriteSame16 {
187+
lba: u64,
188+
number_of_logical_blocks: u32,
189+
anchor: bool,
190+
},
186191
ReadCapacity10,
187192
ReadCapacity16,
188193
ReportSupportedOperationCodes {
@@ -212,6 +217,7 @@ pub(crate) enum CommandType {
212217
RequestSense,
213218
TestUnitReady,
214219
Write10,
220+
WriteSame16,
215221
}
216222

217223
pub(crate) const OPCODES: &[(CommandType, (u8, Option<u16>))] = &[
@@ -222,6 +228,7 @@ pub(crate) const OPCODES: &[(CommandType, (u8, Option<u16>))] = &[
222228
(CommandType::ReadCapacity10, (0x25, None)),
223229
(CommandType::Read10, (0x28, None)),
224230
(CommandType::Write10, (0x2a, None)),
231+
(CommandType::WriteSame16, (0x93, None)),
225232
(CommandType::ReadCapacity16, (0x9e, Some(0x10))),
226233
(CommandType::ReportLuns, (0xa0, None)),
227234
(
@@ -397,6 +404,24 @@ impl CommandType {
397404
0b1111_1111,
398405
0b0000_0100,
399406
],
407+
Self::WriteSame16 => &[
408+
0x93,
409+
0b1111_1001,
410+
0b1111_1111,
411+
0b1111_1111,
412+
0b1111_1111,
413+
0b1111_1111,
414+
0b1111_1111,
415+
0b1111_1111,
416+
0b1111_1111,
417+
0b1111_1111,
418+
0b1111_1111,
419+
0b1111_1111,
420+
0b1111_1111,
421+
0b1111_1111,
422+
0b0011_1111,
423+
0b0000_0100,
424+
],
400425
Self::Inquiry => &[
401426
0x12,
402427
0b0000_0001,
@@ -555,6 +580,24 @@ impl Cdb {
555580
naca: (cdb[9] & 0b0000_0100) != 0,
556581
})
557582
}
583+
CommandType::WriteSame16 => {
584+
if cdb[1] & 0b1110_0001 != 0 {
585+
warn!("Unsupported field in WriteSame16");
586+
// We neither support protections nor logical block provisioning
587+
return Err(ParseError::InvalidField);
588+
}
589+
Ok(Self {
590+
command: Command::LunSpecificCommand(LunSpecificCommand::WriteSame16 {
591+
lba: u64::from_be_bytes(cdb[2..10].try_into().expect("lba should fit u64")),
592+
number_of_logical_blocks: u32::from_be_bytes(
593+
cdb[10..14].try_into().expect("block count should fit u32"),
594+
),
595+
anchor: (cdb[1] & 0b0001_0000) != 0,
596+
}),
597+
allocation_length: None,
598+
naca: (cdb[15] & 0b0000_0100) != 0,
599+
})
600+
}
558601
CommandType::ReadCapacity10 => Ok(Self {
559602
command: Command::LunSpecificCommand(LunSpecificCommand::ReadCapacity10),
560603
allocation_length: None,

crates/scsi/src/scsi/emulation/tests/mod.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,52 @@ fn test_write_10() {
322322
}
323323
}
324324

325+
#[test]
326+
fn test_write_same_16() {
327+
let mut target = EmulatedTarget::new();
328+
let mut backend = TestBackend::new();
329+
let dev = BlockDevice::new(backend.clone());
330+
target.add_lun(Box::new(dev));
331+
332+
// TODO: this test relies on the default logical block size of 512. We should
333+
// make that explicit.
334+
335+
backend
336+
.write_exact_at(&[0xff; 512 * 6], 512 * 5)
337+
.expect("Write should succeed");
338+
339+
let data_out = [0_u8; 512];
340+
341+
do_command_in(
342+
&mut target,
343+
&[
344+
0x93, // WRITE SAME (16)
345+
0, // flags
346+
0, 0, 0, 0, 0, 0, 0, 5, // LBA: 5
347+
0, 0, 0, 5, // tnumber of blocks: 5
348+
0, // reserved, group #
349+
0, // control
350+
],
351+
&data_out,
352+
&[],
353+
);
354+
355+
let mut buf = [0_u8; 512 * 5];
356+
backend
357+
.read_exact_at(&mut buf, 5 * 512)
358+
.expect("Reading should work");
359+
assert_eq!([0_u8; 512 * 5], buf, "5 sectors should have been zero'd");
360+
361+
let mut buf = [0_u8; 512];
362+
backend
363+
.read_exact_at(&mut buf, 10 * 512)
364+
.expect("Reading should work");
365+
assert_eq!(
366+
[0xff_u8; 512], buf,
367+
"sector after write should be left untouched"
368+
);
369+
}
370+
325371
#[test]
326372
fn test_read_capacity_10() {
327373
let mut target = EmulatedTarget::new();

0 commit comments

Comments
 (0)