Skip to content

Commit 07c4d4f

Browse files
committed
Add notification support for relay services
1 parent 451c75f commit 07c4d4f

3 files changed

Lines changed: 158 additions & 12 deletions

File tree

embedded-service/src/relay/mod.rs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,9 @@ pub mod mctp {
164164
type ResultEnumType: for<'buf> mctp_rs::MctpMessageTrait<'buf, Header = Self::HeaderType>
165165
+ RelayResponse<Self::ServiceIdType, Self::HeaderType>;
166166

167+
/// Returns a reference to the notification `Listener`.
168+
fn notification_listener(&self) -> &crate::relay::notifications::Listener<'_>;
169+
167170
/// Process the provided request and yield a result.
168171
fn process_request<'a>(
169172
&'a self,
@@ -449,18 +452,21 @@ pub mod mctp {
449452

450453

451454
pub struct $relay_type_name<'hw> {
455+
listener: $crate::relay::notifications::Listener<'hw>,
452456
$(
453457
[<$service_name:snake _handler>]: &'hw $service_handler_type,
454458
)+
455459
}
456460

457461
impl<'hw> $relay_type_name<'hw> {
458462
pub fn new(
463+
listener: $crate::relay::notifications::Listener<'hw>,
459464
$(
460465
[<$service_name:snake _handler>]: &'hw $service_handler_type,
461466
)+
462467
) -> Self {
463468
Self {
469+
listener,
464470
$(
465471
[<$service_name:snake _handler>],
466472
)+
@@ -474,6 +480,10 @@ pub mod mctp {
474480
type RequestEnumType = HostRequest;
475481
type ResultEnumType = HostResult;
476482

483+
fn notification_listener(&self) -> & $crate::relay::notifications::Listener<'_> {
484+
&self.listener
485+
}
486+
477487
fn process_request<'a>(
478488
&'a self,
479489
message: HostRequest,
@@ -501,3 +511,129 @@ pub mod mctp {
501511

502512
pub use impl_odp_mctp_relay_handler;
503513
}
514+
515+
/// Relay service notification support.
516+
pub mod notifications {
517+
use crate::GlobalRawMutex;
518+
use core::sync::atomic::{AtomicBool, Ordering};
519+
use embassy_sync::channel;
520+
521+
/// Notification error.
522+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
523+
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
524+
pub enum Error {
525+
/// A notification is currently being processed.
526+
Busy,
527+
/// A [`Listener`] has already been instantiated.
528+
ListenerInstantiated,
529+
}
530+
531+
/// Notifier.
532+
///
533+
/// Used by services to send notifications to the relay service.
534+
pub struct Notifier<'ch> {
535+
id: u8,
536+
sender: channel::Sender<'ch, GlobalRawMutex, u8, 1>,
537+
}
538+
539+
impl<'ch> Notifier<'ch> {
540+
/// Notify the relay service that the service holding this [`Notifier`] needs attention from the host.
541+
///
542+
/// This will wait if a different notification is currently being processed.
543+
pub async fn notify(&self) {
544+
self.sender.send(self.id).await
545+
}
546+
547+
/// Try to notify the relay service that the service holding this [`Notifier`] needs attention from the host.
548+
///
549+
/// # Errors
550+
///
551+
/// Returns [`Error::Busy`] if a different notification is currently being processed.
552+
pub fn try_notify(&self) -> Result<(), Error> {
553+
self.sender.try_send(self.id).map_err(|_| Error::Busy)
554+
}
555+
}
556+
557+
/// Listener.
558+
pub struct Listener<'ch> {
559+
receiver: channel::Receiver<'ch, GlobalRawMutex, u8, 1>,
560+
}
561+
562+
impl<'ch> Listener<'ch> {
563+
/// Wait for a notification from any service, then returns the id associated with that notification.
564+
pub async fn listen(&self) -> u8 {
565+
self.receiver.receive().await
566+
}
567+
568+
/// Try to listen for a notification from any service.
569+
///
570+
/// Returns [`None`] if no notification is currently available.
571+
pub fn try_listen(&self) -> Option<u8> {
572+
self.receiver.try_receive().ok()
573+
}
574+
}
575+
576+
/// Notification handler.
577+
pub struct NotificationHandler {
578+
// The channel size is fixed to 1 (and not exposed as a configurable generic) so that
579+
// it does not need to be exposed to consumers of `Notifier` and `Listener`.
580+
//
581+
// This is reasonable because notifications are expected to be handled quickly,
582+
// and it is unlikely that multiple services will need to notify the host simultaneously.
583+
//
584+
// In the event that they do, the channel still provides backpressure so no notifications
585+
// will be lost and the expected latency is minimal.
586+
channel: channel::Channel<GlobalRawMutex, u8, 1>,
587+
listener_instantiated: AtomicBool,
588+
}
589+
590+
impl Default for NotificationHandler {
591+
fn default() -> Self {
592+
Self::new()
593+
}
594+
}
595+
596+
impl NotificationHandler {
597+
/// Create a new [`NotificationHandler`] instance.
598+
///
599+
/// This handler allows as many [`Notifier`]s to be created as needed,
600+
/// but only one [`Listener`] may be created.
601+
pub fn new() -> Self {
602+
Self {
603+
channel: channel::Channel::new(),
604+
listener_instantiated: AtomicBool::new(false),
605+
}
606+
}
607+
608+
/// Create a new [`Notifier`] instance with given id.
609+
///
610+
/// This [`Notifier`] is then typically passed to services upon instantiation.
611+
///
612+
/// The meaning of the id is platform specific and is opaque to services,
613+
/// but expectation is that the relay service understands how to interpret the id.
614+
///
615+
/// Thus, the caller should ensure the given id matches what the relay service expects.
616+
pub fn new_notifier(&self, id: u8) -> Notifier<'_> {
617+
Notifier {
618+
id,
619+
sender: self.channel.sender(),
620+
}
621+
}
622+
623+
/// Create a new [`Listener`] instance.
624+
///
625+
/// # Errors
626+
///
627+
/// Only one [`Listener`] may exist.
628+
/// Attempting to create another will return [`Error::ListenerInstantiated`].
629+
pub fn new_listener(&self) -> Result<Listener<'_>, Error> {
630+
if !self.listener_instantiated.swap(true, Ordering::Relaxed) {
631+
Ok(Listener {
632+
receiver: self.channel.receiver(),
633+
})
634+
} else {
635+
Err(Error::ListenerInstantiated)
636+
}
637+
}
638+
}
639+
}

espi-service/src/espi_service.rs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use core::slice;
22

3-
use embassy_futures::select::select;
3+
use embassy_futures::select::select3;
44
use embassy_imxrt::espi;
55
use embassy_sync::channel::Channel;
66
use embassy_sync::mutex::Mutex;
@@ -56,31 +56,32 @@ impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Service<'h
5656
pub(crate) async fn run_service(&self) -> ! {
5757
let mut espi = self.espi.lock().await;
5858
loop {
59-
let event = select(espi.wait_for_event(), self.host_tx_queue.receive()).await;
59+
let event = select3(
60+
espi.wait_for_event(),
61+
self.host_tx_queue.receive(),
62+
self.relay_handler.notification_listener().listen(),
63+
)
64+
.await;
6065

6166
match event {
62-
embassy_futures::select::Either::First(controller_event) => {
67+
embassy_futures::select::Either3::First(controller_event) => {
6368
self.process_controller_event(&mut espi, controller_event)
6469
.await
6570
.unwrap_or_else(|e| {
6671
error!("Critical error processing eSPI controller event: {:?}", e);
6772
});
6873
}
69-
embassy_futures::select::Either::Second(host_msg) => {
74+
embassy_futures::select::Either3::Second(host_msg) => {
7075
self.process_response_to_host(&mut espi, host_msg).await
7176
}
77+
embassy_futures::select::Either3::Third(id) => {
78+
espi.irq_push(id).await;
79+
info!("espi: Notification id {} sent to Host!", id);
80+
}
7281
}
7382
}
7483
}
7584

76-
// TODO The notification system was not actually used, so this is currently dead code.
77-
// We need to implement some interface for triggering notifications from other subsystems, and it may do something like this:
78-
//
79-
// async fn process_notification_to_host(&self, espi: &mut espi::Espi<'_>, notification: &NotificationMsg) {
80-
// espi.irq_push(notification.offset).await;
81-
// info!("espi: Notification id {} sent to Host!", notification.offset);
82-
// }
83-
8485
fn write_to_hw(&self, espi: &mut espi::Espi<'hw>, packet: &[u8]) -> Result<(), embassy_imxrt::espi::Error> {
8586
// Send packet via your transport medium
8687
// SAFETY: Safe as the access to espi is protected by a mut reference.

thermal-service/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#![allow(clippy::unwrap_used)]
55

66
use embedded_sensors_hal_async::temperature::DegreesCelsius;
7+
use embedded_services::relay::notifications::Notifier;
78
use thermal_service_messages::{ThermalRequest, ThermalResult};
89

910
mod context;
@@ -36,21 +37,29 @@ pub enum Event {
3637

3738
pub struct Service<'hw> {
3839
context: context::Context<'hw>,
40+
notifier: Notifier<'hw>,
3941
}
4042

4143
impl<'hw> Service<'hw> {
4244
pub async fn init(
4345
service_storage: &'hw embassy_sync::once_lock::OnceLock<Service<'hw>>,
4446
sensors: &'hw [&'hw sensor::Device],
4547
fans: &'hw [&'hw fan::Device],
48+
notifier: Notifier<'hw>,
4649
) -> &'hw Self {
4750
service_storage.get_or_init(|| Self {
4851
context: context::Context::new(sensors, fans),
52+
notifier,
4953
})
5054
}
5155

5256
/// Send a thermal event
5357
pub async fn send_event(&self, event: Event) {
58+
// If a threshold event is triggered, we want to notify host
59+
if matches!(event, Event::ThresholdExceeded(_, _, _) | Event::ThresholdCleared(_, _)) {
60+
self.notifier.notify().await;
61+
}
62+
5463
self.context.send_event(event).await
5564
}
5665

0 commit comments

Comments
 (0)