Skip to content

Commit 41cd2de

Browse files
committedMar 14, 2025
Initial cfu-service definitions
Create Cfu-Service crate with host and client definitions. Update readme
1 parent 8f459e4 commit 41cd2de

File tree

5 files changed

+345
-7
lines changed

5 files changed

+345
-7
lines changed
 

‎.github/workflows/check.yml

+6-6
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
fail-fast: false
6060
matrix:
6161
commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}
62-
workdir: [ "embedded-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
62+
workdir: [ "embedded-service", "cfu-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
6363
steps:
6464
- uses: actions/checkout@v4
6565
with:
@@ -86,7 +86,7 @@ jobs:
8686
# Get early warning of new lints which are regularly introduced in beta channels.
8787
toolchain: [stable, beta]
8888
commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}
89-
workdir: [ "embedded-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
89+
workdir: [ "embedded-service", "cfu-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
9090
steps:
9191
- uses: actions/checkout@v4
9292
with:
@@ -136,7 +136,7 @@ jobs:
136136
fail-fast: false
137137
matrix:
138138
commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}
139-
workdir: [ "embedded-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
139+
workdir: [ "embedded-service", "cfu-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
140140
steps:
141141
- uses: actions/checkout@v4
142142
with:
@@ -162,7 +162,7 @@ jobs:
162162
fail-fast: false
163163
matrix:
164164
commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}
165-
workdir: [ "embedded-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
165+
workdir: [ "embedded-service", "cfu-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
166166
steps:
167167
- uses: actions/checkout@v4
168168
with:
@@ -188,7 +188,7 @@ jobs:
188188
fail-fast: false
189189
matrix:
190190
commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}
191-
workdir: [ "embedded-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
191+
workdir: [ "embedded-service", "cfu-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
192192
steps:
193193
- uses: actions/checkout@v4
194194
with:
@@ -214,7 +214,7 @@ jobs:
214214
fail-fast: false
215215
matrix:
216216
commit: ${{ fromJSON(needs.commit_list.outputs.commits) }}
217-
workdir: [ "embedded-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
217+
workdir: [ "embedded-service", "cfu-service", "espi-service", "hid-service", "power-button-service", "storage-bus"]
218218
msrv: ["1.83"] # We're relying on namespaced-features, which
219219
# was released in 1.60
220220
#

‎README.md

+5-1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,10 @@ EC service houses the business logic that glues the EC peripheral Rust driver +
8686
- library crate
8787
- hid-service
8888
- library crate
89+
- cfu-service
90+
- library crate
91+
- host traits
92+
- client traits
8993

9094
### embedded-services
9195

@@ -102,7 +106,7 @@ Protocol agnostric transport allowing dynamic number of endpoints to send and re
102106
erDiagram
103107
service_a ||..|| endpoint_a :contains
104108
endpoint_a ||..|| transport : send_message
105-
transport ||--|{ endpoint_b : route
109+
transport ||--|| endpoint_b : route
106110
service_b ||..|| endpoint_b :contains
107111
transport {
108112
list endpoint_a

‎cfu-service/Cargo.toml

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[package]
2+
name = "cfu-service"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[dependencies]
7+
# ODP dependencies
8+
embedded-cfu-protocol = { git = "https://github.com/OpenDevicePartnership/embedded-cfu" }
9+
embedded-services = { path = "../embedded-service" }
10+
11+
# External dependencies
12+
binary_serde = "1.0.24"
13+
embassy-time = { git = "https://github.com/embassy-rs/embassy" }
14+
embassy-sync = { git = "https://github.com/embassy-rs/embassy" }
15+
embassy-executor = { git = "https://github.com/embassy-rs/embassy" }
16+
log = { version = "0.4.14", optional = true }
17+
defmt = { version = "0.3", optional = true }
18+
heapless = "0.8.*"
19+
20+
[features]
21+
default = []
22+
defmt = [
23+
"dep:defmt",
24+
"embedded-services/defmt",
25+
"embassy-time/defmt",
26+
"embassy-sync/defmt",
27+
"embassy-executor/defmt",
28+
"embedded-cfu-protocol/defmt",
29+
]
30+
log = [
31+
"dep:log",
32+
"embedded-services/log",
33+
"embassy-time/log",
34+
"embassy-sync/log",
35+
"embassy-executor/log",
36+
"embedded-cfu-protocol/log",
37+
]

‎cfu-service/src/host.rs

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use core::future::Future;
2+
3+
use binary_serde::{BinarySerde, Endianness};
4+
use embedded_cfu_protocol::components::CfuComponentTraits;
5+
use embedded_cfu_protocol::host::{CfuHostStates, CfuUpdater};
6+
use embedded_cfu_protocol::protocol_definitions::*;
7+
use embedded_cfu_protocol::{CfuImage, CfuWriter, CfuWriterDefault, CfuWriterError};
8+
use heapless::Vec;
9+
10+
use crate::CfuError;
11+
12+
/// All host side Cfu traits, in some cases this will originate from a OS driver for CFU
13+
pub trait CfuHost: CfuHostStates {
14+
/// Get all images
15+
fn get_cfu_images<I: CfuImage>(&self) -> impl Future<Output = Result<Vec<I, MAX_CMPT_COUNT>, CfuError>>;
16+
/// Gets the firmware version of all components
17+
fn get_all_fw_versions<T: CfuWriter>(
18+
self,
19+
writer: &mut T,
20+
primary_cmpt: ComponentId,
21+
) -> impl Future<Output = Result<GetFwVersionResponse, CfuError>>;
22+
/// Goes through the offer list and returns a slice of offer responses
23+
fn process_cfu_offers<'a, T: CfuWriter>(
24+
offer_commands: &'a [FwUpdateOfferCommand],
25+
writer: &mut T,
26+
) -> impl Future<Output = Result<&'a [FwUpdateOfferResponse], CfuError>>;
27+
/// For a specific component, update its content
28+
fn update_cfu_content<T: CfuWriter>(
29+
writer: &mut T,
30+
) -> impl Future<Output = Result<FwUpdateContentResponse, CfuError>>;
31+
/// For a specific image that was updated, validate its content
32+
fn is_cfu_image_valid<I: CfuImage>(image: I) -> impl Future<Output = Result<bool, CfuError>>;
33+
}
34+
35+
pub struct CfuHostInstance<I: CfuImage, C: CfuComponentTraits> {
36+
pub updater: CfuUpdater,
37+
pub images: heapless::Vec<I, MAX_CMPT_COUNT>,
38+
pub writer: CfuWriterDefault,
39+
pub primary_cmpt: C,
40+
pub host_token: HostToken,
41+
}
42+
43+
impl<I: CfuImage, C: CfuComponentTraits> CfuHostInstance<I, C> {
44+
#[allow(unused)]
45+
fn new(primary_cmpt: C) -> Self {
46+
Self {
47+
updater: CfuUpdater {},
48+
images: Vec::new(),
49+
writer: CfuWriterDefault::default(),
50+
primary_cmpt,
51+
host_token: 0,
52+
}
53+
}
54+
}
55+
56+
impl<I: CfuImage, C: CfuComponentTraits> CfuHostStates for CfuHostInstance<I, C> {
57+
async fn start_transaction<T: CfuWriter>(self, _writer: &mut T) -> Result<FwUpdateOfferResponse, CfuProtocolError> {
58+
let component_id = self.primary_cmpt.get_component_id();
59+
let _mock_cmd = FwUpdateOfferCommand::new_with_command(
60+
self.host_token,
61+
component_id,
62+
FwVersion::default(),
63+
0,
64+
InformationCodeValues::StartOfferList,
65+
0,
66+
);
67+
let mockresponse = FwUpdateOfferResponse::default();
68+
Ok(mockresponse)
69+
}
70+
async fn notify_start_offer_list<T: CfuWriter>(
71+
self,
72+
writer: &mut T,
73+
) -> Result<FwUpdateOfferResponse, CfuProtocolError> {
74+
// Serialize FwUpdateOfferCommand to bytes, pull out componentid, host token
75+
let component_id = self.primary_cmpt.get_component_id();
76+
let mock_cmd = FwUpdateOfferCommand::new_with_command(
77+
self.host_token,
78+
component_id,
79+
FwVersion::default(),
80+
0,
81+
InformationCodeValues::StartOfferList,
82+
0,
83+
);
84+
let mut serialized_mock = [0u8; FwUpdateOfferCommand::SERIALIZED_SIZE];
85+
FwUpdateOfferCommand::binary_serialize(&mock_cmd, &mut serialized_mock, Endianness::Little);
86+
let mut read = [0u8; FwUpdateOfferResponse::SERIALIZED_SIZE];
87+
//self.primary_cmpt.me.writer.write_read_to_component(Some(component_id), &serialized_mock, &mut read).await;
88+
if let Ok(_result) = writer.cfu_write_read(None, &serialized_mock, &mut read).await {
89+
if let Ok(converted) = FwUpdateOfferResponse::binary_deserialize(&read, Endianness::Little) {
90+
if converted.status != CfuOfferStatus::Accept {
91+
Err(CfuProtocolError::CfuStatusError(converted.status))
92+
} else {
93+
Ok(converted)
94+
}
95+
} else {
96+
Err(CfuProtocolError::WriterError(CfuWriterError::ByteConversionError))
97+
}
98+
} else {
99+
Err(CfuProtocolError::WriterError(CfuWriterError::StorageError))
100+
}
101+
}
102+
103+
async fn notify_end_offer_list<T: CfuWriter>(
104+
self,
105+
writer: &mut T,
106+
) -> Result<FwUpdateOfferResponse, CfuProtocolError> {
107+
let component_id = self.primary_cmpt.get_component_id();
108+
let mock_cmd = FwUpdateOfferCommand::new_with_command(
109+
self.host_token,
110+
component_id,
111+
FwVersion::default(),
112+
0,
113+
InformationCodeValues::EndOfferList,
114+
0,
115+
);
116+
let mut serialized_mock = [0u8; FwUpdateOfferCommand::SERIALIZED_SIZE];
117+
FwUpdateOfferCommand::binary_serialize(&mock_cmd, &mut serialized_mock, Endianness::Little);
118+
let mut read = [0u8; FwUpdateOfferResponse::SERIALIZED_SIZE];
119+
if writer.cfu_write_read(None, &serialized_mock, &mut read).await.is_ok() {
120+
// convert back to FwUpdateOfferResponse
121+
if let Ok(converted) = FwUpdateOfferResponse::binary_deserialize(&read, Endianness::Little) {
122+
Ok(converted)
123+
} else {
124+
// error deserializing the bytes that were read
125+
Err(CfuProtocolError::WriterError(CfuWriterError::ByteConversionError))
126+
}
127+
} else {
128+
// unsuccessful write/read from the storage interface
129+
// use result.err() eventually
130+
Err(CfuProtocolError::WriterError(CfuWriterError::StorageError))
131+
}
132+
}
133+
134+
async fn verify_all_updates_completed(resps: &[FwUpdateOfferResponse]) -> Result<bool, CfuProtocolError> {
135+
let mut bad_components: heapless::Vec<u8, MAX_CMPT_COUNT> = Vec::new();
136+
for (i, r) in resps.iter().enumerate() {
137+
if r.status != CfuOfferStatus::Reject {
138+
let _ = bad_components.push(i as u8);
139+
}
140+
}
141+
if bad_components.is_empty() {
142+
Ok(true)
143+
} else {
144+
// probably want to have the component ids that didn't respond properly here too
145+
Err(CfuProtocolError::BadResponse)
146+
}
147+
}
148+
}
149+
150+
impl<I: CfuImage, C: CfuComponentTraits> CfuHost for CfuHostInstance<I, C> {
151+
async fn get_cfu_images<T: CfuImage>(&self) -> Result<Vec<T, MAX_CMPT_COUNT>, CfuError> {
152+
Err(CfuError::BadImage)
153+
}
154+
155+
async fn get_all_fw_versions<T: CfuWriter>(
156+
self,
157+
_writer: &mut T,
158+
primary_cmpt: ComponentId,
159+
) -> Result<GetFwVersionResponse, CfuError> {
160+
let mut vec: Vec<FwVerComponentInfo, MAX_CMPT_COUNT> = Vec::new();
161+
let mut component_count: u8 = 0;
162+
self.primary_cmpt.get_subcomponents().iter().for_each(|x| {
163+
if x.is_some() {
164+
component_count += 1
165+
}
166+
});
167+
let result = self.primary_cmpt.get_fw_version().await;
168+
if result.is_ok() {
169+
// convert bytes back to a GetFwVersionResponse
170+
let inner = FwVerComponentInfo::new(
171+
FwVersion {
172+
major: 0,
173+
minor: 1,
174+
variant: 0,
175+
},
176+
primary_cmpt,
177+
BankType::DualBank,
178+
);
179+
let _ = vec.push(inner);
180+
let arr = vec.into_array().unwrap();
181+
let resp: GetFwVersionResponse = GetFwVersionResponse {
182+
header: GetFwVersionResponseHeader::new(component_count, GetFwVerRespHeaderByte3::default()),
183+
component_info: arr,
184+
misc_and_protocol_version: 0,
185+
};
186+
Ok(resp)
187+
} else {
188+
Err(CfuError::ProtocolError(CfuProtocolError::BadResponse))
189+
}
190+
}
191+
192+
async fn process_cfu_offers<'a, T: CfuWriter>(
193+
_offer_commands: &'a [FwUpdateOfferCommand],
194+
_writer: &mut T,
195+
) -> Result<&'a [FwUpdateOfferResponse], CfuError> {
196+
// TODO
197+
Err(CfuError::BadImage)
198+
}
199+
200+
async fn update_cfu_content<T: CfuWriter>(_writer: &mut T) -> Result<FwUpdateContentResponse, CfuError> {
201+
Err(CfuError::ProtocolError(CfuProtocolError::WriterError(
202+
CfuWriterError::Other,
203+
)))
204+
}
205+
206+
async fn is_cfu_image_valid<T: CfuImage>(_image: T) -> Result<bool, CfuError> {
207+
Ok(true)
208+
}
209+
}

‎cfu-service/src/lib.rs

+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#![no_std]
2+
use embassy_sync::once_lock::OnceLock;
3+
use embedded_cfu_protocol::client::CfuReceiveContent;
4+
use embedded_cfu_protocol::protocol_definitions::*;
5+
use embedded_services::cfu::component::*;
6+
use embedded_services::cfu::{CfuError, ContextToken};
7+
use embedded_services::{comms, error, info};
8+
9+
pub mod host;
10+
11+
pub struct CfuClient {
12+
/// Cfu Client context
13+
context: ContextToken,
14+
/// Comms endpoint
15+
tp: comms::Endpoint,
16+
}
17+
18+
/// use default "do-nothing" implementations
19+
impl<T, C, E: Default> CfuReceiveContent<T, C, E> for CfuClient {}
20+
21+
impl CfuClient {
22+
/// Create a new Cfu Client
23+
pub fn create() -> Option<Self> {
24+
Some(Self {
25+
context: ContextToken::create()?,
26+
tp: comms::Endpoint::uninit(comms::EndpointID::Internal(comms::Internal::Nonvol)),
27+
})
28+
}
29+
pub async fn process_request(&self) -> Result<(), CfuError> {
30+
let request = self.context.wait_request().await;
31+
//let device = self.context.get_device(request.id).await?;
32+
let comp = request.id;
33+
34+
match request.data {
35+
RequestData::FwVersionRequest => {
36+
info!("Received FwVersionRequest, comp {}", comp);
37+
if let Ok(device) = self.context.get_device(comp).await {
38+
let resp = device
39+
.execute_device_request(request.data)
40+
.await
41+
.map_err(CfuError::ProtocolError)?;
42+
43+
// TODO replace with signal to component to get its own fw version
44+
//cfu::send_request(comp, RequestData::FwVersionRequest).await?;
45+
match resp {
46+
InternalResponseData::FwVersionResponse(r) => {
47+
let ver = r.component_info[0].fw_version;
48+
info!("got fw version {:?} for comp {}", ver, comp);
49+
}
50+
_ => {
51+
error!("Invalid response to get fw version {:?} from comp {}", resp, comp);
52+
return Err(CfuError::ProtocolError(CfuProtocolError::BadResponse));
53+
}
54+
}
55+
self.context.send_response(resp).await;
56+
return Ok(());
57+
}
58+
Err(CfuError::InvalidComponent)
59+
}
60+
RequestData::GiveContent(_content_cmd) => Ok(()),
61+
RequestData::GiveOffer(_offer_cmd) => Ok(()),
62+
RequestData::PrepareComponentForUpdate => Ok(()),
63+
RequestData::FinalizeUpdate => Ok(()),
64+
}
65+
}
66+
}
67+
68+
impl comms::MailboxDelegate for CfuClient {
69+
fn receive(&self, _message: &comms::Message) {}
70+
}
71+
72+
#[embassy_executor::task]
73+
pub async fn task() {
74+
info!("Starting cfu client task");
75+
static CLIENT: OnceLock<CfuClient> = OnceLock::new();
76+
let cfuclient = CLIENT.get_or_init(|| CfuClient::create().expect("cfu client singleton already initialized"));
77+
78+
if comms::register_endpoint(cfuclient, &cfuclient.tp).await.is_err() {
79+
error!("Failed to register cfu endpoint");
80+
return;
81+
}
82+
83+
loop {
84+
if let Err(e) = cfuclient.process_request().await {
85+
error!("Error processing request: {:?}", e);
86+
}
87+
}
88+
}

0 commit comments

Comments
 (0)
Please sign in to comment.