Support virtio console device

This commit is contained in:
Yuke Peng 2023-11-07 22:13:15 -08:00 committed by Tate, Hongliang Tian
parent e9544d489f
commit 01e485b96e
16 changed files with 324 additions and 6 deletions

15
Cargo.lock generated
View File

@ -628,6 +628,18 @@ dependencies = [
"spin 0.9.8", "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]] [[package]]
name = "jinux-frame" name = "jinux-frame"
version = "0.1.0" version = "0.1.0"
@ -757,6 +769,7 @@ dependencies = [
"int-to-c-enum", "int-to-c-enum",
"intrusive-collections", "intrusive-collections",
"jinux-block", "jinux-block",
"jinux-console",
"jinux-frame", "jinux-frame",
"jinux-input", "jinux-input",
"jinux-network", "jinux-network",
@ -812,11 +825,13 @@ name = "jinux-virtio"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"align_ext", "align_ext",
"bit_field",
"bitflags 1.3.2", "bitflags 1.3.2",
"bytes", "bytes",
"component", "component",
"int-to-c-enum", "int-to-c-enum",
"jinux-block", "jinux-block",
"jinux-console",
"jinux-frame", "jinux-frame",
"jinux-input", "jinux-input",
"jinux-network", "jinux-network",

View File

@ -39,6 +39,7 @@ members = [
"framework/libs/ktest", "framework/libs/ktest",
"framework/libs/tdx-guest", "framework/libs/tdx-guest",
"services/comps/block", "services/comps/block",
"services/comps/console",
"services/comps/framebuffer", "services/comps/framebuffer",
"services/comps/input", "services/comps/input",
"services/comps/network", "services/comps/network",

View File

@ -4,6 +4,7 @@ std = { name = "jinux-std" }
virtio = { name = "jinux-virtio" } virtio = { name = "jinux-virtio" }
input = { name = "jinux-input" } input = { name = "jinux-input" }
block = { name = "jinux-block" } block = { name = "jinux-block" }
console = { name = "jinux-console" }
time = { name = "jinux-time" } time = { name = "jinux-time" }
framebuffer = { name = "jinux-framebuffer" } framebuffer = { name = "jinux-framebuffer" }
network = { name = "jinux-network" } network = { name = "jinux-network" }

View File

@ -19,6 +19,10 @@ pub const DEVICE_ARGS: &[&str] = &[
"virtio-keyboard-device", "virtio-keyboard-device",
"-device", "-device",
"virtio-net-device,netdev=net01", "virtio-net-device,netdev=net01",
"-device",
"virtio-serial-device",
"-device",
"virtconsole,chardev=mux",
]; ];
pub fn create_bootdev_image(path: PathBuf) -> PathBuf { pub fn create_bootdev_image(path: PathBuf) -> PathBuf {

View File

@ -43,6 +43,10 @@ pub const NOIOMMU_DEVICE_ARGS: &[&str] = &[
"virtio-keyboard-pci,disable-legacy=on,disable-modern=off", "virtio-keyboard-pci,disable-legacy=on,disable-modern=off",
"-device", "-device",
"virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off", "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] = &[ pub const IOMMU_DEVICE_ARGS: &[&str] = &[
@ -53,6 +57,10 @@ pub const IOMMU_DEVICE_ARGS: &[&str] = &[
"-device", "-device",
"virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on", "virtio-net-pci,netdev=net01,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on",
"-device", "-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", "intel-iommu,intremap=on,device-iotlb=on",
"-device", "-device",
"ioh3420,id=pcie.0,chassis=1", "ioh3420,id=pcie.0,chassis=1",

View File

@ -93,10 +93,12 @@ pub const COMMON_ARGS: &[&str] = &[
"-m", "-m",
"2G", "2G",
"-nographic", // TODO: figure out why grub can't shown up without it "-nographic", // TODO: figure out why grub can't shown up without it
"-monitor",
"vc",
"-serial", "-serial",
"mon:stdio", "chardev:mux",
"-monitor",
"chardev:mux",
"-chardev",
"stdio,id=mux,mux=on,signal=off,logfile=qemu.log",
"-display", "-display",
"none", "none",
"-device", "-device",

View File

@ -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]

View File

@ -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<usize>;
fn register_callback(&self, callback: &'static ConsoleCallback);
fn handle_irq(&self);
}
pub fn register_device(name: String, device: Arc<dyn AnyConsoleDevice>) {
COMPONENT
.get()
.unwrap()
.console_table
.lock()
.insert(name, device);
}
pub fn get_device(str: &str) -> Option<Arc<dyn AnyConsoleDevice>> {
COMPONENT
.get()
.unwrap()
.console_table
.lock()
.get(str)
.cloned()
}
pub fn all_devices() -> Vec<(String, Arc<dyn AnyConsoleDevice>)> {
let consoles = COMPONENT.get().unwrap().console_table.lock();
consoles
.iter()
.map(|(name, device)| (name.clone(), device.clone()))
.collect()
}
static COMPONENT: Once<Component> = 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<BTreeMap<String, Arc<dyn AnyConsoleDevice>>>,
}
impl Component {
pub fn init() -> Result<Self, ComponentInitError> {
Ok(Self {
console_table: SpinLock::new(BTreeMap::new()),
})
}
}

View File

@ -14,6 +14,7 @@ align_ext = { path = "../../../framework/libs/align_ext" }
jinux-input = { path = "../input" } jinux-input = { path = "../input" }
jinux-block = { path = "../block" } jinux-block = { path = "../block" }
jinux-network = { path = "../network" } jinux-network = { path = "../network" }
jinux-console = { path = "../console" }
jinux-frame = { path = "../../../framework/jinux-frame" } jinux-frame = { path = "../../../framework/jinux-frame" }
jinux-util = { path = "../../libs/jinux-util" } jinux-util = { path = "../../libs/jinux-util" }
jinux-rights = { path = "../../libs/jinux-rights" } 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" } pod = { git = "https://github.com/jinzhao-dev/pod", rev = "d7dba56" }
component = { path = "../../libs/comp-sys/component" } component = { path = "../../libs/comp-sys/component" }
log = "0.4" log = "0.4"
bit_field = "0.10.1"
int-to-c-enum = { path = "../../libs/int-to-c-enum" } int-to-c-enum = { path = "../../libs/int-to-c-enum" }
smoltcp = { version = "0.9.1", default-features = false, features = [ smoltcp = { version = "0.9.1", default-features = false, features = [
"alloc", "alloc",

View File

@ -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<Self, IoMem> {
let memory = transport.device_config_memory();
SafePtr::new(memory, 0)
}
}

View File

@ -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<VirtioConsoleConfig, IoMem>,
transport: Box<dyn VirtioTransport>,
receive_queue: SpinLock<VirtQueue>,
transmit_queue: SpinLock<VirtQueue>,
buffer: SpinLock<Box<[u8; PAGE_SIZE]>>,
callbacks: SpinLock<Vec<&'static ConsoleCallback>>,
}
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<usize> {
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<dyn VirtioTransport>) -> 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");
}

View File

@ -0,0 +1,4 @@
pub mod config;
pub mod device;
pub static DEVICE_NAME: &str = "Virtio-Console";

View File

@ -2,6 +2,7 @@ use crate::queue::QueueError;
use int_to_c_enum::TryFromInt; use int_to_c_enum::TryFromInt;
pub mod block; pub mod block;
pub mod console;
pub mod input; pub mod input;
pub mod network; pub mod network;

View File

@ -12,8 +12,8 @@ use alloc::boxed::Box;
use bitflags::bitflags; use bitflags::bitflags;
use component::ComponentInitError; use component::ComponentInitError;
use device::{ use device::{
block::device::BlockDevice, input::device::InputDevice, network::device::NetworkDevice, block::device::BlockDevice, console::device::ConsoleDevice, input::device::InputDevice,
VirtioDeviceType, network::device::NetworkDevice, VirtioDeviceType,
}; };
use log::{error, warn}; use log::{error, warn};
use transport::{mmio::VIRTIO_MMIO_DRIVER, pci::VIRTIO_PCI_DRIVER, DeviceStatus}; 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::Block => BlockDevice::init(transport),
VirtioDeviceType::Input => InputDevice::init(transport), VirtioDeviceType::Input => InputDevice::init(transport),
VirtioDeviceType::Network => NetworkDevice::init(transport), VirtioDeviceType::Network => NetworkDevice::init(transport),
VirtioDeviceType::Console => ConsoleDevice::init(transport),
_ => { _ => {
warn!("[Virtio]: Found unimplemented device:{:?}", device_type); warn!("[Virtio]: Found unimplemented device:{:?}", device_type);
Ok(()) Ok(())
@ -82,6 +83,7 @@ fn negotiate_features(transport: &mut Box<dyn VirtioTransport>) {
VirtioDeviceType::Network => NetworkDevice::negotiate_features(device_specified_features), VirtioDeviceType::Network => NetworkDevice::negotiate_features(device_specified_features),
VirtioDeviceType::Block => BlockDevice::negotiate_features(device_specified_features), VirtioDeviceType::Block => BlockDevice::negotiate_features(device_specified_features),
VirtioDeviceType::Input => InputDevice::negotiate_features(device_specified_features), VirtioDeviceType::Input => InputDevice::negotiate_features(device_specified_features),
VirtioDeviceType::Console => ConsoleDevice::negotiate_features(device_specified_features),
_ => device_specified_features, _ => device_specified_features,
}; };
let mut support_feature = Feature::from_bits_truncate(features); let mut support_feature = Feature::from_bits_truncate(features);

View File

@ -12,6 +12,7 @@ pod = { git = "https://github.com/jinzhao-dev/pod", rev = "d7dba56" }
jinux-input = { path = "../../comps/input" } jinux-input = { path = "../../comps/input" }
jinux-block = { path = "../../comps/block" } jinux-block = { path = "../../comps/block" }
jinux-network = { path = "../../comps/network" } jinux-network = { path = "../../comps/network" }
jinux-console = { path = "../../comps/console" }
jinux-time = { path = "../../comps/time" } jinux-time = { path = "../../comps/time" }
jinux-virtio = { path = "../../comps/virtio" } jinux-virtio = { path = "../../comps/virtio" }
jinux-rights = { path = "../jinux-rights" } jinux-rights = { path = "../jinux-rights" }

View File

@ -9,7 +9,9 @@ use crate::{
pub static TTY_DRIVER: Once<Arc<TtyDriver>> = Once::new(); pub static TTY_DRIVER: Once<Arc<TtyDriver>> = Once::new();
pub(super) fn init() { 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()); let tty_driver = Arc::new(TtyDriver::new());
// FIXME: install n_tty into tty_driver? // FIXME: install n_tty into tty_driver?
let n_tty = get_n_tty(); 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) { fn serial_input_callback(item: u8) {
let tty_driver = get_tty_driver(); let tty_driver = get_tty_driver();
tty_driver.receive_char(item); tty_driver.receive_char(item);