Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add typed getters for input device config space #144

Merged
merged 8 commits into from
Jun 20, 2024
224 changes: 201 additions & 23 deletions src/device/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@
use crate::hal::Hal;
use crate::queue::VirtQueue;
use crate::transport::Transport;
use crate::volatile::{volread, volwrite, ReadOnly, WriteOnly};
use crate::Result;
use alloc::boxed::Box;
use core::ptr::NonNull;
use crate::volatile::{volread, volwrite, ReadOnly, VolatileReadable, WriteOnly};
use crate::Error;
use alloc::{
boxed::Box,
string::{FromUtf8Error, String},
};
use core::cmp::min;
use core::mem::size_of;
use core::ptr::{addr_of, NonNull};
use zerocopy::{AsBytes, FromBytes, FromZeroes};

/// Virtual human interface devices such as keyboards, mice and tablets.
Expand All @@ -25,7 +30,7 @@

impl<H: Hal, T: Transport> VirtIOInput<H, T> {
/// Create a new VirtIO-Input driver.
pub fn new(mut transport: T) -> Result<Self> {
pub fn new(mut transport: T) -> Result<Self, Error> {
let mut event_buf = Box::new([InputEvent::default(); QUEUE_SIZE]);

let negotiated_features = transport.begin_init(SUPPORTED_FEATURES);
Expand Down Expand Up @@ -107,17 +112,90 @@
out: &mut [u8],
) -> u8 {
let size;
let data;
// Safe because config points to a valid MMIO region for the config space.
unsafe {
volwrite!(self.config, select, select as u8);
volwrite!(self.config, subsel, subsel);
size = volread!(self.config, size);
data = volread!(self.config, data);
let size_to_copy = min(usize::from(size), out.len());
for i in 0..size_to_copy {

Check warning on line 121 in src/device/input.rs

View workflow job for this annotation

GitHub Actions / clippy

the loop variable `i` is used to index `out`

warning: the loop variable `i` is used to index `out` --> src/device/input.rs:121:22 | 121 | for i in 0..size_to_copy { | ^^^^^^^^^^^^^^^ | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop = note: `#[warn(clippy::needless_range_loop)]` on by default help: consider using an iterator and enumerate() | 121 | for (i, <item>) in out.iter_mut().enumerate().take(size_to_copy) { | ~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
qwandor marked this conversation as resolved.
Show resolved Hide resolved
out[i] = addr_of!((*self.config.as_ptr()).data[i]).vread();
}
}
out[..size as usize].copy_from_slice(&data[..size as usize]);
size
}

/// Queries a specific piece of information by `select` and `subsel`, allocates a sufficiently
/// large byte buffer for it, and returns it.
fn query_config_select_alloc(&mut self, select: InputConfigSelect, subsel: u8) -> Box<[u8]> {
// Safe because config points to a valid MMIO region for the config space.
unsafe {
volwrite!(self.config, select, select as u8);
volwrite!(self.config, subsel, subsel);
let size = usize::from(volread!(self.config, size));
qwandor marked this conversation as resolved.
Show resolved Hide resolved
let mut buf = u8::new_box_slice_zeroed(size);
for i in 0..size {
buf[i] = addr_of!((*self.config.as_ptr()).data[i]).vread();
}
buf
}
}

/// Queries a specific piece of information by `select` and `subsel` into a newly-allocated
/// buffer, and tries to convert it to a string.
///
/// Returns an error if it is not valid UTF-8.
fn query_config_string(
&mut self,
select: InputConfigSelect,
subsel: u8,
) -> Result<String, FromUtf8Error> {
String::from_utf8(self.query_config_select_alloc(select, subsel).into())
}

/// Queries and returns the name of the device, or an error if it is not valid UTF-8.
pub fn name(&mut self) -> Result<String, FromUtf8Error> {
self.query_config_string(InputConfigSelect::IdName, 0)
}

/// Queries and returns the serial number of the device, or an error if it is not valid UTF-8.
pub fn serial_number(&mut self) -> Result<String, FromUtf8Error> {
self.query_config_string(InputConfigSelect::IdSerial, 0)
}

/// Queries and returns the ID information of the device.
pub fn ids(&mut self) -> Result<DevIDs, Error> {
let mut ids = DevIDs::default();
let size = self.query_config_select(InputConfigSelect::IdDevids, 0, ids.as_bytes_mut());
if usize::from(size) == size_of::<DevIDs>() {
Ok(ids)
} else {
Err(Error::InvalidParam)
}
}

/// Queries and returns the input properties of the device.
pub fn prop_bits(&mut self) -> Box<[u8]> {
self.query_config_select_alloc(InputConfigSelect::PropBits, 0)
}

/// Queries and returns a bitmap of supported event codes for the given event type.
///
/// If the event type is not supported an empty slice will be returned.
pub fn ev_bits(&mut self, event_type: u8) -> Box<[u8]> {
self.query_config_select_alloc(InputConfigSelect::EvBits, event_type)
}

/// Queries and returns information about the given axis of the device.
pub fn abs_info(&mut self, axis: u8) -> Result<AbsInfo, Error> {
let mut info = AbsInfo::default();
let size = self.query_config_select(InputConfigSelect::AbsInfo, axis, info.as_bytes_mut());
if usize::from(size) == size_of::<AbsInfo>() {
Ok(info)
} else {
Err(Error::InvalidParam)
}
}
}

// SAFETY: The config space can be accessed from any thread.
Expand Down Expand Up @@ -171,27 +249,38 @@
select: WriteOnly<u8>,
subsel: WriteOnly<u8>,
size: ReadOnly<u8>,
_reversed: [ReadOnly<u8>; 5],
data: ReadOnly<[u8; 128]>,
_reserved: [ReadOnly<u8>; 5],
data: [ReadOnly<u8>; 128],
}

/// Information about an axis of an input device, typically a joystick.
#[repr(C)]
#[derive(Debug)]
struct AbsInfo {
min: u32,
max: u32,
fuzz: u32,
flat: u32,
res: u32,
#[derive(AsBytes, Clone, Debug, Default, Eq, PartialEq, FromBytes, FromZeroes)]
pub struct AbsInfo {
/// The minimum value for the axis.
pub min: u32,
/// The maximum value for the axis.
pub max: u32,
/// The fuzz value used to filter noise from the event stream.
pub fuzz: u32,
/// The size of the dead zone; values less than this will be reported as 0.
pub flat: u32,
/// The resolution for values reported for the axis.
pub res: u32,
}

/// The identifiers of a VirtIO input device.
#[repr(C)]
#[derive(Debug)]
struct DevIDs {
bustype: u16,
vendor: u16,
product: u16,
version: u16,
#[derive(AsBytes, Clone, Debug, Default, Eq, PartialEq, FromBytes, FromZeroes)]
pub struct DevIDs {
/// The bustype identifier.
pub bustype: u16,
/// The vendor identifier.
pub vendor: u16,
/// The product identifier.
pub product: u16,
/// The version identifier.
pub version: u16,
}

/// Both queues use the same `virtio_input_event` struct. `type`, `code` and `value`
Expand All @@ -213,3 +302,92 @@

// a parameter that can change
const QUEUE_SIZE: usize = 32;

#[cfg(test)]
mod tests {
use super::*;
use crate::{
hal::fake::FakeHal,
transport::{
fake::{FakeTransport, QueueStatus, State},
DeviceType,
},
};
use alloc::{sync::Arc, vec};
use core::convert::TryInto;
use std::sync::Mutex;

#[test]
fn config() {
const DEFAULT_DATA: ReadOnly<u8> = ReadOnly::new(0);
let mut config_space = Config {
select: WriteOnly::default(),
subsel: WriteOnly::default(),
size: ReadOnly::new(0),
_reserved: Default::default(),
data: [DEFAULT_DATA; 128],
};
let state = Arc::new(Mutex::new(State {
queues: vec![QueueStatus::default(), QueueStatus::default()],
..Default::default()
}));
let transport = FakeTransport {
device_type: DeviceType::Block,
max_queue_size: QUEUE_SIZE.try_into().unwrap(),
device_features: 0,
config_space: NonNull::from(&mut config_space),
state: state.clone(),
};
let mut input = VirtIOInput::<FakeHal, FakeTransport<Config>>::new(transport).unwrap();

set_data(&mut config_space, "Test input device".as_bytes());
assert_eq!(input.name().unwrap(), "Test input device");
assert_eq!(config_space.select.0, InputConfigSelect::IdName as u8);
assert_eq!(config_space.subsel.0, 0);

set_data(&mut config_space, "Serial number".as_bytes());
assert_eq!(input.serial_number().unwrap(), "Serial number");
assert_eq!(config_space.select.0, InputConfigSelect::IdSerial as u8);
assert_eq!(config_space.subsel.0, 0);

let ids = DevIDs {
bustype: 0x4242,
product: 0x0067,
vendor: 0x1234,
version: 0x4321,
};
set_data(&mut config_space, ids.as_bytes());
assert_eq!(input.ids().unwrap(), ids);
assert_eq!(config_space.select.0, InputConfigSelect::IdDevids as u8);
assert_eq!(config_space.subsel.0, 0);

set_data(&mut config_space, &[0x12, 0x34, 0x56]);
assert_eq!(input.prop_bits().as_ref(), &[0x12, 0x34, 0x56]);
assert_eq!(config_space.select.0, InputConfigSelect::PropBits as u8);
assert_eq!(config_space.subsel.0, 0);

set_data(&mut config_space, &[0x42, 0x66]);
assert_eq!(input.ev_bits(3).as_ref(), &[0x42, 0x66]);
assert_eq!(config_space.select.0, InputConfigSelect::EvBits as u8);
assert_eq!(config_space.subsel.0, 3);

let abs_info = AbsInfo {
min: 12,
max: 1234,
fuzz: 4,
flat: 10,
res: 2,
};
set_data(&mut config_space, abs_info.as_bytes());
assert_eq!(input.abs_info(5).unwrap(), abs_info);
assert_eq!(config_space.select.0, InputConfigSelect::AbsInfo as u8);
assert_eq!(config_space.subsel.0, 5);
}

fn set_data(config_space: &mut Config, value: &[u8]) {
config_space.size.0 = value.len().try_into().unwrap();
for (i, &byte) in value.into_iter().enumerate() {
config_space.data[i].0 = byte;
}
}
}
8 changes: 4 additions & 4 deletions src/volatile.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
/// An MMIO register which can only be read from.
#[derive(Default)]
#[repr(transparent)]
pub struct ReadOnly<T: Copy>(T);
pub struct ReadOnly<T: Copy>(pub(crate) T);

impl<T: Copy> ReadOnly<T> {
/// Construct a new instance for testing.
pub fn new(value: T) -> Self {
pub const fn new(value: T) -> Self {
Self(value)
}
}

/// An MMIO register which can only be written to.
#[derive(Default)]
#[repr(transparent)]
pub struct WriteOnly<T: Copy>(T);
pub struct WriteOnly<T: Copy>(pub(crate) T);

/// An MMIO register which may be both read and written.
#[derive(Default)]
Expand All @@ -22,7 +22,7 @@ pub struct Volatile<T: Copy>(T);

impl<T: Copy> Volatile<T> {
/// Construct a new instance for testing.
pub fn new(value: T) -> Self {
pub const fn new(value: T) -> Self {
Self(value)
}
}
Expand Down
Loading