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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
//! Abstractions for managing I2C devices.

use std::{
	collections::HashMap,
	path::{Path, PathBuf},
	sync::Arc,
};
use phf::{phf_map, phf_set};

use crate::{
	types::*,
	dbus_helpers::props::*,
};

/// A map of supported I2C device types.
///
/// Keys are supported I2C device types values are `bool`s indicating whether or not the
/// device is expected to create a `hwmon` directory when instantiated.
///
/// Functionally this could be simplified to a set (perhaps even with membership implying
/// false instead of true to keep it smaller), but listing all known devices explicitly
/// seems preferable maintainability-wise.
static I2C_HWMON_DEVICES: phf::Map<&'static str, bool> = phf_map! {
	"DPS310"   => false,
	"EMC1412"  => true,
	"EMC1413"  => true,
	"EMC1414"  => true,
	"HDC1080"  => false,
	"JC42"     => true,
	"LM75A"    => true,
	"LM95234"  => true,
	"MAX31725" => true,
	"MAX31730" => true,
	"MAX6581"  => true,
	"MAX6654"  => true,
	"NCT6779"  => true,
	"NCT7802"  => true,
	"SBTSI"    => true,
	"SI7020"   => false,
	"TMP112"   => true,
	"TMP175"   => true,
	"TMP421"   => true,
	"TMP441"   => true,
	"TMP75"    => true,
	"W83773G"  => true,
};

/// A set of device types known to be PMBus sensors.
///
/// While broadly similar, PMBus sensors and "basic" hwmon sensors use somewhat different
/// config schemas; this is used to determine which one we're dealing with.
static I2C_PMBUS_TYPES: phf::Set<&'static str> = phf_set! {
	"ADM1266",
	"ADM1272",
	"ADM1275",
	"ADM1278",
	"ADM1293",
	"ADS7830",
	"BMR490",
	"DPS800",
	"INA219",
	"INA230",
	"IPSPS",
	"IR38060",
	"IR38164",
	"IR38263",
	"ISL68137",
	"ISL68220",
	"ISL68223",
	"ISL69225",
	"ISL69243",
	"ISL69260",
	"LM25066",
	"MAX16601",
	"MAX20710",
	"MAX20730",
	"MAX20734",
	"MAX20796",
	"MAX34451",
	"MP2971",
	"MP2973",
	"MP5023",
	"PLI1209BC",
	"pmbus",
	"PXE1610",
	"RAA228000",
	"RAA228228",
	"RAA228620",
	"RAA229001",
	"RAA229004",
	"RAA229126",
	"TPS53679",
	"TPS546D24",
	"XDPE11280",
	"XDPE12284"
};

/// Retrieve an I2CDeviceType for the given device type string.
pub fn get_device_type(s: &str) -> Option<I2CDeviceType> {
	if let Some(k) = I2C_PMBUS_TYPES.get_key(s) {
		Some(I2CDeviceType::PMBus(PMBusDeviceType { name: k }))
	} else if let Some((k, v)) = I2C_HWMON_DEVICES.get_entry(s) {
		Some(I2CDeviceType::Hwmon(HwmonDeviceType {
			name: k,
			creates_hwmon: *v,
		}))
	} else {
		None
	}
}

/// A basic (non-pmbus) hwmon I2C device type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct HwmonDeviceType {
	/// Device type name (uppercase).
	name: &'static str,
	/// Whether or not we expect the driver to create a `hwmon` subdirectory when
	/// bound to a device.
	creates_hwmon: bool,
}

/// A PMBus I2C device type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PMBusDeviceType {
	/// Device type name (uppercase).
	name: &'static str,
}

/// An enum for an I2C device type.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum I2CDeviceType {
	PMBus(PMBusDeviceType),
	Hwmon(HwmonDeviceType),
}

impl I2CDeviceType {
	/// Return the name of the device type.
	pub fn name(&self) -> &'static str {
		match self {
			Self::PMBus(p) => p.name,
			Self::Hwmon(h) => h.name,
		}
	}

	/// Whether or not the device type is expected to create a `hwmon` subdirectory
	/// when a driver is bound.
	pub fn creates_hwmon(&self) -> bool {
		match self {
			// All PMBus devices are expected to, so we don't store it explicitly.
			Self::PMBus(_) => true,
			Self::Hwmon(h) => h.creates_hwmon,
		}
	}

	/// Return the (lower-case) form of the string used to write into `new_device`.
	fn kernel_type(&self) -> String {
		self.name().to_lowercase()
	}
}

/// The information needed to instantiate an I2C device.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct I2CDeviceParams {
	/// I2C bus number.
	pub bus: u16,
	/// I2C address.
	pub address: u16,
	/// Device type.
	pub devtype: I2CDeviceType,
}

/// The sysfs directory into which all i2c devices are mapped.
const I2C_DEV_DIR: &str = "/sys/bus/i2c/devices";

impl I2CDeviceParams {
	/// Create an [`I2CDeviceParams`] from a set of dbus properties and a type string.
	pub fn from_dbus(cfg: &dbus::arg::PropMap, devtype: I2CDeviceType) -> ErrResult<Self> {
		let bus: u64 = *prop_get_mandatory(cfg, "Bus")?;
		let address: u64 = *prop_get_mandatory(cfg, "Address")?;
		Ok(Self {
			bus: bus.try_into()?,
			address: address.try_into()?,
			devtype,
		})
	}

	/// Return the device name as employed in sysfs, e.g. `"2-004c"`.
	pub fn sysfs_name(&self) -> String {
		format!("{}-{:04x}", self.bus, self.address)
	}

	/// Return the absolute path of the sysfs directory representing the device.
	pub fn sysfs_device_dir(&self) -> PathBuf {
		Path::new(I2C_DEV_DIR).join(self.sysfs_name())
	}

	/// Return the absolute path of the sysfs directory representing the bus via which
	/// the device is attached.
	pub fn sysfs_bus_dir(&self) -> PathBuf {
		Path::new(I2C_DEV_DIR).join(format!("i2c-{}", self.bus))
	}

	/// Test if the device is currently present, i.e. has had a driver successfully
	/// bound to it.
	pub fn device_present(&self) -> bool {
		let mut path = self.sysfs_device_dir();
		if self.devtype.creates_hwmon() {
			path.push("hwmon");
		}
		path.exists()
	}

	/// Test if the device is static, i.e. instantiated from a device-tree node (as
	/// opposed to a dynamic device instantiate by userspace writing to `new_device`).
	pub fn device_static(&self) -> bool {
		if !self.device_present() {
			false
		} else {
			self.sysfs_device_dir().join("of_node").exists()
		}
	}

	/// Attempt to instantiate the device represented by `self`, returning:
	///
	///  * `Ok(None)` if the device is static (we don't need to manage it).
	///  * `Ok(Some(_))` on success (an [`I2CDevice`] that will remove the device when
	///    the last reference to it is dropped).
	///  * `Err(_)` on error.
	pub fn instantiate_device(&self) -> ErrResult<Option<Arc<I2CDevice>>> {
		if self.device_static() {
			Ok(None)
		} else {
			// There exist error cases in which a sensor device that
			// we need is already instantiated, but needs to be
			// destroyed and re-created in order to be useful (for
			// example if we crash after instantiating a device and
			// the sensor device's power is cut before we get
			// restarted, leaving it "present" but not really
			// usable).  To be on the safe side, instantiate a
			// temporary device and immediately destroy it so as to
			// ensure that we end up with a fresh instance of it.
			if self.device_present() {
				drop(I2CDevice::new(self.clone()));
			}
			I2CDevice::new(self.clone()).map(|d| Some(Arc::new(d)))
		}
	}
}

/// An instantiated I2C device.
///
/// Doesn't do much aside from acting as a token that keeps the device instantiated as
/// long as it exists (its [`drop`](I2CDevice::drop) impl deletes the device).
pub struct I2CDevice {
	/// The parameters of the device we've instantiated.
	pub params: I2CDeviceParams,
}

impl I2CDevice {
	/// Instantiate an [`I2CDevice`] from an [`I2CDeviceParams`].
	pub fn new(params: I2CDeviceParams) -> ErrResult<Self> {
		// If it's already instantiated, there's nothing we need to do.
		let dev = Self { params };
		if dev.params.device_present() {
			return Ok(dev);
		}

		// Try to create it: 'echo $devtype $addr > .../i2c-$bus/new_device'
		let ctor_path = dev.params.sysfs_bus_dir().join("new_device");
		let payload = format!("{} {:#02x}\n", dev.params.devtype.kernel_type(),
		                      dev.params.address);
		std::fs::write(&ctor_path, payload)?;

		// Check if that created the requisite sysfs directory
		if dev.params.device_present() {
			Ok(dev)
		} else {
			// ...if not the implicit drop of 'dev' will tear down
			// any partially-instantiated remnants
			Err(err_other("new_device failed to instantiate device"))
		}
	}
}

impl Drop for I2CDevice {
	/// Deletes the I2C device represented by `self` by writing to `delete_device`.
	fn drop(&mut self) {
		// No params.devicePresent() check on this like in
		// I2CDevice::new(), since it might be used to clean up after a
		// device instantiation that was only partially successful
		// (i.e. when params.device_present() would return false but
		// there's still a dummy i2c client device to remove)
		let dtor_path = self.params.sysfs_bus_dir().join("delete_device");
		let payload = format!("{:#02x}\n", self.params.address);
		if let Err(e) = std::fs::write(&dtor_path, payload) {
			eprintln!("Failed to write to {}: {}", dtor_path.display(), e);
		}
	}
}

/// Lookup table for finding I2CDevices.  Weak<_> because we only want them kept alive by
/// (strong, Arc<_>) references from Sensors so they get dropped when the last sensor
/// using them goes away.
pub type I2CDeviceMap = HashMap<I2CDeviceParams, std::sync::Weak<I2CDevice>>;

/// Find an existing [`I2CDevice`] in `devmap` for the given `params`, or instantiate one
/// and add it to `devmap` if not.  Returns:
///
///  * `Ok(Some(_))` if an existing device was found or a new one successfully instantiated.
///  * `Ok(None)` if the device is static.
///  * `Err(_)` on error.
pub fn get_i2cdev(devmap: &mut I2CDeviceMap, params: &I2CDeviceParams)
                  -> ErrResult<Option<Arc<I2CDevice>>>
{
	let d = devmap.get(params).and_then(|w| w.upgrade());
	if d.is_some() {
		Ok(d)
	} else {
		let dev = params.instantiate_device()?;
		if let Some(ref d) = dev {
			devmap.insert(params.clone(), Arc::downgrade(d));
		}
		Ok(dev)
	}
}