rhdl_ubft/card/authorization.rs
1//! Whole-descriptor receive authorization, before any AXI write is issued.
2//!
3//! Routing answers where a packet goes. This guard answers whether its trusted
4//! ingress may modify the entire requested interval there. The fabric must
5//! latch the verdict at descriptor acceptance and drain rejected packets without
6//! issuing writes. Checking each beat after forwarding earlier beats is too late.
7//!
8//! Windows are trusted configuration, never supplied by the packet. One window
9//! describes one contiguous entry owned by one source at one destination. Use
10//! separate windows for interleaved slots; adjacent windows are not combined to
11//! authorize a crossing write. Configuration changes require a drained fabric.
12//! Validate wider host fields before narrowing to these port widths. Malformed
13//! windows grant nothing individually; another valid matching window may still
14//! authorize. Physical bounds concern the requested bytes, not a global audit
15//! of unused configured address space. The fabric must enforce actual length,
16//! strobes and framing against the accepted descriptor as it forwards data.
17//! This guard does not provide atomic publication or non-equivocation.
18use rhdl::prelude::*;
19
20/// One trusted source-owned contiguous interval, relative to destination base.
21#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
22pub struct WriteWindow {
23 /// Disabled windows grant no access.
24 pub enabled: bool,
25 /// Trusted ingress identity allowed to write this entry.
26 pub source: b5,
27 /// Destination selected by the programmed connection table.
28 pub destination: b5,
29 /// First authorized byte offset.
30 pub start: b64,
31 /// Number of authorized bytes; zero grants no access.
32 pub bytes: b64,
33}
34
35/// Decoded descriptor plus trusted fabric configuration.
36#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
37pub struct WriteCheck {
38 /// Runtime membership; supported remote fabric size is 2 through 16.
39 pub members: b5,
40 /// Physical ingress identity, not a sender field in packet contents.
41 pub source: b5,
42 /// Destination resolved by the host-programmed connection table.
43 pub destination: b5,
44 /// Packet-requested byte offset in the destination's region.
45 pub offset: b64,
46 /// Packet-requested byte count.
47 pub length: b32,
48 /// Trusted physical base of the destination region.
49 pub base: b64,
50 /// Trusted destination-region size in bytes.
51 pub region: b64,
52}
53
54/// Authorization outcome. The fabric counts rejected descriptors once on
55/// acceptance; this combinational guard has no counter or handshake state.
56#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
57pub struct WriteVerdict {
58 /// True only for a nonempty interval wholly inside one matching window.
59 pub allowed: bool,
60 /// 0 accepted, 1 membership, 2 empty, 3 region, 4 physical wrap, 5 permission,
61 /// 6 stale owner epoch ([`authorize_owned_write`] only).
62 pub reason: b3,
63}
64
65/// Authorize the complete interval using subtraction after explicit size checks.
66/// Intermediate unsigned differences may wrap, but never authorize on their own:
67/// every comparison is paired with the corresponding lower-bound/size predicate.
68#[kernel]
69pub fn authorize_write<const W: usize>(
70 request: WriteCheck,
71 windows: [WriteWindow; W],
72) -> WriteVerdict {
73 let length: b64 = request.length.resize();
74 let max = !b64(0);
75 let members_ok = request.members >= b5(2)
76 && request.members <= b5(16)
77 && request.source < request.members
78 && request.destination < request.members
79 && request.source != request.destination;
80 let region_ok = length <= request.region && request.offset <= request.region - length;
81 // Validate both additions without ever relying on a wrapped sum. For a
82 // nonempty interval, the last byte (rather than end-exclusive) must fit.
83 let address_ok = request.base <= max - request.offset;
84 let first = request.base + request.offset;
85 let last_ok = length != b64(0) && length - b64(1) <= max - first;
86 let mut permitted = false;
87 for index in 0..W {
88 let window = windows[index];
89 let window_ok = window.bytes != b64(0)
90 && window.bytes <= request.region
91 && window.start <= request.region - window.bytes;
92 let inside = request.offset >= window.start
93 && length <= window.bytes
94 && request.offset - window.start <= window.bytes - length;
95 if window.enabled
96 && window_ok
97 && inside
98 && window.source == request.source
99 && window.destination == request.destination
100 {
101 permitted = true;
102 }
103 }
104 let mut reason = b3(0);
105 if !members_ok {
106 reason = b3(1);
107 } else if length == b64(0) {
108 reason = b3(2);
109 } else if !region_ok {
110 reason = b3(3);
111 } else if !address_ok || !last_ok {
112 reason = b3(4);
113 } else if !permitted {
114 reason = b3(5);
115 }
116 WriteVerdict {
117 allowed: reason == b3(0),
118 reason,
119 }
120}
121
122/// Ingress class of a write: a remote replica's fabric/RoCE connection.
123pub const INGRESS_REMOTE: u128 = 0;
124/// Ingress class: the PCIe host (driver/MMIO) path.
125pub const INGRESS_HOST: u128 = 1;
126/// Ingress class: a card-local DMA engine.
127pub const INGRESS_DMA: u128 = 2;
128/// Ingress class: debug/JTAG/ILA write access.
129pub const INGRESS_DEBUG: u128 = 3;
130
131/// A [`WriteWindow`] with an owner epoch and the ingress classes it admits
132/// (N21). Every write into protected memory, whatever its path, is checked
133/// against these windows by [`authorize_owned_write`].
134///
135/// Re-granting a window (to a new owner, or back to an earlier one after a
136/// role change) installs a larger `epoch`. Descriptors latched under the
137/// previous epoch, for example delayed on the old owner's connection, then no
138/// longer match. Epoch zero means not provisioned and grants nothing. The
139/// table and its epochs are trusted configuration from the host's
140/// provisioning path, never from a packet or the protocol controller.
141#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
142pub struct OwnedWindow {
143 /// The owned interval and its single source.
144 pub window: WriteWindow,
145 /// Current owner epoch, nonzero.
146 pub epoch: b32,
147 /// Admitted ingress classes, bit `c` for class `c` (normally one bit).
148 pub classes: b4,
149}
150
151/// One write descriptor, from any ingress, as the trusted ingress latched it.
152#[derive(Clone, Copy, Debug, PartialEq, Digital, Default)]
153pub struct OwnedCheck {
154 /// Interval, physical source identity and destination. For local classes
155 /// `check.source` is the trusted local port number and `check.destination`
156 /// this card's member index.
157 pub check: WriteCheck,
158 /// Ingress class (`INGRESS_*`), set by the physical path, not by contents.
159 pub ingress: b2,
160 /// Owner epoch the ingress bound to the descriptor at acceptance (the
161 /// connection or port incarnation), never a packet field.
162 pub epoch: b32,
163}
164
165/// Authorize one descriptor against owner-epoch windows, for every ingress
166/// class. Reasons as [`authorize_write`], plus 6 for a stale owner epoch: some
167/// window matches source, class and interval, but under another epoch.
168/// Remote writes require distinct in-range members (as `authorize_write`);
169/// local classes require only an in-range destination.
170#[kernel]
171pub fn authorize_owned_write<const W: usize>(
172 request: OwnedCheck,
173 windows: [OwnedWindow; W],
174) -> WriteVerdict {
175 let r = request.check;
176 let length: b64 = r.length.resize();
177 let max = !b64(0);
178 let remote = request.ingress == b2(0);
179 let members_range = r.members >= b5(2) && r.members <= b5(16) && r.destination < r.members;
180 let members_ok =
181 members_range && (!remote || (r.source < r.members && r.source != r.destination));
182 let region_ok = length <= r.region && r.offset <= r.region - length;
183 let address_ok = r.base <= max - r.offset;
184 let first = r.base + r.offset;
185 let last_ok = length != b64(0) && length - b64(1) <= max - first;
186 let class_bit: b4 = b4(1) << request.ingress.resize::<4>();
187 let mut permitted = false;
188 let mut stale = false;
189 for index in 0..W {
190 let owned = windows[index];
191 let window = owned.window;
192 let window_ok = window.bytes != b64(0)
193 && window.bytes <= r.region
194 && window.start <= r.region - window.bytes;
195 let inside = r.offset >= window.start
196 && length <= window.bytes
197 && r.offset - window.start <= window.bytes - length;
198 let owner = window.enabled
199 && window_ok
200 && inside
201 && window.source == r.source
202 && window.destination == r.destination
203 && (owned.classes & class_bit) != b4(0)
204 && owned.epoch != b32(0);
205 if owner && owned.epoch == request.epoch {
206 permitted = true;
207 }
208 if owner && owned.epoch != request.epoch {
209 stale = true;
210 }
211 }
212 let mut reason = b3(0);
213 if !members_ok {
214 reason = b3(1);
215 } else if length == b64(0) {
216 reason = b3(2);
217 } else if !region_ok {
218 reason = b3(3);
219 } else if !address_ok || !last_ok {
220 reason = b3(4);
221 } else if !permitted && stale {
222 reason = b3(6);
223 } else if !permitted {
224 reason = b3(5);
225 }
226 WriteVerdict {
227 allowed: reason == b3(0),
228 reason,
229 }
230}