use crate::macros::{GenlMessage, NetlinkAttrs};
use super::types::{
DpllAttr, DpllClockQualityLevel, DpllCmd, DpllFeatureState, DpllLockStatus,
DpllLockStatusError, DpllMode, DpllPinAttr, DpllPinCapabilities, DpllPinDirection,
DpllPinState, DpllPinType, DpllType,
};
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = DpllCmd::DeviceGet)]
pub struct DpllDeviceGetRequest {
#[genl_attr(DpllAttr::Id)]
pub id: Option<u32>,
}
impl DpllDeviceGetRequest {
pub fn by_id(id: u32) -> Self {
Self { id: Some(id) }
}
pub fn dump() -> Self {
Self { id: None }
}
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = DpllCmd::DeviceGet)]
#[non_exhaustive]
pub struct DpllDeviceReply {
#[genl_attr(DpllAttr::Id)]
pub id: u32,
#[genl_attr(DpllAttr::ModuleName)]
pub module_name: String,
#[genl_attr(DpllAttr::ClockId)]
pub clock_id: u64,
#[genl_attr(DpllAttr::Mode, repr = "u32")]
pub mode: Option<DpllMode>,
#[genl_attr(DpllAttr::ModeSupported, repr = "u32")]
pub mode_supported: Vec<DpllMode>,
#[genl_attr(DpllAttr::LockStatus, repr = "u32")]
pub lock_status: Option<DpllLockStatus>,
#[genl_attr(DpllAttr::Temp)]
pub temp_mdeg: Option<i32>,
#[genl_attr(DpllAttr::Type, repr = "u32")]
pub kind: Option<DpllType>,
#[genl_attr(DpllAttr::LockStatusError, repr = "u32")]
pub lock_status_error: Option<DpllLockStatusError>,
#[genl_attr(DpllAttr::ClockQualityLevel, repr = "u32")]
pub clock_quality_level: Vec<DpllClockQualityLevel>,
#[genl_attr(DpllAttr::PhaseOffsetMonitor, repr = "u32")]
pub phase_offset_monitor: Option<DpllFeatureState>,
#[genl_attr(DpllAttr::PhaseOffsetAvgFactor)]
pub phase_offset_avg_factor: Option<u32>,
#[genl_attr(DpllAttr::FrequencyMonitor, repr = "u32")]
pub frequency_monitor: Option<DpllFeatureState>,
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = DpllCmd::DeviceSet)]
pub struct DpllDeviceSetRequest {
#[genl_attr(DpllAttr::Id)]
pub id: u32,
#[genl_attr(DpllAttr::Mode, repr = "u32")]
pub mode: Option<DpllMode>,
#[genl_attr(DpllAttr::PhaseOffsetMonitor, repr = "u32")]
pub phase_offset_monitor: Option<DpllFeatureState>,
#[genl_attr(DpllAttr::PhaseOffsetAvgFactor)]
pub phase_offset_avg_factor: Option<u32>,
#[genl_attr(DpllAttr::FrequencyMonitor, repr = "u32")]
pub frequency_monitor: Option<DpllFeatureState>,
}
impl DpllDeviceSetRequest {
pub fn new(id: u32) -> Self {
Self {
id,
..Self::default()
}
}
#[must_use]
pub fn mode(mut self, mode: DpllMode) -> Self {
self.mode = Some(mode);
self
}
#[must_use]
pub fn phase_offset_monitor(mut self, state: DpllFeatureState) -> Self {
self.phase_offset_monitor = Some(state);
self
}
#[must_use]
pub fn phase_offset_avg_factor(mut self, factor: u32) -> Self {
self.phase_offset_avg_factor = Some(factor);
self
}
#[must_use]
pub fn frequency_monitor(mut self, state: DpllFeatureState) -> Self {
self.frequency_monitor = Some(state);
self
}
}
impl DpllDeviceReply {
pub fn temp_celsius(&self) -> Option<f32> {
self.temp_mdeg.map(|m| m as f32 / super::DPLL_TEMP_DIVIDER as f32)
}
}
#[derive(NetlinkAttrs, Debug, Default, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DpllPinParentDevice {
#[genl_attr(DpllPinAttr::ParentId)]
pub parent_id: u32,
#[genl_attr(DpllPinAttr::State, repr = "u32")]
pub state: Option<DpllPinState>,
}
#[derive(NetlinkAttrs, Debug, Default, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct DpllPinParentPin {
#[genl_attr(DpllPinAttr::ParentId)]
pub parent_id: u32,
#[genl_attr(DpllPinAttr::State, repr = "u32")]
pub state: Option<DpllPinState>,
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = DpllCmd::PinGet)]
pub struct DpllPinGetRequest {
#[genl_attr(DpllPinAttr::Id)]
pub id: Option<u32>,
}
impl DpllPinGetRequest {
pub fn by_id(id: u32) -> Self {
Self { id: Some(id) }
}
pub fn dump() -> Self {
Self { id: None }
}
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = DpllCmd::PinGet)]
#[non_exhaustive]
pub struct DpllPinReply {
#[genl_attr(DpllPinAttr::Id)]
pub id: u32,
#[genl_attr(DpllPinAttr::ModuleName)]
pub module_name: String,
#[genl_attr(DpllPinAttr::ClockId)]
pub clock_id: u64,
#[genl_attr(DpllPinAttr::BoardLabel)]
pub board_label: Option<String>,
#[genl_attr(DpllPinAttr::PanelLabel)]
pub panel_label: Option<String>,
#[genl_attr(DpllPinAttr::PackageLabel)]
pub package_label: Option<String>,
#[genl_attr(DpllPinAttr::Type, repr = "u32")]
pub kind: Option<DpllPinType>,
#[genl_attr(DpllPinAttr::Direction, repr = "u32")]
pub direction: Option<DpllPinDirection>,
#[genl_attr(DpllPinAttr::Frequency)]
pub frequency: Option<u64>,
#[genl_attr(DpllPinAttr::FrequencyMin)]
pub frequency_min: Option<u64>,
#[genl_attr(DpllPinAttr::FrequencyMax)]
pub frequency_max: Option<u64>,
#[genl_attr(DpllPinAttr::Prio)]
pub prio: Option<u32>,
#[genl_attr(DpllPinAttr::State, repr = "u32")]
pub state: Option<DpllPinState>,
#[genl_attr(DpllPinAttr::Capabilities, bitflags = "u32")]
pub capabilities: DpllPinCapabilities,
#[genl_attr(DpllPinAttr::ParentDevice, nested)]
pub parent_device: Option<DpllPinParentDevice>,
#[genl_attr(DpllPinAttr::ParentPin, nested)]
pub parent_pin: Option<DpllPinParentPin>,
#[genl_attr(DpllPinAttr::PhaseAdjustMin)]
pub phase_adjust_min: Option<i32>,
#[genl_attr(DpllPinAttr::PhaseAdjustMax)]
pub phase_adjust_max: Option<i32>,
#[genl_attr(DpllPinAttr::PhaseAdjust)]
pub phase_adjust: Option<i32>,
#[genl_attr(DpllPinAttr::PhaseOffset)]
pub phase_offset: Option<i32>,
#[genl_attr(DpllPinAttr::EsyncFrequency)]
pub esync_frequency: Option<u64>,
#[genl_attr(DpllPinAttr::EsyncPulse)]
pub esync_pulse: Option<u32>,
#[genl_attr(DpllPinAttr::PhaseAdjustGran)]
pub phase_adjust_gran: Option<u32>,
#[genl_attr(DpllPinAttr::FractionalFrequencyOffsetPpt)]
pub fractional_frequency_offset_ppt: Option<i32>,
#[genl_attr(DpllPinAttr::MeasuredFrequency)]
pub measured_frequency: Option<u64>,
}
impl DpllPinReply {
pub fn phase_offset_ns(&self) -> Option<i64> {
self.phase_offset
.map(|p| p as i64 / super::DPLL_PHASE_OFFSET_DIVIDER)
}
pub fn measured_frequency_hz(&self) -> Option<u64> {
self.measured_frequency
.map(|m| m / super::DPLL_PIN_MEASURED_FREQUENCY_DIVIDER)
}
}
#[derive(GenlMessage, Debug, Default, Clone)]
#[genl_message(cmd = DpllCmd::PinSet)]
pub struct DpllPinSetRequest {
#[genl_attr(DpllPinAttr::Id)]
pub id: u32,
#[genl_attr(DpllPinAttr::Prio)]
pub prio: Option<u32>,
#[genl_attr(DpllPinAttr::State, repr = "u32")]
pub state: Option<DpllPinState>,
#[genl_attr(DpllPinAttr::Frequency)]
pub frequency: Option<u64>,
#[genl_attr(DpllPinAttr::Direction, repr = "u32")]
pub direction: Option<DpllPinDirection>,
#[genl_attr(DpllPinAttr::PhaseAdjust)]
pub phase_adjust: Option<i32>,
}
impl DpllPinSetRequest {
pub fn new(id: u32) -> Self {
Self {
id,
..Self::default()
}
}
#[must_use]
pub fn prio(mut self, prio: u32) -> Self {
self.prio = Some(prio);
self
}
#[must_use]
pub fn state(mut self, state: DpllPinState) -> Self {
self.state = Some(state);
self
}
#[must_use]
pub fn frequency(mut self, hz: u64) -> Self {
self.frequency = Some(hz);
self
}
#[must_use]
pub fn direction(mut self, direction: DpllPinDirection) -> Self {
self.direction = Some(direction);
self
}
#[must_use]
pub fn phase_adjust(mut self, ps: i32) -> Self {
self.phase_adjust = Some(ps);
self
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::macros::__rt;
use crate::netlink::MessageBuilder;
#[test]
fn device_get_request_with_id_emits_one_attr() {
let req = DpllDeviceGetRequest::by_id(7);
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
req.to_bytes(&mut b).expect("emit");
let bytes = &b.as_bytes()[start..];
let mut attrs: Vec<u16> = Vec::new();
for (ty, _) in __rt::attr_iter(bytes) {
attrs.push(ty);
}
assert_eq!(attrs, vec![DpllAttr::Id as u16]);
}
#[test]
fn device_get_dump_emits_no_attrs() {
let req = DpllDeviceGetRequest::dump();
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
req.to_bytes(&mut b).expect("emit");
let bytes = &b.as_bytes()[start..];
let count = __rt::attr_iter(bytes).count();
assert_eq!(count, 0, "dump request should emit zero attrs");
}
#[test]
fn device_reply_round_trips_a_full_attr_set() {
let original = DpllDeviceReply {
id: 42,
module_name: "ice".to_string(),
clock_id: 0x0011_2233_4455_6677,
mode: Some(DpllMode::Automatic),
mode_supported: vec![DpllMode::Manual, DpllMode::Automatic],
lock_status: Some(DpllLockStatus::LockedHoAcq),
temp_mdeg: Some(47_500),
kind: Some(DpllType::Eec),
lock_status_error: None,
clock_quality_level: vec![DpllClockQualityLevel::ItuOpt1Prc],
phase_offset_monitor: Some(DpllFeatureState::Enable),
phase_offset_avg_factor: Some(8),
frequency_monitor: Some(DpllFeatureState::Disable),
};
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
original.to_bytes(&mut b).expect("emit");
let parsed = DpllDeviceReply::from_bytes(&b.as_bytes()[start..]).expect("parse");
assert_eq!(parsed.id, original.id);
assert_eq!(parsed.module_name, original.module_name);
assert_eq!(parsed.clock_id, original.clock_id);
assert_eq!(parsed.mode, original.mode);
assert_eq!(parsed.mode_supported, original.mode_supported);
assert_eq!(parsed.lock_status, original.lock_status);
assert_eq!(parsed.temp_mdeg, original.temp_mdeg);
assert_eq!(parsed.kind, original.kind);
assert_eq!(parsed.lock_status_error, original.lock_status_error);
assert_eq!(parsed.clock_quality_level, original.clock_quality_level);
assert_eq!(parsed.phase_offset_monitor, original.phase_offset_monitor);
assert_eq!(parsed.phase_offset_avg_factor, original.phase_offset_avg_factor);
assert_eq!(parsed.frequency_monitor, original.frequency_monitor);
}
#[test]
fn device_reply_missing_attrs_yield_defaults_and_nones() {
let parsed = DpllDeviceReply::from_bytes(&[]).expect("parse");
assert_eq!(parsed.id, 0);
assert_eq!(parsed.module_name, "");
assert_eq!(parsed.clock_id, 0);
assert_eq!(parsed.mode, None);
assert!(parsed.mode_supported.is_empty());
assert_eq!(parsed.lock_status, None);
assert_eq!(parsed.temp_mdeg, None);
assert_eq!(parsed.kind, None);
assert_eq!(parsed.lock_status_error, None);
assert!(parsed.clock_quality_level.is_empty());
assert_eq!(parsed.phase_offset_monitor, None);
assert_eq!(parsed.frequency_monitor, None);
}
#[test]
fn device_set_builder_emits_only_set_fields() {
let req = DpllDeviceSetRequest::new(7)
.mode(DpllMode::Manual)
.frequency_monitor(DpllFeatureState::Enable);
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
req.to_bytes(&mut b).expect("emit");
let bytes = &b.as_bytes()[start..];
let mut attrs: Vec<u16> = Vec::new();
for (ty, _) in __rt::attr_iter(bytes) {
attrs.push(ty);
}
assert_eq!(
attrs,
vec![
DpllAttr::Id as u16,
DpllAttr::Mode as u16,
DpllAttr::FrequencyMonitor as u16,
]
);
}
#[test]
fn pin_get_request_with_id_emits_one_attr() {
let req = DpllPinGetRequest::by_id(12);
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
req.to_bytes(&mut b).expect("emit");
let bytes = &b.as_bytes()[start..];
let mut attrs: Vec<u16> = Vec::new();
for (ty, _) in __rt::attr_iter(bytes) {
attrs.push(ty);
}
assert_eq!(attrs, vec![DpllPinAttr::Id as u16]);
}
#[test]
fn pin_reply_round_trips_with_nested_parent_device() {
let original = DpllPinReply {
id: 5,
module_name: "ice".to_string(),
clock_id: 0xC1F0_D000,
board_label: Some("REF0".to_string()),
kind: Some(DpllPinType::SynceEthPort),
direction: Some(DpllPinDirection::Input),
frequency: Some(10_000_000),
prio: Some(1),
state: Some(DpllPinState::Connected),
capabilities: DpllPinCapabilities::PRIORITY_CAN_CHANGE
| DpllPinCapabilities::STATE_CAN_CHANGE,
parent_device: Some(DpllPinParentDevice {
parent_id: 42,
state: Some(DpllPinState::Connected),
}),
phase_offset: Some(123_000),
measured_frequency: Some(10_000_000_000),
..DpllPinReply::default()
};
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
original.to_bytes(&mut b).expect("emit");
let parsed = DpllPinReply::from_bytes(&b.as_bytes()[start..]).expect("parse");
assert_eq!(parsed.id, 5);
assert_eq!(parsed.module_name, "ice");
assert_eq!(parsed.clock_id, 0xC1F0_D000);
assert_eq!(parsed.board_label.as_deref(), Some("REF0"));
assert_eq!(parsed.kind, Some(DpllPinType::SynceEthPort));
assert_eq!(parsed.direction, Some(DpllPinDirection::Input));
assert_eq!(parsed.frequency, Some(10_000_000));
assert_eq!(parsed.prio, Some(1));
assert_eq!(parsed.state, Some(DpllPinState::Connected));
assert_eq!(
parsed.capabilities,
DpllPinCapabilities::PRIORITY_CAN_CHANGE | DpllPinCapabilities::STATE_CAN_CHANGE
);
let parent = parsed.parent_device.expect("parent_device present");
assert_eq!(parent.parent_id, 42);
assert_eq!(parent.state, Some(DpllPinState::Connected));
assert_eq!(parsed.phase_offset, Some(123_000));
assert_eq!(parsed.measured_frequency, Some(10_000_000_000));
}
#[test]
fn pin_reply_helpers_apply_dividers() {
let reply = DpllPinReply {
phase_offset: Some(123_456_000),
measured_frequency: Some(10_000_000_000),
..DpllPinReply::default()
};
assert_eq!(reply.phase_offset_ns(), Some(123_456));
assert_eq!(reply.measured_frequency_hz(), Some(10_000_000));
assert_eq!(DpllPinReply::default().phase_offset_ns(), None);
assert_eq!(DpllPinReply::default().measured_frequency_hz(), None);
}
#[test]
fn pin_set_builder_chains_priority_and_state() {
let req = DpllPinSetRequest::new(7)
.prio(2)
.state(DpllPinState::Selectable);
let mut b = MessageBuilder::new(0, 0);
let start = b.len();
req.to_bytes(&mut b).expect("emit");
let bytes = &b.as_bytes()[start..];
let mut attrs: Vec<u16> = Vec::new();
for (ty, _) in __rt::attr_iter(bytes) {
attrs.push(ty);
}
assert_eq!(
attrs,
vec![
DpllPinAttr::Id as u16,
DpllPinAttr::Prio as u16,
DpllPinAttr::State as u16,
]
);
}
#[test]
fn temp_celsius_helper_applies_divider() {
let reply = DpllDeviceReply {
temp_mdeg: Some(47_500),
..DpllDeviceReply::default()
};
let c = reply.temp_celsius().expect("temp present");
assert!((c - 47.5).abs() < f32::EPSILON, "expected 47.5°C, got {c}");
assert_eq!(
DpllDeviceReply::default().temp_celsius(),
None,
"missing temp_mdeg → None"
);
}
}