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