use std::collections::HashMap;
use std::time::{Duration, Instant};
use super::config::GpioConfig;
use super::device::GpioDirection;
use super::safety::GpioSafetyPolicy;
#[derive(Debug)]
pub struct GpioPin {
pub chip: u32,
pub line: u32,
pub direction: GpioDirection,
pub agent_id: String,
pub acquired_at: Instant,
}
struct ActivePin {
agent_id: String,
direction: GpioDirection,
acquired_at: Instant,
}
pub struct GpioPinManager {
safety: GpioSafetyPolicy,
active_pins: HashMap<(u32, u32), ActivePin>,
max_concurrent: usize,
auto_release_timeout: Duration,
}
impl GpioPinManager {
pub fn from_config(config: &GpioConfig) -> Self {
Self {
safety: GpioSafetyPolicy::from_config(config),
active_pins: HashMap::new(),
max_concurrent: config.max_concurrent_pins,
auto_release_timeout: Duration::from_secs(config.auto_release_timeout_secs),
}
}
pub fn acquire(
&mut self,
chip: u32,
line: u32,
direction: GpioDirection,
agent_id: &str,
) -> Result<GpioPin, String> {
self.safety
.check(chip, line, &direction.to_string(), agent_id)?;
if self.active_pins.len() >= self.max_concurrent {
return Err(format!(
"Maximum concurrent pins ({}) reached",
self.max_concurrent
));
}
let key = (chip, line);
if let Some(existing) = self.active_pins.get(&key) {
return Err(format!(
"Pin chip{chip}/line{line} already held by agent '{}'",
existing.agent_id
));
}
let now = Instant::now();
self.active_pins.insert(
key,
ActivePin {
agent_id: agent_id.to_string(),
direction,
acquired_at: now,
},
);
Ok(GpioPin {
chip,
line,
direction,
agent_id: agent_id.to_string(),
acquired_at: now,
})
}
pub fn release(&mut self, chip: u32, line: u32) {
self.active_pins.remove(&(chip, line));
}
pub fn release_agent(&mut self, agent_id: &str) {
self.active_pins.retain(|_, pin| pin.agent_id != agent_id);
}
pub fn release_timed_out(&mut self) -> Vec<(u32, u32, String)> {
let now = Instant::now();
let mut released = Vec::new();
self.active_pins.retain(|&(chip, line), pin| {
if now.duration_since(pin.acquired_at) >= self.auto_release_timeout {
released.push((chip, line, pin.agent_id.clone()));
tracing::warn!(
"Auto-releasing GPIO pin chip{chip}/line{line} from agent '{}' (timeout)",
pin.agent_id
);
false
} else {
true
}
});
released
}
pub fn active_count(&self) -> usize {
self.active_pins.len()
}
pub fn active_pins(&self) -> Vec<GpioPin> {
self.active_pins
.iter()
.map(|(&(chip, line), pin)| GpioPin {
chip,
line,
direction: pin.direction,
agent_id: pin.agent_id.clone(),
acquired_at: pin.acquired_at,
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_config() -> GpioConfig {
GpioConfig {
allowed_pins: vec![(0, 17), (0, 27), (0, 22)],
max_concurrent_pins: 2,
auto_release_timeout_secs: 60,
}
}
#[test]
fn acquire_allowed_pin() {
let mut mgr = GpioPinManager::from_config(&test_config());
let pin = mgr.acquire(0, 17, GpioDirection::Output, "agent-1");
assert!(pin.is_ok());
assert_eq!(mgr.active_count(), 1);
}
#[test]
fn reject_disallowed_pin() {
let mut mgr = GpioPinManager::from_config(&test_config());
let pin = mgr.acquire(0, 99, GpioDirection::Output, "agent-1");
assert!(pin.is_err());
}
#[test]
fn reject_concurrent_limit() {
let mut mgr = GpioPinManager::from_config(&test_config());
mgr.acquire(0, 17, GpioDirection::Output, "agent-1")
.unwrap();
mgr.acquire(0, 27, GpioDirection::Output, "agent-1")
.unwrap();
let third = mgr.acquire(0, 22, GpioDirection::Output, "agent-1");
assert!(third.is_err());
}
#[test]
fn reject_double_acquire() {
let mut mgr = GpioPinManager::from_config(&test_config());
mgr.acquire(0, 17, GpioDirection::Output, "agent-1")
.unwrap();
let second = mgr.acquire(0, 17, GpioDirection::Input, "agent-2");
assert!(second.is_err());
}
#[test]
fn release_frees_pin() {
let mut mgr = GpioPinManager::from_config(&test_config());
mgr.acquire(0, 17, GpioDirection::Output, "agent-1")
.unwrap();
mgr.release(0, 17);
assert_eq!(mgr.active_count(), 0);
}
#[test]
fn release_agent_frees_all() {
let mut mgr = GpioPinManager::from_config(&test_config());
mgr.acquire(0, 17, GpioDirection::Output, "agent-1")
.unwrap();
mgr.acquire(0, 27, GpioDirection::Output, "agent-1")
.unwrap();
mgr.release_agent("agent-1");
assert_eq!(mgr.active_count(), 0);
}
}