xref: /linux/drivers/gpu/nova-core/falcon.rs (revision 6dfafbd0299a60bfb5d5e277fdf100037c7ded07)
1 // SPDX-License-Identifier: GPL-2.0
2 
3 //! Falcon microprocessor base support
4 
5 use core::ops::Deref;
6 
7 use hal::FalconHal;
8 
9 use kernel::{
10     device,
11     dma::DmaAddress,
12     io::poll::read_poll_timeout,
13     prelude::*,
14     sync::aref::ARef,
15     time::{
16         delay::fsleep,
17         Delta, //
18     },
19 };
20 
21 use crate::{
22     dma::DmaObject,
23     driver::Bar0,
24     gpu::Chipset,
25     num::{
26         FromSafeCast,
27         IntoSafeCast, //
28     },
29     regs,
30     regs::macros::RegisterBase, //
31 };
32 
33 pub(crate) mod gsp;
34 mod hal;
35 pub(crate) mod sec2;
36 
37 // TODO[FPRI]: Replace with `ToPrimitive`.
38 macro_rules! impl_from_enum_to_u8 {
39     ($enum_type:ty) => {
40         impl From<$enum_type> for u8 {
41             fn from(value: $enum_type) -> Self {
42                 value as u8
43             }
44         }
45     };
46 }
47 
48 /// Revision number of a falcon core, used in the [`crate::regs::NV_PFALCON_FALCON_HWCFG1`]
49 /// register.
50 #[repr(u8)]
51 #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
52 pub(crate) enum FalconCoreRev {
53     #[default]
54     Rev1 = 1,
55     Rev2 = 2,
56     Rev3 = 3,
57     Rev4 = 4,
58     Rev5 = 5,
59     Rev6 = 6,
60     Rev7 = 7,
61 }
62 impl_from_enum_to_u8!(FalconCoreRev);
63 
64 // TODO[FPRI]: replace with `FromPrimitive`.
65 impl TryFrom<u8> for FalconCoreRev {
66     type Error = Error;
67 
68     fn try_from(value: u8) -> Result<Self> {
69         use FalconCoreRev::*;
70 
71         let rev = match value {
72             1 => Rev1,
73             2 => Rev2,
74             3 => Rev3,
75             4 => Rev4,
76             5 => Rev5,
77             6 => Rev6,
78             7 => Rev7,
79             _ => return Err(EINVAL),
80         };
81 
82         Ok(rev)
83     }
84 }
85 
86 /// Revision subversion number of a falcon core, used in the
87 /// [`crate::regs::NV_PFALCON_FALCON_HWCFG1`] register.
88 #[repr(u8)]
89 #[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
90 pub(crate) enum FalconCoreRevSubversion {
91     #[default]
92     Subversion0 = 0,
93     Subversion1 = 1,
94     Subversion2 = 2,
95     Subversion3 = 3,
96 }
97 impl_from_enum_to_u8!(FalconCoreRevSubversion);
98 
99 // TODO[FPRI]: replace with `FromPrimitive`.
100 impl TryFrom<u8> for FalconCoreRevSubversion {
101     type Error = Error;
102 
103     fn try_from(value: u8) -> Result<Self> {
104         use FalconCoreRevSubversion::*;
105 
106         let sub_version = match value & 0b11 {
107             0 => Subversion0,
108             1 => Subversion1,
109             2 => Subversion2,
110             3 => Subversion3,
111             _ => return Err(EINVAL),
112         };
113 
114         Ok(sub_version)
115     }
116 }
117 
118 /// Security model of a falcon core, used in the [`crate::regs::NV_PFALCON_FALCON_HWCFG1`]
119 /// register.
120 #[repr(u8)]
121 #[derive(Debug, Default, Copy, Clone)]
122 /// Security mode of the Falcon microprocessor.
123 ///
124 /// See `falcon.rst` for more details.
125 pub(crate) enum FalconSecurityModel {
126     /// Non-Secure: runs unsigned code without privileges.
127     #[default]
128     None = 0,
129     /// Light-Secured (LS): Runs signed code with some privileges.
130     /// Entry into this mode is only possible from 'Heavy-secure' mode, which verifies the code's
131     /// signature.
132     ///
133     /// Also known as Low-Secure, Privilege Level 2 or PL2.
134     Light = 2,
135     /// Heavy-Secured (HS): Runs signed code with full privileges.
136     /// The code's signature is verified by the Falcon Boot ROM (BROM).
137     ///
138     /// Also known as High-Secure, Privilege Level 3 or PL3.
139     Heavy = 3,
140 }
141 impl_from_enum_to_u8!(FalconSecurityModel);
142 
143 // TODO[FPRI]: replace with `FromPrimitive`.
144 impl TryFrom<u8> for FalconSecurityModel {
145     type Error = Error;
146 
147     fn try_from(value: u8) -> Result<Self> {
148         use FalconSecurityModel::*;
149 
150         let sec_model = match value {
151             0 => None,
152             2 => Light,
153             3 => Heavy,
154             _ => return Err(EINVAL),
155         };
156 
157         Ok(sec_model)
158     }
159 }
160 
161 /// Signing algorithm for a given firmware, used in the [`crate::regs::NV_PFALCON2_FALCON_MOD_SEL`]
162 /// register. It is passed to the Falcon Boot ROM (BROM) as a parameter.
163 #[repr(u8)]
164 #[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
165 pub(crate) enum FalconModSelAlgo {
166     /// AES.
167     #[expect(dead_code)]
168     Aes = 0,
169     /// RSA3K.
170     #[default]
171     Rsa3k = 1,
172 }
173 impl_from_enum_to_u8!(FalconModSelAlgo);
174 
175 // TODO[FPRI]: replace with `FromPrimitive`.
176 impl TryFrom<u8> for FalconModSelAlgo {
177     type Error = Error;
178 
179     fn try_from(value: u8) -> Result<Self> {
180         match value {
181             1 => Ok(FalconModSelAlgo::Rsa3k),
182             _ => Err(EINVAL),
183         }
184     }
185 }
186 
187 /// Valid values for the `size` field of the [`crate::regs::NV_PFALCON_FALCON_DMATRFCMD`] register.
188 #[repr(u8)]
189 #[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
190 pub(crate) enum DmaTrfCmdSize {
191     /// 256 bytes transfer.
192     #[default]
193     Size256B = 0x6,
194 }
195 impl_from_enum_to_u8!(DmaTrfCmdSize);
196 
197 // TODO[FPRI]: replace with `FromPrimitive`.
198 impl TryFrom<u8> for DmaTrfCmdSize {
199     type Error = Error;
200 
201     fn try_from(value: u8) -> Result<Self> {
202         match value {
203             0x6 => Ok(Self::Size256B),
204             _ => Err(EINVAL),
205         }
206     }
207 }
208 
209 /// Currently active core on a dual falcon/riscv (Peregrine) controller.
210 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
211 pub(crate) enum PeregrineCoreSelect {
212     /// Falcon core is active.
213     #[default]
214     Falcon = 0,
215     /// RISC-V core is active.
216     Riscv = 1,
217 }
218 
219 impl From<bool> for PeregrineCoreSelect {
220     fn from(value: bool) -> Self {
221         match value {
222             false => PeregrineCoreSelect::Falcon,
223             true => PeregrineCoreSelect::Riscv,
224         }
225     }
226 }
227 
228 impl From<PeregrineCoreSelect> for bool {
229     fn from(value: PeregrineCoreSelect) -> Self {
230         match value {
231             PeregrineCoreSelect::Falcon => false,
232             PeregrineCoreSelect::Riscv => true,
233         }
234     }
235 }
236 
237 /// Different types of memory present in a falcon core.
238 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
239 pub(crate) enum FalconMem {
240     /// Instruction Memory.
241     Imem,
242     /// Data Memory.
243     Dmem,
244 }
245 
246 /// Defines the Framebuffer Interface (FBIF) aperture type.
247 /// This determines the memory type for external memory access during a DMA transfer, which is
248 /// performed by the Falcon's Framebuffer DMA (FBDMA) engine. See falcon.rst for more details.
249 #[derive(Debug, Clone, Default)]
250 pub(crate) enum FalconFbifTarget {
251     /// VRAM.
252     #[default]
253     /// Local Framebuffer (GPU's VRAM memory).
254     LocalFb = 0,
255     /// Coherent system memory (System DRAM).
256     CoherentSysmem = 1,
257     /// Non-coherent system memory (System DRAM).
258     NoncoherentSysmem = 2,
259 }
260 impl_from_enum_to_u8!(FalconFbifTarget);
261 
262 // TODO[FPRI]: replace with `FromPrimitive`.
263 impl TryFrom<u8> for FalconFbifTarget {
264     type Error = Error;
265 
266     fn try_from(value: u8) -> Result<Self> {
267         let res = match value {
268             0 => Self::LocalFb,
269             1 => Self::CoherentSysmem,
270             2 => Self::NoncoherentSysmem,
271             _ => return Err(EINVAL),
272         };
273 
274         Ok(res)
275     }
276 }
277 
278 /// Type of memory addresses to use.
279 #[derive(Debug, Clone, Default)]
280 pub(crate) enum FalconFbifMemType {
281     /// Virtual memory addresses.
282     #[default]
283     Virtual = 0,
284     /// Physical memory addresses.
285     Physical = 1,
286 }
287 
288 /// Conversion from a single-bit register field.
289 impl From<bool> for FalconFbifMemType {
290     fn from(value: bool) -> Self {
291         match value {
292             false => Self::Virtual,
293             true => Self::Physical,
294         }
295     }
296 }
297 
298 impl From<FalconFbifMemType> for bool {
299     fn from(value: FalconFbifMemType) -> Self {
300         match value {
301             FalconFbifMemType::Virtual => false,
302             FalconFbifMemType::Physical => true,
303         }
304     }
305 }
306 
307 /// Type used to represent the `PFALCON` registers address base for a given falcon engine.
308 pub(crate) struct PFalconBase(());
309 
310 /// Type used to represent the `PFALCON2` registers address base for a given falcon engine.
311 pub(crate) struct PFalcon2Base(());
312 
313 /// Trait defining the parameters of a given Falcon engine.
314 ///
315 /// Each engine provides one base for `PFALCON` and `PFALCON2` registers. The `ID` constant is used
316 /// to identify a given Falcon instance with register I/O methods.
317 pub(crate) trait FalconEngine:
318     Send + Sync + RegisterBase<PFalconBase> + RegisterBase<PFalcon2Base> + Sized
319 {
320     /// Singleton of the engine, used to identify it with register I/O methods.
321     const ID: Self;
322 }
323 
324 /// Represents a portion of the firmware to be loaded into a particular memory (e.g. IMEM or DMEM).
325 #[derive(Debug, Clone)]
326 pub(crate) struct FalconLoadTarget {
327     /// Offset from the start of the source object to copy from.
328     pub(crate) src_start: u32,
329     /// Offset from the start of the destination memory to copy into.
330     pub(crate) dst_start: u32,
331     /// Number of bytes to copy.
332     pub(crate) len: u32,
333 }
334 
335 /// Parameters for the falcon boot ROM.
336 #[derive(Debug, Clone)]
337 pub(crate) struct FalconBromParams {
338     /// Offset in `DMEM`` of the firmware's signature.
339     pub(crate) pkc_data_offset: u32,
340     /// Mask of engines valid for this firmware.
341     pub(crate) engine_id_mask: u16,
342     /// ID of the ucode used to infer a fuse register to validate the signature.
343     pub(crate) ucode_id: u8,
344 }
345 
346 /// Trait for providing load parameters of falcon firmwares.
347 pub(crate) trait FalconLoadParams {
348     /// Returns the load parameters for `IMEM`.
349     fn imem_load_params(&self) -> FalconLoadTarget;
350 
351     /// Returns the load parameters for `DMEM`.
352     fn dmem_load_params(&self) -> FalconLoadTarget;
353 
354     /// Returns the parameters to write into the BROM registers.
355     fn brom_params(&self) -> FalconBromParams;
356 
357     /// Returns the start address of the firmware.
358     fn boot_addr(&self) -> u32;
359 }
360 
361 /// Trait for a falcon firmware.
362 ///
363 /// A falcon firmware can be loaded on a given engine, and is presented in the form of a DMA
364 /// object.
365 pub(crate) trait FalconFirmware: FalconLoadParams + Deref<Target = DmaObject> {
366     /// Engine on which this firmware is to be loaded.
367     type Target: FalconEngine;
368 }
369 
370 /// Contains the base parameters common to all Falcon instances.
371 pub(crate) struct Falcon<E: FalconEngine> {
372     hal: KBox<dyn FalconHal<E>>,
373     dev: ARef<device::Device>,
374 }
375 
376 impl<E: FalconEngine + 'static> Falcon<E> {
377     /// Create a new falcon instance.
378     pub(crate) fn new(dev: &device::Device, chipset: Chipset) -> Result<Self> {
379         Ok(Self {
380             hal: hal::falcon_hal(chipset)?,
381             dev: dev.into(),
382         })
383     }
384 
385     /// Resets DMA-related registers.
386     pub(crate) fn dma_reset(&self, bar: &Bar0) {
387         regs::NV_PFALCON_FBIF_CTL::update(bar, &E::ID, |v| v.set_allow_phys_no_ctx(true));
388         regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID);
389     }
390 
391     /// Wait for memory scrubbing to complete.
392     fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result {
393         // TIMEOUT: memory scrubbing should complete in less than 20ms.
394         read_poll_timeout(
395             || Ok(regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID)),
396             |r| r.mem_scrubbing_done(),
397             Delta::ZERO,
398             Delta::from_millis(20),
399         )
400         .map(|_| ())
401     }
402 
403     /// Reset the falcon engine.
404     fn reset_eng(&self, bar: &Bar0) -> Result {
405         let _ = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID);
406 
407         // According to OpenRM's `kflcnPreResetWait_GA102` documentation, HW sometimes does not set
408         // RESET_READY so a non-failing timeout is used.
409         let _ = read_poll_timeout(
410             || Ok(regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID)),
411             |r| r.reset_ready(),
412             Delta::ZERO,
413             Delta::from_micros(150),
414         );
415 
416         regs::NV_PFALCON_FALCON_ENGINE::update(bar, &E::ID, |v| v.set_reset(true));
417 
418         // TIMEOUT: falcon engine should not take more than 10us to reset.
419         fsleep(Delta::from_micros(10));
420 
421         regs::NV_PFALCON_FALCON_ENGINE::update(bar, &E::ID, |v| v.set_reset(false));
422 
423         self.reset_wait_mem_scrubbing(bar)?;
424 
425         Ok(())
426     }
427 
428     /// Reset the controller, select the falcon core, and wait for memory scrubbing to complete.
429     pub(crate) fn reset(&self, bar: &Bar0) -> Result {
430         self.reset_eng(bar)?;
431         self.hal.select_core(self, bar)?;
432         self.reset_wait_mem_scrubbing(bar)?;
433 
434         regs::NV_PFALCON_FALCON_RM::default()
435             .set_value(regs::NV_PMC_BOOT_0::read(bar).into())
436             .write(bar, &E::ID);
437 
438         Ok(())
439     }
440 
441     /// Perform a DMA write according to `load_offsets` from `dma_handle` into the falcon's
442     /// `target_mem`.
443     ///
444     /// `sec` is set if the loaded firmware is expected to run in secure mode.
445     fn dma_wr<F: FalconFirmware<Target = E>>(
446         &self,
447         bar: &Bar0,
448         fw: &F,
449         target_mem: FalconMem,
450         load_offsets: FalconLoadTarget,
451         sec: bool,
452     ) -> Result {
453         const DMA_LEN: u32 = 256;
454 
455         // For IMEM, we want to use the start offset as a virtual address tag for each page, since
456         // code addresses in the firmware (and the boot vector) are virtual.
457         //
458         // For DMEM we can fold the start offset into the DMA handle.
459         let (src_start, dma_start) = match target_mem {
460             FalconMem::Imem => (load_offsets.src_start, fw.dma_handle()),
461             FalconMem::Dmem => (
462                 0,
463                 fw.dma_handle_with_offset(load_offsets.src_start.into_safe_cast())?,
464             ),
465         };
466         if dma_start % DmaAddress::from(DMA_LEN) > 0 {
467             dev_err!(
468                 self.dev,
469                 "DMA transfer start addresses must be a multiple of {}",
470                 DMA_LEN
471             );
472             return Err(EINVAL);
473         }
474 
475         // DMA transfers can only be done in units of 256 bytes. Compute how many such transfers we
476         // need to perform.
477         let num_transfers = load_offsets.len.div_ceil(DMA_LEN);
478 
479         // Check that the area we are about to transfer is within the bounds of the DMA object.
480         // Upper limit of transfer is `(num_transfers * DMA_LEN) + load_offsets.src_start`.
481         match num_transfers
482             .checked_mul(DMA_LEN)
483             .and_then(|size| size.checked_add(load_offsets.src_start))
484         {
485             None => {
486                 dev_err!(self.dev, "DMA transfer length overflow");
487                 return Err(EOVERFLOW);
488             }
489             Some(upper_bound) if usize::from_safe_cast(upper_bound) > fw.size() => {
490                 dev_err!(self.dev, "DMA transfer goes beyond range of DMA object");
491                 return Err(EINVAL);
492             }
493             Some(_) => (),
494         };
495 
496         // Set up the base source DMA address.
497 
498         regs::NV_PFALCON_FALCON_DMATRFBASE::default()
499             // CAST: `as u32` is used on purpose since we do want to strip the upper bits, which
500             // will be written to `NV_PFALCON_FALCON_DMATRFBASE1`.
501             .set_base((dma_start >> 8) as u32)
502             .write(bar, &E::ID);
503         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
504             // CAST: `as u16` is used on purpose since the remaining bits are guaranteed to fit
505             // within a `u16`.
506             .set_base((dma_start >> 40) as u16)
507             .write(bar, &E::ID);
508 
509         let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::default()
510             .set_size(DmaTrfCmdSize::Size256B)
511             .set_imem(target_mem == FalconMem::Imem)
512             .set_sec(if sec { 1 } else { 0 });
513 
514         for pos in (0..num_transfers).map(|i| i * DMA_LEN) {
515             // Perform a transfer of size `DMA_LEN`.
516             regs::NV_PFALCON_FALCON_DMATRFMOFFS::default()
517                 .set_offs(load_offsets.dst_start + pos)
518                 .write(bar, &E::ID);
519             regs::NV_PFALCON_FALCON_DMATRFFBOFFS::default()
520                 .set_offs(src_start + pos)
521                 .write(bar, &E::ID);
522             cmd.write(bar, &E::ID);
523 
524             // Wait for the transfer to complete.
525             // TIMEOUT: arbitrarily large value, no DMA transfer to the falcon's small memories
526             // should ever take that long.
527             read_poll_timeout(
528                 || Ok(regs::NV_PFALCON_FALCON_DMATRFCMD::read(bar, &E::ID)),
529                 |r| r.idle(),
530                 Delta::ZERO,
531                 Delta::from_secs(2),
532             )?;
533         }
534 
535         Ok(())
536     }
537 
538     /// Perform a DMA load into `IMEM` and `DMEM` of `fw`, and prepare the falcon to run it.
539     pub(crate) fn dma_load<F: FalconFirmware<Target = E>>(&self, bar: &Bar0, fw: &F) -> Result {
540         self.dma_reset(bar);
541         regs::NV_PFALCON_FBIF_TRANSCFG::update(bar, &E::ID, 0, |v| {
542             v.set_target(FalconFbifTarget::CoherentSysmem)
543                 .set_mem_type(FalconFbifMemType::Physical)
544         });
545 
546         self.dma_wr(bar, fw, FalconMem::Imem, fw.imem_load_params(), true)?;
547         self.dma_wr(bar, fw, FalconMem::Dmem, fw.dmem_load_params(), true)?;
548 
549         self.hal.program_brom(self, bar, &fw.brom_params())?;
550 
551         // Set `BootVec` to start of non-secure code.
552         regs::NV_PFALCON_FALCON_BOOTVEC::default()
553             .set_value(fw.boot_addr())
554             .write(bar, &E::ID);
555 
556         Ok(())
557     }
558 
559     /// Wait until the falcon CPU is halted.
560     pub(crate) fn wait_till_halted(&self, bar: &Bar0) -> Result<()> {
561         // TIMEOUT: arbitrarily large value, firmwares should complete in less than 2 seconds.
562         read_poll_timeout(
563             || Ok(regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID)),
564             |r| r.halted(),
565             Delta::ZERO,
566             Delta::from_secs(2),
567         )?;
568 
569         Ok(())
570     }
571 
572     /// Start the falcon CPU.
573     pub(crate) fn start(&self, bar: &Bar0) -> Result<()> {
574         match regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID).alias_en() {
575             true => regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::default()
576                 .set_startcpu(true)
577                 .write(bar, &E::ID),
578             false => regs::NV_PFALCON_FALCON_CPUCTL::default()
579                 .set_startcpu(true)
580                 .write(bar, &E::ID),
581         }
582 
583         Ok(())
584     }
585 
586     /// Writes values to the mailbox registers if provided.
587     pub(crate) fn write_mailboxes(&self, bar: &Bar0, mbox0: Option<u32>, mbox1: Option<u32>) {
588         if let Some(mbox0) = mbox0 {
589             regs::NV_PFALCON_FALCON_MAILBOX0::default()
590                 .set_value(mbox0)
591                 .write(bar, &E::ID);
592         }
593 
594         if let Some(mbox1) = mbox1 {
595             regs::NV_PFALCON_FALCON_MAILBOX1::default()
596                 .set_value(mbox1)
597                 .write(bar, &E::ID);
598         }
599     }
600 
601     /// Reads the value from `mbox0` register.
602     pub(crate) fn read_mailbox0(&self, bar: &Bar0) -> u32 {
603         regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, &E::ID).value()
604     }
605 
606     /// Reads the value from `mbox1` register.
607     pub(crate) fn read_mailbox1(&self, bar: &Bar0) -> u32 {
608         regs::NV_PFALCON_FALCON_MAILBOX1::read(bar, &E::ID).value()
609     }
610 
611     /// Reads values from both mailbox registers.
612     pub(crate) fn read_mailboxes(&self, bar: &Bar0) -> (u32, u32) {
613         let mbox0 = self.read_mailbox0(bar);
614         let mbox1 = self.read_mailbox1(bar);
615 
616         (mbox0, mbox1)
617     }
618 
619     /// Start running the loaded firmware.
620     ///
621     /// `mbox0` and `mbox1` are optional parameters to write into the `MBOX0` and `MBOX1` registers
622     /// prior to running.
623     ///
624     /// Wait up to two seconds for the firmware to complete, and return its exit status read from
625     /// the `MBOX0` and `MBOX1` registers.
626     pub(crate) fn boot(
627         &self,
628         bar: &Bar0,
629         mbox0: Option<u32>,
630         mbox1: Option<u32>,
631     ) -> Result<(u32, u32)> {
632         self.write_mailboxes(bar, mbox0, mbox1);
633         self.start(bar)?;
634         self.wait_till_halted(bar)?;
635         Ok(self.read_mailboxes(bar))
636     }
637 
638     /// Returns the fused version of the signature to use in order to run a HS firmware on this
639     /// falcon instance. `engine_id_mask` and `ucode_id` are obtained from the firmware header.
640     pub(crate) fn signature_reg_fuse_version(
641         &self,
642         bar: &Bar0,
643         engine_id_mask: u16,
644         ucode_id: u8,
645     ) -> Result<u32> {
646         self.hal
647             .signature_reg_fuse_version(self, bar, engine_id_mask, ucode_id)
648     }
649 
650     /// Check if the RISC-V core is active.
651     ///
652     /// Returns `true` if the RISC-V core is active, `false` otherwise.
653     pub(crate) fn is_riscv_active(&self, bar: &Bar0) -> bool {
654         let cpuctl = regs::NV_PRISCV_RISCV_CPUCTL::read(bar, &E::ID);
655         cpuctl.active_stat()
656     }
657 
658     /// Write the application version to the OS register.
659     pub(crate) fn write_os_version(&self, bar: &Bar0, app_version: u32) {
660         regs::NV_PFALCON_FALCON_OS::default()
661             .set_value(app_version)
662             .write(bar, &E::ID);
663     }
664 }
665