1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
//! Backend providing support for ADC sensors.
//!
//! A la dbus-sensors's `adcsensor` daemon.

use std::{
	collections::HashMap,
	sync::Arc,
	time::Duration,
};

use crate::{
	DaemonState,
	types::*,
	gpio::BridgeGPIOConfig,
	powerstate::PowerState,
	sensor,
	sensor::{
		Sensor,
		SensorConfig,
		SensorMode::ReadOnly,
		SensorType,
	},
	sysfs,
	threshold,
	threshold::ThresholdConfig,
	dbus_helpers::props::*,
};

/// Internal representation of the dbus config data for an ADC sensor (one channel).
#[derive(Debug)]
pub struct ADCSensorConfig {
	/// Sensor name.
	name: String,
	/// Index of this sensor (channel) within the ADC hardware device.
	index: u64,
	/// Polling interval for the sensor.
	poll_interval: Duration,
	/// Scaling multiplier for the sensor.
	///
	/// We store this as the reciprocal of what was provided via the dbus config so
	/// that we can multiply instead of dividing
	scale: f64,
	/// Host power state in which this sensor is active.
	power_state: PowerState,
	/// Threshold settings for the sensor.
	thresholds: Vec<ThresholdConfig>,
	/// An optional GPIO that must be asserted before reading the sensor.
	///
	/// Common for battery voltage sensors to reduce parasitic battery drain.
	bridge_gpio: Option<Arc<BridgeGPIOConfig>>,
}

/// DBus interface used for ADC sensor bridge GPIO config data.
const BRIDGE_GPIO_CONFIG_INTF: &str = "xyz.openbmc_project.Configuration.ADC.BridgeGpio0";

impl ADCSensorConfig {
	/// Construct an [`ADCSensorConfig`] from raw dbus config data.
	pub fn from_dbus(basecfg: &dbus::arg::PropMap, baseintf: &str,
	                 intfs: &HashMap<String, dbus::arg::PropMap>) -> ErrResult<Self> {
		let name: &String = prop_get_mandatory(basecfg, "Name")?;
		let index: u64 = *prop_get_mandatory(basecfg, "Index")?;
		let poll_sec: u64 = *prop_get_default(basecfg, "PollRate", &1u64)?;
		let scale: f64 = *prop_get_default(basecfg, "ScaleFactor", &1.0f64)?;
		let power_state = prop_get_default_from(basecfg, "PowerState", PowerState::Always)?;
		let bridge_gpio = match intfs.get(BRIDGE_GPIO_CONFIG_INTF) {
			Some(map) => Some(Arc::new(BridgeGPIOConfig::from_dbus(map)?)),
			None => None,
		};
		let thresholds = threshold::get_configs_from_dbus(baseintf, intfs);

		if !scale.is_finite() || scale == 0.0 {
			let msg = format!("{}: ScaleFactor must be finite and non-zero (got {})",
			                  name, scale);
			return Err(err_invalid_data(msg));
		}

		Ok(Self {
			name: name.clone(),
			index,
			poll_interval: Duration::from_secs(poll_sec),
			scale: 1.0 / scale, // convert to a multiplier
			power_state,
			thresholds,
			bridge_gpio,
		})
	}
}

/// The directory where we expect to find the ADC sensor device.
const IIO_HWMON_PATH: &str = "/sys/devices/platform/iio-hwmon";

/// Instantiate any active ADC sensors configured in `cfgmap`.
pub async fn instantiate_sensors(daemonstate: &DaemonState, dbuspaths: &FilterSet<InventoryPath>)
                                 -> ErrResult<()>
{
	let hwmondir = sysfs::get_single_hwmon_dir(std::path::Path::new(IIO_HWMON_PATH))?;
	let cfgmap = daemonstate.config.lock().await;
	let configs = cfgmap.iter()
		.filter_map(|(path, cfg)| {
			match cfg {
				SensorConfig::ADC(c) if dbuspaths.contains(path) => Some((path, c)),
				_ => None,
			}
		});
	for (path, adccfg) in configs {
		let mut sensors = daemonstate.sensors.lock().await;

		let Some(entry) = sensor::get_nonactive_sensor_entry(&mut sensors,
		                                                     adccfg.name.clone()).await else {
			continue;
		};

		let ioctx = match sysfs::prepare_indexed_hwmon_ioctx(&hwmondir, adccfg.index,
		                                                     SensorType::Voltage,
		                                                     adccfg.power_state,
		                                                     &adccfg.bridge_gpio).await {
			Ok(Some(ioctx)) => ioctx,
			Ok(None) => continue,
			Err(e) => {
				eprintln!("Error preparing {} from {}: {}", adccfg.name,
				          hwmondir.display(), e);
				continue;
			},
		};

		let ctor = || {
			Sensor::new(path, &adccfg.name, SensorType::Voltage, &daemonstate.sensor_intfs,
			            &daemonstate.bus, ReadOnly)
				.with_poll_interval(adccfg.poll_interval)
				.with_scale(adccfg.scale)
				.with_power_state(adccfg.power_state)
				.with_thresholds_from(&adccfg.thresholds,
				                      &daemonstate.sensor_intfs.thresholds,
				                      &daemonstate.bus)
				.with_minval(0.0)
				.with_maxval(1.8 * adccfg.scale) // 1.8 cargo-culted from ADCSensorMain.cpp
		};
		sensor::install_or_activate(entry, &daemonstate.crossroads, ioctx,
		                            &daemonstate.sensor_intfs, ctor).await;
	}
	Ok(())
}

/// Whether or not the given `cfgtype` is supported by the `adc` sensor backend.
pub fn match_cfgtype(cfgtype: &str) -> bool {
	cfgtype == "ADC"
}