From 01e485b96ef3b26a325f94c3dec5f13632c88934 Mon Sep 17 00:00:00 2001 From: Yuke Peng Date: Tue, 7 Nov 2023 22:13:15 -0800 Subject: [PATCH] Support virtio console device --- Cargo.lock | 15 ++ Cargo.toml | 1 + Components.toml | 1 + runner/src/machine/microvm.rs | 4 + runner/src/machine/qemu_grub_efi/mod.rs | 8 + runner/src/main.rs | 8 +- services/comps/console/Cargo.toml | 16 ++ services/comps/console/src/lib.rs | 71 +++++++++ services/comps/virtio/Cargo.toml | 2 + .../comps/virtio/src/device/console/config.rs | 34 ++++ .../comps/virtio/src/device/console/device.rs | 147 ++++++++++++++++++ .../comps/virtio/src/device/console/mod.rs | 4 + services/comps/virtio/src/device/mod.rs | 1 + services/comps/virtio/src/lib.rs | 6 +- services/libs/jinux-std/Cargo.toml | 1 + .../libs/jinux-std/src/device/tty/driver.rs | 11 +- 16 files changed, 324 insertions(+), 6 deletions(-) create mode 100644 services/comps/console/Cargo.toml create mode 100644 services/comps/console/src/lib.rs create mode 100644 services/comps/virtio/src/device/console/config.rs create mode 100644 services/comps/virtio/src/device/console/device.rs create mode 100644 services/comps/virtio/src/device/console/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 9ff23c88..0445ac64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -628,6 +628,18 @@ dependencies = [ "spin 0.9.8", ] +[[package]] +name = "jinux-console" +version = "0.1.0" +dependencies = [ + "bitflags 1.3.2", + "component", + "jinux-frame", + "jinux-util", + "log", + "spin 0.9.8", +] + [[package]] name = "jinux-frame" version = "0.1.0" @@ -757,6 +769,7 @@ dependencies = [ "int-to-c-enum", "intrusive-collections", "jinux-block", + "jinux-console", "jinux-frame", "jinux-input", "jinux-network", @@ -812,11 +825,13 @@ name = "jinux-virtio" version = "0.1.0" dependencies = [ "align_ext", + "bit_field", "bitflags 1.3.2", "bytes", "component", "int-to-c-enum", "jinux-block", + "jinux-console", "jinux-frame", "jinux-input", "jinux-network", diff --git a/Cargo.toml b/Cargo.toml index 68c41868..04742b0e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ members = [ "framework/libs/ktest", "framework/libs/tdx-guest", "services/comps/block", + "services/comps/console", "services/comps/framebuffer", "services/comps/input", "services/comps/network", diff --git a/Components.toml b/Components.toml index a608eaa3..3f5a396a 100644 --- a/Components.toml +++ b/Components.toml @@ -4,6 +4,7 @@ std = { name = "jinux-std" } virtio = { name = "jinux-virtio" } input = { name = "jinux-input" } block = { name = "jinux-block" } +console = { name = "jinux-console" } time = { name = "jinux-time" } framebuffer = { name = "jinux-framebuffer" } network = { name = "jinux-network" } diff --git a/runner/src/machine/microvm.rs b/runner/src/machine/microvm.rs index 8c26a19f..3e8e9b10 100644 --- a/runner/src/machine/microvm.rs +++ b/runner/src/machine/microvm.rs @@ -19,6 +19,10 @@ pub const DEVICE_ARGS: &[&str] = &[ "virtio-keyboard-device", "-device", "virtio-net-device,netdev=net01", + "-device", + "virtio-serial-device", + "-device", + "virtconsole,chardev=mux", ]; pub fn create_bootdev_image(path: PathBuf) -> PathBuf { diff --git a/runner/src/machine/qemu_grub_efi/mod.rs b/runner/src/machine/qemu_grub_efi/mod.rs index 766029dc..8cb1454c 100644 --- a/runner/src/machine/qemu_grub_efi/mod.rs +++ b/runner/src/machine/qemu_grub_efi/mod.rs @@ -43,6 +43,10 @@ pub const NOIOMMU_DEVICE_ARGS: &[&str] = &[ "virtio-keyboard-pci,disable-legacy=on,disable-modern=off", "-device", "virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off", + "-device", + "virtio-serial-pci,disable-legacy=on,disable-modern=off", + "-device", + "virtconsole,chardev=mux", ]; pub const IOMMU_DEVICE_ARGS: &[&str] = &[ @@ -53,6 +57,10 @@ pub const IOMMU_DEVICE_ARGS: &[&str] = &[ "-device", "virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", "-device", + "virtio-serial-pci,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", + "-device", + "virtconsole,chardev=mux", + "-device", "intel-iommu,intremap=on,device-iotlb=on", "-device", "ioh3420,id=pcie.0,chassis=1", diff --git a/runner/src/main.rs b/runner/src/main.rs index e57aa819..09750f4a 100644 --- a/runner/src/main.rs +++ b/runner/src/main.rs @@ -93,10 +93,12 @@ pub const COMMON_ARGS: &[&str] = &[ "-m", "2G", "-nographic", // TODO: figure out why grub can't shown up without it - "-monitor", - "vc", "-serial", - "mon:stdio", + "chardev:mux", + "-monitor", + "chardev:mux", + "-chardev", + "stdio,id=mux,mux=on,signal=off,logfile=qemu.log", "-display", "none", "-device", diff --git a/services/comps/console/Cargo.toml b/services/comps/console/Cargo.toml new file mode 100644 index 00000000..6e022aaa --- /dev/null +++ b/services/comps/console/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "jinux-console" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitflags = "1.3" +spin = "0.9.4" +jinux-frame = { path = "../../../framework/jinux-frame" } +jinux-util = { path = "../../libs/jinux-util" } +component = { path = "../../libs/comp-sys/component" } +log = "0.4" + +[features] diff --git a/services/comps/console/src/lib.rs b/services/comps/console/src/lib.rs new file mode 100644 index 00000000..111a0e86 --- /dev/null +++ b/services/comps/console/src/lib.rs @@ -0,0 +1,71 @@ +//! The console device of jinux +#![no_std] +#![forbid(unsafe_code)] +#![feature(fn_traits)] + +extern crate alloc; + +use alloc::{collections::BTreeMap, fmt::Debug, string::String, sync::Arc, vec::Vec}; +use core::any::Any; + +use component::{init_component, ComponentInitError}; +use jinux_frame::sync::SpinLock; +use spin::Once; + +pub type ConsoleCallback = dyn Fn(&[u8]) + Send + Sync; + +pub trait AnyConsoleDevice: Send + Sync + Any + Debug { + fn send(&self, buf: &[u8]); + fn recv(&self, buf: &mut [u8]) -> Option; + fn register_callback(&self, callback: &'static ConsoleCallback); + fn handle_irq(&self); +} + +pub fn register_device(name: String, device: Arc) { + COMPONENT + .get() + .unwrap() + .console_table + .lock() + .insert(name, device); +} + +pub fn get_device(str: &str) -> Option> { + COMPONENT + .get() + .unwrap() + .console_table + .lock() + .get(str) + .cloned() +} + +pub fn all_devices() -> Vec<(String, Arc)> { + let consoles = COMPONENT.get().unwrap().console_table.lock(); + consoles + .iter() + .map(|(name, device)| (name.clone(), device.clone())) + .collect() +} + +static COMPONENT: Once = Once::new(); + +#[init_component] +fn component_init() -> Result<(), ComponentInitError> { + let a = Component::init()?; + COMPONENT.call_once(|| a); + Ok(()) +} + +#[derive(Debug)] +struct Component { + console_table: SpinLock>>, +} + +impl Component { + pub fn init() -> Result { + Ok(Self { + console_table: SpinLock::new(BTreeMap::new()), + }) + } +} diff --git a/services/comps/virtio/Cargo.toml b/services/comps/virtio/Cargo.toml index 82f6e24a..e71cd2b2 100644 --- a/services/comps/virtio/Cargo.toml +++ b/services/comps/virtio/Cargo.toml @@ -14,6 +14,7 @@ align_ext = { path = "../../../framework/libs/align_ext" } jinux-input = { path = "../input" } jinux-block = { path = "../block" } jinux-network = { path = "../network" } +jinux-console = { path = "../console" } jinux-frame = { path = "../../../framework/jinux-frame" } jinux-util = { path = "../../libs/jinux-util" } jinux-rights = { path = "../../libs/jinux-rights" } @@ -21,6 +22,7 @@ typeflags-util = { path = "../../libs/typeflags-util" } pod = { git = "https://github.com/jinzhao-dev/pod", rev = "d7dba56" } component = { path = "../../libs/comp-sys/component" } log = "0.4" +bit_field = "0.10.1" int-to-c-enum = { path = "../../libs/int-to-c-enum" } smoltcp = { version = "0.9.1", default-features = false, features = [ "alloc", diff --git a/services/comps/virtio/src/device/console/config.rs b/services/comps/virtio/src/device/console/config.rs new file mode 100644 index 00000000..b4212f96 --- /dev/null +++ b/services/comps/virtio/src/device/console/config.rs @@ -0,0 +1,34 @@ +use jinux_frame::io_mem::IoMem; +use jinux_util::safe_ptr::SafePtr; +use pod::Pod; + +use crate::transport::VirtioTransport; + +bitflags::bitflags! { + pub struct ConsoleFeatures: u64{ + /// Configuration cols and rows are valid. + const VIRTIO_CONSOLE_F_SIZE = 1 << 0; + /// Device has support for multiple ports; + /// max_nr_ports is valid and control virtqueues will be used. + const VIRTIO_CONSOLE_F_MULTIPORT = 1 << 1; + /// Device has support for emergency write. + /// Configuration field emerg_wr is valid. + const VIRTIO_CONSOLE_F_EMERG_WRITE = 1 << 2; + } +} + +#[derive(Debug, Pod, Clone, Copy)] +#[repr(C)] +pub struct VirtioConsoleConfig { + pub cols: u16, + pub row: u16, + pub max_nr_ports: u32, + pub emerg_wr: u32, +} + +impl VirtioConsoleConfig { + pub(super) fn new(transport: &dyn VirtioTransport) -> SafePtr { + let memory = transport.device_config_memory(); + SafePtr::new(memory, 0) + } +} diff --git a/services/comps/virtio/src/device/console/device.rs b/services/comps/virtio/src/device/console/device.rs new file mode 100644 index 00000000..29f9bee4 --- /dev/null +++ b/services/comps/virtio/src/device/console/device.rs @@ -0,0 +1,147 @@ +use core::hint::spin_loop; + +use alloc::{boxed::Box, fmt::Debug, string::ToString, sync::Arc, vec::Vec}; +use jinux_console::{AnyConsoleDevice, ConsoleCallback}; +use jinux_frame::{config::PAGE_SIZE, io_mem::IoMem, sync::SpinLock, trap::TrapFrame}; +use jinux_util::safe_ptr::SafePtr; +use log::debug; + +use crate::{ + device::{console::config::ConsoleFeatures, VirtioDeviceError}, + queue::VirtQueue, + transport::VirtioTransport, +}; + +use super::{config::VirtioConsoleConfig, DEVICE_NAME}; + +pub struct ConsoleDevice { + config: SafePtr, + transport: Box, + receive_queue: SpinLock, + transmit_queue: SpinLock, + buffer: SpinLock>, + callbacks: SpinLock>, +} + +impl AnyConsoleDevice for ConsoleDevice { + fn send(&self, value: &[u8]) { + let mut transmit_queue = self.transmit_queue.lock_irq_disabled(); + transmit_queue.add(&[value], &[]).unwrap(); + if transmit_queue.should_notify() { + transmit_queue.notify(); + } + while !transmit_queue.can_pop() { + spin_loop(); + } + transmit_queue.pop_used().unwrap(); + } + + fn recv(&self, buf: &mut [u8]) -> Option { + let mut receive_queue = self.receive_queue.lock_irq_disabled(); + if !receive_queue.can_pop() { + return None; + } + let (_, len) = receive_queue.pop_used().unwrap(); + + let mut recv_buffer = self.buffer.lock(); + buf.copy_from_slice(&recv_buffer.as_ref()[..len as usize]); + receive_queue.add(&[], &[recv_buffer.as_mut()]).unwrap(); + if receive_queue.should_notify() { + receive_queue.notify(); + } + Some(len as usize) + } + + fn register_callback(&self, callback: &'static (dyn Fn(&[u8]) + Send + Sync)) { + self.callbacks.lock().push(callback); + } + + fn handle_irq(&self) { + let mut receive_queue = self.receive_queue.lock_irq_disabled(); + if !receive_queue.can_pop() { + return; + } + let (_, len) = receive_queue.pop_used().unwrap(); + let mut recv_buffer = self.buffer.lock(); + let buffer = &recv_buffer.as_ref()[..len as usize]; + let lock = self.callbacks.lock(); + for callback in lock.iter() { + callback.call((buffer,)); + } + receive_queue.add(&[], &[recv_buffer.as_mut()]).unwrap(); + if receive_queue.should_notify() { + receive_queue.notify(); + } + } +} + +impl Debug for ConsoleDevice { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ConsoleDevice") + .field("config", &self.config) + .field("transport", &self.transport) + .field("receive_queue", &self.receive_queue) + .field("transmit_queue", &self.transmit_queue) + .finish() + } +} + +impl ConsoleDevice { + pub fn negotiate_features(features: u64) -> u64 { + let mut features = ConsoleFeatures::from_bits_truncate(features); + // A virtio console device may have multiple ports, but we only use one port to communicate now. + features.remove(ConsoleFeatures::VIRTIO_CONSOLE_F_MULTIPORT); + features.bits() + } + + pub fn init(mut transport: Box) -> Result<(), VirtioDeviceError> { + let config = VirtioConsoleConfig::new(transport.as_ref()); + const RECV0_QUEUE_INDEX: u16 = 0; + const TRANSMIT0_QUEUE_INDEX: u16 = 1; + let receive_queue = + SpinLock::new(VirtQueue::new(RECV0_QUEUE_INDEX, 2, transport.as_mut()).unwrap()); + let transmit_queue = + SpinLock::new(VirtQueue::new(TRANSMIT0_QUEUE_INDEX, 2, transport.as_mut()).unwrap()); + + let mut device = Self { + config, + transport, + receive_queue, + transmit_queue, + buffer: SpinLock::new(Box::new([0; PAGE_SIZE])), + callbacks: SpinLock::new(Vec::new()), + }; + + let mut recv_lock = device.receive_queue.lock(); + recv_lock + .add(&[], &[device.buffer.lock().as_mut()]) + .unwrap(); + if recv_lock.should_notify() { + recv_lock.notify(); + } + drop(recv_lock); + device + .transport + .register_queue_callback(RECV0_QUEUE_INDEX, Box::new(handle_console_input), false) + .unwrap(); + device + .transport + .register_cfg_callback(Box::new(config_space_change)) + .unwrap(); + device.transport.finish_init(); + + jinux_console::register_device(DEVICE_NAME.to_string(), Arc::new(device)); + + Ok(()) + } +} + +fn handle_console_input(_: &TrapFrame) { + jinux_console::get_device(&DEVICE_NAME.to_string()) + .unwrap() + .handle_irq(); +} + +fn config_space_change(_: &TrapFrame) { + debug!("Virtio-Console device configuration space change"); +} diff --git a/services/comps/virtio/src/device/console/mod.rs b/services/comps/virtio/src/device/console/mod.rs new file mode 100644 index 00000000..b322d6a3 --- /dev/null +++ b/services/comps/virtio/src/device/console/mod.rs @@ -0,0 +1,4 @@ +pub mod config; +pub mod device; + +pub static DEVICE_NAME: &str = "Virtio-Console"; diff --git a/services/comps/virtio/src/device/mod.rs b/services/comps/virtio/src/device/mod.rs index 9b314a34..a8e00cd5 100644 --- a/services/comps/virtio/src/device/mod.rs +++ b/services/comps/virtio/src/device/mod.rs @@ -2,6 +2,7 @@ use crate::queue::QueueError; use int_to_c_enum::TryFromInt; pub mod block; +pub mod console; pub mod input; pub mod network; diff --git a/services/comps/virtio/src/lib.rs b/services/comps/virtio/src/lib.rs index 4a8397d9..aee89fc0 100644 --- a/services/comps/virtio/src/lib.rs +++ b/services/comps/virtio/src/lib.rs @@ -12,8 +12,8 @@ use alloc::boxed::Box; use bitflags::bitflags; use component::ComponentInitError; use device::{ - block::device::BlockDevice, input::device::InputDevice, network::device::NetworkDevice, - VirtioDeviceType, + block::device::BlockDevice, console::device::ConsoleDevice, input::device::InputDevice, + network::device::NetworkDevice, VirtioDeviceType, }; use log::{error, warn}; use transport::{mmio::VIRTIO_MMIO_DRIVER, pci::VIRTIO_PCI_DRIVER, DeviceStatus}; @@ -49,6 +49,7 @@ fn virtio_component_init() -> Result<(), ComponentInitError> { VirtioDeviceType::Block => BlockDevice::init(transport), VirtioDeviceType::Input => InputDevice::init(transport), VirtioDeviceType::Network => NetworkDevice::init(transport), + VirtioDeviceType::Console => ConsoleDevice::init(transport), _ => { warn!("[Virtio]: Found unimplemented device:{:?}", device_type); Ok(()) @@ -82,6 +83,7 @@ fn negotiate_features(transport: &mut Box) { VirtioDeviceType::Network => NetworkDevice::negotiate_features(device_specified_features), VirtioDeviceType::Block => BlockDevice::negotiate_features(device_specified_features), VirtioDeviceType::Input => InputDevice::negotiate_features(device_specified_features), + VirtioDeviceType::Console => ConsoleDevice::negotiate_features(device_specified_features), _ => device_specified_features, }; let mut support_feature = Feature::from_bits_truncate(features); diff --git a/services/libs/jinux-std/Cargo.toml b/services/libs/jinux-std/Cargo.toml index d35b2504..f826e428 100644 --- a/services/libs/jinux-std/Cargo.toml +++ b/services/libs/jinux-std/Cargo.toml @@ -12,6 +12,7 @@ pod = { git = "https://github.com/jinzhao-dev/pod", rev = "d7dba56" } jinux-input = { path = "../../comps/input" } jinux-block = { path = "../../comps/block" } jinux-network = { path = "../../comps/network" } +jinux-console = { path = "../../comps/console" } jinux-time = { path = "../../comps/time" } jinux-virtio = { path = "../../comps/virtio" } jinux-rights = { path = "../jinux-rights" } diff --git a/services/libs/jinux-std/src/device/tty/driver.rs b/services/libs/jinux-std/src/device/tty/driver.rs index dbdec9d1..b78036fa 100644 --- a/services/libs/jinux-std/src/device/tty/driver.rs +++ b/services/libs/jinux-std/src/device/tty/driver.rs @@ -9,7 +9,9 @@ use crate::{ pub static TTY_DRIVER: Once> = Once::new(); pub(super) fn init() { - register_console_input_callback(&serial_input_callback); + for (_, device) in jinux_console::all_devices() { + device.register_callback(&console_input_callback) + } let tty_driver = Arc::new(TtyDriver::new()); // FIXME: install n_tty into tty_driver? let n_tty = get_n_tty(); @@ -66,6 +68,13 @@ impl TtyDriver { } } +fn console_input_callback(items: &[u8]) { + let tty_driver = get_tty_driver(); + for item in items { + tty_driver.receive_char(*item); + } +} + fn serial_input_callback(item: u8) { let tty_driver = get_tty_driver(); tty_driver.receive_char(item);