Skip to main content

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}