1use crate::access_policy::{
16 MmioResource, ProtocolCeiling, ResourceCeiling, ResourceId, ResourceKind,
17};
18use crate::digest::{Sha256Digest, combined_digest, per_resource_digests};
19use std::collections::BTreeMap;
20
21pub const EMBEDDED_PROVIDER: &str = "embedded";
23
24#[derive(Debug)]
26pub struct ProvidedResources<B> {
27 pub provider: String,
30 pub node_identity: String,
33 pub resources: BTreeMap<ResourceId, MmioResource>,
35 pub ceiling: BTreeMap<ResourceId, ResourceCeiling>,
38 pub backends: BTreeMap<ResourceId, B>,
40}
41
42#[derive(Clone, Debug, PartialEq, Eq)]
44pub enum ProviderError {
45 ResourceBackendMismatch {
47 missing: Vec<ResourceId>,
49 },
50 CeilingWithoutResource {
54 missing: Vec<ResourceId>,
56 },
57 MappingSmallerThanLogical {
60 resource: ResourceId,
62 },
63}
64
65impl<B> ProvidedResources<B> {
66 pub fn empty(provider: &str, node_identity: &str) -> Self {
68 Self {
69 provider: provider.to_string(),
70 node_identity: node_identity.to_string(),
71 resources: BTreeMap::new(),
72 ceiling: BTreeMap::new(),
73 backends: BTreeMap::new(),
74 }
75 }
76
77 pub fn embedded(node_identity: &str) -> Self {
79 Self::empty(EMBEDDED_PROVIDER, node_identity)
80 }
81
82 pub fn add_mmio(
85 &mut self,
86 id: ResourceId,
87 name: impl Into<String>,
88 logical_size: u64,
89 mapped_size: u64,
90 ceiling: ResourceCeiling,
91 backend: B,
92 ) {
93 self.resources.insert(id, MmioResource::mmio(name, logical_size, mapped_size));
94 self.ceiling.insert(id, ceiling);
95 self.backends.insert(id, backend);
96 }
97
98 pub fn add_protocol(
100 &mut self,
101 id: ResourceId,
102 name: impl Into<String>,
103 kind: ResourceKind,
104 ceiling: ProtocolCeiling,
105 backend: B,
106 ) {
107 let resource = match kind {
108 ResourceKind::Gpio => MmioResource::gpio(name),
109 ResourceKind::I2c => MmioResource::i2c(name),
110 ResourceKind::Spi => MmioResource::spi(name),
111 ResourceKind::Clock => MmioResource::clock(name),
112 ResourceKind::Reset => MmioResource::reset(name),
113 ResourceKind::Serial => MmioResource::serial(name),
114 ResourceKind::Interrupt => MmioResource::interrupt(name),
115 ResourceKind::Mmio => MmioResource::mmio(name, 0, 0),
116 };
117 self.resources.insert(id, resource);
118 self.ceiling.insert(
119 id,
120 ResourceCeiling {
121 hard_denied: vec![],
122 allow_unknown_reads: false,
123 allow_poll: false,
124 writable_registers: vec![],
125 protocol: Some(ceiling),
126 allow_interrupt: kind == ResourceKind::Interrupt,
127 },
128 );
129 self.backends.insert(id, backend);
130 }
131
132 pub fn add_interrupt(&mut self, id: ResourceId, name: impl Into<String>, backend: B) {
134 self.resources.insert(id, MmioResource::interrupt(name));
135 self.ceiling.insert(
136 id,
137 ResourceCeiling {
138 hard_denied: vec![],
139 allow_unknown_reads: false,
140 allow_poll: false,
141 writable_registers: vec![],
142 protocol: Some(ProtocolCeiling::default_for(ResourceKind::Interrupt)),
143 allow_interrupt: true,
144 },
145 );
146 self.backends.insert(id, backend);
147 }
148
149 pub fn validate(&self) -> Result<(), ProviderError> {
151 let resource_ids: Vec<ResourceId> = self.resources.keys().copied().collect();
152 let backend_ids: Vec<ResourceId> = self.backends.keys().copied().collect();
153 if resource_ids != backend_ids {
154 let missing = resource_ids
155 .iter()
156 .filter(|id| !self.backends.contains_key(id))
157 .chain(backend_ids.iter().filter(|id| !self.resources.contains_key(id)))
158 .copied()
159 .collect();
160 return Err(ProviderError::ResourceBackendMismatch { missing });
161 }
162 let dangling: Vec<ResourceId> =
163 self.ceiling.keys().filter(|id| !self.resources.contains_key(id)).copied().collect();
164 if !dangling.is_empty() {
165 return Err(ProviderError::CeilingWithoutResource { missing: dangling });
166 }
167 for (id, resource) in &self.resources {
168 if resource.mapped_size < resource.logical_size {
169 return Err(ProviderError::MappingSmallerThanLogical { resource: *id });
170 }
171 }
172 Ok(())
173 }
174
175 pub fn digests(&self) -> BTreeMap<ResourceId, Sha256Digest> {
178 per_resource_digests(&self.node_identity, &self.provider, &self.resources, &self.ceiling)
179 }
180
181 pub fn combined_digest(&self) -> Sha256Digest {
183 combined_digest(self.digests().values().copied())
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::hardware_backend::FakeMmio;
191
192 fn resource(logical: u64, mapped: u64) -> MmioResource {
193 MmioResource {
194 name: "ctrl".to_string(),
195 kind: crate::access_policy::ResourceKind::Mmio,
196 logical_size: logical,
197 mapped_size: mapped,
198 }
199 }
200
201 fn bundle() -> ProvidedResources<FakeMmio> {
202 let mut bundle = ProvidedResources::empty("fake", "node");
203 bundle.resources.insert(1, resource(0x100, 0x1000));
204 bundle.ceiling.insert(
205 1,
206 ResourceCeiling {
207 hard_denied: vec![],
208 allow_unknown_reads: true,
209 allow_poll: false,
210 writable_registers: vec![],
211 protocol: None,
212 allow_interrupt: false,
213 },
214 );
215 bundle.backends.insert(1, FakeMmio::new());
216 bundle
217 }
218
219 #[test]
220 fn valid_bundle_passes() {
221 assert_eq!(bundle().validate(), Ok(()));
222 assert_eq!(ProvidedResources::<FakeMmio>::empty("fake", "node").validate(), Ok(()));
223 }
224
225 #[test]
226 fn missing_backend_is_rejected() {
227 let mut bundle = bundle();
228 bundle.backends.clear();
229 assert_eq!(
230 bundle.validate(),
231 Err(ProviderError::ResourceBackendMismatch { missing: vec![1] })
232 );
233 }
234
235 #[test]
236 fn dangling_ceiling_is_rejected() {
237 let mut bundle = bundle();
238 bundle.ceiling.insert(
239 9,
240 ResourceCeiling {
241 hard_denied: vec![],
242 allow_unknown_reads: false,
243 allow_poll: false,
244 writable_registers: vec![],
245 protocol: None,
246 allow_interrupt: false,
247 },
248 );
249 assert_eq!(
250 bundle.validate(),
251 Err(ProviderError::CeilingWithoutResource { missing: vec![9] })
252 );
253 }
254
255 #[test]
256 fn resource_without_ceiling_is_legal() {
257 let mut bundle = bundle();
258 bundle.ceiling.clear();
259 assert_eq!(bundle.validate(), Ok(()));
260 }
261
262 #[test]
263 fn short_mapping_is_rejected() {
264 let mut bundle = bundle();
265 bundle.resources.insert(1, resource(0x1000, 0x100));
266 assert_eq!(
267 bundle.validate(),
268 Err(ProviderError::MappingSmallerThanLogical { resource: 1 })
269 );
270 }
271
272 #[test]
273 fn digests_are_ordered_and_combined() {
274 let bundle = bundle();
275 let digests = bundle.digests();
276 assert_eq!(digests.len(), 1);
277 assert_eq!(bundle.combined_digest(), combined_digest(digests.values().copied()));
278 }
279
280 #[test]
281 fn embedded_provider_registers_mmio_protocol_and_interrupt() {
282 let mut bundle = ProvidedResources::embedded("sample-driver");
283 assert_eq!(bundle.provider, EMBEDDED_PROVIDER);
284 assert_eq!(bundle.node_identity, "sample-driver");
285
286 bundle.add_mmio(
287 0,
288 "regs",
289 0x100,
290 0x1000,
291 ResourceCeiling {
292 hard_denied: vec![0x40..0x44],
293 allow_unknown_reads: true,
294 allow_poll: true,
295 writable_registers: vec![],
296 protocol: None,
297 allow_interrupt: false,
298 },
299 FakeMmio::new(),
300 );
301 bundle.add_protocol(
302 1,
303 "gpio0",
304 ResourceKind::Gpio,
305 ProtocolCeiling::default_for(ResourceKind::Gpio),
306 FakeMmio::new(),
307 );
308 bundle.add_interrupt(2, "irq0", FakeMmio::new());
309
310 assert_eq!(bundle.validate(), Ok(()));
311 assert_eq!(bundle.resources.len(), 3);
312 assert_eq!(bundle.resources[&0].kind, ResourceKind::Mmio);
313 assert_eq!(bundle.resources[&1].kind, ResourceKind::Gpio);
314 assert_eq!(bundle.resources[&2].kind, ResourceKind::Interrupt);
315 assert!(bundle.ceiling[&2].allow_interrupt);
316 }
317}