Преглед изворни кода

Improve VirtIOConsole (#39)

* Add method to return information about console device.

* Test VirtIOConsole in aarch64 example.

* Improve VirtIOConsole.

* Keep track of the token of the outstanding receive request, and ensure
  that there is never more than one.
* Check whether the request has completed at the start of recv as well as
  in ack_interrupt, in case the client is not using interrupts.
* Improve documentation a bit.

* Add more documentation for VirtIOConsole.
Andrew Walbran пре 2 година
родитељ
комит
d9ac63e1a5
3 измењених фајлова са 115 додато и 34 уклоњено
  1. 9 4
      examples/aarch64/Makefile
  2. 16 1
      examples/aarch64/src/main.rs
  3. 90 29
      src/console.rs

+ 9 - 4
examples/aarch64/Makefile

@@ -42,25 +42,30 @@ qemu: $(kernel_bin) $(img)
 	  $(QEMU_ARGS) \
 		-machine virt \
 		-cpu max \
-		-serial mon:stdio \
+		-serial chardev:char0 \
 		-kernel $(kernel_bin) \
 		-global virtio-mmio.force-legacy=false \
 		-nic none \
 		-drive file=$(img),if=none,format=raw,id=x0 \
 		-device virtio-blk-device,drive=x0 \
-		-device virtio-gpu-device
+		-device virtio-gpu-device \
+		-device virtio-serial,id=virtio-serial0 \
+		-chardev stdio,id=char0,mux=on \
+		-device virtconsole,chardev=char0
 
 qemu-pci: $(kernel_bin) $(img)
 	qemu-system-aarch64 \
 		-machine virt \
 		-cpu max \
-		-serial mon:stdio \
+		-serial chardev:char0 \
 		-kernel $(kernel_bin) \
 		-nic none \
 		-drive file=$(img),if=none,format=raw,id=x0 \
 		-device virtio-blk-pci,drive=x0 \
 		-device virtio-gpu-pci \
-		-device virtio-serial,id=virtio-serial0
+		-device virtio-serial,id=virtio-serial0 \
+		-chardev stdio,id=char0,mux=on \
+		-device virtconsole,chardev=char0
 
 $(img):
 	dd if=/dev/zero of=$@ bs=512 count=32

+ 16 - 1
examples/aarch64/src/main.rs

@@ -20,7 +20,8 @@ use virtio_drivers::{
         bus::{BarInfo, Cam, Command, DeviceFunction, MemoryBarType, PciRoot},
         virtio_device_type, PciTransport,
     },
-    DeviceType, MmioTransport, Transport, VirtIOBlk, VirtIOGpu, VirtIOHeader, VirtIONet,
+    DeviceType, MmioTransport, Transport, VirtIOBlk, VirtIOConsole, VirtIOGpu, VirtIOHeader,
+    VirtIONet,
 };
 
 #[no_mangle]
@@ -96,6 +97,7 @@ fn virtio_device(transport: impl Transport) {
         DeviceType::Block => virtio_blk(transport),
         DeviceType::GPU => virtio_gpu(transport),
         DeviceType::Network => virtio_net(transport),
+        DeviceType::Console => virtio_console(transport),
         t => warn!("Unrecognized virtio device: {:?}", t),
     }
 }
@@ -144,6 +146,19 @@ fn virtio_net<T: Transport>(transport: T) {
     info!("virtio-net test finished");
 }
 
+fn virtio_console<T: Transport>(transport: T) {
+    let mut console =
+        VirtIOConsole::<HalImpl, T>::new(transport).expect("Failed to create console driver");
+    let info = console.info();
+    info!("VirtIO console {}x{}", info.rows, info.columns);
+    for &c in b"Hello world on console!\n" {
+        console.send(c).expect("Failed to send character");
+    }
+    let c = console.recv(true).expect("Failed to read from console");
+    info!("Read {:?}", c);
+    info!("virtio-console test finished");
+}
+
 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
 enum PciRangeType {
     ConfigurationSpace,

+ 90 - 29
src/console.rs

@@ -3,26 +3,61 @@ use crate::queue::VirtQueue;
 use crate::transport::Transport;
 use crate::volatile::{volread, ReadOnly, WriteOnly};
 use bitflags::*;
+use core::ptr::NonNull;
 use log::*;
 
 const QUEUE_RECEIVEQ_PORT_0: u16 = 0;
 const QUEUE_TRANSMITQ_PORT_0: u16 = 1;
 const QUEUE_SIZE: u16 = 2;
 
-/// Virtio console. Only one single port is allowed since ``alloc'' is disabled.
-/// Emergency and cols/rows unimplemented.
+/// Driver for a VirtIO console device.
+///
+/// Only a single port is allowed since `alloc` is disabled. Emergency write and cols/rows are not
+/// implemented.
+///
+/// # Example
+///
+/// ```
+/// # use virtio_drivers::{Error, Hal, Transport};
+/// use virtio_drivers::VirtIOConsole;
+/// # fn example<HalImpl: Hal, T: Transport>(transport: T) -> Result<(), Error> {
+/// let mut console = VirtIOConsole::<HalImpl, _>::new(transport)?;
+///
+/// let info = console.info();
+/// println!("VirtIO console {}x{}", info.rows, info.columns);
+///
+/// for &c in b"Hello console!\n" {
+///   console.send(c)?;
+/// }
+///
+/// let c = console.recv(true)?;
+/// println!("Read {:?} from console.", c);
+/// # Ok(())
+/// # }
+/// ```
 pub struct VirtIOConsole<'a, H: Hal, T: Transport> {
     transport: T,
+    config_space: NonNull<Config>,
     receiveq: VirtQueue<H>,
     transmitq: VirtQueue<H>,
     queue_buf_dma: Dma<H>,
     queue_buf_rx: &'a mut [u8],
     cursor: usize,
     pending_len: usize,
+    /// The token of the outstanding receive request, if there is one.
+    receive_token: Option<u16>,
+}
+
+/// Information about a console device, read from its configuration space.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct ConsoleInfo {
+    pub rows: u16,
+    pub columns: u16,
+    pub max_ports: u32,
 }
 
 impl<H: Hal, T: Transport> VirtIOConsole<'_, H, T> {
-    /// Create a new VirtIO-Console driver.
+    /// Creates a new VirtIO console driver.
     pub fn new(mut transport: T) -> Result<Self> {
         transport.begin_init(|features| {
             let features = Features::from_bits_truncate(features);
@@ -31,15 +66,6 @@ impl<H: Hal, T: Transport> VirtIOConsole<'_, H, T> {
             (features & supported_features).bits()
         });
         let config_space = transport.config_space::<Config>()?;
-        unsafe {
-            let columns = volread!(config_space, cols);
-            let rows = volread!(config_space, rows);
-            let max_ports = volread!(config_space, max_nr_ports);
-            info!(
-                "Columns: {} Rows: {} Max ports: {}",
-                columns, rows, max_ports,
-            );
-        }
         let receiveq = VirtQueue::new(&mut transport, QUEUE_RECEIVEQ_PORT_0, QUEUE_SIZE)?;
         let transmitq = VirtQueue::new(&mut transport, QUEUE_TRANSMITQ_PORT_0, QUEUE_SIZE)?;
         let queue_buf_dma = Dma::new(1)?;
@@ -47,56 +73,91 @@ impl<H: Hal, T: Transport> VirtIOConsole<'_, H, T> {
         transport.finish_init();
         let mut console = VirtIOConsole {
             transport,
+            config_space,
             receiveq,
             transmitq,
             queue_buf_dma,
             queue_buf_rx,
             cursor: 0,
             pending_len: 0,
+            receive_token: None,
         };
         console.poll_retrieve()?;
         Ok(console)
     }
 
+    /// Returns a struct with information about the console device, such as the number of rows and columns.
+    pub fn info(&self) -> ConsoleInfo {
+        // Safe because config_space is a valid pointer to the device configuration space.
+        unsafe {
+            let columns = volread!(self.config_space, cols);
+            let rows = volread!(self.config_space, rows);
+            let max_ports = volread!(self.config_space, max_nr_ports);
+            ConsoleInfo {
+                rows,
+                columns,
+                max_ports,
+            }
+        }
+    }
+
+    /// Makes a request to the device to receive data, if there is not already an outstanding
+    /// receive request or some data already received and not yet returned.
     fn poll_retrieve(&mut self) -> Result<()> {
-        // Safe because the buffer lasts at least as long as the queue.
-        unsafe { self.receiveq.add(&[], &[self.queue_buf_rx])? };
+        if self.receive_token.is_none() && self.cursor == self.pending_len {
+            // Safe because the buffer lasts at least as long as the queue, and there are no other
+            // outstanding requests using the buffer.
+            self.receive_token = Some(unsafe { self.receiveq.add(&[], &[self.queue_buf_rx]) }?);
+        }
         Ok(())
     }
 
-    /// Acknowledge interrupt.
+    /// Acknowledges a pending interrupt, if any, and completes the outstanding finished read
+    /// request if there is one.
+    ///
+    /// Returns true if new data has been received.
     pub fn ack_interrupt(&mut self) -> Result<bool> {
-        let ack = self.transport.ack_interrupt();
-        if !ack {
+        if !self.transport.ack_interrupt() {
             return Ok(false);
         }
+
+        Ok(self.finish_receive())
+    }
+
+    /// If there is an outstanding receive request and it has finished, completes it.
+    ///
+    /// Returns true if new data has been received.
+    fn finish_receive(&mut self) -> bool {
         let mut flag = false;
-        while let Ok((_token, len)) = self.receiveq.pop_used() {
-            assert!(!flag);
-            flag = true;
-            assert_ne!(len, 0);
-            self.cursor = 0;
-            self.pending_len = len as usize;
+        if let Some(receive_token) = self.receive_token {
+            if let Ok((token, len)) = self.receiveq.pop_used() {
+                assert_eq!(token, receive_token);
+                flag = true;
+                assert_ne!(len, 0);
+                self.cursor = 0;
+                self.pending_len = len as usize;
+            }
         }
-        Ok(flag)
+        flag
     }
 
-    /// Try get char.
+    /// Returns the next available character from the console, if any.
+    ///
+    /// If no data has been received this will not block but immediately return `Ok<None>`.
     pub fn recv(&mut self, pop: bool) -> Result<Option<u8>> {
+        self.finish_receive();
         if self.cursor == self.pending_len {
             return Ok(None);
         }
         let ch = self.queue_buf_rx[self.cursor];
         if pop {
             self.cursor += 1;
-            if self.cursor == self.pending_len {
-                self.poll_retrieve()?;
-            }
+            self.poll_retrieve()?;
         }
         Ok(Some(ch))
     }
 
-    /// Put a char onto the device.
+    /// Sends a character to the console.
     pub fn send(&mut self, chr: u8) -> Result<()> {
         let buf: [u8; 1] = [chr];
         // Safe because the buffer is valid until we pop_used below.