riot_wrappers/saul/registration.rs
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
//! Tools for registering a Rust device in SAUL
//!
//! A SAUL sensor or actuator is expressed as an implementation of [Drivable]. Once built, that
//! drivable is registered at SAUL, either through [register_and_then], or through building a
//! [Registration] for a `'static` place and calling [Registration::register_static].
//!
//! As SAUL decouples the per-type parts of a sensor from the per-instance parts, there is a
//! [Driver] struct that manages the per-type aspects. This driver also manages the dynamic
//! dispatch by being generic over the [Drivable] and exposing untyped function pointers. (In a
//! sense, SAUL ships its own version of Rust's `dyn`, and Driver manages that).
use core::ffi::CStr;
use riot_sys::libc;
use super::{Class, Phydat};
use crate::error::NegativeErrorExt;
/// The single error read and write operations may produce; corresponds to an `-ECANCELED`.
/// (-ENOTSUP is expressed by not having support for the operation in the first place, indicated by
/// the `HAS_{READ,WRITE}` consts).
pub struct Error;
/// API through which SAUL operations are done
///
/// This is typically implemented on a `&T` (where T is what the Driver and Registration is for),
/// but can be alternatively implemented on a newtype around such pointers to drive various aspects
/// of a device.
pub trait Drivable: Sized {
/// Sensor class (type)
const CLASS: Class;
/// Set to true if `read` is implemented.
///
/// Doing this on the type level (rather than having read and write return a more
/// differentiated error) allows the driver to point to the shared [riot_sys::saul_read_notsup]
/// / [riot_sys::saul_write_notsup] handler rather than to monomorphize a custom erring handler
/// for each device.
const HAS_READ: bool = false;
/// Set to true if `write` is implemented.
const HAS_WRITE: bool = false;
/// Read the current state
fn read(self) -> Result<Phydat, Error> {
// This function's presence in generated code should already show that something is
// configured badly; could consider making that a linker error (but riot-wrappers is not in
// the habit of doing that).
unimplemented!("Sensor reading not implemented; HAS_READ should not have been set.")
}
/// Set the state of an actuator, or reconfigure a sensor
///
/// A &self is passed in on write because there could be concurrent access from multiple SAUL
/// users. One option of handling this is to implement Drivable for `Mutex<T>`.
///
/// Note that due to the way SAUL is structured, the drivable can not know the number of
/// entries which the user intended to set. The Drivable trait always builds the Rust Phydat
/// (which contains a length) with the maximum available length (some of which may contain
/// uninitialized data, which is OK as i16 has no uninhabited values), and the writer needs to
/// return how many of the entries it actually used.
fn write(self, _data: &Phydat) -> Result<u8, Error> {
// See also comment in read()
unimplemented!("Sensor writing not implemented; HAS_READ should not have been set.")
}
}
/// A typed saul_driver_t, created from a Drivable's build_driver() static method, and used as
/// statically lived references registrations.
///
/// `DEV` indicates the type of the item pointed to in the registration's field, which is usually the
/// Drivable itself, but may be specialized by AsRef into a particular drivable, eg. when a device
/// is used by two drivers representing different aspects of the device.
pub struct Driver<DEV, DRIV = &'static DEV>
where
DEV: Sized + Sync + 'static,
&'static DEV: Into<DRIV>,
DRIV: Drivable + 'static,
{
driver: riot_sys::saul_driver_t,
_phantom: core::marker::PhantomData<(DEV, DRIV)>,
}
// While the old supported RIOT version has a `saul_notsup` and the new has
// `saul_{read,write}_notsup`, it's easier to just implement our own. After the 2022.07 release
// they can go away again, and users go with riot_sys::saul_[...]_notsup
extern "C" fn saul_read_notsup(_dev: *const libc::c_void, _dat: *mut riot_sys::phydat_t) -> i32 {
-(riot_sys::ENOTSUP as i32)
}
extern "C" fn saul_write_notsup(_dev: *const libc::c_void, _dat: *const riot_sys::phydat_t) -> i32 {
-(riot_sys::ENOTSUP as i32)
}
impl<DEV, DRIV> Driver<DEV, DRIV>
where
DEV: Sized + Sync + 'static,
&'static DEV: Into<DRIV>,
DRIV: Drivable + 'static,
{
pub const fn new() -> Self {
Driver {
driver: riot_sys::saul_driver_t {
read: if DRIV::HAS_READ {
Some(Self::read_raw)
} else {
Some(saul_read_notsup)
},
write: if DRIV::HAS_WRITE {
Some(Self::write_raw)
} else {
Some(saul_write_notsup)
},
type_: DRIV::CLASS.to_c(),
},
_phantom: core::marker::PhantomData,
}
}
unsafe extern "C" fn read_raw(dev: *const libc::c_void, res: *mut riot_sys::phydat_t) -> i32 {
let device = &*(dev as *const DEV);
let device = device.into();
match device.read() {
Ok(d) => {
res.write(d.values);
d.length.into()
}
// The only legal device error -- ENOTSUP would mean there's no handler at all
Err(_) => -(riot_sys::ECANCELED as i32),
}
}
unsafe extern "C" fn write_raw(
dev: *const libc::c_void,
data: *const riot_sys::phydat_t,
) -> i32 {
let device = &*(dev as *const DEV);
let device = device.into();
let data = *data;
// PHYDAT_DIM: See write documentation
let data = Phydat {
values: data,
length: riot_sys::PHYDAT_DIM as _,
};
match device.write(&data) {
Ok(n) => n as _,
// The only legal device error -- ENOTSUP would mean there's no handler at all
Err(_) => -(riot_sys::ECANCELED as i32),
}
}
}
// unsafe: All the content we have in the inner struct is Send (being just plain functions), so is
// the whole. (We don't store the DEV pointer here, so we don't need DEV Sync, but Driver needs it)
unsafe impl<DEV, DRIV> Send for Driver<DEV, DRIV>
where
DEV: Sized + Sync + 'static,
&'static DEV: Into<DRIV>,
DRIV: Drivable + 'static,
{
}
pub struct Registration<DEV, DRIV = &'static DEV>
where
DEV: Sized + Sync + 'static,
&'static DEV: Into<DRIV>,
DRIV: Drivable + 'static,
{
reg: riot_sys::saul_reg_t,
_phantom: core::marker::PhantomData<(&'static DEV, DRIV)>,
}
// unsafe: The registration is as Send as the pointer to the DEV it contains (and for DEV Sync is
// required)
unsafe impl<DEV, DRIV> Send for Registration<DEV, DRIV>
where
DEV: Sized + Sync + 'static,
&'static DEV: Into<DRIV>,
DRIV: Drivable + 'static,
{
}
impl<DEV, DRIV> Registration<DEV, DRIV>
where
DEV: Sized + Sync + 'static,
&'static DEV: Into<DRIV>,
DRIV: Drivable + 'static,
{
// Unlike in the old implementation, no attempt is made to build it short-lived and then
// upgrade -- not for lifetime reasons, but because for `register_static` all is already
// static anyway, and `build_with` can just as well be called with the components, claim their
// lifetime first and then go through here.
pub fn new(
driver: &'static Driver<DEV, DRIV>,
device: &'static DEV,
name: Option<&'static CStr>,
) -> Self {
Registration {
reg: riot_sys::saul_reg_t {
next: core::ptr::null_mut(),
dev: device as *const _ as *mut _,
name: name.map(|n| n.as_ptr() as _).unwrap_or(core::ptr::null()),
driver: &driver.driver as *const _,
},
_phantom: core::marker::PhantomData,
}
}
/// Hook the registration in with the global SAUL list
///
/// If you can not obtain a &'static, you may consider [`register_and_then()`].
pub fn register_static(&'static mut self) {
(unsafe { riot_sys::saul_reg_add(&mut self.reg) })
.negative_to_error()
.expect("Constructed registries are always valid");
}
}
/// Hook the registration in with the global SAUL list
///
/// Compared to [`Registration::register_static()`], this is convenient for threads that run
/// forever and which just need a reference to move into an infinitely executing closure to get the
/// same guarantees as from a static reference.
// It would be nice to have a helper function that proves the infinite lifetime independently
// of the registration, but none such is known.
//
// If unwinding is ever added in RIOT, this will need a guard similar to the one in the
// `replace_with` crate.
pub fn register_and_then<DEV, DRIV>(
driver: &Driver<DEV, DRIV>,
device: &DEV,
name: Option<&CStr>,
f: impl FnOnce() -> crate::never::Never,
) -> !
where
DEV: Sized + Sync + 'static,
&'static DEV: Into<DRIV>,
DRIV: Drivable + 'static,
{
// Reborrow for 'static lifetime.
//
// This is safe because we never terminate, and thus keep these references forever.
// (It could be done prettier, but see above on preferring to have a proper library for
// this anyway).
let (driver, device, name) = unsafe { core::mem::transmute((driver, device, name)) };
let mut registration = Registration::<DEV, DRIV>::new(driver, device, name);
(unsafe { riot_sys::saul_reg_add(&mut registration.reg) })
.negative_to_error()
.expect("Constructed registries are always valid");
f()
}