Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/drivers/gpu/nova-core/falcon.rs
49478 views
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
666