|
11 | 11 | use std::fmt;
|
12 | 12 | use std::result;
|
13 | 13 |
|
| 14 | +use vm_memory::{Address, GuestAddress, GuestUsize}; |
| 15 | + |
14 | 16 | /// The error type for command line building operations.
|
15 | 17 | #[derive(Debug, PartialEq)]
|
16 | 18 | pub enum Error {
|
17 | 19 | /// Operation would have resulted in a non-printable ASCII character.
|
18 | 20 | InvalidAscii,
|
| 21 | + /// Invalid device passed to the kernel command line builder. |
| 22 | + InvalidDevice(String), |
19 | 23 | /// Key/Value Operation would have had a space in it.
|
20 | 24 | HasSpace,
|
21 | 25 | /// Key/Value Operation would have had an equals sign in it.
|
22 | 26 | HasEquals,
|
| 27 | + /// 0-sized virtio MMIO device passed to the kernel command line builder. |
| 28 | + MmioSize, |
23 | 29 | /// Operation would have made the command line too large.
|
24 | 30 | TooLarge,
|
25 | 31 | }
|
26 | 32 |
|
27 | 33 | impl fmt::Display for Error {
|
28 | 34 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
29 |
| - write!( |
30 |
| - f, |
31 |
| - "{}", |
32 |
| - match *self { |
33 |
| - Error::InvalidAscii => "String contains a non-printable ASCII character.", |
34 |
| - Error::HasSpace => "String contains a space.", |
35 |
| - Error::HasEquals => "String contains an equals sign.", |
36 |
| - Error::TooLarge => "Inserting string would make command line too long.", |
37 |
| - } |
38 |
| - ) |
| 35 | + match *self { |
| 36 | + Error::InvalidAscii => write!(f, "String contains a non-printable ASCII character."), |
| 37 | + Error::InvalidDevice(ref dev) => write!( |
| 38 | + f, |
| 39 | + "Invalid device passed to the kernel command line builder: {}.", |
| 40 | + dev |
| 41 | + ), |
| 42 | + Error::HasSpace => write!(f, "String contains a space."), |
| 43 | + Error::HasEquals => write!(f, "String contains an equals sign."), |
| 44 | + Error::MmioSize => write!( |
| 45 | + f, |
| 46 | + "0-sized virtio MMIO device passed to the kernel command line builder." |
| 47 | + ), |
| 48 | + Error::TooLarge => write!(f, "Inserting string would make command line too long."), |
| 49 | + } |
39 | 50 | }
|
40 | 51 | }
|
41 | 52 |
|
@@ -209,6 +220,75 @@ impl Cmdline {
|
209 | 220 | pub fn as_str(&self) -> &str {
|
210 | 221 | self.line.as_str()
|
211 | 222 | }
|
| 223 | + |
| 224 | + /// Adds a virtio MMIO device to the kernel command line. |
| 225 | + /// |
| 226 | + /// Multiple devices can be specified, with multiple `virtio_mmio.device=` options. This |
| 227 | + /// function must be called once per device. |
| 228 | + /// The function appends a string of the following format to the kernel command line: |
| 229 | + /// `<size>@<baseaddr>:<irq>[:<id>]`. |
| 230 | + /// For more details see the [documentation] (section `virtio_mmio.device=`). |
| 231 | + /// |
| 232 | + /// # Arguments |
| 233 | + /// |
| 234 | + /// * `size` - size of the slot the device occupies on the MMIO bus. |
| 235 | + /// * `baseaddr` - physical base address of the device. |
| 236 | + /// * `irq` - interrupt number to be used by the device. |
| 237 | + /// * `id` - optional platform device ID. |
| 238 | + /// |
| 239 | + /// # Examples |
| 240 | + /// |
| 241 | + /// ```rust |
| 242 | + /// # use linux_loader::cmdline::*; |
| 243 | + /// # use std::ffi::CString; |
| 244 | + /// # use vm_memory::{GuestAddress, GuestUsize}; |
| 245 | + /// let mut cl = Cmdline::new(100); |
| 246 | + /// cl.add_virtio_mmio_device(1 << 12, GuestAddress(0x1000), 5, Some(42)).unwrap(); |
| 247 | + /// let cl_cstring = CString::new(cl).unwrap(); |
| 248 | + /// assert_eq!(cl_cstring.to_str().unwrap(), "virtio_mmio.device=4K@0x1000:5:42"); |
| 249 | + /// ``` |
| 250 | + /// |
| 251 | + /// [documentation]: https://www.kernel.org/doc/html/latest/admin-guide/kernel-parameters.html |
| 252 | + pub fn add_virtio_mmio_device( |
| 253 | + &mut self, |
| 254 | + size: GuestUsize, |
| 255 | + baseaddr: GuestAddress, |
| 256 | + irq: u32, |
| 257 | + id: Option<u32>, |
| 258 | + ) -> Result<()> { |
| 259 | + if size == 0 { |
| 260 | + return Err(Error::MmioSize); |
| 261 | + } |
| 262 | + |
| 263 | + let mut device_str = format!( |
| 264 | + "virtio_mmio.device={}@0x{:x?}:{}", |
| 265 | + Self::guestusize_to_str(size), |
| 266 | + baseaddr.raw_value(), |
| 267 | + irq |
| 268 | + ); |
| 269 | + if let Some(id) = id { |
| 270 | + device_str.push_str(format!(":{}", id).as_str()); |
| 271 | + } |
| 272 | + self.insert_str(&device_str) |
| 273 | + } |
| 274 | + |
| 275 | + // Converts a `GuestUsize` to a concise string representation, with multiplier suffixes. |
| 276 | + fn guestusize_to_str(size: GuestUsize) -> String { |
| 277 | + const KB_MULT: u64 = 1 << 10; |
| 278 | + const MB_MULT: u64 = KB_MULT << 10; |
| 279 | + const GB_MULT: u64 = MB_MULT << 10; |
| 280 | + |
| 281 | + if size % GB_MULT == 0 { |
| 282 | + return format!("{}G", size / GB_MULT); |
| 283 | + } |
| 284 | + if size % MB_MULT == 0 { |
| 285 | + return format!("{}M", size / MB_MULT); |
| 286 | + } |
| 287 | + if size % KB_MULT == 0 { |
| 288 | + return format!("{}K", size / KB_MULT); |
| 289 | + } |
| 290 | + size.to_string() |
| 291 | + } |
212 | 292 | }
|
213 | 293 |
|
214 | 294 | impl Into<Vec<u8>> for Cmdline {
|
@@ -296,4 +376,42 @@ mod tests {
|
296 | 376 | assert_eq!(cl.insert("c", "da"), Err(Error::TooLarge)); // adds 5 (including space) length
|
297 | 377 | assert!(cl.insert("c", "d").is_ok()); // adds 4 (including space) length
|
298 | 378 | }
|
| 379 | + |
| 380 | + #[test] |
| 381 | + fn test_add_virtio_mmio_device() { |
| 382 | + let mut cl = Cmdline::new(5); |
| 383 | + assert_eq!( |
| 384 | + cl.add_virtio_mmio_device(0, GuestAddress(0), 0, None), |
| 385 | + Err(Error::MmioSize) |
| 386 | + ); |
| 387 | + assert_eq!( |
| 388 | + cl.add_virtio_mmio_device(1, GuestAddress(0), 0, None), |
| 389 | + Err(Error::TooLarge) |
| 390 | + ); |
| 391 | + |
| 392 | + let mut cl = Cmdline::new(150); |
| 393 | + assert!(cl |
| 394 | + .add_virtio_mmio_device(1, GuestAddress(0), 1, None) |
| 395 | + .is_ok()); |
| 396 | + let mut expected_str = "virtio_mmio.device=1@0x0:1".to_string(); |
| 397 | + assert_eq!(cl.as_str(), &expected_str); |
| 398 | + |
| 399 | + assert!(cl |
| 400 | + .add_virtio_mmio_device(2 << 10, GuestAddress(0x100), 2, None) |
| 401 | + .is_ok()); |
| 402 | + expected_str.push_str(" virtio_mmio.device=2K@0x100:2"); |
| 403 | + assert_eq!(cl.as_str(), &expected_str); |
| 404 | + |
| 405 | + assert!(cl |
| 406 | + .add_virtio_mmio_device(3 << 20, GuestAddress(0x1000), 3, None) |
| 407 | + .is_ok()); |
| 408 | + expected_str.push_str(" virtio_mmio.device=3M@0x1000:3"); |
| 409 | + assert_eq!(cl.as_str(), &expected_str); |
| 410 | + |
| 411 | + assert!(cl |
| 412 | + .add_virtio_mmio_device(4 << 30, GuestAddress(0x0001_0000), 4, Some(42)) |
| 413 | + .is_ok()); |
| 414 | + expected_str.push_str(" virtio_mmio.device=4G@0x10000:4:42"); |
| 415 | + assert_eq!(cl.as_str(), &expected_str); |
| 416 | + } |
299 | 417 | }
|
0 commit comments