* [PATCH 1/3] rust: num: add cv! macro to create values from constant expressions
2026-08-28 9:15 [PATCH 0/3] rust: introduce cv! macro for safe const conversions of integer-like types Eliot Courtney
@ 2026-08-28 9:15 ` Eliot Courtney
2026-08-28 9:15 ` [PATCH 2/3] rust: prelude: add `num::cv` Eliot Courtney
2026-08-28 9:15 ` [PATCH 3/3] gpu: nova-core: use cv! for constant casts Eliot Courtney
2 siblings, 0 replies; 4+ messages in thread
From: Eliot Courtney @ 2026-08-28 9:15 UTC (permalink / raw)
To: Alexandre Courbot, Yury Norov, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida,
Tamir Duberstein, Onur Özkan, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Timur Tabi, rust-for-linux,
linux-kernel, nova-gpu, dri-devel, Eliot Courtney
Currently, using NonZero/Bounded constants is quite verbose. It's
unfortunate because it disincentivizes using it in interface boundaries.
Introduce a macro to make it nicer to use. The macro `cv!` (for constant
value) takes a const integer expression and widens it to i128 (at build
time only) before passing it as a const generic value to a new trait
`FromConst`. The value is then converted and appears in the
associated constant `FromConst::VALUE`. The trait is implemented by
NonZero, Bounded, and Alignment and lets values of each be constructed
from constants without a verbose turbofish syntax.
For example, `const { NonZero::new(1).unwrap() }` can be written as
`cv!(1)`.
Suggested-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
rust/kernel/num.rs | 132 +++++++++++++++++++++++++++++++++++++++++++++
rust/kernel/num/bounded.rs | 20 ++++++-
rust/kernel/ptr.rs | 12 +++++
3 files changed, 163 insertions(+), 1 deletion(-)
diff --git a/rust/kernel/num.rs b/rust/kernel/num.rs
index dbe848e30efe..d9e4a089d128 100644
--- a/rust/kernel/num.rs
+++ b/rust/kernel/num.rs
@@ -2,6 +2,7 @@
//! Additional numerical features for the kernel.
+use crate::const_assert;
use core::ops;
pub mod bounded;
@@ -9,6 +10,137 @@
pub use bounded::*;
+/// Creates a value from an integer constant expression, with validity checked at build time.
+///
+/// This works for any type that implements [`FromConst`], with the target type inferred from
+/// the context, or named explicitly with `cv!(value => Type)`.
+///
+/// # Examples
+///
+/// ```
+/// use core::num::NonZero;
+/// use kernel::num::Bounded;
+/// use kernel::num::cv;
+/// use kernel::ptr::Alignment;
+///
+/// let v: NonZero<usize> = cv!(8);
+/// assert_eq!(v.get(), 8);
+///
+/// // Any integer constant expression works, not only literals.
+/// let m: NonZero<usize> = cv!(usize::MAX);
+/// assert_eq!(m.get(), usize::MAX);
+///
+/// let b: Bounded<u32, 4> = cv!(15);
+/// assert_eq!(b.get(), 15);
+///
+/// let a: Alignment = cv!(4096);
+/// assert_eq!(a.as_usize(), 4096);
+///
+/// // Checked narrowing of integer constants, including in `const` items.
+/// const SMALL: u8 = cv!(200u32);
+/// assert_eq!(SMALL, 200);
+///
+/// const N: NonZero<u8> = cv!(5);
+/// assert_eq!(N.get(), 5);
+///
+/// // The target type can be given explicitly.
+/// let e = cv!(200u32 => u8);
+/// assert_eq!(e, 200);
+///
+/// // With an explicit primitive target, the expression can use generic parameters.
+/// const fn as_u64<const KEY: u16>() -> u64 {
+/// cv!(KEY => u64)
+/// }
+/// assert_eq!(as_u64::<0x40>(), 0x40);
+/// ```
+#[macro_export]
+#[doc(hidden)]
+macro_rules! cv {
+ (@cast $v:expr => $t:ty) => {
+ const {
+ #[allow(unused_comparisons, unused_assignments, clippy::as_underscore)]
+ {
+ let v = $v;
+ let r = v as $t;
+ // Pin `back` to `v`'s type so `as _` casts back to the source type.
+ let mut back = v;
+ back = r as _;
+
+ ::core::assert!(
+ back == v && (v < 0) == (r < 0),
+ "value does not fit into the target type"
+ );
+
+ r
+ }
+ }
+ };
+ ($v:expr => u8) => { $crate::cv!(@cast $v => u8) };
+ ($v:expr => u16) => { $crate::cv!(@cast $v => u16) };
+ ($v:expr => u32) => { $crate::cv!(@cast $v => u32) };
+ ($v:expr => u64) => { $crate::cv!(@cast $v => u64) };
+ ($v:expr => u128) => { $crate::cv!(@cast $v => u128) };
+ ($v:expr => usize) => { $crate::cv!(@cast $v => usize) };
+ ($v:expr => i8) => { $crate::cv!(@cast $v => i8) };
+ ($v:expr => i16) => { $crate::cv!(@cast $v => i16) };
+ ($v:expr => i32) => { $crate::cv!(@cast $v => i32) };
+ ($v:expr => i64) => { $crate::cv!(@cast $v => i64) };
+ ($v:expr => i128) => { $crate::cv!(@cast $v => i128) };
+ ($v:expr => isize) => { $crate::cv!(@cast $v => isize) };
+ ($v:expr => $t:ty) => {
+ <$t as $crate::num::FromConst<{ $crate::cv!(@cast $v => i128) }>>::VALUE
+ };
+ ($v:expr) => {
+ <_ as $crate::num::FromConst<{ $crate::cv!(@cast $v => i128) }>>::VALUE
+ };
+}
+#[doc(inline)]
+pub use cv;
+
+/// Types that can be created from an integer constant expression validated at build time.
+pub trait FromConst<const V: i128>: Sized {
+ /// The value that corresponds to the constant `V`.
+ ///
+ /// Fails the build if `V` is not a valid value for `Self`.
+ const VALUE: Self;
+}
+
+/// Implements [`FromConst`] for primitive integer types and their [`NonZero`](core::num::NonZero)
+/// versions.
+macro_rules! impl_from_const {
+ ($($type:ty)*) => {
+ $(
+ impl<const V: i128> FromConst<V> for $type {
+ const VALUE: Self = {
+ const_assert!(
+ V >= <$type>::MIN as i128 && V <= <$type>::MAX as i128,
+ "Constant cannot be represented by the target type."
+ );
+
+ V as $type
+ };
+ }
+
+ impl<const V: i128> FromConst<V> for core::num::NonZero<$type> {
+ const VALUE: Self = {
+ const_assert!(V != 0, "Constant cannot be zero.");
+ const_assert!(
+ V >= <$type>::MIN as i128 && V <= <$type>::MAX as i128,
+ "Constant cannot be represented by the underlying type."
+ );
+
+ core::num::NonZero::new(V as $type).unwrap()
+ };
+ }
+ )*
+ };
+}
+
+impl_from_const!(
+ u8 u16 u32 u64 usize
+ i8 i16 i32 i64 isize
+);
+
/// Designates unsigned primitive types.
pub enum Unsigned {}
diff --git a/rust/kernel/num/bounded.rs b/rust/kernel/num/bounded.rs
index d192610a687d..b5ab77fd99cf 100644
--- a/rust/kernel/num/bounded.rs
+++ b/rust/kernel/num/bounded.rs
@@ -13,7 +13,10 @@
};
use kernel::{
- num::Integer,
+ num::{
+ FromConst,
+ Integer, //
+ },
prelude::*, //
};
@@ -262,6 +265,21 @@ pub const fn new<const VALUE: $type>() -> Self {
unsafe { Self::__new(VALUE) }
}
}
+
+ impl<const N: u32, const V: i128> FromConst<V> for Bounded<$type, N> {
+ const VALUE: Self = {
+ const_assert!(
+ V >= <$type>::MIN as i128 && V <= <$type>::MAX as i128,
+ "Constant cannot be represented by the underlying type."
+ );
+ // Statically assert that `V` fits within the set number of bits.
+ const_assert!(fits_within!(V as $type, $type, N));
+
+ // SAFETY: the asserts above confirmed that `V` can be represented within `N`
+ // bits.
+ unsafe { Self::__new(V as $type) }
+ };
+ }
)*
};
}
diff --git a/rust/kernel/ptr.rs b/rust/kernel/ptr.rs
index 82acb531b17b..7d75a537ecae 100644
--- a/rust/kernel/ptr.rs
+++ b/rust/kernel/ptr.rs
@@ -166,6 +166,18 @@ pub const fn mask(self) -> usize {
}
}
+impl<const V: i128> crate::num::FromConst<V> for Alignment {
+ const VALUE: Self = {
+ const_assert!(
+ V > 0 && V <= usize::MAX as i128,
+ "Constant cannot be represented as an Alignment."
+ );
+
+ // The unwrap fails the build if `V` is not a power of two.
+ Alignment::new_checked(V as usize).unwrap()
+ };
+}
+
/// Trait for items that can be aligned against an [`Alignment`].
pub trait Alignable: Sized {
/// Aligns `self` down to `alignment`.
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread* [PATCH 3/3] gpu: nova-core: use cv! for constant casts
2026-08-28 9:15 [PATCH 0/3] rust: introduce cv! macro for safe const conversions of integer-like types Eliot Courtney
2026-08-28 9:15 ` [PATCH 1/3] rust: num: add cv! macro to create values from constant expressions Eliot Courtney
2026-08-28 9:15 ` [PATCH 2/3] rust: prelude: add `num::cv` Eliot Courtney
@ 2026-08-28 9:15 ` Eliot Courtney
2 siblings, 0 replies; 4+ messages in thread
From: Eliot Courtney @ 2026-08-28 9:15 UTC (permalink / raw)
To: Alexandre Courbot, Yury Norov, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Alice Ryhl, Trevor Gross, Danilo Krummrich, Daniel Almeida,
Tamir Duberstein, Onur Özkan, David Airlie, Simona Vetter
Cc: John Hubbard, Alistair Popple, Timur Tabi, rust-for-linux,
linux-kernel, nova-gpu, dri-devel, Eliot Courtney
The new `cv!` macro allows safe casting of constant expressions in a
const context. Update code in nova-core to use it.
Signed-off-by: Eliot Courtney <ecourtney@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 7 ++--
drivers/gpu/nova-core/fb/hal/gb100.rs | 8 ++---
drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 7 ++--
drivers/gpu/nova-core/fsp.rs | 7 ++--
drivers/gpu/nova-core/gsp/cmdq.rs | 11 +++---
drivers/gpu/nova-core/gsp/fw.rs | 39 +++++++++-------------
drivers/gpu/nova-core/gsp/fw/commands.rs | 2 +-
7 files changed, 28 insertions(+), 53 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index dfe4d2e6f82e..e07537346ec5 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -20,10 +20,7 @@
},
Io,
},
- num::casts::{
- self,
- FromSafeCast, //
- },
+ num::casts::FromSafeCast,
prelude::*,
time::Delta,
};
@@ -510,7 +507,7 @@ fn dma_wr(
target_mem: FalconMem,
load_offsets: FalconDmaLoadTarget,
) -> Result {
- const DMA_LEN: u32 = casts::usize_into_u32::<{ MEM_BLOCK_ALIGNMENT }>();
+ const DMA_LEN: u32 = cv!(MEM_BLOCK_ALIGNMENT);
// DMA transfers can only be done in units of 256 bytes. Compute how many such transfers we
// need to perform.
diff --git a/drivers/gpu/nova-core/fb/hal/gb100.rs b/drivers/gpu/nova-core/fb/hal/gb100.rs
index a53d8b435dc2..0bf00d381607 100644
--- a/drivers/gpu/nova-core/fb/hal/gb100.rs
+++ b/drivers/gpu/nova-core/fb/hal/gb100.rs
@@ -11,10 +11,7 @@
},
Io, //
},
- num::{
- casts::usize_into_u32,
- Bounded, //
- },
+ num::Bounded,
prelude::*,
ptr::{
const_align_up,
@@ -84,8 +81,7 @@ fn write_sysmem_flush_page_gb100(bar: Bar0<'_>, addr: Bounded<u64, 52>) {
// This PMU reservation size is r570-specific.
pub(super) const fn pmu_reserved_size_gb100() -> u32 {
- usize_into_u32::<{ const_align_up(SZ_8M + SZ_16M + SZ_4K, Alignment::new::<SZ_128K>()).unwrap() }>(
- )
+ cv!(const_align_up(SZ_8M + SZ_16M + SZ_4K, Alignment::new::<SZ_128K>()).unwrap())
}
impl FbHal for Gb100 {
diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
index d1fb7d2d7480..d637cc4ce312 100644
--- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -15,10 +15,7 @@
io::{register::WithBase, Io},
num::casts::FromSafeCast,
prelude::*,
- ptr::{
- Alignable,
- Alignment, //
- },
+ ptr::Alignable,
sizes,
transmute::AsBytes,
};
@@ -134,7 +131,7 @@ pub(crate) fn new(
let code_size = usize::from_safe_cast(tlv.get_u32(b"CDSZ")?);
let code = blob.get(..code_size).ok_or(EINVAL)?;
let aligned_code_size = code_size
- .align_up(Alignment::new::<{ falcon::MEM_BLOCK_ALIGNMENT }>())
+ .align_up(cv!(falcon::MEM_BLOCK_ALIGNMENT))
.ok_or(EINVAL)?;
let mut ucode = KVec::with_capacity(aligned_code_size, GFP_KERNEL)?;
diff --git a/drivers/gpu/nova-core/fsp.rs b/drivers/gpu/nova-core/fsp.rs
index b9ca298dc97f..e15d0af95499 100644
--- a/drivers/gpu/nova-core/fsp.rs
+++ b/drivers/gpu/nova-core/fsp.rs
@@ -11,10 +11,7 @@
device,
dma::Coherent,
io::poll::read_poll_timeout,
- num::{
- casts,
- TryIntoBounded, //
- },
+ num::TryIntoBounded,
prelude::*,
ptr::{
Alignable,
@@ -287,7 +284,7 @@ fn new<'a>(
};
let version = hal.cot_version();
- let size = casts::usize_into_u16::<{ core::mem::size_of::<NvdmPayloadCot>() }>();
+ let size = cv!(core::mem::size_of::<NvdmPayloadCot>() => u16);
Ok(init!(Self {
header: FspMessageHeader::new(NvdmType::Cot),
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index 658d0a9b2cfb..4a471efe237c 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -23,10 +23,7 @@
Io, //
},
new_mutex,
- num::casts::{
- self,
- FromSafeCast, //
- },
+ num::casts::FromSafeCast,
prelude::*,
ptr,
sync::{
@@ -165,7 +162,7 @@ fn read(
#[repr(C, align(0x1000))]
#[derive(Debug)]
struct MsgqData {
- data: [[u8; GSP_PAGE_SIZE]; casts::u32_as_usize(MSGQ_NUM_PAGES)],
+ data: [[u8; GSP_PAGE_SIZE]; cv!(MSGQ_NUM_PAGES)],
}
// Annoyingly we are forced to use a literal to specify the alignment of
@@ -238,8 +235,8 @@ unsafe impl FromBytes for GspMem {}
impl DmaGspMem {
/// Allocate a new instance and map it for `dev`.
fn new(dev: &device::Device<device::Bound>) -> Result<Self> {
- const MSGQ_SIZE: u32 = casts::usize_into_u32::<{ size_of::<Msgq>() }>();
- const RX_HDR_OFF: u32 = casts::usize_into_u32::<{ mem::offset_of!(Msgq, rx) }>();
+ const MSGQ_SIZE: u32 = cv!(size_of::<Msgq>());
+ const RX_HDR_OFF: u32 = cv!(mem::offset_of!(Msgq, rx));
let mut gsp_mem = CoherentBox::<GspMem>::zeroed(dev, GFP_KERNEL)?;
gsp_mem.cpuq.tx = MsgqTxHeader::new(MSGQ_SIZE, RX_HDR_OFF, MSGQ_NUM_PAGES);
diff --git a/drivers/gpu/nova-core/gsp/fw.rs b/drivers/gpu/nova-core/gsp/fw.rs
index 61064b6a251b..880764c2e5b1 100644
--- a/drivers/gpu/nova-core/gsp/fw.rs
+++ b/drivers/gpu/nova-core/gsp/fw.rs
@@ -19,10 +19,7 @@
io_read,
io_write, //
},
- num::casts::{
- self,
- FromSafeCast, //
- },
+ num::casts::FromSafeCast,
prelude::*,
ptr::{
Alignable,
@@ -57,7 +54,7 @@
/// Maximum size of a single GSP message queue element in bytes.
pub(crate) const GSP_MSG_QUEUE_ELEMENT_SIZE_MAX: usize =
- casts::u32_as_usize(bindings::GSP_MSG_QUEUE_ELEMENT_SIZE_MAX);
+ cv!(bindings::GSP_MSG_QUEUE_ELEMENT_SIZE_MAX);
/// Empty type to group methods related to heap parameters for running the GSP firmware.
enum GspFwHeapParams {}
@@ -110,20 +107,18 @@ pub(crate) struct LibosParams {
impl LibosParams {
/// Version 2 of the GSP LIBOS (Turing and GA100)
const LIBOS2: LibosParams = LibosParams {
- carveout_size: casts::u32_as_u64(bindings::GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS2),
- allowed_heap_size: casts::u32_as_u64(bindings::GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS2_MIN_MB)
+ carveout_size: cv!(bindings::GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS2),
+ allowed_heap_size: cv!(bindings::GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS2_MIN_MB => u64)
* u64::SZ_1M
- ..casts::u32_as_u64(bindings::GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS2_MAX_MB) * u64::SZ_1M,
+ ..cv!(bindings::GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS2_MAX_MB => u64) * u64::SZ_1M,
};
/// Version 3 of the GSP LIBOS (GA102+)
const LIBOS3: LibosParams = LibosParams {
- carveout_size: casts::u32_as_u64(bindings::GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS3_BAREMETAL),
- allowed_heap_size: casts::u32_as_u64(
- bindings::GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS3_BAREMETAL_MIN_MB,
- ) * u64::SZ_1M
- ..casts::u32_as_u64(bindings::GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS3_BAREMETAL_MAX_MB)
- * u64::SZ_1M,
+ carveout_size: cv!(bindings::GSP_FW_HEAP_PARAM_OS_SIZE_LIBOS3_BAREMETAL),
+ allowed_heap_size: cv!(bindings::GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS3_BAREMETAL_MIN_MB => u64)
+ * u64::SZ_1M
+ ..cv!(bindings::GSP_FW_HEAP_SIZE_OVERRIDE_LIBOS3_BAREMETAL_MAX_MB => u64) * u64::SZ_1M,
};
/// Returns the libos parameters corresponding to `chipset`.
@@ -682,12 +677,8 @@ fn id8(name: &str) -> u64 {
id8: id8(name),
pa: obj.dma_address(),
size: u64::from_safe_cast(obj.size()),
- kind: casts::u32_into_u8::<
- { bindings::LibosMemoryRegionKind_LIBOS_MEMORY_REGION_CONTIGUOUS },
- >(),
- loc: casts::u32_into_u8::<
- { bindings::LibosMemoryRegionLoc_LIBOS_MEMORY_REGION_LOC_SYSMEM },
- >(),
+ kind: cv!(bindings::LibosMemoryRegionKind_LIBOS_MEMORY_REGION_CONTIGUOUS),
+ loc: cv!(bindings::LibosMemoryRegionLoc_LIBOS_MEMORY_REGION_LOC_SYSMEM),
..Zeroable::init_zeroed()
});
@@ -715,12 +706,12 @@ pub(crate) fn new(msgq_size: u32, rx_hdr_offset: u32, msg_count: u32) -> Self {
Self(bindings::msgqTxHeader {
version: 0,
size: msgq_size,
- msgSize: casts::usize_into_u32::<GSP_PAGE_SIZE>(),
+ msgSize: cv!(GSP_PAGE_SIZE),
msgCount: msg_count,
writePtr: 0,
flags: 1,
rxHdrOff: rx_hdr_offset,
- entryOff: casts::usize_into_u32::<GSP_PAGE_SIZE>(),
+ entryOff: cv!(GSP_PAGE_SIZE),
})
}
@@ -947,7 +938,7 @@ impl MessageQueueInitArguments {
fn new(cmdq: &Cmdq) -> impl Init<Self> + '_ {
init!(MessageQueueInitArguments {
sharedMemPhysAddr: cmdq.dma_addr,
- pageTableEntryCount: casts::usize_into_u32::<{ Cmdq::NUM_PTES }>(),
+ pageTableEntryCount: cv!(Cmdq::NUM_PTES),
cmdQueueOffset: u64::from_safe_cast(Cmdq::CMDQ_OFFSET),
statQueueOffset: u64::from_safe_cast(Cmdq::STATQ_OFFSET),
..Zeroable::init_zeroed()
@@ -969,7 +960,7 @@ impl GspAcrBootGspRmParams {
fn new(target: GspDmaTarget, wpr_meta_addr: u64) -> impl Init<Self> {
let params = init!(Self {
target: target as u32,
- gspRmDescSize: casts::usize_into_u32::<{ size_of::<GspFwWprMeta>() }>(),
+ gspRmDescSize: cv!(size_of::<GspFwWprMeta>()),
gspRmDescOffset: wpr_meta_addr,
bIsGspRmBoot: 1,
wprCarveoutOffset: 0,
diff --git a/drivers/gpu/nova-core/gsp/fw/commands.rs b/drivers/gpu/nova-core/gsp/fw/commands.rs
index 201594fa437b..60b0355d1d2e 100644
--- a/drivers/gpu/nova-core/gsp/fw/commands.rs
+++ b/drivers/gpu/nova-core/gsp/fw/commands.rs
@@ -82,7 +82,7 @@ pub(crate) fn new(offset: u32, value: u32) -> Self {
// We only support DWORD types for now. Support for other types
// will come later if required.
- type_: bindings::REGISTRY_TABLE_ENTRY_TYPE_DWORD as u8,
+ type_: cv!(bindings::REGISTRY_TABLE_ENTRY_TYPE_DWORD),
__bindgen_padding_0: Default::default(),
data: value,
length: 0,
--
2.55.0
^ permalink raw reply [flat|nested] 4+ messages in thread