milestone: desktop path Phases 1-5

Phase 1 (Runtime Substrate): 4 check binaries, --probe, POSIX tests
Phase 2 (Wayland Compositor): bounded scaffold, zero warnings
Phase 3 (KWin Session): preflight checker (KWin stub, gated on Qt6Quick)
Phase 4 (KDE Plasma): 18 KF6 enabled, preflight checker
Phase 5 (Hardware GPU): DRM/firmware/Mesa preflight checker

Build: zero warnings, all scripts syntax-clean. Oracle-verified.
This commit is contained in:
2026-04-29 09:54:06 +01:00
parent b23714f542
commit 8acc73d774
508 changed files with 76526 additions and 396 deletions
@@ -0,0 +1 @@
/target
@@ -0,0 +1,21 @@
[package]
name = "ps2d"
description = "PS/2 driver"
version = "0.1.0"
edition = "2018"
[dependencies]
bitflags.workspace = true
log.workspace = true
orbclient.workspace = true
redox_event.workspace = true
redox_syscall.workspace = true
redox-scheme.workspace = true
libredox.workspace = true
common = { path = "../../common" }
daemon = { path = "../../../daemon" }
inputd = { path = "../../inputd" }
[lints]
workspace = true
@@ -0,0 +1,389 @@
//! PS/2 controller, see:
//! - https://wiki.osdev.org/I8042_PS/2_Controller
//! - http://www.mcamafia.de/pdf/ibm_hitrc07.pdf
use common::{
io::{Io, ReadOnly, WriteOnly},
timeout::Timeout,
};
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use common::io::Pio;
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
use common::io::Mmio;
use log::{debug, error, info, trace, warn};
use std::fmt;
#[derive(Debug)]
pub enum Error {
CommandRetry,
NoMoreTries,
ReadTimeout,
WriteTimeout,
CommandTimeout(Command),
WriteConfigTimeout(ConfigFlags),
KeyboardCommandFail(KeyboardCommand),
KeyboardCommandDataFail(KeyboardCommandData),
}
bitflags! {
pub struct StatusFlags: u8 {
const OUTPUT_FULL = 1;
const INPUT_FULL = 1 << 1;
const SYSTEM = 1 << 2;
const COMMAND = 1 << 3;
// Chipset specific
const KEYBOARD_LOCK = 1 << 4;
// Chipset specific
const SECOND_OUTPUT_FULL = 1 << 5;
const TIME_OUT = 1 << 6;
const PARITY = 1 << 7;
}
}
bitflags! {
#[derive(Clone, Copy, Debug)]
pub struct ConfigFlags: u8 {
const FIRST_INTERRUPT = 1 << 0;
const SECOND_INTERRUPT = 1 << 1;
const POST_PASSED = 1 << 2;
// 1 << 3 should be zero
const CONFIG_RESERVED_3 = 1 << 3;
const FIRST_DISABLED = 1 << 4;
const SECOND_DISABLED = 1 << 5;
const FIRST_TRANSLATE = 1 << 6;
// 1 << 7 should be zero
const CONFIG_RESERVED_7 = 1 << 7;
}
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum Command {
ReadConfig = 0x20,
WriteConfig = 0x60,
DisableSecond = 0xA7,
EnableSecond = 0xA8,
TestSecond = 0xA9,
TestController = 0xAA,
TestFirst = 0xAB,
Diagnostic = 0xAC,
DisableFirst = 0xAD,
EnableFirst = 0xAE,
WriteSecond = 0xD4,
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum KeyboardCommand {
EnableReporting = 0xF4,
SetDefaultsDisable = 0xF5,
SetDefaults = 0xF6,
Reset = 0xFF,
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
enum KeyboardCommandData {
ScancodeSet = 0xF0,
}
// Default timeout in microseconds
const DEFAULT_TIMEOUT: u64 = 50_000;
// Reset timeout in microseconds
const RESET_TIMEOUT: u64 = 1_000_000;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub struct Ps2 {
data: Pio<u8>,
status: ReadOnly<Pio<u8>>,
command: WriteOnly<Pio<u8>>,
//TODO: keep in state instead
pub mouse_resets: usize,
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
pub struct Ps2 {
data: Mmio<u8>,
status: ReadOnly<Mmio<u8>>,
command: WriteOnly<Mmio<u8>>,
//TODO: keep in state instead
pub mouse_resets: usize,
}
impl Ps2 {
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub fn new() -> Self {
Ps2 {
data: Pio::new(0x60),
status: ReadOnly::new(Pio::new(0x64)),
command: WriteOnly::new(Pio::new(0x64)),
mouse_resets: 0,
}
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
pub fn new() -> Self {
unimplemented!()
}
fn status(&mut self) -> StatusFlags {
StatusFlags::from_bits_truncate(self.status.read())
}
fn wait_read(&mut self, micros: u64) -> Result<(), Error> {
let timeout = Timeout::from_micros(micros);
loop {
if self.status().contains(StatusFlags::OUTPUT_FULL) {
return Ok(());
}
timeout.run().map_err(|()| Error::ReadTimeout)?
}
}
fn wait_write(&mut self, micros: u64) -> Result<(), Error> {
let timeout = Timeout::from_micros(micros);
loop {
if !self.status().contains(StatusFlags::INPUT_FULL) {
return Ok(());
}
timeout.run().map_err(|()| Error::WriteTimeout)?
}
}
fn command(&mut self, command: Command) -> Result<(), Error> {
self.wait_write(DEFAULT_TIMEOUT)
.map_err(|_| Error::CommandTimeout(command))?;
self.command.write(command as u8);
Ok(())
}
fn read(&mut self) -> Result<u8, Error> {
self.read_timeout(DEFAULT_TIMEOUT)
}
fn read_timeout(&mut self, micros: u64) -> Result<u8, Error> {
self.wait_read(micros)?;
let data = self.data.read();
Ok(data)
}
fn write(&mut self, data: u8) -> Result<(), Error> {
self.wait_write(DEFAULT_TIMEOUT)?;
self.data.write(data);
Ok(())
}
fn retry<T, F: Fn(&mut Self) -> Result<T, Error>>(
&mut self,
name: fmt::Arguments,
retries: usize,
f: F,
) -> Result<T, Error> {
trace!("{}", name);
let mut res = Err(Error::NoMoreTries);
for retry in 0..retries {
res = f(self);
match res {
Ok(ok) => {
return Ok(ok);
}
Err(ref err) => {
debug!("{}: retry {}/{}: {:?}", name, retry + 1, retries, err);
}
}
}
res
}
fn config(&mut self) -> Result<ConfigFlags, Error> {
self.retry(format_args!("read config"), 4, |x| {
x.command(Command::ReadConfig)?;
x.read()
})
.map(ConfigFlags::from_bits_truncate)
}
fn set_config(&mut self, config: ConfigFlags) -> Result<(), Error> {
self.retry(format_args!("write config {:?}", config), 4, |x| {
x.command(Command::WriteConfig)?;
x.write(config.bits())
.map_err(|_| Error::WriteConfigTimeout(config))?;
Ok(0)
})?;
Ok(())
}
fn keyboard_command_inner(&mut self, command: u8) -> Result<u8, Error> {
self.write(command)?;
match self.read()? {
0xFE => Err(Error::CommandRetry),
value => Ok(value),
}
}
fn keyboard_command(&mut self, command: KeyboardCommand) -> Result<u8, Error> {
self.retry(format_args!("keyboard command {:?}", command), 4, |x| {
x.keyboard_command_inner(command as u8)
.map_err(|_| Error::KeyboardCommandFail(command))
})
}
fn keyboard_command_data(
&mut self,
command: KeyboardCommandData,
data: u8,
) -> Result<u8, Error> {
self.retry(
format_args!("keyboard command {:?} {:#x}", command, data),
4,
|x| {
let res = x
.keyboard_command_inner(command as u8)
.map_err(|_| Error::KeyboardCommandDataFail(command))?;
if res != 0xFA {
warn!("keyboard incorrect result of set command: {command:?} {res:02X}");
return Ok(res);
}
x.write(data)?;
x.read()
},
)
}
pub fn mouse_command_async(&mut self, command: u8) -> Result<(), Error> {
self.command(Command::WriteSecond)?;
self.write(command as u8)
}
pub fn next(&mut self) -> Option<(bool, u8)> {
let status = self.status();
if status.contains(StatusFlags::OUTPUT_FULL) {
let data = self.data.read();
Some((!status.contains(StatusFlags::SECOND_OUTPUT_FULL), data))
} else {
None
}
}
pub fn init_keyboard(&mut self) -> Result<(), Error> {
let mut b;
{
// Enable first device
self.command(Command::EnableFirst)?;
}
{
// Reset keyboard
b = self.keyboard_command(KeyboardCommand::Reset)?;
if b == 0xFA {
b = self.read().unwrap_or(0);
if b != 0xAA {
error!("keyboard failed self test: {:02X}", b);
}
} else {
error!("keyboard failed to reset: {:02X}", b);
}
}
{
// Set scancode set to 2
let scancode_set = 2;
b = self.keyboard_command_data(KeyboardCommandData::ScancodeSet, scancode_set)?;
if b != 0xFA {
error!(
"keyboard failed to set scancode set {}: {:02X}",
scancode_set, b
);
}
}
Ok(())
}
pub fn init(&mut self) -> Result<(), Error> {
{
// Disable devices
self.command(Command::DisableFirst)?;
self.command(Command::DisableSecond)?;
}
// Disable clocks, disable interrupts, and disable translate
{
// Since the default config may have interrupts enabled, and the kernel may eat up
// our data in that case, we will write a config without reading the current one
let config = ConfigFlags::POST_PASSED
| ConfigFlags::FIRST_DISABLED
| ConfigFlags::SECOND_DISABLED;
self.set_config(config)?;
}
// The keyboard seems to still collect bytes even when we disable
// the port, so we must disable the keyboard too
self.retry(format_args!("keyboard defaults"), 4, |x| {
// Set defaults and disable scanning
let b = x.keyboard_command(KeyboardCommand::SetDefaultsDisable)?;
if b != 0xFA {
error!("keyboard failed to set defaults: {:02X}", b);
return Err(Error::CommandRetry);
}
Ok(b)
})?;
{
// Perform the self test
self.command(Command::TestController)?;
let r = self.read()?;
if r != 0x55 {
warn!("self test unexpected value: {:02X}", r);
}
}
// Initialize keyboard
if let Err(err) = self.init_keyboard() {
error!("failed to initialize keyboard: {:?}", err);
return Err(err);
}
// Enable second device
let enable_mouse = match self.command(Command::EnableSecond) {
Ok(()) => true,
Err(err) => {
error!("failed to initialize mouse: {:?}", err);
false
}
};
{
// Enable keyboard data reporting
// Use inner function to prevent retries
// Response is ignored since scanning is now on
if let Err(err) = self.keyboard_command_inner(KeyboardCommand::EnableReporting as u8) {
error!("failed to initialize keyboard reporting: {:?}", err);
//TODO: fix by using interrupts?
}
}
// Enable clocks and interrupts
{
let config = ConfigFlags::POST_PASSED
| ConfigFlags::FIRST_INTERRUPT
| ConfigFlags::FIRST_TRANSLATE
| if enable_mouse {
ConfigFlags::SECOND_INTERRUPT
} else {
ConfigFlags::SECOND_DISABLED
};
self.set_config(config)?;
}
Ok(())
}
}
@@ -0,0 +1,135 @@
#[macro_use]
extern crate bitflags;
extern crate orbclient;
extern crate syscall;
use std::fs::OpenOptions;
use std::io::Read;
use std::os::unix::fs::OpenOptionsExt;
use std::os::unix::io::AsRawFd;
use std::process;
use common::acquire_port_io_rights;
use event::{user_data, EventQueue};
use inputd::ProducerHandle;
use crate::state::Ps2d;
mod controller;
mod mouse;
mod state;
mod vm;
fn daemon(daemon: daemon::Daemon) -> ! {
common::setup_logging(
"input",
"ps2",
"ps2",
common::output_level(),
common::file_level(),
);
acquire_port_io_rights().expect("ps2d: failed to get I/O permission");
let input = ProducerHandle::new().expect("ps2d: failed to open input producer");
user_data! {
enum Source {
Keyboard,
Mouse,
Time,
}
}
let event_queue: EventQueue<Source> =
EventQueue::new().expect("ps2d: failed to create event queue");
let mut key_file = OpenOptions::new()
.read(true)
.write(true)
.custom_flags(syscall::O_NONBLOCK as i32)
.open("/scheme/serio/0")
.expect("ps2d: failed to open /scheme/serio/0");
event_queue
.subscribe(
key_file.as_raw_fd() as usize,
Source::Keyboard,
event::EventFlags::READ,
)
.unwrap();
let mut mouse_file = OpenOptions::new()
.read(true)
.write(true)
.custom_flags(syscall::O_NONBLOCK as i32)
.open("/scheme/serio/1")
.expect("ps2d: failed to open /scheme/serio/1");
event_queue
.subscribe(
mouse_file.as_raw_fd() as usize,
Source::Mouse,
event::EventFlags::READ,
)
.unwrap();
let time_file = OpenOptions::new()
.read(true)
.write(true)
.custom_flags(syscall::O_NONBLOCK as i32)
.open(format!("/scheme/time/{}", syscall::CLOCK_MONOTONIC))
.expect("ps2d: failed to open /scheme/time");
event_queue
.subscribe(
time_file.as_raw_fd() as usize,
Source::Time,
event::EventFlags::READ,
)
.unwrap();
libredox::call::setrens(0, 0).expect("ps2d: failed to enter null namespace");
daemon.ready();
let mut ps2d = Ps2d::new(input, time_file);
let mut data = [0; 256];
for event in event_queue.map(|e| e.expect("ps2d: failed to get next event").user_data) {
// There are some gotchas with ps/2 controllers that require this weird
// way of doing things. You read key and mouse data from the same
// place. There is a status register that may show you which the data
// came from, but if it is even implemented it can have a race
// condition causing keyboard data to be read as mouse data.
//
// Due to this, we have a kernel driver doing a small amount of work
// to grab bytes and sort them based on the source
let (file, keyboard) = match event {
Source::Keyboard => (&mut key_file, true),
Source::Mouse => (&mut mouse_file, false),
Source::Time => {
ps2d.time_event();
continue;
}
};
loop {
let count = match file.read(&mut data) {
Ok(0) => break,
Ok(count) => count,
Err(_) => break,
};
for i in 0..count {
ps2d.handle(keyboard, data[i]);
}
}
}
process::exit(0);
}
fn main() {
daemon::Daemon::new(daemon);
}
@@ -0,0 +1,387 @@
use crate::controller::Ps2;
use std::time::Duration;
pub const RESET_RETRIES: usize = 10;
pub const RESET_TIMEOUT: Duration = Duration::from_millis(1000);
pub const COMMAND_TIMEOUT: Duration = Duration::from_millis(100);
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum MouseCommand {
SetScaling1To1 = 0xE6,
SetScaling2To1 = 0xE7,
StatusRequest = 0xE9,
GetDeviceId = 0xF2,
EnableReporting = 0xF4,
SetDefaultsDisable = 0xF5,
SetDefaults = 0xF6,
Reset = 0xFF,
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
enum MouseCommandData {
SetResolution = 0xE8,
SetSampleRate = 0xF3,
}
#[derive(Debug)]
struct MouseTx {
write: &'static [u8],
write_i: usize,
read: Vec<u8>,
read_bytes: usize,
}
impl MouseTx {
fn new(write: &'static [u8], read_bytes: usize, ps2: &mut Ps2) -> Result<Self, ()> {
let mut this = Self {
write,
write_i: 0,
read: Vec::with_capacity(read_bytes),
read_bytes,
};
this.try_write(ps2)?;
Ok(this)
}
fn try_write(&mut self, ps2: &mut Ps2) -> Result<(), ()> {
if let Some(write) = self.write.get(self.write_i) {
if let Err(err) = ps2.mouse_command_async(*write) {
log::error!("failed to write {:02X} to mouse: {:?}", write, err);
return Err(());
}
}
Ok(())
}
fn handle(&mut self, data: u8, ps2: &mut Ps2) -> Result<bool, ()> {
if self.write_i < self.write.len() {
if data == 0xFA {
self.write_i += 1;
self.try_write(ps2)?;
} else {
log::error!("unknown mouse response {:02X}", data);
return Err(());
}
} else {
self.read.push(data);
}
Ok(self.write_i >= self.write.len() && self.read.len() >= self.read_bytes)
}
}
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
#[allow(dead_code)]
enum MouseId {
/// Mouse sends three bytes
Base = 0x00,
/// Mouse sends fourth byte with scroll
Intellimouse1 = 0x03,
/// Mouse sends fourth byte with scroll, button 4, and button 5
//TODO: support this mouse type
Intellimouse2 = 0x04,
}
// From Synaptics TouchPad Interfacing Guide
#[derive(Clone, Copy, Debug)]
#[repr(u8)]
pub enum TouchpadCommand {
Identify = 0x00,
}
#[derive(Debug)]
pub enum MouseState {
/// No mouse found
None,
/// Ready to initialize mouse
Init,
/// Reset command is sent
Reset,
/// BAT completion code returned
Bat,
/// Identify touchpad
IdentifyTouchpad { tx: MouseTx },
/// Enable intellimouse features
EnableIntellimouse { tx: MouseTx },
/// Status request
Status { index: usize },
/// Device ID update
DeviceId,
/// Enable reporting command sent
EnableReporting { id: u8 },
/// Mouse is streaming
Streaming { id: u8 },
}
#[derive(Debug)]
#[must_use]
pub enum MouseResult {
None,
Packet(u8, bool),
Timeout(Duration),
}
impl MouseState {
pub fn reset(&mut self, ps2: &mut Ps2) -> MouseResult {
if ps2.mouse_resets < RESET_RETRIES {
ps2.mouse_resets += 1;
} else {
log::error!("tried to reset mouse {} times, giving up", ps2.mouse_resets);
*self = MouseState::None;
return MouseResult::None;
}
match ps2.mouse_command_async(MouseCommand::Reset as u8) {
Ok(()) => {
*self = MouseState::Reset;
MouseResult::Timeout(RESET_TIMEOUT)
}
Err(err) => {
log::error!("failed to send mouse reset command: {:?}", err);
//TODO: retry reset?
*self = MouseState::None;
MouseResult::None
}
}
}
fn enable_reporting(&mut self, id: u8, ps2: &mut Ps2) -> MouseResult {
match ps2.mouse_command_async(MouseCommand::EnableReporting as u8) {
Ok(()) => {
*self = MouseState::EnableReporting { id };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
Err(err) => {
log::error!("failed to enable mouse reporting: {:?}", err);
//TODO: reset mouse?
*self = MouseState::None;
MouseResult::None
}
}
}
fn request_status(&mut self, ps2: &mut Ps2) -> MouseResult {
match ps2.mouse_command_async(MouseCommand::StatusRequest as u8) {
Ok(()) => {
*self = MouseState::Status { index: 0 };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
Err(err) => {
log::error!("failed to request mouse status: {:?}", err);
//TODO: reset mouse instead?
self.request_id(ps2)
}
}
}
fn request_id(&mut self, ps2: &mut Ps2) -> MouseResult {
match ps2.mouse_command_async(MouseCommand::GetDeviceId as u8) {
Ok(()) => {
*self = MouseState::DeviceId;
MouseResult::Timeout(COMMAND_TIMEOUT)
}
Err(err) => {
log::error!("failed to request mouse id: {:?}", err);
//TODO: reset mouse instead?
self.enable_reporting(MouseId::Base as u8, ps2)
}
}
}
fn identify_touchpad(&mut self, ps2: &mut Ps2) -> MouseResult {
let cmd = TouchpadCommand::Identify as u8;
match MouseTx::new(
&[
// Ensure command alignment
MouseCommand::SetScaling1To1 as u8,
// Send special identify touchpad command
MouseCommandData::SetResolution as u8,
0,
MouseCommandData::SetResolution as u8,
0,
MouseCommandData::SetResolution as u8,
0,
MouseCommandData::SetResolution as u8,
0,
// Status request
MouseCommand::StatusRequest as u8,
],
3,
ps2,
) {
Ok(tx) => {
*self = MouseState::IdentifyTouchpad { tx };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
Err(()) => self.enable_intellimouse(ps2),
}
}
fn enable_intellimouse(&mut self, ps2: &mut Ps2) -> MouseResult {
match MouseTx::new(
&[
MouseCommandData::SetSampleRate as u8,
200,
MouseCommandData::SetSampleRate as u8,
100,
MouseCommandData::SetSampleRate as u8,
80,
],
0,
ps2,
) {
Ok(tx) => {
*self = MouseState::EnableIntellimouse { tx };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
Err(()) => self.request_id(ps2),
}
}
pub fn handle(&mut self, data: u8, ps2: &mut Ps2) -> MouseResult {
match *self {
MouseState::None | MouseState::Init => {
//TODO: enable port in this case, mouse hotplug may send 0xAA 0x00
log::error!(
"received mouse byte {:02X} when mouse not initialized",
data
);
MouseResult::None
}
MouseState::Reset => {
if data == 0xFA {
log::debug!("mouse reset ok");
MouseResult::Timeout(RESET_TIMEOUT)
} else if data == 0xAA {
log::debug!("BAT completed");
*self = MouseState::Bat;
MouseResult::Timeout(COMMAND_TIMEOUT)
} else {
log::warn!("unknown mouse response {:02X} after reset", data);
self.reset(ps2)
}
}
MouseState::Bat => {
if data == MouseId::Base as u8 {
// Enable intellimouse features
log::debug!("BAT mouse id {:02X} (base)", data);
self.identify_touchpad(ps2)
} else if data == MouseId::Intellimouse1 as u8 {
// Extra packet already enabled
log::debug!("BAT mouse id {:02X} (intellimouse)", data);
self.enable_reporting(data, ps2)
} else {
log::warn!("unknown mouse id {:02X} after BAT", data);
MouseResult::Timeout(RESET_TIMEOUT)
}
}
MouseState::IdentifyTouchpad { ref mut tx } => {
match tx.handle(data, ps2) {
Ok(done) => {
if done {
//TODO: handle touchpad identification
// If tx.read[1] == 0x47, this is a synaptics touchpad
self.request_status(ps2)
} else {
MouseResult::Timeout(COMMAND_TIMEOUT)
}
}
Err(()) => self.enable_intellimouse(ps2),
}
}
MouseState::EnableIntellimouse { ref mut tx } => match tx.handle(data, ps2) {
Ok(done) => {
if done {
self.request_status(ps2)
} else {
MouseResult::Timeout(COMMAND_TIMEOUT)
}
}
Err(()) => self.request_status(ps2),
},
MouseState::Status { index } => {
match index {
0 => {
//TODO: check response
*self = MouseState::Status { index: 1 };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
1 => {
*self = MouseState::Status { index: 2 };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
2 => {
*self = MouseState::Status { index: 3 };
MouseResult::Timeout(COMMAND_TIMEOUT)
}
_ => self.request_id(ps2),
}
}
MouseState::DeviceId => {
if data == 0xFA {
// Command OK response
//TODO: handle this separately?
MouseResult::Timeout(COMMAND_TIMEOUT)
} else if data == MouseId::Base as u8 || data == MouseId::Intellimouse1 as u8 {
log::debug!("mouse id {:02X}", data);
self.enable_reporting(data, ps2)
} else {
log::warn!("unknown mouse id {:02X} after requesting id", data);
self.reset(ps2)
}
}
MouseState::EnableReporting { id } => {
log::debug!("mouse id {:02X} enable reporting {:02X}", id, data);
//TODO: handle response ok/error
*self = MouseState::Streaming { id };
MouseResult::None
}
MouseState::Streaming { id } => {
MouseResult::Packet(data, id == MouseId::Intellimouse1 as u8)
}
}
}
pub fn handle_timeout(&mut self, ps2: &mut Ps2) -> MouseResult {
match *self {
MouseState::None | MouseState::Streaming { .. } => MouseResult::None,
MouseState::Init => {
// The state uses a timeout on init to request a reset
self.reset(ps2)
}
MouseState::Reset => {
log::warn!("timeout waiting for mouse reset");
self.reset(ps2)
}
MouseState::Bat => {
log::warn!("timeout waiting for BAT completion");
self.reset(ps2)
}
MouseState::IdentifyTouchpad { .. } => {
//TODO: retry?
log::warn!("timeout identifying touchpad");
self.request_status(ps2)
}
MouseState::EnableIntellimouse { .. } => {
//TODO: retry?
log::warn!("timeout enabling intellimouse");
self.request_status(ps2)
}
MouseState::Status { index } => {
log::warn!("timeout waiting for mouse status {}", index);
self.request_id(ps2)
}
MouseState::DeviceId => {
log::warn!("timeout requesting mouse id");
self.enable_reporting(0, ps2)
}
MouseState::EnableReporting { id } => {
log::warn!("timeout enabling reporting");
//TODO: limit number of retries
self.enable_reporting(id, ps2)
}
}
}
}
@@ -0,0 +1,487 @@
use inputd::ProducerHandle;
use log::{error, warn};
use orbclient::{ButtonEvent, KeyEvent, MouseEvent, MouseRelativeEvent, ScrollEvent};
use std::{
convert::TryInto,
fs::File,
io::{Read, Write},
time::Duration,
};
use syscall::TimeSpec;
use crate::controller::Ps2;
use crate::mouse::{MouseResult, MouseState};
use crate::vm;
bitflags! {
pub struct MousePacketFlags: u8 {
const LEFT_BUTTON = 1;
const RIGHT_BUTTON = 1 << 1;
const MIDDLE_BUTTON = 1 << 2;
const ALWAYS_ON = 1 << 3;
const X_SIGN = 1 << 4;
const Y_SIGN = 1 << 5;
const X_OVERFLOW = 1 << 6;
const Y_OVERFLOW = 1 << 7;
}
}
fn timespec_from_duration(duration: Duration) -> TimeSpec {
TimeSpec {
tv_sec: duration.as_secs().try_into().unwrap(),
tv_nsec: duration.subsec_nanos().try_into().unwrap(),
}
}
fn duration_from_timespec(timespec: TimeSpec) -> Duration {
Duration::new(
timespec.tv_sec.try_into().unwrap(),
timespec.tv_nsec.try_into().unwrap(),
)
}
pub struct Ps2d {
ps2: Ps2,
vmmouse: bool,
vmmouse_relative: bool,
input: ProducerHandle,
time_file: File,
extended: bool,
mouse_x: i32,
mouse_y: i32,
mouse_left: bool,
mouse_middle: bool,
mouse_right: bool,
mouse_state: MouseState,
mouse_timeout: Option<TimeSpec>,
packets: [u8; 4],
packet_i: usize,
}
impl Ps2d {
pub fn new(input: ProducerHandle, time_file: File) -> Self {
let mut ps2 = Ps2::new();
ps2.init().expect("failed to initialize");
// FIXME add an option for orbital to disable this when an app captures the mouse.
let vmmouse_relative = false;
let vmmouse = vm::enable(vmmouse_relative);
// TODO: QEMU hack, maybe do this when Init timed out?
if vmmouse {
// 3 = MouseId::Intellimouse1
MouseState::Bat.handle(3, &mut ps2);
}
let mut this = Ps2d {
ps2,
vmmouse,
vmmouse_relative,
input,
time_file,
extended: false,
mouse_x: 0,
mouse_y: 0,
mouse_left: false,
mouse_middle: false,
mouse_right: false,
mouse_state: MouseState::Init,
mouse_timeout: None,
packets: [0; 4],
packet_i: 0,
};
if !this.vmmouse {
// This triggers initializing the mouse
this.handle_mouse(None);
}
this
}
pub fn irq(&mut self) {
while let Some((keyboard, data)) = self.ps2.next() {
self.handle(keyboard, data);
}
}
pub fn time_event(&mut self) {
let mut time = TimeSpec::default();
match self.time_file.read(&mut time) {
Ok(_count) => {}
Err(err) => {
log::error!("failed to read time file: {}", err);
return;
}
}
if let Some(mouse_timeout) = self.mouse_timeout {
if time.tv_sec > mouse_timeout.tv_sec
|| (time.tv_sec == mouse_timeout.tv_sec && time.tv_nsec >= mouse_timeout.tv_nsec)
{
self.handle_mouse(None);
}
}
}
pub fn handle(&mut self, keyboard: bool, data: u8) {
if keyboard {
if data == 0xE0 {
self.extended = true;
} else {
let (ps2_scancode, pressed) = if data >= 0x80 {
(data - 0x80, false)
} else {
(data, true)
};
let scancode = if self.extended {
self.extended = false;
match ps2_scancode {
0x1C => orbclient::K_NUM_ENTER,
0x1D => orbclient::K_RIGHT_CTRL,
0x20 => orbclient::K_VOLUME_TOGGLE,
0x22 => orbclient::K_MEDIA_PLAY_PAUSE,
0x24 => orbclient::K_MEDIA_STOP,
0x10 => orbclient::K_MEDIA_REWIND,
0x19 => orbclient::K_MEDIA_FAST_FORWARD,
0x2E => orbclient::K_VOLUME_DOWN,
0x30 => orbclient::K_VOLUME_UP,
0x35 => orbclient::K_NUM_SLASH,
0x38 => orbclient::K_ALT_GR,
0x47 => orbclient::K_HOME,
0x48 => orbclient::K_UP,
0x49 => orbclient::K_PGUP,
0x4B => orbclient::K_LEFT,
0x4D => orbclient::K_RIGHT,
0x4F => orbclient::K_END,
0x50 => orbclient::K_DOWN,
0x51 => orbclient::K_PGDN,
0x52 => orbclient::K_INS,
0x53 => orbclient::K_DEL,
0x5B => orbclient::K_LEFT_SUPER,
0x5C => orbclient::K_RIGHT_SUPER,
0x5D => orbclient::K_APP,
0x5E => orbclient::K_POWER,
0x5F => orbclient::K_SLEEP,
/* 0x80 to 0xFF used for press/release detection */
_ => {
if pressed {
warn!("unknown extended scancode {:02X}", ps2_scancode);
}
0
}
}
} else {
match ps2_scancode {
/* 0x00 unused */
0x01 => orbclient::K_ESC,
0x02 => orbclient::K_1,
0x03 => orbclient::K_2,
0x04 => orbclient::K_3,
0x05 => orbclient::K_4,
0x06 => orbclient::K_5,
0x07 => orbclient::K_6,
0x08 => orbclient::K_7,
0x09 => orbclient::K_8,
0x0A => orbclient::K_9,
0x0B => orbclient::K_0,
0x0C => orbclient::K_MINUS,
0x0D => orbclient::K_EQUALS,
0x0E => orbclient::K_BKSP,
0x0F => orbclient::K_TAB,
0x10 => orbclient::K_Q,
0x11 => orbclient::K_W,
0x12 => orbclient::K_E,
0x13 => orbclient::K_R,
0x14 => orbclient::K_T,
0x15 => orbclient::K_Y,
0x16 => orbclient::K_U,
0x17 => orbclient::K_I,
0x18 => orbclient::K_O,
0x19 => orbclient::K_P,
0x1A => orbclient::K_BRACE_OPEN,
0x1B => orbclient::K_BRACE_CLOSE,
0x1C => orbclient::K_ENTER,
0x1D => orbclient::K_CTRL,
0x1E => orbclient::K_A,
0x1F => orbclient::K_S,
0x20 => orbclient::K_D,
0x21 => orbclient::K_F,
0x22 => orbclient::K_G,
0x23 => orbclient::K_H,
0x24 => orbclient::K_J,
0x25 => orbclient::K_K,
0x26 => orbclient::K_L,
0x27 => orbclient::K_SEMICOLON,
0x28 => orbclient::K_QUOTE,
0x29 => orbclient::K_TICK,
0x2A => orbclient::K_LEFT_SHIFT,
0x2B => orbclient::K_BACKSLASH,
0x2C => orbclient::K_Z,
0x2D => orbclient::K_X,
0x2E => orbclient::K_C,
0x2F => orbclient::K_V,
0x30 => orbclient::K_B,
0x31 => orbclient::K_N,
0x32 => orbclient::K_M,
0x33 => orbclient::K_COMMA,
0x34 => orbclient::K_PERIOD,
0x35 => orbclient::K_SLASH,
0x36 => orbclient::K_RIGHT_SHIFT,
0x37 => orbclient::K_NUM_ASTERISK,
0x38 => orbclient::K_ALT,
0x39 => orbclient::K_SPACE,
0x3A => orbclient::K_CAPS,
0x3B => orbclient::K_F1,
0x3C => orbclient::K_F2,
0x3D => orbclient::K_F3,
0x3E => orbclient::K_F4,
0x3F => orbclient::K_F5,
0x40 => orbclient::K_F6,
0x41 => orbclient::K_F7,
0x42 => orbclient::K_F8,
0x43 => orbclient::K_F9,
0x44 => orbclient::K_F10,
0x45 => orbclient::K_NUM,
0x46 => orbclient::K_SCROLL,
0x47 => orbclient::K_NUM_7,
0x48 => orbclient::K_NUM_8,
0x49 => orbclient::K_NUM_9,
0x4A => orbclient::K_NUM_MINUS,
0x4B => orbclient::K_NUM_4,
0x4C => orbclient::K_NUM_5,
0x4D => orbclient::K_NUM_6,
0x4E => orbclient::K_NUM_PLUS,
0x4F => orbclient::K_NUM_1,
0x50 => orbclient::K_NUM_2,
0x51 => orbclient::K_NUM_3,
0x52 => orbclient::K_NUM_0,
0x53 => orbclient::K_NUM_PERIOD,
/* 0x54 to 0x55 unused */
0x56 => 0x56, // UK Backslash
0x57 => orbclient::K_F11,
0x58 => orbclient::K_F12,
/* 0x59 to 0x7F unused */
/* 0x80 to 0xFF used for press/release detection */
_ => {
if pressed {
warn!("unknown scancode {:02X}", ps2_scancode);
}
0
}
}
};
if scancode != 0 {
self.input
.write_event(
KeyEvent {
character: '\0',
scancode,
pressed,
}
.to_event(),
)
.expect("failed to write key event");
}
}
} else if self.vmmouse {
for _i in 0..256 {
let (status, _, _, _) = unsafe { vm::cmd(vm::ABSPOINTER_STATUS, 0) };
//TODO if ((status & VMMOUSE_ERROR) == VMMOUSE_ERROR)
let queue_length = status & 0xffff;
if queue_length == 0 {
break;
}
if queue_length % 4 != 0 {
error!("queue length not a multiple of 4: {}", queue_length);
break;
}
let (status, dx, dy, dz) = unsafe { vm::cmd(vm::ABSPOINTER_DATA, 4) };
if self.vmmouse_relative {
if dx != 0 || dy != 0 {
self.input
.write_event(
MouseRelativeEvent {
dx: dx as i32,
dy: dy as i32,
}
.to_event(),
)
.expect("ps2d: failed to write mouse event");
}
} else {
let x = dx as i32;
let y = dy as i32;
if x != self.mouse_x || y != self.mouse_y {
self.mouse_x = x;
self.mouse_y = y;
self.input
.write_event(MouseEvent { x, y }.to_event())
.expect("ps2d: failed to write mouse event");
}
};
if dz != 0 {
self.input
.write_event(
ScrollEvent {
x: 0,
y: -(dz as i32),
}
.to_event(),
)
.expect("ps2d: failed to write scroll event");
}
let left = status & vm::LEFT_BUTTON == vm::LEFT_BUTTON;
let middle = status & vm::MIDDLE_BUTTON == vm::MIDDLE_BUTTON;
let right = status & vm::RIGHT_BUTTON == vm::RIGHT_BUTTON;
if left != self.mouse_left
|| middle != self.mouse_middle
|| right != self.mouse_right
{
self.mouse_left = left;
self.mouse_middle = middle;
self.mouse_right = right;
self.input
.write_event(
ButtonEvent {
left,
middle,
right,
}
.to_event(),
)
.expect("ps2d: failed to write button event");
}
}
} else {
self.handle_mouse(Some(data));
}
}
pub fn handle_mouse(&mut self, data_opt: Option<u8>) {
// log::trace!(
// "handle_mouse state {:?} data {:?}",
// self.mouse_state,
// data_opt
// );
let mouse_res = match data_opt {
Some(data) => self.mouse_state.handle(data, &mut self.ps2),
None => self.mouse_state.handle_timeout(&mut self.ps2),
};
self.mouse_timeout = None;
let (packet_data, extra_packet) = match mouse_res {
MouseResult::None => {
return;
}
MouseResult::Packet(packet_data, extra_packet) => (packet_data, extra_packet),
MouseResult::Timeout(duration) => {
// Read current time
let mut time = TimeSpec::default();
match self.time_file.read(&mut time) {
Ok(_count) => {}
Err(err) => {
log::error!("failed to read time file: {}", err);
return;
}
}
// Add duration to time
time = timespec_from_duration(duration_from_timespec(time) + duration);
// Write next time
match self.time_file.write(&time) {
Ok(_count) => {}
Err(err) => {
log::error!("failed to write time file: {}", err);
}
}
self.mouse_timeout = Some(time);
return;
}
};
self.packets[self.packet_i] = packet_data;
self.packet_i += 1;
let flags = MousePacketFlags::from_bits_truncate(self.packets[0]);
if !flags.contains(MousePacketFlags::ALWAYS_ON) {
error!("mouse misalign {:X}", self.packets[0]);
self.packets = [0; 4];
self.packet_i = 0;
} else if self.packet_i >= self.packets.len() || (!extra_packet && self.packet_i >= 3) {
if !flags.contains(MousePacketFlags::X_OVERFLOW)
&& !flags.contains(MousePacketFlags::Y_OVERFLOW)
{
let mut dx = self.packets[1] as i32;
if flags.contains(MousePacketFlags::X_SIGN) {
dx -= 0x100;
}
let mut dy = -(self.packets[2] as i32);
if flags.contains(MousePacketFlags::Y_SIGN) {
dy += 0x100;
}
let mut dz = 0;
if extra_packet {
let mut scroll = (self.packets[3] & 0xF) as i8;
if scroll & (1 << 3) == 1 << 3 {
scroll -= 16;
}
dz = -scroll as i32;
}
if dx != 0 || dy != 0 {
self.input
.write_event(MouseRelativeEvent { dx, dy }.to_event())
.expect("ps2d: failed to write mouse event");
}
if dz != 0 {
self.input
.write_event(ScrollEvent { x: 0, y: dz }.to_event())
.expect("ps2d: failed to write scroll event");
}
let left = flags.contains(MousePacketFlags::LEFT_BUTTON);
let middle = flags.contains(MousePacketFlags::MIDDLE_BUTTON);
let right = flags.contains(MousePacketFlags::RIGHT_BUTTON);
if left != self.mouse_left
|| middle != self.mouse_middle
|| right != self.mouse_right
{
self.mouse_left = left;
self.mouse_middle = middle;
self.mouse_right = right;
self.input
.write_event(
ButtonEvent {
left,
middle,
right,
}
.to_event(),
)
.expect("ps2d: failed to write button event");
}
} else {
warn!(
"overflow {:X} {:X} {:X} {:X}",
self.packets[0], self.packets[1], self.packets[2], self.packets[3]
);
}
self.packets = [0; 4];
self.packet_i = 0;
}
}
}
@@ -0,0 +1,107 @@
// This code is informed by the QEMU implementation found here:
// https://github.com/qemu/qemu/blob/master/hw/input/vmmouse.c
//
// As well as the Linux implementation here:
// http://elixir.free-electrons.com/linux/v4.1/source/drivers/input/mouse/vmmouse.c
use core::arch::asm;
use log::{error, info, trace};
const MAGIC: u32 = 0x564D5868;
const PORT: u16 = 0x5658;
pub const GETVERSION: u32 = 10;
pub const ABSPOINTER_DATA: u32 = 39;
pub const ABSPOINTER_STATUS: u32 = 40;
pub const ABSPOINTER_COMMAND: u32 = 41;
pub const CMD_ENABLE: u32 = 0x45414552;
pub const CMD_DISABLE: u32 = 0x000000f5;
pub const CMD_REQUEST_ABSOLUTE: u32 = 0x53424152;
pub const CMD_REQUEST_RELATIVE: u32 = 0x4c455252;
const VERSION: u32 = 0x3442554a;
pub const RELATIVE_PACKET: u32 = 0x00010000;
pub const LEFT_BUTTON: u32 = 0x20;
pub const RIGHT_BUTTON: u32 = 0x10;
pub const MIDDLE_BUTTON: u32 = 0x08;
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
pub unsafe fn cmd(cmd: u32, arg: u32) -> (u32, u32, u32, u32) {
let a: u32;
let b: u32;
let c: u32;
let d: u32;
// ebx can't be used as input or output constraint in rust as LLVM reserves it.
// Use xchg to pass it through r9 instead while restoring the original value in
// rbx when leaving the inline asm block. si and di are clobbered too.
#[cfg(not(target_arch = "x86"))]
asm!(
"xchg r9, rbx; in eax, dx; xchg r9, rbx",
inout("eax") MAGIC => a,
inout("r9") arg => b,
inout("ecx") cmd => c,
inout("edx") PORT as u32 => d,
out("rsi") _,
out("rdi") _,
);
// On x86 we don't have a spare register, so push ebx to the stack instead.
#[cfg(target_arch = "x86")]
asm!(
"push ebx; mov ebx, edi; in eax, dx; mov edi, ebx; pop ebx",
inout("eax") MAGIC => a,
inout("edi") arg => b,
inout("ecx") cmd => c,
inout("edx") PORT as u32 => d,
);
(a, b, c, d)
}
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
pub unsafe fn cmd(cmd: u32, arg: u32) -> (u32, u32, u32, u32) {
unimplemented!()
}
pub fn enable(relative: bool) -> bool {
trace!("Enable vmmouse");
unsafe {
let (eax, ebx, _, _) = cmd(GETVERSION, 0);
if ebx != MAGIC || eax == 0xFFFFFFFF {
info!("No vmmouse support");
return false;
}
let _ = cmd(ABSPOINTER_COMMAND, CMD_ENABLE);
let (status, _, _, _) = cmd(ABSPOINTER_STATUS, 0);
if (status & 0x0000ffff) == 0 {
info!("No vmmouse");
return false;
}
let (version, _, _, _) = cmd(ABSPOINTER_DATA, 1);
if version != VERSION {
error!(
"Invalid vmmouse version: {} instead of {}",
version, VERSION
);
let _ = cmd(ABSPOINTER_COMMAND, CMD_DISABLE);
return false;
}
if relative {
cmd(ABSPOINTER_COMMAND, CMD_REQUEST_RELATIVE);
} else {
cmd(ABSPOINTER_COMMAND, CMD_REQUEST_ABSOLUTE);
}
}
return true;
}
@@ -0,0 +1 @@
/target
@@ -0,0 +1,24 @@
[package]
name = "usbhidd"
description = "USB HID driver"
version = "0.1.0"
authors = ["4lDO2 <4lDO2@protonmail.com>"]
edition = "2018"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow.workspace = true
bitflags.workspace = true
log.workspace = true
orbclient.workspace = true
redox_syscall.workspace = true
rehid = { git = "https://gitlab.redox-os.org/redox-os/rehid.git" }
xhcid = { path = "../../usb/xhcid" }
common = { path = "../../common" }
inputd = { path = "../../inputd" }
[lints]
workspace = true
@@ -0,0 +1,457 @@
use anyhow::{Context, Result};
use std::{env, thread, time};
use inputd::ProducerHandle;
use orbclient::KeyEvent as OrbKeyEvent;
use rehid::{
report_desc::{ReportTy, REPORT_DESC_TY},
report_handler::ReportHandler,
usage_tables::{GenericDesktopUsage, UsagePage},
};
use xhcid_interface::{
ConfigureEndpointsReq, DevDesc, EndpDirection, EndpointTy, PortId, PortReqRecipient,
XhciClientHandle,
};
mod reqs;
fn send_key_event(display: &mut ProducerHandle, usage_page: u16, usage: u16, pressed: bool) {
let scancode = match usage_page {
0x07 => match usage {
0x04 => orbclient::K_A,
0x05 => orbclient::K_B,
0x06 => orbclient::K_C,
0x07 => orbclient::K_D,
0x08 => orbclient::K_E,
0x09 => orbclient::K_F,
0x0A => orbclient::K_G,
0x0B => orbclient::K_H,
0x0C => orbclient::K_I,
0x0D => orbclient::K_J,
0x0E => orbclient::K_K,
0x0F => orbclient::K_L,
0x10 => orbclient::K_M,
0x11 => orbclient::K_N,
0x12 => orbclient::K_O,
0x13 => orbclient::K_P,
0x14 => orbclient::K_Q,
0x15 => orbclient::K_R,
0x16 => orbclient::K_S,
0x17 => orbclient::K_T,
0x18 => orbclient::K_U,
0x19 => orbclient::K_V,
0x1A => orbclient::K_W,
0x1B => orbclient::K_X,
0x1C => orbclient::K_Y,
0x1D => orbclient::K_Z,
0x1E => orbclient::K_1,
0x1F => orbclient::K_2,
0x20 => orbclient::K_3,
0x21 => orbclient::K_4,
0x22 => orbclient::K_5,
0x23 => orbclient::K_6,
0x24 => orbclient::K_7,
0x25 => orbclient::K_8,
0x26 => orbclient::K_9,
0x27 => orbclient::K_0,
0x28 => orbclient::K_ENTER,
0x29 => orbclient::K_ESC,
0x2A => orbclient::K_BKSP,
0x2B => orbclient::K_TAB,
0x2C => orbclient::K_SPACE,
0x2D => orbclient::K_MINUS,
0x2E => orbclient::K_EQUALS,
0x2F => orbclient::K_BRACE_OPEN,
0x30 => orbclient::K_BRACE_CLOSE,
0x31 => orbclient::K_BACKSLASH,
// 0x32 non-us # and ~
0x32 => 0x56,
0x33 => orbclient::K_SEMICOLON,
0x34 => orbclient::K_QUOTE,
0x35 => orbclient::K_TICK,
0x36 => orbclient::K_COMMA,
0x37 => orbclient::K_PERIOD,
0x38 => orbclient::K_SLASH,
0x39 => orbclient::K_CAPS,
0x3A => orbclient::K_F1,
0x3B => orbclient::K_F2,
0x3C => orbclient::K_F3,
0x3D => orbclient::K_F4,
0x3E => orbclient::K_F5,
0x3F => orbclient::K_F6,
0x40 => orbclient::K_F7,
0x41 => orbclient::K_F8,
0x42 => orbclient::K_F9,
0x43 => orbclient::K_F10,
0x44 => orbclient::K_F11,
0x45 => orbclient::K_F12,
0x46 => orbclient::K_PRTSC,
0x47 => orbclient::K_SCROLL,
// 0x48 pause
0x49 => orbclient::K_INS,
0x4A => orbclient::K_HOME,
0x4B => orbclient::K_PGUP,
0x4C => orbclient::K_DEL,
0x4D => orbclient::K_END,
0x4E => orbclient::K_PGDN,
0x4F => orbclient::K_RIGHT,
0x50 => orbclient::K_LEFT,
0x51 => orbclient::K_DOWN,
0x52 => orbclient::K_UP,
0x53 => orbclient::K_NUM,
0x54 => orbclient::K_NUM_SLASH,
0x55 => orbclient::K_NUM_ASTERISK,
0x56 => orbclient::K_NUM_MINUS,
0x57 => orbclient::K_NUM_PLUS,
0x58 => orbclient::K_NUM_ENTER,
0x59 => orbclient::K_NUM_1,
0x5A => orbclient::K_NUM_2,
0x5B => orbclient::K_NUM_3,
0x5C => orbclient::K_NUM_4,
0x5D => orbclient::K_NUM_5,
0x5E => orbclient::K_NUM_6,
0x5F => orbclient::K_NUM_7,
0x60 => orbclient::K_NUM_8,
0x61 => orbclient::K_NUM_9,
0x62 => orbclient::K_NUM_0,
// 0x62 num .
// 0x64 non-us \ and |
0x64 => orbclient::K_APP,
0x66 => orbclient::K_POWER,
// 0x67 num =
// unmapped values
0xE0 => orbclient::K_LEFT_CTRL,
0xE1 => orbclient::K_LEFT_SHIFT,
0xE2 => orbclient::K_ALT,
0xE3 => orbclient::K_LEFT_SUPER,
0xE4 => orbclient::K_RIGHT_CTRL,
0xE5 => orbclient::K_RIGHT_SHIFT,
0xE6 => orbclient::K_ALT_GR,
0xE7 => orbclient::K_RIGHT_SUPER,
// reserved values
_ => {
log::warn!("unknown usage_page {:#x} usage {:#x}", usage_page, usage);
return;
}
},
_ => {
log::warn!("unknown usage_page {:#x}", usage_page);
return;
}
};
let key_event = OrbKeyEvent {
character: '\0',
scancode,
pressed,
};
match display.write_event(key_event.to_event()) {
Ok(_) => (),
Err(err) => {
log::warn!("failed to send key event to orbital: {}", err);
}
}
}
fn main() -> Result<()> {
let mut args = env::args().skip(1);
const USAGE: &'static str = "usbhidd <scheme> <port> <interface>";
let scheme = args.next().expect(USAGE);
let port = args
.next()
.expect(USAGE)
.parse::<PortId>()
.expect("Expected port ID");
let interface_num = args
.next()
.expect(USAGE)
.parse::<u8>()
.expect("Expected integer as input of interface");
let name = format!("{}_{}_{}_hid", scheme, port, interface_num);
common::setup_logging(
"usb",
"usbhid",
&name,
common::output_level(),
common::file_level(),
);
log::info!(
"USB HID driver spawned with scheme `{}`, port {}, interface {}",
scheme,
port,
interface_num
);
let handle = XhciClientHandle::new(scheme, port).context("Failed to open XhciClientHandle")?;
let desc: DevDesc = handle
.get_standard_descs()
.context("Failed to get standard descriptors")?;
log::info!(
"USB HID driver: {:?} serial {:?}",
desc.product_str.as_ref().map(|s| s.as_str()).unwrap_or(""),
desc.serial_str.as_ref().map(|s| s.as_str()).unwrap_or(""),
);
log::debug!("{:X?}", desc);
let mut endp_count = 0;
let (conf_desc, (if_desc, endp_desc_opt, hid_desc)) = desc
.config_descs
.iter()
.find_map(|conf_desc| {
let if_desc = conf_desc.interface_descs.iter().find_map(|if_desc| {
if if_desc.number == interface_num {
let endp_desc_opt = if_desc.endpoints.iter().find_map(|endp_desc| {
endp_count += 1;
if endp_desc.ty() == EndpointTy::Interrupt
&& endp_desc.direction() == EndpDirection::In
{
Some((endp_count, endp_desc.clone()))
} else {
None
}
});
let hid_desc = if_desc.hid_descs.iter().find_map(|hid_desc| {
//TODO: should we do any filtering?
Some(hid_desc)
})?;
Some((if_desc.clone(), endp_desc_opt, hid_desc))
} else {
endp_count += if_desc.endpoints.len();
None
}
})?;
Some((conf_desc.clone(), if_desc))
})
.context("Failed to find suitable configuration")?;
handle
.configure_endpoints(&ConfigureEndpointsReq {
config_desc: conf_desc.configuration_value,
interface_desc: Some(interface_num),
alternate_setting: Some(if_desc.alternate_setting),
hub_ports: None,
})
.context("Failed to configure endpoints")?;
//TODO: do we need to set protocol to report? It fails for mice.
//TODO: dynamically create good values, fix xhcid so it does not block on each request
// This sets all reports to a duration of 4ms
reqs::set_idle(&handle, 1, 0, interface_num as u16).context("Failed to set idle")?;
let report_desc_len = hid_desc.desc_len;
assert_eq!(hid_desc.desc_ty, REPORT_DESC_TY);
let mut report_desc_bytes = vec![0u8; report_desc_len as usize];
handle
.get_descriptor(
PortReqRecipient::Interface,
REPORT_DESC_TY,
0,
//TODO: should this be an index into interface_descs?
interface_num as u16,
&mut report_desc_bytes,
)
.context("Failed to retrieve report descriptor")?;
let mut handler =
ReportHandler::new(&report_desc_bytes).expect("failed to parse report descriptor");
let report_len = match endp_desc_opt {
Some((_endp_num, endp_desc)) => endp_desc.max_packet_size as usize,
None => handler.total_byte_length as usize,
};
let mut report_buffer = vec![0u8; report_len];
let report_ty = ReportTy::Input;
let report_id = 0;
let mut display = ProducerHandle::new().context("Failed to open input socket")?;
let mut endpoint_opt = match endp_desc_opt {
Some((endp_num, _endp_desc)) => match handle.open_endpoint(endp_num as u8) {
Ok(ok) => Some(ok),
Err(err) => {
log::warn!("failed to open endpoint {endp_num}: {err}");
None
}
},
None => None,
};
let mut left_shift = false;
let mut right_shift = false;
let mut last_mouse_pos = (0, 0);
let mut last_buttons = [false, false, false];
loop {
//TODO: get frequency from device
//TODO: use sleeps when accuracy is better: thread::sleep(time::Duration::from_millis(10));
let timer = time::Instant::now();
while timer.elapsed() < time::Duration::from_millis(1) {
thread::yield_now();
}
if let Some(endpoint) = &mut endpoint_opt {
// interrupt transfer
endpoint
.transfer_read(&mut report_buffer)
.context("failed to get report")?;
} else {
// control transfer
reqs::get_report(
&handle,
report_ty,
report_id,
//TODO: should this be an index into interface_descs?
interface_num as u16,
&mut report_buffer,
)
.context("failed to get report")?;
}
let mut mouse_pos = last_mouse_pos;
let mut mouse_dx = 0i32;
let mut mouse_dy = 0i32;
let mut scroll_y = 0i32;
let mut buttons = last_buttons;
for event in handler
.handle(&report_buffer)
.expect("failed to parse report")
{
log::debug!("{}", event);
if event.usage_page == UsagePage::GenericDesktop as u16 {
if event.usage == GenericDesktopUsage::X as u16 {
if event.relative {
mouse_dx += event.value as i32;
} else {
mouse_pos.0 = event.value as i32;
}
} else if event.usage == GenericDesktopUsage::Y as u16 {
if event.relative {
mouse_dy += event.value as i32;
} else {
mouse_pos.1 = event.value as i32;
}
} else if event.usage == GenericDesktopUsage::Wheel as u16 {
//TODO: what is X scroll?
if event.relative {
scroll_y += event.value as i32;
} else {
log::warn!("absolute mouse wheel not supported");
}
} else {
log::info!(
"unsupported generic desktop usage 0x{:X}:0x{:X} value {}",
event.usage_page,
event.usage,
event.value
);
}
} else if event.usage_page == UsagePage::KeyboardOrKeypad as u16 {
let (pressed, shift_opt) = if event.value != 0 {
(true, Some(left_shift | right_shift))
} else {
(false, None)
};
if event.usage == 0xE1 {
left_shift = pressed;
} else if event.usage == 0xE5 {
right_shift = pressed;
}
send_key_event(&mut display, event.usage_page, event.usage, pressed);
} else if event.usage_page == UsagePage::Button as u16 {
if event.usage > 0 && event.usage as usize <= buttons.len() {
buttons[event.usage as usize - 1] = event.value != 0;
} else {
log::info!(
"unsupported buttons usage 0x{:X}:0x{:X} value {}",
event.usage_page,
event.usage,
event.value
);
}
} else if event.usage_page >= 0xFF00 {
// Ignore vendor defined event
} else {
log::info!(
"unsupported usage 0x{:X}:0x{:X} value {}",
event.usage_page,
event.usage,
event.value
);
}
}
if mouse_pos != last_mouse_pos {
last_mouse_pos = mouse_pos;
// TODO
// ps2d uses 0..=65535 as range, while usb uses 0..=32767. orbital
// expects the former range, so multiply by two here to temporarily
// align with orbital expectation. This workaround will make cursor
// looks out of sync in QEMU using virtio-vga with usb-tablet.
let mouse_event = orbclient::event::MouseEvent {
x: mouse_pos.0 * 2,
y: mouse_pos.1 * 2,
};
match display.write_event(mouse_event.to_event()) {
Ok(_) => (),
Err(err) => {
log::warn!("failed to send mouse event to orbital: {}", err);
}
}
}
if mouse_dx != 0 || mouse_dy != 0 {
// TODO: This is a filter to prevent random mouse jumps
if mouse_dx > -127 && mouse_dx < 127 {
let mouse_event = orbclient::event::MouseRelativeEvent {
dx: mouse_dx,
dy: mouse_dy,
};
match display.write_event(mouse_event.to_event()) {
Ok(_) => (),
Err(err) => {
log::warn!("failed to send mouse event to orbital: {}", err);
}
}
}
}
if scroll_y != 0 {
let scroll_event = orbclient::event::ScrollEvent { x: 0, y: scroll_y };
match display.write_event(scroll_event.to_event()) {
Ok(_) => (),
Err(err) => {
log::warn!("failed to send scroll event to orbital: {}", err);
}
}
}
if buttons != last_buttons {
last_buttons = buttons;
let button_event = orbclient::event::ButtonEvent {
left: buttons[0],
right: buttons[1],
middle: buttons[2],
};
match display.write_event(button_event.to_event()) {
Ok(_) => (),
Err(err) => {
log::warn!("failed to send button event to orbital: {}", err);
}
}
}
// log::trace!("took {}ms", timer.elapsed().as_millis())
}
}
@@ -0,0 +1,109 @@
use std::slice;
use rehid::report_desc::ReportTy;
use xhcid_interface::{
DeviceReqData, PortReqRecipient, PortReqTy, XhciClientHandle, XhciClientHandleError,
};
const GET_REPORT_REQ: u8 = 0x1;
const SET_REPORT_REQ: u8 = 0x9;
const GET_IDLE_REQ: u8 = 0x2;
const SET_IDLE_REQ: u8 = 0xA;
const GET_PROTOCOL_REQ: u8 = 0x3;
const SET_PROTOCOL_REQ: u8 = 0xB;
fn concat(hi: u8, lo: u8) -> u16 {
(u16::from(hi) << 8) | u16::from(lo)
}
pub fn get_report(
handle: &XhciClientHandle,
report_ty: ReportTy,
report_id: u8,
if_num: u16,
buffer: &mut [u8],
) -> Result<(), XhciClientHandleError> {
handle.device_request(
PortReqTy::Class,
PortReqRecipient::Interface,
GET_REPORT_REQ,
concat(report_ty as u8, report_id),
if_num,
DeviceReqData::In(buffer),
)
}
pub fn set_report(
handle: &XhciClientHandle,
report_ty: ReportTy,
report_id: u8,
if_num: u16,
buffer: &[u8],
) -> Result<(), XhciClientHandleError> {
handle.device_request(
PortReqTy::Class,
PortReqRecipient::Interface,
SET_REPORT_REQ,
concat(report_id, report_ty as u8),
if_num,
DeviceReqData::Out(buffer),
)
}
pub fn get_idle(
handle: &XhciClientHandle,
report_id: u8,
if_num: u16,
) -> Result<u8, XhciClientHandleError> {
let mut idle_rate = 0;
let buffer = slice::from_mut(&mut idle_rate);
handle.device_request(
PortReqTy::Class,
PortReqRecipient::Interface,
GET_IDLE_REQ,
u16::from(report_id),
if_num,
DeviceReqData::In(buffer),
)?;
Ok(idle_rate)
}
pub fn set_idle(
handle: &XhciClientHandle,
duration: u8,
report_id: u8,
if_num: u16,
) -> Result<(), XhciClientHandleError> {
handle.device_request(
PortReqTy::Class,
PortReqRecipient::Interface,
SET_IDLE_REQ,
concat(duration, report_id),
if_num,
DeviceReqData::NoData,
)
}
pub fn get_protocol(handle: &XhciClientHandle, if_num: u16) -> Result<u8, XhciClientHandleError> {
let mut protocol = 0;
let buffer = slice::from_mut(&mut protocol);
handle.device_request(
PortReqTy::Class,
PortReqRecipient::Interface,
GET_PROTOCOL_REQ,
0,
if_num,
DeviceReqData::In(buffer),
)?;
Ok(protocol)
}
pub fn set_protocol(
handle: &XhciClientHandle,
protocol: u8,
if_num: u16,
) -> Result<(), XhciClientHandleError> {
handle.device_request(
PortReqTy::Class,
PortReqRecipient::Interface,
SET_PROTOCOL_REQ,
u16::from(protocol),
if_num,
DeviceReqData::NoData,
)
}