nvme_mi_dev/
lib.rs

1// SPDX-License-Identifier: GPL-3.0-only
2/*
3 * Copyright (c) 2025 Code Construct
4 */
5#![no_std]
6
7use deku::{DekuContainerWrite, DekuError};
8use flagset::FlagSet;
9use hmac::Mac;
10use log::debug;
11use mctp::AsyncRespChannel;
12use nvme::{
13    AdminGetLogPageLidRequestType, LidSupportedAndEffectsFlags, LogPageAttributes,
14    mi::ResponseStatus,
15};
16use uuid::Uuid;
17
18pub mod nvme;
19mod pcie;
20mod wire;
21
22extern crate deku;
23
24const MAX_CONTROLLERS: usize = 2;
25const MAX_NAMESPACES: usize = 4;
26const MAX_PORTS: usize = 2;
27const MAX_NIDTS: usize = 2;
28
29pub mod smbus {
30    #[derive(Debug, Clone, Copy, Eq, PartialEq)]
31    pub enum BusFrequency {
32        NotSupported,
33        Freq100Khz,
34        Freq400Khz,
35        Freq1Mhz,
36    }
37}
38
39#[derive(Debug)]
40pub enum CommandEffect {
41    SetMtu {
42        port_id: PortId,
43        mtus: usize,
44    },
45    SetSmbusFreq {
46        port_id: PortId,
47        freq: smbus::BusFrequency,
48    },
49}
50
51#[derive(Debug)]
52pub enum CommandEffectError {
53    Unsupported,
54    InternalError,
55}
56
57trait RequestHandler {
58    type Ctx;
59
60    async fn handle<A, C>(
61        &self,
62        ctx: &Self::Ctx,
63        mep: &mut crate::ManagementEndpoint,
64        subsys: &mut crate::Subsystem,
65        rest: &[u8],
66        resp: &mut C,
67        app: A,
68    ) -> Result<(), ResponseStatus>
69    where
70        A: AsyncFnMut(CommandEffect) -> Result<(), CommandEffectError>,
71        C: AsyncRespChannel;
72}
73
74trait Encode<const S: usize>: DekuContainerWrite {
75    fn encode(&self) -> Result<([u8; S], usize), DekuError> {
76        let mut buf = [0u8; S];
77        self.to_slice(&mut buf).map(|len| (buf, len))
78    }
79}
80
81/// # Safety
82///
83/// Must only be implemented for enums with attribute #[repr(T)]
84unsafe trait Discriminant<T: Copy> {
85    fn id(&self) -> T {
86        // https://doc.rust-lang.org/reference/items/enumerations.html#r-items.enum.discriminant.access-memory
87        unsafe { *(self as *const Self as *const T) }
88    }
89}
90
91#[derive(Clone, Copy, Debug, Eq, PartialEq)]
92pub struct PciePort {
93    b: u16,
94    d: u16,
95    f: u16,
96    seg: u8,
97    mps: pcie::PayloadSize,
98    cls: pcie::LinkSpeed,
99    mlw: pcie::LinkWidth,
100    nlw: pcie::LinkWidth,
101}
102
103impl PciePort {
104    pub fn new() -> Self {
105        Self {
106            b: 0,
107            d: 0,
108            f: 0,
109            seg: 0,
110            mps: pcie::PayloadSize::Payload128B,
111            cls: pcie::LinkSpeed::Gts2p5,
112            mlw: pcie::LinkWidth::X2,
113            nlw: pcie::LinkWidth::X1,
114        }
115    }
116}
117
118impl Default for PciePort {
119    fn default() -> Self {
120        Self::new()
121    }
122}
123
124#[derive(Clone, Copy, Debug, Eq, PartialEq)]
125pub struct TwoWirePort {
126    // MI v2.0, 5.7.2, Figure 116
127    cvpdaddr: u8,
128    mvpdfreq: smbus::BusFrequency,
129    cmeaddr: u8,
130    i3csprt: bool,
131    msmbfreq: smbus::BusFrequency,
132    nvmebms: bool,
133    // Local state
134    smbfreq: smbus::BusFrequency,
135}
136
137impl TwoWirePort {
138    pub fn new() -> Self {
139        Self {
140            cvpdaddr: 0,
141            mvpdfreq: smbus::BusFrequency::NotSupported,
142            cmeaddr: 0x1d,
143            i3csprt: false,
144            msmbfreq: smbus::BusFrequency::Freq100Khz,
145            nvmebms: false,
146            smbfreq: smbus::BusFrequency::Freq100Khz,
147        }
148    }
149
150    pub fn builder() -> TwoWirePortBuilder {
151        Default::default()
152    }
153}
154
155impl Default for TwoWirePort {
156    fn default() -> Self {
157        Self::new()
158    }
159}
160
161pub struct TwoWirePortBuilder {
162    msmbfreq: smbus::BusFrequency,
163}
164
165impl TwoWirePortBuilder {
166    pub fn new() -> Self {
167        Self {
168            msmbfreq: smbus::BusFrequency::Freq100Khz,
169        }
170    }
171
172    pub fn msmbfreq(&mut self, freq: smbus::BusFrequency) -> &mut Self {
173        self.msmbfreq = freq;
174        self
175    }
176
177    pub fn build(&self) -> TwoWirePort {
178        TwoWirePort {
179            msmbfreq: self.msmbfreq,
180            ..Default::default()
181        }
182    }
183}
184
185impl Default for TwoWirePortBuilder {
186    fn default() -> Self {
187        Self::new()
188    }
189}
190
191#[derive(Debug, PartialEq, Eq)]
192#[repr(u8)]
193pub enum PortType {
194    Inactive,
195    Pcie(PciePort),
196    TwoWire(TwoWirePort),
197}
198
199// MI v2.0, Figure 114, PRTCAP
200#[derive(Clone, Copy, Debug)]
201struct PortCapabilities {
202    ciaps: bool,
203    aems: bool,
204}
205
206impl PortCapabilities {
207    fn new() -> Self {
208        PortCapabilities {
209            ciaps: false,
210            aems: false,
211        }
212    }
213}
214
215#[derive(Debug)]
216pub struct Port {
217    id: PortId,
218    // MI v2.0, 5.7.2, Figure 114
219    typ: PortType,
220    caps: PortCapabilities,
221    mmtus: u16,
222    mebs: u32,
223    // Local state
224    mtus: u16,
225}
226
227impl Port {
228    fn new(id: PortId, typ: PortType) -> Self {
229        Self {
230            id,
231            typ,
232            caps: PortCapabilities::new(),
233            mmtus: 64,
234            mebs: 0,
235            mtus: 64,
236        }
237    }
238}
239
240#[derive(Clone, Copy, Debug, PartialEq)]
241pub struct PortId(u8);
242
243#[derive(Clone, Copy, Debug, Default)]
244struct ManagementEndpointControllerState {
245    cc: nvme::ControllerConfiguration,
246    csts: FlagSet<nvme::ControllerStatusFlags>,
247    chscf: FlagSet<nvme::mi::ControllerHealthStatusChangedFlags>,
248}
249
250#[derive(Debug)]
251pub struct ManagementEndpoint {
252    #[expect(dead_code)]
253    port: PortId,
254    mecss: [ManagementEndpointControllerState; MAX_CONTROLLERS],
255    ccsf: nvme::mi::CompositeControllerStatusFlagSet,
256}
257
258impl ManagementEndpoint {
259    pub fn new(port: PortId) -> Self {
260        Self {
261            port,
262            mecss: [ManagementEndpointControllerState::default(); MAX_CONTROLLERS],
263            ccsf: nvme::mi::CompositeControllerStatusFlagSet::empty(),
264        }
265    }
266}
267
268#[derive(Debug)]
269struct MiCapability {
270    mjr: u8,
271    mnr: u8,
272}
273
274impl MiCapability {
275    fn new() -> Self {
276        Self { mjr: 1, mnr: 2 }
277    }
278}
279
280#[derive(Debug, PartialEq, Eq)]
281pub enum UnitKind {
282    Kelvin,
283    Percent,
284}
285
286#[derive(Debug)]
287pub enum Temperature<T> {
288    Kelvin(T),
289    Celcius(T),
290}
291
292#[derive(Debug)]
293struct OperatingRange<T> {
294    kind: UnitKind,
295    lower: T,
296    upper: T,
297}
298
299impl<T> OperatingRange<T> {
300    fn new(kind: UnitKind, lower: T, upper: T) -> Self {
301        Self { kind, lower, upper }
302    }
303}
304
305#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
306pub struct ControllerId(u16);
307
308#[derive(Debug)]
309pub struct SecondaryController {
310    #[expect(dead_code)]
311    id: ControllerId,
312}
313
314#[derive(Debug, Clone, Copy, PartialEq)]
315enum ControllerType {
316    Io,
317    #[expect(dead_code)]
318    Discovery,
319    #[expect(dead_code)]
320    Administrative,
321}
322
323#[derive(Debug)]
324pub struct Controller {
325    id: ControllerId,
326    cntrltype: ControllerType,
327    port: PortId,
328    secondaries: heapless::Vec<SecondaryController, 0>,
329    active_ns: heapless::Vec<NamespaceId, MAX_NAMESPACES>,
330    temp: u16,
331    temp_range: OperatingRange<u16>,
332    capacity: u64,
333    spare: u64,
334    spare_range: OperatingRange<u64>,
335    write_age: u64,
336    write_lifespan: u64,
337    ro: bool,
338    cc: nvme::ControllerConfiguration,
339    csts: FlagSet<nvme::ControllerStatusFlags>,
340    lpa: FlagSet<LogPageAttributes>,
341    lsaes: [FlagSet<LidSupportedAndEffectsFlags>; 130],
342    fna: FlagSet<nvme::FormatNvmAttributes>,
343}
344
345#[derive(Debug)]
346pub enum ControllerError {
347    NamespaceAlreadyAttached,
348    NamespaceNotAttached,
349    NamespaceAttachmentLimitExceeded,
350}
351
352impl Controller {
353    fn new(id: ControllerId, port: PortId) -> Self {
354        Self {
355            id,
356            cntrltype: ControllerType::Io,
357            port,
358            secondaries: heapless::Vec::new(),
359            active_ns: heapless::Vec::new(),
360            temp: 293,
361            temp_range: OperatingRange::new(UnitKind::Kelvin, 213, 400),
362            capacity: 100,
363            spare: 100,
364            spare_range: OperatingRange::new(UnitKind::Percent, 5, 100),
365            write_age: 38,
366            write_lifespan: 100,
367            ro: false,
368            cc: nvme::ControllerConfiguration::default(),
369            csts: FlagSet::empty(),
370            lpa: FlagSet::empty(),
371            lsaes: {
372                let mut arr = [FlagSet::default(); 130];
373                arr[AdminGetLogPageLidRequestType::SupportedLogPages.id() as usize] =
374                    LidSupportedAndEffectsFlags::Lsupp.into();
375                arr[AdminGetLogPageLidRequestType::SmartHealthInformation.id() as usize] =
376                    LidSupportedAndEffectsFlags::Lsupp.into();
377                arr[AdminGetLogPageLidRequestType::FeatureIdentifiersSupportedAndEffects.id()
378                    as usize] = LidSupportedAndEffectsFlags::Lsupp.into();
379                arr[AdminGetLogPageLidRequestType::SanitizeStatus.id() as usize] =
380                    LidSupportedAndEffectsFlags::Lsupp.into();
381                arr
382            },
383            fna: (nvme::FormatNvmAttributes::Fns
384                | nvme::FormatNvmAttributes::Sens
385                | nvme::FormatNvmAttributes::Fnvmbs),
386        }
387    }
388
389    pub fn set_property(&mut self, prop: nvme::ControllerProperties) {
390        match prop {
391            nvme::ControllerProperties::Cc(cc) => {
392                self.cc = cc;
393                if self.cc.en {
394                    self.csts |= nvme::ControllerStatusFlags::Rdy;
395                } else {
396                    self.csts -= nvme::ControllerStatusFlags::Rdy;
397                }
398            }
399        }
400    }
401
402    pub fn set_temperature(&mut self, temp: Temperature<u16>) {
403        let Temperature::Kelvin(k) = temp else {
404            todo!("Support units other than kelvin");
405        };
406
407        self.temp = k;
408    }
409
410    pub fn attach_namespace(&mut self, nsid: NamespaceId) -> Result<(), ControllerError> {
411        debug!("Attaching NSID {} to CTLRID {}", nsid.0, self.id.0);
412        if self.active_ns.iter().any(|ns| ns.0 == nsid.0) {
413            return Err(ControllerError::NamespaceAlreadyAttached);
414        }
415
416        if self.active_ns.push(nsid).is_err() {
417            return Err(ControllerError::NamespaceAttachmentLimitExceeded);
418        }
419
420        Ok(())
421    }
422
423    pub fn detach_namespace(&mut self, nsid: NamespaceId) -> Result<(), ControllerError> {
424        debug!("Detaching NSID {} from CTRLID {}", nsid.0, self.id.0);
425        let Some((idx, _)) = self
426            .active_ns
427            .iter()
428            .enumerate()
429            .find(|args| args.1.0 == nsid.0)
430        else {
431            return Err(ControllerError::NamespaceNotAttached);
432        };
433
434        let _ = self.active_ns.swap_remove(idx);
435
436        Ok(())
437    }
438}
439
440#[derive(Debug)]
441struct SubsystemHealth {
442    nss: FlagSet<crate::nvme::mi::NvmSubsystemStatusFlags>,
443}
444
445impl SubsystemHealth {
446    fn new() -> Self {
447        Self {
448            nss: crate::nvme::mi::NvmSubsystemStatusFlags::Rnr
449                | crate::nvme::mi::NvmSubsystemStatusFlags::Df,
450        }
451    }
452}
453
454#[derive(Clone, Copy, Debug)]
455pub enum NamespaceIdentifierType {
456    Ieuid([u8; 8]),
457    Nguid([u8; 16]),
458    Nuuid(Uuid),
459    Csi(nvme::CommandSetIdentifier),
460}
461
462// Base v2.1, 3.2.1
463// Base v2.1, 3.2.1.5, Figure 71
464#[derive(Clone, Copy, Debug)]
465enum NamespaceIdDisposition<'a> {
466    Invalid,
467    Broadcast,
468    Unallocated,
469    Inactive(&'a Namespace),
470    Active(&'a Namespace),
471}
472
473// NSID
474#[derive(Debug, Clone, Copy, PartialEq, Eq)]
475pub struct NamespaceId(u32);
476
477impl NamespaceId {
478    fn disposition<'a>(&self, subsys: &'a Subsystem) -> NamespaceIdDisposition<'a> {
479        if self.0 == 0 {
480            return NamespaceIdDisposition::Invalid;
481        }
482
483        if self.0 == u32::MAX {
484            return NamespaceIdDisposition::Broadcast;
485        }
486
487        assert!(subsys.nss.capacity() <= u32::MAX.try_into().unwrap());
488        if self.0 > subsys.nss.capacity() as u32 {
489            return NamespaceIdDisposition::Invalid;
490        }
491
492        let Some(ns) = subsys.nss.iter().find(|nsid| self.0 == nsid.id.0) else {
493            return NamespaceIdDisposition::Unallocated;
494        };
495
496        if !subsys
497            .ctlrs
498            .iter()
499            .flat_map(|c| c.active_ns.iter())
500            .any(|&nsid| nsid.0 == self.0)
501        {
502            return NamespaceIdDisposition::Inactive(ns);
503        }
504
505        NamespaceIdDisposition::Active(ns)
506    }
507
508    fn max(subsys: &Subsystem) -> u32 {
509        subsys
510            .nss
511            .capacity()
512            .try_into()
513            .expect("Too many namespaces")
514    }
515}
516
517#[derive(Debug)]
518pub struct Namespace {
519    id: NamespaceId,
520    size: u64,
521    capacity: u64,
522    used: u64,
523    block_order: u8,
524    nids: [NamespaceIdentifierType; 2],
525}
526
527impl Namespace {
528    fn generate_uuid(seed: &[u8], nsid: NamespaceId) -> Uuid {
529        let mut hasher = hmac::Hmac::<sha2::Sha256>::new_from_slice(seed).unwrap();
530        hasher.update(&nsid.0.to_be_bytes());
531        let digest = hasher.finalize().into_bytes();
532        let digest: [u8; 16] = digest[..16].try_into().unwrap();
533        uuid::Builder::from_random_bytes(digest).into_uuid()
534    }
535
536    pub fn new(nsid: NamespaceId, uuid: Uuid, capacity: u64) -> Self {
537        Self {
538            id: nsid,
539            size: capacity,
540            capacity,
541            used: 0,
542            block_order: 9,
543            nids: [
544                NamespaceIdentifierType::Nuuid(uuid),
545                NamespaceIdentifierType::Csi(nvme::CommandSetIdentifier::Nvm),
546            ],
547        }
548    }
549}
550
551#[derive(Debug, Eq, PartialEq)]
552pub enum SubsystemError {
553    ControllerLimitExceeded,
554    NamespaceIdentifierUnavailable,
555}
556
557#[derive(Clone, Copy, Debug)]
558pub struct SubsystemInfo {
559    pub pci_vid: u16,
560    pub pci_did: u16,
561    pub pci_svid: u16,
562    pub pci_sdid: u16,
563    pub ieee_oui: [u8; 3],
564    pub instance: [u8; 16],
565}
566
567impl SubsystemInfo {
568    fn acquire_source_date_epoch() -> u64 {
569        env!("SOURCE_DATE_EPOCH").parse::<u64>().unwrap_or(0)
570    }
571
572    fn acquire_ieee_oui() -> [u8; 3] {
573        let mut oui = [0u8; 3];
574        // ac-de-48 is allocated as private, used as the example value in the
575        // IEEE Guidelines for use of EUI, OUI, and CID documentation
576        for (idx, val) in option_env!("NVME_MI_DEV_IEEE_OUI")
577            .unwrap_or("ac-de-48")
578            .split('-')
579            .take(oui.len())
580            .map(|v| {
581                u8::from_str_radix(v, 16).expect(
582                    "NVME_MI_DEV_IEEE_OUI must be set in the IEEE RA hexadecimal representation",
583                )
584            })
585            .enumerate()
586        {
587            oui[idx] = val;
588        }
589        oui
590    }
591
592    fn acquire_pci_ids() -> (u16, u16, u16, u16) {
593        // ffff is the value returned by an aborted access
594        let vid = u16::from_str_radix(option_env!("NVME_MI_DEV_PCI_VID").unwrap_or("ffff"), 16)
595            .expect("NVME_MI_DEV_PCI_VID must be set to a 16-bit value in base-16 representation");
596        let did = u16::from_str_radix(option_env!("NVME_MI_DEV_PCI_DID").unwrap_or("ffff"), 16)
597            .expect("NVME_MI_DEV_PCI_DID must be set to a 16-bit value in base-16 representation");
598        let svid = u16::from_str_radix(option_env!("NVME_MI_DEV_PCI_SVID").unwrap_or("ffff"), 16)
599            .expect("NVME_MI_DEV_PCI_SVID must be set to a 16-bit value in base-16 representation");
600        let sdid = u16::from_str_radix(option_env!("NVME_MI_DEV_PCI_SDID").unwrap_or("ffff"), 16)
601            .expect("NVME_MI_DEV_PCI_SDID must be set to a 16-bit value in base-16 representation");
602        (vid, did, svid, sdid)
603    }
604
605    pub fn invalid() -> Self {
606        Self {
607            pci_vid: 0xffff,
608            pci_did: 0xffff,
609            pci_svid: 0xffff,
610            pci_sdid: 0xffff,
611            ieee_oui: [0xac, 0xde, 0x48],
612            instance: [0; 16],
613        }
614    }
615
616    pub fn environment() -> Self {
617        let (vid, did, svid, sdid) = SubsystemInfo::acquire_pci_ids();
618        let sde = SubsystemInfo::acquire_source_date_epoch().to_le_bytes();
619        let mut instance = [0u8; 16];
620        instance[..sde.len()].copy_from_slice(&sde);
621        Self {
622            pci_vid: vid,
623            pci_did: did,
624            pci_svid: svid,
625            pci_sdid: sdid,
626            ieee_oui: SubsystemInfo::acquire_ieee_oui(),
627            instance,
628        }
629    }
630}
631
632#[derive(Debug)]
633pub struct Subsystem {
634    info: SubsystemInfo,
635    caps: nvme::mi::SubsystemCapabilities,
636    ports: heapless::Vec<Port, MAX_PORTS>,
637    ctlrs: heapless::Vec<Controller, MAX_CONTROLLERS>,
638    nsids: u32,
639    nss: heapless::Vec<Namespace, MAX_NAMESPACES>,
640    health: SubsystemHealth,
641    sanicap: FlagSet<nvme::SanitizeCapabilityFlags>,
642    nodmmas: nvme::NoDeallocateModifiesMediaAfterSanitize,
643    ssi: nvme::SanitizeStateInformation,
644    sstat: nvme::SanitizeStatus,
645    sconf: Option<nvme::AdminSanitizeConfiguration>,
646    mi: MiCapability,
647    sn: &'static str,
648    mn: &'static str,
649    fr: &'static str,
650}
651
652impl Subsystem {
653    pub fn new(info: SubsystemInfo) -> Self {
654        Subsystem {
655            info,
656            caps: nvme::mi::SubsystemCapabilities::new(),
657            ports: heapless::Vec::new(),
658            ctlrs: heapless::Vec::new(),
659            nsids: 0,
660            nss: heapless::Vec::new(),
661            health: SubsystemHealth::new(),
662            mi: MiCapability::new(),
663            sn: "1000",
664            mn: "MIDEV",
665            fr: "00.00.01",
666            sstat: Default::default(),
667            sconf: None,
668            ssi: Default::default(),
669            sanicap: nvme::SanitizeCapabilityFlags::Ces
670                | nvme::SanitizeCapabilityFlags::Bes
671                | nvme::SanitizeCapabilityFlags::Ows
672                | nvme::SanitizeCapabilityFlags::Ndi,
673            nodmmas: Default::default(),
674        }
675    }
676
677    pub fn add_port(&mut self, typ: PortType) -> Result<PortId, Port> {
678        debug_assert!(self.ctlrs.len() <= u8::MAX.into());
679        let p = Port::new(PortId(self.ports.len() as u8), typ);
680        self.ports.push(p).map(|_p| self.ports.last().unwrap().id)
681    }
682
683    pub fn add_controller(&mut self, port: PortId) -> Result<ControllerId, SubsystemError> {
684        debug_assert!(self.ctlrs.len() <= u16::MAX.into());
685        let cid = ControllerId(self.ctlrs.len() as u16);
686        let c = Controller::new(cid, port);
687        self.ctlrs
688            .push(c)
689            .map_err(|_| SubsystemError::ControllerLimitExceeded)?;
690        Ok(cid)
691    }
692
693    pub fn controller_mut(&mut self, id: ControllerId) -> &mut Controller {
694        self.ctlrs
695            .get_mut(id.0 as usize)
696            .expect("Invalid ControllerId provided")
697    }
698
699    pub fn add_namespace(&mut self, capacity: u64) -> Result<NamespaceId, SubsystemError> {
700        let Some(allocated) = self.nsids.checked_add(1) else {
701            debug!("Implement allocation tracking with reuse");
702            return Err(SubsystemError::NamespaceIdentifierUnavailable);
703        };
704        self.nsids = allocated;
705        let nsid = NamespaceId(self.nsids);
706        let ns = Namespace::new(
707            nsid,
708            Namespace::generate_uuid(&self.info.instance, nsid),
709            capacity,
710        );
711        match self.nss.push(ns) {
712            Ok(_) => Ok(nsid),
713            Err(_) => Err(SubsystemError::NamespaceIdentifierUnavailable),
714        }
715    }
716
717    pub fn remove_namespace(&mut self, nsid: NamespaceId) -> Result<(), SubsystemError> {
718        if nsid.0 == u32::MAX {
719            self.nss.clear();
720            return Ok(());
721        }
722        let Some(e) = self.nss.iter().enumerate().find(|args| args.1.id == nsid) else {
723            return Err(SubsystemError::NamespaceIdentifierUnavailable);
724        };
725        let _ = self.nss.swap_remove(e.0);
726        Ok(())
727    }
728}