Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions embedded-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub mod init;
pub mod ipc;
pub mod keyboard;
pub mod relay;
pub mod service;
pub mod sync;

/// Hidden re-exports used by macros defined in this crate.
Expand Down
92 changes: 92 additions & 0 deletions embedded-service/src/service.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//! This module contains helper traits and functions for services that run on the EC.

/// A trait for a service that can be run on the EC.
/// Implementations of Service should have an init() function to construct the service that
/// returns a Runner, which the user is expected to spawn a task for.
pub trait Service<'hw>: Sized {
/// A type that can be used to run the service. This is returned by the init() function and the user is
/// expected to call its run() method in an embassy task (or similar parallel execution context on other
/// async runtimes).
type Runner: ServiceRunner<'hw>;

/// Any memory resources that your service needs. This is typically an opaque types that is only used by the service
/// and is not interacted with by users of the service. Must be default-constructible for spawn_service!() to work.
type Resources: ServiceResources;

/// The error type that your `init` function can return on failure.
type ErrorType;

/// Any initialization parameters that your service needs to run.
type InitParams;

/// Initializes an instance of the service using the provided storage and returns a control handle for the service and
/// a runner that can be used to run the service.
fn new(
storage: &'hw mut Self::Resources,
params: Self::InitParams,
) -> impl core::future::Future<Output = Result<(Self, Self::Runner), Self::ErrorType>>;
}

/// Represents the memory resources that a service needs to run. This is typically an opaque type that is only used by the service.
/// Must be default-constructible; the service is expected to populate its contents in its init() function.
pub trait ServiceResources {
/// Creates a new instance of the service's resources.
fn new() -> Self;
}

/// A trait for a run handle used to execute a service's event loop. This is returned by Service::init()
/// and the user is expected to call its run() method in an embassy task (or similar parallel execution context
/// on other async runtimes).
pub trait ServiceRunner<'hw> {
/// Run the service event loop. This future never completes.
fn run(self) -> impl core::future::Future<Output = crate::Never> + 'hw;
}

/// Initializes a service, creates an embassy task to run it, and spawns that task.
///
/// This macro handles the boilerplate of:
/// 1. Creating a `static` [`OnceLock`](embassy_sync::once_lock::OnceLock) to hold the service
/// 2. Calling the service's `init()` method
/// 3. Defining an embassy_executor::task to run the service
/// 4. Spawning the task on the provided executor
///
/// Returns a Result<reference-to-service, Error> where Error is the error type of $service_ty::init().
///
/// Arguments
///
/// - spawner: An embassy_executor::Spawner.
/// - service_ty: The service type that implements Service that you want to create and run.
/// - init_arg: The init argument type to pass to `Service::init()`
///
/// Example:
///
/// ```ignore
/// let time_service = embedded_services::spawn_service!(
/// spawner,
/// time_alarm_service::Service<'static>,
/// time_alarm_service::ServiceInitParams { dt_clock, tz, ac_expiration, ac_policy, dc_expiration, dc_policy }
/// ).expect("failed to initialize time_alarm service");
/// ```
#[macro_export]
macro_rules! spawn_service {
($spawner:expr, $service_ty:ty, $init_arg:expr) => {{
use $crate::service::{Service, ServiceResources, ServiceRunner};
static SERVICE_RESOURCES: StaticCell<(<$service_ty as Service>::Resources)> = StaticCell::new();
let service_resources =
SERVICE_RESOURCES.init(<<$service_ty as Service>::Resources as ServiceResources>::new());

#[embassy_executor::task]
async fn service_task_fn(runner: <$service_ty as $crate::service::Service<'static>>::Runner) {
runner.run().await;
}

<$service_ty>::new(service_resources, $init_arg)
.await
.map(|(control_handle, runner)| {
$spawner.must_spawn(service_task_fn(runner));
control_handle
})
}};
}

pub use spawn_service;
76 changes: 61 additions & 15 deletions espi-service/src/espi_service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use embassy_futures::select::select;
use embassy_imxrt::espi;
use embassy_sync::channel::Channel;
use embassy_sync::mutex::Mutex;
use embassy_sync::once_lock::OnceLock;
use embedded_services::{GlobalRawMutex, error, info, trace};
use mctp_rs::smbus_espi::SmbusEspiMedium;
use mctp_rs::smbus_espi::SmbusEspiReplyContext;
Expand Down Expand Up @@ -32,28 +31,77 @@ pub enum Error {
Buffer(embedded_services::buffer::Error),
}

/// The memory required by the eSPI service to run
pub struct Resources<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> {
inner: Option<ServiceInner<'hw, RelayHandler>>,
}

impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> embedded_services::service::ServiceResources
for Resources<'hw, RelayHandler>
{
fn new() -> Self {
Self { inner: None }
}
}

/// Service runner for the eSPI service. Users must call the run() method on the service to start processing events.
pub struct Runner<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> {
inner: &'hw ServiceInner<'hw, RelayHandler>,
}

impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> embedded_services::service::ServiceRunner<'hw>
for Runner<'hw, RelayHandler>
{
/// Run the service event loop.
async fn run(self) -> embedded_services::Never {
self.inner.run().await
}
}

pub struct Service<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> {
_inner: &'hw ServiceInner<'hw, RelayHandler>,
}

impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> embedded_services::service::Service<'hw>
for Service<'hw, RelayHandler>
{
type Resources = Resources<'hw, RelayHandler>;
type Runner = Runner<'hw, RelayHandler>;
type ErrorType = core::convert::Infallible;
type InitParams = InitParams<'hw, RelayHandler>;

async fn new(
resources: &'hw mut Self::Resources,
params: InitParams<'hw, RelayHandler>,
) -> Result<(Self, Self::Runner), core::convert::Infallible> {
let inner = resources.inner.insert(ServiceInner::new(params).await);
Ok((Self { _inner: inner }, Runner { inner }))
}
}

pub struct InitParams<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> {
pub espi: espi::Espi<'hw>,
pub relay_handler: RelayHandler,
}

struct ServiceInner<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> {
espi: Mutex<GlobalRawMutex, espi::Espi<'hw>>,
host_tx_queue: Channel<GlobalRawMutex, HostResultMessage<RelayHandler>, HOST_TX_QUEUE_SIZE>,
relay_handler: RelayHandler,
}

impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Service<'hw, RelayHandler> {
pub async fn init(
service_storage: &'hw OnceLock<Self>,
mut espi: espi::Espi<'hw>,
relay_handler: RelayHandler,
) -> &'hw Self {
espi.wait_for_plat_reset().await;
impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> ServiceInner<'hw, RelayHandler> {
async fn new(mut init_params: InitParams<'hw, RelayHandler>) -> Self {
init_params.espi.wait_for_plat_reset().await;

service_storage.get_or_init(|| Service {
espi: Mutex::new(espi),
Self {
espi: Mutex::new(init_params.espi),
host_tx_queue: Channel::new(),
relay_handler,
})
relay_handler: init_params.relay_handler,
}
}

pub(crate) async fn run_service(&self) -> ! {
async fn run(&self) -> embedded_services::Never {
let mut espi = self.espi.lock().await;
loop {
let event = select(espi.wait_for_event(), self.host_tx_queue.receive()).await;
Expand Down Expand Up @@ -180,9 +228,7 @@ impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Service<'h
}
Ok(())
}
}

impl<'hw, RelayHandler: embedded_services::relay::mctp::RelayHandler> Service<'hw, RelayHandler> {
async fn process_request_to_ec(
&self,
(header, body): (
Expand Down
3 changes: 0 additions & 3 deletions espi-service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,5 @@
#[cfg(not(test))]
mod espi_service;

#[cfg(not(test))]
pub mod task;

#[cfg(not(test))]
pub use espi_service::*;
7 changes: 0 additions & 7 deletions espi-service/src/task.rs

This file was deleted.

34 changes: 13 additions & 21 deletions examples/rt685s-evk/src/bin/time_alarm.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#![no_std]
#![no_main]

use embassy_sync::once_lock::OnceLock;
use embedded_mcu_hal::{
Nvram,
time::{Datetime, Month, UncheckedDatetime},
Expand All @@ -24,38 +23,31 @@ async fn main(spawner: embassy_executor::Spawner) {
embedded_services::init().await;
info!("services initialized");

static TIME_SERVICE: OnceLock<time_alarm_service::Service> = OnceLock::new();
let time_service = time_alarm_service::Service::init(
&TIME_SERVICE,
dt_clock,
tz,
ac_expiration,
ac_policy,
dc_expiration,
dc_policy,
let time_service = embedded_services::spawn_service!(
spawner,
time_alarm_service::Service<'static>,
time_alarm_service::InitParams {
backing_clock: dt_clock,
tz_storage: tz,
ac_expiration_storage: ac_expiration,
ac_policy_storage: ac_policy,
dc_expiration_storage: dc_expiration,
dc_policy_storage: dc_policy
}
)
.await
.expect("Failed to initialize time-alarm service");

#[embassy_executor::task]
async fn time_alarm_task(service: &'static time_alarm_service::Service<'static>) {
time_alarm_service::task::run_service(service).await
}

spawner.must_spawn(time_alarm_task(time_service));
.expect("Failed to spawn time alarm service");

use embedded_services::relay::mctp::impl_odp_mctp_relay_handler;
impl_odp_mctp_relay_handler!(
EspiRelayHandler;
TimeAlarm, 0x0B, time_alarm_service::Service<'static>;
);

let _relay_handler = EspiRelayHandler::new(time_service);
let _relay_handler = EspiRelayHandler::new(&time_service);

// Here, you'd normally pass _relay_handler to your relay service (e.g. eSPI service).
// In this example, we're not leveraging a relay service, so we'll just demonstrate some direct calls.
//

time_service
.set_real_time(AcpiTimestamp {
datetime: Datetime::new(UncheckedDatetime {
Expand Down
Loading