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` block request types.
36///
37/// Values sourced from `virtio_bindings::virtio_blk::VIRTIO_BLK_T_*`.
38#[repr(u32)]
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum BlockRequestType {
41    /// Read request.
42    In = virtio_bindings::virtio_blk::VIRTIO_BLK_T_IN,
43    /// Write request.
44    Out = virtio_bindings::virtio_blk::VIRTIO_BLK_T_OUT,
45    /// Flush request.
46    Flush = virtio_bindings::virtio_blk::VIRTIO_BLK_T_FLUSH,
47    /// Get device ID.
48    GetId = virtio_bindings::virtio_blk::VIRTIO_BLK_T_GET_ID,
49    /// Discard request.
50    Discard = virtio_bindings::virtio_blk::VIRTIO_BLK_T_DISCARD,
51    /// Write zeroes request.
52    WriteZeroes = virtio_bindings::virtio_blk::VIRTIO_BLK_T_WRITE_ZEROES,
53}
54
55impl TryFrom<u32> for BlockRequestType {
56    type Error = VirtioError;
57
58    fn try_from(value: u32) -> std::result::Result<Self, Self::Error> {
59        use virtio_bindings::virtio_blk;
60        match value {
61            virtio_blk::VIRTIO_BLK_T_IN => Ok(Self::In),
62            virtio_blk::VIRTIO_BLK_T_OUT => Ok(Self::Out),
63            virtio_blk::VIRTIO_BLK_T_FLUSH => Ok(Self::Flush),
64            virtio_blk::VIRTIO_BLK_T_GET_ID => Ok(Self::GetId),
65            virtio_blk::VIRTIO_BLK_T_DISCARD => Ok(Self::Discard),
66            virtio_blk::VIRTIO_BLK_T_WRITE_ZEROES => Ok(Self::WriteZeroes),
67            _ => Err(VirtioError::InvalidOperation(format!(
68                "Unknown block request type: {value}"
69            ))),
70        }
71    }
72}
73
74/// `VirtIO` block request status.
75///
76/// Values sourced from `virtio_bindings::virtio_blk::VIRTIO_BLK_S_*`.
77#[repr(u8)]
78#[derive(Debug, Clone, Copy)]
79pub enum BlockStatus {
80    /// Success.
81    Ok = virtio_bindings::virtio_blk::VIRTIO_BLK_S_OK as u8,
82    /// I/O error.
83    IoErr = virtio_bindings::virtio_blk::VIRTIO_BLK_S_IOERR as u8,
84    /// Unsupported operation.
85    Unsupp = virtio_bindings::virtio_blk::VIRTIO_BLK_S_UNSUPP as u8,
86}
87
88/// `VirtIO` block request header.
89#[repr(C)]
90#[derive(Debug, Clone, Copy)]
91pub struct BlockRequestHeader {
92    /// Request type.
93    pub request_type: u32,
94    /// Reserved.
95    pub reserved: u32,
96    /// Sector offset.
97    pub sector: u64,
98}
99
100impl BlockRequestHeader {
101    /// Size of the header in bytes.
102    pub const SIZE: usize = 16;
103
104    /// Parses header from bytes.
105    #[must_use]
106    pub fn from_bytes(bytes: &[u8]) -> Option<Self> {
107        if bytes.len() < Self::SIZE {
108            return None;
109        }
110
111        Some(Self {
112            request_type: u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
113            reserved: u32::from_le_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
114            sector: u64::from_le_bytes([
115                bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14],
116                bytes[15],
117            ]),
118        })
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn test_request_header_parsing() {
128        let bytes = [
129            0x00, 0x00, 0x00, 0x00, // type: IN
130            0x00, 0x00, 0x00, 0x00, // reserved
131            0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // sector: 16
132        ];
133
134        let header = BlockRequestHeader::from_bytes(&bytes).unwrap();
135        assert_eq!(header.request_type, 0);
136        assert_eq!(header.sector, 16);
137    }
138
139    #[test]
140    fn test_request_header_too_short() {
141        let bytes = [0x00, 0x00, 0x00];
142        let header = BlockRequestHeader::from_bytes(&bytes);
143        assert!(header.is_none());
144    }
145
146    #[test]
147    fn test_invalid_request_type() {
148        let result = BlockRequestType::try_from(999u32);
149        assert!(result.is_err());
150    }
151
152    #[test]
153    fn test_all_request_types() {
154        assert_eq!(BlockRequestType::try_from(0).unwrap(), BlockRequestType::In);
155        assert_eq!(
156            BlockRequestType::try_from(1).unwrap(),
157            BlockRequestType::Out
158        );
159        assert_eq!(
160            BlockRequestType::try_from(4).unwrap(),
161            BlockRequestType::Flush
162        );
163        assert_eq!(
164            BlockRequestType::try_from(8).unwrap(),
165            BlockRequestType::GetId
166        );
167        assert_eq!(
168            BlockRequestType::try_from(11).unwrap(),
169            BlockRequestType::Discard
170        );
171        assert_eq!(
172            BlockRequestType::try_from(13).unwrap(),
173            BlockRequestType::WriteZeroes
174        );
175    }
176}