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

View File

@ -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",

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;
pub mod block;
pub mod console;
pub mod input;
pub mod network;

View File

@ -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<dyn VirtioTransport>) {
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);