[PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt

Alexandre Courbot posted 3 patches 4 months ago
[PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Alexandre Courbot 4 months ago
Use BoundedInt with the register!() macro and adapt the nova-core code
accordingly. This makes it impossible to trim values when setting a
register field, because either the value of the field has been inferred
at compile-time to fit within the bounds of the field, or the user has
been forced to check at runtime that it does indeed fit.

The use of BoundedInt actually simplifies register fields definitions,
as they don't need an intermediate storage type (the "as ..." part of
fields definitions). Instead, the internal storage type for each field
is now the bounded integer of its width in bits, which can optionally be
converted to another type that implements `From`` or `TryFrom`` for that
bounded integer type.

This means that something like

  register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
      3:3     status_valid as bool,
      31:8    addr as u32,
  });

Now becomes

  register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
      3:3     status_valid => bool,
      31:8    addr,
  });

(here `status_valid` is infallibly converted to a bool for convenience
and to remain compatible with the previous semantics)

The field setter/getters are also simplified. If a field has no target
type, then its setter expects any type that implements `Into` to the
field's bounded integer type. Due to the many `From` implementations for
primitive types, this means that most calls can be left unchanged. If
the caller passes a value that is potentially larger than the field's
capacity, it must use the `try_` variant of the setter, which returns an
error if the value cannot be converted at runtime.

For fields that use `=>` to convert to another type, both setter and
getter are always infallible.

For fields that use `?=>` to fallibly convert to another type, only the
getter needs to be fallible as the setter always provide valid values by
design.

Outside of the register macro, the biggest changes occur in `falcon.rs`,
which defines many enums for fields - their conversion implementations
need to be changed from the original primitive type of the field to the
new corresponding bounded int type. Hopefully the TryFrom/Into derive
macros [1] can take care of implementing these, but it will need to be
adapted to support bounded integers... :/

But overall, I am rather happy at how simple it was to convert the whole
of nova-core to this.

Note: This RFC uses nova-core's register!() macro for practical
purposes, but the hope is to move this patch on top of the bitfield
macro after it is split out [2].

[1] https://lore.kernel.org/rust-for-linux/cover.1755235180.git.y.j3ms.n@gmail.com/
[2] https://lore.kernel.org/rust-for-linux/20251003154748.1687160-1-joelagnelf@nvidia.com/

Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
 drivers/gpu/nova-core/falcon.rs           | 134 ++++++++-------
 drivers/gpu/nova-core/falcon/hal/ga102.rs |   5 +-
 drivers/gpu/nova-core/fb/hal/ga100.rs     |   3 +-
 drivers/gpu/nova-core/gpu.rs              |   9 +-
 drivers/gpu/nova-core/regs.rs             | 139 ++++++++--------
 drivers/gpu/nova-core/regs/macros.rs      | 264 +++++++++++++++---------------
 6 files changed, 283 insertions(+), 271 deletions(-)

diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 3f505b870601..71cb09cf7d67 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -6,6 +6,7 @@
 use hal::FalconHal;
 use kernel::device;
 use kernel::dma::DmaAddress;
+use kernel::num::{Boundable, BoundedInt};
 use kernel::prelude::*;
 use kernel::sync::aref::ARef;
 use kernel::time::Delta;
@@ -22,11 +23,14 @@
 pub(crate) mod sec2;
 
 // TODO[FPRI]: Replace with `ToPrimitive`.
-macro_rules! impl_from_enum_to_u8 {
-    ($enum_type:ty) => {
-        impl From<$enum_type> for u8 {
+macro_rules! impl_from_enum_to_bounded {
+    ($enum_type:ty, $length:literal) => {
+        impl<T> From<$enum_type> for BoundedInt<T, $length>
+        where
+            T: From<u8> + Boundable<$length>,
+        {
             fn from(value: $enum_type) -> Self {
-                value as u8
+                BoundedInt::new(T::from(value as u8))
             }
         }
     };
@@ -46,16 +50,19 @@ pub(crate) enum FalconCoreRev {
     Rev6 = 6,
     Rev7 = 7,
 }
-impl_from_enum_to_u8!(FalconCoreRev);
+impl_from_enum_to_bounded!(FalconCoreRev, 4);
 
 // TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconCoreRev {
+impl<T> TryFrom<BoundedInt<T, 4>> for FalconCoreRev
+where
+    T: Boundable<4>,
+{
     type Error = Error;
 
-    fn try_from(value: u8) -> Result<Self> {
+    fn try_from(value: BoundedInt<T, 4>) -> Result<Self> {
         use FalconCoreRev::*;
 
-        let rev = match value {
+        let rev = match u8::from(value) {
             1 => Rev1,
             2 => Rev2,
             3 => Rev3,
@@ -81,24 +88,25 @@ pub(crate) enum FalconCoreRevSubversion {
     Subversion2 = 2,
     Subversion3 = 3,
 }
-impl_from_enum_to_u8!(FalconCoreRevSubversion);
+impl_from_enum_to_bounded!(FalconCoreRevSubversion, 2);
 
 // TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconCoreRevSubversion {
-    type Error = Error;
-
-    fn try_from(value: u8) -> Result<Self> {
+impl<T> From<BoundedInt<T, 2>> for FalconCoreRevSubversion
+where
+    T: Boundable<2>,
+{
+    fn from(value: BoundedInt<T, 2>) -> Self {
         use FalconCoreRevSubversion::*;
 
-        let sub_version = match value & 0b11 {
+        match u8::from(value) {
             0 => Subversion0,
             1 => Subversion1,
             2 => Subversion2,
             3 => Subversion3,
-            _ => return Err(EINVAL),
-        };
-
-        Ok(sub_version)
+            // TODO: somehow the compiler cannot infer that `value` cannot be > 3. Find a way to
+            // handle this gracefully, or switch back to fallible ops.
+            _ => panic!(),
+        }
     }
 }
 
@@ -125,16 +133,19 @@ pub(crate) enum FalconSecurityModel {
     /// Also known as High-Secure, Privilege Level 3 or PL3.
     Heavy = 3,
 }
-impl_from_enum_to_u8!(FalconSecurityModel);
+impl_from_enum_to_bounded!(FalconSecurityModel, 2);
 
 // TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconSecurityModel {
+impl<T> TryFrom<BoundedInt<T, 2>> for FalconSecurityModel
+where
+    T: Boundable<2>,
+{
     type Error = Error;
 
-    fn try_from(value: u8) -> Result<Self> {
+    fn try_from(value: BoundedInt<T, 2>) -> Result<Self> {
         use FalconSecurityModel::*;
 
-        let sec_model = match value {
+        let sec_model = match u8::from(value) {
             0 => None,
             2 => Light,
             3 => Heavy,
@@ -157,14 +168,17 @@ pub(crate) enum FalconModSelAlgo {
     #[default]
     Rsa3k = 1,
 }
-impl_from_enum_to_u8!(FalconModSelAlgo);
+impl_from_enum_to_bounded!(FalconModSelAlgo, 8);
 
 // TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconModSelAlgo {
+impl<T> TryFrom<BoundedInt<T, 8>> for FalconModSelAlgo
+where
+    T: Boundable<8>,
+{
     type Error = Error;
 
-    fn try_from(value: u8) -> Result<Self> {
-        match value {
+    fn try_from(value: BoundedInt<T, 8>) -> Result<Self> {
+        match u8::from(value) {
             1 => Ok(FalconModSelAlgo::Rsa3k),
             _ => Err(EINVAL),
         }
@@ -179,14 +193,17 @@ pub(crate) enum DmaTrfCmdSize {
     #[default]
     Size256B = 0x6,
 }
-impl_from_enum_to_u8!(DmaTrfCmdSize);
+impl_from_enum_to_bounded!(DmaTrfCmdSize, 3);
 
 // TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for DmaTrfCmdSize {
+impl<T> TryFrom<BoundedInt<T, 3>> for DmaTrfCmdSize
+where
+    T: Boundable<3>,
+{
     type Error = Error;
 
-    fn try_from(value: u8) -> Result<Self> {
-        match value {
+    fn try_from(value: BoundedInt<T, 3>) -> Result<Self> {
+        match u8::from(value) {
             0x6 => Ok(Self::Size256B),
             _ => Err(EINVAL),
         }
@@ -202,25 +219,20 @@ pub(crate) enum PeregrineCoreSelect {
     /// RISC-V core is active.
     Riscv = 1,
 }
+impl_from_enum_to_bounded!(PeregrineCoreSelect, 1);
 
-impl From<bool> for PeregrineCoreSelect {
-    fn from(value: bool) -> Self {
-        match value {
+impl<T> From<BoundedInt<T, 1>> for PeregrineCoreSelect
+where
+    T: Boundable<1> + Zeroable,
+{
+    fn from(value: BoundedInt<T, 1>) -> Self {
+        match bool::from(value) {
             false => PeregrineCoreSelect::Falcon,
             true => PeregrineCoreSelect::Riscv,
         }
     }
 }
 
-impl From<PeregrineCoreSelect> for bool {
-    fn from(value: PeregrineCoreSelect) -> Self {
-        match value {
-            PeregrineCoreSelect::Falcon => false,
-            PeregrineCoreSelect::Riscv => true,
-        }
-    }
-}
-
 /// Different types of memory present in a falcon core.
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub(crate) enum FalconMem {
@@ -244,14 +256,17 @@ pub(crate) enum FalconFbifTarget {
     /// Non-coherent system memory (System DRAM).
     NoncoherentSysmem = 2,
 }
-impl_from_enum_to_u8!(FalconFbifTarget);
+impl_from_enum_to_bounded!(FalconFbifTarget, 2);
 
 // TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconFbifTarget {
+impl<T> TryFrom<BoundedInt<T, 2>> for FalconFbifTarget
+where
+    T: Boundable<2>,
+{
     type Error = Error;
 
-    fn try_from(value: u8) -> Result<Self> {
-        let res = match value {
+    fn try_from(value: BoundedInt<T, 2>) -> Result<Self> {
+        let res = match u8::from(value) {
             0 => Self::LocalFb,
             1 => Self::CoherentSysmem,
             2 => Self::NoncoherentSysmem,
@@ -271,26 +286,21 @@ pub(crate) enum FalconFbifMemType {
     /// Physical memory addresses.
     Physical = 1,
 }
+impl_from_enum_to_bounded!(FalconFbifMemType, 1);
 
 /// Conversion from a single-bit register field.
-impl From<bool> for FalconFbifMemType {
-    fn from(value: bool) -> Self {
-        match value {
+impl<T> From<BoundedInt<T, 1>> for FalconFbifMemType
+where
+    T: Boundable<1> + Zeroable,
+{
+    fn from(value: BoundedInt<T, 1>) -> Self {
+        match bool::from(value) {
             false => Self::Virtual,
             true => Self::Physical,
         }
     }
 }
 
-impl From<FalconFbifMemType> for bool {
-    fn from(value: FalconFbifMemType) -> Self {
-        match value {
-            FalconFbifMemType::Virtual => false,
-            FalconFbifMemType::Physical => true,
-        }
-    }
-}
-
 /// Type used to represent the `PFALCON` registers address base for a given falcon engine.
 pub(crate) struct PFalconBase(());
 
@@ -440,7 +450,7 @@ pub(crate) fn reset(&self, bar: &Bar0) -> Result {
         self.reset_wait_mem_scrubbing(bar)?;
 
         regs::NV_PFALCON_FALCON_RM::default()
-            .set_value(regs::NV_PMC_BOOT_0::read(bar).into())
+            .set_value(u32::from(regs::NV_PMC_BOOT_0::read(bar)))
             .write(bar, &E::ID);
 
         Ok(())
@@ -507,18 +517,18 @@ fn dma_wr<F: FalconFirmware<Target = E>>(
             .set_base((dma_start >> 8) as u32)
             .write(bar, &E::ID);
         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
-            .set_base((dma_start >> 40) as u16)
+            .try_set_base(dma_start >> 40)?
             .write(bar, &E::ID);
 
         let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::default()
             .set_size(DmaTrfCmdSize::Size256B)
             .set_imem(target_mem == FalconMem::Imem)
-            .set_sec(if sec { 1 } else { 0 });
+            .set_sec(BoundedInt::new(if sec { 1 } else { 0 }));
 
         for pos in (0..num_transfers).map(|i| i * DMA_LEN) {
             // Perform a transfer of size `DMA_LEN`.
             regs::NV_PFALCON_FALCON_DMATRFMOFFS::default()
-                .set_offs(load_offsets.dst_start + pos)
+                .try_set_offs(load_offsets.dst_start + pos)?
                 .write(bar, &E::ID);
             regs::NV_PFALCON_FALCON_DMATRFFBOFFS::default()
                 .set_offs(src_start + pos)
diff --git a/drivers/gpu/nova-core/falcon/hal/ga102.rs b/drivers/gpu/nova-core/falcon/hal/ga102.rs
index 0b1cbe7853b3..08c8b4123ce8 100644
--- a/drivers/gpu/nova-core/falcon/hal/ga102.rs
+++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs
@@ -55,7 +55,7 @@ fn signature_reg_fuse_version_ga102(
 
     // `ucode_idx` is guaranteed to be in the range [0..15], making the `read` calls provable valid
     // at build-time.
-    let reg_fuse_version = if engine_id_mask & 0x0001 != 0 {
+    let reg_fuse_version: u16 = if engine_id_mask & 0x0001 != 0 {
         regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::read(bar, ucode_idx).data()
     } else if engine_id_mask & 0x0004 != 0 {
         regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::read(bar, ucode_idx).data()
@@ -64,7 +64,8 @@ fn signature_reg_fuse_version_ga102(
     } else {
         dev_err!(dev, "unexpected engine_id_mask {:#x}", engine_id_mask);
         return Err(EINVAL);
-    };
+    }
+    .into();
 
     // TODO[NUMM]: replace with `last_set_bit` once it lands.
     Ok(u16::BITS - reg_fuse_version.leading_zeros())
diff --git a/drivers/gpu/nova-core/fb/hal/ga100.rs b/drivers/gpu/nova-core/fb/hal/ga100.rs
index 871c42bf033a..5449902f2489 100644
--- a/drivers/gpu/nova-core/fb/hal/ga100.rs
+++ b/drivers/gpu/nova-core/fb/hal/ga100.rs
@@ -2,6 +2,7 @@
 
 struct Ga100;
 
+use kernel::num::BoundedInt;
 use kernel::prelude::*;
 
 use crate::driver::Bar0;
@@ -18,7 +19,7 @@ pub(super) fn read_sysmem_flush_page_ga100(bar: &Bar0) -> u64 {
 
 pub(super) fn write_sysmem_flush_page_ga100(bar: &Bar0, addr: u64) {
     regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::default()
-        .set_adr_63_40((addr >> FLUSH_SYSMEM_ADDR_SHIFT_HI) as u32)
+        .set_adr_63_40(BoundedInt::new(addr >> FLUSH_SYSMEM_ADDR_SHIFT_HI).cast())
         .write(bar);
     regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::default()
         .set_adr_39_08((addr >> FLUSH_SYSMEM_ADDR_SHIFT) as u32)
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 5da9ad726483..7ed382d82fc7 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -1,5 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
 
+use kernel::num::BoundedInt;
 use kernel::{device, devres::Devres, error::code::*, pci, prelude::*, sync::Arc};
 
 use crate::driver::Bar0;
@@ -131,15 +132,15 @@ fn try_from(value: u8) -> Result<Self> {
 }
 
 pub(crate) struct Revision {
-    major: u8,
-    minor: u8,
+    major: BoundedInt<u8, 4>,
+    minor: BoundedInt<u8, 4>,
 }
 
 impl Revision {
     fn from_boot0(boot0: regs::NV_PMC_BOOT_0) -> Self {
         Self {
-            major: boot0.major_revision(),
-            minor: boot0.minor_revision(),
+            major: boot0.major_revision().cast(),
+            minor: boot0.minor_revision().cast(),
         }
     }
 }
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 206dab2e1335..1542d72e4a65 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -17,18 +17,19 @@
 // PMC
 
 register!(NV_PMC_BOOT_0 @ 0x00000000, "Basic revision information about the GPU" {
-    3:0     minor_revision as u8, "Minor revision of the chip";
-    7:4     major_revision as u8, "Major revision of the chip";
-    8:8     architecture_1 as u8, "MSB of the architecture";
-    23:20   implementation as u8, "Implementation version of the architecture";
-    28:24   architecture_0 as u8, "Lower bits of the architecture";
+    3:0     minor_revision, "Minor revision of the chip";
+    7:4     major_revision, "Major revision of the chip";
+    8:8     architecture_1, "MSB of the architecture";
+    23:20   implementation, "Implementation version of the architecture";
+    28:24   architecture_0, "Lower bits of the architecture";
 });
 
 impl NV_PMC_BOOT_0 {
     /// Combines `architecture_0` and `architecture_1` to obtain the architecture of the chip.
     pub(crate) fn architecture(self) -> Result<Architecture> {
         Architecture::try_from(
-            self.architecture_0() | (self.architecture_1() << Self::ARCHITECTURE_0_RANGE.len()),
+            u8::from(self.architecture_0())
+                | (u8::from(self.architecture_1()) << Self::ARCHITECTURE_0_RANGE.len()),
         )
     }
 
@@ -49,7 +50,7 @@ pub(crate) fn chipset(self) -> Result<Chipset> {
 
 register!(NV_PBUS_SW_SCRATCH_0E_FRTS_ERR => NV_PBUS_SW_SCRATCH[0xe],
     "scratch register 0xe used as FRTS firmware error code" {
-    31:16   frts_err_code as u16;
+    31:16   frts_err_code;
 });
 
 // PFB
@@ -58,17 +59,17 @@ pub(crate) fn chipset(self) -> Result<Chipset> {
 // GPU to perform sysmembar operations (see `fb::SysmemFlush`).
 
 register!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR @ 0x00100c10 {
-    31:0    adr_39_08 as u32;
+    31:0    adr_39_08;
 });
 
 register!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI @ 0x00100c40 {
-    23:0    adr_63_40 as u32;
+    23:0    adr_63_40;
 });
 
 register!(NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE @ 0x00100ce0 {
-    3:0     lower_scale as u8;
-    9:4     lower_mag as u8;
-    30:30   ecc_mode_enabled as bool;
+    3:0     lower_scale;
+    9:4     lower_mag;
+    30:30   ecc_mode_enabled => bool;
 });
 
 impl NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE {
@@ -87,7 +88,7 @@ pub(crate) fn usable_fb_size(self) -> u64 {
 }
 
 register!(NV_PFB_PRI_MMU_WPR2_ADDR_LO@0x001fa824  {
-    31:4    lo_val as u32, "Bits 12..40 of the lower (inclusive) bound of the WPR2 region";
+    31:4    lo_val, "Bits 12..40 of the lower (inclusive) bound of the WPR2 region";
 });
 
 impl NV_PFB_PRI_MMU_WPR2_ADDR_LO {
@@ -98,7 +99,7 @@ pub(crate) fn lower_bound(self) -> u64 {
 }
 
 register!(NV_PFB_PRI_MMU_WPR2_ADDR_HI@0x001fa828  {
-    31:4    hi_val as u32, "Bits 12..40 of the higher (exclusive) bound of the WPR2 region";
+    31:4    hi_val, "Bits 12..40 of the higher (exclusive) bound of the WPR2 region";
 });
 
 impl NV_PFB_PRI_MMU_WPR2_ADDR_HI {
@@ -123,7 +124,7 @@ pub(crate) fn higher_bound(self) -> u64 {
 // `PGC6_AON_SECURE_SCRATCH_GROUP_05` register (which it needs to read GFW_BOOT).
 register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK @ 0x00118128,
           "Privilege level mask register" {
-    0:0     read_protection_level0 as bool, "Set after FWSEC lowers its protection level";
+    0:0     read_protection_level0 => bool, "Set after FWSEC lowers its protection level";
 });
 
 // OpenRM defines this as a register array, but doesn't specify its size and only uses its first
@@ -133,7 +134,7 @@ pub(crate) fn higher_bound(self) -> u64 {
 register!(
     NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT => NV_PGC6_AON_SECURE_SCRATCH_GROUP_05[0],
     "Scratch group 05 register 0 used as GFW boot progress indicator" {
-        7:0    progress as u8, "Progress of GFW boot (0xff means completed)";
+        7:0    progress, "Progress of GFW boot (0xff means completed)";
     }
 );
 
@@ -145,13 +146,13 @@ pub(crate) fn completed(self) -> bool {
 }
 
 register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_42 @ 0x001183a4 {
-    31:0    value as u32;
+    31:0    value;
 });
 
 register!(
     NV_USABLE_FB_SIZE_IN_MB => NV_PGC6_AON_SECURE_SCRATCH_GROUP_42,
     "Scratch group 42 register used as framebuffer size" {
-        31:0    value as u32, "Usable framebuffer size, in megabytes";
+        31:0    value, "Usable framebuffer size, in megabytes";
     }
 );
 
@@ -165,8 +166,8 @@ pub(crate) fn usable_fb_size(self) -> u64 {
 // PDISP
 
 register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
-    3:3     status_valid as bool, "Set if the `addr` field is valid";
-    31:8    addr as u32, "VGA workspace base address divided by 0x10000";
+    3:3     status_valid => bool, "Set if the `addr` field is valid";
+    31:8    addr, "VGA workspace base address divided by 0x10000";
 });
 
 impl NV_PDISP_VGA_WORKSPACE_BASE {
@@ -185,40 +186,40 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> {
 pub(crate) const NV_FUSE_OPT_FPF_SIZE: usize = 16;
 
 register!(NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION @ 0x00824100[NV_FUSE_OPT_FPF_SIZE] {
-    15:0    data as u16;
+    15:0    data;
 });
 
 register!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140[NV_FUSE_OPT_FPF_SIZE] {
-    15:0    data as u16;
+    15:0    data;
 });
 
 register!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0[NV_FUSE_OPT_FPF_SIZE] {
-    15:0    data as u16;
+    15:0    data;
 });
 
 // PFALCON
 
 register!(NV_PFALCON_FALCON_IRQSCLR @ PFalconBase[0x00000004] {
-    4:4     halt as bool;
-    6:6     swgen0 as bool;
+    4:4     halt => bool;
+    6:6     swgen0 => bool;
 });
 
 register!(NV_PFALCON_FALCON_MAILBOX0 @ PFalconBase[0x00000040] {
-    31:0    value as u32;
+    31:0    value => u32;
 });
 
 register!(NV_PFALCON_FALCON_MAILBOX1 @ PFalconBase[0x00000044] {
-    31:0    value as u32;
+    31:0    value => u32;
 });
 
 register!(NV_PFALCON_FALCON_RM @ PFalconBase[0x00000084] {
-    31:0    value as u32;
+    31:0    value => u32;
 });
 
 register!(NV_PFALCON_FALCON_HWCFG2 @ PFalconBase[0x000000f4] {
-    10:10   riscv as bool;
-    12:12   mem_scrubbing as bool, "Set to 0 after memory scrubbing is completed";
-    31:31   reset_ready as bool, "Signal indicating that reset is completed (GA102+)";
+    10:10   riscv => bool;
+    12:12   mem_scrubbing => bool, "Set to 0 after memory scrubbing is completed";
+    31:31   reset_ready => bool, "Signal indicating that reset is completed (GA102+)";
 });
 
 impl NV_PFALCON_FALCON_HWCFG2 {
@@ -229,101 +230,101 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
 }
 
 register!(NV_PFALCON_FALCON_CPUCTL @ PFalconBase[0x00000100] {
-    1:1     startcpu as bool;
-    4:4     halted as bool;
-    6:6     alias_en as bool;
+    1:1     startcpu => bool;
+    4:4     halted => bool;
+    6:6     alias_en => bool;
 });
 
 register!(NV_PFALCON_FALCON_BOOTVEC @ PFalconBase[0x00000104] {
-    31:0    value as u32;
+    31:0    value => u32;
 });
 
 register!(NV_PFALCON_FALCON_DMACTL @ PFalconBase[0x0000010c] {
-    0:0     require_ctx as bool;
-    1:1     dmem_scrubbing as bool;
-    2:2     imem_scrubbing as bool;
-    6:3     dmaq_num as u8;
-    7:7     secure_stat as bool;
+    0:0     require_ctx => bool;
+    1:1     dmem_scrubbing => bool;
+    2:2     imem_scrubbing => bool;
+    6:3     dmaq_num;
+    7:7     secure_stat => bool;
 });
 
 register!(NV_PFALCON_FALCON_DMATRFBASE @ PFalconBase[0x00000110] {
-    31:0    base as u32;
+    31:0    base => u32;
 });
 
 register!(NV_PFALCON_FALCON_DMATRFMOFFS @ PFalconBase[0x00000114] {
-    23:0    offs as u32;
+    23:0    offs;
 });
 
 register!(NV_PFALCON_FALCON_DMATRFCMD @ PFalconBase[0x00000118] {
-    0:0     full as bool;
-    1:1     idle as bool;
-    3:2     sec as u8;
-    4:4     imem as bool;
-    5:5     is_write as bool;
-    10:8    size as u8 ?=> DmaTrfCmdSize;
-    14:12   ctxdma as u8;
-    16:16   set_dmtag as u8;
+    0:0     full => bool;
+    1:1     idle => bool;
+    3:2     sec;
+    4:4     imem => bool;
+    5:5     is_write => bool;
+    10:8    size ?=> DmaTrfCmdSize;
+    14:12   ctxdma;
+    16:16   set_dmtag;
 });
 
 register!(NV_PFALCON_FALCON_DMATRFFBOFFS @ PFalconBase[0x0000011c] {
-    31:0    offs as u32;
+    31:0    offs => u32;
 });
 
 register!(NV_PFALCON_FALCON_DMATRFBASE1 @ PFalconBase[0x00000128] {
-    8:0     base as u16;
+    8:0     base;
 });
 
 register!(NV_PFALCON_FALCON_HWCFG1 @ PFalconBase[0x0000012c] {
-    3:0     core_rev as u8 ?=> FalconCoreRev, "Core revision";
-    5:4     security_model as u8 ?=> FalconSecurityModel, "Security model";
-    7:6     core_rev_subversion as u8 ?=> FalconCoreRevSubversion, "Core revision subversion";
+    3:0     core_rev ?=> FalconCoreRev, "Core revision";
+    5:4     security_model ?=> FalconSecurityModel, "Security model";
+    7:6     core_rev_subversion => FalconCoreRevSubversion, "Core revision subversion";
 });
 
 register!(NV_PFALCON_FALCON_CPUCTL_ALIAS @ PFalconBase[0x00000130] {
-    1:1     startcpu as bool;
+    1:1     startcpu => bool;
 });
 
 // Actually known as `NV_PSEC_FALCON_ENGINE` and `NV_PGSP_FALCON_ENGINE` depending on the falcon
 // instance.
 register!(NV_PFALCON_FALCON_ENGINE @ PFalconBase[0x000003c0] {
-    0:0     reset as bool;
+    0:0     reset => bool;
 });
 
 register!(NV_PFALCON_FBIF_TRANSCFG @ PFalconBase[0x00000600[8]] {
-    1:0     target as u8 ?=> FalconFbifTarget;
-    2:2     mem_type as bool => FalconFbifMemType;
+    1:0     target ?=> FalconFbifTarget;
+    2:2     mem_type => FalconFbifMemType;
 });
 
 register!(NV_PFALCON_FBIF_CTL @ PFalconBase[0x00000624] {
-    7:7     allow_phys_no_ctx as bool;
+    7:7     allow_phys_no_ctx => bool;
 });
 
 /* PFALCON2 */
 
 register!(NV_PFALCON2_FALCON_MOD_SEL @ PFalcon2Base[0x00000180] {
-    7:0     algo as u8 ?=> FalconModSelAlgo;
+    7:0     algo ?=> FalconModSelAlgo;
 });
 
 register!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ PFalcon2Base[0x00000198] {
-    7:0    ucode_id as u8;
+    7:0    ucode_id => u8;
 });
 
 register!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ PFalcon2Base[0x0000019c] {
-    31:0    value as u32;
+    31:0    value => u32;
 });
 
 // OpenRM defines this as a register array, but doesn't specify its size and only uses its first
 // element. Be conservative until we know the actual size or need to use more registers.
 register!(NV_PFALCON2_FALCON_BROM_PARAADDR @ PFalcon2Base[0x00000210[1]] {
-    31:0    value as u32;
+    31:0    value => u32;
 });
 
 // PRISCV
 
 register!(NV_PRISCV_RISCV_BCR_CTRL @ PFalconBase[0x00001668] {
-    0:0     valid as bool;
-    4:4     core_select as bool => PeregrineCoreSelect;
-    8:8     br_fetch as bool;
+    0:0     valid => bool;
+    4:4     core_select => PeregrineCoreSelect;
+    8:8     br_fetch => bool;
 });
 
 // The modules below provide registers that are not identical on all supported chips. They should
@@ -333,7 +334,7 @@ pub(crate) mod gm107 {
     // FUSE
 
     register!(NV_FUSE_STATUS_OPT_DISPLAY @ 0x00021c04 {
-        0:0     display_disabled as bool;
+        0:0     display_disabled => bool;
     });
 }
 
@@ -341,6 +342,6 @@ pub(crate) mod ga100 {
     // FUSE
 
     register!(NV_FUSE_STATUS_OPT_DISPLAY @ 0x00820c04 {
-        0:0     display_disabled as bool;
+        0:0     display_disabled => bool;
     });
 }
diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs
index 73811a115762..54c7f6fc746b 100644
--- a/drivers/gpu/nova-core/regs/macros.rs
+++ b/drivers/gpu/nova-core/regs/macros.rs
@@ -398,12 +398,9 @@ fn from(reg: $name) -> u32 {
         register!(@fields_dispatcher $name { $($fields)* });
     };
 
-    // Captures the fields and passes them to all the implementers that require field information.
-    //
-    // Used to simplify the matching rules for implementers, so they don't need to match the entire
-    // complex fields rule even though they only make use of part of it.
+    // Dispatch fields for the bounded integers syntax.
     (@fields_dispatcher $name:ident {
-        $($hi:tt:$lo:tt $field:ident as $type:tt
+        $($hi:tt:$lo:tt $field:ident
             $(?=> $try_into_type:ty)?
             $(=> $into_type:ty)?
             $(, $comment:literal)?
@@ -411,123 +408,25 @@ fn from(reg: $name) -> u32 {
         )*
     }
     ) => {
-        register!(@field_accessors $name {
-            $(
-                $hi:$lo $field as $type
-                $(?=> $try_into_type)?
-                $(=> $into_type)?
-                $(, $comment)?
-            ;
-            )*
-        });
-        register!(@debug $name { $($field;)* });
-        register!(@default $name { $($field;)* });
-    };
-
-    // Defines all the field getter/methods methods for `$name`.
-    (
-        @field_accessors $name:ident {
-        $($hi:tt:$lo:tt $field:ident as $type:tt
-            $(?=> $try_into_type:ty)?
-            $(=> $into_type:ty)?
-            $(, $comment:literal)?
-        ;
-        )*
-        }
-    ) => {
-        $(
-            register!(@check_field_bounds $hi:$lo $field as $type);
-        )*
-
         #[allow(dead_code)]
         impl $name {
-            $(
-            register!(@field_accessor $name $hi:$lo $field as $type
-                $(?=> $try_into_type)?
-                $(=> $into_type)?
-                $(, $comment)?
-                ;
-            );
-            )*
-        }
-    };
-
-    // Boolean fields must have `$hi == $lo`.
-    (@check_field_bounds $hi:tt:$lo:tt $field:ident as bool) => {
-        #[allow(clippy::eq_op)]
-        const _: () = {
-            ::kernel::build_assert!(
-                $hi == $lo,
-                concat!("boolean field `", stringify!($field), "` covers more than one bit")
-            );
-        };
-    };
-
-    // Non-boolean fields must have `$hi >= $lo`.
-    (@check_field_bounds $hi:tt:$lo:tt $field:ident as $type:tt) => {
-        #[allow(clippy::eq_op)]
-        const _: () = {
-            ::kernel::build_assert!(
-                $hi >= $lo,
-                concat!("field `", stringify!($field), "`'s MSB is smaller than its LSB")
-            );
-        };
-    };
-
-    // Catches fields defined as `bool` and convert them into a boolean value.
-    (
-        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as bool => $into_type:ty
-            $(, $comment:literal)?;
-    ) => {
-        register!(
-            @leaf_accessor $name $hi:$lo $field
-            { |f| <$into_type>::from(if f != 0 { true } else { false }) }
-            bool $into_type => $into_type $(, $comment)?;
+        $(
+        register!(@private_field_accessors $name $hi:$lo $field);
+        register!(@public_field_accessors $name $hi:$lo $field
+            $(?=> $try_into_type)?
+            $(=> $into_type)?
+            $(, $comment)?
         );
+        )*
+        }
+
+        register!(@debug $name { $($field;)* });
+        register!(@default $name { $($field;)* });
+
     };
 
-    // Shortcut for fields defined as `bool` without the `=>` syntax.
     (
-        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as bool $(, $comment:literal)?;
-    ) => {
-        register!(@field_accessor $name $hi:$lo $field as bool => bool $(, $comment)?;);
-    };
-
-    // Catches the `?=>` syntax for non-boolean fields.
-    (
-        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt ?=> $try_into_type:ty
-            $(, $comment:literal)?;
-    ) => {
-        register!(@leaf_accessor $name $hi:$lo $field
-            { |f| <$try_into_type>::try_from(f as $type) } $type $try_into_type =>
-            ::core::result::Result<
-                $try_into_type,
-                <$try_into_type as ::core::convert::TryFrom<$type>>::Error
-            >
-            $(, $comment)?;);
-    };
-
-    // Catches the `=>` syntax for non-boolean fields.
-    (
-        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt => $into_type:ty
-            $(, $comment:literal)?;
-    ) => {
-        register!(@leaf_accessor $name $hi:$lo $field
-            { |f| <$into_type>::from(f as $type) } $type $into_type => $into_type $(, $comment)?;);
-    };
-
-    // Shortcut for non-boolean fields defined without the `=>` or `?=>` syntax.
-    (
-        @field_accessor $name:ident $hi:tt:$lo:tt $field:ident as $type:tt
-            $(, $comment:literal)?;
-    ) => {
-        register!(@field_accessor $name $hi:$lo $field as $type => $type $(, $comment)?;);
-    };
-
-    // Generates the accessor methods for a single field.
-    (
-        @leaf_accessor $name:ident $hi:tt:$lo:tt $field:ident
-            { $process:expr } $prim_type:tt $to_type:ty => $res_type:ty $(, $comment:literal)?;
+        @private_field_accessors $name:ident $hi:tt:$lo:tt $field:ident
     ) => {
         ::kernel::macros::paste!(
         const [<$field:upper _RANGE>]: ::core::ops::RangeInclusive<u8> = $lo..=$hi;
@@ -535,38 +434,135 @@ impl $name {
         const [<$field:upper _SHIFT>]: u32 = Self::[<$field:upper _MASK>].trailing_zeros();
         );
 
-        $(
-        #[doc="Returns the value of this field:"]
-        #[doc=$comment]
-        )?
-        #[inline(always)]
-        pub(crate) fn $field(self) -> $res_type {
-            ::kernel::macros::paste!(
+        ::kernel::macros::paste!(
+        fn [<$field _internal>](self) ->
+            ::kernel::num::BoundedInt<u32, { $hi + 1 - $lo }> {
             const MASK: u32 = $name::[<$field:upper _MASK>];
             const SHIFT: u32 = $name::[<$field:upper _SHIFT>];
+            // Ensure the returned type has the same width as the field.
+            ::kernel::static_assert!(
+                MASK >> SHIFT == <u32 as ::kernel::num::Boundable<{ $hi + 1 - $lo }>>::MASK
             );
+
             let field = ((self.0 & MASK) >> SHIFT);
 
-            $process(field)
+            ::kernel::num::BoundedInt::<u32, { $hi + 1 - $lo }>::new(field)
         }
 
-        ::kernel::macros::paste!(
-        $(
-        #[doc="Sets the value of this field:"]
-        #[doc=$comment]
-        )?
-        #[inline(always)]
-        pub(crate) fn [<set_ $field>](mut self, value: $to_type) -> Self {
+        fn [<set_ $field _internal>](
+            mut self,
+            value: ::kernel::num::BoundedInt<u32, { $hi + 1 - $lo }>,
+        ) -> Self
+        {
             const MASK: u32 = $name::[<$field:upper _MASK>];
             const SHIFT: u32 = $name::[<$field:upper _SHIFT>];
-            let value = (u32::from($prim_type::from(value)) << SHIFT) & MASK;
+            // Ensure the returned type has the same width as the field.
+            ::kernel::static_assert!(
+                MASK >> SHIFT == <u32 as ::kernel::num::Boundable<{ $hi + 1 - $lo }>>::MASK
+            );
+
+            let value = (value.get() << SHIFT) & MASK;
             self.0 = (self.0 & !MASK) | value;
 
             self
         }
+
+        fn [<try_set_ $field _internal>]<T>(
+            mut self,
+            value: T,
+        ) -> ::kernel::error::Result<Self>
+            where T: ::kernel::num::TryIntoBounded<u32, { $hi + 1 - $lo }>,
+        {
+            const MASK: u32 = $name::[<$field:upper _MASK>];
+            const SHIFT: u32 = $name::[<$field:upper _SHIFT>];
+            // Ensure the returned type has the same width as the field.
+            ::kernel::static_assert!(
+                MASK >> SHIFT == <u32 as ::kernel::num::Boundable<{ $hi + 1 - $lo }>>::MASK
+            );
+            let value = value.try_into()?;
+
+            let value = (value.get() << SHIFT) & MASK;
+            self.0 = (self.0 & !MASK) | value;
+
+            Ok(self)
+        }
         );
     };
 
+    // Generates the public accessors for fields infallibly (`=>`) converted to a type.
+    (
+        @public_field_accessors $name:ident $hi:tt:$lo:tt $field:ident => $into_type:ty
+            $(, $comment:literal)?
+    ) => {
+        ::kernel::macros::paste!(
+        pub(crate) fn $field(self) -> $into_type
+        {
+            self.[<$field _internal>]().into()
+        }
+
+        pub(crate) fn [<set_ $field>](self, value: $into_type) -> Self
+        {
+            self.[<set_ $field _internal>](value.into())
+        }
+        );
+    };
+
+    // Generates the public accessors for fields fallibly (`?=>`) converted to a type.
+    (
+        @public_field_accessors $name:ident $hi:tt:$lo:tt $field:ident ?=> $try_into_type:ty
+            $(, $comment:literal)?
+    ) => {
+        ::kernel::macros::paste!(
+        pub(crate) fn $field(self) ->
+            Result<
+                $try_into_type,
+                <$try_into_type as ::core::convert::TryFrom<
+                    ::kernel::num::BoundedInt<u32, { $hi + 1 - $lo }>
+                >>::Error
+            >
+        {
+            self.[<$field _internal>]().try_into()
+        }
+
+        pub(crate) fn [<set_ $field>](self, value: $try_into_type) -> Self
+        {
+            self.[<set_ $field _internal>](value.into())
+        }
+        );
+    };
+
+    // Generates the public accessors for fields not converted to a type.
+    (
+        @public_field_accessors $name:ident $hi:tt:$lo:tt $field:ident $(, $comment:literal)?
+    ) => {
+        ::kernel::macros::paste!(
+        pub(crate) fn $field(self) ->
+            ::kernel::num::BoundedInt<u32, { $hi + 1 - $lo }>
+        {
+            self.[<$field _internal>]()
+        }
+
+        pub(crate) fn [<set_ $field>]<T>(
+            self,
+            value: T,
+        ) -> Self
+            where T: Into<::kernel::num::BoundedInt<u32, { $hi + 1 - $lo }>>,
+        {
+            self.[<set_ $field _internal>](value.into())
+        }
+
+        pub(crate) fn [<try_set_ $field>]<T>(
+            self,
+            value: T,
+        ) -> ::kernel::error::Result<Self>
+            where T: ::kernel::num::TryIntoBounded<u32, { $hi + 1 - $lo }>,
+        {
+            Ok(self.[<set_ $field _internal>](value.try_into()?))
+        }
+        );
+    };
+
+
     // Generates the `Debug` implementation for `$name`.
     (@debug $name:ident { $($field:ident;)* }) => {
         impl ::core::fmt::Debug for $name {
@@ -582,6 +578,8 @@ fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
     };
 
     // Generates the `Default` implementation for `$name`.
+    // TODO: hack - we should not use the internal method. Maybe add private methods returning the
+    // defaults value for each field?
     (@default $name:ident { $($field:ident;)* }) => {
         /// Returns a value for the register where all fields are set to their default value.
         impl ::core::default::Default for $name {
@@ -591,7 +589,7 @@ fn default() -> Self {
 
                 ::kernel::macros::paste!(
                 $(
-                value.[<set_ $field>](Default::default());
+                value.[<set_ $field _internal>](Default::default());
                 )*
                 );
 

-- 
2.51.0
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Yury Norov 4 months ago
Hi Alexandre,

On Thu, Oct 09, 2025 at 09:37:10PM +0900, Alexandre Courbot wrote:
> Use BoundedInt with the register!() macro and adapt the nova-core code
> accordingly. This makes it impossible to trim values when setting a
> register field, because either the value of the field has been inferred
> at compile-time to fit within the bounds of the field, or the user has
> been forced to check at runtime that it does indeed fit.

In C23 we've got _BitInt(), which works like:

        unsigned _BitInt(2) a = 5; // compile-time error

Can you consider a similar name and syntax in rust?

> The use of BoundedInt actually simplifies register fields definitions,
> as they don't need an intermediate storage type (the "as ..." part of
> fields definitions). Instead, the internal storage type for each field
> is now the bounded integer of its width in bits, which can optionally be
> converted to another type that implements `From`` or `TryFrom`` for that
> bounded integer type.
> 
> This means that something like
> 
>   register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
>       3:3     status_valid as bool,
>       31:8    addr as u32,
>   });
> 
> Now becomes
> 
>   register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
>       3:3     status_valid => bool,
>       31:8    addr,
>   });

That looks nicer, really. But now that you don't make user to provide
a representation type, how would one distinguish signed and unsigned
fields? Assuming that BoundedInt is intended to become a generic type,
people may want to use it as a storage for counters and other
non-bitfield type of things. Maybe:

   register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
       s 3:0     cnt,
         7:4     flags, // implies unsigned - ?
       u 31:8    addr,
   });
 
> (here `status_valid` is infallibly converted to a bool for convenience
> and to remain compatible with the previous semantics)
> 
> The field setter/getters are also simplified. If a field has no target
> type, then its setter expects any type that implements `Into` to the
> field's bounded integer type. Due to the many `From` implementations for
> primitive types, this means that most calls can be left unchanged. If
> the caller passes a value that is potentially larger than the field's
> capacity, it must use the `try_` variant of the setter, which returns an
> error if the value cannot be converted at runtime.
> 
> For fields that use `=>` to convert to another type, both setter and
> getter are always infallible.
> 
> For fields that use `?=>` to fallibly convert to another type, only the
> getter needs to be fallible as the setter always provide valid values by
> design.

Can you share a couple examples? Not sure I understand this part,
especially how setters may not be fallible, and getters may fail.
 
> Outside of the register macro, the biggest changes occur in `falcon.rs`,
> which defines many enums for fields - their conversion implementations
> need to be changed from the original primitive type of the field to the
> new corresponding bounded int type. Hopefully the TryFrom/Into derive
> macros [1] can take care of implementing these, but it will need to be
> adapted to support bounded integers... :/
> 
> But overall, I am rather happy at how simple it was to convert the whole
> of nova-core to this.
> 
> Note: This RFC uses nova-core's register!() macro for practical
> purposes, but the hope is to move this patch on top of the bitfield
> macro after it is split out [2].
> 
> [1] https://lore.kernel.org/rust-for-linux/cover.1755235180.git.y.j3ms.n@gmail.com/
> [2] https://lore.kernel.org/rust-for-linux/20251003154748.1687160-1-joelagnelf@nvidia.com/
> 
> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> ---

...

>          regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
> -            .set_base((dma_start >> 40) as u16)
> +            .try_set_base(dma_start >> 40)?
>              .write(bar, &E::ID);

Does it mean that something like the following syntax is possible?

        regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
            .try_set_base1(base1 >> 40)?        // fail here
            .try_set_base2(base2 >> 40)?        // skip
            .write(bar, &E::ID) else { pr_err!(); return -EINVAL };

This is my main concern: Rust is advertised a as runtime-safe language
(at lease safer than C), but current design isn't safe against one of
the most common errors: type overflow.

If your syntax above allows to handle errors in .try_set() path this way
or another, I think the rest is manageable. 

As a side note: it's a huge pain in C to grep for functions that
defined by using a macro. Here you do a similar thing. One can't
easily grep the 'try_set_base' implementation, and would have to
make a not so pleasant detour to the low-level internals. Maybe
switch it to:
        
        regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
            .try_set(base, dma_start >> 40)?
            .write(bar, &E::ID);

Thanks,
Yury
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Alexandre Courbot 4 months ago
On Fri Oct 10, 2025 at 1:40 AM JST, Yury Norov wrote:
> Hi Alexandre,
>
> On Thu, Oct 09, 2025 at 09:37:10PM +0900, Alexandre Courbot wrote:
>> Use BoundedInt with the register!() macro and adapt the nova-core code
>> accordingly. This makes it impossible to trim values when setting a
>> register field, because either the value of the field has been inferred
>> at compile-time to fit within the bounds of the field, or the user has
>> been forced to check at runtime that it does indeed fit.
>
> In C23 we've got _BitInt(), which works like:
>
>         unsigned _BitInt(2) a = 5; // compile-time error
>
> Can you consider a similar name and syntax in rust?

I like the shorter `BitInt`! For the syntax, we will have to conform to
what is idiomatic Rust. And I don't think we can make something similar
to `= 5` work here - that would require overloading the `=` operator,
which cannot be done AFAICT. A constructor is a requirement.

>
>> The use of BoundedInt actually simplifies register fields definitions,
>> as they don't need an intermediate storage type (the "as ..." part of
>> fields definitions). Instead, the internal storage type for each field
>> is now the bounded integer of its width in bits, which can optionally be
>> converted to another type that implements `From`` or `TryFrom`` for that
>> bounded integer type.
>> 
>> This means that something like
>> 
>>   register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
>>       3:3     status_valid as bool,
>>       31:8    addr as u32,
>>   });
>> 
>> Now becomes
>> 
>>   register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
>>       3:3     status_valid => bool,
>>       31:8    addr,
>>   });
>
> That looks nicer, really. But now that you don't make user to provide
> a representation type, how would one distinguish signed and unsigned
> fields? Assuming that BoundedInt is intended to become a generic type,
> people may want to use it as a storage for counters and other
> non-bitfield type of things. Maybe:
>
>    register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
>        s 3:0     cnt,
>          7:4     flags, // implies unsigned - ?
>        u 31:8    addr,
>    });

The expectation would be to use the `=>` syntax to convert the field to
a signed type (similarly to how `status_valid` is turned into a `bool`
in my example).

>  
>> (here `status_valid` is infallibly converted to a bool for convenience
>> and to remain compatible with the previous semantics)
>> 
>> The field setter/getters are also simplified. If a field has no target
>> type, then its setter expects any type that implements `Into` to the
>> field's bounded integer type. Due to the many `From` implementations for
>> primitive types, this means that most calls can be left unchanged. If
>> the caller passes a value that is potentially larger than the field's
>> capacity, it must use the `try_` variant of the setter, which returns an
>> error if the value cannot be converted at runtime.
>> 
>> For fields that use `=>` to convert to another type, both setter and
>> getter are always infallible.
>> 
>> For fields that use `?=>` to fallibly convert to another type, only the
>> getter needs to be fallible as the setter always provide valid values by
>> design.
>
> Can you share a couple examples? Not sure I understand this part,
> especially how setters may not be fallible, and getters may fail.

Imagine you have this enum:

  enum GpioState {
    Low = 0,
    High = 1,
    Floating = 2,
  }

and this field:

  2:0 gpio_state ?=> GpioState,

When you set it, you must pass an instance of `GpioState` as argument,
which means that the value will always be valid. However, when you try
to access the field, you have no guarantee at all that the value of the
field won't be `3` - the IO space might be inaccessible, or the register
value be forged arbitrarily. Thus the getter needs to return a
`Result<GpioState>`.

>  
>> Outside of the register macro, the biggest changes occur in `falcon.rs`,
>> which defines many enums for fields - their conversion implementations
>> need to be changed from the original primitive type of the field to the
>> new corresponding bounded int type. Hopefully the TryFrom/Into derive
>> macros [1] can take care of implementing these, but it will need to be
>> adapted to support bounded integers... :/
>> 
>> But overall, I am rather happy at how simple it was to convert the whole
>> of nova-core to this.
>> 
>> Note: This RFC uses nova-core's register!() macro for practical
>> purposes, but the hope is to move this patch on top of the bitfield
>> macro after it is split out [2].
>> 
>> [1] https://lore.kernel.org/rust-for-linux/cover.1755235180.git.y.j3ms.n@gmail.com/
>> [2] https://lore.kernel.org/rust-for-linux/20251003154748.1687160-1-joelagnelf@nvidia.com/
>> 
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
>
> ...
>
>>          regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>> -            .set_base((dma_start >> 40) as u16)
>> +            .try_set_base(dma_start >> 40)?
>>              .write(bar, &E::ID);
>
> Does it mean that something like the following syntax is possible?
>
>         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>             .try_set_base1(base1 >> 40)?        // fail here
>             .try_set_base2(base2 >> 40)?        // skip
>             .write(bar, &E::ID) else { pr_err!(); return -EINVAL };
>
> This is my main concern: Rust is advertised a as runtime-safe language
> (at lease safer than C), but current design isn't safe against one of
> the most common errors: type overflow.

Not sure I understand what you mean, but if you are talking about fields
overflow, this cannot happen with the current design. The non-fallible
setter can only be invoked if the compiler can prove that the argument
does fit withing the field. Otherwise, one has to use the fallible
setter (as this chunk does, because `dma_start >> 40` can still spill
over the capacity of `base`), which performs a runtime check and returns
`EOVERFLOW` if the value didn't fit.

>
> If your syntax above allows to handle errors in .try_set() path this way
> or another, I think the rest is manageable. 
>
> As a side note: it's a huge pain in C to grep for functions that
> defined by using a macro. Here you do a similar thing. One can't
> easily grep the 'try_set_base' implementation, and would have to
> make a not so pleasant detour to the low-level internals. Maybe
> switch it to:
>         
>         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>             .try_set(base, dma_start >> 40)?
>             .write(bar, &E::ID);

`base` here is passed by value, what type would it be? I don't think it
is easily doable without jumping through many hoops.

Using LSP with Rust actually makes it very easy to jump to either the
definition of the register, or of the `try_set` block in the macro - 
I've done this many times. LSP is pretty much a requirement to code
efficiently in Rust, so I think it is reasonable to rely on it here.
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Yury Norov 3 months, 4 weeks ago
On Fri, Oct 10, 2025 at 06:19:17PM +0900, Alexandre Courbot wrote:
> On Fri Oct 10, 2025 at 1:40 AM JST, Yury Norov wrote:
> > Hi Alexandre,
> >
> > On Thu, Oct 09, 2025 at 09:37:10PM +0900, Alexandre Courbot wrote:
> >> Use BoundedInt with the register!() macro and adapt the nova-core code
> >> accordingly. This makes it impossible to trim values when setting a
> >> register field, because either the value of the field has been inferred
> >> at compile-time to fit within the bounds of the field, or the user has
> >> been forced to check at runtime that it does indeed fit.
> >
> > In C23 we've got _BitInt(), which works like:
> >
> >         unsigned _BitInt(2) a = 5; // compile-time error
> >
> > Can you consider a similar name and syntax in rust?
> 
> I like the shorter `BitInt`! For the syntax, we will have to conform to
> what is idiomatic Rust. And I don't think we can make something similar
> to `= 5` work here - that would require overloading the `=` operator,
> which cannot be done AFAICT. A constructor is a requirement.

Sure, BitInt + constructor is nice.

> >> The use of BoundedInt actually simplifies register fields definitions,
> >> as they don't need an intermediate storage type (the "as ..." part of
> >> fields definitions). Instead, the internal storage type for each field
> >> is now the bounded integer of its width in bits, which can optionally be
> >> converted to another type that implements `From`` or `TryFrom`` for that
> >> bounded integer type.
> >> 
> >> This means that something like
> >> 
> >>   register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
> >>       3:3     status_valid as bool,
> >>       31:8    addr as u32,
> >>   });
> >> 
> >> Now becomes
> >> 
> >>   register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
> >>       3:3     status_valid => bool,
> >>       31:8    addr,
> >>   });
> >
> > That looks nicer, really. But now that you don't make user to provide
> > a representation type, how would one distinguish signed and unsigned
> > fields? Assuming that BoundedInt is intended to become a generic type,
> > people may want to use it as a storage for counters and other
> > non-bitfield type of things. Maybe:
> >
> >    register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
> >        s 3:0     cnt,
> >          7:4     flags, // implies unsigned - ?
> >        u 31:8    addr,
> >    });

> The expectation would be to use the `=>` syntax to convert the field to
> a signed type (similarly to how `status_valid` is turned into a `bool`
> in my example).
 
So, you suggest like this?

    register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
        3:0     cnt => i8,
        7:4     flags, // implied unsigned
        31:8    addr,  // implied unsigned
    });

That answers my question. Can you please highlight this use case
in commit message?

And just to wrap up:

 - all fields by default are unsigned integers;
 - one may use '=>' to switch to signed integers, enums or booleans;
 - all other types are not allowed.

Is that correct?
 
> >> (here `status_valid` is infallibly converted to a bool for convenience
> >> and to remain compatible with the previous semantics)
> >> 
> >> The field setter/getters are also simplified. If a field has no target
> >> type, then its setter expects any type that implements `Into` to the
> >> field's bounded integer type. Due to the many `From` implementations for
> >> primitive types, this means that most calls can be left unchanged. If
> >> the caller passes a value that is potentially larger than the field's
> >> capacity, it must use the `try_` variant of the setter, which returns an
> >> error if the value cannot be converted at runtime.
> >> 
> >> For fields that use `=>` to convert to another type, both setter and
> >> getter are always infallible.
> >> 
> >> For fields that use `?=>` to fallibly convert to another type, only the
> >> getter needs to be fallible as the setter always provide valid values by
> >> design.
> >
> > Can you share a couple examples? Not sure I understand this part,
> > especially how setters may not be fallible, and getters may fail.
> 
> Imagine you have this enum:
> 
>   enum GpioState {
>     Low = 0,
>     High = 1,
>     Floating = 2,
>   }
> 
> and this field:
> 
>   2:0 gpio_state ?=> GpioState,
> 
> When you set it, you must pass an instance of `GpioState` as argument,
> which means that the value will always be valid. However, when you try
> to access the field, you have no guarantee at all that the value of the
> field won't be `3` - the IO space might be inaccessible, or the register
> value be forged arbitrarily. Thus the getter needs to return a
> `Result<GpioState>`.

Ack, thanks.
  
> >> Outside of the register macro, the biggest changes occur in `falcon.rs`,
> >> which defines many enums for fields - their conversion implementations
> >> need to be changed from the original primitive type of the field to the
> >> new corresponding bounded int type. Hopefully the TryFrom/Into derive
> >> macros [1] can take care of implementing these, but it will need to be
> >> adapted to support bounded integers... :/
> >> 
> >> But overall, I am rather happy at how simple it was to convert the whole
> >> of nova-core to this.
> >> 
> >> Note: This RFC uses nova-core's register!() macro for practical
> >> purposes, but the hope is to move this patch on top of the bitfield
> >> macro after it is split out [2].
> >> 
> >> [1] https://lore.kernel.org/rust-for-linux/cover.1755235180.git.y.j3ms.n@gmail.com/
> >> [2] https://lore.kernel.org/rust-for-linux/20251003154748.1687160-1-joelagnelf@nvidia.com/
> >> 
> >> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
> >> ---
> >
> > ...
> >
> >>          regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
> >> -            .set_base((dma_start >> 40) as u16)
> >> +            .try_set_base(dma_start >> 40)?
> >>              .write(bar, &E::ID);
> >
> > Does it mean that something like the following syntax is possible?
> >
> >         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
> >             .try_set_base1(base1 >> 40)?        // fail here
> >             .try_set_base2(base2 >> 40)?        // skip
> >             .write(bar, &E::ID) else { pr_err!(); return -EINVAL };
> >
> > This is my main concern: Rust is advertised a as runtime-safe language
> > (at lease safer than C), but current design isn't safe against one of
> > the most common errors: type overflow.
> 
> Not sure I understand what you mean, but if you are talking about fields
> overflow, this cannot happen with the current design. The non-fallible
> setter can only be invoked if the compiler can prove that the argument
> does fit withing the field. Otherwise, one has to use the fallible
> setter (as this chunk does, because `dma_start >> 40` can still spill
> over the capacity of `base`), which performs a runtime check and returns
> `EOVERFLOW` if the value didn't fit.
 
Yeah, this design addresses my major question to the bitfields series
from Joel: setters must be fallible. I played with this approach, and
it does exactly what I have in mind.

I still have a question regarding compile-time flavor of the setter.
In C we've got a builtin_constant_p, and use it like:
        
   static inline int set_base(unsigned int base)
   {
        BUILD_BUG_ON_ZERO(const_true(base > MAX_BASE));

        // Eliminated for compile-time 'base'
        if (base > MAX_BASE)
                return -EOVERFLOW;

        __set_base(base);

        return 0;
   }

Can we do the same trick in rust? Would be nice to have a single
setter for both compile and runtime cases.

> > If your syntax above allows to handle errors in .try_set() path this way
> > or another, I think the rest is manageable. 
> >
> > As a side note: it's a huge pain in C to grep for functions that
> > defined by using a macro. Here you do a similar thing. One can't
> > easily grep the 'try_set_base' implementation, and would have to
> > make a not so pleasant detour to the low-level internals. Maybe
> > switch it to:
> >         
> >         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
> >             .try_set(base, dma_start >> 40)?
> >             .write(bar, &E::ID);
> 
> `base` here is passed by value, what type would it be? I don't think it
> is easily doable without jumping through many hoops.
> 
> Using LSP with Rust actually makes it very easy to jump to either the
> definition of the register, or of the `try_set` block in the macro - 
> I've done this many times. LSP is pretty much a requirement to code
> efficiently in Rust, so I think it is reasonable to rely on it here.

OK, then this one is also addressed. If LSP is a requirement, maybe
it's worth to mention it in Documentation?
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Joel Fernandes 3 months, 3 weeks ago
Hi Yury,

On 10/10/2025 1:20 PM, Yury Norov wrote:
[...]
>>>>          regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>>>> -            .set_base((dma_start >> 40) as u16)
>>>> +            .try_set_base(dma_start >> 40)?
>>>>              .write(bar, &E::ID);
>>>
>>> Does it mean that something like the following syntax is possible?
>>>
>>>         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>>>             .try_set_base1(base1 >> 40)?        // fail here
>>>             .try_set_base2(base2 >> 40)?        // skip
>>>             .write(bar, &E::ID) else { pr_err!(); return -EINVAL };
>>>
>>> This is my main concern: Rust is advertised a as runtime-safe language
>>> (at lease safer than C), but current design isn't safe against one of
>>> the most common errors: type overflow.
>>
>> Not sure I understand what you mean, but if you are talking about fields
>> overflow, this cannot happen with the current design. The non-fallible
>> setter can only be invoked if the compiler can prove that the argument
>> does fit withing the field. Otherwise, one has to use the fallible
>> setter (as this chunk does, because `dma_start >> 40` can still spill
>> over the capacity of `base`), which performs a runtime check and returns
>> `EOVERFLOW` if the value didn't fit.
>  
> Yeah, this design addresses my major question to the bitfields series
> from Joel: setters must be fallible. I played with this approach, and
> it does exactly what I have in mind.
> 
> I still have a question regarding compile-time flavor of the setter.
> In C we've got a builtin_constant_p, and use it like:
>         
>    static inline int set_base(unsigned int base)
>    {
>         BUILD_BUG_ON_ZERO(const_true(base > MAX_BASE));
> 
>         // Eliminated for compile-time 'base'
>         if (base > MAX_BASE)
>                 return -EOVERFLOW;
> 
>         __set_base(base);
> 
>         return 0;
>    }
> 
> Can we do the same trick in rust? Would be nice to have a single
> setter for both compile and runtime cases.

I don't think we could combine the setter and try setter variants on the rust
side, because the former returns Self and the latter returns Result. Also, both
the variants already have compile time asserts which may cover what you're
referring to.

The try setter variants in fact are not strictly needed, because the user can
provide a bounded integer (after performing any fallible conversions on the
caller side). Alex and me discussed adding that for a better user/caller
experience [1].

[1] https://lore.kernel.org/all/C35B5306-98C6-447B-A239-9D6A6C548A4F@nvidia.com/

Or did you mean something else?

thanks,

 - Joel
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Danilo Krummrich 3 months, 4 weeks ago
On Fri Oct 10, 2025 at 7:20 PM CEST, Yury Norov wrote:
> On Fri, Oct 10, 2025 at 06:19:17PM +0900, Alexandre Courbot wrote:
>> On Fri Oct 10, 2025 at 1:40 AM JST, Yury Norov wrote:
>     register!(NV_PDISP_VGA_WORKSPACE_BASE @ 0x00625f04 {
>         3:0     cnt => i8,
>         7:4     flags, // implied unsigned
>         31:8    addr,  // implied unsigned
>     });
>
> That answers my question. Can you please highlight this use case
> in commit message?
>
> And just to wrap up:
>
>  - all fields by default are unsigned integers;
>  - one may use '=>' to switch to signed integers, enums or booleans;
>  - all other types are not allowed.

We do allow other types.

In Rust we have the From [1] and TryFrom [2] traits (analogously Into and
TryInto).

This way, if some type T implements, for instance, From<u8> it means that we
can always derive an instance of T from any u8.

Similarly, if T implements TryFrom<u8> we can always derive a Result<T>, which
either contains a valid instance of T or some error that we can handle instead.

Hence, the following is valid is well:

	register!(NV_PFALCON_FALCON_HWCFG1 @ PFalconBase[0x0000012c] {
	    3:0     core_rev ?=> FalconCoreRev, "Core revision";
	    5:4     security_model ?=> FalconSecurityModel, "Security model";
	    7:6     core_rev_subversion as ?=> FalconCoreRevSubversion, "Core revision subversion";
	});

The '?=>' notation means TryFrom, hence the generated accessor method - e.g.
security_model() - returns a Result<FalconCoreRevSubversion>.

In this exaple all three types are actually enums, but it would be the exact
same for structs.

In fact, enums in Rust are very different than enums in C anyways and can be
complex types, such as:

	enum Message {
	    Quit,
	    Move { x: i32, y: i32 },
	    Write(String),
	    ChangeColor(i32, i32, i32),
	}

[1] https://rust.docs.kernel.org/core/convert/trait.From.html
[2] https://rust.docs.kernel.org/core/convert/trait.TryFrom.html

> I still have a question regarding compile-time flavor of the setter.
> In C we've got a builtin_constant_p, and use it like:
>         
>    static inline int set_base(unsigned int base)
>    {
>         BUILD_BUG_ON_ZERO(const_true(base > MAX_BASE));
>
>         // Eliminated for compile-time 'base'
>         if (base > MAX_BASE)
>                 return -EOVERFLOW;
>
>         __set_base(base);
>
>         return 0;
>    }
>
> Can we do the same trick in rust? Would be nice to have a single
> setter for both compile and runtime cases.

Technically, we could, but it would be very inefficient: Even if the function /
method is called in an infallible way it would still return a Result<T> instead
of just T, which the caller would need to handle.

Analogously, for a setter the function / method would still return a Result,
which must be handled.

Ignoring a returned Result causes a compiler warning:

	warning: unused `core::result::Result` that must be used
	  --> drivers/gpu/nova-core/driver.rs:64:9
	   |
	64 |         foo();
	   |         ^^^^^
	   |
	   = note: this `Result` may be an `Err` variant, which should be handled
	   = note: `#[warn(unused_must_use)]` on by default
	help: use `let _ = ...` to ignore the resulting value
	   |
	64 |         let _ = foo();
	   |         +++++++
	
	warning: 1 warning emitted

This is intentional, users should not be able to ignore error conditions
willy-nilly:

It is a potential source for bugs if errors are ignored. For instance, a caller
might not check the returned Result initially because a compile time check is
expected. However, when changed later on to derive the value at runtime it is
easy to forget handling the Result instead.
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Joel Fernandes 4 months ago

On 10/9/2025 12:40 PM, Yury Norov wrote:
[..]
>> The field setter/getters are also simplified. If a field has no target
>> type, then its setter expects any type that implements `Into` to the
>> field's bounded integer type. Due to the many `From` implementations for
>> primitive types, this means that most calls can be left unchanged. If
>> the caller passes a value that is potentially larger than the field's
>> capacity, it must use the `try_` variant of the setter, which returns an
>> error if the value cannot be converted at runtime.
>>
>> For fields that use `=>` to convert to another type, both setter and
>> getter are always infallible.
>>
>> For fields that use `?=>` to fallibly convert to another type, only the
>> getter needs to be fallible as the setter always provide valid values by
>> design.
> 
> Can you share a couple examples? Not sure I understand this part,
> especially how setters may not be fallible, and getters may fail.

Because a getter has to convert the raw bitfield value from memory into an enum,
there is no guarantee that the memory in concern does not overflow the enum, say
if register bit meanings change, or something. ?=> was before fallible in both
directions hence the "?". Now with Alex's patch it is fallible only in one
direction.

>> Outside of the register macro, the biggest changes occur in `falcon.rs`,
>> which defines many enums for fields - their conversion implementations
>> need to be changed from the original primitive type of the field to the
>> new corresponding bounded int type. Hopefully the TryFrom/Into derive
>> macros [1] can take care of implementing these, but it will need to be
>> adapted to support bounded integers... :/
>>
>> But overall, I am rather happy at how simple it was to convert the whole
>> of nova-core to this.
>>
>> Note: This RFC uses nova-core's register!() macro for practical
>> purposes, but the hope is to move this patch on top of the bitfield
>> macro after it is split out [2].
>>
>> [1] https://lore.kernel.org/rust-for-linux/cover.1755235180.git.y.j3ms.n@gmail.com/
>> [2] https://lore.kernel.org/rust-for-linux/20251003154748.1687160-1-joelagnelf@nvidia.com/
>>
>> Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
>> ---
> 
> ...
> 
>>          regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>> -            .set_base((dma_start >> 40) as u16)
>> +            .try_set_base(dma_start >> 40)?
>>              .write(bar, &E::ID);
> 
> Does it mean that something like the following syntax is possible?
> 
>         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>             .try_set_base1(base1 >> 40)?        // fail here

Danilo already replied here, but this code is fine as we early return due to the
try operator (?).

>             .try_set_base2(base2 >> 40)?        // skip
>             .write(bar, &E::ID) else { pr_err!(); return -EINVAL };

Here I am guessing (based on your concern from an earlier thread) but is your
concern was that -EINVAL will get written to the register accidentally? That
wont happen, the above code is fine. Or do you mean something else?

> 
> This is my main concern: Rust is advertised a as runtime-safe language
> (at lease safer than C), but current design isn't safe against one of
> the most common errors: type overflow.

I'd be a bit careful when using "isn't safe" in the context of Rust. Rust's
notion of "safety" is about preventing undefined behavior (UB), not preventing
every possible runtime mistake. In rust, integer overflows for example are not
undefined so overflows are not "unsafe" (it might be a logical bug but that's
not what unsafety is about). In fact an overflow might be exactly what some
algorithm needs (wrap counters in RCU are example of harmless overflow). Another
example is a deadlock is not undefined behavior in Rust, it is defined, even if
bad :).

Thanks.
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Danilo Krummrich 4 months ago
On Thu Oct 9, 2025 at 6:40 PM CEST, Yury Norov wrote:
> On Thu, Oct 09, 2025 at 09:37:10PM +0900, Alexandre Courbot wrote:
>> Use BoundedInt with the register!() macro and adapt the nova-core code
>> accordingly. This makes it impossible to trim values when setting a
>> register field, because either the value of the field has been inferred
>> at compile-time to fit within the bounds of the field, or the user has
>> been forced to check at runtime that it does indeed fit.
>
> In C23 we've got _BitInt(), which works like:
>
>         unsigned _BitInt(2) a = 5; // compile-time error
>
> Can you consider a similar name and syntax in rust?

Rust is a different language and has its own syntax, I think we should not try
to use C syntax instead.

>>          regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>> -            .set_base((dma_start >> 40) as u16)
>> +            .try_set_base(dma_start >> 40)?
>>              .write(bar, &E::ID);
>
> Does it mean that something like the following syntax is possible?
>
>         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>             .try_set_base1(base1 >> 40)?        // fail here

Note that try_set_base1() returns a Result [1], which is handled immediately by
the question mark operator [2]. I.e. if try_set_base1() returns an error it is
propagated to the caller right away without executing any of the code below.

>             .try_set_base2(base2 >> 40)?        // skip
>             .write(bar, &E::ID) else { pr_err!(); return -EINVAL };
>
> This is my main concern: Rust is advertised a as runtime-safe language
> (at lease safer than C), but current design isn't safe against one of
> the most common errors: type overflow.

Where do you see a potential runtime overflows in the register!() code?

[1] https://rust.docs.kernel.org/kernel/error/type.Result.html
[2] https://doc.rust-lang.org/reference/expressions/operator-expr.html?highlight=question%20mark#the-question-mark-operator
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Yury Norov 4 months ago
On Thu, Oct 09, 2025 at 07:18:33PM +0200, Danilo Krummrich wrote:
> On Thu Oct 9, 2025 at 6:40 PM CEST, Yury Norov wrote:
> > On Thu, Oct 09, 2025 at 09:37:10PM +0900, Alexandre Courbot wrote:
> >> Use BoundedInt with the register!() macro and adapt the nova-core code
> >> accordingly. This makes it impossible to trim values when setting a
> >> register field, because either the value of the field has been inferred
> >> at compile-time to fit within the bounds of the field, or the user has
> >> been forced to check at runtime that it does indeed fit.
> >
> > In C23 we've got _BitInt(), which works like:
> >
> >         unsigned _BitInt(2) a = 5; // compile-time error
> >
> > Can you consider a similar name and syntax in rust?
> 
> Rust is a different language and has its own syntax, I think we should not try
> to use C syntax instead.

Up to you guys. But having in mind that C is the only language that
really works for system engineering, I would rather consider to stay
in line with it on semantic level.

If your goal is to make rust adopted by system engineers, you may
want to make your language somewhat familiar to what people already
know.
 
> >>          regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
> >> -            .set_base((dma_start >> 40) as u16)
> >> +            .try_set_base(dma_start >> 40)?
> >>              .write(bar, &E::ID);
> >
> > Does it mean that something like the following syntax is possible?
> >
> >         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
> >             .try_set_base1(base1 >> 40)?        // fail here
> 
> Note that try_set_base1() returns a Result [1], which is handled immediately by
> the question mark operator [2]. I.e. if try_set_base1() returns an error it is
> propagated to the caller right away without executing any of the code below.

Thanks for the links. I am definitely the very beginning on the
learning curve for this.
 
> >             .try_set_base2(base2 >> 40)?        // skip
> >             .write(bar, &E::ID) else { pr_err!(); return -EINVAL };
> >
> > This is my main concern: Rust is advertised a as runtime-safe language
> > (at lease safer than C), but current design isn't safe against one of
> > the most common errors: type overflow.
> 
> Where do you see a potential runtime overflows in the register!() code?

Assuming base is 10-bit,
        
        let ret = some_c_wrapper()      // 0..1024 or -EINVAL
        regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
             .try_set_base1(ret)

Or maybe I misunderstood the question, because if there's no possibility
to overflow a field, what for the .try_set_xxx() is needed at all?

> [1] https://rust.docs.kernel.org/kernel/error/type.Result.html
> [2] https://doc.rust-lang.org/reference/expressions/operator-expr.html?highlight=question%20mark#the-question-mark-operator
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Danilo Krummrich 4 months ago
On Thu Oct 9, 2025 at 8:28 PM CEST, Yury Norov wrote:
> On Thu, Oct 09, 2025 at 07:18:33PM +0200, Danilo Krummrich wrote:
>> On Thu Oct 9, 2025 at 6:40 PM CEST, Yury Norov wrote:
>> > On Thu, Oct 09, 2025 at 09:37:10PM +0900, Alexandre Courbot wrote:
>> >> Use BoundedInt with the register!() macro and adapt the nova-core code
>> >> accordingly. This makes it impossible to trim values when setting a
>> >> register field, because either the value of the field has been inferred
>> >> at compile-time to fit within the bounds of the field, or the user has
>> >> been forced to check at runtime that it does indeed fit.
>> >
>> > In C23 we've got _BitInt(), which works like:
>> >
>> >         unsigned _BitInt(2) a = 5; // compile-time error
>> >
>> > Can you consider a similar name and syntax in rust?
>> 
>> Rust is a different language and has its own syntax, I think we should not try
>> to use C syntax instead.
>
> Up to you guys. But having in mind that C is the only language that
> really works for system engineering, I would rather consider to stay
> in line with it on semantic level.

I think you asked about syntax above; semantically it is (and should be) exactly
the same.

> If your goal is to make rust adopted by system engineers, you may
> want to make your language somewhat familiar to what people already
> know.

The goal is to add support for Rust in the Linux kernel; to adapt its advanced
type system features offering compile time checked lifetime and ownership
semantics leading to better memory safety and more compile time validation
overall.

In general I think we should stay as close to existing patterns as possible,
i.e. consistency does matter.

However, sometimes it is necessary to break with existing patterns and design
things slightly different to take full advantage of the capabilities we get from
the language (BoundedInt / BitInt is not one of those).

In other words, introducing a new language with capabilities that solve real
problems is pointless if we subsequently limit ourselfs to "what people already
know" for people who haven't been in touch with the language before.
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Miguel Ojeda 4 months ago
On Thu, Oct 9, 2025 at 8:28 PM Yury Norov <yury.norov@gmail.com> wrote:
>
> Up to you guys. But having in mind that C is the only language that
> really works for system engineering, I would rather consider to stay
> in line with it on semantic level.
>
> If your goal is to make rust adopted by system engineers, you may
> want to make your language somewhat familiar to what people already
> know.

I am not sure what you mean here, but just to clarify: our goal is to
improve Linux with Rust, not to increase its adoption (even if
obviously many of us would like that). And just in case, it is not
"our" language either -- most of us do not come from upstream Rust.

In general, we do try to make things as familiar as possible, but
sometimes it doesn't make much sense, so we don't follow C blindly.

For instance, C23 also got checked integer arithmetic macros, but that
doesn't mean we should start using names like `ckd_add`, returning
booleans and, worse, using macros for those. C23 also got `nullptr_t`
-- similar story.

Cheers,
Miguel
Re: [PATCH RFC v2 3/3] gpu: nova-core: use BoundedInt
Posted by Joel Fernandes 4 months ago
On 10/9/2025 2:28 PM, Yury Norov wrote:
[..]
>>>>          regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>>>> -            .set_base((dma_start >> 40) as u16)
>>>> +            .try_set_base(dma_start >> 40)?
>>>>              .write(bar, &E::ID);
>>>
>>> Does it mean that something like the following syntax is possible?
>>>
>>>         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>>>             .try_set_base1(base1 >> 40)?        // fail here
>>
>> Note that try_set_base1() returns a Result [1], which is handled immediately by
>> the question mark operator [2]. I.e. if try_set_base1() returns an error it is
>> propagated to the caller right away without executing any of the code below.
> 
> Thanks for the links. I am definitely the very beginning on the
> learning curve for this.
>  
>>>             .try_set_base2(base2 >> 40)?        // skip
>>>             .write(bar, &E::ID) else { pr_err!(); return -EINVAL };
>>>
>>> This is my main concern: Rust is advertised a as runtime-safe language
>>> (at lease safer than C), but current design isn't safe against one of
>>> the most common errors: type overflow.
>>
>> Where do you see a potential runtime overflows in the register!() code?
> 
> Assuming base is 10-bit,
>         
>         let ret = some_c_wrapper()      // 0..1024 or -EINVAL
>         regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
>              .try_set_base1(ret)
> 
> Or maybe I misunderstood the question, because if there's no possibility
> to overflow a field, what for the .try_set_xxx() is needed at all?

Because 'ret' is a value determined at runtime in this example, there is no way
for the compiler to know that ret will fit into the bounded int, at compile
time. So the "try_" means it is runtime checked and validated (via return of
Result). Sure it may well not fail, but the compiler doesn't know that.

Thanks.