Skip to main content

arcbox_virtio_blk/
request.rs

1//! Block device wire types — config, request header, request type, status.
2
3use std::path::PathBuf;
4
5use arcbox_virtio_core::error::VirtioError;
6use arcbox_virtio_core::virtio_bindings;
7
8/// Block device configuration.
9#[derive(Debug, Clone)]
10pub struct BlockConfig {
11    /// Disk capacity in 512-byte sectors.
12    pub capacity: u64,
13    /// Block size (usually 512).
14    pub blk_size: u32,
15    /// Path to the backing file/device.
16    pub path: PathBuf,
17    /// Read-only mode.
18    pub read_only: bool,
19    /// Number of request queues (1 = single queue, >1 = multi-queue with `F_MQ`).
20    pub num_queues: u16,
21}
22
23impl Default for BlockConfig {
24    fn default() -> Self {
25        Self {
26            capacity: 0,
27            blk_size: 512,
28            path: PathBuf::new(),
29            read_only: false,
30            num_queues: 1,
31        }
32    }
33}
34
35/// `virtio_blk_discard_write_zeroes.flags` bit requesting deallocation while
36/// zeroing. ArcBox advertises `write_zeroes_may_unmap=0`, so callers must still
37/// guarantee zero reads even when this bit is set.
38pub const WRITE_ZEROES_FLAG_UNMAP: u32 = 1;
39
40/// One entry in a DISCARD / WRITE_ZEROES request's range list.
41///
42/// The on-wire struct is `virtio_blk_discard_write_zeroes`: sector (le64),
43/// num_sectors (le32), flags (le32) — 16 bytes total.
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct DiscardWriteZeroesRange {
46    /// Starting sector in the guest block device.
47    pub sector: u64,
48    /// Number of sectors covered by this range.
49    pub num_sectors: u32,
50    /// Request flags (`WRITE_ZEROES_FLAG_UNMAP` is valid for WRITE_ZEROES only).
51    pub flags: u32,
52}
53
54const RANGE_ENTRY_SIZE: usize = 16;
55
56/// Parses a DISCARD / WRITE_ZEROES range list.
57///
58/// # Errors
59///
60/// Returns an error if the list is empty or not composed of whole 16-byte
61/// entries.
62pub fn parse_range_list(
63    bytes: &[u8],
64) -> std::result::Result<Vec<DiscardWriteZeroesRange>, VirtioError> {
65    if bytes.is_empty() || bytes.len() % RANGE_ENTRY_SIZE != 0 {
66        return Err(VirtioError::InvalidOperation(format!(
67            "range list size {} not a multiple of 16",
68            bytes.len()
69        )));
70    }
71    let mut ranges = Vec::with_capacity(bytes.len() / RANGE_ENTRY_SIZE);
72    for chunk in bytes.chunks_exact(RANGE_ENTRY_SIZE) {
73        ranges.push(DiscardWriteZeroesRange {
74            sector: u64::from_le_bytes(chunk[0..8].try_into().unwrap()),
75            num_sectors: u32::from_le_bytes(chunk[8..12].try_into().unwrap()),
76            flags: u32::from_le_bytes(chunk[12..16].try_into().unwrap()),
77        });
78    }
79    Ok(ranges)
80}
81
82impl DiscardWriteZeroesRange {
83    /// Converts the sector range to a checked byte range `[start, end)`.
84    ///
85    /// # Errors
86    ///
87    /// Returns an error if the sector range overflows the configured block size
88    /// or extends past the device capacity.
89    pub fn checked_byte_range(
90        self,
91        blk_size: u32,
92        capacity_sectors: u64,
93    ) -> std::result::Result<(u64, u64), VirtioError> {
94        let sector_end = self
95            .sector
96            .checked_add(u64::from(self.num_sectors))
97            .ok_or_else(|| VirtioError::InvalidOperation("range sector overflow".into()))?;
98        if sector_end > capacity_sectors {
99            return Err(VirtioError::InvalidOperation(format!(
100                "range exceeds device capacity: {}..{} > {} sectors",
101                self.sector, sector_end, capacity_sectors
102            )));
103        }
104
105        let block_size = u64::from(blk_size);
106        let start = self
107            .sector
108            .checked_mul(block_size)
109            .ok_or_else(|| VirtioError::InvalidOperation("range byte offset overflow".into()))?;
110        let bytes = u64::from(self.num_sectors)
111            .checked_mul(block_size)
112            .ok_or_else(|| VirtioError::InvalidOperation("range byte length overflow".into()))?;
113        let end = start
114            .checked_add(bytes)
115            .ok_or_else(|| VirtioError::InvalidOperation("range byte end overflow".into()))?;
116        Ok((start, end))
117    }
118}
119
120/// Converts a normal read/write request to a checked byte range.
121///
122/// # Errors
123///
124/// Returns an error if the byte offset overflows, the requested byte length
125/// overflows the device capacity, or the request extends past capacity.
126pub fn checked_io_byte_range(
127    sector: u64,
128    byte_len: usize,
129    blk_size: u32,
130    capacity_sectors: u64,
131) -> std::result::Result<(u64, u64), VirtioError> {
132    let block_size = u64::from(blk_size);
133    let capacity_bytes = capacity_sectors
134        .checked_mul(block_size)
135        .ok_or_else(|| VirtioError::InvalidOperation("device capacity overflow".into()))?;
136    let start = sector
137        .checked_mul(block_size)
138        .ok_or_else(|| VirtioError::InvalidOperation("I/O byte offset overflow".into()))?;
139    let len = u64::try_from(byte_len)
140        .map_err(|_| VirtioError::InvalidOperation("I/O byte length overflow".into()))?;
141    let end = start
142        .checked_add(len)
143        .ok_or_else(|| VirtioError::InvalidOperation("I/O byte end overflow".into()))?;
144    if end > capacity_bytes {
145        return Err(VirtioError::InvalidOperation(format!(
146            "I/O exceeds device capacity: bytes {}..{} > {}",
147            start, end, capacity_bytes
148        )));
149    }
150    Ok((start, end))
151}
152
153/// `VirtIO` block request types.
154///
155/// Values sourced from `virtio_bindings::virtio_blk::VIRTIO_BLK_T_*`.
156#[repr(u32)]
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
158pub enum BlockRequestType {
159    /// Read request.
160    In = virtio_bindings::virtio_blk::VIRTIO_BLK_T_IN,
161    /// Write request.
162    Out = virtio_bindings::virtio_blk::VIRTIO_BLK_T_OUT,
163    /// Flush request.
164    Flush = virtio_bindings::virtio_blk::VIRTIO_BLK_T_FLUSH,
165    /// Get device ID.
166    GetId = virtio_bindings::virtio_blk::VIRTIO_BLK_T_GET_ID,
167    /// Discard request.
168    Discard = virtio_bindings::virtio_blk::VIRTIO_BLK_T_DISCARD,
169    /// Write zeroes request.
170    WriteZeroes = virtio_bindings::virtio_blk::VIRTIO_BLK_T_WRITE_ZEROES,
171}
172
173impl TryFrom<u32> for BlockRequestType {
174    type Error = VirtioError;
175
176    fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
177        use virtio_bindings::virtio_blk;
178        match value {
179            virtio_blk::VIRTIO_BLK_T_IN => Ok(Self::In),
180            virtio_blk::VIRTIO_BLK_T_OUT => Ok(Self::Out),
181            virtio_blk::VIRTIO_BLK_T_FLUSH => Ok(Self::Flush),
182            virtio_blk::VIRTIO_BLK_T_GET_ID => Ok(Self::GetId),
183            virtio_blk::VIRTIO_BLK_T_DISCARD => Ok(Self::Discard),
184            virtio_blk::VIRTIO_BLK_T_WRITE_ZEROES => Ok(Self::WriteZeroes),
185            _ => Err(VirtioError::InvalidOperation(format!(
186                "Unknown block request type: {value}"
187            ))),
188        }
189    }
190}
191
192/// `VirtIO` block request status.
193///
194/// Values sourced from `virtio_bindings::virtio_blk::VIRTIO_BLK_S_*`.
195#[repr(u8)]
196#[derive(Debug, Clone, Copy)]
197pub enum BlockStatus {
198    /// Success.
199    Ok = virtio_bindings::virtio_blk::VIRTIO_BLK_S_OK as u8,
200    /// I/O error.
201    IoErr = virtio_bindings::virtio_blk::VIRTIO_BLK_S_IOERR as u8,
202    /// Unsupported operation.
203    Unsupp = virtio_bindings::virtio_blk::VIRTIO_BLK_S_UNSUPP as u8,
204}
205
206/// `VirtIO` block request header.
207#[repr(C)]
208#[derive(Debug, Clone, Copy)]
209pub struct BlockRequestHeader {
210    /// Request type.
211    pub request_type: u32,
212    /// Reserved.
213    pub reserved: u32,
214    /// Sector offset.
215    pub sector: u64,
216}
217
218impl BlockRequestHeader {
219    /// Size of the header in bytes.
220    pub const SIZE: usize = 16;
221
222    /// Parses header from bytes.
223    #[must_use]
224    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
225        if bytes.len() < Self::SIZE {
226            return None;
227        }
228
229        Some(Self {
230            request_type: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
231            reserved: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
232            sector: u64::from_le_bytes([
233                bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14],
234                bytes[15],
235            ]),
236        })
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_request_header_parsing() {
246        let bytes = [
247            0x00, 0x00, 0x00, 0x00, // type: IN
248            0x00, 0x00, 0x00, 0x00, // reserved
249            0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sector: 16
250        ];
251
252        let header = BlockRequestHeader::from_bytes(&bytes).unwrap();
253        assert_eq!(header.request_type, 0);
254        assert_eq!(header.sector, 16);
255    }
256
257    #[test]
258    fn test_request_header_too_short() {
259        let bytes = [0x00, 0x00, 0x00];
260        let header = BlockRequestHeader::from_bytes(&bytes);
261        assert!(header.is_none());
262    }
263
264    #[test]
265    fn test_invalid_request_type() {
266        let result = BlockRequestType::try_from(999u32);
267        assert!(result.is_err());
268    }
269
270    #[test]
271    fn test_all_request_types() {
272        assert_eq!(BlockRequestType::try_from(0).unwrap(), BlockRequestType::In);
273        assert_eq!(
274            BlockRequestType::try_from(1).unwrap(),
275            BlockRequestType::Out
276        );
277        assert_eq!(
278            BlockRequestType::try_from(4).unwrap(),
279            BlockRequestType::Flush
280        );
281        assert_eq!(
282            BlockRequestType::try_from(8).unwrap(),
283            BlockRequestType::GetId
284        );
285        assert_eq!(
286            BlockRequestType::try_from(11).unwrap(),
287            BlockRequestType::Discard
288        );
289        assert_eq!(
290            BlockRequestType::try_from(13).unwrap(),
291            BlockRequestType::WriteZeroes
292        );
293    }
294}