497 lines
17 KiB
Rust
497 lines
17 KiB
Rust
use std::collections::BTreeMap;
|
|
use std::process;
|
|
|
|
use anyhow::{Context, Result};
|
|
use redox_scheme::scheme::SchemeSync;
|
|
use redox_scheme::{CallerCtx, OpenResult, Socket};
|
|
use scheme_utils::{Blocking, HandleMap};
|
|
use serde::{Deserialize, Serialize};
|
|
use syscall::schemev2::NewFdFlags;
|
|
use syscall::{Error as SysError, EACCES, EBADF, EINVAL, ENOENT};
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct GpioControllerInfo {
|
|
pub id: u32,
|
|
pub name: String,
|
|
pub pin_count: usize,
|
|
pub supports_interrupt: bool,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum GpioControlRequest {
|
|
RegisterController { info: GpioControllerInfo },
|
|
ReadPin { controller_id: u32, pin: u32 },
|
|
WritePin { controller_id: u32, pin: u32, value: bool },
|
|
ConfigurePin { controller_id: u32, pin: u32, config: PinConfig },
|
|
ListControllers,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub struct PinConfig {
|
|
pub direction: PinDirection,
|
|
pub pull: PullMode,
|
|
pub interrupt_mode: Option<InterruptMode>,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum PinDirection {
|
|
Input,
|
|
Output,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum PullMode {
|
|
None,
|
|
Up,
|
|
Down,
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
pub enum InterruptMode {
|
|
EdgeRising,
|
|
EdgeFalling,
|
|
EdgeBoth,
|
|
LevelHigh,
|
|
LevelLow,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
enum GpioControlResponse {
|
|
ControllerRegistered { id: u32 },
|
|
Controllers(Vec<GpioControllerInfo>),
|
|
Controller(GpioControllerInfo),
|
|
PinValue(bool),
|
|
Ack,
|
|
Error(String),
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
enum PinOpKind {
|
|
Read,
|
|
Write,
|
|
Configure,
|
|
}
|
|
|
|
enum Handle {
|
|
SchemeRoot,
|
|
Register { pending: Vec<u8> },
|
|
Provider { controller_id: u32, pending: Vec<u8> },
|
|
ControllersDir { pending: Vec<u8> },
|
|
ControllerDetail { id: u32, pending: Vec<u8> },
|
|
PinOp { kind: PinOpKind, pending: Vec<u8> },
|
|
}
|
|
|
|
struct ControllerEntry {
|
|
info: GpioControllerInfo,
|
|
provider_handle: usize,
|
|
}
|
|
|
|
struct GpioDaemon {
|
|
handles: HandleMap<Handle>,
|
|
controllers: BTreeMap<u32, ControllerEntry>,
|
|
next_id: u32,
|
|
}
|
|
|
|
impl GpioDaemon {
|
|
fn new() -> Self {
|
|
Self {
|
|
handles: HandleMap::new(),
|
|
controllers: BTreeMap::new(),
|
|
next_id: 0,
|
|
}
|
|
}
|
|
|
|
fn controller_list(&self) -> Vec<GpioControllerInfo> {
|
|
self.controllers
|
|
.values()
|
|
.map(|entry| entry.info.clone())
|
|
.collect()
|
|
}
|
|
|
|
fn serialize_response(response: &GpioControlResponse) -> syscall::Result<Vec<u8>> {
|
|
ron::ser::to_string(response)
|
|
.map(|text| text.into_bytes())
|
|
.map_err(|err| {
|
|
log::error!("gpiod: failed to serialize control response: {err}");
|
|
SysError::new(EINVAL)
|
|
})
|
|
}
|
|
|
|
fn deserialize_request(buf: &[u8]) -> syscall::Result<GpioControlRequest> {
|
|
let text = std::str::from_utf8(buf).map_err(|err| {
|
|
log::warn!("gpiod: invalid UTF-8 request payload: {err}");
|
|
SysError::new(EINVAL)
|
|
})?;
|
|
|
|
ron::from_str(text).map_err(|err| {
|
|
log::warn!("gpiod: failed to decode control request: {err}");
|
|
SysError::new(EINVAL)
|
|
})
|
|
}
|
|
|
|
fn set_pending_response(handle: &mut Handle, response: GpioControlResponse) -> syscall::Result<()> {
|
|
let pending = Self::serialize_response(&response)?;
|
|
Self::set_pending_bytes(handle, pending)
|
|
}
|
|
|
|
fn set_pending_bytes(handle: &mut Handle, pending: Vec<u8>) -> syscall::Result<()> {
|
|
match handle {
|
|
Handle::Register { pending: slot }
|
|
| Handle::Provider { pending: slot, .. }
|
|
| Handle::ControllersDir { pending: slot }
|
|
| Handle::ControllerDetail { pending: slot, .. }
|
|
| Handle::PinOp { pending: slot, .. } => {
|
|
*slot = pending;
|
|
Ok(())
|
|
}
|
|
Handle::SchemeRoot => Err(SysError::new(EBADF)),
|
|
}
|
|
}
|
|
|
|
fn copy_pending(handle: &mut Handle, buf: &mut [u8], offset: u64) -> syscall::Result<usize> {
|
|
let pending = match handle {
|
|
Handle::Register { pending }
|
|
| Handle::Provider { pending, .. }
|
|
| Handle::ControllersDir { pending }
|
|
| Handle::ControllerDetail { pending, .. }
|
|
| Handle::PinOp { pending, .. } => pending,
|
|
Handle::SchemeRoot => return Err(SysError::new(EBADF)),
|
|
};
|
|
|
|
let offset = usize::try_from(offset).map_err(|_| SysError::new(EINVAL))?;
|
|
if offset >= pending.len() {
|
|
return Ok(0);
|
|
}
|
|
|
|
let copy_len = buf.len().min(pending.len() - offset);
|
|
buf[..copy_len].copy_from_slice(&pending[offset..offset + copy_len]);
|
|
Ok(copy_len)
|
|
}
|
|
|
|
fn validate_pin_target(
|
|
&self,
|
|
controller_id: u32,
|
|
pin: u32,
|
|
) -> std::result::Result<GpioControllerInfo, String> {
|
|
let entry = self
|
|
.controllers
|
|
.get(&controller_id)
|
|
.ok_or_else(|| format!("unknown controller {controller_id}"))?;
|
|
if usize::try_from(pin)
|
|
.ok()
|
|
.filter(|pin| *pin < entry.info.pin_count)
|
|
.is_none()
|
|
{
|
|
return Err(format!(
|
|
"pin {pin} is out of range for controller {} (pin_count={})",
|
|
entry.info.name, entry.info.pin_count
|
|
));
|
|
}
|
|
Ok(entry.info.clone())
|
|
}
|
|
}
|
|
|
|
impl SchemeSync for GpioDaemon {
|
|
fn scheme_root(&mut self) -> syscall::Result<usize> {
|
|
Ok(self.handles.insert(Handle::SchemeRoot))
|
|
}
|
|
|
|
fn openat(
|
|
&mut self,
|
|
dirfd: usize,
|
|
path: &str,
|
|
_flags: usize,
|
|
_fcntl_flags: u32,
|
|
_ctx: &CallerCtx,
|
|
) -> syscall::Result<OpenResult> {
|
|
let handle = self.handles.get(dirfd)?;
|
|
let segments = path.trim_matches('/');
|
|
|
|
let new_handle = match handle {
|
|
Handle::SchemeRoot => {
|
|
if segments.is_empty() {
|
|
return Err(SysError::new(EINVAL));
|
|
}
|
|
|
|
let mut parts = segments.split('/');
|
|
match parts.next() {
|
|
Some("register") if parts.next().is_none() => Handle::Register {
|
|
pending: Vec::new(),
|
|
},
|
|
Some("controllers") => match parts.next() {
|
|
None => Handle::ControllersDir {
|
|
pending: Vec::new(),
|
|
},
|
|
Some(id) if parts.next().is_none() => Handle::ControllerDetail {
|
|
id: id.parse::<u32>().map_err(|_| SysError::new(EINVAL))?,
|
|
pending: Vec::new(),
|
|
},
|
|
_ => return Err(SysError::new(EINVAL)),
|
|
},
|
|
Some("read_pin") if parts.next().is_none() => Handle::PinOp {
|
|
kind: PinOpKind::Read,
|
|
pending: Vec::new(),
|
|
},
|
|
Some("write_pin") if parts.next().is_none() => Handle::PinOp {
|
|
kind: PinOpKind::Write,
|
|
pending: Vec::new(),
|
|
},
|
|
Some("configure_pin") if parts.next().is_none() => Handle::PinOp {
|
|
kind: PinOpKind::Configure,
|
|
pending: Vec::new(),
|
|
},
|
|
_ => return Err(SysError::new(ENOENT)),
|
|
}
|
|
}
|
|
Handle::ControllersDir { .. } => {
|
|
if segments.is_empty() {
|
|
return Err(SysError::new(EINVAL));
|
|
}
|
|
|
|
Handle::ControllerDetail {
|
|
id: segments.parse::<u32>().map_err(|_| SysError::new(EINVAL))?,
|
|
pending: Vec::new(),
|
|
}
|
|
}
|
|
_ => return Err(SysError::new(EACCES)),
|
|
};
|
|
|
|
let fd = self.handles.insert(new_handle);
|
|
Ok(OpenResult::ThisScheme {
|
|
number: fd,
|
|
flags: NewFdFlags::empty(),
|
|
})
|
|
}
|
|
|
|
fn read(
|
|
&mut self,
|
|
id: usize,
|
|
buf: &mut [u8],
|
|
offset: u64,
|
|
_fcntl_flags: u32,
|
|
_ctx: &CallerCtx,
|
|
) -> syscall::Result<usize> {
|
|
let controllers = self.controller_list();
|
|
let detail = match self.handles.get(id)? {
|
|
Handle::ControllerDetail { id, .. } => self.controllers.get(id).map(|entry| entry.info.clone()),
|
|
_ => None,
|
|
};
|
|
|
|
let handle = self.handles.get_mut(id)?;
|
|
match handle {
|
|
Handle::ControllersDir { pending } if pending.is_empty() => {
|
|
*pending = Self::serialize_response(&GpioControlResponse::Controllers(controllers))?;
|
|
}
|
|
Handle::ControllerDetail { id, pending } if pending.is_empty() => {
|
|
let info = detail.ok_or(SysError::new(ENOENT))?;
|
|
*pending = Self::serialize_response(&GpioControlResponse::Controller(info))?;
|
|
log::debug!("gpiod: served controller detail for id={id}");
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
Self::copy_pending(handle, buf, offset)
|
|
}
|
|
|
|
fn write(
|
|
&mut self,
|
|
id: usize,
|
|
buf: &[u8],
|
|
_offset: u64,
|
|
_fcntl_flags: u32,
|
|
_ctx: &CallerCtx,
|
|
) -> syscall::Result<usize> {
|
|
let request = Self::deserialize_request(buf)?;
|
|
|
|
match request {
|
|
GpioControlRequest::RegisterController { mut info } => {
|
|
if !matches!(self.handles.get(id)?, Handle::Register { .. }) {
|
|
return Err(SysError::new(EINVAL));
|
|
}
|
|
|
|
let controller_id = self.next_id;
|
|
self.next_id = self.next_id.checked_add(1).ok_or(SysError::new(EINVAL))?;
|
|
info.id = controller_id;
|
|
self.controllers.insert(
|
|
controller_id,
|
|
ControllerEntry {
|
|
info: info.clone(),
|
|
provider_handle: id,
|
|
},
|
|
);
|
|
|
|
let handle = self.handles.get_mut(id)?;
|
|
*handle = Handle::Provider {
|
|
controller_id,
|
|
pending: Self::serialize_response(&GpioControlResponse::ControllerRegistered {
|
|
id: controller_id,
|
|
})?,
|
|
};
|
|
|
|
log::info!(
|
|
"RB_GPIOD_CONTROLLER_REGISTERED id={} name={} pin_count={} supports_interrupt={}",
|
|
info.id,
|
|
info.name,
|
|
info.pin_count,
|
|
info.supports_interrupt,
|
|
);
|
|
Ok(buf.len())
|
|
}
|
|
GpioControlRequest::ListControllers => {
|
|
let controllers = self.controller_list();
|
|
let handle = self.handles.get_mut(id)?;
|
|
Self::set_pending_response(handle, GpioControlResponse::Controllers(controllers))?;
|
|
Ok(buf.len())
|
|
}
|
|
GpioControlRequest::ReadPin { controller_id, pin } => {
|
|
let validation = self.validate_pin_target(controller_id, pin);
|
|
let handle = self.handles.get_mut(id)?;
|
|
match handle {
|
|
Handle::PinOp {
|
|
kind: PinOpKind::Read,
|
|
..
|
|
} => {
|
|
match validation {
|
|
Ok(info) => {
|
|
log::info!(
|
|
"RB_GPIOD_PIN_READ controller_id={} name={} pin={} routed=stub",
|
|
controller_id,
|
|
info.name,
|
|
pin,
|
|
);
|
|
Self::set_pending_response(handle, GpioControlResponse::PinValue(false))?;
|
|
}
|
|
Err(message) => {
|
|
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
|
|
}
|
|
}
|
|
Ok(buf.len())
|
|
}
|
|
_ => Err(SysError::new(EINVAL)),
|
|
}
|
|
}
|
|
GpioControlRequest::WritePin {
|
|
controller_id,
|
|
pin,
|
|
value,
|
|
} => {
|
|
let validation = self.validate_pin_target(controller_id, pin);
|
|
let handle = self.handles.get_mut(id)?;
|
|
match handle {
|
|
Handle::PinOp {
|
|
kind: PinOpKind::Write,
|
|
..
|
|
} => {
|
|
match validation {
|
|
Ok(info) => {
|
|
log::info!(
|
|
"RB_GPIOD_PIN_WRITE controller_id={} name={} pin={} value={} routed=stub",
|
|
controller_id,
|
|
info.name,
|
|
pin,
|
|
value,
|
|
);
|
|
Self::set_pending_response(handle, GpioControlResponse::Ack)?;
|
|
}
|
|
Err(message) => {
|
|
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
|
|
}
|
|
}
|
|
Ok(buf.len())
|
|
}
|
|
_ => Err(SysError::new(EINVAL)),
|
|
}
|
|
}
|
|
GpioControlRequest::ConfigurePin {
|
|
controller_id,
|
|
pin,
|
|
config,
|
|
} => {
|
|
let validation = self.validate_pin_target(controller_id, pin);
|
|
let handle = self.handles.get_mut(id)?;
|
|
match handle {
|
|
Handle::PinOp {
|
|
kind: PinOpKind::Configure,
|
|
..
|
|
} => {
|
|
match validation {
|
|
Ok(info) => {
|
|
log::info!(
|
|
"RB_GPIOD_PIN_CONFIG controller_id={} name={} pin={} direction={:?} pull={:?} interrupt={:?} routed=stub",
|
|
controller_id,
|
|
info.name,
|
|
pin,
|
|
config.direction,
|
|
config.pull,
|
|
config.interrupt_mode,
|
|
);
|
|
Self::set_pending_response(handle, GpioControlResponse::Ack)?;
|
|
}
|
|
Err(message) => {
|
|
Self::set_pending_response(handle, GpioControlResponse::Error(message))?;
|
|
}
|
|
}
|
|
Ok(buf.len())
|
|
}
|
|
_ => Err(SysError::new(EINVAL)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn on_close(&mut self, id: usize) {
|
|
let Some(handle) = self.handles.remove(id) else {
|
|
return;
|
|
};
|
|
if let Handle::Provider { controller_id, .. } = handle {
|
|
if let Some(entry) = self.controllers.remove(&controller_id) {
|
|
log::info!(
|
|
"RB_GPIOD_CONTROLLER_REMOVED id={} name={} provider_handle={}",
|
|
controller_id,
|
|
entry.info.name,
|
|
entry.provider_handle,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn run_daemon(daemon: daemon::SchemeDaemon) -> Result<()> {
|
|
let socket = Socket::create().context("failed to create gpio scheme socket")?;
|
|
let mut scheme = GpioDaemon::new();
|
|
let handler = Blocking::new(&socket, 16);
|
|
|
|
daemon
|
|
.ready_sync_scheme(&socket, &mut scheme)
|
|
.context("failed to publish gpio scheme root")?;
|
|
|
|
log::info!("RB_GPIOD_SCHEMA version=1");
|
|
|
|
libredox::call::setrens(0, 0).context("failed to enter null namespace")?;
|
|
|
|
handler
|
|
.process_requests_blocking(scheme)
|
|
.context("failed to process gpiod requests")?;
|
|
}
|
|
|
|
fn daemon_runner(daemon: daemon::SchemeDaemon) -> ! {
|
|
if let Err(err) = run_daemon(daemon) {
|
|
log::error!("gpiod: {err:#}");
|
|
process::exit(1);
|
|
}
|
|
|
|
process::exit(0);
|
|
}
|
|
|
|
fn main() {
|
|
common::setup_logging(
|
|
"gpio",
|
|
"gpio",
|
|
"gpiod",
|
|
common::output_level(),
|
|
common::file_level(),
|
|
);
|
|
|
|
daemon::SchemeDaemon::new(daemon_runner);
|
|
}
|