* [PATCH v2] Documentation: locking: Document hwspinlock bust() callback
From: Antonio Borneo @ 2026-03-23 10:58 UTC (permalink / raw)
To: Bjorn Andersson, Baolin Wang, Peter Zijlstra, Ingo Molnar,
Will Deacon, Boqun Feng, Waiman Long, Jonathan Corbet, Shuah Khan
Cc: Antonio Borneo, linux-remoteproc, linux-kernel, linux-doc,
linux-stm32
Add the missing callback and the corresponding description in the
relevant chapter.
Signed-off-by: Antonio Borneo <antonio.borneo@foss.st.com>
---
Changes in v2:
- align with review from sashiko.dev:
http://sashiko.dev/#/patchset/20260314170142.18290-1-antonio.borneo%40foss.st.com
- update the number of callbacks;
- report the returned value;
- report it can sleep.
- rebase on v7.0-rc5
- v1 available at:
https://lore.kernel.org/lkml/20260314170142.18290-1-antonio.borneo@foss.st.com/
---
Documentation/locking/hwspinlock.rst | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/Documentation/locking/hwspinlock.rst b/Documentation/locking/hwspinlock.rst
index a737c702a7d1d..a5fff33181c1b 100644
--- a/Documentation/locking/hwspinlock.rst
+++ b/Documentation/locking/hwspinlock.rst
@@ -420,11 +420,12 @@ initialized by the hwspinlock core itself.
Implementation callbacks
========================
-There are three possible callbacks defined in 'struct hwspinlock_ops'::
+There are four possible callbacks defined in 'struct hwspinlock_ops'::
struct hwspinlock_ops {
int (*trylock)(struct hwspinlock *lock);
void (*unlock)(struct hwspinlock *lock);
+ int (*bust)(struct hwspinlock *lock, unsigned int id);
void (*relax)(struct hwspinlock *lock);
};
@@ -436,6 +437,11 @@ return 0 on failure and 1 on success. This callback may **not** sleep.
The ->unlock() callback releases the lock. It always succeed, and it, too,
may **not** sleep.
+The ->bust() callback is optional. It is called by hwspinlock core to bust a
+specific lock when the remote processor 'id' is not responding, e.g. due to a
+firmware crash. It return 0 on success and a negative error code on failure.
+It can sleep.
+
The ->relax() callback is optional. It is called by hwspinlock core while
spinning on a lock, and can be used by the underlying implementation to force
a delay between two successive invocations of ->trylock(). It may **not** sleep.
base-commit: c369299895a591d96745d6492d4888259b004a9e
--
2.34.1
^ permalink raw reply related
* Re: [PATCH RFC v2 2/9] iio: frequency: ad9910: initial driver implementation
From: Andy Shevchenko @ 2026-03-23 11:00 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: Jonathan Cameron, Rodrigo Alencar via B4 Relay, rodrigo.alencar,
linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, David Lechner,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Philipp Zabel, Jonathan Corbet, Shuah Khan
In-Reply-To: <grjlyz76cmjy7t3ytwycaklauipbqcj7adjr2t7k3axwlayowr@4zbuzehcrpvs>
On Mon, Mar 23, 2026 at 10:34:37AM +0000, Rodrigo Alencar wrote:
> On 26/03/22 04:50PM, Jonathan Cameron wrote:
> > On Wed, 18 Mar 2026 17:56:02 +0000
> > Rodrigo Alencar via B4 Relay <devnull+rodrigo.alencar.analog.com@kernel.org> wrote:
...
> > > +#include <linux/array_size.h>
> > > +#include <linux/bitfield.h>
> > > +#include <linux/clk.h>
> > > +#include <linux/delay.h>
> > > +#include <linux/device.h>
> >
> > Generally can avoid including device.h in favour of more specific
> > headers. There are a few exceptions where we can't such as actual
> > dereferencing of struct device, but I don't recall seeing a case in here.
>
> I understood that the usage of devm_add_action_or_reset() would justify
> the header.
It's in the device/devres.h.
...
> > > + st->data.pll_enabled = device_property_read_bool(dev, "adi,pll-enable");
> > > + if (st->data.pll_enabled) {
> > > + tmp = AD9910_ICP_MAX_uA;
> >
> > Defaulting to max current seems unusual.
Agree.
> > What's the motivation? Normal instinct is go minimum if no other info.
>
> ICP_MAX_uA leads to 111 in the CFR3_ICP field, which is the default value
> when the device resets or when it powers on. I suppose that if we are not
> touching that property, there would be no reason to change that.
I believe we should think different, id est about potential damages or
current drain. I would expect a minimum or hi-impedance (power off) state
of the related part of the device.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH RFC v2 3/9] iio: frequency: ad9910: add simple parallel port mode support
From: Andy Shevchenko @ 2026-03-23 11:01 UTC (permalink / raw)
To: Rodrigo Alencar
Cc: rodrigo.alencar, linux-iio, devicetree, linux-kernel, linux-doc,
Lars-Peter Clausen, Michael Hennerich, Jonathan Cameron,
David Lechner, Andy Shevchenko, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Philipp Zabel, Jonathan Corbet, Shuah Khan
In-Reply-To: <oqspz57p3xgzeq2lvcvhgihu3yeucufdjxurwc3dpuvj6dfu6j@kv23vp6xxud3>
On Mon, Mar 23, 2026 at 10:39:06AM +0000, Rodrigo Alencar wrote:
> On 26/03/18 08:28PM, Andy Shevchenko wrote:
> > On Wed, Mar 18, 2026 at 05:56:03PM +0000, Rodrigo Alencar via B4 Relay wrote:
> >
> > > Add parallel port channel with frequency scale, frequency offset, phase
> > > offset, and amplitude offset extended attributes for configuring the
> > > parallel data path.
...
> > > + case IIO_CHAN_INFO_ENABLE:
> > > + val = !!val;
> >
> > Only used once, why do we need this...
>
> Next patches introduce more channels here, so the additions are easier to review.
Yeah, but shouldn't be better to put this in each FIELD_PREP() as it will
immediately show the correctness and the value range without looking backwards
in the code?
> > > + switch (chan->channel) {
> > > + case AD9910_CHANNEL_PARALLEL_PORT:
> > > + tmp32 = FIELD_PREP(AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK, val);
> >
> > ...and not just here?
> >
> > > + return ad9910_reg32_update(st, AD9910_REG_CFR2,
> > > + AD9910_CFR2_PARALLEL_DATA_PORT_EN_MSK,
> > > + tmp32, true);
> > > + default:
> > > + return -EINVAL;
> > > + }
>
> --
> Kind regards,
>
> Rodrigo Alencar
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* Re: [PATCH] Documentation: PCI: Document decoding of TLP Header in AER messages
From: Mika Westerberg @ 2026-03-23 11:03 UTC (permalink / raw)
To: Lukas Wunner
Cc: Bjorn Helgaas, Jonathan Corbet, linux-pci, linux-doc,
Ilpo Jarvinen, Maciej Grochowski, Kai-Heng Feng
In-Reply-To: <bf826c41b4c1d255c7dcb16e266b52f774d944ed.1774246067.git.lukas@wunner.de>
On Mon, Mar 23, 2026 at 07:52:39AM +0100, Lukas Wunner wrote:
> The prefix/header of the TLP that caused an error is recorded by the Root
> Complex and emitted to the kernel log in raw hex format. Document the
> existence and usage of tlp-tool, which allows decoding the TLP Header
> into human-readable form.
>
> The TLP Header hints at the root cause of an error, yet is often ignored
> because of its seeming opaqueness. Instead, PCIe errors are frequently
> worked around by a change in the kernel without fully understanding the
> actual source of the problem. With more documentation on available tools
> we'll hopefully come up with better solutions.
>
> There are also wireshark dissectors for TLPs, but it seems they expect a
> complete TLP, not just the header, and they cannot grok the hex format
> emitted by the kernel directly. tlp-tool appears to be the most cut and
> dried solution out there.
>
> Signed-off-by: Lukas Wunner <lukas@wunner.de>
> Cc: Maciej Grochowski <mx2pg@pm.me>
Good idea, this is useful.
Reviewed-by: Mika Westerberg <mika.westerberg@linux.intel.com>
^ permalink raw reply
* [PATCH v3 00/10] gpu: nova-core: convert registers to use the kernel register macro
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
nova-core carried its own helper macro to declare register types. Its
purpose was to be temporary since the beginning, and to serve as a
testbed to develop an equivalent that could be used kernel-wide.
That equivalent has now been merged, so it is time to retire the
nova-core local version.
The kernel register macro has evolved into something significantly
different from the one in nova-core, so it cannot be used as a drop-in
replacement. All declarations and sites using registers need to be
updated. No semantic change should happen as a result.
All the patches in this series could also be squashed into a single one
without altering their reviewability significantly.
I hope to be able to merge this quickly so we can rebase in-flight
series to use the updated register syntax.
This series is based on drm-rust-next as of 2026-03-23 and has been
tested on Turing.
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
Changes in v3:
- Remove obsolete part of PMC commit message.
- Add { } brackets around const expression used in `Bounded`
constructor.
- Use `MAX..` arm to `bounded_enum` to catch missing values in the
required range of the `From` implementation. (thanks Gary!)
- Remove unneeded const asserts that are already caught by `Bounded::new`.
- Link to v2: https://patch.msgid.link/20260320-b4-nova-register-v2-0-88fcf103e8d4@nvidia.com
Changes in v2:
- Reorder register fields in descending order.
- Drop `nv_reg` macro and use the kernel `register` macro directly.
- Make `FUSE_UCODE1_VERSION` registers return a u16 directly.
- Use `into_raw` instead of accessing the inner value of registers directly.
- Use safer macro to generate the `From` and `TryFrom` implementations
of falcon enum types.
- Link to v1: https://patch.msgid.link/20260318-b4-nova-register-v1-0-22a358aa4c63@nvidia.com
---
Alexandre Courbot (10):
gpu: nova-core: convert PMC registers to kernel register macro
gpu: nova-core: convert PBUS registers to kernel register macro
gpu: nova-core: convert PFB registers to kernel register macro
gpu: nova-core: convert GC6 registers to kernel register macro
gpu: nova-core: convert FUSE registers to kernel register macro
gpu: nova-core: convert PDISP registers to kernel register macro
gpu: nova-core: falcon: introduce `bounded_enum` macro
gpu: nova-core: convert falcon registers to kernel register macro
gpu: nova-core: remove `io::` qualifier to register macro invocations
Documentation: nova: remove register abstraction task
Documentation/gpu/nova/core/todo.rst | 76 ---
drivers/gpu/nova-core/falcon.rs | 577 ++++++++--------
drivers/gpu/nova-core/falcon/gsp.rs | 27 +-
drivers/gpu/nova-core/falcon/hal/ga102.rs | 70 +-
drivers/gpu/nova-core/falcon/hal/tu102.rs | 12 +-
drivers/gpu/nova-core/falcon/sec2.rs | 17 +-
drivers/gpu/nova-core/fb.rs | 6 +-
drivers/gpu/nova-core/fb/hal/ga100.rs | 37 +-
drivers/gpu/nova-core/fb/hal/ga102.rs | 7 +-
drivers/gpu/nova-core/fb/hal/tu102.rs | 17 +-
drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 19 +-
drivers/gpu/nova-core/gfw.rs | 11 +-
drivers/gpu/nova-core/gpu.rs | 37 +-
drivers/gpu/nova-core/gsp/boot.rs | 11 +-
drivers/gpu/nova-core/gsp/cmdq.rs | 9 +-
drivers/gpu/nova-core/regs.rs | 601 +++++++++--------
drivers/gpu/nova-core/regs/macros.rs | 739 ---------------------
17 files changed, 760 insertions(+), 1513 deletions(-)
---
base-commit: a19457958c3018783881c4416f272cd594f13049
change-id: 20260318-b4-nova-register-6908b5118552
Best regards,
--
Alexandre Courbot <acourbot@nvidia.com>
^ permalink raw reply
* [PATCH v3 01/10] gpu: nova-core: convert PMC registers to kernel register macro
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
Convert all PMC registers to use the kernel's register macro and update
the code accordingly.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 7 ++++--
drivers/gpu/nova-core/gpu.rs | 37 +++++++++++++++---------------
drivers/gpu/nova-core/regs.rs | 50 ++++++++++++++++++++++++++---------------
3 files changed, 55 insertions(+), 39 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 7097a206ec3c..5a4f7fc85160 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -13,7 +13,10 @@
DmaAddress,
DmaMask, //
},
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ Io, //
+ },
prelude::*,
sync::aref::ARef,
time::Delta,
@@ -532,7 +535,7 @@ pub(crate) fn reset(&self, bar: &Bar0) -> Result {
self.hal.reset_wait_mem_scrubbing(bar)?;
regs::NV_PFALCON_FALCON_RM::default()
- .set_value(regs::NV_PMC_BOOT_0::read(bar).into())
+ .set_value(bar.read(regs::NV_PMC_BOOT_0).into())
.write(bar, &E::ID);
Ok(())
diff --git a/drivers/gpu/nova-core/gpu.rs b/drivers/gpu/nova-core/gpu.rs
index 8579d632e717..cb549c8b254f 100644
--- a/drivers/gpu/nova-core/gpu.rs
+++ b/drivers/gpu/nova-core/gpu.rs
@@ -4,6 +4,8 @@
device,
devres::Devres,
fmt,
+ io::Io,
+ num::Bounded,
pci,
prelude::*,
sync::Arc, //
@@ -129,24 +131,18 @@ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
}
/// Enum representation of the GPU generation.
-///
-/// TODO: remove the `Default` trait implementation, and the `#[default]`
-/// attribute, once the register!() macro (which creates Architecture items) no
-/// longer requires it for read-only fields.
-#[derive(fmt::Debug, Default, Copy, Clone)]
-#[repr(u8)]
+#[derive(fmt::Debug, Copy, Clone)]
pub(crate) enum Architecture {
- #[default]
Turing = 0x16,
Ampere = 0x17,
Ada = 0x19,
}
-impl TryFrom<u8> for Architecture {
+impl TryFrom<Bounded<u32, 6>> for Architecture {
type Error = Error;
- fn try_from(value: u8) -> Result<Self> {
- match value {
+ fn try_from(value: Bounded<u32, 6>) -> Result<Self> {
+ match u8::from(value) {
0x16 => Ok(Self::Turing),
0x17 => Ok(Self::Ampere),
0x19 => Ok(Self::Ada),
@@ -155,23 +151,26 @@ fn try_from(value: u8) -> Result<Self> {
}
}
-impl From<Architecture> for u8 {
+impl From<Architecture> for Bounded<u32, 6> {
fn from(value: Architecture) -> Self {
- // CAST: `Architecture` is `repr(u8)`, so this cast is always lossless.
- value as u8
+ match value {
+ Architecture::Turing => Bounded::<u32, _>::new::<0x16>(),
+ Architecture::Ampere => Bounded::<u32, _>::new::<0x17>(),
+ Architecture::Ada => Bounded::<u32, _>::new::<0x19>(),
+ }
}
}
pub(crate) struct Revision {
- major: u8,
- minor: u8,
+ major: Bounded<u8, 4>,
+ minor: Bounded<u8, 4>,
}
impl From<regs::NV_PMC_BOOT_42> for Revision {
fn from(boot0: regs::NV_PMC_BOOT_42) -> Self {
Self {
- major: boot0.major_revision(),
- minor: boot0.minor_revision(),
+ major: boot0.major_revision().cast(),
+ minor: boot0.minor_revision().cast(),
}
}
}
@@ -208,13 +207,13 @@ fn new(dev: &device::Device, bar: &Bar0) -> Result<Spec> {
// from an earlier (pre-Fermi) era, and then using boot42 to precisely identify the GPU.
// Somewhere in the Rubin timeframe, boot0 will no longer have space to add new GPU IDs.
- let boot0 = regs::NV_PMC_BOOT_0::read(bar);
+ let boot0 = bar.read(regs::NV_PMC_BOOT_0);
if boot0.is_older_than_fermi() {
return Err(ENODEV);
}
- let boot42 = regs::NV_PMC_BOOT_42::read(bar);
+ let boot42 = bar.read(regs::NV_PMC_BOOT_42);
Spec::try_from(boot42).inspect_err(|_| {
dev_err!(dev, "Unsupported chipset: {}\n", boot42);
})
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 53f412f0ca32..58fb807605dd 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -8,6 +8,7 @@
pub(crate) mod macros;
use kernel::{
+ io,
prelude::*,
time, //
};
@@ -37,18 +38,38 @@
// 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";
-});
+io::register! {
+ /// Basic revision information about the GPU.
+ pub(crate) NV_PMC_BOOT_0(u32) @ 0x00000000 {
+ /// Lower bits of the architecture.
+ 28:24 architecture_0;
+ /// Implementation version of the architecture.
+ 23:20 implementation;
+ /// MSB of the architecture.
+ 8:8 architecture_1;
+ /// Major revision of the chip.
+ 7:4 major_revision;
+ /// Minor revision of the chip.
+ 3:0 minor_revision;
+ }
+
+ /// Extended architecture information.
+ pub(crate) NV_PMC_BOOT_42(u32) @ 0x00000a00 {
+ /// Architecture value.
+ 29:24 architecture ?=> Architecture;
+ /// Implementation version of the architecture.
+ 23:20 implementation;
+ /// Major revision of the chip.
+ 19:16 major_revision;
+ /// Minor revision of the chip.
+ 15:12 minor_revision;
+ }
+}
impl NV_PMC_BOOT_0 {
pub(crate) fn is_older_than_fermi(self) -> bool {
// From https://github.com/NVIDIA/open-gpu-doc/tree/master/manuals :
- const NV_PMC_BOOT_0_ARCHITECTURE_GF100: u8 = 0xc;
+ const NV_PMC_BOOT_0_ARCHITECTURE_GF100: u32 = 0xc;
// Older chips left arch1 zeroed out. That, combined with an arch0 value that is less than
// GF100, means "older than Fermi".
@@ -56,13 +77,6 @@ pub(crate) fn is_older_than_fermi(self) -> bool {
}
}
-register!(NV_PMC_BOOT_42 @ 0x00000a00, "Extended architecture information" {
- 15:12 minor_revision as u8, "Minor revision of the chip";
- 19:16 major_revision as u8, "Major revision of the chip";
- 23:20 implementation as u8, "Implementation version of the architecture";
- 29:24 architecture as u8 ?=> Architecture, "Architecture value";
-});
-
impl NV_PMC_BOOT_42 {
/// Combines `architecture` and `implementation` to obtain a code unique to the chipset.
pub(crate) fn chipset(self) -> Result<Chipset> {
@@ -76,8 +90,8 @@ pub(crate) fn chipset(self) -> Result<Chipset> {
/// Returns the raw architecture value from the register.
fn architecture_raw(self) -> u8 {
- ((self.0 >> Self::ARCHITECTURE_RANGE.start()) & ((1 << Self::ARCHITECTURE_RANGE.len()) - 1))
- as u8
+ ((self.into_raw() >> Self::ARCHITECTURE_RANGE.start())
+ & ((1 << Self::ARCHITECTURE_RANGE.len()) - 1)) as u8
}
}
@@ -86,7 +100,7 @@ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
write!(
f,
"boot42 = 0x{:08x} (architecture 0x{:x}, implementation 0x{:x})",
- self.0,
+ self.inner,
self.architecture_raw(),
self.implementation()
)
--
2.53.0
^ permalink raw reply related
* [PATCH v3 02/10] gpu: nova-core: convert PBUS registers to kernel register macro
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
Convert all PBUS registers to use the kernel's register macro and update
the code accordingly.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/gsp/boot.rs | 5 ++++-
drivers/gpu/nova-core/regs.rs | 12 +++++++-----
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 5e73bd769dcc..06b8313c1219 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -5,6 +5,7 @@
dma::CoherentAllocation,
dma_write,
io::poll::read_poll_timeout,
+ io::Io,
pci,
prelude::*,
time::Delta, //
@@ -87,7 +88,9 @@ fn run_fwsec_frts(
}
// SCRATCH_E contains the error code for FWSEC-FRTS.
- let frts_status = regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR::read(bar).frts_err_code();
+ let frts_status = bar
+ .read(regs::NV_PBUS_SW_SCRATCH_0E_FRTS_ERR)
+ .frts_err_code();
if frts_status != 0 {
dev_err!(
dev,
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 58fb807605dd..533d912659ba 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -109,12 +109,14 @@ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
// PBUS
-register!(NV_PBUS_SW_SCRATCH @ 0x00001400[64] {});
+io::register! {
+ pub(crate) NV_PBUS_SW_SCRATCH(u32)[64] @ 0x00001400 {}
-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;
-});
+ /// Scratch register 0xe used as FRTS firmware error code.
+ pub(crate) NV_PBUS_SW_SCRATCH_0E_FRTS_ERR(u32) => NV_PBUS_SW_SCRATCH[0xe] {
+ 31:16 frts_err_code;
+ }
+}
// PFB
--
2.53.0
^ permalink raw reply related
* [PATCH v3 03/10] gpu: nova-core: convert PFB registers to kernel register macro
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
Convert all PFB registers to use the kernel's register macro and update
the code accordingly.
NV_PGSP_QUEUE_HEAD was somehow caught in the PFB section, so move it to
its own section and convert it as well.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/fb/hal/ga100.rs | 34 +++++++++++++--------
drivers/gpu/nova-core/fb/hal/tu102.rs | 14 +++++----
drivers/gpu/nova-core/gsp/boot.rs | 6 ++--
drivers/gpu/nova-core/gsp/cmdq.rs | 9 +++---
drivers/gpu/nova-core/regs.rs | 57 ++++++++++++++++++++---------------
5 files changed, 70 insertions(+), 50 deletions(-)
diff --git a/drivers/gpu/nova-core/fb/hal/ga100.rs b/drivers/gpu/nova-core/fb/hal/ga100.rs
index e0acc41aa7cd..629588c75778 100644
--- a/drivers/gpu/nova-core/fb/hal/ga100.rs
+++ b/drivers/gpu/nova-core/fb/hal/ga100.rs
@@ -1,6 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
-use kernel::prelude::*;
+use kernel::{
+ io::Io,
+ num::Bounded,
+ prelude::*, //
+};
use crate::{
driver::Bar0,
@@ -13,22 +17,26 @@
struct Ga100;
pub(super) fn read_sysmem_flush_page_ga100(bar: &Bar0) -> u64 {
- u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::read(bar).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT
- | u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::read(bar).adr_63_40())
+ u64::from(bar.read(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT
+ | u64::from(bar.read(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI).adr_63_40())
<< FLUSH_SYSMEM_ADDR_SHIFT_HI
}
pub(super) fn write_sysmem_flush_page_ga100(bar: &Bar0, addr: u64) {
- regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::default()
- // CAST: `as u32` is used on purpose since the remaining bits are guaranteed to fit within
- // a `u32`.
- .set_adr_63_40((addr >> FLUSH_SYSMEM_ADDR_SHIFT_HI) as u32)
- .write(bar);
- regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::default()
- // CAST: `as u32` is used on purpose since we want to strip the upper bits that have been
- // written to `NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI`.
- .set_adr_39_08((addr >> FLUSH_SYSMEM_ADDR_SHIFT) as u32)
- .write(bar);
+ bar.write_reg(
+ regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI::zeroed().with_adr_63_40(
+ Bounded::<u64, _>::from(addr)
+ .shr::<FLUSH_SYSMEM_ADDR_SHIFT_HI, _>()
+ .cast(),
+ ),
+ );
+
+ bar.write_reg(
+ regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::zeroed()
+ // CAST: `as u32` is used on purpose since we want to strip the upper bits that have
+ // been written to `NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI`.
+ .with_adr_39_08((addr >> FLUSH_SYSMEM_ADDR_SHIFT) as u32),
+ );
}
pub(super) fn display_enabled_ga100(bar: &Bar0) -> bool {
diff --git a/drivers/gpu/nova-core/fb/hal/tu102.rs b/drivers/gpu/nova-core/fb/hal/tu102.rs
index eec984f4e816..515d50872224 100644
--- a/drivers/gpu/nova-core/fb/hal/tu102.rs
+++ b/drivers/gpu/nova-core/fb/hal/tu102.rs
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
-use kernel::prelude::*;
+use kernel::{
+ io::Io,
+ prelude::*, //
+};
use crate::{
driver::Bar0,
@@ -13,7 +16,7 @@
pub(super) const FLUSH_SYSMEM_ADDR_SHIFT: u32 = 8;
pub(super) fn read_sysmem_flush_page_gm107(bar: &Bar0) -> u64 {
- u64::from(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::read(bar).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT
+ u64::from(bar.read(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR).adr_39_08()) << FLUSH_SYSMEM_ADDR_SHIFT
}
pub(super) fn write_sysmem_flush_page_gm107(bar: &Bar0, addr: u64) -> Result {
@@ -21,9 +24,7 @@ pub(super) fn write_sysmem_flush_page_gm107(bar: &Bar0, addr: u64) -> Result {
u32::try_from(addr >> FLUSH_SYSMEM_ADDR_SHIFT)
.map_err(|_| EINVAL)
.map(|addr| {
- regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::default()
- .set_adr_39_08(addr)
- .write(bar)
+ bar.write_reg(regs::NV_PFB_NISO_FLUSH_SYSMEM_ADDR::zeroed().with_adr_39_08(addr))
})
}
@@ -32,7 +33,8 @@ pub(super) fn display_enabled_gm107(bar: &Bar0) -> bool {
}
pub(super) fn vidmem_size_gp102(bar: &Bar0) -> u64 {
- regs::NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE::read(bar).usable_fb_size()
+ bar.read(regs::NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE)
+ .usable_fb_size()
}
struct Tu102;
diff --git a/drivers/gpu/nova-core/gsp/boot.rs b/drivers/gpu/nova-core/gsp/boot.rs
index 06b8313c1219..55d8aef5ff1e 100644
--- a/drivers/gpu/nova-core/gsp/boot.rs
+++ b/drivers/gpu/nova-core/gsp/boot.rs
@@ -58,7 +58,7 @@ fn run_fwsec_frts(
) -> Result<()> {
// Check that the WPR2 region does not already exists - if it does, we cannot run
// FWSEC-FRTS until the GPU is reset.
- if regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI::read(bar).higher_bound() != 0 {
+ if bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound() != 0 {
dev_err!(
dev,
"WPR2 region already exists - GPU needs to be reset to proceed\n"
@@ -103,8 +103,8 @@ fn run_fwsec_frts(
// Check that the WPR2 region has been created as we requested.
let (wpr2_lo, wpr2_hi) = (
- regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO::read(bar).lower_bound(),
- regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI::read(bar).higher_bound(),
+ bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_LO).lower_bound(),
+ bar.read(regs::NV_PFB_PRI_MMU_WPR2_ADDR_HI).higher_bound(),
);
match (wpr2_lo, wpr2_hi) {
diff --git a/drivers/gpu/nova-core/gsp/cmdq.rs b/drivers/gpu/nova-core/gsp/cmdq.rs
index d36a62ba1c60..b8b0edba5afd 100644
--- a/drivers/gpu/nova-core/gsp/cmdq.rs
+++ b/drivers/gpu/nova-core/gsp/cmdq.rs
@@ -11,7 +11,10 @@
DmaAddress, //
},
dma_write,
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ Io, //
+ },
new_mutex,
prelude::*,
sync::{
@@ -503,9 +506,7 @@ fn calculate_checksum<T: Iterator<Item = u8>>(it: T) -> u32 {
/// Notifies the GSP that we have updated the command queue pointers.
fn notify_gsp(bar: &Bar0) {
- regs::NV_PGSP_QUEUE_HEAD::default()
- .set_address(0)
- .write(bar);
+ bar.write_reg(regs::NV_PGSP_QUEUE_HEAD::zeroed().with_address(0u32));
}
/// Sends `command` to the GSP and waits for the reply.
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 533d912659ba..4f5cd64c2fce 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -120,26 +120,35 @@ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
// PFB
-// The following two registers together hold the physical system memory address that is used by the
-// GPU to perform sysmembar operations (see `fb::SysmemFlush`).
+io::register! {
+ /// Low bits of the physical system memory address used by the GPU to perform sysmembar
+ /// operations (see [`crate::fb::SysmemFlush`]).
+ pub(crate) NV_PFB_NISO_FLUSH_SYSMEM_ADDR(u32) @ 0x00100c10 {
+ 31:0 adr_39_08;
+ }
-register!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR @ 0x00100c10 {
- 31:0 adr_39_08 as u32;
-});
+ /// High bits of the physical system memory address used by the GPU to perform sysmembar
+ /// operations (see [`crate::fb::SysmemFlush`]).
+ pub(crate) NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI(u32) @ 0x00100c40 {
+ 23:0 adr_63_40;
+ }
-register!(NV_PFB_NISO_FLUSH_SYSMEM_ADDR_HI @ 0x00100c40 {
- 23:0 adr_63_40 as u32;
-});
+ pub(crate) NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE(u32) @ 0x00100ce0 {
+ 30:30 ecc_mode_enabled => bool;
+ 9:4 lower_mag;
+ 3:0 lower_scale;
+ }
-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;
-});
+ pub(crate) NV_PFB_PRI_MMU_WPR2_ADDR_LO(u32) @ 0x001fa824 {
+ /// Bits 12..40 of the lower (inclusive) bound of the WPR2 region.
+ 31:4 lo_val;
+ }
-register!(NV_PGSP_QUEUE_HEAD @ 0x00110c00 {
- 31:0 address as u32;
-});
+ pub(crate) NV_PFB_PRI_MMU_WPR2_ADDR_HI(u32) @ 0x001fa828 {
+ /// Bits 12..40 of the higher (exclusive) bound of the WPR2 region.
+ 31:4 hi_val;
+ }
+}
impl NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE {
/// Returns the usable framebuffer size, in bytes.
@@ -156,10 +165,6 @@ 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";
-});
-
impl NV_PFB_PRI_MMU_WPR2_ADDR_LO {
/// Returns the lower (inclusive) bound of the WPR2 region.
pub(crate) fn lower_bound(self) -> u64 {
@@ -167,10 +172,6 @@ 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";
-});
-
impl NV_PFB_PRI_MMU_WPR2_ADDR_HI {
/// Returns the higher (exclusive) bound of the WPR2 region.
///
@@ -180,6 +181,14 @@ pub(crate) fn higher_bound(self) -> u64 {
}
}
+// PGSP
+
+io::register! {
+ pub(crate) NV_PGSP_QUEUE_HEAD(u32) @ 0x00110c00 {
+ 31:0 address;
+ }
+}
+
// PGC6 register space.
//
// `GC6` is a GPU low-power state where VRAM is in self-refresh and the GPU is powered down (except
--
2.53.0
^ permalink raw reply related
* [PATCH v3 04/10] gpu: nova-core: convert GC6 registers to kernel register macro
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
Convert all GC6 registers to use the kernel's register macro and update
the code accordingly.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon/gsp.rs | 7 ++--
drivers/gpu/nova-core/fb/hal/ga102.rs | 7 ++--
drivers/gpu/nova-core/gfw.rs | 11 ++++--
drivers/gpu/nova-core/regs.rs | 67 ++++++++++++++++++-----------------
4 files changed, 52 insertions(+), 40 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
index 67edef3636c1..e52f57abc223 100644
--- a/drivers/gpu/nova-core/falcon/gsp.rs
+++ b/drivers/gpu/nova-core/falcon/gsp.rs
@@ -1,7 +1,10 @@
// SPDX-License-Identifier: GPL-2.0
use kernel::{
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ Io, //
+ },
prelude::*,
time::Delta, //
};
@@ -47,7 +50,7 @@ pub(crate) fn clear_swgen0_intr(&self, bar: &Bar0) {
/// Checks if GSP reload/resume has completed during the boot process.
pub(crate) fn check_reload_completed(&self, bar: &Bar0, timeout: Delta) -> Result<bool> {
read_poll_timeout(
- || Ok(regs::NV_PGC6_BSI_SECURE_SCRATCH_14::read(bar)),
+ || Ok(bar.read(regs::NV_PGC6_BSI_SECURE_SCRATCH_14)),
|val| val.boot_stage_3_handoff(),
Delta::ZERO,
timeout,
diff --git a/drivers/gpu/nova-core/fb/hal/ga102.rs b/drivers/gpu/nova-core/fb/hal/ga102.rs
index 734605905031..4b9f0f74d0e7 100644
--- a/drivers/gpu/nova-core/fb/hal/ga102.rs
+++ b/drivers/gpu/nova-core/fb/hal/ga102.rs
@@ -1,6 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
-use kernel::prelude::*;
+use kernel::{
+ io::Io,
+ prelude::*, //
+};
use crate::{
driver::Bar0,
@@ -9,7 +12,7 @@
};
fn vidmem_size_ga102(bar: &Bar0) -> u64 {
- regs::NV_USABLE_FB_SIZE_IN_MB::read(bar).usable_fb_size()
+ bar.read(regs::NV_USABLE_FB_SIZE_IN_MB).usable_fb_size()
}
struct Ga102;
diff --git a/drivers/gpu/nova-core/gfw.rs b/drivers/gpu/nova-core/gfw.rs
index 9121f400046d..fb75dd10a172 100644
--- a/drivers/gpu/nova-core/gfw.rs
+++ b/drivers/gpu/nova-core/gfw.rs
@@ -19,7 +19,10 @@
//! Note that the devinit sequence also needs to run during suspend/resume.
use kernel::{
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ Io, //
+ },
prelude::*,
time::Delta, //
};
@@ -58,9 +61,11 @@ pub(crate) fn wait_gfw_boot_completion(bar: &Bar0) -> Result {
Ok(
// Check that FWSEC has lowered its protection level before reading the GFW_BOOT
// status.
- regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK::read(bar)
+ bar.read(regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK)
.read_protection_level0()
- && regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT::read(bar).completed(),
+ && bar
+ .read(regs::NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT)
+ .completed(),
)
},
|&gfw_booted| gfw_booted,
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 4f5cd64c2fce..6f49467e78ec 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -198,29 +198,41 @@ pub(crate) fn higher_bound(self) -> u64 {
// These scratch registers remain powered on even in a low-power state and have a designated group
// number.
-// Boot Sequence Interface (BSI) register used to determine
-// if GSP reload/resume has completed during the boot process.
-register!(NV_PGC6_BSI_SECURE_SCRATCH_14 @ 0x001180f8 {
- 26:26 boot_stage_3_handoff as bool;
-});
-
-// Privilege level mask register. It dictates whether the host CPU has privilege to access the
-// `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";
-});
-
-// 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_PGC6_AON_SECURE_SCRATCH_GROUP_05 @ 0x00118234[1] {});
-
-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)";
+io::register! {
+ /// Boot Sequence Interface (BSI) register used to determine
+ /// if GSP reload/resume has completed during the boot process.
+ pub(crate) NV_PGC6_BSI_SECURE_SCRATCH_14(u32) @ 0x001180f8 {
+ 26:26 boot_stage_3_handoff => bool;
}
-);
+
+ /// Privilege level mask register. It dictates whether the host CPU has privilege to access the
+ /// `PGC6_AON_SECURE_SCRATCH_GROUP_05` register (which it needs to read GFW_BOOT).
+ pub(crate) NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_PRIV_LEVEL_MASK(u32) @ 0x00118128 {
+ /// Set after FWSEC lowers its protection level.
+ 0:0 read_protection_level0 => bool;
+ }
+
+ /// 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.
+ pub(crate) NV_PGC6_AON_SECURE_SCRATCH_GROUP_05(u32)[1] @ 0x00118234 {}
+
+ /// Scratch group 05 register 0 used as GFW boot progress indicator.
+ pub(crate) NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT(u32)
+ => NV_PGC6_AON_SECURE_SCRATCH_GROUP_05[0] {
+ /// Progress of GFW boot (0xff means completed).
+ 7:0 progress;
+ }
+
+ pub(crate) NV_PGC6_AON_SECURE_SCRATCH_GROUP_42(u32) @ 0x001183a4 {
+ 31:0 value;
+ }
+
+ /// Scratch group 42 register used as framebuffer size.
+ pub(crate) NV_USABLE_FB_SIZE_IN_MB(u32) => NV_PGC6_AON_SECURE_SCRATCH_GROUP_42 {
+ /// Usable framebuffer size, in megabytes.
+ 31:0 value;
+ }
+}
impl NV_PGC6_AON_SECURE_SCRATCH_GROUP_05_0_GFW_BOOT {
/// Returns `true` if GFW boot is completed.
@@ -229,17 +241,6 @@ pub(crate) fn completed(self) -> bool {
}
}
-register!(NV_PGC6_AON_SECURE_SCRATCH_GROUP_42 @ 0x001183a4 {
- 31:0 value as u32;
-});
-
-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";
- }
-);
-
impl NV_USABLE_FB_SIZE_IN_MB {
/// Returns the usable framebuffer size, in bytes.
pub(crate) fn usable_fb_size(self) -> u64 {
--
2.53.0
^ permalink raw reply related
* [PATCH v3 05/10] gpu: nova-core: convert FUSE registers to kernel register macro
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
Convert all FUSE registers to use the kernel's register macro and update
the code accordingly.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon/hal/ga102.rs | 17 +++++++++----
drivers/gpu/nova-core/fb/hal/ga100.rs | 3 ++-
drivers/gpu/nova-core/fb/hal/tu102.rs | 3 ++-
drivers/gpu/nova-core/regs.rs | 40 +++++++++++++++++++------------
4 files changed, 41 insertions(+), 22 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon/hal/ga102.rs b/drivers/gpu/nova-core/falcon/hal/ga102.rs
index 8f62df10da0a..cbdf36bad633 100644
--- a/drivers/gpu/nova-core/falcon/hal/ga102.rs
+++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs
@@ -4,7 +4,11 @@
use kernel::{
device,
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ register::Array,
+ Io, //
+ },
prelude::*,
time::Delta, //
};
@@ -60,12 +64,15 @@ 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 {
- regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::read(bar, ucode_idx).data()
+ let reg_fuse_version: u16 = if engine_id_mask & 0x0001 != 0 {
+ bar.read(regs::NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION::at(ucode_idx))
+ .data()
} else if engine_id_mask & 0x0004 != 0 {
- regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::read(bar, ucode_idx).data()
+ bar.read(regs::NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION::at(ucode_idx))
+ .data()
} else if engine_id_mask & 0x0400 != 0 {
- regs::NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION::read(bar, ucode_idx).data()
+ bar.read(regs::NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION::at(ucode_idx))
+ .data()
} else {
dev_err!(dev, "unexpected engine_id_mask {:#x}\n", engine_id_mask);
return Err(EINVAL);
diff --git a/drivers/gpu/nova-core/fb/hal/ga100.rs b/drivers/gpu/nova-core/fb/hal/ga100.rs
index 629588c75778..1c03783cddef 100644
--- a/drivers/gpu/nova-core/fb/hal/ga100.rs
+++ b/drivers/gpu/nova-core/fb/hal/ga100.rs
@@ -40,7 +40,8 @@ pub(super) fn write_sysmem_flush_page_ga100(bar: &Bar0, addr: u64) {
}
pub(super) fn display_enabled_ga100(bar: &Bar0) -> bool {
- !regs::ga100::NV_FUSE_STATUS_OPT_DISPLAY::read(bar).display_disabled()
+ !bar.read(regs::ga100::NV_FUSE_STATUS_OPT_DISPLAY)
+ .display_disabled()
}
/// Shift applied to the sysmem address before it is written into
diff --git a/drivers/gpu/nova-core/fb/hal/tu102.rs b/drivers/gpu/nova-core/fb/hal/tu102.rs
index 515d50872224..281bb796e198 100644
--- a/drivers/gpu/nova-core/fb/hal/tu102.rs
+++ b/drivers/gpu/nova-core/fb/hal/tu102.rs
@@ -29,7 +29,8 @@ pub(super) fn write_sysmem_flush_page_gm107(bar: &Bar0, addr: u64) -> Result {
}
pub(super) fn display_enabled_gm107(bar: &Bar0) -> bool {
- !regs::gm107::NV_FUSE_STATUS_OPT_DISPLAY::read(bar).display_disabled()
+ !bar.read(regs::gm107::NV_FUSE_STATUS_OPT_DISPLAY)
+ .display_disabled()
}
pub(super) fn vidmem_size_gp102(bar: &Bar0) -> u64 {
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 6f49467e78ec..61a8dba22d88 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -270,17 +270,19 @@ 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;
-});
+io::register! {
+ pub(crate) NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION(u32)[NV_FUSE_OPT_FPF_SIZE] @ 0x00824100 {
+ 15:0 data => u16;
+ }
-register!(NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION @ 0x00824140[NV_FUSE_OPT_FPF_SIZE] {
- 15:0 data as u16;
-});
+ pub(crate) NV_FUSE_OPT_FPF_SEC2_UCODE1_VERSION(u32)[NV_FUSE_OPT_FPF_SIZE] @ 0x00824140 {
+ 15:0 data => u16;
+ }
-register!(NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION @ 0x008241c0[NV_FUSE_OPT_FPF_SIZE] {
- 15:0 data as u16;
-});
+ pub(crate) NV_FUSE_OPT_FPF_GSP_UCODE1_VERSION(u32)[NV_FUSE_OPT_FPF_SIZE] @ 0x008241c0 {
+ 15:0 data => u16;
+ }
+}
// PFALCON
@@ -491,17 +493,25 @@ pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) {
// only be used in HAL modules.
pub(crate) mod gm107 {
+ use kernel::io;
+
// FUSE
- register!(NV_FUSE_STATUS_OPT_DISPLAY @ 0x00021c04 {
- 0:0 display_disabled as bool;
- });
+ io::register! {
+ pub(crate) NV_FUSE_STATUS_OPT_DISPLAY(u32) @ 0x00021c04 {
+ 0:0 display_disabled => bool;
+ }
+ }
}
pub(crate) mod ga100 {
+ use kernel::io;
+
// FUSE
- register!(NV_FUSE_STATUS_OPT_DISPLAY @ 0x00820c04 {
- 0:0 display_disabled as bool;
- });
+ io::register! {
+ pub(crate) NV_FUSE_STATUS_OPT_DISPLAY(u32) @ 0x00820c04 {
+ 0:0 display_disabled => bool;
+ }
+ }
}
--
2.53.0
^ permalink raw reply related
* [PATCH v3 06/10] gpu: nova-core: convert PDISP registers to kernel register macro
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
Convert all PDISP registers to use the kernel's register macro and
update the code accordingly.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Reviewed-by: Joel Fernandes <joelagnelf@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/fb.rs | 6 +++++-
drivers/gpu/nova-core/regs.rs | 12 ++++++++----
2 files changed, 13 insertions(+), 5 deletions(-)
diff --git a/drivers/gpu/nova-core/fb.rs b/drivers/gpu/nova-core/fb.rs
index 6536d0035cb1..62fc90fa6a84 100644
--- a/drivers/gpu/nova-core/fb.rs
+++ b/drivers/gpu/nova-core/fb.rs
@@ -8,6 +8,7 @@
use kernel::{
device,
fmt,
+ io::Io,
prelude::*,
ptr::{
Alignable,
@@ -189,7 +190,10 @@ pub(crate) fn new(chipset: Chipset, bar: &Bar0, gsp_fw: &GspFirmware) -> Result<
let base = fb.end - NV_PRAMIN_SIZE;
if hal.supports_display(bar) {
- match regs::NV_PDISP_VGA_WORKSPACE_BASE::read(bar).vga_workspace_addr() {
+ match bar
+ .read(regs::NV_PDISP_VGA_WORKSPACE_BASE)
+ .vga_workspace_addr()
+ {
Some(addr) => {
if addr < base {
const VBIOS_WORKSPACE_SIZE: u64 = usize_as_u64(SZ_128K);
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 61a8dba22d88..b051d5568cd8 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -250,10 +250,14 @@ 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";
-});
+io::register! {
+ pub(crate) NV_PDISP_VGA_WORKSPACE_BASE(u32) @ 0x00625f04 {
+ /// VGA workspace base address divided by 0x10000.
+ 31:8 addr;
+ /// Set if the `addr` field is valid.
+ 3:3 status_valid => bool;
+ }
+}
impl NV_PDISP_VGA_WORKSPACE_BASE {
/// Returns the base address of the VGA workspace, or `None` if none exists.
--
2.53.0
^ permalink raw reply related
* [PATCH v3 07/10] gpu: nova-core: falcon: introduce `bounded_enum` macro
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
Introduce a powered-up version of our ad-hoc `impl_from_enum_to_u8`
macro that allows the definition of an enum type associated to a
`Bounded` of a given width, and provides the `From` and `TryFrom`
implementations required to use that enum as a register field member.
The next patch will make use of it to convert all falcon registers to
the kernel register macro.
The macro is unused in this patch: it is introduced ahead-of-time to
avoid diff mingling in the next patch that would make it difficult to
review.
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 82 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 5a4f7fc85160..5221e4476f90 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -54,6 +54,88 @@ fn from(value: $enum_type) -> Self {
};
}
+/// Creates an enum type associated to a `Bounded`, with a `From` conversion to the associated
+/// `Bounded` and either a `TryFrom` or `From` converting from the associated `Bounded`.
+// TODO[FPRI]: This is a temporary solution to be replaced with the corresponding derive macros
+// once they land.
+#[expect(unused)]
+macro_rules! bounded_enum {
+ (
+ $(#[doc = $enum_doc:expr])*
+ enum $enum_type:ident with $from_impl:ident<Bounded<$width:ty, $length:literal>> {
+ $( $(#[doc = $variant_doc:expr])* $variant:ident = $value:expr),* $(,)*
+ }
+ ) => {
+ $(#[doc = $enum_doc])*
+ #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+ pub(crate) enum $enum_type {
+ $(
+ $(#[doc = $variant_doc])*
+ $variant = $value
+ ),*
+ }
+
+ impl From<$enum_type> for Bounded<$width, $length> {
+ fn from(value: $enum_type) -> Self {
+ match value {
+ $($enum_type::$variant => Bounded::<$width, _>::new::<$value>()),*
+ }
+ }
+ }
+
+ bounded_enum!(@impl_from $enum_type with $from_impl<Bounded<$width, $length>> {
+ $($variant = $value),*
+ });
+ };
+
+ // `TryFrom` implementation from associated `Bounded` to enum type.
+ (@impl_from $enum_type:ident with TryFrom<Bounded<$width:ty, $length:literal>> {
+ $($variant:ident = $value:expr),* $(,)*
+ }) => {
+ impl TryFrom<Bounded<$width, $length>> for $enum_type {
+ type Error = Error;
+
+ fn try_from(value: Bounded<$width, $length>) -> Result<Self> {
+ match value.get() {
+ $(
+ $value => Ok($enum_type::$variant),
+ )*
+ _ => Err(EINVAL),
+ }
+ }
+ }
+ };
+
+ // `From` implementation from associated `Bounded` to enum type. Triggers a `build_error` if
+ // all possible values of the `Bounded` are not covered by the enum type.
+ (@impl_from $enum_type:ident with From<Bounded<$width:ty, $length:literal>> {
+ $($variant:ident = $value:expr),* $(,)*
+ }) => {
+ impl From<Bounded<$width, $length>> for $enum_type {
+ fn from(value: Bounded<$width, $length>) -> Self {
+ $(
+ // Ensure all enum values fit into the `Bounded` type.
+ const { assert!(
+ $value < (1 << $length),
+ "Enum variant doesn't fit into assigned `Bounded` type."
+ ); }
+ )*
+
+ // Makes the compiler optimizer aware of the possible range of values.
+ let value = value.get() & ((1 << $length) - 1);
+ match value {
+ $(
+ $value => $enum_type::$variant,
+ )*
+ // We land here if the match didn't cover all possible values for the
+ // `Bounded`.
+ _ => build_error!("Enum doesn't cover all values of the `Bounded` type."),
+ }
+ }
+ }
+ }
+}
+
/// Revision number of a falcon core, used in the [`crate::regs::NV_PFALCON_FALCON_HWCFG1`]
/// register.
#[repr(u8)]
--
2.53.0
^ permalink raw reply related
* [PATCH v3 09/10] gpu: nova-core: remove `io::` qualifier to register macro invocations
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
The kernel's `register` macro would clash with nova-core's own version
if it was imported directly, so it was accessed through its `io` module
during the conversion phase.
Now that nova-core's `register` macro doesn't exist anymore, we can
import and use it directly without risk of name collision.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/regs.rs | 30 +++++++++++++++---------------
1 file changed, 15 insertions(+), 15 deletions(-)
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index 87c2977ba6e4..2f171a4ff9ba 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -2,7 +2,7 @@
use kernel::{
io::{
- self,
+ register,
register::WithBase,
Io, //
},
@@ -35,7 +35,7 @@
// PMC
-io::register! {
+register! {
/// Basic revision information about the GPU.
pub(crate) NV_PMC_BOOT_0(u32) @ 0x00000000 {
/// Lower bits of the architecture.
@@ -106,7 +106,7 @@ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
// PBUS
-io::register! {
+register! {
pub(crate) NV_PBUS_SW_SCRATCH(u32)[64] @ 0x00001400 {}
/// Scratch register 0xe used as FRTS firmware error code.
@@ -117,7 +117,7 @@ fn fmt(&self, f: &mut kernel::fmt::Formatter<'_>) -> kernel::fmt::Result {
// PFB
-io::register! {
+register! {
/// Low bits of the physical system memory address used by the GPU to perform sysmembar
/// operations (see [`crate::fb::SysmemFlush`]).
pub(crate) NV_PFB_NISO_FLUSH_SYSMEM_ADDR(u32) @ 0x00100c10 {
@@ -180,7 +180,7 @@ pub(crate) fn higher_bound(self) -> u64 {
// PGSP
-io::register! {
+register! {
pub(crate) NV_PGSP_QUEUE_HEAD(u32) @ 0x00110c00 {
31:0 address;
}
@@ -195,7 +195,7 @@ pub(crate) fn higher_bound(self) -> u64 {
// These scratch registers remain powered on even in a low-power state and have a designated group
// number.
-io::register! {
+register! {
/// Boot Sequence Interface (BSI) register used to determine
/// if GSP reload/resume has completed during the boot process.
pub(crate) NV_PGC6_BSI_SECURE_SCRATCH_14(u32) @ 0x001180f8 {
@@ -247,7 +247,7 @@ pub(crate) fn usable_fb_size(self) -> u64 {
// PDISP
-io::register! {
+register! {
pub(crate) NV_PDISP_VGA_WORKSPACE_BASE(u32) @ 0x00625f04 {
/// VGA workspace base address divided by 0x10000.
31:8 addr;
@@ -271,7 +271,7 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> {
pub(crate) const NV_FUSE_OPT_FPF_SIZE: usize = 16;
-io::register! {
+register! {
pub(crate) NV_FUSE_OPT_FPF_NVDEC_UCODE1_VERSION(u32)[NV_FUSE_OPT_FPF_SIZE] @ 0x00824100 {
15:0 data => u16;
}
@@ -287,7 +287,7 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> {
// PFALCON
-io::register! {
+register! {
pub(crate) NV_PFALCON_FALCON_IRQSCLR(u32) @ PFalconBase + 0x00000004 {
6:6 swgen0 => bool;
4:4 halt => bool;
@@ -468,7 +468,7 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
/* PFALCON2 */
-io::register! {
+register! {
pub(crate) NV_PFALCON2_FALCON_MOD_SEL(u32) @ PFalcon2Base + 0x00000180 {
7:0 algo ?=> FalconModSelAlgo;
}
@@ -490,7 +490,7 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
// PRISCV
-io::register! {
+register! {
/// RISC-V status register for debug (Turing and GA100 only).
/// Reflects current RISC-V core status.
pub(crate) NV_PRISCV_RISCV_CORE_SWITCH_RISCV_STATUS(u32) @ PFalcon2Base + 0x00000240 {
@@ -516,11 +516,11 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
// only be used in HAL modules.
pub(crate) mod gm107 {
- use kernel::io;
+ use kernel::io::register;
// FUSE
- io::register! {
+ register! {
pub(crate) NV_FUSE_STATUS_OPT_DISPLAY(u32) @ 0x00021c04 {
0:0 display_disabled => bool;
}
@@ -528,11 +528,11 @@ pub(crate) mod gm107 {
}
pub(crate) mod ga100 {
- use kernel::io;
+ use kernel::io::register;
// FUSE
- io::register! {
+ register! {
pub(crate) NV_FUSE_STATUS_OPT_DISPLAY(u32) @ 0x00820c04 {
0:0 display_disabled => bool;
}
--
2.53.0
^ permalink raw reply related
* [PATCH v3 08/10] gpu: nova-core: convert falcon registers to kernel register macro
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
Convert all PFALCON, PFALCON2 and PRISCV registers to use the kernel's
register macro and update the code accordingly.
Because they rely on the same types to implement relative registers,
they need to be updated in lockstep.
nova-core's local register macro is now unused, so remove it.
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
drivers/gpu/nova-core/falcon.rs | 530 ++++++---------
drivers/gpu/nova-core/falcon/gsp.rs | 22 +-
drivers/gpu/nova-core/falcon/hal/ga102.rs | 55 +-
drivers/gpu/nova-core/falcon/hal/tu102.rs | 12 +-
drivers/gpu/nova-core/falcon/sec2.rs | 17 +-
drivers/gpu/nova-core/firmware/fwsec/bootloader.rs | 19 +-
drivers/gpu/nova-core/regs.rs | 353 +++++-----
drivers/gpu/nova-core/regs/macros.rs | 739 ---------------------
8 files changed, 454 insertions(+), 1293 deletions(-)
diff --git a/drivers/gpu/nova-core/falcon.rs b/drivers/gpu/nova-core/falcon.rs
index 5221e4476f90..f0b76289c311 100644
--- a/drivers/gpu/nova-core/falcon.rs
+++ b/drivers/gpu/nova-core/falcon.rs
@@ -15,8 +15,13 @@
},
io::{
poll::read_poll_timeout,
- Io, //
+ register::{
+ RegisterBase,
+ WithBase, //
+ },
+ Io,
},
+ num::Bounded,
prelude::*,
sync::aref::ARef,
time::Delta,
@@ -33,7 +38,6 @@
IntoSafeCast, //
},
regs,
- regs::macros::RegisterBase, //
};
pub(crate) mod gsp;
@@ -43,22 +47,10 @@
/// Alignment (in bytes) of falcon memory blocks.
pub(crate) const MEM_BLOCK_ALIGNMENT: usize = 256;
-// TODO[FPRI]: Replace with `ToPrimitive`.
-macro_rules! impl_from_enum_to_u8 {
- ($enum_type:ty) => {
- impl From<$enum_type> for u8 {
- fn from(value: $enum_type) -> Self {
- value as u8
- }
- }
- };
-}
-
/// Creates an enum type associated to a `Bounded`, with a `From` conversion to the associated
/// `Bounded` and either a `TryFrom` or `From` converting from the associated `Bounded`.
// TODO[FPRI]: This is a temporary solution to be replaced with the corresponding derive macros
// once they land.
-#[expect(unused)]
macro_rules! bounded_enum {
(
$(#[doc = $enum_doc:expr])*
@@ -78,7 +70,7 @@ pub(crate) enum $enum_type {
impl From<$enum_type> for Bounded<$width, $length> {
fn from(value: $enum_type) -> Self {
match value {
- $($enum_type::$variant => Bounded::<$width, _>::new::<$value>()),*
+ $($enum_type::$variant => Bounded::<$width, _>::new::<{ $value }>()),*
}
}
}
@@ -106,20 +98,14 @@ fn try_from(value: Bounded<$width, $length>) -> Result<Self> {
}
};
- // `From` implementation from associated `Bounded` to enum type. Triggers a `build_error` if
+ // `From` implementation from associated `Bounded` to enum type. Triggers a built-time error if
// all possible values of the `Bounded` are not covered by the enum type.
(@impl_from $enum_type:ident with From<Bounded<$width:ty, $length:literal>> {
$($variant:ident = $value:expr),* $(,)*
}) => {
impl From<Bounded<$width, $length>> for $enum_type {
fn from(value: Bounded<$width, $length>) -> Self {
- $(
- // Ensure all enum values fit into the `Bounded` type.
- const { assert!(
- $value < (1 << $length),
- "Enum variant doesn't fit into assigned `Bounded` type."
- ); }
- )*
+ const MAX: $width = 1 << $length;
// Makes the compiler optimizer aware of the possible range of values.
let value = value.get() & ((1 << $length) - 1);
@@ -127,201 +113,90 @@ fn from(value: Bounded<$width, $length>) -> Self {
$(
$value => $enum_type::$variant,
)*
- // We land here if the match didn't cover all possible values for the
- // `Bounded`.
- _ => build_error!("Enum doesn't cover all values of the `Bounded` type."),
+ // PANIC: we cannot reach this arm as all possible variants are handled by the
+ // match arms above. It is here to make the compiler complain if `$enum_type`
+ // does not cover all values of the `0..MAX` range.
+ MAX.. => unreachable!(),
}
}
}
}
}
-/// Revision number of a falcon core, used in the [`crate::regs::NV_PFALCON_FALCON_HWCFG1`]
-/// register.
-#[repr(u8)]
-#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub(crate) enum FalconCoreRev {
- #[default]
- Rev1 = 1,
- Rev2 = 2,
- Rev3 = 3,
- Rev4 = 4,
- Rev5 = 5,
- Rev6 = 6,
- Rev7 = 7,
-}
-impl_from_enum_to_u8!(FalconCoreRev);
-
-// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconCoreRev {
- type Error = Error;
-
- fn try_from(value: u8) -> Result<Self> {
- use FalconCoreRev::*;
-
- let rev = match value {
- 1 => Rev1,
- 2 => Rev2,
- 3 => Rev3,
- 4 => Rev4,
- 5 => Rev5,
- 6 => Rev6,
- 7 => Rev7,
- _ => return Err(EINVAL),
- };
-
- Ok(rev)
+bounded_enum! {
+ /// Revision number of a falcon core, used in the [`crate::regs::NV_PFALCON_FALCON_HWCFG1`]
+ /// register.
+ enum FalconCoreRev with TryFrom<Bounded<u32, 4>> {
+ Rev1 = 1,
+ Rev2 = 2,
+ Rev3 = 3,
+ Rev4 = 4,
+ Rev5 = 5,
+ Rev6 = 6,
+ Rev7 = 7,
}
}
-/// Revision subversion number of a falcon core, used in the
-/// [`crate::regs::NV_PFALCON_FALCON_HWCFG1`] register.
-#[repr(u8)]
-#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
-pub(crate) enum FalconCoreRevSubversion {
- #[default]
- Subversion0 = 0,
- Subversion1 = 1,
- Subversion2 = 2,
- Subversion3 = 3,
-}
-impl_from_enum_to_u8!(FalconCoreRevSubversion);
-
-// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconCoreRevSubversion {
- type Error = Error;
-
- fn try_from(value: u8) -> Result<Self> {
- use FalconCoreRevSubversion::*;
-
- let sub_version = match value & 0b11 {
- 0 => Subversion0,
- 1 => Subversion1,
- 2 => Subversion2,
- 3 => Subversion3,
- _ => return Err(EINVAL),
- };
-
- Ok(sub_version)
+bounded_enum! {
+ /// Revision subversion number of a falcon core, used in the
+ /// [`crate::regs::NV_PFALCON_FALCON_HWCFG1`] register.
+ enum FalconCoreRevSubversion with From<Bounded<u32, 2>> {
+ Subversion0 = 0,
+ Subversion1 = 1,
+ Subversion2 = 2,
+ Subversion3 = 3,
}
}
-/// Security model of a falcon core, used in the [`crate::regs::NV_PFALCON_FALCON_HWCFG1`]
-/// register.
-#[repr(u8)]
-#[derive(Debug, Default, Copy, Clone)]
-/// Security mode of the Falcon microprocessor.
-///
-/// See `falcon.rst` for more details.
-pub(crate) enum FalconSecurityModel {
- /// Non-Secure: runs unsigned code without privileges.
- #[default]
- None = 0,
- /// Light-Secured (LS): Runs signed code with some privileges.
- /// Entry into this mode is only possible from 'Heavy-secure' mode, which verifies the code's
- /// signature.
+bounded_enum! {
+ /// Security mode of the Falcon microprocessor.
///
- /// Also known as Low-Secure, Privilege Level 2 or PL2.
- Light = 2,
- /// Heavy-Secured (HS): Runs signed code with full privileges.
- /// The code's signature is verified by the Falcon Boot ROM (BROM).
- ///
- /// Also known as High-Secure, Privilege Level 3 or PL3.
- Heavy = 3,
-}
-impl_from_enum_to_u8!(FalconSecurityModel);
-
-// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconSecurityModel {
- type Error = Error;
-
- fn try_from(value: u8) -> Result<Self> {
- use FalconSecurityModel::*;
-
- let sec_model = match value {
- 0 => None,
- 2 => Light,
- 3 => Heavy,
- _ => return Err(EINVAL),
- };
-
- Ok(sec_model)
+ /// See `falcon.rst` for more details.
+ enum FalconSecurityModel with TryFrom<Bounded<u32, 2>> {
+ /// Non-Secure: runs unsigned code without privileges.
+ None = 0,
+ /// Light-Secured (LS): Runs signed code with some privileges.
+ /// Entry into this mode is only possible from 'Heavy-secure' mode, which verifies the
+ /// code's signature.
+ ///
+ /// Also known as Low-Secure, Privilege Level 2 or PL2.
+ Light = 2,
+ /// Heavy-Secured (HS): Runs signed code with full privileges.
+ /// The code's signature is verified by the Falcon Boot ROM (BROM).
+ ///
+ /// Also known as High-Secure, Privilege Level 3 or PL3.
+ Heavy = 3,
}
}
-/// Signing algorithm for a given firmware, used in the [`crate::regs::NV_PFALCON2_FALCON_MOD_SEL`]
-/// register. It is passed to the Falcon Boot ROM (BROM) as a parameter.
-#[repr(u8)]
-#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
-pub(crate) enum FalconModSelAlgo {
- /// AES.
- #[expect(dead_code)]
- Aes = 0,
- /// RSA3K.
- #[default]
- Rsa3k = 1,
-}
-impl_from_enum_to_u8!(FalconModSelAlgo);
-
-// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconModSelAlgo {
- type Error = Error;
-
- fn try_from(value: u8) -> Result<Self> {
- match value {
- 1 => Ok(FalconModSelAlgo::Rsa3k),
- _ => Err(EINVAL),
- }
+bounded_enum! {
+ /// Signing algorithm for a given firmware, used in the
+ /// [`crate::regs::NV_PFALCON2_FALCON_MOD_SEL`] register. It is passed to the Falcon Boot ROM
+ /// (BROM) as a parameter.
+ enum FalconModSelAlgo with TryFrom<Bounded<u32, 8>> {
+ /// AES.
+ Aes = 0,
+ /// RSA3K.
+ Rsa3k = 1,
}
}
-/// Valid values for the `size` field of the [`crate::regs::NV_PFALCON_FALCON_DMATRFCMD`] register.
-#[repr(u8)]
-#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)]
-pub(crate) enum DmaTrfCmdSize {
- /// 256 bytes transfer.
- #[default]
- Size256B = 0x6,
-}
-impl_from_enum_to_u8!(DmaTrfCmdSize);
-
-// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for DmaTrfCmdSize {
- type Error = Error;
-
- fn try_from(value: u8) -> Result<Self> {
- match value {
- 0x6 => Ok(Self::Size256B),
- _ => Err(EINVAL),
- }
+bounded_enum! {
+ /// Valid values for the `size` field of the [`crate::regs::NV_PFALCON_FALCON_DMATRFCMD`]
+ /// register.
+ enum DmaTrfCmdSize with TryFrom<Bounded<u32, 3>> {
+ /// 256 bytes transfer.
+ Size256B = 0x6,
}
}
-/// Currently active core on a dual falcon/riscv (Peregrine) controller.
-#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
-pub(crate) enum PeregrineCoreSelect {
- /// Falcon core is active.
- #[default]
- Falcon = 0,
- /// RISC-V core is active.
- Riscv = 1,
-}
-
-impl From<bool> for PeregrineCoreSelect {
- fn from(value: bool) -> Self {
- match 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,
- }
+bounded_enum! {
+ /// Currently active core on a dual falcon/riscv (Peregrine) controller.
+ enum PeregrineCoreSelect with From<Bounded<u32, 1>> {
+ /// Falcon core is active.
+ Falcon = 0,
+ /// RISC-V core is active.
+ Riscv = 1,
}
}
@@ -337,64 +212,27 @@ pub(crate) enum FalconMem {
Dmem,
}
-/// Defines the Framebuffer Interface (FBIF) aperture type.
-/// This determines the memory type for external memory access during a DMA transfer, which is
-/// performed by the Falcon's Framebuffer DMA (FBDMA) engine. See falcon.rst for more details.
-#[derive(Debug, Clone, Default)]
-pub(crate) enum FalconFbifTarget {
- /// VRAM.
- #[default]
- /// Local Framebuffer (GPU's VRAM memory).
- LocalFb = 0,
- /// Coherent system memory (System DRAM).
- CoherentSysmem = 1,
- /// Non-coherent system memory (System DRAM).
- NoncoherentSysmem = 2,
-}
-impl_from_enum_to_u8!(FalconFbifTarget);
-
-// TODO[FPRI]: replace with `FromPrimitive`.
-impl TryFrom<u8> for FalconFbifTarget {
- type Error = Error;
-
- fn try_from(value: u8) -> Result<Self> {
- let res = match value {
- 0 => Self::LocalFb,
- 1 => Self::CoherentSysmem,
- 2 => Self::NoncoherentSysmem,
- _ => return Err(EINVAL),
- };
-
- Ok(res)
+bounded_enum! {
+ /// Defines the Framebuffer Interface (FBIF) aperture type.
+ /// This determines the memory type for external memory access during a DMA transfer, which is
+ /// performed by the Falcon's Framebuffer DMA (FBDMA) engine. See falcon.rst for more details.
+ enum FalconFbifTarget with TryFrom<Bounded<u32, 2>> {
+ /// Local Framebuffer (GPU's VRAM memory).
+ LocalFb = 0,
+ /// Coherent system memory (System DRAM).
+ CoherentSysmem = 1,
+ /// Non-coherent system memory (System DRAM).
+ NoncoherentSysmem = 2,
}
}
-/// Type of memory addresses to use.
-#[derive(Debug, Clone, Default)]
-pub(crate) enum FalconFbifMemType {
- /// Virtual memory addresses.
- #[default]
- Virtual = 0,
- /// Physical memory addresses.
- Physical = 1,
-}
-
-/// Conversion from a single-bit register field.
-impl From<bool> for FalconFbifMemType {
- fn from(value: bool) -> Self {
- match 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,
- }
+bounded_enum! {
+ /// Type of memory addresses to use.
+ enum FalconFbifMemType with From<Bounded<u32, 1>> {
+ /// Virtual memory addresses.
+ Virtual = 0,
+ /// Physical memory addresses.
+ Physical = 1,
}
}
@@ -406,13 +244,10 @@ fn from(value: FalconFbifMemType) -> Self {
/// Trait defining the parameters of a given Falcon engine.
///
-/// Each engine provides one base for `PFALCON` and `PFALCON2` registers. The `ID` constant is used
-/// to identify a given Falcon instance with register I/O methods.
+/// Each engine provides one base for `PFALCON` and `PFALCON2` registers.
pub(crate) trait FalconEngine:
Send + Sync + RegisterBase<PFalconBase> + RegisterBase<PFalcon2Base> + Sized
{
- /// Singleton of the engine, used to identify it with register I/O methods.
- const ID: Self;
}
/// Represents a portion of the firmware to be loaded into a particular memory (e.g. IMEM or DMEM)
@@ -606,8 +441,14 @@ pub(crate) fn new(dev: &device::Device, chipset: Chipset) -> Result<Self> {
/// Resets DMA-related registers.
pub(crate) fn dma_reset(&self, bar: &Bar0) {
- regs::NV_PFALCON_FBIF_CTL::update(bar, &E::ID, |v| v.set_allow_phys_no_ctx(true));
- regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID);
+ bar.update(regs::NV_PFALCON_FBIF_CTL::of::<E>(), |v| {
+ v.with_allow_phys_no_ctx(true)
+ });
+
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMACTL::zeroed(),
+ );
}
/// Reset the controller, select the falcon core, and wait for memory scrubbing to complete.
@@ -616,9 +457,10 @@ pub(crate) fn reset(&self, bar: &Bar0) -> Result {
self.hal.select_core(self, bar)?;
self.hal.reset_wait_mem_scrubbing(bar)?;
- regs::NV_PFALCON_FALCON_RM::default()
- .set_value(bar.read(regs::NV_PMC_BOOT_0).into())
- .write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_RM::from(bar.read(regs::NV_PMC_BOOT_0).into_raw()),
+ );
Ok(())
}
@@ -636,25 +478,27 @@ fn pio_wr_imem_slice(&self, bar: &Bar0, load_offsets: FalconPioImemLoadTarget<'_
return Err(EINVAL);
}
- regs::NV_PFALCON_FALCON_IMEMC::default()
- .set_secure(load_offsets.secure)
- .set_aincw(true)
- .set_offs(load_offsets.dst_start)
- .write(bar, &E::ID, Self::PIO_PORT);
+ bar.write(
+ WithBase::of::<E>().at(Self::PIO_PORT),
+ regs::NV_PFALCON_FALCON_IMEMC::zeroed()
+ .with_secure(load_offsets.secure)
+ .with_aincw(true)
+ .with_offs(load_offsets.dst_start),
+ );
for (n, block) in load_offsets.data.chunks(MEM_BLOCK_ALIGNMENT).enumerate() {
let n = u16::try_from(n)?;
let tag: u16 = load_offsets.start_tag.checked_add(n).ok_or(ERANGE)?;
- regs::NV_PFALCON_FALCON_IMEMT::default().set_tag(tag).write(
- bar,
- &E::ID,
- Self::PIO_PORT,
+ bar.write(
+ WithBase::of::<E>().at(Self::PIO_PORT),
+ regs::NV_PFALCON_FALCON_IMEMT::zeroed().with_tag(tag),
);
for word in block.chunks_exact(4) {
let w = [word[0], word[1], word[2], word[3]];
- regs::NV_PFALCON_FALCON_IMEMD::default()
- .set_data(u32::from_le_bytes(w))
- .write(bar, &E::ID, Self::PIO_PORT);
+ bar.write(
+ WithBase::of::<E>().at(Self::PIO_PORT),
+ regs::NV_PFALCON_FALCON_IMEMD::zeroed().with_data(u32::from_le_bytes(w)),
+ );
}
}
@@ -671,16 +515,19 @@ fn pio_wr_dmem_slice(&self, bar: &Bar0, load_offsets: FalconPioDmemLoadTarget<'_
return Err(EINVAL);
}
- regs::NV_PFALCON_FALCON_DMEMC::default()
- .set_aincw(true)
- .set_offs(load_offsets.dst_start)
- .write(bar, &E::ID, Self::PIO_PORT);
+ bar.write(
+ WithBase::of::<E>().at(Self::PIO_PORT),
+ regs::NV_PFALCON_FALCON_DMEMC::zeroed()
+ .with_aincw(true)
+ .with_offs(load_offsets.dst_start),
+ );
for word in load_offsets.data.chunks_exact(4) {
let w = [word[0], word[1], word[2], word[3]];
- regs::NV_PFALCON_FALCON_DMEMD::default()
- .set_data(u32::from_le_bytes(w))
- .write(bar, &E::ID, Self::PIO_PORT);
+ bar.write(
+ WithBase::of::<E>().at(Self::PIO_PORT),
+ regs::NV_PFALCON_FALCON_DMEMD::zeroed().with_data(u32::from_le_bytes(w)),
+ );
}
Ok(())
@@ -692,11 +539,14 @@ pub(crate) fn pio_load<F: FalconFirmware<Target = E> + FalconPioLoadable>(
bar: &Bar0,
fw: &F,
) -> Result {
- regs::NV_PFALCON_FBIF_CTL::read(bar, &E::ID)
- .set_allow_phys_no_ctx(true)
- .write(bar, &E::ID);
+ bar.update(regs::NV_PFALCON_FBIF_CTL::of::<E>(), |v| {
+ v.with_allow_phys_no_ctx(true)
+ });
- regs::NV_PFALCON_FALCON_DMACTL::default().write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMACTL::zeroed(),
+ );
if let Some(imem_ns) = fw.imem_ns_load_params() {
self.pio_wr_imem_slice(bar, imem_ns)?;
@@ -708,9 +558,10 @@ pub(crate) fn pio_load<F: FalconFirmware<Target = E> + FalconPioLoadable>(
self.hal.program_brom(self, bar, &fw.brom_params())?;
- regs::NV_PFALCON_FALCON_BOOTVEC::default()
- .set_value(fw.boot_addr())
- .write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_BOOTVEC::zeroed().with_value(fw.boot_addr()),
+ );
Ok(())
}
@@ -779,36 +630,42 @@ fn dma_wr(
// Set up the base source DMA address.
- regs::NV_PFALCON_FALCON_DMATRFBASE::default()
- // CAST: `as u32` is used on purpose since we do want to strip the upper bits, which
- // will be written to `NV_PFALCON_FALCON_DMATRFBASE1`.
- .set_base((dma_start >> 8) as u32)
- .write(bar, &E::ID);
- regs::NV_PFALCON_FALCON_DMATRFBASE1::default()
- // CAST: `as u16` is used on purpose since the remaining bits are guaranteed to fit
- // within a `u16`.
- .set_base((dma_start >> 40) as u16)
- .write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMATRFBASE::zeroed().with_base(
+ // CAST: `as u32` is used on purpose since we do want to strip the upper bits,
+ // which will be written to `NV_PFALCON_FALCON_DMATRFBASE1`.
+ (dma_start >> 8) as u32,
+ ),
+ );
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMATRFBASE1::zeroed().try_with_base(dma_start >> 40)?,
+ );
- let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::default()
- .set_size(DmaTrfCmdSize::Size256B)
+ let cmd = regs::NV_PFALCON_FALCON_DMATRFCMD::zeroed()
+ .with_size(DmaTrfCmdSize::Size256B)
.with_falcon_mem(target_mem);
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)
- .write(bar, &E::ID);
- regs::NV_PFALCON_FALCON_DMATRFFBOFFS::default()
- .set_offs(src_start + pos)
- .write(bar, &E::ID);
- cmd.write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMATRFMOFFS::zeroed()
+ .try_with_offs(load_offsets.dst_start + pos)?,
+ );
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_DMATRFFBOFFS::zeroed().with_offs(src_start + pos),
+ );
+
+ bar.write(WithBase::of::<E>(), cmd);
// Wait for the transfer to complete.
// TIMEOUT: arbitrarily large value, no DMA transfer to the falcon's small memories
// should ever take that long.
read_poll_timeout(
- || Ok(regs::NV_PFALCON_FALCON_DMATRFCMD::read(bar, &E::ID)),
+ || Ok(bar.read(regs::NV_PFALCON_FALCON_DMATRFCMD::of::<E>())),
|r| r.idle(),
Delta::ZERO,
Delta::from_secs(2),
@@ -829,9 +686,9 @@ fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>(
let dma_obj = DmaObject::from_data(dev, fw.as_slice())?;
self.dma_reset(bar);
- regs::NV_PFALCON_FBIF_TRANSCFG::update(bar, &E::ID, 0, |v| {
- v.set_target(FalconFbifTarget::CoherentSysmem)
- .set_mem_type(FalconFbifMemType::Physical)
+ bar.update(regs::NV_PFALCON_FBIF_TRANSCFG::of::<E>().at(0), |v| {
+ v.with_target(FalconFbifTarget::CoherentSysmem)
+ .with_mem_type(FalconFbifMemType::Physical)
});
self.dma_wr(
@@ -845,9 +702,10 @@ fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>(
self.hal.program_brom(self, bar, &fw.brom_params())?;
// Set `BootVec` to start of non-secure code.
- regs::NV_PFALCON_FALCON_BOOTVEC::default()
- .set_value(fw.boot_addr())
- .write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_BOOTVEC::zeroed().with_value(fw.boot_addr()),
+ );
Ok(())
}
@@ -856,7 +714,7 @@ fn dma_load<F: FalconFirmware<Target = E> + FalconDmaLoadable>(
pub(crate) fn wait_till_halted(&self, bar: &Bar0) -> Result<()> {
// TIMEOUT: arbitrarily large value, firmwares should complete in less than 2 seconds.
read_poll_timeout(
- || Ok(regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID)),
+ || Ok(bar.read(regs::NV_PFALCON_FALCON_CPUCTL::of::<E>())),
|r| r.halted(),
Delta::ZERO,
Delta::from_secs(2),
@@ -867,13 +725,18 @@ pub(crate) fn wait_till_halted(&self, bar: &Bar0) -> Result<()> {
/// Start the falcon CPU.
pub(crate) fn start(&self, bar: &Bar0) -> Result<()> {
- match regs::NV_PFALCON_FALCON_CPUCTL::read(bar, &E::ID).alias_en() {
- true => regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::default()
- .set_startcpu(true)
- .write(bar, &E::ID),
- false => regs::NV_PFALCON_FALCON_CPUCTL::default()
- .set_startcpu(true)
- .write(bar, &E::ID),
+ match bar
+ .read(regs::NV_PFALCON_FALCON_CPUCTL::of::<E>())
+ .alias_en()
+ {
+ true => bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_CPUCTL_ALIAS::zeroed().with_startcpu(true),
+ ),
+ false => bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_CPUCTL::zeroed().with_startcpu(true),
+ ),
}
Ok(())
@@ -882,26 +745,30 @@ pub(crate) fn start(&self, bar: &Bar0) -> Result<()> {
/// Writes values to the mailbox registers if provided.
pub(crate) fn write_mailboxes(&self, bar: &Bar0, mbox0: Option<u32>, mbox1: Option<u32>) {
if let Some(mbox0) = mbox0 {
- regs::NV_PFALCON_FALCON_MAILBOX0::default()
- .set_value(mbox0)
- .write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_MAILBOX0::zeroed().with_value(mbox0),
+ );
}
if let Some(mbox1) = mbox1 {
- regs::NV_PFALCON_FALCON_MAILBOX1::default()
- .set_value(mbox1)
- .write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_MAILBOX1::zeroed().with_value(mbox1),
+ );
}
}
/// Reads the value from `mbox0` register.
pub(crate) fn read_mailbox0(&self, bar: &Bar0) -> u32 {
- regs::NV_PFALCON_FALCON_MAILBOX0::read(bar, &E::ID).value()
+ bar.read(regs::NV_PFALCON_FALCON_MAILBOX0::of::<E>())
+ .value()
}
/// Reads the value from `mbox1` register.
pub(crate) fn read_mailbox1(&self, bar: &Bar0) -> u32 {
- regs::NV_PFALCON_FALCON_MAILBOX1::read(bar, &E::ID).value()
+ bar.read(regs::NV_PFALCON_FALCON_MAILBOX1::of::<E>())
+ .value()
}
/// Reads values from both mailbox registers.
@@ -966,8 +833,9 @@ pub(crate) fn load<F: FalconFirmware<Target = E> + FalconDmaLoadable>(
/// Write the application version to the OS register.
pub(crate) fn write_os_version(&self, bar: &Bar0, app_version: u32) {
- regs::NV_PFALCON_FALCON_OS::default()
- .set_value(app_version)
- .write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON_FALCON_OS::zeroed().with_value(app_version),
+ );
}
}
diff --git a/drivers/gpu/nova-core/falcon/gsp.rs b/drivers/gpu/nova-core/falcon/gsp.rs
index e52f57abc223..df6d5a382c7a 100644
--- a/drivers/gpu/nova-core/falcon/gsp.rs
+++ b/drivers/gpu/nova-core/falcon/gsp.rs
@@ -3,7 +3,11 @@
use kernel::{
io::{
poll::read_poll_timeout,
- Io, //
+ register::{
+ RegisterBase,
+ WithBase, //
+ },
+ Io,
},
prelude::*,
time::Delta, //
@@ -17,10 +21,7 @@
PFalcon2Base,
PFalconBase, //
},
- regs::{
- self,
- macros::RegisterBase, //
- },
+ regs,
};
/// Type specifying the `Gsp` falcon engine. Cannot be instantiated.
@@ -34,17 +35,16 @@ impl RegisterBase<PFalcon2Base> for Gsp {
const BASE: usize = 0x00111000;
}
-impl FalconEngine for Gsp {
- const ID: Self = Gsp(());
-}
+impl FalconEngine for Gsp {}
impl Falcon<Gsp> {
/// Clears the SWGEN0 bit in the Falcon's IRQ status clear register to
/// allow GSP to signal CPU for processing new messages in message queue.
pub(crate) fn clear_swgen0_intr(&self, bar: &Bar0) {
- regs::NV_PFALCON_FALCON_IRQSCLR::default()
- .set_swgen0(true)
- .write(bar, &Gsp::ID);
+ bar.write(
+ WithBase::of::<Gsp>(),
+ regs::NV_PFALCON_FALCON_IRQSCLR::zeroed().with_swgen0(true),
+ );
}
/// Checks if GSP reload/resume has completed during the boot process.
diff --git a/drivers/gpu/nova-core/falcon/hal/ga102.rs b/drivers/gpu/nova-core/falcon/hal/ga102.rs
index cbdf36bad633..8368a61ddeef 100644
--- a/drivers/gpu/nova-core/falcon/hal/ga102.rs
+++ b/drivers/gpu/nova-core/falcon/hal/ga102.rs
@@ -6,7 +6,10 @@
device,
io::{
poll::read_poll_timeout,
- register::Array,
+ register::{
+ Array,
+ WithBase, //
+ },
Io, //
},
prelude::*,
@@ -29,15 +32,16 @@
use super::FalconHal;
fn select_core_ga102<E: FalconEngine>(bar: &Bar0) -> Result {
- let bcr_ctrl = regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, &E::ID);
+ let bcr_ctrl = bar.read(regs::NV_PRISCV_RISCV_BCR_CTRL::of::<E>());
if bcr_ctrl.core_select() != PeregrineCoreSelect::Falcon {
- regs::NV_PRISCV_RISCV_BCR_CTRL::default()
- .set_core_select(PeregrineCoreSelect::Falcon)
- .write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PRISCV_RISCV_BCR_CTRL::zeroed().with_core_select(PeregrineCoreSelect::Falcon),
+ );
// TIMEOUT: falcon core should take less than 10ms to report being enabled.
read_poll_timeout(
- || Ok(regs::NV_PRISCV_RISCV_BCR_CTRL::read(bar, &E::ID)),
+ || Ok(bar.read(regs::NV_PRISCV_RISCV_BCR_CTRL::of::<E>())),
|r| r.valid(),
Delta::ZERO,
Delta::from_millis(10),
@@ -83,18 +87,23 @@ fn signature_reg_fuse_version_ga102(
}
fn program_brom_ga102<E: FalconEngine>(bar: &Bar0, params: &FalconBromParams) -> Result {
- regs::NV_PFALCON2_FALCON_BROM_PARAADDR::default()
- .set_value(params.pkc_data_offset)
- .write(bar, &E::ID, 0);
- regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::default()
- .set_value(u32::from(params.engine_id_mask))
- .write(bar, &E::ID);
- regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::default()
- .set_ucode_id(params.ucode_id)
- .write(bar, &E::ID);
- regs::NV_PFALCON2_FALCON_MOD_SEL::default()
- .set_algo(FalconModSelAlgo::Rsa3k)
- .write(bar, &E::ID);
+ bar.write(
+ WithBase::of::<E>().at(0),
+ regs::NV_PFALCON2_FALCON_BROM_PARAADDR::zeroed().with_value(params.pkc_data_offset),
+ );
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON2_FALCON_BROM_ENGIDMASK::zeroed()
+ .with_value(u32::from(params.engine_id_mask)),
+ );
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID::zeroed().with_ucode_id(params.ucode_id),
+ );
+ bar.write(
+ WithBase::of::<E>(),
+ regs::NV_PFALCON2_FALCON_MOD_SEL::zeroed().with_algo(FalconModSelAlgo::Rsa3k),
+ );
Ok(())
}
@@ -127,14 +136,14 @@ fn program_brom(&self, _falcon: &Falcon<E>, bar: &Bar0, params: &FalconBromParam
}
fn is_riscv_active(&self, bar: &Bar0) -> bool {
- let cpuctl = regs::NV_PRISCV_RISCV_CPUCTL::read(bar, &E::ID);
- cpuctl.active_stat()
+ bar.read(regs::NV_PRISCV_RISCV_CPUCTL::of::<E>())
+ .active_stat()
}
fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result {
// TIMEOUT: memory scrubbing should complete in less than 20ms.
read_poll_timeout(
- || Ok(regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID)),
+ || Ok(bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<E>())),
|r| r.mem_scrubbing_done(),
Delta::ZERO,
Delta::from_millis(20),
@@ -143,12 +152,12 @@ fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result {
}
fn reset_eng(&self, bar: &Bar0) -> Result {
- let _ = regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID);
+ let _ = bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<E>());
// According to OpenRM's `kflcnPreResetWait_GA102` documentation, HW sometimes does not set
// RESET_READY so a non-failing timeout is used.
let _ = read_poll_timeout(
- || Ok(regs::NV_PFALCON_FALCON_HWCFG2::read(bar, &E::ID)),
+ || Ok(bar.read(regs::NV_PFALCON_FALCON_HWCFG2::of::<E>())),
|r| r.reset_ready(),
Delta::ZERO,
Delta::from_micros(150),
diff --git a/drivers/gpu/nova-core/falcon/hal/tu102.rs b/drivers/gpu/nova-core/falcon/hal/tu102.rs
index 7de6f24cc0a0..c7a90266cb44 100644
--- a/drivers/gpu/nova-core/falcon/hal/tu102.rs
+++ b/drivers/gpu/nova-core/falcon/hal/tu102.rs
@@ -3,7 +3,11 @@
use core::marker::PhantomData;
use kernel::{
- io::poll::read_poll_timeout,
+ io::{
+ poll::read_poll_timeout,
+ register::WithBase,
+ Io, //
+ },
prelude::*,
time::Delta, //
};
@@ -49,14 +53,14 @@ fn program_brom(&self, _falcon: &Falcon<E>, _bar: &Bar0, _params: &FalconBromPar
}
fn is_riscv_active(&self, bar: &Bar0) -> bool {
- let cpuctl = regs::NV_PRISCV_RISCV_CORE_SWITCH_RISCV_STATUS::read(bar, &E::ID);
- cpuctl.active_stat()
+ bar.read(regs::NV_PRISCV_RISCV_CORE_SWITCH_RISCV_STATUS::of::<E>())
+ .active_stat()
}
fn reset_wait_mem_scrubbing(&self, bar: &Bar0) -> Result {
// TIMEOUT: memory scrubbing should complete in less than 10ms.
read_poll_timeout(
- || Ok(regs::NV_PFALCON_FALCON_DMACTL::read(bar, &E::ID)),
+ || Ok(bar.read(regs::NV_PFALCON_FALCON_DMACTL::of::<E>())),
|r| r.mem_scrubbing_done(),
Delta::ZERO,
Delta::from_millis(10),
diff --git a/drivers/gpu/nova-core/falcon/sec2.rs b/drivers/gpu/nova-core/falcon/sec2.rs
index b57d362e576a..91ec7d49c1f5 100644
--- a/drivers/gpu/nova-core/falcon/sec2.rs
+++ b/drivers/gpu/nova-core/falcon/sec2.rs
@@ -1,12 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
-use crate::{
- falcon::{
- FalconEngine,
- PFalcon2Base,
- PFalconBase, //
- },
- regs::macros::RegisterBase,
+use kernel::io::register::RegisterBase;
+
+use crate::falcon::{
+ FalconEngine,
+ PFalcon2Base,
+ PFalconBase, //
};
/// Type specifying the `Sec2` falcon engine. Cannot be instantiated.
@@ -20,6 +19,4 @@ impl RegisterBase<PFalcon2Base> for Sec2 {
const BASE: usize = 0x00841000;
}
-impl FalconEngine for Sec2 {
- const ID: Self = Sec2(());
-}
+impl FalconEngine for Sec2 {}
diff --git a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
index 342dba59b2f9..3b12d90d9412 100644
--- a/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
+++ b/drivers/gpu/nova-core/firmware/fwsec/bootloader.rs
@@ -12,6 +12,10 @@
self,
Device, //
},
+ io::{
+ register::WithBase, //
+ Io,
+ },
prelude::*,
ptr::{
Alignable,
@@ -33,7 +37,6 @@
Falcon,
FalconBromParams,
FalconDmaLoadable,
- FalconEngine,
FalconFbifMemType,
FalconFbifTarget,
FalconFirmware,
@@ -288,15 +291,15 @@ pub(crate) fn run(
.inspect_err(|e| dev_err!(dev, "Failed to load FWSEC firmware: {:?}\n", e))?;
// Configure DMA index for the bootloader to fetch the FWSEC firmware from system memory.
- regs::NV_PFALCON_FBIF_TRANSCFG::try_update(
- bar,
- &Gsp::ID,
- usize::from_safe_cast(self.dmem_desc.ctx_dma),
+ bar.update(
+ regs::NV_PFALCON_FBIF_TRANSCFG::of::<Gsp>()
+ .try_at(usize::from_safe_cast(self.dmem_desc.ctx_dma))
+ .ok_or(EINVAL)?,
|v| {
- v.set_target(FalconFbifTarget::CoherentSysmem)
- .set_mem_type(FalconFbifMemType::Physical)
+ v.with_target(FalconFbifTarget::CoherentSysmem)
+ .with_mem_type(FalconFbifMemType::Physical)
},
- )?;
+ );
let (mbox0, _) = falcon
.boot(bar, Some(0), None)
diff --git a/drivers/gpu/nova-core/regs.rs b/drivers/gpu/nova-core/regs.rs
index b051d5568cd8..87c2977ba6e4 100644
--- a/drivers/gpu/nova-core/regs.rs
+++ b/drivers/gpu/nova-core/regs.rs
@@ -1,14 +1,11 @@
// SPDX-License-Identifier: GPL-2.0
-// Required to retain the original register names used by OpenRM, which are all capital snake case
-// but are mapped to types.
-#![allow(non_camel_case_types)]
-
-#[macro_use]
-pub(crate) mod macros;
-
use kernel::{
- io,
+ io::{
+ self,
+ register::WithBase,
+ Io, //
+ },
prelude::*,
time, //
};
@@ -290,60 +287,147 @@ pub(crate) fn vga_workspace_addr(self) -> Option<u64> {
// PFALCON
-register!(NV_PFALCON_FALCON_IRQSCLR @ PFalconBase[0x00000004] {
- 4:4 halt as bool;
- 6:6 swgen0 as bool;
-});
+io::register! {
+ pub(crate) NV_PFALCON_FALCON_IRQSCLR(u32) @ PFalconBase + 0x00000004 {
+ 6:6 swgen0 => bool;
+ 4:4 halt => bool;
+ }
-register!(NV_PFALCON_FALCON_MAILBOX0 @ PFalconBase[0x00000040] {
- 31:0 value as u32;
-});
+ pub(crate) NV_PFALCON_FALCON_MAILBOX0(u32) @ PFalconBase + 0x00000040 {
+ 31:0 value => u32;
+ }
-register!(NV_PFALCON_FALCON_MAILBOX1 @ PFalconBase[0x00000044] {
- 31:0 value as u32;
-});
+ pub(crate) NV_PFALCON_FALCON_MAILBOX1(u32) @ PFalconBase + 0x00000044 {
+ 31:0 value => u32;
+ }
-// Used to store version information about the firmware running
-// on the Falcon processor.
-register!(NV_PFALCON_FALCON_OS @ PFalconBase[0x00000080] {
- 31:0 value as u32;
-});
+ /// Used to store version information about the firmware running
+ /// on the Falcon processor.
+ pub(crate) NV_PFALCON_FALCON_OS(u32) @ PFalconBase + 0x00000080 {
+ 31:0 value => u32;
+ }
-register!(NV_PFALCON_FALCON_RM @ PFalconBase[0x00000084] {
- 31:0 value as u32;
-});
+ pub(crate) NV_PFALCON_FALCON_RM(u32) @ PFalconBase + 0x00000084 {
+ 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+)";
-});
+ pub(crate) NV_PFALCON_FALCON_HWCFG2(u32) @ PFalconBase + 0x000000f4 {
+ /// Signal indicating that reset is completed (GA102+).
+ 31:31 reset_ready => bool;
+ /// Set to 0 after memory scrubbing is completed.
+ 12:12 mem_scrubbing => bool;
+ 10:10 riscv => bool;
+ }
-impl NV_PFALCON_FALCON_HWCFG2 {
- /// Returns `true` if memory scrubbing is completed.
- pub(crate) fn mem_scrubbing_done(self) -> bool {
- !self.mem_scrubbing()
+ pub(crate) NV_PFALCON_FALCON_CPUCTL(u32) @ PFalconBase + 0x00000100 {
+ 6:6 alias_en => bool;
+ 4:4 halted => bool;
+ 1:1 startcpu => bool;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_BOOTVEC(u32) @ PFalconBase + 0x00000104 {
+ 31:0 value => u32;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_DMACTL(u32) @ PFalconBase + 0x0000010c {
+ 7:7 secure_stat => bool;
+ 6:3 dmaq_num;
+ 2:2 imem_scrubbing => bool;
+ 1:1 dmem_scrubbing => bool;
+ 0:0 require_ctx => bool;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_DMATRFBASE(u32) @ PFalconBase + 0x00000110 {
+ 31:0 base => u32;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_DMATRFMOFFS(u32) @ PFalconBase + 0x00000114 {
+ 23:0 offs;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_DMATRFCMD(u32) @ PFalconBase + 0x00000118 {
+ 16:16 set_dmtag;
+ 14:12 ctxdma;
+ 10:8 size ?=> DmaTrfCmdSize;
+ 5:5 is_write => bool;
+ 4:4 imem => bool;
+ 3:2 sec;
+ 1:1 idle => bool;
+ 0:0 full => bool;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_DMATRFFBOFFS(u32) @ PFalconBase + 0x0000011c {
+ 31:0 offs => u32;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_DMATRFBASE1(u32) @ PFalconBase + 0x00000128 {
+ 8:0 base;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_HWCFG1(u32) @ PFalconBase + 0x0000012c {
+ /// Core revision subversion.
+ 7:6 core_rev_subversion => FalconCoreRevSubversion;
+ /// Security model.
+ 5:4 security_model ?=> FalconSecurityModel;
+ /// Core revision.
+ 3:0 core_rev ?=> FalconCoreRev;
+ }
+
+ pub(crate) NV_PFALCON_FALCON_CPUCTL_ALIAS(u32) @ PFalconBase + 0x00000130 {
+ 1:1 startcpu => bool;
+ }
+
+ /// IMEM access control register. Up to 4 ports are available for IMEM access.
+ pub(crate) NV_PFALCON_FALCON_IMEMC(u32)[4, stride = 16] @ PFalconBase + 0x00000180 {
+ /// Access secure IMEM.
+ 28:28 secure => bool;
+ /// Auto-increment on write.
+ 24:24 aincw => bool;
+ /// IMEM block and word offset.
+ 15:0 offs;
+ }
+
+ /// IMEM data register. Reading/writing this register accesses IMEM at the address
+ /// specified by the corresponding IMEMC register.
+ pub(crate) NV_PFALCON_FALCON_IMEMD(u32)[4, stride = 16] @ PFalconBase + 0x00000184 {
+ 31:0 data;
+ }
+
+ /// IMEM tag register. Used to set the tag for the current IMEM block.
+ pub(crate) NV_PFALCON_FALCON_IMEMT(u32)[4, stride = 16] @ PFalconBase + 0x00000188 {
+ 15:0 tag;
+ }
+
+ /// DMEM access control register. Up to 8 ports are available for DMEM access.
+ pub(crate) NV_PFALCON_FALCON_DMEMC(u32)[8, stride = 8] @ PFalconBase + 0x000001c0 {
+ /// Auto-increment on write.
+ 24:24 aincw => bool;
+ /// DMEM block and word offset.
+ 15:0 offs;
+ }
+
+ /// DMEM data register. Reading/writing this register accesses DMEM at the address
+ /// specified by the corresponding DMEMC register.
+ pub(crate) NV_PFALCON_FALCON_DMEMD(u32)[8, stride = 8] @ PFalconBase + 0x000001c4 {
+ 31:0 data;
+ }
+
+ /// Actually known as `NV_PSEC_FALCON_ENGINE` and `NV_PGSP_FALCON_ENGINE` depending on the
+ /// falcon instance.
+ pub(crate) NV_PFALCON_FALCON_ENGINE(u32) @ PFalconBase + 0x000003c0 {
+ 0:0 reset => bool;
+ }
+
+ pub(crate) NV_PFALCON_FBIF_TRANSCFG(u32)[8] @ PFalconBase + 0x00000600 {
+ 2:2 mem_type => FalconFbifMemType;
+ 1:0 target ?=> FalconFbifTarget;
+ }
+
+ pub(crate) NV_PFALCON_FBIF_CTL(u32) @ PFalconBase + 0x00000624 {
+ 7:7 allow_phys_no_ctx => bool;
}
}
-register!(NV_PFALCON_FALCON_CPUCTL @ PFalconBase[0x00000100] {
- 1:1 startcpu as bool;
- 4:4 halted as bool;
- 6:6 alias_en as bool;
-});
-
-register!(NV_PFALCON_FALCON_BOOTVEC @ PFalconBase[0x00000104] {
- 31:0 value as 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;
-});
-
impl NV_PFALCON_FALCON_DMACTL {
/// Returns `true` if memory scrubbing is completed.
pub(crate) fn mem_scrubbing_done(self) -> bool {
@@ -351,147 +435,82 @@ pub(crate) fn mem_scrubbing_done(self) -> bool {
}
}
-register!(NV_PFALCON_FALCON_DMATRFBASE @ PFalconBase[0x00000110] {
- 31:0 base as u32;
-});
-
-register!(NV_PFALCON_FALCON_DMATRFMOFFS @ PFalconBase[0x00000114] {
- 23:0 offs as u32;
-});
-
-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;
-});
-
impl NV_PFALCON_FALCON_DMATRFCMD {
/// Programs the `imem` and `sec` fields for the given FalconMem
pub(crate) fn with_falcon_mem(self, mem: FalconMem) -> Self {
- self.set_imem(mem != FalconMem::Dmem)
- .set_sec(if mem == FalconMem::ImemSecure { 1 } else { 0 })
+ let this = self.with_imem(mem != FalconMem::Dmem);
+
+ match mem {
+ FalconMem::ImemSecure => this.with_const_sec::<1>(),
+ _ => this.with_const_sec::<0>(),
+ }
}
}
-register!(NV_PFALCON_FALCON_DMATRFFBOFFS @ PFalconBase[0x0000011c] {
- 31:0 offs as u32;
-});
-
-register!(NV_PFALCON_FALCON_DMATRFBASE1 @ PFalconBase[0x00000128] {
- 8:0 base as u16;
-});
-
-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";
-});
-
-register!(NV_PFALCON_FALCON_CPUCTL_ALIAS @ PFalconBase[0x00000130] {
- 1:1 startcpu as bool;
-});
-
-// IMEM access control register. Up to 4 ports are available for IMEM access.
-register!(NV_PFALCON_FALCON_IMEMC @ PFalconBase[0x00000180[4; 16]] {
- 15:0 offs as u16, "IMEM block and word offset";
- 24:24 aincw as bool, "Auto-increment on write";
- 28:28 secure as bool, "Access secure IMEM";
-});
-
-// IMEM data register. Reading/writing this register accesses IMEM at the address
-// specified by the corresponding IMEMC register.
-register!(NV_PFALCON_FALCON_IMEMD @ PFalconBase[0x00000184[4; 16]] {
- 31:0 data as u32;
-});
-
-// IMEM tag register. Used to set the tag for the current IMEM block.
-register!(NV_PFALCON_FALCON_IMEMT @ PFalconBase[0x00000188[4; 16]] {
- 15:0 tag as u16;
-});
-
-// DMEM access control register. Up to 8 ports are available for DMEM access.
-register!(NV_PFALCON_FALCON_DMEMC @ PFalconBase[0x000001c0[8; 8]] {
- 15:0 offs as u16, "DMEM block and word offset";
- 24:24 aincw as bool, "Auto-increment on write";
-});
-
-// DMEM data register. Reading/writing this register accesses DMEM at the address
-// specified by the corresponding DMEMC register.
-register!(NV_PFALCON_FALCON_DMEMD @ PFalconBase[0x000001c4[8; 8]] {
- 31:0 data as u32;
-});
-
-// 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;
-});
-
impl NV_PFALCON_FALCON_ENGINE {
/// Resets the falcon
pub(crate) fn reset_engine<E: FalconEngine>(bar: &Bar0) {
- Self::read(bar, &E::ID).set_reset(true).write(bar, &E::ID);
+ bar.update(Self::of::<E>(), |r| r.with_reset(true));
// TIMEOUT: falcon engine should not take more than 10us to reset.
time::delay::fsleep(time::Delta::from_micros(10));
- Self::read(bar, &E::ID).set_reset(false).write(bar, &E::ID);
+ bar.update(Self::of::<E>(), |r| r.with_reset(false));
}
}
-register!(NV_PFALCON_FBIF_TRANSCFG @ PFalconBase[0x00000600[8]] {
- 1:0 target as u8 ?=> FalconFbifTarget;
- 2:2 mem_type as bool => FalconFbifMemType;
-});
-
-register!(NV_PFALCON_FBIF_CTL @ PFalconBase[0x00000624] {
- 7:7 allow_phys_no_ctx as bool;
-});
+impl NV_PFALCON_FALCON_HWCFG2 {
+ /// Returns `true` if memory scrubbing is completed.
+ pub(crate) fn mem_scrubbing_done(self) -> bool {
+ !self.mem_scrubbing()
+ }
+}
/* PFALCON2 */
-register!(NV_PFALCON2_FALCON_MOD_SEL @ PFalcon2Base[0x00000180] {
- 7:0 algo as u8 ?=> FalconModSelAlgo;
-});
+io::register! {
+ pub(crate) NV_PFALCON2_FALCON_MOD_SEL(u32) @ PFalcon2Base + 0x00000180 {
+ 7:0 algo ?=> FalconModSelAlgo;
+ }
-register!(NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID @ PFalcon2Base[0x00000198] {
- 7:0 ucode_id as u8;
-});
+ pub(crate) NV_PFALCON2_FALCON_BROM_CURR_UCODE_ID(u32) @ PFalcon2Base + 0x00000198 {
+ 7:0 ucode_id => u8;
+ }
-register!(NV_PFALCON2_FALCON_BROM_ENGIDMASK @ PFalcon2Base[0x0000019c] {
- 31:0 value as u32;
-});
+ pub(crate) NV_PFALCON2_FALCON_BROM_ENGIDMASK(u32) @ PFalcon2Base + 0x0000019c {
+ 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;
-});
+ /// 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.
+ pub(crate) NV_PFALCON2_FALCON_BROM_PARAADDR(u32)[1] @ PFalcon2Base + 0x00000210 {
+ 31:0 value => u32;
+ }
+}
// PRISCV
-// RISC-V status register for debug (Turing and GA100 only).
-// Reflects current RISC-V core status.
-register!(NV_PRISCV_RISCV_CORE_SWITCH_RISCV_STATUS @ PFalcon2Base[0x00000240] {
- 0:0 active_stat as bool, "RISC-V core active/inactive status";
-});
+io::register! {
+ /// RISC-V status register for debug (Turing and GA100 only).
+ /// Reflects current RISC-V core status.
+ pub(crate) NV_PRISCV_RISCV_CORE_SWITCH_RISCV_STATUS(u32) @ PFalcon2Base + 0x00000240 {
+ /// RISC-V core active/inactive status.
+ 0:0 active_stat => bool;
+ }
-// GA102 and later
-register!(NV_PRISCV_RISCV_CPUCTL @ PFalcon2Base[0x00000388] {
- 0:0 halted as bool;
- 7:7 active_stat as bool;
-});
+ /// GA102 and later.
+ pub(crate) NV_PRISCV_RISCV_CPUCTL(u32) @ PFalcon2Base + 0x00000388 {
+ 7:7 active_stat => bool;
+ 0:0 halted => bool;
+ }
-register!(NV_PRISCV_RISCV_BCR_CTRL @ PFalcon2Base[0x00000668] {
- 0:0 valid as bool;
- 4:4 core_select as bool => PeregrineCoreSelect;
- 8:8 br_fetch as bool;
-});
+ /// GA102 and later.
+ pub(crate) NV_PRISCV_RISCV_BCR_CTRL(u32) @ PFalcon2Base + 0x00000668 {
+ 8:8 br_fetch => bool;
+ 4:4 core_select => PeregrineCoreSelect;
+ 0:0 valid => bool;
+ }
+}
// The modules below provide registers that are not identical on all supported chips. They should
// only be used in HAL modules.
diff --git a/drivers/gpu/nova-core/regs/macros.rs b/drivers/gpu/nova-core/regs/macros.rs
deleted file mode 100644
index ed624be1f39b..000000000000
--- a/drivers/gpu/nova-core/regs/macros.rs
+++ /dev/null
@@ -1,739 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-
-//! `register!` macro to define register layout and accessors.
-//!
-//! A single register typically includes several fields, which are accessed through a combination
-//! of bit-shift and mask operations that introduce a class of potential mistakes, notably because
-//! not all possible field values are necessarily valid.
-//!
-//! The `register!` macro in this module provides an intuitive and readable syntax for defining a
-//! dedicated type for each register. Each such type comes with its own field accessors that can
-//! return an error if a field's value is invalid. Please look at the [`bitfield`] macro for the
-//! complete syntax of fields definitions.
-
-/// Trait providing a base address to be added to the offset of a relative register to obtain
-/// its actual offset.
-///
-/// The `T` generic argument is used to distinguish which base to use, in case a type provides
-/// several bases. It is given to the `register!` macro to restrict the use of the register to
-/// implementors of this particular variant.
-pub(crate) trait RegisterBase<T> {
- const BASE: usize;
-}
-
-/// Defines a dedicated type for a register with an absolute offset, including getter and setter
-/// methods for its fields and methods to read and write it from an `Io` region.
-///
-/// Example:
-///
-/// ```no_run
-/// register!(BOOT_0 @ 0x00000100, "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";
-/// 28:20 chipset as u32 ?=> Chipset, "Chipset model";
-/// });
-/// ```
-///
-/// This defines a `BOOT_0` type which can be read or written from offset `0x100` of an `Io`
-/// region. It is composed of 3 fields, for instance `minor_revision` is made of the 4 least
-/// significant bits of the register. Each field can be accessed and modified using accessor
-/// methods:
-///
-/// ```no_run
-/// // Read from the register's defined offset (0x100).
-/// let boot0 = BOOT_0::read(&bar);
-/// pr_info!("chip revision: {}.{}", boot0.major_revision(), boot0.minor_revision());
-///
-/// // `Chipset::try_from` is called with the value of the `chipset` field and returns an
-/// // error if it is invalid.
-/// let chipset = boot0.chipset()?;
-///
-/// // Update some fields and write the value back.
-/// boot0.set_major_revision(3).set_minor_revision(10).write(&bar);
-///
-/// // Or, just read and update the register in a single step:
-/// BOOT_0::update(&bar, |r| r.set_major_revision(3).set_minor_revision(10));
-/// ```
-///
-/// The documentation strings are optional. If present, they will be added to the type's
-/// definition, or the field getter and setter methods they are attached to.
-///
-/// It is also possible to create a alias register by using the `=> ALIAS` syntax. This is useful
-/// for cases where a register's interpretation depends on the context:
-///
-/// ```no_run
-/// register!(SCRATCH @ 0x00000200, "Scratch register" {
-/// 31:0 value as u32, "Raw value";
-/// });
-///
-/// register!(SCRATCH_BOOT_STATUS => SCRATCH, "Boot status of the firmware" {
-/// 0:0 completed as bool, "Whether the firmware has completed booting";
-/// });
-/// ```
-///
-/// In this example, `SCRATCH_0_BOOT_STATUS` uses the same I/O address as `SCRATCH`, while also
-/// providing its own `completed` field.
-///
-/// ## Relative registers
-///
-/// A register can be defined as being accessible from a fixed offset of a provided base. For
-/// instance, imagine the following I/O space:
-///
-/// ```text
-/// +-----------------------------+
-/// | ... |
-/// | |
-/// 0x100--->+------------CPU0-------------+
-/// | |
-/// 0x110--->+-----------------------------+
-/// | CPU_CTL |
-/// +-----------------------------+
-/// | ... |
-/// | |
-/// | |
-/// 0x200--->+------------CPU1-------------+
-/// | |
-/// 0x210--->+-----------------------------+
-/// | CPU_CTL |
-/// +-----------------------------+
-/// | ... |
-/// +-----------------------------+
-/// ```
-///
-/// `CPU0` and `CPU1` both have a `CPU_CTL` register that starts at offset `0x10` of their I/O
-/// space segment. Since both instances of `CPU_CTL` share the same layout, we don't want to define
-/// them twice and would prefer a way to select which one to use from a single definition
-///
-/// This can be done using the `Base[Offset]` syntax when specifying the register's address.
-///
-/// `Base` is an arbitrary type (typically a ZST) to be used as a generic parameter of the
-/// [`RegisterBase`] trait to provide the base as a constant, i.e. each type providing a base for
-/// this register needs to implement `RegisterBase<Base>`. Here is the above example translated
-/// into code:
-///
-/// ```no_run
-/// // Type used to identify the base.
-/// pub(crate) struct CpuCtlBase;
-///
-/// // ZST describing `CPU0`.
-/// struct Cpu0;
-/// impl RegisterBase<CpuCtlBase> for Cpu0 {
-/// const BASE: usize = 0x100;
-/// }
-/// // Singleton of `CPU0` used to identify it.
-/// const CPU0: Cpu0 = Cpu0;
-///
-/// // ZST describing `CPU1`.
-/// struct Cpu1;
-/// impl RegisterBase<CpuCtlBase> for Cpu1 {
-/// const BASE: usize = 0x200;
-/// }
-/// // Singleton of `CPU1` used to identify it.
-/// const CPU1: Cpu1 = Cpu1;
-///
-/// // This makes `CPU_CTL` accessible from all implementors of `RegisterBase<CpuCtlBase>`.
-/// register!(CPU_CTL @ CpuCtlBase[0x10], "CPU core control" {
-/// 0:0 start as bool, "Start the CPU core";
-/// });
-///
-/// // The `read`, `write` and `update` methods of relative registers take an extra `base` argument
-/// // that is used to resolve its final address by adding its `BASE` to the offset of the
-/// // register.
-///
-/// // Start `CPU0`.
-/// CPU_CTL::update(bar, &CPU0, |r| r.set_start(true));
-///
-/// // Start `CPU1`.
-/// CPU_CTL::update(bar, &CPU1, |r| r.set_start(true));
-///
-/// // Aliases can also be defined for relative register.
-/// register!(CPU_CTL_ALIAS => CpuCtlBase[CPU_CTL], "Alias to CPU core control" {
-/// 1:1 alias_start as bool, "Start the aliased CPU core";
-/// });
-///
-/// // Start the aliased `CPU0`.
-/// CPU_CTL_ALIAS::update(bar, &CPU0, |r| r.set_alias_start(true));
-/// ```
-///
-/// ## Arrays of registers
-///
-/// Some I/O areas contain consecutive values that can be interpreted in the same way. These areas
-/// can be defined as an array of identical registers, allowing them to be accessed by index with
-/// compile-time or runtime bound checking. Simply define their address as `Address[Size]`, and add
-/// an `idx` parameter to their `read`, `write` and `update` methods:
-///
-/// ```no_run
-/// # fn no_run() -> Result<(), Error> {
-/// # fn get_scratch_idx() -> usize {
-/// # 0x15
-/// # }
-/// // Array of 64 consecutive registers with the same layout starting at offset `0x80`.
-/// register!(SCRATCH @ 0x00000080[64], "Scratch registers" {
-/// 31:0 value as u32;
-/// });
-///
-/// // Read scratch register 0, i.e. I/O address `0x80`.
-/// let scratch_0 = SCRATCH::read(bar, 0).value();
-/// // Read scratch register 15, i.e. I/O address `0x80 + (15 * 4)`.
-/// let scratch_15 = SCRATCH::read(bar, 15).value();
-///
-/// // This is out of bounds and won't build.
-/// // let scratch_128 = SCRATCH::read(bar, 128).value();
-///
-/// // Runtime-obtained array index.
-/// let scratch_idx = get_scratch_idx();
-/// // Access on a runtime index returns an error if it is out-of-bounds.
-/// let some_scratch = SCRATCH::try_read(bar, scratch_idx)?.value();
-///
-/// // Alias to a particular register in an array.
-/// // Here `SCRATCH[8]` is used to convey the firmware exit code.
-/// register!(FIRMWARE_STATUS => SCRATCH[8], "Firmware exit status code" {
-/// 7:0 status as u8;
-/// });
-///
-/// let status = FIRMWARE_STATUS::read(bar).status();
-///
-/// // Non-contiguous register arrays can be defined by adding a stride parameter.
-/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
-/// // registers of the two declarations below are interleaved.
-/// register!(SCRATCH_INTERLEAVED_0 @ 0x000000c0[16 ; 8], "Scratch registers bank 0" {
-/// 31:0 value as u32;
-/// });
-/// register!(SCRATCH_INTERLEAVED_1 @ 0x000000c4[16 ; 8], "Scratch registers bank 1" {
-/// 31:0 value as u32;
-/// });
-/// # Ok(())
-/// # }
-/// ```
-///
-/// ## Relative arrays of registers
-///
-/// Combining the two features described in the sections above, arrays of registers accessible from
-/// a base can also be defined:
-///
-/// ```no_run
-/// # fn no_run() -> Result<(), Error> {
-/// # fn get_scratch_idx() -> usize {
-/// # 0x15
-/// # }
-/// // Type used as parameter of `RegisterBase` to specify the base.
-/// pub(crate) struct CpuCtlBase;
-///
-/// // ZST describing `CPU0`.
-/// struct Cpu0;
-/// impl RegisterBase<CpuCtlBase> for Cpu0 {
-/// const BASE: usize = 0x100;
-/// }
-/// // Singleton of `CPU0` used to identify it.
-/// const CPU0: Cpu0 = Cpu0;
-///
-/// // ZST describing `CPU1`.
-/// struct Cpu1;
-/// impl RegisterBase<CpuCtlBase> for Cpu1 {
-/// const BASE: usize = 0x200;
-/// }
-/// // Singleton of `CPU1` used to identify it.
-/// const CPU1: Cpu1 = Cpu1;
-///
-/// // 64 per-cpu scratch registers, arranged as an contiguous array.
-/// register!(CPU_SCRATCH @ CpuCtlBase[0x00000080[64]], "Per-CPU scratch registers" {
-/// 31:0 value as u32;
-/// });
-///
-/// let cpu0_scratch_0 = CPU_SCRATCH::read(bar, &Cpu0, 0).value();
-/// let cpu1_scratch_15 = CPU_SCRATCH::read(bar, &Cpu1, 15).value();
-///
-/// // This won't build.
-/// // let cpu0_scratch_128 = CPU_SCRATCH::read(bar, &Cpu0, 128).value();
-///
-/// // Runtime-obtained array index.
-/// let scratch_idx = get_scratch_idx();
-/// // Access on a runtime value returns an error if it is out-of-bounds.
-/// let cpu0_some_scratch = CPU_SCRATCH::try_read(bar, &Cpu0, scratch_idx)?.value();
-///
-/// // `SCRATCH[8]` is used to convey the firmware exit code.
-/// register!(CPU_FIRMWARE_STATUS => CpuCtlBase[CPU_SCRATCH[8]],
-/// "Per-CPU firmware exit status code" {
-/// 7:0 status as u8;
-/// });
-///
-/// let cpu0_status = CPU_FIRMWARE_STATUS::read(bar, &Cpu0).status();
-///
-/// // Non-contiguous register arrays can be defined by adding a stride parameter.
-/// // Here, each of the 16 registers of the array are separated by 8 bytes, meaning that the
-/// // registers of the two declarations below are interleaved.
-/// register!(CPU_SCRATCH_INTERLEAVED_0 @ CpuCtlBase[0x00000d00[16 ; 8]],
-/// "Scratch registers bank 0" {
-/// 31:0 value as u32;
-/// });
-/// register!(CPU_SCRATCH_INTERLEAVED_1 @ CpuCtlBase[0x00000d04[16 ; 8]],
-/// "Scratch registers bank 1" {
-/// 31:0 value as u32;
-/// });
-/// # Ok(())
-/// # }
-/// ```
-macro_rules! register {
- // Creates a register at a fixed offset of the MMIO space.
- ($name:ident @ $offset:literal $(, $comment:literal)? { $($fields:tt)* } ) => {
- bitfield!(pub(crate) struct $name(u32) $(, $comment)? { $($fields)* } );
- register!(@io_fixed $name @ $offset);
- };
-
- // Creates an alias register of fixed offset register `alias` with its own fields.
- ($name:ident => $alias:ident $(, $comment:literal)? { $($fields:tt)* } ) => {
- bitfield!(pub(crate) struct $name(u32) $(, $comment)? { $($fields)* } );
- register!(@io_fixed $name @ $alias::OFFSET);
- };
-
- // Creates a register at a relative offset from a base address provider.
- ($name:ident @ $base:ty [ $offset:literal ] $(, $comment:literal)? { $($fields:tt)* } ) => {
- bitfield!(pub(crate) struct $name(u32) $(, $comment)? { $($fields)* } );
- register!(@io_relative $name @ $base [ $offset ]);
- };
-
- // Creates an alias register of relative offset register `alias` with its own fields.
- ($name:ident => $base:ty [ $alias:ident ] $(, $comment:literal)? { $($fields:tt)* }) => {
- bitfield!(pub(crate) struct $name(u32) $(, $comment)? { $($fields)* } );
- register!(@io_relative $name @ $base [ $alias::OFFSET ]);
- };
-
- // Creates an array of registers at a fixed offset of the MMIO space.
- (
- $name:ident @ $offset:literal [ $size:expr ; $stride:expr ] $(, $comment:literal)? {
- $($fields:tt)*
- }
- ) => {
- static_assert!(::core::mem::size_of::<u32>() <= $stride);
- bitfield!(pub(crate) struct $name(u32) $(, $comment)? { $($fields)* } );
- register!(@io_array $name @ $offset [ $size ; $stride ]);
- };
-
- // Shortcut for contiguous array of registers (stride == size of element).
- (
- $name:ident @ $offset:literal [ $size:expr ] $(, $comment:literal)? {
- $($fields:tt)*
- }
- ) => {
- register!($name @ $offset [ $size ; ::core::mem::size_of::<u32>() ] $(, $comment)? {
- $($fields)*
- } );
- };
-
- // Creates an array of registers at a relative offset from a base address provider.
- (
- $name:ident @ $base:ty [ $offset:literal [ $size:expr ; $stride:expr ] ]
- $(, $comment:literal)? { $($fields:tt)* }
- ) => {
- static_assert!(::core::mem::size_of::<u32>() <= $stride);
- bitfield!(pub(crate) struct $name(u32) $(, $comment)? { $($fields)* } );
- register!(@io_relative_array $name @ $base [ $offset [ $size ; $stride ] ]);
- };
-
- // Shortcut for contiguous array of relative registers (stride == size of element).
- (
- $name:ident @ $base:ty [ $offset:literal [ $size:expr ] ] $(, $comment:literal)? {
- $($fields:tt)*
- }
- ) => {
- register!($name @ $base [ $offset [ $size ; ::core::mem::size_of::<u32>() ] ]
- $(, $comment)? { $($fields)* } );
- };
-
- // Creates an alias of register `idx` of relative array of registers `alias` with its own
- // fields.
- (
- $name:ident => $base:ty [ $alias:ident [ $idx:expr ] ] $(, $comment:literal)? {
- $($fields:tt)*
- }
- ) => {
- static_assert!($idx < $alias::SIZE);
- bitfield!(pub(crate) struct $name(u32) $(, $comment)? { $($fields)* } );
- register!(@io_relative $name @ $base [ $alias::OFFSET + $idx * $alias::STRIDE ] );
- };
-
- // Creates an alias of register `idx` of array of registers `alias` with its own fields.
- // This rule belongs to the (non-relative) register arrays set, but needs to be put last
- // to avoid it being interpreted in place of the relative register array alias rule.
- ($name:ident => $alias:ident [ $idx:expr ] $(, $comment:literal)? { $($fields:tt)* }) => {
- static_assert!($idx < $alias::SIZE);
- bitfield!(pub(crate) struct $name(u32) $(, $comment)? { $($fields)* } );
- register!(@io_fixed $name @ $alias::OFFSET + $idx * $alias::STRIDE );
- };
-
- // Generates the IO accessors for a fixed offset register.
- (@io_fixed $name:ident @ $offset:expr) => {
- #[allow(dead_code)]
- impl $name {
- pub(crate) const OFFSET: usize = $offset;
-
- /// Read the register from its address in `io`.
- #[inline(always)]
- pub(crate) fn read<T, I>(io: &T) -> Self where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- {
- Self(io.read32($offset))
- }
-
- /// Write the value contained in `self` to the register address in `io`.
- #[inline(always)]
- pub(crate) fn write<T, I>(self, io: &T) where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- {
- io.write32(self.0, $offset)
- }
-
- /// Read the register from its address in `io` and run `f` on its value to obtain a new
- /// value to write back.
- #[inline(always)]
- pub(crate) fn update<T, I, F>(
- io: &T,
- f: F,
- ) where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- F: ::core::ops::FnOnce(Self) -> Self,
- {
- let reg = f(Self::read(io));
- reg.write(io);
- }
- }
- };
-
- // Generates the IO accessors for a relative offset register.
- (@io_relative $name:ident @ $base:ty [ $offset:expr ]) => {
- #[allow(dead_code)]
- impl $name {
- pub(crate) const OFFSET: usize = $offset;
-
- /// Read the register from `io`, using the base address provided by `base` and adding
- /// the register's offset to it.
- #[inline(always)]
- pub(crate) fn read<T, I, B>(
- io: &T,
- #[allow(unused_variables)]
- base: &B,
- ) -> Self where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- B: crate::regs::macros::RegisterBase<$base>,
- {
- const OFFSET: usize = $name::OFFSET;
-
- let value = io.read32(
- <B as crate::regs::macros::RegisterBase<$base>>::BASE + OFFSET
- );
-
- Self(value)
- }
-
- /// Write the value contained in `self` to `io`, using the base address provided by
- /// `base` and adding the register's offset to it.
- #[inline(always)]
- pub(crate) fn write<T, I, B>(
- self,
- io: &T,
- #[allow(unused_variables)]
- base: &B,
- ) where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- B: crate::regs::macros::RegisterBase<$base>,
- {
- const OFFSET: usize = $name::OFFSET;
-
- io.write32(
- self.0,
- <B as crate::regs::macros::RegisterBase<$base>>::BASE + OFFSET
- );
- }
-
- /// Read the register from `io`, using the base address provided by `base` and adding
- /// the register's offset to it, then run `f` on its value to obtain a new value to
- /// write back.
- #[inline(always)]
- pub(crate) fn update<T, I, B, F>(
- io: &T,
- base: &B,
- f: F,
- ) where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- B: crate::regs::macros::RegisterBase<$base>,
- F: ::core::ops::FnOnce(Self) -> Self,
- {
- let reg = f(Self::read(io, base));
- reg.write(io, base);
- }
- }
- };
-
- // Generates the IO accessors for an array of registers.
- (@io_array $name:ident @ $offset:literal [ $size:expr ; $stride:expr ]) => {
- #[allow(dead_code)]
- impl $name {
- pub(crate) const OFFSET: usize = $offset;
- pub(crate) const SIZE: usize = $size;
- pub(crate) const STRIDE: usize = $stride;
-
- /// Read the array register at index `idx` from its address in `io`.
- #[inline(always)]
- pub(crate) fn read<T, I>(
- io: &T,
- idx: usize,
- ) -> Self where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- {
- build_assert!(idx < Self::SIZE);
-
- let offset = Self::OFFSET + (idx * Self::STRIDE);
- let value = io.read32(offset);
-
- Self(value)
- }
-
- /// Write the value contained in `self` to the array register with index `idx` in `io`.
- #[inline(always)]
- pub(crate) fn write<T, I>(
- self,
- io: &T,
- idx: usize
- ) where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- {
- build_assert!(idx < Self::SIZE);
-
- let offset = Self::OFFSET + (idx * Self::STRIDE);
-
- io.write32(self.0, offset);
- }
-
- /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
- /// new value to write back.
- #[inline(always)]
- pub(crate) fn update<T, I, F>(
- io: &T,
- idx: usize,
- f: F,
- ) where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- F: ::core::ops::FnOnce(Self) -> Self,
- {
- let reg = f(Self::read(io, idx));
- reg.write(io, idx);
- }
-
- /// Read the array register at index `idx` from its address in `io`.
- ///
- /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
- /// access was out-of-bounds.
- #[inline(always)]
- pub(crate) fn try_read<T, I>(
- io: &T,
- idx: usize,
- ) -> ::kernel::error::Result<Self> where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- {
- if idx < Self::SIZE {
- Ok(Self::read(io, idx))
- } else {
- Err(EINVAL)
- }
- }
-
- /// Write the value contained in `self` to the array register with index `idx` in `io`.
- ///
- /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
- /// access was out-of-bounds.
- #[inline(always)]
- pub(crate) fn try_write<T, I>(
- self,
- io: &T,
- idx: usize,
- ) -> ::kernel::error::Result where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- {
- if idx < Self::SIZE {
- Ok(self.write(io, idx))
- } else {
- Err(EINVAL)
- }
- }
-
- /// Read the array register at index `idx` in `io` and run `f` on its value to obtain a
- /// new value to write back.
- ///
- /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
- /// access was out-of-bounds.
- #[inline(always)]
- pub(crate) fn try_update<T, I, F>(
- io: &T,
- idx: usize,
- f: F,
- ) -> ::kernel::error::Result where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- F: ::core::ops::FnOnce(Self) -> Self,
- {
- if idx < Self::SIZE {
- Ok(Self::update(io, idx, f))
- } else {
- Err(EINVAL)
- }
- }
- }
- };
-
- // Generates the IO accessors for an array of relative registers.
- (
- @io_relative_array $name:ident @ $base:ty
- [ $offset:literal [ $size:expr ; $stride:expr ] ]
- ) => {
- #[allow(dead_code)]
- impl $name {
- pub(crate) const OFFSET: usize = $offset;
- pub(crate) const SIZE: usize = $size;
- pub(crate) const STRIDE: usize = $stride;
-
- /// Read the array register at index `idx` from `io`, using the base address provided
- /// by `base` and adding the register's offset to it.
- #[inline(always)]
- pub(crate) fn read<T, I, B>(
- io: &T,
- #[allow(unused_variables)]
- base: &B,
- idx: usize,
- ) -> Self where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- B: crate::regs::macros::RegisterBase<$base>,
- {
- build_assert!(idx < Self::SIZE);
-
- let offset = <B as crate::regs::macros::RegisterBase<$base>>::BASE +
- Self::OFFSET + (idx * Self::STRIDE);
- let value = io.read32(offset);
-
- Self(value)
- }
-
- /// Write the value contained in `self` to `io`, using the base address provided by
- /// `base` and adding the offset of array register `idx` to it.
- #[inline(always)]
- pub(crate) fn write<T, I, B>(
- self,
- io: &T,
- #[allow(unused_variables)]
- base: &B,
- idx: usize
- ) where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- B: crate::regs::macros::RegisterBase<$base>,
- {
- build_assert!(idx < Self::SIZE);
-
- let offset = <B as crate::regs::macros::RegisterBase<$base>>::BASE +
- Self::OFFSET + (idx * Self::STRIDE);
-
- io.write32(self.0, offset);
- }
-
- /// Read the array register at index `idx` from `io`, using the base address provided
- /// by `base` and adding the register's offset to it, then run `f` on its value to
- /// obtain a new value to write back.
- #[inline(always)]
- pub(crate) fn update<T, I, B, F>(
- io: &T,
- base: &B,
- idx: usize,
- f: F,
- ) where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- B: crate::regs::macros::RegisterBase<$base>,
- F: ::core::ops::FnOnce(Self) -> Self,
- {
- let reg = f(Self::read(io, base, idx));
- reg.write(io, base, idx);
- }
-
- /// Read the array register at index `idx` from `io`, using the base address provided
- /// by `base` and adding the register's offset to it.
- ///
- /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
- /// access was out-of-bounds.
- #[inline(always)]
- pub(crate) fn try_read<T, I, B>(
- io: &T,
- base: &B,
- idx: usize,
- ) -> ::kernel::error::Result<Self> where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- B: crate::regs::macros::RegisterBase<$base>,
- {
- if idx < Self::SIZE {
- Ok(Self::read(io, base, idx))
- } else {
- Err(EINVAL)
- }
- }
-
- /// Write the value contained in `self` to `io`, using the base address provided by
- /// `base` and adding the offset of array register `idx` to it.
- ///
- /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
- /// access was out-of-bounds.
- #[inline(always)]
- pub(crate) fn try_write<T, I, B>(
- self,
- io: &T,
- base: &B,
- idx: usize,
- ) -> ::kernel::error::Result where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- B: crate::regs::macros::RegisterBase<$base>,
- {
- if idx < Self::SIZE {
- Ok(self.write(io, base, idx))
- } else {
- Err(EINVAL)
- }
- }
-
- /// Read the array register at index `idx` from `io`, using the base address provided
- /// by `base` and adding the register's offset to it, then run `f` on its value to
- /// obtain a new value to write back.
- ///
- /// The validity of `idx` is checked at run-time, and `EINVAL` is returned is the
- /// access was out-of-bounds.
- #[inline(always)]
- pub(crate) fn try_update<T, I, B, F>(
- io: &T,
- base: &B,
- idx: usize,
- f: F,
- ) -> ::kernel::error::Result where
- T: ::core::ops::Deref<Target = I>,
- I: ::kernel::io::IoKnownSize + ::kernel::io::IoCapable<u32>,
- B: crate::regs::macros::RegisterBase<$base>,
- F: ::core::ops::FnOnce(Self) -> Self,
- {
- if idx < Self::SIZE {
- Ok(Self::update(io, base, idx, f))
- } else {
- Err(EINVAL)
- }
- }
- }
- };
-}
--
2.53.0
^ permalink raw reply related
* [PATCH v3 10/10] Documentation: nova: remove register abstraction task
From: Alexandre Courbot @ 2026-03-23 11:07 UTC (permalink / raw)
To: Danilo Krummrich, Alice Ryhl, David Airlie, Simona Vetter,
Maarten Lankhorst, Maxime Ripard, Thomas Zimmermann, Miguel Ojeda,
Boqun Feng, Gary Guo, Björn Roy Baron, Benno Lossin,
Andreas Hindborg, Trevor Gross
Cc: John Hubbard, Alistair Popple, Joel Fernandes, Timur Tabi,
Zhi Wang, Eliot Courtney, dri-devel, linux-kernel, linux-riscv,
linux-doc, rust-for-linux, Alexandre Courbot
In-Reply-To: <20260323-b4-nova-register-v3-0-ae2486ecef1b@nvidia.com>
The `register!` macro has been implemented and all nova-core code
converted to use it. Remove the corresponding task in todo.rst.
Reviewed-by: Eliot Courtney <ecourtney@nvidia.com>
Reviewed-by: Gary Guo <gary@garyguo.net>
Signed-off-by: Alexandre Courbot <acourbot@nvidia.com>
---
Documentation/gpu/nova/core/todo.rst | 76 ------------------------------------
1 file changed, 76 deletions(-)
diff --git a/Documentation/gpu/nova/core/todo.rst b/Documentation/gpu/nova/core/todo.rst
index d1964eb645e2..d5130b2b08fb 100644
--- a/Documentation/gpu/nova/core/todo.rst
+++ b/Documentation/gpu/nova/core/todo.rst
@@ -51,82 +51,6 @@ There also have been considerations of ToPrimitive [2].
| Link: https://lore.kernel.org/all/cover.1750689857.git.y.j3ms.n@gmail.com/ [1]
| Link: https://rust-for-linux.zulipchat.com/#narrow/channel/288089-General/topic/Implement.20.60FromPrimitive.60.20trait.20.2B.20derive.20macro.20for.20nova-core/with/541971854 [2]
-Generic register abstraction [REGA]
------------------------------------
-
-Work out how register constants and structures can be automatically generated
-through generalized macros.
-
-Example:
-
-.. code-block:: rust
-
- register!(BOOT0, 0x0, u32, pci::Bar<SIZE>, Fields [
- MINOR_REVISION(3:0, RO),
- MAJOR_REVISION(7:4, RO),
- REVISION(7:0, RO), // Virtual register combining major and minor rev.
- ])
-
-This could expand to something like:
-
-.. code-block:: rust
-
- const BOOT0_OFFSET: usize = 0x00000000;
- const BOOT0_MINOR_REVISION_SHIFT: u8 = 0;
- const BOOT0_MINOR_REVISION_MASK: u32 = 0x0000000f;
- const BOOT0_MAJOR_REVISION_SHIFT: u8 = 4;
- const BOOT0_MAJOR_REVISION_MASK: u32 = 0x000000f0;
- const BOOT0_REVISION_SHIFT: u8 = BOOT0_MINOR_REVISION_SHIFT;
- const BOOT0_REVISION_MASK: u32 = BOOT0_MINOR_REVISION_MASK | BOOT0_MAJOR_REVISION_MASK;
-
- struct Boot0(u32);
-
- impl Boot0 {
- #[inline]
- fn read(bar: &RevocableGuard<'_, pci::Bar<SIZE>>) -> Self {
- Self(bar.readl(BOOT0_OFFSET))
- }
-
- #[inline]
- fn minor_revision(&self) -> u32 {
- (self.0 & BOOT0_MINOR_REVISION_MASK) >> BOOT0_MINOR_REVISION_SHIFT
- }
-
- #[inline]
- fn major_revision(&self) -> u32 {
- (self.0 & BOOT0_MAJOR_REVISION_MASK) >> BOOT0_MAJOR_REVISION_SHIFT
- }
-
- #[inline]
- fn revision(&self) -> u32 {
- (self.0 & BOOT0_REVISION_MASK) >> BOOT0_REVISION_SHIFT
- }
- }
-
-Usage:
-
-.. code-block:: rust
-
- let bar = bar.try_access().ok_or(ENXIO)?;
-
- let boot0 = Boot0::read(&bar);
- pr_info!("Revision: {}\n", boot0.revision());
-
-A work-in-progress implementation currently resides in
-`drivers/gpu/nova-core/regs/macros.rs` and is used in nova-core. It would be
-nice to improve it (possibly using proc macros) and move it to the `kernel`
-crate so it can be used by other components as well.
-
-Features desired before this happens:
-
-* Make I/O optional I/O (for field values that are not registers),
-* Support other sizes than `u32`,
-* Allow visibility control for registers and individual fields,
-* Use Rust slice syntax to express fields ranges.
-
-| Complexity: Advanced
-| Contact: Alexandre Courbot
-
Numerical operations [NUMM]
---------------------------
--
2.53.0
^ permalink raw reply related
* [PATCH v1 0/6] misc: amd-sbi: Refactor SBTSI driver with I3C support and ioctl interface
From: Akshay Gupta @ 2026-03-23 11:08 UTC (permalink / raw)
To: linux-kernel
Cc: corbet, skhan, linux, arnd, gregkh, akshay.gupta, Prathima.Lk,
naveenkrishna.chatradhi, Anand.Umarji, linux-doc, linux-hwmon,
kunyi, Akshay Gupta
This series refactors the AMD SB-TSI (Side-Band Temperature Sensor
Interface) driver by moving it from the hwmon subsystem into the
drivers/misc/amd-sbi framework, alongside the existing SB-RMI driver.
Background:
The SB-TSI driver currently lives under drivers/hwmon/sbtsi_temp.c and
is limited to exposing temperature readings via the hwmon interface.
As AMD platforms evolve, SB-TSI access is required from multiple
consumers (hwmon, userspace via ioctl, I3C-attached devices), making
the hwmon-only placement insufficient.
This series restructures the driver into a layered design:
- tsi-core.c : core register access and ioctl/miscdevice support
- tsi-hwmon.c : hwmon sensor layer built on top of the core
- tsi.c : I2C/I3C probe and glue
Changes in this series:
1. Move core SBTSI driver from drivers/hwmon into drivers/misc/amd-sbi,
renaming sbtsi_temp.c -> sbtsi.c and wiring it into the amd-sbi
Kconfig/Makefile. The hwmon probe entry point is preserved so
existing device-tree bindings and hwmon consumers are unaffected.
2. Update the Kconfig entry to explicitly state that the driver targets
BMC-side I2C/I3C controllers (ARM/ARM64), avoiding accidental
enablement on non-BMC targets.
3. Split the hwmon sensor handling (sysfs attributes, hwmon_chip_info)
into tsi-hwmon.c so the core transport layer is not entangled with
hwmon internals, making future interface additions cleaner.
4. Extend the driver to support SB-TSI over I3C in addition to I2C.
Both buses share the same core read/write path via sbtsi_xfer();
the is_i3c flag selects the underlying transport at probe time.
Backward compatibility with existing I2C deployments is maintained.
5. Add a miscdevice (/dev/sbtsi-<addr>) and an ioctl interface
(SBTSI_IOCTL_REG_XFER_CMD) that allows root userspace to perform
SB-TSI register read/write operations through the APML protocol,
consistent with the existing SBRMI ioctl interface.
6. Document the new SBTSI miscdevice and its ioctl in
Documentation/misc-devices/amd-sbi.rst.
Testing:
Tested on AMD Genoa/Turin/Venice BMC platforms with both I2C and I3C-attached
SB-TSI targets. hwmon sysfs attributes (tempX_input, tempX_max, etc.)
and ioctl register transfers verified against hardware.
Akshay Gupta (1):
misc: amd-sbi: Update SBTSI Kconfig to clarify this is BMC driver
Prathima (5):
hwmon/misc: amd-sbi: Move core SBTSI support from hwmon to misc
misc: amd-sbi: Split SBTSI hwmon sensor handling into a separate
entity
misc: amd-sbi: Add support for SB-TSI over I3C
misc: amd-sbi: Add SBTSI ioctl register transfer interface
docs: misc: amd-sbi: Document SBTSI userspace interface
Documentation/misc-devices/amd-sbi.rst | 18 ++
drivers/hwmon/Kconfig | 10 -
drivers/hwmon/Makefile | 1 -
drivers/hwmon/sbtsi_temp.c | 250 -------------------------
drivers/misc/amd-sbi/Kconfig | 24 +++
drivers/misc/amd-sbi/Makefile | 4 +
drivers/misc/amd-sbi/tsi-core.c | 146 +++++++++++++++
drivers/misc/amd-sbi/tsi-core.h | 44 +++++
drivers/misc/amd-sbi/tsi-hwmon.c | 198 ++++++++++++++++++++
drivers/misc/amd-sbi/tsi.c | 162 ++++++++++++++++
include/uapi/misc/amd-apml.h | 23 +++
11 files changed, 619 insertions(+), 261 deletions(-)
delete mode 100644 drivers/hwmon/sbtsi_temp.c
create mode 100644 drivers/misc/amd-sbi/tsi-core.c
create mode 100644 drivers/misc/amd-sbi/tsi-core.h
create mode 100644 drivers/misc/amd-sbi/tsi-hwmon.c
create mode 100644 drivers/misc/amd-sbi/tsi.c
--
2.34.1
^ permalink raw reply
* [PATCH v1 1/6] hwmon/misc: amd-sbi: Move core SBTSI support from hwmon to misc
From: Akshay Gupta @ 2026-03-23 11:08 UTC (permalink / raw)
To: linux-kernel
Cc: corbet, skhan, linux, arnd, gregkh, akshay.gupta, Prathima.Lk,
naveenkrishna.chatradhi, Anand.Umarji, linux-doc, linux-hwmon,
kunyi, Akshay Gupta
In-Reply-To: <20260323110811.2898997-1-Akshay.Gupta@amd.com>
From: Prathima <Prathima.Lk@amd.com>
Move SBTSI core functionality out of the hwmon-only path and into
drivers/misc/amd-sbi so it can be reused by non-hwmon consumers.
This split prepares the driver for additional interfaces while keeping
hwmon support as an optional layer on top of common SBTSI core logic.
Reviewed-by: Akshay Gupta <Akshay.Gupta@amd.com>
Signed-off-by: Prathima <Prathima.Lk@amd.com>
---
drivers/hwmon/Kconfig | 10 ----------
drivers/hwmon/Makefile | 1 -
drivers/misc/amd-sbi/Kconfig | 11 +++++++++++
drivers/misc/amd-sbi/Makefile | 2 ++
drivers/{hwmon/sbtsi_temp.c => misc/amd-sbi/sbtsi.c} | 2 +-
5 files changed, 14 insertions(+), 12 deletions(-)
rename drivers/{hwmon/sbtsi_temp.c => misc/amd-sbi/sbtsi.c} (98%)
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index 328867242cb3..758f9c9f9e4f 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1936,16 +1936,6 @@ config SENSORS_SL28CPLD
This driver can also be built as a module. If so, the module
will be called sl28cpld-hwmon.
-config SENSORS_SBTSI
- tristate "Emulated SB-TSI temperature sensor"
- depends on I2C
- help
- If you say yes here you get support for emulated temperature
- sensors on AMD SoCs with SB-TSI interface connected to a BMC device.
-
- This driver can also be built as a module. If so, the module will
- be called sbtsi_temp.
-
config SENSORS_SHT15
tristate "Sensiron humidity and temperature sensors. SHT15 and compat."
depends on GPIOLIB || COMPILE_TEST
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index 5833c807c688..c9b6661cfc72 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -199,7 +199,6 @@ obj-$(CONFIG_SENSORS_PT5161L) += pt5161l.o
obj-$(CONFIG_SENSORS_PWM_FAN) += pwm-fan.o
obj-$(CONFIG_SENSORS_QNAP_MCU_HWMON) += qnap-mcu-hwmon.o
obj-$(CONFIG_SENSORS_RASPBERRYPI_HWMON) += raspberrypi-hwmon.o
-obj-$(CONFIG_SENSORS_SBTSI) += sbtsi_temp.o
obj-$(CONFIG_SENSORS_SBRMI) += sbrmi.o
obj-$(CONFIG_SENSORS_SCH56XX_COMMON)+= sch56xx-common.o
obj-$(CONFIG_SENSORS_SCH5627) += sch5627.o
diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig
index 30e7fad7356c..e6a422b4a371 100644
--- a/drivers/misc/amd-sbi/Kconfig
+++ b/drivers/misc/amd-sbi/Kconfig
@@ -20,3 +20,14 @@ config AMD_SBRMI_HWMON
This provides support for RMI device hardware monitoring. If enabled,
a hardware monitoring device will be created for each socket in
the system.
+
+config SENSORS_SBTSI
+ tristate "Emulated SB-TSI temperature sensor"
+ depends on I2C
+ depends on HWMON
+ help
+ If you say yes here you get support for emulated temperature
+ sensors on AMD SoCs with SB-TSI interface connected to a BMC device.
+
+ This driver can also be built as a module. If so, the module will
+ be called sbtsi.
diff --git a/drivers/misc/amd-sbi/Makefile b/drivers/misc/amd-sbi/Makefile
index 38eaaa651fd9..f04273c0d3ad 100644
--- a/drivers/misc/amd-sbi/Makefile
+++ b/drivers/misc/amd-sbi/Makefile
@@ -2,3 +2,5 @@
sbrmi-i2c-objs += rmi-i2c.o rmi-core.o
sbrmi-i2c-$(CONFIG_AMD_SBRMI_HWMON) += rmi-hwmon.o
obj-$(CONFIG_AMD_SBRMI_I2C) += sbrmi-i2c.o
+# SBTSI Configuration
+obj-$(CONFIG_SENSORS_SBTSI) += sbtsi.o
diff --git a/drivers/hwmon/sbtsi_temp.c b/drivers/misc/amd-sbi/sbtsi.c
similarity index 98%
rename from drivers/hwmon/sbtsi_temp.c
rename to drivers/misc/amd-sbi/sbtsi.c
index c5b2488c4c7f..e09b10c17771 100644
--- a/drivers/hwmon/sbtsi_temp.c
+++ b/drivers/misc/amd-sbi/sbtsi.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * sbtsi_temp.c - hwmon driver for a SBI Temperature Sensor Interface (SB-TSI)
+ * sbtsi.c - hwmon driver for a SBI Temperature Sensor Interface (SB-TSI)
* compliant AMD SoC temperature device.
*
* Copyright (c) 2020, Google Inc.
--
2.34.1
^ permalink raw reply related
* [PATCH v1 2/6] misc: amd-sbi: Update SBTSI Kconfig to clarify this is BMC driver
From: Akshay Gupta @ 2026-03-23 11:08 UTC (permalink / raw)
To: linux-kernel
Cc: corbet, skhan, linux, arnd, gregkh, akshay.gupta, Prathima.Lk,
naveenkrishna.chatradhi, Anand.Umarji, linux-doc, linux-hwmon,
kunyi, Akshay Gupta
In-Reply-To: <20260323110811.2898997-1-Akshay.Gupta@amd.com>
Add an architecture dependency to the SBTSI Kconfig
This limits enablement to supported targets
Also clarify in the help text that the SBTSI driver is
intended to run on the BMC, not on the managed host node.
Signed-off-by: Akshay Gupta <Akshay.Gupta@amd.com>
---
drivers/misc/amd-sbi/Kconfig | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig
index e6a422b4a371..19a8d596e8f5 100644
--- a/drivers/misc/amd-sbi/Kconfig
+++ b/drivers/misc/amd-sbi/Kconfig
@@ -24,10 +24,12 @@ config AMD_SBRMI_HWMON
config SENSORS_SBTSI
tristate "Emulated SB-TSI temperature sensor"
depends on I2C
+ depends on ARM || ARM64 || COMPILE_TEST
depends on HWMON
help
If you say yes here you get support for emulated temperature
sensors on AMD SoCs with SB-TSI interface connected to a BMC device.
+ This driver is intended to run on the BMC, not the managed node.
This driver can also be built as a module. If so, the module will
be called sbtsi.
--
2.34.1
^ permalink raw reply related
* [PATCH v1 4/6] misc: amd-sbi: Add support for SB-TSI over I3C
From: Akshay Gupta @ 2026-03-23 11:08 UTC (permalink / raw)
To: linux-kernel
Cc: corbet, skhan, linux, arnd, gregkh, akshay.gupta, Prathima.Lk,
naveenkrishna.chatradhi, Anand.Umarji, linux-doc, linux-hwmon,
kunyi, Akshay Gupta
In-Reply-To: <20260323110811.2898997-1-Akshay.Gupta@amd.com>
From: Prathima <Prathima.Lk@amd.com>
- Extend the SB-TSI driver to support both I2C and I3C bus interfaces.
- The driver maintains backward compatibility with existing I2C
deployments while enabling support for systems using the I3C bus.
Reviewed-by: Akshay Gupta <Akshay.Gupta@amd.com>
Signed-off-by: Prathima <Prathima.Lk@amd.com>
---
drivers/misc/amd-sbi/Kconfig | 5 ++-
drivers/misc/amd-sbi/Makefile | 2 +-
drivers/misc/amd-sbi/tsi-core.c | 64 ++++++++++++++++++++++++++++++++
drivers/misc/amd-sbi/tsi-core.h | 11 +++++-
drivers/misc/amd-sbi/tsi-hwmon.c | 50 ++++++++++++++++++-------
drivers/misc/amd-sbi/tsi.c | 62 +++++++++++++++++++++++++++++--
6 files changed, 173 insertions(+), 21 deletions(-)
create mode 100644 drivers/misc/amd-sbi/tsi-core.c
diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig
index 2710860a86c7..d18652537788 100644
--- a/drivers/misc/amd-sbi/Kconfig
+++ b/drivers/misc/amd-sbi/Kconfig
@@ -23,11 +23,12 @@ config AMD_SBRMI_HWMON
config SENSORS_SBTSI
tristate "Emulated SB-TSI temperature sensor"
- depends on I2C
+ depends on I3C_OR_I2C
depends on ARM || ARM64 || COMPILE_TEST
help
If you say yes here you get support for emulated temperature
- sensors on AMD SoCs with SB-TSI interface connected to a BMC device.
+ sensors on AMD SoCs with SB-TSI interface connected to a BMC device
+ over I2C and I3C.
This driver is intended to run on the BMC, not the managed node.
This driver can also be built as a module. If so, the module will
diff --git a/drivers/misc/amd-sbi/Makefile b/drivers/misc/amd-sbi/Makefile
index a874136e438f..e87c8e618dcc 100644
--- a/drivers/misc/amd-sbi/Makefile
+++ b/drivers/misc/amd-sbi/Makefile
@@ -3,6 +3,6 @@ sbrmi-i2c-objs += rmi-i2c.o rmi-core.o
sbrmi-i2c-$(CONFIG_AMD_SBRMI_HWMON) += rmi-hwmon.o
obj-$(CONFIG_AMD_SBRMI_I2C) += sbrmi-i2c.o
# SBTSI Configuration
-sbtsi-objs += tsi.o
+sbtsi-objs += tsi.o tsi-core.o
sbtsi-$(CONFIG_AMD_SBTSI_HWMON) += tsi-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi.o
diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c
new file mode 100644
index 000000000000..fcb7fcf87a55
--- /dev/null
+++ b/drivers/misc/amd-sbi/tsi-core.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * tsi-core.c - file defining SB-TSI protocols compliant
+ * AMD SoC device.
+ * Copyright (c) 2020, Google Inc.
+ * Copyright (c) 2020, Kun Yi <kunyi@google.com>
+ */
+
+#include "tsi-core.h"
+
+/* I3C read transfer function */
+static int sbtsi_i3c_read(struct sbtsi_data *data, u8 reg, u8 *val)
+{
+ struct i3c_xfer xfers[2];
+
+ /* Send register address */
+ xfers[0].rnw = false;
+ xfers[0].len = 1;
+ xfers[0].data.out = ®
+
+ /* Read data */
+ xfers[1].rnw = true;
+ xfers[1].len = 1;
+ xfers[1].data.in = val;
+
+ return i3c_device_do_xfers(data->i3cdev, xfers, 2, I3C_SDR);
+}
+
+/* I3C write transfer function */
+static int sbtsi_i3c_write(struct sbtsi_data *data, u8 reg, u8 *val)
+{
+ u8 buf[2] = { reg, *val };
+ struct i3c_xfer xfers = {
+ .rnw = false,
+ .len = 2,
+ .data.out = buf,
+ };
+
+ return i3c_device_do_xfers(data->i3cdev, &xfers, 1, I3C_SDR);
+}
+
+/* I2C transfer function */
+static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
+{
+ if (is_read) {
+ int ret = i2c_smbus_read_byte_data(data->client, reg);
+
+ if (ret < 0)
+ return ret;
+ *val = ret;
+ return 0;
+ }
+ return i2c_smbus_write_byte_data(data->client, reg, *val);
+}
+
+/* Unified transfer function for I2C and I3C access */
+int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
+{
+ if (data->is_i3c)
+ return is_read ? sbtsi_i3c_read(data, reg, val)
+ : sbtsi_i3c_write(data, reg, val);
+
+ return sbtsi_i2c_xfer(data, reg, val, is_read);
+}
diff --git a/drivers/misc/amd-sbi/tsi-core.h b/drivers/misc/amd-sbi/tsi-core.h
index e60cf25fda7a..a4ded17942c9 100644
--- a/drivers/misc/amd-sbi/tsi-core.h
+++ b/drivers/misc/amd-sbi/tsi-core.h
@@ -11,14 +11,23 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/types.h>
+#include <linux/i3c/device.h>
+#include <linux/i3c/master.h>
/* Each client has this additional data */
struct sbtsi_data {
- struct i2c_client *client;
+ union {
+ struct i2c_client *client;
+ struct i3c_device *i3cdev;
+ };
bool ext_range_mode;
bool read_order;
+ bool is_i3c;
};
+int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read);
+
#ifdef CONFIG_AMD_SBTSI_HWMON
int create_sbtsi_hwmon_sensor_device(struct device *dev, struct sbtsi_data *data);
#else
diff --git a/drivers/misc/amd-sbi/tsi-hwmon.c b/drivers/misc/amd-sbi/tsi-hwmon.c
index efd450a85d34..9b447042d4aa 100644
--- a/drivers/misc/amd-sbi/tsi-hwmon.c
+++ b/drivers/misc/amd-sbi/tsi-hwmon.c
@@ -27,8 +27,7 @@
#define SBTSI_TEMP_MIN 0
#define SBTSI_TEMP_MAX 255875
-/*
- * From SB-TSI spec: CPU temperature readings and limit registers encode the
+/* From SB-TSI spec: CPU temperature readings and limit registers encode the
* temperature in increments of 0.125 from 0 to 255.875. The "high byte"
* register encodes the base-2 of the integer portion, and the upper 3 bits of
* the "low byte" encode in base-2 the decimal portion.
@@ -61,24 +60,50 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
{
struct sbtsi_data *data = dev_get_drvdata(dev);
s32 temp_int, temp_dec;
+ u8 reg_val;
+ int err;
switch (attr) {
case hwmon_temp_input:
if (data->read_order) {
- temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
- temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_DEC, ®_val, true);
+ if (err < 0)
+ return err;
+ temp_dec = reg_val;
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_INT, ®_val, true);
+ if (err < 0)
+ return err;
+ temp_int = reg_val;
} else {
- temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_INT);
- temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_DEC);
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_INT, ®_val, true);
+ if (err < 0)
+ return err;
+ temp_int = reg_val;
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_DEC, ®_val, true);
+ if (err < 0)
+ return err;
+ temp_dec = reg_val;
}
break;
case hwmon_temp_max:
- temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_INT);
- temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_HIGH_DEC);
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_HIGH_INT, ®_val, true);
+ if (err < 0)
+ return err;
+ temp_int = reg_val;
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_HIGH_DEC, ®_val, true);
+ if (err < 0)
+ return err;
+ temp_dec = reg_val;
break;
case hwmon_temp_min:
- temp_int = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_INT);
- temp_dec = i2c_smbus_read_byte_data(data->client, SBTSI_REG_TEMP_LOW_DEC);
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_LOW_INT, ®_val, true);
+ if (err < 0)
+ return err;
+ temp_int = reg_val;
+ err = sbtsi_xfer(data, SBTSI_REG_TEMP_LOW_DEC, ®_val, true);
+ if (err < 0)
+ return err;
+ temp_dec = reg_val;
break;
default:
return -EINVAL;
@@ -115,12 +140,11 @@ static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type,
val += SBTSI_TEMP_EXT_RANGE_ADJ;
val = clamp_val(val, SBTSI_TEMP_MIN, SBTSI_TEMP_MAX);
sbtsi_mc_to_reg(val, &temp_int, &temp_dec);
-
- err = i2c_smbus_write_byte_data(data->client, reg_int, temp_int);
+ err = sbtsi_xfer(data, reg_int, &temp_int, false);
if (err)
return err;
- err = i2c_smbus_write_byte_data(data->client, reg_dec, temp_dec);
+ err = sbtsi_xfer(data, reg_dec, &temp_dec, false);
if (err)
return err;
return 0;
diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c
index df754e60d84b..d7a8237fc4fd 100644
--- a/drivers/misc/amd-sbi/tsi.c
+++ b/drivers/misc/amd-sbi/tsi.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * tsi.c - Side band TSI over I2C support for AMD out of band management.
+ * tsi.c - Side band TSI over I2C/I3C support for AMD out of band management.
*
* Copyright (c) 2020-2026, Google Inc.
* Copyright (c) 2020-2026, Kun Yi <kunyi@google.com>
@@ -26,7 +26,7 @@
#define SBTSI_CONFIG_READ_ORDER_SHIFT 5
-static int sbtsi_probe(struct i2c_client *client)
+static int sbtsi_i2c_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct sbtsi_data *data;
@@ -36,6 +36,7 @@ static int sbtsi_probe(struct i2c_client *client)
if (!data)
return -ENOMEM;
+ data->is_i3c = false;
data->client = client;
err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
if (err < 0)
@@ -66,11 +67,64 @@ static struct i2c_driver sbtsi_driver = {
.name = "sbtsi",
.of_match_table = of_match_ptr(sbtsi_of_match),
},
- .probe = sbtsi_probe,
+ .probe = sbtsi_i2c_probe,
.id_table = sbtsi_id,
};
-module_i2c_driver(sbtsi_driver);
+static int sbtsi_i3c_probe(struct i3c_device *i3cdev)
+{
+ struct device *dev = i3cdev_to_dev(i3cdev);
+ struct sbtsi_data *data;
+ int err;
+ u8 val;
+ /*
+ * AMD OOB devices differ on basis of Instance ID,
+ * for SBTSI, instance ID is 0.
+ * As the device Id match is not on basis of Instance ID,
+ * add the below check to probe the SBTSI device only and
+ * not other OOB devices.
+ */
+ if (I3C_PID_INSTANCE_ID(i3cdev->desc->info.pid) != 0)
+ return -ENXIO;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->i3cdev = i3cdev;
+ data->is_i3c = true;
+
+ err = sbtsi_xfer(data, SBTSI_REG_CONFIG, &val, true);
+ if (err)
+ return err;
+
+ data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), val);
+ data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val);
+
+ dev_set_drvdata(dev, data);
+ return create_sbtsi_hwmon_sensor_device(dev, data);
+}
+
+static const struct i3c_device_id sbtsi_i3c_id[] = {
+ /* PID for AMD SBTSI device */
+ I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x1, NULL),
+ I3C_DEVICE_EXTRA_INFO(0x0, 0x0, 0x118, NULL), /* Socket:0, Venice A0 */
+ I3C_DEVICE_EXTRA_INFO(0x0, 0x100, 0x118, NULL), /* Socket:1, Venice A0 */
+ I3C_DEVICE_EXTRA_INFO(0x112, 0x0, 0x119, NULL), /* Socket:0, Venice B0 */
+ I3C_DEVICE_EXTRA_INFO(0x112, 0x100, 0x119, NULL), /* Socket:1, Venice B0 */
+ {}
+};
+MODULE_DEVICE_TABLE(i3c, sbtsi_i3c_id);
+
+static struct i3c_driver sbtsi_i3c_driver = {
+ .driver = {
+ .name = "sbtsi-i3c",
+ },
+ .probe = sbtsi_i3c_probe,
+ .id_table = sbtsi_i3c_id,
+};
+
+module_i3c_i2c_driver(sbtsi_i3c_driver, &sbtsi_driver);
MODULE_AUTHOR("Kun Yi <kunyi@google.com>");
MODULE_DESCRIPTION("Hwmon driver for AMD SB-TSI emulated sensor");
--
2.34.1
^ permalink raw reply related
* [PATCH v1 3/6] misc: amd-sbi: Split SBTSI hwmon sensor handling into a separate entity
From: Akshay Gupta @ 2026-03-23 11:08 UTC (permalink / raw)
To: linux-kernel
Cc: corbet, skhan, linux, arnd, gregkh, akshay.gupta, Prathima.Lk,
naveenkrishna.chatradhi, Anand.Umarji, linux-doc, linux-hwmon,
kunyi, Akshay Gupta
In-Reply-To: <20260323110811.2898997-1-Akshay.Gupta@amd.com>
From: Prathima <Prathima.Lk@amd.com>
Separate SBTSI hwmon sensor handling from the core path to improve
code organization and maintainability.
This keeps core transport/register access independent while preserving
existing hwmon functionality through a dedicated hwmon component.
Reviewed-by: Akshay Gupta <Akshay.Gupta@amd.com>
Signed-off-by: Prathima <Prathima.Lk@amd.com>
---
drivers/misc/amd-sbi/Kconfig | 12 +-
drivers/misc/amd-sbi/Makefile | 2 +
drivers/misc/amd-sbi/tsi-core.h | 30 +++++
drivers/misc/amd-sbi/{sbtsi.c => tsi-hwmon.c} | 120 ++++--------------
drivers/misc/amd-sbi/tsi.c | 77 +++++++++++
5 files changed, 142 insertions(+), 99 deletions(-)
create mode 100644 drivers/misc/amd-sbi/tsi-core.h
rename drivers/misc/amd-sbi/{sbtsi.c => tsi-hwmon.c} (57%)
create mode 100644 drivers/misc/amd-sbi/tsi.c
diff --git a/drivers/misc/amd-sbi/Kconfig b/drivers/misc/amd-sbi/Kconfig
index 19a8d596e8f5..2710860a86c7 100644
--- a/drivers/misc/amd-sbi/Kconfig
+++ b/drivers/misc/amd-sbi/Kconfig
@@ -25,7 +25,6 @@ config SENSORS_SBTSI
tristate "Emulated SB-TSI temperature sensor"
depends on I2C
depends on ARM || ARM64 || COMPILE_TEST
- depends on HWMON
help
If you say yes here you get support for emulated temperature
sensors on AMD SoCs with SB-TSI interface connected to a BMC device.
@@ -33,3 +32,14 @@ config SENSORS_SBTSI
This driver can also be built as a module. If so, the module will
be called sbtsi.
+
+config AMD_SBTSI_HWMON
+ bool "SBTSI hardware monitoring for AMD platforms"
+ depends on SENSORS_SBTSI && HWMON
+ depends on !(SENSORS_SBTSI=y && HWMON=m)
+ default y
+ help
+ This provides support for TSI device hardware monitoring.
+ When enabled, a hwmon device is created for each socket to expose
+ temperature monitoring data through the standard Linux
+ hwmon interface.
diff --git a/drivers/misc/amd-sbi/Makefile b/drivers/misc/amd-sbi/Makefile
index f04273c0d3ad..a874136e438f 100644
--- a/drivers/misc/amd-sbi/Makefile
+++ b/drivers/misc/amd-sbi/Makefile
@@ -3,4 +3,6 @@ sbrmi-i2c-objs += rmi-i2c.o rmi-core.o
sbrmi-i2c-$(CONFIG_AMD_SBRMI_HWMON) += rmi-hwmon.o
obj-$(CONFIG_AMD_SBRMI_I2C) += sbrmi-i2c.o
# SBTSI Configuration
+sbtsi-objs += tsi.o
+sbtsi-$(CONFIG_AMD_SBTSI_HWMON) += tsi-hwmon.o
obj-$(CONFIG_SENSORS_SBTSI) += sbtsi.o
diff --git a/drivers/misc/amd-sbi/tsi-core.h b/drivers/misc/amd-sbi/tsi-core.h
new file mode 100644
index 000000000000..e60cf25fda7a
--- /dev/null
+++ b/drivers/misc/amd-sbi/tsi-core.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2026 Advanced Micro Devices, Inc.
+ */
+
+#ifndef _TSI_CORE_H_
+#define _TSI_CORE_H_
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+/* Each client has this additional data */
+struct sbtsi_data {
+ struct i2c_client *client;
+ bool ext_range_mode;
+ bool read_order;
+};
+
+#ifdef CONFIG_AMD_SBTSI_HWMON
+int create_sbtsi_hwmon_sensor_device(struct device *dev, struct sbtsi_data *data);
+#else
+static inline int create_sbtsi_hwmon_sensor_device(struct device *dev, struct sbtsi_data *data)
+{
+ return 0;
+}
+#endif
+#endif /*_TSI_CORE_H_*/
diff --git a/drivers/misc/amd-sbi/sbtsi.c b/drivers/misc/amd-sbi/tsi-hwmon.c
similarity index 57%
rename from drivers/misc/amd-sbi/sbtsi.c
rename to drivers/misc/amd-sbi/tsi-hwmon.c
index e09b10c17771..efd450a85d34 100644
--- a/drivers/misc/amd-sbi/sbtsi.c
+++ b/drivers/misc/amd-sbi/tsi-hwmon.c
@@ -1,59 +1,31 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
- * sbtsi.c - hwmon driver for a SBI Temperature Sensor Interface (SB-TSI)
- * compliant AMD SoC temperature device.
+ * tsi-hwmon.c - hwmon sensor support for side band TSI
*
* Copyright (c) 2020, Google Inc.
* Copyright (c) 2020, Kun Yi <kunyi@google.com>
*/
-#include <linux/err.h>
-#include <linux/i2c.h>
-#include <linux/init.h>
#include <linux/hwmon.h>
-#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/bitfield.h>
+#include "tsi-core.h"
/*
* SB-TSI registers only support SMBus byte data access. "_INT" registers are
* the integer part of a temperature value or limit, and "_DEC" registers are
* corresponding decimal parts.
*/
-#define SBTSI_REG_TEMP_INT 0x01 /* RO */
-#define SBTSI_REG_STATUS 0x02 /* RO */
-#define SBTSI_REG_CONFIG 0x03 /* RO */
-#define SBTSI_REG_TEMP_HIGH_INT 0x07 /* RW */
-#define SBTSI_REG_TEMP_LOW_INT 0x08 /* RW */
-#define SBTSI_REG_TEMP_DEC 0x10 /* RW */
-#define SBTSI_REG_TEMP_HIGH_DEC 0x13 /* RW */
-#define SBTSI_REG_TEMP_LOW_DEC 0x14 /* RW */
+#define SBTSI_REG_TEMP_INT 0x01 /* RO */
+#define SBTSI_REG_STATUS 0x02 /* RO */
+#define SBTSI_REG_TEMP_HIGH_INT 0x07 /* RW */
+#define SBTSI_REG_TEMP_LOW_INT 0x08 /* RW */
+#define SBTSI_REG_TEMP_DEC 0x10 /* RW */
+#define SBTSI_REG_TEMP_HIGH_DEC 0x13 /* RW */
+#define SBTSI_REG_TEMP_LOW_DEC 0x14 /* RW */
-/*
- * Bit for reporting value with temperature measurement range.
- * bit == 0: Use default temperature range (0C to 255.875C).
- * bit == 1: Use extended temperature range (-49C to +206.875C).
- */
-#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2
-/*
- * ReadOrder bit specifies the reading order of integer and decimal part of
- * CPU temperature for atomic reads. If bit == 0, reading integer part triggers
- * latching of the decimal part, so integer part should be read first.
- * If bit == 1, read order should be reversed.
- */
-#define SBTSI_CONFIG_READ_ORDER_SHIFT 5
-
-#define SBTSI_TEMP_EXT_RANGE_ADJ 49000
-
-#define SBTSI_TEMP_MIN 0
-#define SBTSI_TEMP_MAX 255875
+#define SBTSI_TEMP_EXT_RANGE_ADJ 49000
-/* Each client has this additional data */
-struct sbtsi_data {
- struct i2c_client *client;
- bool ext_range_mode;
- bool read_order;
-};
+#define SBTSI_TEMP_MIN 0
+#define SBTSI_TEMP_MAX 255875
/*
* From SB-TSI spec: CPU temperature readings and limit registers encode the
@@ -85,7 +57,7 @@ static inline void sbtsi_mc_to_reg(s32 temp, u8 *integer, u8 *decimal)
}
static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
- u32 attr, int channel, long *val)
+ u32 attr, int channel, long *val)
{
struct sbtsi_data *data = dev_get_drvdata(dev);
s32 temp_int, temp_dec;
@@ -112,12 +84,6 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
return -EINVAL;
}
-
- if (temp_int < 0)
- return temp_int;
- if (temp_dec < 0)
- return temp_dec;
-
*val = sbtsi_reg_to_mc(temp_int, temp_dec);
if (data->ext_range_mode)
*val -= SBTSI_TEMP_EXT_RANGE_ADJ;
@@ -126,7 +92,7 @@ static int sbtsi_read(struct device *dev, enum hwmon_sensor_types type,
}
static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type,
- u32 attr, int channel, long val)
+ u32 attr, int channel, long val)
{
struct sbtsi_data *data = dev_get_drvdata(dev);
int reg_int, reg_dec, err;
@@ -154,12 +120,15 @@ static int sbtsi_write(struct device *dev, enum hwmon_sensor_types type,
if (err)
return err;
- return i2c_smbus_write_byte_data(data->client, reg_dec, temp_dec);
+ err = i2c_smbus_write_byte_data(data->client, reg_dec, temp_dec);
+ if (err)
+ return err;
+ return 0;
}
static umode_t sbtsi_is_visible(const void *data,
- enum hwmon_sensor_types type,
- u32 attr, int channel)
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
{
switch (type) {
case hwmon_temp:
@@ -195,56 +164,11 @@ static const struct hwmon_chip_info sbtsi_chip_info = {
.info = sbtsi_info,
};
-static int sbtsi_probe(struct i2c_client *client)
+int create_sbtsi_hwmon_sensor_device(struct device *dev, struct sbtsi_data *data)
{
- struct device *dev = &client->dev;
struct device *hwmon_dev;
- struct sbtsi_data *data;
- int err;
- data = devm_kzalloc(dev, sizeof(struct sbtsi_data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
-
- data->client = client;
-
- err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
- if (err < 0)
- return err;
- data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), err);
- data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err);
-
- hwmon_dev = devm_hwmon_device_register_with_info(dev, client->name, data,
+ hwmon_dev = devm_hwmon_device_register_with_info(dev, "sbtsi", data,
&sbtsi_chip_info, NULL);
-
return PTR_ERR_OR_ZERO(hwmon_dev);
}
-
-static const struct i2c_device_id sbtsi_id[] = {
- {"sbtsi"},
- {}
-};
-MODULE_DEVICE_TABLE(i2c, sbtsi_id);
-
-static const struct of_device_id __maybe_unused sbtsi_of_match[] = {
- {
- .compatible = "amd,sbtsi",
- },
- { },
-};
-MODULE_DEVICE_TABLE(of, sbtsi_of_match);
-
-static struct i2c_driver sbtsi_driver = {
- .driver = {
- .name = "sbtsi",
- .of_match_table = of_match_ptr(sbtsi_of_match),
- },
- .probe = sbtsi_probe,
- .id_table = sbtsi_id,
-};
-
-module_i2c_driver(sbtsi_driver);
-
-MODULE_AUTHOR("Kun Yi <kunyi@google.com>");
-MODULE_DESCRIPTION("Hwmon driver for AMD SB-TSI emulated sensor");
-MODULE_LICENSE("GPL");
diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c
new file mode 100644
index 000000000000..df754e60d84b
--- /dev/null
+++ b/drivers/misc/amd-sbi/tsi.c
@@ -0,0 +1,77 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * tsi.c - Side band TSI over I2C support for AMD out of band management.
+ *
+ * Copyright (c) 2020-2026, Google Inc.
+ * Copyright (c) 2020-2026, Kun Yi <kunyi@google.com>
+ */
+
+#include <linux/bitfield.h>
+#include "tsi-core.h"
+
+#define SBTSI_REG_CONFIG 0x03 /* RO */
+
+/*
+ * Bit for reporting value with temperature measurement range.
+ * bit == 0: Use default temperature range (0C to 255.875C).
+ * bit == 1: Use extended temperature range (-49C to +206.875C).
+ */
+#define SBTSI_CONFIG_EXT_RANGE_SHIFT 2
+
+/*
+ * ReadOrder bit specifies the reading order of integer and decimal part of
+ * CPU temperature for atomic reads. If bit == 0, reading integer part triggers
+ * latching of the decimal part, so integer part should be read first.
+ */
+
+#define SBTSI_CONFIG_READ_ORDER_SHIFT 5
+
+static int sbtsi_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct sbtsi_data *data;
+ int err;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->client = client;
+ err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
+ if (err < 0)
+ return err;
+ data->ext_range_mode = FIELD_GET(BIT(SBTSI_CONFIG_EXT_RANGE_SHIFT), err);
+ data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err);
+
+ dev_set_drvdata(dev, data);
+ return create_sbtsi_hwmon_sensor_device(dev, data);
+}
+
+static const struct i2c_device_id sbtsi_id[] = {
+ {"sbtsi"},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, sbtsi_id);
+
+static const struct of_device_id __maybe_unused sbtsi_of_match[] = {
+ {
+ .compatible = "amd,sbtsi",
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, sbtsi_of_match);
+
+static struct i2c_driver sbtsi_driver = {
+ .driver = {
+ .name = "sbtsi",
+ .of_match_table = of_match_ptr(sbtsi_of_match),
+ },
+ .probe = sbtsi_probe,
+ .id_table = sbtsi_id,
+};
+
+module_i2c_driver(sbtsi_driver);
+
+MODULE_AUTHOR("Kun Yi <kunyi@google.com>");
+MODULE_DESCRIPTION("Hwmon driver for AMD SB-TSI emulated sensor");
+MODULE_LICENSE("GPL");
--
2.34.1
^ permalink raw reply related
* [PATCH v1 5/6] misc: amd-sbi: Add SBTSI ioctl register transfer interface
From: Akshay Gupta @ 2026-03-23 11:08 UTC (permalink / raw)
To: linux-kernel
Cc: corbet, skhan, linux, arnd, gregkh, akshay.gupta, Prathima.Lk,
naveenkrishna.chatradhi, Anand.Umarji, linux-doc, linux-hwmon,
kunyi, Akshay Gupta
In-Reply-To: <20260323110811.2898997-1-Akshay.Gupta@amd.com>
From: Prathima <Prathima.Lk@amd.com>
Implement IOCTL interface for SB-TSI driver to enable userspace access
to TSI register read/write operations through the AMD Advanced Platform
Management Link (APML) protocol.
Reviewed-by: Akshay Gupta <Akshay.Gupta@amd.com>
Signed-off-by: Prathima <Prathima.Lk@amd.com>
---
drivers/misc/amd-sbi/tsi-core.c | 86 ++++++++++++++++++++++++++++++++-
drivers/misc/amd-sbi/tsi-core.h | 5 ++
drivers/misc/amd-sbi/tsi.c | 37 ++++++++++++--
include/uapi/misc/amd-apml.h | 23 +++++++++
4 files changed, 146 insertions(+), 5 deletions(-)
diff --git a/drivers/misc/amd-sbi/tsi-core.c b/drivers/misc/amd-sbi/tsi-core.c
index fcb7fcf87a55..080cf1f368df 100644
--- a/drivers/misc/amd-sbi/tsi-core.c
+++ b/drivers/misc/amd-sbi/tsi-core.c
@@ -6,6 +6,10 @@
* Copyright (c) 2020, Kun Yi <kunyi@google.com>
*/
+#include <linux/fs.h>
+#include <linux/ioctl.h>
+#include <linux/uaccess.h>
+#include <uapi/misc/amd-apml.h>
#include "tsi-core.h"
/* I3C read transfer function */
@@ -56,9 +60,87 @@ static int sbtsi_i2c_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read
/* Unified transfer function for I2C and I3C access */
int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read)
{
+ int ret;
+
+ mutex_lock(&data->lock);
if (data->is_i3c)
- return is_read ? sbtsi_i3c_read(data, reg, val)
+ ret = is_read ? sbtsi_i3c_read(data, reg, val)
: sbtsi_i3c_write(data, reg, val);
+ else
+ ret = sbtsi_i2c_xfer(data, reg, val, is_read);
+ mutex_unlock(&data->lock);
+ return ret;
+}
+
+static int apml_tsi_reg_xfer(struct sbtsi_data *data,
+ struct apml_tsi_xfer_msg __user *arg)
+{
+ struct apml_tsi_xfer_msg msg = { 0 };
+ u8 val;
+ int ret;
+
+ /* Copy the structure from user */
+ if (copy_from_user(&msg, arg, sizeof(struct apml_tsi_xfer_msg)))
+ return -EFAULT;
+
+ if (msg.rflag) {
+ /* Read operation */
+ ret = sbtsi_xfer(data, msg.reg_addr, &val, true);
+ if (!ret)
+ msg.data_in_out = val;
+ } else {
+ /* Write operation */
+ ret = sbtsi_xfer(data, msg.reg_addr, &msg.data_in_out, false);
+ }
+
+ if (msg.rflag && !ret) {
+ if (copy_to_user(arg, &msg, sizeof(struct apml_tsi_xfer_msg)))
+ return -EFAULT;
+ }
+ return ret;
+}
+
+static long sbtsi_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
+{
+ void __user *argp = (void __user *)arg;
+ struct sbtsi_data *data;
+
+ data = container_of(fp->private_data, struct sbtsi_data, sbtsi_misc_dev);
+ switch (cmd) {
+ case SBTSI_IOCTL_REG_XFER_CMD:
+ return apml_tsi_reg_xfer(data, argp);
+ default:
+ return -ENOTTY;
+ }
+}
+
+static const struct file_operations sbtsi_fops = {
+ .owner = THIS_MODULE,
+ .unlocked_ioctl = sbtsi_ioctl,
+ .compat_ioctl = compat_ptr_ioctl,
+};
+
+int create_misc_tsi_device(struct sbtsi_data *data,
+ struct device *dev)
+{
+ int ret;
+
+ data->sbtsi_misc_dev.name = devm_kasprintf(dev, GFP_KERNEL,
+ "sbtsi-%x", data->dev_addr);
+ if (!data->sbtsi_misc_dev.name)
+ return -ENOMEM;
+ data->sbtsi_misc_dev.minor = MISC_DYNAMIC_MINOR;
+ data->sbtsi_misc_dev.fops = &sbtsi_fops;
+ data->sbtsi_misc_dev.parent = dev;
+ data->sbtsi_misc_dev.nodename = devm_kasprintf(dev, GFP_KERNEL,
+ "sbtsi-%x", data->dev_addr);
+ if (!data->sbtsi_misc_dev.nodename)
+ return -ENOMEM;
+ data->sbtsi_misc_dev.mode = 0600;
+
+ ret = misc_register(&data->sbtsi_misc_dev);
+ if (ret)
+ return ret;
- return sbtsi_i2c_xfer(data, reg, val, is_read);
+ return ret;
}
diff --git a/drivers/misc/amd-sbi/tsi-core.h b/drivers/misc/amd-sbi/tsi-core.h
index a4ded17942c9..d130ceadde04 100644
--- a/drivers/misc/amd-sbi/tsi-core.h
+++ b/drivers/misc/amd-sbi/tsi-core.h
@@ -9,6 +9,7 @@
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
+#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/types.h>
@@ -21,12 +22,16 @@ struct sbtsi_data {
struct i2c_client *client;
struct i3c_device *i3cdev;
};
+ struct miscdevice sbtsi_misc_dev;
+ struct mutex lock; /* serializes SBTSI register access */
bool ext_range_mode;
bool read_order;
bool is_i3c;
+ u8 dev_addr;
};
int sbtsi_xfer(struct sbtsi_data *data, u8 reg, u8 *val, bool is_read);
+int create_misc_tsi_device(struct sbtsi_data *data, struct device *dev);
#ifdef CONFIG_AMD_SBTSI_HWMON
int create_sbtsi_hwmon_sensor_device(struct device *dev, struct sbtsi_data *data);
diff --git a/drivers/misc/amd-sbi/tsi.c b/drivers/misc/amd-sbi/tsi.c
index d7a8237fc4fd..6e321064a3f3 100644
--- a/drivers/misc/amd-sbi/tsi.c
+++ b/drivers/misc/amd-sbi/tsi.c
@@ -38,6 +38,7 @@ static int sbtsi_i2c_probe(struct i2c_client *client)
data->is_i3c = false;
data->client = client;
+ mutex_init(&data->lock);
err = i2c_smbus_read_byte_data(data->client, SBTSI_REG_CONFIG);
if (err < 0)
return err;
@@ -45,7 +46,21 @@ static int sbtsi_i2c_probe(struct i2c_client *client)
data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), err);
dev_set_drvdata(dev, data);
- return create_sbtsi_hwmon_sensor_device(dev, data);
+ err = create_sbtsi_hwmon_sensor_device(dev, data);
+ if (err < 0)
+ return err;
+ data->dev_addr = client->addr;
+ return create_misc_tsi_device(data, dev);
+}
+
+static void sbtsi_i2c_remove(struct i2c_client *client)
+{
+ struct sbtsi_data *data = dev_get_drvdata(&client->dev);
+
+ if (data)
+ misc_deregister(&data->sbtsi_misc_dev);
+
+ dev_info(&client->dev, "Removed sbtsi driver\n");
}
static const struct i2c_device_id sbtsi_id[] = {
@@ -68,6 +83,7 @@ static struct i2c_driver sbtsi_driver = {
.of_match_table = of_match_ptr(sbtsi_of_match),
},
.probe = sbtsi_i2c_probe,
+ .remove = sbtsi_i2c_remove,
.id_table = sbtsi_id,
};
@@ -90,7 +106,7 @@ static int sbtsi_i3c_probe(struct i3c_device *i3cdev)
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
-
+ mutex_init(&data->lock);
data->i3cdev = i3cdev;
data->is_i3c = true;
@@ -102,7 +118,21 @@ static int sbtsi_i3c_probe(struct i3c_device *i3cdev)
data->read_order = FIELD_GET(BIT(SBTSI_CONFIG_READ_ORDER_SHIFT), val);
dev_set_drvdata(dev, data);
- return create_sbtsi_hwmon_sensor_device(dev, data);
+ err = create_sbtsi_hwmon_sensor_device(dev, data);
+ if (err < 0)
+ return err;
+ data->dev_addr = i3cdev->desc->info.dyn_addr;
+ return create_misc_tsi_device(data, dev);
+}
+
+static void sbtsi_i3c_remove(struct i3c_device *i3cdev)
+{
+ struct sbtsi_data *data = dev_get_drvdata(&i3cdev->dev);
+
+ if (data)
+ misc_deregister(&data->sbtsi_misc_dev);
+
+ dev_info(&i3cdev->dev, "Removed sbtsi-i3c driver\n");
}
static const struct i3c_device_id sbtsi_i3c_id[] = {
@@ -121,6 +151,7 @@ static struct i3c_driver sbtsi_i3c_driver = {
.name = "sbtsi-i3c",
},
.probe = sbtsi_i3c_probe,
+ .remove = sbtsi_i3c_remove,
.id_table = sbtsi_i3c_id,
};
diff --git a/include/uapi/misc/amd-apml.h b/include/uapi/misc/amd-apml.h
index 745b3338fc06..199532b8e64a 100644
--- a/include/uapi/misc/amd-apml.h
+++ b/include/uapi/misc/amd-apml.h
@@ -73,6 +73,13 @@ struct apml_reg_xfer_msg {
__u8 rflag;
};
+struct apml_tsi_xfer_msg {
+ __u8 reg_addr; /* TSI register address offset */
+ __u8 data_in_out; /* Register data for read/write */
+ __u8 rflag; /* Register read or write */
+ __u8 pad; /* Explicit padding */
+};
+
/*
* AMD sideband interface base IOCTL
*/
@@ -149,4 +156,20 @@ struct apml_reg_xfer_msg {
*/
#define SBRMI_IOCTL_REG_XFER_CMD _IOWR(SB_BASE_IOCTL_NR, 3, struct apml_reg_xfer_msg)
+/**
+ * DOC: SBTSI_IOCTL_REG_XFER_CMD
+ *
+ * @Parameters
+ *
+ * @struct apml_tsi_xfer_msg
+ * Pointer to the &struct apml_tsi_xfer_msg that will contain the protocol
+ * information
+ *
+ * @Description
+ * IOCTL command for APML TSI messages using generic _IOWR
+ * The IOCTL provides userspace access to AMD sideband TSI register xfer protocol
+ * - TSI protocol to read/write temperature sensor registers
+ */
+#define SBTSI_IOCTL_REG_XFER_CMD _IOWR(SB_BASE_IOCTL_NR, 4, struct apml_tsi_xfer_msg)
+
#endif /*_AMD_APML_H_*/
--
2.34.1
^ permalink raw reply related
* [PATCH v1 6/6] docs: misc: amd-sbi: Document SBTSI userspace interface
From: Akshay Gupta @ 2026-03-23 11:08 UTC (permalink / raw)
To: linux-kernel
Cc: corbet, skhan, linux, arnd, gregkh, akshay.gupta, Prathima.Lk,
naveenkrishna.chatradhi, Anand.Umarji, linux-doc, linux-hwmon,
kunyi, Akshay Gupta
In-Reply-To: <20260323110811.2898997-1-Akshay.Gupta@amd.com>
From: Prathima <Prathima.Lk@amd.com>
- Document AMD sideband IOCTL description defined
for SBTSI and its usage.
User space C-APIs are made available by esmi_oob_library [1],
which is provided by the E-SMS project [2].
Link: https://github.com/amd/esmi_oob_library [1]
Link: https://www.amd.com/en/developer/e-sms.html [2]
Reviewed-by: Akshay Gupta <Akshay.Gupta@amd.com>
Signed-off-by: Prathima <Prathima.Lk@amd.com>
---
Documentation/misc-devices/amd-sbi.rst | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
diff --git a/Documentation/misc-devices/amd-sbi.rst b/Documentation/misc-devices/amd-sbi.rst
index f91ddadefe48..ddff3cea8ed1 100644
--- a/Documentation/misc-devices/amd-sbi.rst
+++ b/Documentation/misc-devices/amd-sbi.rst
@@ -48,6 +48,20 @@ Access restrictions:
* APML Mailbox messages and Register xfer access are read-write,
* CPUID and MCA_MSR access is read-only.
+SBTSI device
+============
+
+apml_sbtsi driver under the drivers/misc/amd-sbi creates miscdevice
+/dev/sbtsi-* to let user space programs run APML TSI register xfer
+commands.
+
+The driver supports both I2C and I3C transports for SB-TSI targets.
+The transport is selected by the bus where the device is enumerated.
+
+Access restrictions:
+ * Only root user is allowed to open the file.
+ * APML TSI Register xfer access is read-write.
+
Driver IOCTLs
=============
@@ -63,6 +77,9 @@ Driver IOCTLs
.. c:macro:: SBRMI_IOCTL_REG_XFER_CMD
.. kernel-doc:: include/uapi/misc/amd-apml.h
:doc: SBRMI_IOCTL_REG_XFER_CMD
+.. c:macro:: SBTSI_IOCTL_REG_XFER_CMD
+.. kernel-doc:: include/uapi/misc/amd-apml.h
+ :doc: SBTSI_IOCTL_REG_XFER_CMD
User-space usage
================
@@ -92,6 +109,7 @@ The following IOCTLs are defined:
``#define SBRMI_IOCTL_CPUID_CMD _IOWR(SB_BASE_IOCTL_NR, 1, struct apml_cpuid_msg)``
``#define SBRMI_IOCTL_MCAMSR_CMD _IOWR(SB_BASE_IOCTL_NR, 2, struct apml_mcamsr_msg)``
``#define SBRMI_IOCTL_REG_XFER_CMD _IOWR(SB_BASE_IOCTL_NR, 3, struct apml_reg_xfer_msg)``
+``#define SBTSI_IOCTL_REG_XFER_CMD _IOWR(SB_BASE_IOCTL_NR, 4, struct apml_tsi_xfer_msg)``
User space C-APIs are made available by esmi_oob_library, hosted at
--
2.34.1
^ permalink raw reply related
* RE: [PATCH 07/10] docs: kdoc: better handle source when producing YAML output
From: Loktionov, Aleksandr @ 2026-03-23 11:15 UTC (permalink / raw)
To: Mauro Carvalho Chehab, Jonathan Corbet, Linux Doc Mailing List,
Mauro Carvalho Chehab
Cc: linux-kernel@vger.kernel.org, Randy Dunlap
In-Reply-To: <77902dafabb5c3250486aa2dc1568d5fafa95c5b.1774256269.git.mchehab+huawei@kernel.org>
> -----Original Message-----
> From: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
> Sent: Monday, March 23, 2026 10:11 AM
> To: Jonathan Corbet <corbet@lwn.net>; Linux Doc Mailing List <linux-
> doc@vger.kernel.org>; Mauro Carvalho Chehab <mchehab@kernel.org>
> Cc: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>; linux-
> kernel@vger.kernel.org; Loktionov, Aleksandr
> <aleksandr.loktionov@intel.com>; Randy Dunlap <rdunlap@infradead.org>
> Subject: [PATCH 07/10] docs: kdoc: better handle source when producing
> YAML output
>
> The current logic was storing symbols source code on a list, not
> linked to the actual KdocItem. While this works fine when kernel-doc
> markups are OK, on places where there is a "/**"
> without a valid kernel-doc markup, it ends that the 1:1 match between
> source code and KdocItem doesn't happen, causing problems to generate
> the YAML output.
>
> Fix it by storing the source code directly into the KdocItem
> structure.
>
> This shouldn't affect performance or memory footprint, except when --
> yaml option is used.
>
> While here, add a __repr__() function for KdocItem, as it helps
> debugging it.
>
Not sure, do we need Fixes: tag, what do you think?
> Signed-off-by: Mauro Carvalho Chehab <mchehab+huawei@kernel.org>
> ---
> tools/lib/python/kdoc/kdoc_files.py | 8 +-
> tools/lib/python/kdoc/kdoc_item.py | 6 +-
> tools/lib/python/kdoc/kdoc_parser.py | 100 ++++++++++++-----------
> -
> tools/lib/python/kdoc/kdoc_yaml_file.py | 28 +++----
> tools/unittests/test_kdoc_parser.py | 9 +++
> 5 files changed, 79 insertions(+), 72 deletions(-)
>
> diff --git a/tools/lib/python/kdoc/kdoc_files.py
> b/tools/lib/python/kdoc/kdoc_files.py
> index 5a299ed44d62..2428cfc4e843 100644
> --- a/tools/lib/python/kdoc/kdoc_files.py
> +++ b/tools/lib/python/kdoc/kdoc_files.py
> @@ -203,10 +203,6 @@ class KernelFiles():
>
> self.results[fname] = entries
>
...
> self.reset_state(ln)
>
> elif doc_content.search(line):
> @@ -1596,15 +1606,6 @@ class KernelDoc:
> state.DOCBLOCK: process_docblock,
> }
>
> - def get_source(self):
> - """
> - Return the file content of the lines handled by kernel-doc at
> the
> - latest parse_kdoc() run.
> -
> - Returns none if KernelDoc() was not initialized with
> store_src,
> - """
> - return self.source
> -
> def parse_kdoc(self):
> """
> Open and process each line of a C source file.
> @@ -1618,8 +1619,8 @@ class KernelDoc:
> prev = ""
> prev_ln = None
> export_table = set()
> - self.source = []
> self.state = state.NORMAL
> + source = ""
>
> try:
> with open(self.fname, "r", encoding="utf8", @@ -1646,7
> +1647,11 @@ class KernelDoc:
> ln, state.name[self.state],
> line)
>
> - prev_state = self.state
> + if self.store_src:
> + if source and self.state == state.NORMAL:
> + source = ""
> + elif self.state != state.NORMAL:
> + source += line + "\n"
>
> # This is an optimization over the original
> script.
> # There, when export_file was used for the same
> file, @@ -1655,16 +1660,11 @@ class KernelDoc:
> #
> if (self.state != state.NORMAL) or \
> not self.process_export(export_table, line):
> + prev_state = self.state
> # Hand this line to the appropriate state
> handler
> - self.state_actions[self.state](self, ln,
> line)
> -
> - if self.store_src and prev_state != self.state or
> self.state != state.NORMAL:
> - if self.state == state.NAME:
> - # A "/**" was detected. Add a new source
> element
> - self.source.append({"ln": ln, "data":
> line + "\n"})
> - else:
> - # Append to the existing one
> - self.source[-1]["data"] += line + "\n"
> + self.state_actions[self.state](self, ln,
> line, source)
> + if prev_state == state.NORMAL and self.state
> != state.NORMAL:
It looks this block is not guarded by `if self.store_src`,
and even when store_src=False (i.e., --yaml was NOT passed)
> + source += line + "\n"
It populates `source` unconditionally regardless of --yaml.
Isn't it?
>
> self.emit_unused_warnings()
>
> diff --git a/tools/lib/python/kdoc/kdoc_yaml_file.py
> b/tools/lib/python/kdoc/kdoc_yaml_file.py
> index 18737abb1176..1e2ae7c59d70 100644
> --- a/tools/lib/python/kdoc/kdoc_yaml_file.py
> +++ b/tools/lib/python/kdoc/kdoc_yaml_file.py
> @@ -85,7 +85,7 @@ class KDocTestFile():
>
> return d
>
...
> result = clean_whitespc(d[key], relax_whitespace)
> value = clean_whitespc(value, relax_whitespace)
>
> --
> 2.53.0
^ permalink raw reply
* Re: [PATCH v9 4/5] arm64: kexec: Add support for crashkernel CMA reservation
From: Jinjie Ruan @ 2026-03-23 11:17 UTC (permalink / raw)
To: Breno Leitao
Cc: corbet, skhan, catalin.marinas, will, chenhuacai, kernel, maddy,
mpe, npiggin, chleroy, pjw, palmer, aou, alex, tglx, mingo, bp,
dave.hansen, hpa, robh, saravanak, akpm, bhe, vgoyal, dyoung,
rdunlap, peterz, feng.tang, pawan.kumar.gupta, dapeng1.mi, kees,
elver, paulmck, lirongqing, safinaskar, rppt, ardb, jbohac,
cfsworks, osandov, tangyouling, sourabhjain, ritesh.list, eajames,
songshuaishuai, kevin.brodsky, samuel.holland, vishal.moola,
junhui.liu, coxu, liaoyuanhong, fuqiang.wang, x86, linux-doc,
linux-kernel, linux-arm-kernel, loongarch, linuxppc-dev,
linux-riscv, devicetree, kexec
In-Reply-To: <acETyW3FYaWCShUc@gmail.com>
On 2026/3/23 18:20, Breno Leitao wrote:
> On Mon, Mar 23, 2026 at 03:27:44PM +0800, Jinjie Ruan wrote:
>> Commit 35c18f2933c5 ("Add a new optional ",cma" suffix to the
>> crashkernel= command line option") and commit ab475510e042 ("kdump:
>> implement reserve_crashkernel_cma") added CMA support for kdump
>> crashkernel reservation.
>>
>> Crash kernel memory reservation wastes production resources if too
>> large, risks kdump failure if too small, and faces allocation difficulties
>> on fragmented systems due to contiguous block constraints. The new
>> CMA-based crashkernel reservation scheme splits the "large fixed
>> reservation" into a "small fixed region + large CMA dynamic region": the
>> CMA memory is available to userspace during normal operation to avoid
>> waste, and is reclaimed for kdump upon crash—saving memory while
>> improving reliability.
>>
>> So extend crashkernel CMA reservation support to arm64. The following
>> changes are made to enable CMA reservation:
>>
>> - Parse and obtain the CMA reservation size along with other crashkernel
>> parameters.
>> - Call reserve_crashkernel_cma() to allocate the CMA region for kdump.
>> - Include the CMA-reserved ranges for kdump kernel to use.
>> - Exclude the CMA-reserved ranges from the crash kernel memory to
>> prevent them from being exported through /proc/vmcore, which is already
>> done in the crash core.
>>
>> Update kernel-parameters.txt to document CMA support for crashkernel on
>> arm64 architecture.
>>
>> Acked-by: Rob Herring (Arm) <robh@kernel.org>
>> Acked-by: Baoquan He <bhe@redhat.com>
>> Acked-by: Mike Rapoport (Microsoft) <rppt@kernel.org>
>> Acked-by: Ard Biesheuvel <ardb@kernel.org>
>> Signed-off-by: Jinjie Ruan <ruanjinjie@huawei.com>
>> ---
>> v7:
>> - Correct the inclusion of CMA-reserved ranges for kdump
>> kernel in of/kexec.
>> v3:
>> - Add Acked-by.
>> v2:
>> - Free cmem in prepare_elf_headers()
>> - Add the mtivation.
>> ---
>> Documentation/admin-guide/kernel-parameters.txt | 2 +-
>> arch/arm64/kernel/machine_kexec_file.c | 2 +-
>> arch/arm64/mm/init.c | 5 +++--
>> drivers/of/fdt.c | 9 +++++----
>> drivers/of/kexec.c | 9 +++++++++
>> 5 files changed, 19 insertions(+), 8 deletions(-)
>>
>> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
>> index cb850e5290c2..afb3112510f7 100644
>> --- a/Documentation/admin-guide/kernel-parameters.txt
>> +++ b/Documentation/admin-guide/kernel-parameters.txt
>> @@ -1121,7 +1121,7 @@ Kernel parameters
>> It will be ignored when crashkernel=X,high is not used
>> or memory reserved is below 4G.
>> crashkernel=size[KMG],cma
>> - [KNL, X86, ppc] Reserve additional crash kernel memory from
>> + [KNL, X86, ARM64, PPC] Reserve additional crash kernel memory from
>> CMA. This reservation is usable by the first system's
>> userspace memory and kernel movable allocations (memory
>> balloon, zswap). Pages allocated from this memory range
>> diff --git a/arch/arm64/kernel/machine_kexec_file.c b/arch/arm64/kernel/machine_kexec_file.c
>> index c338506a580b..cc577d77df00 100644
>> --- a/arch/arm64/kernel/machine_kexec_file.c
>> +++ b/arch/arm64/kernel/machine_kexec_file.c
>> @@ -42,7 +42,7 @@ int arch_kimage_file_post_load_cleanup(struct kimage *image)
>> #ifdef CONFIG_CRASH_DUMP
>> unsigned int arch_get_system_nr_ranges(void)
>> {
>> - unsigned int nr_ranges = 2; /* for exclusion of crashkernel region */
>> + unsigned int nr_ranges = 2 + crashk_cma_cnt; /* for exclusion of crashkernel region */
>
> You update arch_get_system_nr_ranges() to account for CMA ranges, but
> prepare_elf_headers() in the same file (line 51) still has the
> hardcoded:
>
> nr_ranges = 2; /* for exclusion of crashkernel region */
I don't see any logic related to prepare_elf_headers() or hardcoded
nr_ranges = 2 in the arm64 implementation.
Did I miss something here?
>
> and does not exclude CMA ranges from cmem. If the generic crash core
> handles CMA exclusion from vmcore, then shouldn't
> arch_get_system_nr_ranges() also not need this change?
>
^ permalink raw reply
* Re: [PATCH v3 01/10] gpu: nova-core: convert PMC registers to kernel register macro
From: Danilo Krummrich @ 2026-03-23 11:21 UTC (permalink / raw)
To: Alexandre Courbot
Cc: Alice Ryhl, David Airlie, Simona Vetter, Maarten Lankhorst,
Maxime Ripard, Thomas Zimmermann, Miguel Ojeda, Boqun Feng,
Gary Guo, Björn Roy Baron, Benno Lossin, Andreas Hindborg,
Trevor Gross, John Hubbard, Alistair Popple, Joel Fernandes,
Timur Tabi, Zhi Wang, Eliot Courtney, dri-devel, linux-kernel,
linux-riscv, linux-doc, rust-for-linux
In-Reply-To: <20260323-b4-nova-register-v3-1-ae2486ecef1b@nvidia.com>
On Mon Mar 23, 2026 at 12:07 PM CET, Alexandre Courbot wrote:
> -impl TryFrom<u8> for Architecture {
> +impl TryFrom<Bounded<u32, 6>> for Architecture {
> type Error = Error;
>
> - fn try_from(value: u8) -> Result<Self> {
> - match value {
> + fn try_from(value: Bounded<u32, 6>) -> Result<Self> {
> + match u8::from(value) {
> 0x16 => Ok(Self::Turing),
> 0x17 => Ok(Self::Ampere),
> 0x19 => Ok(Self::Ada),
> @@ -155,23 +151,26 @@ fn try_from(value: u8) -> Result<Self> {
> }
> }
>
> -impl From<Architecture> for u8 {
> +impl From<Architecture> for Bounded<u32, 6> {
> fn from(value: Architecture) -> Self {
> - // CAST: `Architecture` is `repr(u8)`, so this cast is always lossless.
> - value as u8
> + match value {
> + Architecture::Turing => Bounded::<u32, _>::new::<0x16>(),
> + Architecture::Ampere => Bounded::<u32, _>::new::<0x17>(),
> + Architecture::Ada => Bounded::<u32, _>::new::<0x19>(),
> + }
> }
> }
Can this use bounded_enum!()?
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox