Red Bear OS base baseline from 0.1.0 pre-patched archive
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "i2c-hidd"
|
||||
description = "I2C HID client daemon"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
orbclient.workspace = true
|
||||
redox_syscall = { workspace = true, features = ["std"] }
|
||||
libredox.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
ron.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
acpi-resource = { path = "../../acpi-resource" }
|
||||
amlserde = { path = "../../amlserde" }
|
||||
common = { path = "../../common" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
i2c-interface = { path = "../../i2c/i2c-interface" }
|
||||
inputd = { path = "../../inputd" }
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,307 @@
|
||||
use acpi_resource::{GpioDescriptor, ResourceDescriptor};
|
||||
use amlserde::{AmlSerde, AmlSerdeValue};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use libredox::flag::{O_CLOEXEC, O_RDWR};
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::{ErrorKind, Read};
|
||||
|
||||
use crate::quirks::ProbeFailureQuirk;
|
||||
|
||||
const I2C_HID_DSM_GUID: [u8; 16] = [
|
||||
0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x41, 0x76,
|
||||
];
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct I2cBinding {
|
||||
pub adapter: String,
|
||||
pub address: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AcpiDeviceResources {
|
||||
pub i2c: I2cBinding,
|
||||
pub irq: Option<u32>,
|
||||
pub gpio_int: Vec<GpioDescriptor>,
|
||||
pub gpio_io: Vec<GpioDescriptor>,
|
||||
}
|
||||
|
||||
pub fn scan_acpi_i2c_hid_devices() -> Result<Vec<String>> {
|
||||
let entries = match fs::read_dir("/scheme/acpi/symbols") {
|
||||
Ok(entries) => entries,
|
||||
Err(err) if err.kind() == ErrorKind::WouldBlock || err.raw_os_error() == Some(11) => {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
Err(err) => return Err(err).context("failed to read /scheme/acpi/symbols"),
|
||||
};
|
||||
|
||||
let mut devices = BTreeSet::new();
|
||||
for entry in entries {
|
||||
let entry = entry.context("failed to enumerate ACPI symbol entry")?;
|
||||
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
|
||||
continue;
|
||||
};
|
||||
if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let symbol = read_aml_symbol(&file_name)
|
||||
.with_context(|| format!("failed to read ACPI symbol {file_name}"))?;
|
||||
let Some(id) = decode_hardware_id(&symbol.value) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if matches!(id.as_str(), "PNP0C50" | "ACPI0C50") {
|
||||
let device = symbol
|
||||
.name
|
||||
.strip_suffix("._HID")
|
||||
.or_else(|| symbol.name.strip_suffix("._CID"))
|
||||
.unwrap_or(&symbol.name)
|
||||
.trim_start_matches('\\')
|
||||
.trim_matches('/')
|
||||
.replace('/', ".");
|
||||
if !device.is_empty() {
|
||||
devices.insert(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(devices.into_iter().collect())
|
||||
}
|
||||
|
||||
pub fn read_decoded_resources(path: &str) -> Result<AcpiDeviceResources> {
|
||||
let resource_path = format!("/scheme/acpi/resources/{}", normalize_device_path(path));
|
||||
let serialized = fs::read_to_string(&resource_path)
|
||||
.with_context(|| format!("failed to read {resource_path}"))?;
|
||||
let descriptors: Vec<ResourceDescriptor> = ron::from_str(&serialized)
|
||||
.with_context(|| format!("invalid ACPI resources in {resource_path}"))?;
|
||||
|
||||
let mut i2c = None;
|
||||
let mut irq = None;
|
||||
let mut gpio_int = Vec::new();
|
||||
let mut gpio_io = Vec::new();
|
||||
|
||||
for descriptor in descriptors {
|
||||
match descriptor {
|
||||
ResourceDescriptor::I2cSerialBus(bus) => {
|
||||
if i2c.is_none() {
|
||||
let adapter = bus
|
||||
.resource_source
|
||||
.as_ref()
|
||||
.map(|source| source.source.clone())
|
||||
.filter(|source| !source.is_empty())
|
||||
.unwrap_or_else(|| "ACPI-I2C".to_string());
|
||||
i2c = Some(I2cBinding {
|
||||
adapter,
|
||||
address: bus.slave_address,
|
||||
});
|
||||
}
|
||||
}
|
||||
ResourceDescriptor::Irq(descriptor) => {
|
||||
if irq.is_none() {
|
||||
irq = descriptor.interrupts.first().copied().map(u32::from);
|
||||
}
|
||||
}
|
||||
ResourceDescriptor::ExtendedIrq(descriptor) => {
|
||||
if irq.is_none() {
|
||||
irq = descriptor.interrupts.first().copied();
|
||||
}
|
||||
}
|
||||
ResourceDescriptor::GpioInt(descriptor) => gpio_int.push(descriptor),
|
||||
ResourceDescriptor::GpioIo(descriptor) => gpio_io.push(descriptor),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
let mut resources = AcpiDeviceResources {
|
||||
i2c: i2c.ok_or_else(|| anyhow!("no I2cSerialBus resource in _CRS"))?,
|
||||
irq,
|
||||
gpio_int,
|
||||
gpio_io,
|
||||
};
|
||||
|
||||
if let Some(override_address) = companion_icrs_override(path)? {
|
||||
log::info!(
|
||||
"{}: applying THC companion ICRS override {:04x} -> {:04x}",
|
||||
path,
|
||||
resources.i2c.address,
|
||||
override_address
|
||||
);
|
||||
resources.i2c.address = override_address;
|
||||
}
|
||||
|
||||
Ok(resources)
|
||||
}
|
||||
|
||||
pub fn prepare_acpi_device(path: &str) -> Result<()> {
|
||||
let sta = evaluate_integer_method(path, "_STA").ok();
|
||||
if let Some(sta) = sta {
|
||||
if sta & 0x01 == 0 {
|
||||
bail!("ACPI device is not present according to _STA={sta:#x}");
|
||||
}
|
||||
}
|
||||
|
||||
let _ = evaluate_method(path, "_PS0", &[]);
|
||||
let _ = evaluate_method(path, "_INI", &[]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn recover_acpi_device(
|
||||
path: &str,
|
||||
resources: &AcpiDeviceResources,
|
||||
quirk: Option<&ProbeFailureQuirk>,
|
||||
) -> Result<()> {
|
||||
let _ = evaluate_method(path, "_PS3", &[]);
|
||||
|
||||
if let Some(quirk) = quirk {
|
||||
if !resources.gpio_io.is_empty() {
|
||||
log::warn!(
|
||||
"{}: applying GPIO probe-failure recovery quirk {} vendor={:?} product={:?} board={:?} across {} GPIO IO resources",
|
||||
path,
|
||||
quirk.name,
|
||||
quirk.system_vendor,
|
||||
quirk.product_name,
|
||||
quirk.board_name,
|
||||
resources.gpio_io.len()
|
||||
);
|
||||
} else {
|
||||
log::warn!(
|
||||
"{}: quirk {} vendor={:?} product={:?} board={:?} matched but no GPIO IO resource was exposed",
|
||||
path,
|
||||
quirk.name
|
||||
,
|
||||
quirk.system_vendor,
|
||||
quirk.product_name,
|
||||
quirk.board_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let _ = evaluate_method(path, "_PS0", &[]);
|
||||
let _ = evaluate_method(path, "_INI", &[]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn hid_descriptor_address(path: &str) -> Result<u16> {
|
||||
let args = [
|
||||
AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()),
|
||||
AmlSerdeValue::Integer(1),
|
||||
AmlSerdeValue::Integer(1),
|
||||
AmlSerdeValue::Package {
|
||||
contents: Vec::new(),
|
||||
},
|
||||
];
|
||||
|
||||
match evaluate_method(path, "_DSM", &args) {
|
||||
Ok(AmlSerdeValue::Integer(value)) => {
|
||||
return u16::try_from(value).context("_DSM descriptor address out of range")
|
||||
}
|
||||
Ok(other) => log::warn!(
|
||||
"{}._DSM returned unexpected value {:?}; retrying fallback index",
|
||||
path,
|
||||
other
|
||||
),
|
||||
Err(err) => log::warn!(
|
||||
"{}._DSM function 1 failed: {err}; retrying function 0",
|
||||
path
|
||||
),
|
||||
}
|
||||
|
||||
let fallback = [
|
||||
AmlSerdeValue::Buffer(I2C_HID_DSM_GUID.to_vec()),
|
||||
AmlSerdeValue::Integer(1),
|
||||
AmlSerdeValue::Integer(0),
|
||||
AmlSerdeValue::Package {
|
||||
contents: Vec::new(),
|
||||
},
|
||||
];
|
||||
|
||||
match evaluate_method(path, "_DSM", &fallback)? {
|
||||
AmlSerdeValue::Integer(value) => {
|
||||
u16::try_from(value).context("fallback _DSM descriptor address out of range")
|
||||
}
|
||||
other => bail!("fallback _DSM returned unexpected value {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn companion_icrs_override(path: &str) -> Result<Option<u16>> {
|
||||
let value = match evaluate_integer_method(path, "ICRS") {
|
||||
Ok(value) => value,
|
||||
Err(_) => return Ok(None),
|
||||
};
|
||||
Ok(Some(
|
||||
u16::try_from(value).context("ICRS override out of range")?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
|
||||
match evaluate_method(path, method, &[])? {
|
||||
AmlSerdeValue::Integer(value) => Ok(value),
|
||||
other => bail!(
|
||||
"{}.{} returned non-integer AML value {other:?}",
|
||||
path,
|
||||
method
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate_method(path: &str, method: &str, args: &[AmlSerdeValue]) -> Result<AmlSerdeValue> {
|
||||
let symbol_name = format!("{}.{}", normalize_device_path(path), method);
|
||||
let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
|
||||
let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
|
||||
.with_context(|| format!("failed to open {symbol_path} for ACPI evaluation"))?;
|
||||
|
||||
let serialized = ron::to_string(args)
|
||||
.with_context(|| format!("failed to serialize ACPI arguments for {symbol_name}"))?;
|
||||
let mut payload = serialized.into_bytes();
|
||||
payload.resize(payload.len() + 4096, 0);
|
||||
|
||||
let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
|
||||
.with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
|
||||
let response = std::str::from_utf8(&payload[..used])
|
||||
.with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
|
||||
ron::from_str(response)
|
||||
.with_context(|| format!("failed to decode ACPI response for {symbol_name}"))
|
||||
}
|
||||
|
||||
fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
|
||||
let path = format!("/scheme/acpi/symbols/{file_name}");
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&path)
|
||||
.with_context(|| format!("failed to open {path}"))?;
|
||||
let mut ron_text = String::new();
|
||||
file.read_to_string(&mut ron_text)
|
||||
.with_context(|| format!("failed to read {path}"))?;
|
||||
ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
|
||||
}
|
||||
|
||||
fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
|
||||
match value {
|
||||
AmlSerdeValue::String(value) => Some(value.clone()),
|
||||
AmlSerdeValue::Integer(integer) => {
|
||||
let vendor = integer & 0xFFFF;
|
||||
let device = (integer >> 16) & 0xFFFF;
|
||||
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
|
||||
let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
|
||||
let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
|
||||
let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
|
||||
let device_1 = (device >> 4) & 0xF;
|
||||
let device_2 = (device >> 0) & 0xF;
|
||||
let device_3 = (device >> 12) & 0xF;
|
||||
let device_4 = (device >> 8) & 0xF;
|
||||
|
||||
Some(format!(
|
||||
"{}{}{}{:01X}{:01X}{:01X}{:01X}",
|
||||
vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn normalize_device_path(path: &str) -> String {
|
||||
path.trim_start_matches('\\')
|
||||
.trim_matches('/')
|
||||
.replace('/', ".")
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::{Read, Write};
|
||||
|
||||
use anyhow::{bail, Context, Result};
|
||||
use i2c_interface::{I2cTransferRequest, I2cTransferResponse, I2cTransferSegment};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::acpi::I2cBinding;
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct HidDescriptor {
|
||||
pub hid_desc_length: u16,
|
||||
pub bcd_version: u16,
|
||||
pub report_desc_length: u16,
|
||||
pub report_desc_register: u16,
|
||||
pub input_register: u16,
|
||||
pub max_input_length: u16,
|
||||
pub output_register: u16,
|
||||
pub max_output_length: u16,
|
||||
pub command_register: u16,
|
||||
pub data_register: u16,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct ReportDescriptorSummary {
|
||||
pub has_keyboard_page: bool,
|
||||
pub has_pointer_page: bool,
|
||||
pub report_ids: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct I2cAdapterClient {
|
||||
binding: I2cBinding,
|
||||
}
|
||||
|
||||
impl I2cAdapterClient {
|
||||
pub fn new(binding: I2cBinding) -> Self {
|
||||
Self { binding }
|
||||
}
|
||||
pub fn transfer(&self, segments: Vec<I2cTransferSegment>) -> Result<I2cTransferResponse> {
|
||||
let request = I2cTransferRequest {
|
||||
adapter: self.binding.adapter.clone(),
|
||||
segments,
|
||||
stop: true,
|
||||
};
|
||||
|
||||
let serialized = ron::to_string(&request).context("failed to serialize I2C request")?;
|
||||
let mut handle = OpenOptions::new()
|
||||
.read(true)
|
||||
.write(true)
|
||||
.open("/scheme/i2c/transfer")
|
||||
.context("failed to open /scheme/i2c/transfer")?;
|
||||
handle
|
||||
.write_all(serialized.as_bytes())
|
||||
.context("failed to write I2C transfer request")?;
|
||||
|
||||
let mut response = String::new();
|
||||
handle
|
||||
.read_to_string(&mut response)
|
||||
.context("failed to read I2C transfer response")?;
|
||||
|
||||
let transfer: I2cTransferResponse =
|
||||
ron::from_str(&response).context("failed to decode I2C transfer response")?;
|
||||
if !transfer.ok {
|
||||
bail!(
|
||||
"I2C transfer failed: {}",
|
||||
transfer
|
||||
.error
|
||||
.unwrap_or_else(|| "unspecified transfer error".to_string())
|
||||
);
|
||||
}
|
||||
Ok(transfer)
|
||||
}
|
||||
|
||||
pub fn write_read(&self, address: u16, write_data: &[u8], read_len: usize) -> Result<Vec<u8>> {
|
||||
let response = self.transfer(vec![
|
||||
I2cTransferSegment::write(address, write_data.to_vec()),
|
||||
I2cTransferSegment::read(address, read_len),
|
||||
])?;
|
||||
|
||||
response
|
||||
.read_data
|
||||
.last()
|
||||
.cloned()
|
||||
.ok_or_else(|| anyhow::anyhow!("I2C transfer returned no readable segment payload"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch_hid_descriptor(
|
||||
adapter: &I2cAdapterClient,
|
||||
address: u16,
|
||||
hid_desc_addr: u16,
|
||||
) -> Result<HidDescriptor> {
|
||||
let prefix = adapter
|
||||
.write_read(address, &hid_desc_addr.to_le_bytes(), 2)
|
||||
.context("failed to read HID descriptor length prefix")?;
|
||||
if prefix.len() < 2 {
|
||||
bail!("short HID descriptor prefix: {} bytes", prefix.len());
|
||||
}
|
||||
|
||||
let hid_desc_length = u16::from_le_bytes([prefix[0], prefix[1]]);
|
||||
if hid_desc_length < 18 {
|
||||
bail!("invalid HID descriptor length {hid_desc_length}");
|
||||
}
|
||||
|
||||
let raw = adapter
|
||||
.write_read(
|
||||
address,
|
||||
&hid_desc_addr.to_le_bytes(),
|
||||
usize::from(hid_desc_length),
|
||||
)
|
||||
.context("failed to read full HID descriptor")?;
|
||||
parse_hid_descriptor(&raw)
|
||||
}
|
||||
|
||||
pub fn fetch_report_descriptor(
|
||||
adapter: &I2cAdapterClient,
|
||||
address: u16,
|
||||
desc: &HidDescriptor,
|
||||
) -> Result<Vec<u8>> {
|
||||
adapter
|
||||
.write_read(
|
||||
address,
|
||||
&desc.report_desc_register.to_le_bytes(),
|
||||
usize::from(desc.report_desc_length),
|
||||
)
|
||||
.context("failed to read HID report descriptor")
|
||||
}
|
||||
|
||||
pub fn stream_input_reports(
|
||||
adapter: &I2cAdapterClient,
|
||||
address: u16,
|
||||
desc: &HidDescriptor,
|
||||
report_desc: &[u8],
|
||||
sink: &mut crate::input::InputForwarder,
|
||||
) -> Result<()> {
|
||||
let summary = summarize_report_descriptor(report_desc);
|
||||
let input_len = usize::from(desc.max_input_length.max(4));
|
||||
|
||||
loop {
|
||||
let report = adapter
|
||||
.write_read(address, &desc.input_register.to_le_bytes(), input_len)
|
||||
.context("failed to fetch I2C HID input report")?;
|
||||
sink.forward_report(&summary, &report)?;
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_hid_descriptor(bytes: &[u8]) -> Result<HidDescriptor> {
|
||||
if bytes.len() < 18 {
|
||||
bail!("short HID descriptor: {} bytes", bytes.len());
|
||||
}
|
||||
|
||||
Ok(HidDescriptor {
|
||||
hid_desc_length: le16(bytes, 0)?,
|
||||
bcd_version: le16(bytes, 2)?,
|
||||
report_desc_length: le16(bytes, 4)?,
|
||||
report_desc_register: le16(bytes, 6)?,
|
||||
input_register: le16(bytes, 8)?,
|
||||
max_input_length: le16(bytes, 10)?,
|
||||
output_register: le16(bytes, 12)?,
|
||||
max_output_length: le16(bytes, 14)?,
|
||||
command_register: le16(bytes, 16)?,
|
||||
data_register: if bytes.len() >= 20 {
|
||||
le16(bytes, 18)?
|
||||
} else {
|
||||
0
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn summarize_report_descriptor(report_desc: &[u8]) -> ReportDescriptorSummary {
|
||||
let mut summary = ReportDescriptorSummary::default();
|
||||
|
||||
for window in report_desc.windows(2) {
|
||||
match window {
|
||||
[0x05, 0x01] => summary.has_pointer_page = true,
|
||||
[0x05, 0x07] => summary.has_keyboard_page = true,
|
||||
[0x85, _] => summary.report_ids = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
if !summary.has_keyboard_page && !summary.has_pointer_page {
|
||||
summary.has_pointer_page = true;
|
||||
}
|
||||
|
||||
summary
|
||||
}
|
||||
|
||||
fn le16(bytes: &[u8], offset: usize) -> Result<u16> {
|
||||
let slice = bytes
|
||||
.get(offset..offset + 2)
|
||||
.ok_or_else(|| anyhow::anyhow!("short LE16 field at offset {offset}"))?;
|
||||
Ok(u16::from_le_bytes([slice[0], slice[1]]))
|
||||
}
|
||||
@@ -0,0 +1,175 @@
|
||||
use std::collections::BTreeSet;
|
||||
|
||||
use anyhow::Result;
|
||||
use inputd::ProducerHandle;
|
||||
use orbclient::{
|
||||
ButtonEvent, KeyEvent, MouseRelativeEvent, ScrollEvent, K_ALT, K_ALT_GR, K_BKSP, K_BRACE_CLOSE,
|
||||
K_BRACE_OPEN, K_CAPS, K_COMMA, K_ENTER, K_EQUALS, K_ESC, K_LEFT_CTRL, K_LEFT_SHIFT,
|
||||
K_LEFT_SUPER, K_MINUS, K_PERIOD, K_QUOTE, K_RIGHT_CTRL, K_RIGHT_SHIFT, K_RIGHT_SUPER,
|
||||
K_SEMICOLON, K_SLASH, K_SPACE, K_TAB, K_TICK,
|
||||
};
|
||||
|
||||
use crate::hid::ReportDescriptorSummary;
|
||||
|
||||
pub struct InputForwarder {
|
||||
producer: ProducerHandle,
|
||||
keyboard_state: BTreeSet<u8>,
|
||||
last_buttons: u8,
|
||||
}
|
||||
|
||||
impl InputForwarder {
|
||||
pub fn new() -> Result<Self> {
|
||||
Ok(Self {
|
||||
producer: ProducerHandle::new()?,
|
||||
keyboard_state: BTreeSet::new(),
|
||||
last_buttons: 0,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn forward_report(
|
||||
&mut self,
|
||||
summary: &ReportDescriptorSummary,
|
||||
report: &[u8],
|
||||
) -> Result<()> {
|
||||
if report.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if summary.has_keyboard_page && report.len() >= 8 {
|
||||
self.forward_boot_keyboard(report)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if summary.has_pointer_page && report.len() >= 3 {
|
||||
self.forward_boot_pointer(report)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn forward_boot_keyboard(&mut self, report: &[u8]) -> Result<()> {
|
||||
let modifiers = report[0];
|
||||
for (bit, scancode) in [
|
||||
(0_u8, K_LEFT_CTRL),
|
||||
(1, K_LEFT_SHIFT),
|
||||
(2, K_ALT),
|
||||
(3, K_LEFT_SUPER),
|
||||
(4, K_RIGHT_CTRL),
|
||||
(5, K_RIGHT_SHIFT),
|
||||
(6, K_ALT_GR),
|
||||
(7, K_RIGHT_SUPER),
|
||||
] {
|
||||
self.producer.write_event(
|
||||
KeyEvent {
|
||||
character: '\0',
|
||||
scancode,
|
||||
pressed: modifiers & (1 << bit) != 0,
|
||||
}
|
||||
.to_event(),
|
||||
)?;
|
||||
}
|
||||
|
||||
let current = report[2..8]
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|code| *code != 0)
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
for code in current.difference(&self.keyboard_state) {
|
||||
if let Some(scancode) = map_boot_keyboard_usage(*code) {
|
||||
self.producer.write_event(
|
||||
KeyEvent {
|
||||
character: '\0',
|
||||
scancode,
|
||||
pressed: true,
|
||||
}
|
||||
.to_event(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
for code in self.keyboard_state.difference(¤t) {
|
||||
if let Some(scancode) = map_boot_keyboard_usage(*code) {
|
||||
self.producer.write_event(
|
||||
KeyEvent {
|
||||
character: '\0',
|
||||
scancode,
|
||||
pressed: false,
|
||||
}
|
||||
.to_event(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
self.keyboard_state = current;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn forward_boot_pointer(&mut self, report: &[u8]) -> Result<()> {
|
||||
let dx = i8::from_ne_bytes([report[1]]) as i32;
|
||||
let dy = i8::from_ne_bytes([report[2]]) as i32;
|
||||
if dx != 0 || dy != 0 {
|
||||
self.producer
|
||||
.write_event(MouseRelativeEvent { dx, dy }.to_event())?;
|
||||
}
|
||||
|
||||
if let Some(scroll) = report.get(3).copied() {
|
||||
let scroll = i8::from_ne_bytes([scroll]) as i32;
|
||||
if scroll != 0 {
|
||||
self.producer
|
||||
.write_event(ScrollEvent { x: 0, y: scroll }.to_event())?;
|
||||
}
|
||||
}
|
||||
|
||||
let buttons = report[0] & 0x07;
|
||||
for index in 0..3 {
|
||||
let mask = 1 << index;
|
||||
if (buttons & mask) != (self.last_buttons & mask) {
|
||||
self.producer.write_event(
|
||||
ButtonEvent {
|
||||
left: buttons & 0x01 != 0,
|
||||
middle: buttons & 0x04 != 0,
|
||||
right: buttons & 0x02 != 0,
|
||||
}
|
||||
.to_event(),
|
||||
)?;
|
||||
break;
|
||||
}
|
||||
}
|
||||
self.last_buttons = buttons;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn map_boot_keyboard_usage(usage: u8) -> Option<u8> {
|
||||
Some(match usage {
|
||||
0x04..=0x1D => b'a' + (usage - 0x04),
|
||||
0x1E => b'1',
|
||||
0x1F => b'2',
|
||||
0x20 => b'3',
|
||||
0x21 => b'4',
|
||||
0x22 => b'5',
|
||||
0x23 => b'6',
|
||||
0x24 => b'7',
|
||||
0x25 => b'8',
|
||||
0x26 => b'9',
|
||||
0x27 => b'0',
|
||||
0x28 => K_ENTER,
|
||||
0x29 => K_ESC,
|
||||
0x2A => K_BKSP,
|
||||
0x2B => K_TAB,
|
||||
0x2C => K_SPACE,
|
||||
0x2D => K_MINUS,
|
||||
0x2E => K_EQUALS,
|
||||
0x2F => K_BRACE_OPEN,
|
||||
0x30 => K_BRACE_CLOSE,
|
||||
0x33 => K_SEMICOLON,
|
||||
0x34 => K_QUOTE,
|
||||
0x35 => K_TICK,
|
||||
0x36 => K_COMMA,
|
||||
0x37 => K_PERIOD,
|
||||
0x38 => K_SLASH,
|
||||
0x39 => K_CAPS,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
use std::process;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
|
||||
mod acpi;
|
||||
mod hid;
|
||||
mod input;
|
||||
mod quirks;
|
||||
|
||||
use acpi::{
|
||||
hid_descriptor_address, prepare_acpi_device, read_decoded_resources, recover_acpi_device,
|
||||
scan_acpi_i2c_hid_devices,
|
||||
};
|
||||
use hid::{fetch_hid_descriptor, fetch_report_descriptor, stream_input_reports, I2cAdapterClient};
|
||||
use input::InputForwarder;
|
||||
use quirks::match_probe_failure_quirk;
|
||||
|
||||
fn main() {
|
||||
daemon::Daemon::new(daemon);
|
||||
}
|
||||
|
||||
fn daemon(daemon: daemon::Daemon) -> ! {
|
||||
common::setup_logging(
|
||||
"input",
|
||||
"i2c-hid",
|
||||
"i2c-hidd",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
if let Err(err) = run(daemon) {
|
||||
log::error!("RB_I2C_HIDD_BLOCKER stage=startup error={err:#}");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
fn run(daemon: daemon::Daemon) -> Result<()> {
|
||||
log::info!("RB_I2C_HIDD_SCHEMA version=1");
|
||||
|
||||
let devices = scan_acpi_i2c_hid_devices().context("failed to scan ACPI I2C HID devices")?;
|
||||
if devices.is_empty() {
|
||||
log::warn!("RB_I2C_HIDD_BLOCKER stage=scan error=no PNP0C50/ACPI0C50 devices found");
|
||||
}
|
||||
|
||||
let mut workers = Vec::new();
|
||||
for device in devices {
|
||||
log::info!("RB_I2C_HIDD_SNAPSHOT device={device}");
|
||||
workers.push(thread::spawn(move || {
|
||||
if let Err(err) = bind_device(&device) {
|
||||
log::error!("RB_I2C_HIDD_BLOCKER device={} error={:#}", device, err);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
daemon.ready();
|
||||
|
||||
if workers.is_empty() {
|
||||
loop {
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
|
||||
for worker in workers {
|
||||
let _ = worker.join();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn bind_device(device_path: &str) -> Result<()> {
|
||||
prepare_acpi_device(device_path)
|
||||
.with_context(|| format!("failed to prepare ACPI device {device_path}"))?;
|
||||
|
||||
let resources = read_decoded_resources(device_path)
|
||||
.with_context(|| format!("failed to decode _CRS for {device_path}"))?;
|
||||
log::info!(
|
||||
"RB_I2C_HIDD_SNAPSHOT device={} adapter={} addr={:04x} irq={:?} gpio_int={} gpio_io={}",
|
||||
device_path,
|
||||
resources.i2c.adapter,
|
||||
resources.i2c.address,
|
||||
resources.irq,
|
||||
resources.gpio_int.len(),
|
||||
resources.gpio_io.len()
|
||||
);
|
||||
|
||||
let hid_desc_addr = hid_descriptor_address(device_path)
|
||||
.with_context(|| format!("failed to evaluate _DSM for {device_path}"))?;
|
||||
let adapter = I2cAdapterClient::new(resources.i2c.clone());
|
||||
let hid_desc = fetch_hid_descriptor(&adapter, resources.i2c.address, hid_desc_addr)
|
||||
.with_context(|| format!("failed to fetch HID descriptor for {device_path}"))?;
|
||||
let report_desc = fetch_report_descriptor(&adapter, resources.i2c.address, &hid_desc)
|
||||
.with_context(|| format!("failed to fetch report descriptor for {device_path}"))?;
|
||||
let mut forwarder = InputForwarder::new().context("failed to connect to inputd producer")?;
|
||||
|
||||
match stream_input_reports(
|
||||
&adapter,
|
||||
resources.i2c.address,
|
||||
&hid_desc,
|
||||
&report_desc,
|
||||
&mut forwarder,
|
||||
) {
|
||||
Ok(()) => Ok(()),
|
||||
Err(err) => {
|
||||
let quirk =
|
||||
match_probe_failure_quirk().context("failed to evaluate DMI recovery quirks")?;
|
||||
recover_acpi_device(device_path, &resources, quirk.as_ref())
|
||||
.with_context(|| format!("failed ACPI recovery for {device_path}"))?;
|
||||
Err(err).with_context(|| format!("streaming input reports failed for {device_path}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
use std::fs;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ProbeFailureQuirk {
|
||||
pub name: String,
|
||||
pub system_vendor: Option<String>,
|
||||
pub product_name: Option<String>,
|
||||
pub board_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
struct ProbeFailureQuirkFile {
|
||||
quirks: Vec<ProbeFailureQuirkEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize)]
|
||||
struct ProbeFailureQuirkEntry {
|
||||
name: String,
|
||||
system_vendor: Option<String>,
|
||||
product_name: Option<String>,
|
||||
board_name: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct DmiSnapshot {
|
||||
system_vendor: String,
|
||||
product_name: String,
|
||||
board_name: String,
|
||||
}
|
||||
|
||||
pub fn match_probe_failure_quirk() -> Result<Option<ProbeFailureQuirk>> {
|
||||
let snapshot = read_dmi_snapshot()?;
|
||||
for entry in load_quirks()? {
|
||||
if field_matches(&entry.system_vendor, &snapshot.system_vendor)
|
||||
&& field_matches(&entry.product_name, &snapshot.product_name)
|
||||
&& field_matches(&entry.board_name, &snapshot.board_name)
|
||||
{
|
||||
return Ok(Some(ProbeFailureQuirk {
|
||||
name: entry.name,
|
||||
system_vendor: entry.system_vendor,
|
||||
product_name: entry.product_name,
|
||||
board_name: entry.board_name,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn load_quirks() -> Result<Vec<ProbeFailureQuirkEntry>> {
|
||||
let path = "/etc/i2c-hidd-quirks.ron";
|
||||
let text = match fs::read_to_string(path) {
|
||||
Ok(text) => text,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(Vec::new()),
|
||||
Err(err) => return Err(err).with_context(|| format!("failed to read {path}")),
|
||||
};
|
||||
|
||||
let file: ProbeFailureQuirkFile =
|
||||
ron::from_str(&text).with_context(|| format!("failed to decode {path}"))?;
|
||||
Ok(file.quirks)
|
||||
}
|
||||
|
||||
fn read_dmi_snapshot() -> Result<DmiSnapshot> {
|
||||
Ok(DmiSnapshot {
|
||||
system_vendor: read_dmi_field("system_vendor")?,
|
||||
product_name: read_dmi_field("product_name")?,
|
||||
board_name: read_dmi_field("board_name")?,
|
||||
})
|
||||
}
|
||||
|
||||
fn read_dmi_field(field: &str) -> Result<String> {
|
||||
let path = format!("/scheme/acpi/dmi/{field}");
|
||||
match fs::read_to_string(&path) {
|
||||
Ok(value) => Ok(value.trim().to_string()),
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(String::new()),
|
||||
Err(err) => Err(err).with_context(|| format!("failed to read {path}")),
|
||||
}
|
||||
}
|
||||
|
||||
fn field_matches(expected: &Option<String>, actual: &str) -> bool {
|
||||
expected
|
||||
.as_deref()
|
||||
.map(|expected| actual.eq_ignore_ascii_case(expected))
|
||||
.unwrap_or(true)
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "intel-thc-hidd"
|
||||
description = "Intel THC QuickI2C HID transport daemon"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
log.workspace = true
|
||||
pci_types = "0.10.1"
|
||||
redox_syscall = { workspace = true, features = ["std"] }
|
||||
libredox.workspace = true
|
||||
redox-scheme.workspace = true
|
||||
ron.workspace = true
|
||||
serde.workspace = true
|
||||
|
||||
acpi-resource = { path = "../../acpi-resource" }
|
||||
amlserde = { path = "../../amlserde" }
|
||||
common = { path = "../../common" }
|
||||
daemon = { path = "../../../daemon" }
|
||||
i2c-interface = { path = "../../i2c/i2c-interface" }
|
||||
pcid = { path = "../../pcid" }
|
||||
scheme-utils = { path = "../../../scheme-utils" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,260 @@
|
||||
use std::collections::BTreeSet;
|
||||
use std::fs::{self, OpenOptions};
|
||||
use std::io::Read;
|
||||
use std::process;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use acpi_resource::ResourceDescriptor;
|
||||
use amlserde::{AmlSerde, AmlSerdeValue};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use libredox::flag::{O_CLOEXEC, O_RDWR};
|
||||
use pcid_interface::PciFunctionHandle;
|
||||
|
||||
mod quicki2c;
|
||||
mod thc;
|
||||
|
||||
use quicki2c::QuickI2cTransport;
|
||||
use thc::{ThcController, SUPPORTED_PCI_IDS};
|
||||
|
||||
fn main() {
|
||||
pcid_interface::pci_daemon(daemon);
|
||||
}
|
||||
|
||||
fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! {
|
||||
common::setup_logging(
|
||||
"input",
|
||||
"intel-thc",
|
||||
"intel-thc-hidd",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
|
||||
if let Err(err) = run(daemon, &mut pcid_handle) {
|
||||
log::error!("RB_THC_HIDD_FATAL error={err:#}");
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
process::exit(0);
|
||||
}
|
||||
|
||||
fn run(daemon: daemon::Daemon, pcid_handle: &mut PciFunctionHandle) -> Result<()> {
|
||||
log::info!("RB_THC_HIDD_SCHEMA version=1");
|
||||
|
||||
let pci_config = pcid_handle.config();
|
||||
let id = (
|
||||
pci_config.func.full_device_id.vendor_id,
|
||||
pci_config.func.full_device_id.device_id,
|
||||
);
|
||||
if !SUPPORTED_PCI_IDS.contains(&id) {
|
||||
bail!("unsupported Intel THC PCI device {:04x}:{:04x}", id.0, id.1);
|
||||
}
|
||||
|
||||
pcid_handle.enable_device();
|
||||
let bar = unsafe { pcid_handle.map_bar(0) };
|
||||
let controller = ThcController::new(bar.ptr.as_ptr(), bar.bar_size)
|
||||
.context("failed to create THC controller")?;
|
||||
|
||||
let companion = resolve_acpi_companion(&pci_config.func.addr)
|
||||
.context("failed to resolve ACPI companion for THC device")?;
|
||||
let override_address = companion
|
||||
.as_deref()
|
||||
.map(companion_slave_address_override)
|
||||
.transpose()
|
||||
.context("failed to evaluate THC slave-address override")?
|
||||
.flatten();
|
||||
let hid_devices = scan_bound_i2c_hid_devices(companion.as_deref())
|
||||
.context("failed to scan PNP0C50 devices for THC controller")?;
|
||||
|
||||
let effective_address = override_address.unwrap_or(0x0015);
|
||||
let transport = QuickI2cTransport::new(controller, effective_address);
|
||||
transport.prime_controller();
|
||||
transport.emulate_transfer(&[]);
|
||||
log::debug!("RB_THC_HIDD status={:#x}", transport.status());
|
||||
|
||||
match transport.register_with_i2cd(companion.as_deref(), override_address) {
|
||||
Ok(()) => {}
|
||||
Err(err) => {
|
||||
log::warn!("RB_THC_HIDD registration error={err:#}");
|
||||
}
|
||||
}
|
||||
|
||||
log::info!(
|
||||
"RB_THC_HIDD pci={} companion={:?} override={:?} hid_devices={}",
|
||||
pci_config.func.name(),
|
||||
companion,
|
||||
override_address,
|
||||
hid_devices.len()
|
||||
);
|
||||
|
||||
daemon.ready();
|
||||
|
||||
loop {
|
||||
thread::sleep(Duration::from_secs(5));
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_acpi_companion(addr: &pci_types::PciAddress) -> Result<Option<String>> {
|
||||
let entries =
|
||||
fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
|
||||
let expected_adr = (u64::from(addr.device()) << 16) | u64::from(addr.function());
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry.context("failed to enumerate ACPI symbol entry")?;
|
||||
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
|
||||
continue;
|
||||
};
|
||||
if !file_name.ends_with("._ADR") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let symbol = read_aml_symbol(&file_name)?;
|
||||
if !matches!(symbol.value, AmlSerdeValue::Integer(value) if value == expected_adr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let device = symbol
|
||||
.name
|
||||
.strip_suffix("._ADR")
|
||||
.unwrap_or(&symbol.name)
|
||||
.trim_start_matches('\\')
|
||||
.replace('/', ".");
|
||||
return Ok(Some(device));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
fn companion_slave_address_override(path: &str) -> Result<Option<u16>> {
|
||||
let icrs = evaluate_integer_method(path, "ICRS").ok();
|
||||
let isub = evaluate_integer_method(path, "ISUB").ok();
|
||||
Ok(icrs
|
||||
.or(isub)
|
||||
.map(|value| u16::try_from(value))
|
||||
.transpose()
|
||||
.context("THC ACPI override out of range")?)
|
||||
}
|
||||
|
||||
fn scan_bound_i2c_hid_devices(companion: Option<&str>) -> Result<Vec<String>> {
|
||||
let entries =
|
||||
fs::read_dir("/scheme/acpi/symbols").context("failed to read /scheme/acpi/symbols")?;
|
||||
let mut devices = BTreeSet::new();
|
||||
|
||||
for entry in entries {
|
||||
let entry = entry.context("failed to enumerate ACPI HID entry")?;
|
||||
let Some(file_name) = entry.file_name().to_str().map(str::to_owned) else {
|
||||
continue;
|
||||
};
|
||||
if !file_name.ends_with("._HID") && !file_name.ends_with("._CID") {
|
||||
continue;
|
||||
}
|
||||
|
||||
let symbol = read_aml_symbol(&file_name)?;
|
||||
let is_hid = matches!(
|
||||
decode_hardware_id(&symbol.value).as_deref(),
|
||||
Some("PNP0C50" | "ACPI0C50")
|
||||
);
|
||||
if !is_hid {
|
||||
continue;
|
||||
}
|
||||
|
||||
let device = symbol
|
||||
.name
|
||||
.strip_suffix("._HID")
|
||||
.or_else(|| symbol.name.strip_suffix("._CID"))
|
||||
.unwrap_or(&symbol.name)
|
||||
.trim_start_matches('\\')
|
||||
.replace('/', ".");
|
||||
if let Some(companion) = companion {
|
||||
if !is_bound_to_companion(&device, companion)? {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
devices.insert(device);
|
||||
}
|
||||
|
||||
Ok(devices.into_iter().collect())
|
||||
}
|
||||
|
||||
fn is_bound_to_companion(device: &str, companion: &str) -> Result<bool> {
|
||||
let resource_path = format!("/scheme/acpi/resources/{device}");
|
||||
let serialized = match fs::read_to_string(&resource_path) {
|
||||
Ok(serialized) => serialized,
|
||||
Err(err) if err.kind() == std::io::ErrorKind::NotFound => return Ok(false),
|
||||
Err(err) => return Err(err).with_context(|| format!("failed to read {resource_path}")),
|
||||
};
|
||||
|
||||
let resources: Vec<ResourceDescriptor> =
|
||||
ron::from_str(&serialized).with_context(|| format!("failed to decode {resource_path}"))?;
|
||||
Ok(resources.into_iter().any(|resource| match resource {
|
||||
ResourceDescriptor::I2cSerialBus(bus) => bus
|
||||
.resource_source
|
||||
.as_ref()
|
||||
.map(|source| source.source == companion)
|
||||
.unwrap_or(false),
|
||||
_ => false,
|
||||
}))
|
||||
}
|
||||
|
||||
fn evaluate_integer_method(path: &str, method: &str) -> Result<u64> {
|
||||
let symbol_name = format!("{}.{}", normalize_device_path(path), method);
|
||||
let symbol_path = format!("/scheme/acpi/symbols/{symbol_name}");
|
||||
let fd = libredox::Fd::open(&symbol_path, O_RDWR | O_CLOEXEC, 0)
|
||||
.with_context(|| format!("failed to open {symbol_path}"))?;
|
||||
|
||||
let mut payload = ron::to_string(&Vec::<AmlSerdeValue>::new())
|
||||
.context("failed to serialize ACPI call arguments")?
|
||||
.into_bytes();
|
||||
payload.resize(payload.len() + 2048, 0);
|
||||
let used = libredox::call::call_ro(fd.raw(), &mut payload, syscall::CallFlags::empty(), &[])
|
||||
.with_context(|| format!("ACPI evaluation failed for {symbol_name}"))?;
|
||||
let response = std::str::from_utf8(&payload[..used])
|
||||
.with_context(|| format!("invalid UTF-8 ACPI response for {symbol_name}"))?;
|
||||
match ron::from_str::<AmlSerdeValue>(response)
|
||||
.with_context(|| format!("failed to decode ACPI response for {symbol_name}"))?
|
||||
{
|
||||
AmlSerdeValue::Integer(value) => Ok(value),
|
||||
other => bail!("{}.{} returned non-integer value {other:?}", path, method),
|
||||
}
|
||||
}
|
||||
|
||||
fn read_aml_symbol(file_name: &str) -> Result<AmlSerde> {
|
||||
let path = format!("/scheme/acpi/symbols/{file_name}");
|
||||
let mut file = OpenOptions::new()
|
||||
.read(true)
|
||||
.open(&path)
|
||||
.with_context(|| format!("failed to open {path}"))?;
|
||||
let mut ron_text = String::new();
|
||||
file.read_to_string(&mut ron_text)
|
||||
.with_context(|| format!("failed to read {path}"))?;
|
||||
ron::from_str(&ron_text).with_context(|| format!("failed to decode {path}"))
|
||||
}
|
||||
|
||||
fn decode_hardware_id(value: &AmlSerdeValue) -> Option<String> {
|
||||
match value {
|
||||
AmlSerdeValue::String(value) => Some(value.clone()),
|
||||
AmlSerdeValue::Integer(integer) => {
|
||||
let vendor = integer & 0xFFFF;
|
||||
let device = (integer >> 16) & 0xFFFF;
|
||||
let vendor_rev = ((vendor & 0xFF) << 8) | (vendor >> 8);
|
||||
let vendor_1 = (((vendor_rev >> 10) & 0x1f) as u8 + 64) as char;
|
||||
let vendor_2 = (((vendor_rev >> 5) & 0x1f) as u8 + 64) as char;
|
||||
let vendor_3 = (((vendor_rev >> 0) & 0x1f) as u8 + 64) as char;
|
||||
let device_1 = (device >> 4) & 0xF;
|
||||
let device_2 = (device >> 0) & 0xF;
|
||||
let device_3 = (device >> 12) & 0xF;
|
||||
let device_4 = (device >> 8) & 0xF;
|
||||
Some(format!(
|
||||
"{}{}{}{:01X}{:01X}{:01X}{:01X}",
|
||||
vendor_1, vendor_2, vendor_3, device_1, device_2, device_3, device_4
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize_device_path(path: &str) -> String {
|
||||
path.trim_start_matches('\\')
|
||||
.trim_matches('/')
|
||||
.replace('/', ".")
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use i2c_interface::{I2cAdapterRegistration, I2cTransferSegment};
|
||||
|
||||
use crate::thc::ThcController;
|
||||
|
||||
const QUICKI2C_OPCODE_WRITE: u32 = 0x1;
|
||||
const QUICKI2C_OPCODE_READ: u32 = 0x2;
|
||||
|
||||
pub struct QuickI2cTransport {
|
||||
controller: ThcController,
|
||||
slave_address: u16,
|
||||
}
|
||||
|
||||
impl QuickI2cTransport {
|
||||
pub fn new(controller: ThcController, slave_address: u16) -> Self {
|
||||
Self {
|
||||
controller,
|
||||
slave_address,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prime_controller(&self) {
|
||||
self.controller.initialize_quicki2c_mode();
|
||||
}
|
||||
|
||||
pub fn emulate_transfer(&self, segments: &[I2cTransferSegment]) {
|
||||
for segment in segments {
|
||||
match &segment.op {
|
||||
i2c_interface::I2cTransferOp::Write(data) => {
|
||||
self.controller.program_subip_transaction(
|
||||
QUICKI2C_OPCODE_WRITE,
|
||||
segment.address,
|
||||
data.len(),
|
||||
);
|
||||
for (index, chunk) in data.chunks(4).enumerate() {
|
||||
let mut word = [0_u8; 4];
|
||||
word[..chunk.len()].copy_from_slice(chunk);
|
||||
self.controller
|
||||
.write_subip_data(index * 4, u32::from_le_bytes(word));
|
||||
}
|
||||
}
|
||||
i2c_interface::I2cTransferOp::Read(len) => {
|
||||
self.controller.program_subip_transaction(
|
||||
QUICKI2C_OPCODE_READ,
|
||||
segment.address,
|
||||
*len,
|
||||
);
|
||||
let _ = self.controller.read_subip_data(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn status(&self) -> u32 {
|
||||
self.controller.status()
|
||||
}
|
||||
|
||||
pub fn register_with_i2cd(
|
||||
&self,
|
||||
acpi_companion: Option<&str>,
|
||||
override_address: Option<u16>,
|
||||
) -> Result<()> {
|
||||
let registration = I2cAdapterRegistration {
|
||||
name: "intel-thc-quicki2c".to_string(),
|
||||
description: format!(
|
||||
"Intel THC QuickI2C adapter for slave {:04x}",
|
||||
self.slave_address
|
||||
),
|
||||
acpi_companion: acpi_companion.map(str::to_owned),
|
||||
slave_address_override: override_address,
|
||||
};
|
||||
let payload =
|
||||
ron::to_string(®istration).context("failed to serialize i2cd registration")?;
|
||||
|
||||
let mut file = OpenOptions::new()
|
||||
.write(true)
|
||||
.open("/scheme/i2c/register")
|
||||
.context("failed to open /scheme/i2c/register")?;
|
||||
file.write_all(payload.as_bytes())
|
||||
.context("failed to write i2cd adapter registration")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
use std::ptr::NonNull;
|
||||
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
pub const SUPPORTED_PCI_IDS: &[(u16, u16)] = &[
|
||||
(0x8086, 0x7eb8),
|
||||
(0x8086, 0x7eb9),
|
||||
(0x8086, 0x7ebd),
|
||||
(0x8086, 0x7ebe),
|
||||
(0x8086, 0xa8b8),
|
||||
(0x8086, 0xa8b9),
|
||||
];
|
||||
|
||||
pub const REG_CONTROL: usize = 0x0000;
|
||||
pub const REG_STATUS: usize = 0x0004;
|
||||
pub const REG_MODE: usize = 0x0010;
|
||||
pub const REG_SUBIP_OPCODE: usize = 0x0800;
|
||||
pub const REG_SUBIP_ADDRESS: usize = 0x0804;
|
||||
pub const REG_SUBIP_LENGTH: usize = 0x0808;
|
||||
pub const REG_SUBIP_DOORBELL: usize = 0x080C;
|
||||
pub const REG_SUBIP_DATA: usize = 0x0810;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct ThcController {
|
||||
base: NonNull<u8>,
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl ThcController {
|
||||
pub fn new(base: *mut u8, len: usize) -> Result<Self> {
|
||||
let Some(base) = NonNull::new(base) else {
|
||||
bail!("THC BAR mapping returned null base pointer");
|
||||
};
|
||||
Ok(Self { base, len })
|
||||
}
|
||||
|
||||
pub fn initialize_quicki2c_mode(&self) {
|
||||
self.write32(REG_MODE, 0x1);
|
||||
self.write32(REG_CONTROL, 0x1);
|
||||
}
|
||||
|
||||
pub fn status(&self) -> u32 {
|
||||
self.read32(REG_STATUS)
|
||||
}
|
||||
|
||||
pub fn program_subip_transaction(&self, opcode: u32, address: u16, len: usize) {
|
||||
self.write32(REG_SUBIP_OPCODE, opcode);
|
||||
self.write32(REG_SUBIP_ADDRESS, u32::from(address));
|
||||
self.write32(REG_SUBIP_LENGTH, len as u32);
|
||||
self.write32(REG_SUBIP_DOORBELL, 1);
|
||||
}
|
||||
|
||||
pub fn write_subip_data(&self, offset: usize, value: u32) {
|
||||
self.write32(REG_SUBIP_DATA + offset, value);
|
||||
}
|
||||
|
||||
pub fn read_subip_data(&self, offset: usize) -> u32 {
|
||||
self.read32(REG_SUBIP_DATA + offset)
|
||||
}
|
||||
|
||||
fn read32(&self, offset: usize) -> u32 {
|
||||
if offset + 4 > self.len {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let ptr = unsafe { self.base.as_ptr().add(offset).cast::<u32>() };
|
||||
unsafe { ptr.read_volatile() }
|
||||
}
|
||||
|
||||
fn write32(&self, offset: usize, value: u32) {
|
||||
if offset + 4 > self.len {
|
||||
return;
|
||||
}
|
||||
|
||||
let ptr = unsafe { self.base.as_ptr().add(offset).cast::<u32>() };
|
||||
unsafe { ptr.write_volatile(value) };
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user