* [PATCH WIP 0/5] qcom-socinfo Rust Implementation
@ 2025-08-19 23:12 Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 1/5] rust: Add soc_device support Matthew Maurer
` (4 more replies)
0 siblings, 5 replies; 6+ messages in thread
From: Matthew Maurer @ 2025-08-19 23:12 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin, Dirk Beheme
Cc: linux-kernel, rust-for-linux, Matthew Maurer
THIS IS NOT A PROPOSAL TO LAND THIS SERIES
This is a demonstration of implementing a real driver that uses DebugFS,
primarily uploaded to serve as an example for API discussions on the
DebugFS series [1].
There are a number of rough edges (transmute, intra-module bindings) and
un-landed abstractions (SoC registration, randomness), but the main
purpose of this series is to look at sample usage of DebugFS in
* driver/soc/qcom/socinfo_rust/socinfo_rust_scoped.rs
* driver/soc/qcom/socinfo_rust/socinfo_rust_file.rs
to show how this would look in the two different APIs.
The example outputs byte-identical (as checked by recursive diff on
debugfs mounts) DebugFS files to the existing C driver. A real rewrite
would likely be simpler as it would be willing to not precisely mimic
newline quirks, etc.
[1] https://lore.kernel.org/all/20250819-debugfs-rust-v10-0-86e20f3cf3bb@google.com/
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
Matthew Maurer (5):
rust: Add soc_device support
rust: transmute: Cleanup + Fixes
rust: Add support for feeding entropy to randomness pool
soc: qcom: socinfo: `File`-based example
soc: qcom: socinfo: `Scoped`-based example
MAINTAINERS | 1 +
drivers/soc/qcom/Kconfig | 12 +
drivers/soc/qcom/Makefile | 2 +
drivers/soc/qcom/smem.c | 9 +
drivers/soc/qcom/socinfo_rust/bindings.rs | 126 +++++
drivers/soc/qcom/socinfo_rust/data.rs | 387 +++++++++++++++
drivers/soc/qcom/socinfo_rust/socinfo_rust_file.rs | 538 +++++++++++++++++++++
.../soc/qcom/socinfo_rust/socinfo_rust_scoped.rs | 367 ++++++++++++++
rust/bindings/bindings_helper.h | 8 +
rust/kernel/lib.rs | 4 +
rust/kernel/rand.rs | 14 +
rust/kernel/soc.rs | 137 ++++++
rust/kernel/transmute.rs | 126 +++--
13 files changed, 1681 insertions(+), 50 deletions(-)
---
base-commit: f3c5631f70e434e318c44001e2417d4770f06cd0
change-id: 20250818-qcom-socinfo-c58407a81ac6
prerequisite-message-id: <20250811213851.65644-1-christiansantoslima21@gmail.com>
prerequisite-patch-id: 9448855f52cb137ad246ae5fde9eab12eac5da94
prerequisite-change-id: 20250428-debugfs-rust-3cd5c97eb7d1:v10
prerequisite-patch-id: 78c729bc8c164ce7a7c3ff841e720518a7f28c54
prerequisite-patch-id: ca7c06b342af0d1f5b03f8907206f8b69a189c9b
prerequisite-patch-id: f3e7541c4e907d874c8f303c2da14dd3aafe05f6
prerequisite-patch-id: e892f83de14710bea11f042ed874c05ccb3bc972
prerequisite-patch-id: ce8a5988c32a6491bc36e87fb33b2e9adf254db8
prerequisite-patch-id: 0762df7763820dd608da15dfbd8b5309d7692aa9
prerequisite-patch-id: 23da818045894ee8c6f2a7d8ade002ccfc96cc3a
prerequisite-patch-id: 2bfdaa7c29838c44d80003c623067051617f2b5e
Best regards,
--
Matthew Maurer <mmaurer@google.com>
^ permalink raw reply [flat|nested] 6+ messages in thread
* [PATCH WIP 1/5] rust: Add soc_device support
2025-08-19 23:12 [PATCH WIP 0/5] qcom-socinfo Rust Implementation Matthew Maurer
@ 2025-08-19 23:12 ` Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 2/5] rust: transmute: Cleanup + Fixes Matthew Maurer
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Maurer @ 2025-08-19 23:12 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin, Dirk Beheme
Cc: linux-kernel, rust-for-linux, Matthew Maurer
Adds the ability to register SoC devices.
(This will be sent upstream in a separate request, it's uploaded now as
a dependency of the example driver.)
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
MAINTAINERS | 1 +
rust/bindings/bindings_helper.h | 1 +
rust/kernel/lib.rs | 2 +
rust/kernel/soc.rs | 137 ++++++++++++++++++++++++++++++++++++++++
4 files changed, 141 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 2cbe890085dbb6a652623b38dd0eadeeaa127a94..e0ff2731f1c2ae4bb01d361e99c1f4517fbd45d5 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7481,6 +7481,7 @@ F: rust/kernel/devres.rs
F: rust/kernel/driver.rs
F: rust/kernel/faux.rs
F: rust/kernel/platform.rs
+F: rust/kernel/soc.rs
F: samples/rust/rust_debugfs.rs
F: samples/rust/rust_scoped_debugfs.rs
F: samples/rust/rust_driver_platform.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index e847820dc807fdda2d682d496a3c6361bb944c10..140e2f4e60c0b745ac5d5c7456d60af28e21f55a 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -72,6 +72,7 @@
#include <linux/sched.h>
#include <linux/security.h>
#include <linux/slab.h>
+#include <linux/sys_soc.h>
#include <linux/tracepoint.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 828620c8441566a638f31d03633fc1bf4c1bda85..045f1088938cf646519edea2102439402fb27660 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -117,6 +117,8 @@
pub mod security;
pub mod seq_file;
pub mod sizes;
+#[cfg(CONFIG_SOC_BUS)]
+pub mod soc;
mod static_assert;
#[doc(hidden)]
pub mod std_vendor;
diff --git a/rust/kernel/soc.rs b/rust/kernel/soc.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b8412751a5ca8839e588cf5bd52f2e6a7f33d457
--- /dev/null
+++ b/rust/kernel/soc.rs
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! SoC Driver Abstraction
+//!
+//! C header: [`include/linux/sys_soc.h`](srctree/include/linux/sys_soc.h)
+
+use crate::bindings;
+use crate::error;
+use crate::prelude::*;
+use crate::str::CString;
+use core::marker::PhantomPinned;
+use core::ptr::addr_of;
+
+/// Attributes for a SoC device
+pub struct DeviceAttribute {
+ /// Machine
+ pub machine: Option<CString>,
+ /// Family
+ pub family: Option<CString>,
+ /// Revision
+ pub revision: Option<CString>,
+ /// Serial Number
+ pub serial_number: Option<CString>,
+ /// SoC ID
+ pub soc_id: Option<CString>,
+}
+
+// SAFETY: We provide no operations through `&BuiltDeviceAttribute`
+unsafe impl Sync for BuiltDeviceAttribute {}
+
+// SAFETY: All pointers are normal allocations, not thread-specific
+unsafe impl Send for BuiltDeviceAttribute {}
+
+#[pin_data]
+struct BuiltDeviceAttribute {
+ #[pin]
+ backing: DeviceAttribute,
+ inner: bindings::soc_device_attribute,
+ // Since `inner` has pointers to `backing`, we are !Unpin
+ #[pin]
+ _pin: PhantomPinned,
+}
+
+fn cstring_to_c(mcs: &Option<CString>) -> *const kernel::ffi::c_char {
+ mcs.as_ref()
+ .map(|cs| cs.as_char_ptr())
+ .unwrap_or(core::ptr::null())
+}
+
+impl BuiltDeviceAttribute {
+ fn as_mut_ptr(&self) -> *mut bindings::soc_device_attribute {
+ core::ptr::from_ref(&self.inner).cast_mut()
+ }
+}
+
+impl DeviceAttribute {
+ fn build(self) -> impl PinInit<BuiltDeviceAttribute> {
+ pin_init!(BuiltDeviceAttribute {
+ inner: bindings::soc_device_attribute {
+ machine: cstring_to_c(&self.machine),
+ family: cstring_to_c(&self.family),
+ revision: cstring_to_c(&self.revision),
+ serial_number: cstring_to_c(&self.serial_number),
+ soc_id: cstring_to_c(&self.soc_id),
+ data: core::ptr::null(),
+ custom_attr_group: core::ptr::null(),
+ },
+ backing: self,
+ _pin: PhantomPinned,
+ })
+ }
+}
+
+// SAFETY: We provide no operations through &Device
+unsafe impl Sync for Device {}
+
+// SAFETY: Device holds a pointer to a `soc_device`, which may be sent to any thread.
+unsafe impl Send for Device {}
+
+/// A registered soc device
+#[repr(transparent)]
+pub struct Device(*mut bindings::soc_device);
+
+impl Device {
+ /// # Safety
+ /// * `attr` must be pinned
+ /// * `attr` must be valid for reads during the function call
+ /// * If a device is returned (e.g. no error), `attr` must remain valid for reads until the
+ /// returned `Device` is dropped.
+ unsafe fn register(attr: *const BuiltDeviceAttribute) -> Result<Device> {
+ let raw_soc =
+ // SAFETY: The struct provided through attr is backed by pinned data next to it, so as
+ // long as attr lives, the strings pointed to by the struct will too. By caller
+ // invariant, `attr` is pinned, so the pinned data won't move. By caller invariant,
+ // `attr` is valid during this call. If it returns a device, and so others may try to
+ // read this data, by caller invariant, `attr` won't be released until the device is.
+ error::from_err_ptr(unsafe { bindings::soc_device_register((*attr).as_mut_ptr()) })?;
+ Ok(Device(raw_soc))
+ }
+}
+
+#[pin_data(PinnedDrop)]
+/// Registration handle for your soc_dev. If you let it go out of scope, your soc_dev will be
+/// unregistered.
+pub struct DeviceRegistration {
+ #[pin]
+ attr: BuiltDeviceAttribute,
+ soc_dev: Device,
+ // Since Device transitively points to the contents of attr, we are !Unpin
+ #[pin]
+ _pin: PhantomPinned,
+}
+
+#[pinned_drop]
+impl PinnedDrop for DeviceRegistration {
+ fn drop(self: Pin<&mut Self>) {
+ // SAFETY: Device always contains a live pointer to a soc_device that can be unregistered
+ unsafe { bindings::soc_device_unregister(self.soc_dev.0) }
+ }
+}
+
+impl DeviceRegistration {
+ /// Register a new SoC device
+ pub fn register(attr: DeviceAttribute) -> impl PinInit<Self, Error> {
+ try_pin_init!(&this in Self {
+ attr <- attr.build(),
+ // SAFETY: We have already initialized attr, and we are inside PinInit and Self
+ // is !Unpin, so attr won't be moved and is valid. If it returns success, attr
+ // will not be dropped until after our `PinnedDrop` implementation runs, so the
+ // device will be unregistered first.
+ soc_dev: unsafe { Device::register(addr_of!((*this.as_ptr()).attr))? },
+ _pin: PhantomPinned,
+ }? Error)
+ }
+}
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH WIP 2/5] rust: transmute: Cleanup + Fixes
2025-08-19 23:12 [PATCH WIP 0/5] qcom-socinfo Rust Implementation Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 1/5] rust: Add soc_device support Matthew Maurer
@ 2025-08-19 23:12 ` Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 3/5] rust: Add support for feeding entropy to randomness pool Matthew Maurer
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Maurer @ 2025-08-19 23:12 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin, Dirk Beheme
Cc: linux-kernel, rust-for-linux, Matthew Maurer
This change is not intended to go upstream as-is, the original
`FromBytes`/`AsBytes` is being litigated on the list. This just fixes it
up so that I can use it for this example.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
rust/kernel/lib.rs | 1 +
rust/kernel/transmute.rs | 126 ++++++++++++++++++++++++++++-------------------
2 files changed, 77 insertions(+), 50 deletions(-)
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 045f1088938cf646519edea2102439402fb27660..0461f25cb5aee797d25153a2004d63b6b41f4ae3 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -18,6 +18,7 @@
//
// Stable since Rust 1.79.0.
#![feature(inline_const)]
+#![feature(pointer_is_aligned)]
//
// Stable since Rust 1.81.0.
#![feature(lint_reasons)]
diff --git a/rust/kernel/transmute.rs b/rust/kernel/transmute.rs
index ba21fe49e4f07808c0a43f16461b535fadc033f1..452b1cfb1dbecfdddec7bb59204f7290ae5040af 100644
--- a/rust/kernel/transmute.rs
+++ b/rust/kernel/transmute.rs
@@ -46,63 +46,71 @@ fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut Self>
Self: AsBytes;
}
-/// Provide an auto-implementation of FromBytes's methods for all
-/// sized types, if you need an implementation for your type use this instead.
-///
-/// # Safety
-///
-/// All bit-patterns must be valid for this type. This type must not have interior mutability.
-pub unsafe trait FromBytesSized: Sized {}
+/// Helper for implementing `from_bytes` for sized types.
+pub fn sized_from_bytes<T: FromBytes>(bytes: &[u8]) -> Option<&T> {
+ if bytes.len() == core::mem::size_of::<T>() {
+ let slice_ptr = bytes.as_ptr().cast::<T>();
+ if !slice_ptr.is_aligned() {
+ None
+ } else {
+ // SAFETY:
+ // * T is FromBytes, so anything in the bytes array is a valid bit pattern
+ // * The pointer is aligned
+ // * The pointer points to a region of the appropriate size
+ unsafe { Some(&*slice_ptr) }
+ }
+ } else {
+ None
+ }
+}
-macro_rules! impl_frombytessized {
+/// Helper for implementing `from_bytes_mut` for sized types.
+pub fn sized_from_bytes_mut<T: FromBytes + AsBytes>(bytes: &mut [u8]) -> Option<&mut T> {
+ if bytes.len() == core::mem::size_of::<T>() {
+ let slice_ptr = bytes.as_mut_ptr().cast::<T>();
+ if !slice_ptr.is_aligned() {
+ None
+ } else {
+ // SAFETY:
+ // * T is FromBytes, so anything in the bytes array is a valid bit pattern
+ // * T is AsBytes, so mutating T will not expose padding to the byte array
+ // * The pointer is aligned
+ // * The pointer points to a region of the appropriate size
+ unsafe { Some(&mut *slice_ptr) }
+ }
+ } else {
+ None
+ }
+}
+
+macro_rules! impl_from_bytes{
($($({$($generics:tt)*})? $t:ty, )*) => {
// SAFETY: Safety comments written in the macro invocation.
- $(unsafe impl$($($generics)*)? FromBytesSized for $t {})*
+ $(unsafe impl$($($generics)*)? FromBytes for $t {
+ fn from_bytes(bytes: &[u8]) -> Option<&$t> {
+ sized_from_bytes(bytes)
+ }
+
+ fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut $t>
+ where
+ Self: AsBytes,
+ {
+ sized_from_bytes_mut(bytes)
+ }
+ })*
};
}
-impl_frombytessized! {
+impl_from_bytes! {
// SAFETY: All bit patterns are acceptable values of the types below.
+ // Checking the pointer size makes this operation safe and it's necessary
+ // to dereference to get the value and return it as a reference to `Self`.
u8, u16, u32, u64, usize,
i8, i16, i32, i64, isize,
// SAFETY: If all bit patterns are acceptable for individual values in an array, then all bit
// patterns are also acceptable for arrays of that type.
- {<T: FromBytesSized, const N: usize>} [T; N],
-}
-
-// SAFETY: The `FromBytesSized` implementation guarantees that all bit
-// patterns are acceptable values of the types and in array case if
-// all bit patterns are acceptable for individual values in an array,
-// then all bit patterns are also acceptable for arrays of that type.
-unsafe impl<T> FromBytes for T
-where
- T: FromBytesSized,
-{
- fn from_bytes(bytes: &[u8]) -> Option<&Self> {
- let slice_ptr = bytes.as_ptr().cast::<T>();
- let size = ::core::mem::size_of::<T>();
- if bytes.len() == size && slice_ptr.is_aligned() {
- // SAFETY: Since the code checks the size and alignment, the slice is valid.
- unsafe { Some(&*slice_ptr) }
- } else {
- None
- }
- }
-
- fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut Self>
- where
- Self: AsBytes,
- {
- let slice_ptr = bytes.as_mut_ptr().cast::<T>();
- let size = ::core::mem::size_of::<T>();
- if bytes.len() == size && slice_ptr.is_aligned() {
- // SAFETY: Since the code checks the size and alignment, the slice is valid.
- unsafe { Some(&mut *slice_ptr) }
- } else {
- None
- }
- }
+ {<T: FromBytes, const N: usize>} [T; N],
}
// SAFETY: If all bit patterns are acceptable for individual values in an array, then all bit
@@ -110,7 +118,7 @@ fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut Self>
unsafe impl<T: FromBytes> FromBytes for [T] {
fn from_bytes(bytes: &[u8]) -> Option<&Self> {
let size = ::core::mem::size_of::<T>();
- build_assert!(size == 0, "Can't create a slice with zero elements");
+ build_assert!(size != 0, "Can't create a slice with zero-sized elements");
let slice_ptr = bytes.as_ptr().cast::<T>();
if bytes.len() % size == 0 && slice_ptr.is_aligned() {
// SAFETY: Since the number of elements is different from
@@ -126,7 +134,7 @@ fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut Self>
Self: AsBytes,
{
let size = ::core::mem::size_of::<T>();
- build_assert!(size == 0, "Can't create a slice with zero elements");
+ build_assert!(size != 0, "Can't create a slice with zero-sized elements");
let slice_ptr = bytes.as_mut_ptr().cast::<T>();
if bytes.len() % size == 0 && slice_ptr.is_aligned() {
// SAFETY: Since the number of elements is different from
@@ -158,16 +166,34 @@ fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut Self>
///
/// Values of this type may not contain any uninitialized bytes. This type must not have interior
/// mutability.
-pub unsafe trait AsBytes {}
+pub unsafe trait AsBytes {
+ /// View data structure as a buffer
+ fn as_bytes(&self) -> &[u8] {
+ let len = core::mem::size_of_val(self);
+ // SAFETY: By unsafe trait impl precondition, there's no interior mutability and no
+ // uninitialized bytes.
+ unsafe { core::slice::from_raw_parts(core::ptr::from_ref(self).cast::<u8>(), len) }
+ }
+ /// View data structure as a mutable buffer
+ fn as_mut_bytes(&mut self) -> &mut [u8]
+ where
+ Self: FromBytes,
+ {
+ let len = core::mem::size_of_val(self);
+ // SAFETY: By unsafe trait impl precondition, there's no interior mutability, and no
+ // unititialized bytes. By FromBytes trait impl precondition, all bit patterns are valid.
+ unsafe { core::slice::from_raw_parts_mut(core::ptr::from_mut(self).cast::<u8>(), len) }
+ }
+}
-macro_rules! impl_asbytes {
+macro_rules! impl_as_bytes {
($($({$($generics:tt)*})? $t:ty, )*) => {
// SAFETY: Safety comments written in the macro invocation.
$(unsafe impl$($($generics)*)? AsBytes for $t {})*
};
}
-impl_asbytes! {
+impl_as_bytes! {
// SAFETY: Instances of the following types have no uninitialized portions.
u8, u16, u32, u64, usize,
i8, i16, i32, i64, isize,
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH WIP 3/5] rust: Add support for feeding entropy to randomness pool
2025-08-19 23:12 [PATCH WIP 0/5] qcom-socinfo Rust Implementation Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 1/5] rust: Add soc_device support Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 2/5] rust: transmute: Cleanup + Fixes Matthew Maurer
@ 2025-08-19 23:12 ` Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 4/5] soc: qcom: socinfo: `File`-based example Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 5/5] soc: qcom: socinfo: `Scoped`-based example Matthew Maurer
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Maurer @ 2025-08-19 23:12 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin, Dirk Beheme
Cc: linux-kernel, rust-for-linux, Matthew Maurer
(This will be separately sent to the list later.)
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
rust/bindings/bindings_helper.h | 1 +
rust/kernel/lib.rs | 1 +
rust/kernel/rand.rs | 14 ++++++++++++++
3 files changed, 16 insertions(+)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 140e2f4e60c0b745ac5d5c7456d60af28e21f55a..afe8be55a6db1d29706c19a91dc51c1ae3494ed4 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -67,6 +67,7 @@
#include <linux/pm_opp.h>
#include <linux/poll.h>
#include <linux/property.h>
+#include <linux/random.h>
#include <linux/refcount.h>
#include <linux/regulator/consumer.h>
#include <linux/sched.h>
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 0461f25cb5aee797d25153a2004d63b6b41f4ae3..39a9088a8d63f0ae54a0101eaeb03b17f82e12e8 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -112,6 +112,7 @@
pub mod platform;
pub mod prelude;
pub mod print;
+pub mod rand;
pub mod rbtree;
pub mod regulator;
pub mod revocable;
diff --git a/rust/kernel/rand.rs b/rust/kernel/rand.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b3fb30f40a8950ac7b47d48129eb89024a1cbd26
--- /dev/null
+++ b/rust/kernel/rand.rs
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Randomness.
+//!
+//! C header: [`include/linux/random.h`](../../../../include/linux/random.h)
+
+use crate::bindings;
+use crate::ffi::c_void;
+
+/// Adds the given buffer to the entropy pool.
+pub fn add_device_randomness(buf: &[u8]) {
+ // SAFETY: We just need the pointer to be valid for the length, which a slice provides.
+ unsafe { bindings::add_device_randomness(buf.as_ptr().cast::<c_void>(), buf.len()) };
+}
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH WIP 4/5] soc: qcom: socinfo: `File`-based example
2025-08-19 23:12 [PATCH WIP 0/5] qcom-socinfo Rust Implementation Matthew Maurer
` (2 preceding siblings ...)
2025-08-19 23:12 ` [PATCH WIP 3/5] rust: Add support for feeding entropy to randomness pool Matthew Maurer
@ 2025-08-19 23:12 ` Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 5/5] soc: qcom: socinfo: `Scoped`-based example Matthew Maurer
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Maurer @ 2025-08-19 23:12 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin, Dirk Beheme
Cc: linux-kernel, rust-for-linux, Matthew Maurer
Re-implements qcom-socinfo driver in Rust, using `File`-based DebugFS
bindings.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
drivers/soc/qcom/Kconfig | 12 +
drivers/soc/qcom/Makefile | 1 +
drivers/soc/qcom/smem.c | 4 +
drivers/soc/qcom/socinfo_rust/bindings.rs | 126 +++++
drivers/soc/qcom/socinfo_rust/data.rs | 387 +++++++++++++++
drivers/soc/qcom/socinfo_rust/socinfo_rust_file.rs | 538 +++++++++++++++++++++
rust/bindings/bindings_helper.h | 6 +
7 files changed, 1074 insertions(+)
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 2caadbbcf8307ff94f5afbdd1481e5e5e291749f..362ea8c1a18a24a06bb5456e7d1ac8a0cb1003b5 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -227,6 +227,18 @@ config QCOM_SOCINFO
Say yes here to support the Qualcomm socinfo driver, providing
information about the SoC to user space.
+config QCOM_SOCINFO_RUST
+ tristate "Qualcomm socinfo driver"
+ depends on QCOM_SMEM && RUST
+ select SOC_BUS
+ help
+ Say yes here to support the Qualcomm socinfo driver, providing
+ information about the SoC to user space.
+
+ This is a draft Rust re-implementation which will enable two
+ additional copies of the driver to registered alongside the
+ original.
+
config QCOM_SPM
tristate "Qualcomm Subsystem Power Manager (SPM)"
depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index b7f1d2a5736748b8772c090fd24462fa91f321c6..6453a4f4f7da89d4e1677ad8f75257e94cec90f4 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -40,3 +40,4 @@ qcom_ice-objs += ice.o
obj-$(CONFIG_QCOM_INLINE_CRYPTO_ENGINE) += qcom_ice.o
obj-$(CONFIG_QCOM_PBS) += qcom-pbs.o
obj-$(CONFIG_QCOM_UBWC_CONFIG) += ubwc_config.o
+obj-$(CONFIG_QCOM_SOCINFO_RUST) += socinfo_rust/socinfo_rust_file.o
diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c
index cf425930539e406fb81307a17803955371545f32..5279db46a1e0dd7b2994b79d131367175e35290f 100644
--- a/drivers/soc/qcom/smem.c
+++ b/drivers/soc/qcom/smem.c
@@ -1230,7 +1230,11 @@ static int qcom_smem_probe(struct platform_device *pdev)
__smem = smem;
+ // TODO don't double load, this is just to diff the debugfs
smem->socinfo = platform_device_register_data(&pdev->dev, "qcom-socinfo",
+ PLATFORM_DEVID_NONE, NULL, 0);
+ // TODO MODULE_ALIAS
+ smem->socinfo = platform_device_register_data(&pdev->dev, "qcom_socinfo_driver_file_rust",
PLATFORM_DEVID_NONE, NULL,
0);
if (IS_ERR(smem->socinfo))
diff --git a/drivers/soc/qcom/socinfo_rust/bindings.rs b/drivers/soc/qcom/socinfo_rust/bindings.rs
new file mode 100644
index 0000000000000000000000000000000000000000..8c2b5cecbfdde05e1a81ad0c78be0e7b6432afc5
--- /dev/null
+++ b/drivers/soc/qcom/socinfo_rust/bindings.rs
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+use core::ops::Deref;
+use kernel::error::from_err_ptr;
+use kernel::ffi::c_char;
+use kernel::prelude::*;
+use kernel::transmute::{sized_from_bytes, sized_from_bytes_mut, AsBytes, FromBytes};
+
+pub(crate) fn qcom_smem_get(host: i32, item: u32) -> Result<&'static [u8]> {
+ let mut size = 0;
+ // SAFETY: qcom_smem_get only requires that the size pointer be a writable size_t,
+ // host and item are error checked in the qcom_smem module.
+ let err_ptr = unsafe { kernel::bindings::qcom_smem_get(host as u32, item, &mut size) };
+ let ptr = from_err_ptr(err_ptr)?;
+ // SAFETY: If qcom_smem_get does not return an error, the returned pointer points to a readable
+ // byte buffer with its size written into size. Because these buffers are derived from the
+ // static ranges in the DT, this buffer remains accessible even if the qcom_smem module is
+ // unloaded, so 'static is appropriate. The underlying buffer cannot mutate, so upgrading it
+ // to a reference is allowed.
+ Ok(unsafe { core::slice::from_raw_parts(ptr as *const u8, size) })
+}
+
+pub(crate) const SMEM_IMAGE_VERSION_TABLE: u32 = 469;
+
+// bindgen does not know that struct socinfo is AsBytes, so we implement it here
+#[derive(Default, Copy, Clone)]
+#[repr(transparent)]
+pub(crate) struct RawSocInfo(kernel::bindings::socinfo);
+// SAFETY: Transparent wrapper for the socinfo struct, which is a padding-free
+// C-origin type
+unsafe impl AsBytes for RawSocInfo {}
+// SAFETY: Transparent wrapper for the socinfo struct, a C-origin type
+unsafe impl FromBytes for RawSocInfo {
+ fn from_bytes(bytes: &[u8]) -> Option<&Self> {
+ sized_from_bytes(bytes)
+ }
+ fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut Self> {
+ sized_from_bytes_mut(bytes)
+ }
+}
+
+impl Deref for RawSocInfo {
+ type Target = kernel::bindings::socinfo;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl RawSocInfo {
+ /// Produce RawSocInfo by initializing from a byte buffer that may be too small.
+ /// Code using the resulting value is expected to use the info_fmt field to determine
+ /// which fields have meaningful values.
+ pub(crate) fn from_partial_bytes(soc_info_mem: &[u8]) -> Self {
+ let mut soc_info = RawSocInfo::default();
+ let byte_view = soc_info.as_mut_bytes();
+ let len = core::cmp::min(soc_info_mem.len(), byte_view.len());
+ byte_view[..len].copy_from_slice(&soc_info_mem[..len]);
+ soc_info
+ }
+}
+
+#[repr(C)]
+pub(crate) struct PmicEntry {
+ pub(crate) model: u32,
+ pub(crate) die_rev: u32,
+}
+
+// SAFETY: All bit patterns are valid for all fields, and C layout for two u32s
+// does not require padding
+unsafe impl AsBytes for PmicEntry {}
+// SAFETY: All bit patterns are valid for all fields
+unsafe impl FromBytes for PmicEntry {
+ fn from_bytes(bytes: &[u8]) -> Option<&Self> {
+ sized_from_bytes(bytes)
+ }
+ fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut Self> {
+ sized_from_bytes_mut(bytes)
+ }
+}
+
+#[derive(Default)]
+#[repr(transparent)]
+pub(crate) struct PmicArray(pub(crate) &'static [PmicEntry]);
+
+impl PmicArray {
+ pub(crate) fn from_bytes(bytes: &'static [u8]) -> Option<Self> {
+ Some(Self(FromBytes::from_bytes(bytes)?))
+ }
+}
+
+const SMEM_IMAGE_VERSION_NAME_SIZE: usize = 75;
+const SMEM_IMAGE_VERSION_VARIANT_SIZE: usize = 20;
+const SMEM_IMAGE_VERSION_OEM_SIZE: usize = 32;
+
+#[repr(C)]
+pub(crate) struct ImageVersion {
+ pub(crate) name: [c_char; SMEM_IMAGE_VERSION_NAME_SIZE],
+ pub(crate) variant: [c_char; SMEM_IMAGE_VERSION_VARIANT_SIZE],
+ pub(crate) pad: c_char,
+ pub(crate) oem: [c_char; SMEM_IMAGE_VERSION_OEM_SIZE],
+}
+
+// SAFETY: All bit patterns are valid for all fields, and C layout for two u32s
+// does not require padding
+unsafe impl AsBytes for ImageVersion {}
+// SAFETY: All bit patterns are valid for all fields
+unsafe impl FromBytes for ImageVersion {
+ fn from_bytes(bytes: &[u8]) -> Option<&Self> {
+ sized_from_bytes(bytes)
+ }
+ fn from_bytes_mut(bytes: &mut [u8]) -> Option<&mut Self> {
+ sized_from_bytes_mut(bytes)
+ }
+}
+
+#[derive(Default)]
+#[repr(transparent)]
+pub(crate) struct ImageVersions(pub(crate) &'static [ImageVersion]);
+
+impl ImageVersions {
+ pub(crate) fn from_bytes(bytes: &'static [u8]) -> Option<Self> {
+ Some(Self(FromBytes::from_bytes(bytes)?))
+ }
+}
diff --git a/drivers/soc/qcom/socinfo_rust/data.rs b/drivers/soc/qcom/socinfo_rust/data.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1575d3c5452f801ae27c2c67ea71410e36521846
--- /dev/null
+++ b/drivers/soc/qcom/socinfo_rust/data.rs
@@ -0,0 +1,387 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! Data tables for QCom SocInfo driver
+use kernel::c_str;
+use kernel::prelude::*;
+
+macro_rules! id_entry {
+ ($id:ident) => {
+ kernel::macros::paste! {
+ SocId {
+ id: kernel::bindings::[<QCOM_ID_ $id>],
+ name: c_str!(stringify!($id)),
+ }
+ }
+ };
+ ($id:ident, $name:tt) => {
+ SocId {
+ id: kernel::macros::paste!(kernel::bindings::[<QCOM_ID_ $id>]), name: c_str!($name),
+ }
+ }
+}
+
+pub(crate) struct SocId {
+ pub(crate) id: u32,
+ pub(crate) name: &'static CStr,
+}
+
+pub(crate) static SOC_IDS: &[SocId] = &[
+ { id_entry!(MSM8260) },
+ { id_entry!(MSM8660) },
+ { id_entry!(APQ8060) },
+ { id_entry!(MSM8960) },
+ { id_entry!(APQ8064) },
+ { id_entry!(MSM8930) },
+ { id_entry!(MSM8630) },
+ { id_entry!(MSM8230) },
+ { id_entry!(APQ8030) },
+ { id_entry!(MSM8627) },
+ { id_entry!(MSM8227) },
+ { id_entry!(MSM8660A) },
+ { id_entry!(MSM8260A) },
+ { id_entry!(APQ8060A) },
+ { id_entry!(MSM8974) },
+ { id_entry!(MSM8225) },
+ { id_entry!(MSM8625) },
+ { id_entry!(MPQ8064) },
+ { id_entry!(MSM8960AB) },
+ { id_entry!(APQ8060AB) },
+ { id_entry!(MSM8260AB) },
+ { id_entry!(MSM8660AB) },
+ { id_entry!(MSM8930AA) },
+ { id_entry!(MSM8630AA) },
+ { id_entry!(MSM8230AA) },
+ { id_entry!(MSM8626) },
+ { id_entry!(MSM8610) },
+ { id_entry!(APQ8064AB) },
+ { id_entry!(MSM8930AB) },
+ { id_entry!(MSM8630AB) },
+ { id_entry!(MSM8230AB) },
+ { id_entry!(APQ8030AB) },
+ { id_entry!(MSM8226) },
+ { id_entry!(MSM8526) },
+ { id_entry!(APQ8030AA) },
+ { id_entry!(MSM8110) },
+ { id_entry!(MSM8210) },
+ { id_entry!(MSM8810) },
+ { id_entry!(MSM8212) },
+ { id_entry!(MSM8612) },
+ { id_entry!(MSM8112) },
+ { id_entry!(MSM8125) },
+ { id_entry!(MSM8225Q) },
+ { id_entry!(MSM8625Q) },
+ { id_entry!(MSM8125Q) },
+ { id_entry!(APQ8064AA) },
+ { id_entry!(APQ8084) },
+ { id_entry!(MSM8130) },
+ { id_entry!(MSM8130AA) },
+ { id_entry!(MSM8130AB) },
+ { id_entry!(MSM8627AA) },
+ { id_entry!(MSM8227AA) },
+ { id_entry!(APQ8074) },
+ { id_entry!(MSM8274) },
+ { id_entry!(MSM8674) },
+ { id_entry!(MDM9635) },
+ { id_entry!(MSM8974PRO_AC, "MSM8974PRO-AC") },
+ { id_entry!(MSM8126) },
+ { id_entry!(APQ8026) },
+ { id_entry!(MSM8926) },
+ { id_entry!(IPQ8062) },
+ { id_entry!(IPQ8064) },
+ { id_entry!(IPQ8066) },
+ { id_entry!(IPQ8068) },
+ { id_entry!(MSM8326) },
+ { id_entry!(MSM8916) },
+ { id_entry!(MSM8994) },
+ { id_entry!(APQ8074PRO_AA, "APQ8074PRO-AA") },
+ { id_entry!(APQ8074PRO_AB, "APQ8074PRO-AB") },
+ { id_entry!(APQ8074PRO_AC, "APQ8074PRO-AC") },
+ { id_entry!(MSM8274PRO_AA, "MSM8274PRO-AA") },
+ { id_entry!(MSM8274PRO_AB, "MSM8274PRO-AB") },
+ { id_entry!(MSM8274PRO_AC, "MSM8274PRO-AC") },
+ { id_entry!(MSM8674PRO_AA, "MSM8674PRO-AA") },
+ { id_entry!(MSM8674PRO_AB, "MSM8674PRO-AB") },
+ { id_entry!(MSM8674PRO_AC, "MSM8674PRO-AC") },
+ { id_entry!(MSM8974PRO_AA, "MSM8974PRO-AA") },
+ { id_entry!(MSM8974PRO_AB, "MSM8974PRO-AB") },
+ { id_entry!(APQ8028) },
+ { id_entry!(MSM8128) },
+ { id_entry!(MSM8228) },
+ { id_entry!(MSM8528) },
+ { id_entry!(MSM8628) },
+ { id_entry!(MSM8928) },
+ { id_entry!(MSM8510) },
+ { id_entry!(MSM8512) },
+ { id_entry!(MSM8936) },
+ { id_entry!(MDM9640) },
+ { id_entry!(MSM8939) },
+ { id_entry!(APQ8036) },
+ { id_entry!(APQ8039) },
+ { id_entry!(MSM8236) },
+ { id_entry!(MSM8636) },
+ { id_entry!(MSM8909) },
+ { id_entry!(MSM8996) },
+ { id_entry!(APQ8016) },
+ { id_entry!(MSM8216) },
+ { id_entry!(MSM8116) },
+ { id_entry!(MSM8616) },
+ { id_entry!(MSM8992) },
+ { id_entry!(APQ8092) },
+ { id_entry!(APQ8094) },
+ { id_entry!(MSM8209) },
+ { id_entry!(MSM8208) },
+ { id_entry!(MDM9209) },
+ { id_entry!(MDM9309) },
+ { id_entry!(MDM9609) },
+ { id_entry!(MSM8239) },
+ { id_entry!(MSM8952) },
+ { id_entry!(APQ8009) },
+ { id_entry!(MSM8956) },
+ { id_entry!(MSM8929) },
+ { id_entry!(MSM8629) },
+ { id_entry!(MSM8229) },
+ { id_entry!(APQ8029) },
+ { id_entry!(APQ8056) },
+ { id_entry!(MSM8609) },
+ { id_entry!(APQ8076) },
+ { id_entry!(MSM8976) },
+ { id_entry!(IPQ8065) },
+ { id_entry!(IPQ8069) },
+ { id_entry!(MDM9650) },
+ { id_entry!(MDM9655) },
+ { id_entry!(MDM9250) },
+ { id_entry!(MDM9255) },
+ { id_entry!(MDM9350) },
+ { id_entry!(APQ8052) },
+ { id_entry!(MDM9607) },
+ { id_entry!(APQ8096) },
+ { id_entry!(MSM8998) },
+ { id_entry!(MSM8953) },
+ { id_entry!(MSM8937) },
+ { id_entry!(APQ8037) },
+ { id_entry!(MDM8207) },
+ { id_entry!(MDM9207) },
+ { id_entry!(MDM9307) },
+ { id_entry!(MDM9628) },
+ { id_entry!(MSM8909W) },
+ { id_entry!(APQ8009W) },
+ { id_entry!(MSM8996L) },
+ { id_entry!(MSM8917) },
+ { id_entry!(APQ8053) },
+ { id_entry!(MSM8996SG) },
+ { id_entry!(APQ8017) },
+ { id_entry!(MSM8217) },
+ { id_entry!(MSM8617) },
+ { id_entry!(MSM8996AU) },
+ { id_entry!(APQ8096AU) },
+ { id_entry!(APQ8096SG) },
+ { id_entry!(MSM8940) },
+ { id_entry!(SDX201) },
+ { id_entry!(SDM660) },
+ { id_entry!(SDM630) },
+ { id_entry!(APQ8098) },
+ { id_entry!(MSM8920) },
+ { id_entry!(SDM845) },
+ { id_entry!(MDM9206) },
+ { id_entry!(IPQ8074) },
+ { id_entry!(SDA660) },
+ { id_entry!(SDM658) },
+ { id_entry!(SDA658) },
+ { id_entry!(SDA630) },
+ { id_entry!(MSM8905) },
+ { id_entry!(SDX202) },
+ { id_entry!(SDM670) },
+ { id_entry!(SDM450) },
+ { id_entry!(SM8150) },
+ { id_entry!(SDA845) },
+ { id_entry!(IPQ8072) },
+ { id_entry!(IPQ8076) },
+ { id_entry!(IPQ8078) },
+ { id_entry!(SDM636) },
+ { id_entry!(SDA636) },
+ { id_entry!(SDM632) },
+ { id_entry!(SDA632) },
+ { id_entry!(SDA450) },
+ { id_entry!(SDM439) },
+ { id_entry!(SDM429) },
+ { id_entry!(SM8250) },
+ { id_entry!(SA8155) },
+ { id_entry!(SDA439) },
+ { id_entry!(SDA429) },
+ { id_entry!(SM7150) },
+ { id_entry!(SM7150P) },
+ { id_entry!(IPQ8070) },
+ { id_entry!(IPQ8071) },
+ { id_entry!(QM215) },
+ { id_entry!(IPQ8072A) },
+ { id_entry!(IPQ8074A) },
+ { id_entry!(IPQ8076A) },
+ { id_entry!(IPQ8078A) },
+ { id_entry!(SM6125) },
+ { id_entry!(IPQ8070A) },
+ { id_entry!(IPQ8071A) },
+ { id_entry!(IPQ8172) },
+ { id_entry!(IPQ8173) },
+ { id_entry!(IPQ8174) },
+ { id_entry!(IPQ6018) },
+ { id_entry!(IPQ6028) },
+ { id_entry!(SDM429W) },
+ { id_entry!(SM4250) },
+ { id_entry!(IPQ6000) },
+ { id_entry!(IPQ6010) },
+ { id_entry!(SC7180) },
+ { id_entry!(SM6350) },
+ { id_entry!(QCM2150) },
+ { id_entry!(SDA429W) },
+ { id_entry!(SM8350) },
+ { id_entry!(QCM2290) },
+ { id_entry!(SM7125) },
+ { id_entry!(SM6115) },
+ { id_entry!(IPQ5010) },
+ { id_entry!(IPQ5018) },
+ { id_entry!(IPQ5028) },
+ { id_entry!(SC8280XP) },
+ { id_entry!(IPQ6005) },
+ { id_entry!(QRB5165) },
+ { id_entry!(SM8450) },
+ { id_entry!(SM7225) },
+ { id_entry!(SA8295P) },
+ { id_entry!(SA8540P) },
+ { id_entry!(QCM4290) },
+ { id_entry!(QCS4290) },
+ { id_entry!(SM7325) },
+ { id_entry!(SM8450_2, "SM8450") },
+ { id_entry!(SM8450_3, "SM8450") },
+ { id_entry!(SC7280) },
+ { id_entry!(SC7180P) },
+ { id_entry!(QCM6490) },
+ { id_entry!(SM7325P) },
+ { id_entry!(IPQ5000) },
+ { id_entry!(IPQ0509) },
+ { id_entry!(IPQ0518) },
+ { id_entry!(SM6375) },
+ { id_entry!(IPQ9514) },
+ { id_entry!(IPQ9550) },
+ { id_entry!(IPQ9554) },
+ { id_entry!(IPQ9570) },
+ { id_entry!(IPQ9574) },
+ { id_entry!(SM8550) },
+ { id_entry!(IPQ5016) },
+ { id_entry!(IPQ9510) },
+ { id_entry!(QRB4210) },
+ { id_entry!(QRB2210) },
+ { id_entry!(SAR2130P) },
+ { id_entry!(SM8475) },
+ { id_entry!(SM8475P) },
+ { id_entry!(SA8255P) },
+ { id_entry!(SA8775P) },
+ { id_entry!(QRU1000) },
+ { id_entry!(SM8475_2) },
+ { id_entry!(QDU1000) },
+ { id_entry!(X1E80100) },
+ { id_entry!(SM8650) },
+ { id_entry!(SM4450) },
+ { id_entry!(SAR1130P) },
+ { id_entry!(QDU1010) },
+ { id_entry!(QRU1032) },
+ { id_entry!(QRU1052) },
+ { id_entry!(QRU1062) },
+ { id_entry!(IPQ5332) },
+ { id_entry!(IPQ5322) },
+ { id_entry!(IPQ5312) },
+ { id_entry!(IPQ5302) },
+ { id_entry!(QCS8550) },
+ { id_entry!(QCM8550) },
+ { id_entry!(IPQ5300) },
+ { id_entry!(IPQ5321) },
+ { id_entry!(IPQ5424) },
+ { id_entry!(IPQ5404) },
+ { id_entry!(QCS9100) },
+ { id_entry!(QCS8300) },
+ { id_entry!(QCS8275) },
+ { id_entry!(QCS9075) },
+ { id_entry!(QCS615) },
+];
+
+pub(crate) const PMIC_MODELS: [Option<&str>; 84] = {
+ let mut models = [None; 84];
+ models[0] = Some("Unknown PMIC model");
+ models[1] = Some("PM8941");
+ models[2] = Some("PM8841");
+ models[3] = Some("PM8019");
+ models[4] = Some("PM8226");
+ models[5] = Some("PM8110");
+ models[6] = Some("PMA8084");
+ models[7] = Some("PMI8962");
+ models[8] = Some("PMD9635");
+ models[9] = Some("PM8994");
+ models[10] = Some("PMI8994");
+ models[11] = Some("PM8916");
+ models[12] = Some("PM8004");
+ models[13] = Some("PM8909/PM8058");
+ models[14] = Some("PM8028");
+ models[15] = Some("PM8901");
+ models[16] = Some("PM8950/PM8027");
+ models[17] = Some("PMI8950/ISL9519");
+ models[18] = Some("PMK8001/PM8921");
+ models[19] = Some("PMI8996/PM8018");
+ models[20] = Some("PM8998/PM8015");
+ models[21] = Some("PMI8998/PM8014");
+ models[22] = Some("PM8821");
+ models[23] = Some("PM8038");
+ models[24] = Some("PM8005/PM8922");
+ models[25] = Some("PM8917/PM8937");
+ models[26] = Some("PM660L");
+ models[27] = Some("PM660");
+ models[30] = Some("PM8150");
+ models[31] = Some("PM8150L");
+ models[32] = Some("PM8150B");
+ models[33] = Some("PMK8002");
+ models[36] = Some("PM8009");
+ models[37] = Some("PMI632");
+ models[38] = Some("PM8150C");
+ models[40] = Some("PM6150");
+ models[41] = Some("SMB2351");
+ models[44] = Some("PM8008");
+ models[45] = Some("PM6125");
+ models[46] = Some("PM7250B");
+ models[47] = Some("PMK8350");
+ models[48] = Some("PM8350");
+ models[49] = Some("PM8350C");
+ models[50] = Some("PM8350B");
+ models[51] = Some("PMR735A");
+ models[52] = Some("PMR735B");
+ models[54] = Some("PM6350");
+ models[55] = Some("PM4125");
+ models[58] = Some("PM8450");
+ models[65] = Some("PM8010");
+ models[69] = Some("PM8550VS");
+ models[70] = Some("PM8550VE");
+ models[71] = Some("PM8550B");
+ models[72] = Some("PMR735D");
+ models[73] = Some("PM8550");
+ models[74] = Some("PMK8550");
+ models[82] = Some("PMC8380");
+ models[83] = Some("SMB2360");
+ models
+};
+
+pub(crate) const IMAGE_NAMES: &[(&CStr, usize)] = &[
+ (c_str!("adsp"), 12),
+ (c_str!("apps"), 10),
+ (c_str!("appsbl"), 9),
+ (c_str!("boot"), 0),
+ (c_str!("cnss"), 13),
+ (c_str!("mpss"), 11),
+ (c_str!("rpm"), 3),
+ (c_str!("tz"), 1),
+ (c_str!("video"), 14),
+ (c_str!("dsps"), 15),
+ (c_str!("cdsp"), 16),
+ (c_str!("cdsp1"), 19),
+ (c_str!("gpdsp"), 20),
+ (c_str!("gpdsp1"), 21),
+ (c_str!("tme"), 28),
+];
diff --git a/drivers/soc/qcom/socinfo_rust/socinfo_rust_file.rs b/drivers/soc/qcom/socinfo_rust/socinfo_rust_file.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6a7f7af807b53341325dd3e5c5318f36efc52b1d
--- /dev/null
+++ b/drivers/soc/qcom/socinfo_rust/socinfo_rust_file.rs
@@ -0,0 +1,538 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+#![recursion_limit = "256"]
+
+//! Re-implementation of Qualcomm's Socinfo driver in Rust
+use core::convert::From;
+use core::fmt;
+use core::fmt::{Debug, Formatter};
+use core::mem::MaybeUninit;
+use kernel::c_str;
+use kernel::debugfs::{Dir, File, Render};
+use kernel::device::Core;
+use kernel::module_platform_driver;
+use kernel::platform::{self, Device};
+use kernel::prelude::*;
+use kernel::soc;
+use kernel::str::CString;
+
+use pin_init::pin_init_from_closure;
+
+mod bindings;
+mod data;
+
+use bindings::{qcom_smem_get, ImageVersion, ImageVersions, PmicArray, PmicEntry, RawSocInfo};
+use data::{IMAGE_NAMES, PMIC_MODELS, SOC_IDS};
+
+module_platform_driver! {
+ type: QcomSocInfo,
+ name: "qcom_socinfo_driver_file_rust",
+ authors: ["Matthew Maurer"],
+ description: "Rust re-implementation of Qualcomm's Socinfo driver",
+ license: "GPL",
+}
+
+#[pin_data]
+struct QcomSocInfo {
+ #[pin]
+ registration: soc::DeviceRegistration,
+ #[pin]
+ _debugfs: QcomSocInfoDebugFs,
+}
+
+#[derive(Default)]
+#[repr(transparent)]
+struct PmicModel(u32);
+
+impl From<u32> for PmicModel {
+ fn from(x: u32) -> Self {
+ Self(x)
+ }
+}
+
+impl Debug for PmicModel {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ let model = SocInfo::version_split(self.0).1;
+ if let Some(Some(model)) = PMIC_MODELS.get(model as usize) {
+ write!(f, "{model}")
+ } else {
+ write!(f, "unknown ({})", model)
+ }
+ }
+}
+
+#[derive(Default)]
+#[repr(transparent)]
+struct PmicDieRev(u32);
+
+impl From<u32> for PmicDieRev {
+ fn from(x: u32) -> Self {
+ Self(x)
+ }
+}
+
+impl Debug for PmicDieRev {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ let (major, minor) = SocInfo::version_split(self.0);
+ write!(f, "{major}.{minor}")
+ }
+}
+
+impl Render for PmicArray {
+ fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ for pmic_entry in self.0 {
+ let (die_rev_major, die_rev_minor) =
+ SocInfo::version_split(u32::from_le(pmic_entry.die_rev));
+ let model_idx = SocInfo::version_split(pmic_entry.model).1 as usize;
+ if let Some(Some(model)) = PMIC_MODELS.get(model_idx) {
+ writeln!(f, "{model} {die_rev_major}.{die_rev_minor}")?
+ } else {
+ writeln!(f, "unknown ({})", pmic_entry.model)?
+ }
+ }
+ Ok(())
+ }
+}
+
+struct Params {
+ info_fmt: u32,
+ build_id: [u8; 32],
+ raw_version: Option<u32>,
+ hardware_platform: Option<u32>,
+ platform_version: Option<u32>,
+ accessory_chip: Option<u32>,
+ hardware_platform_subtype: Option<u32>,
+ pmic_model: Option<PmicModel>,
+ pmic_die_rev: Option<PmicDieRev>,
+ foundry_id: Option<u32>,
+ pmic_model_array: Option<PmicArray>,
+ chip_family: Option<u32>, // x32
+ raw_device_family: Option<u32>, // x32
+ raw_device_number: Option<u32>, // x32
+ nproduct_id: Option<u32>,
+ chip_id: Option<[u8; 32]>,
+ num_clusters: Option<u32>,
+ ncluster_array_offset: Option<u32>,
+ num_subset_parts: Option<u32>,
+ nsubset_parts_array_offset: Option<u32>,
+ nmodem_supported: Option<u32>,
+ feature_code: Option<u32>,
+ pcode: Option<u32>,
+ oem_variant: Option<u32>,
+ boot_core: Option<u32>,
+ boot_cluster: Option<u32>,
+ num_func_clusters: Option<u32>,
+ versions: Option<ImageVersions>,
+}
+
+#[derive(Copy, Clone)]
+struct SocInfo<'a> {
+ soc_info: RawSocInfo,
+ soc_info_mem: &'a [u8],
+ version_mem: &'a [u8],
+}
+
+impl<'a> SocInfo<'a> {
+ fn from_mem(soc_info_mem: &'a [u8], version_mem: &'a [u8]) -> Self {
+ Self {
+ soc_info: RawSocInfo::from_partial_bytes(soc_info_mem),
+ soc_info_mem,
+ version_mem,
+ }
+ }
+ fn id(&self) -> u32 {
+ u32::from_le(self.soc_info.id)
+ }
+ fn version_split(ver: u32) -> (u16, u16) {
+ let major = (ver >> 16) as u16;
+ let minor = (ver & 0xFFFF) as u16;
+ (major, minor)
+ }
+ fn version_fuse(major: u16, minor: u16) -> u32 {
+ (u32::from(major) << 16) | u32::from(minor)
+ }
+ fn version(&self) -> (u16, u16) {
+ Self::version_split(self.soc_info.ver)
+ }
+ fn serial(&self) -> u32 {
+ u32::from_le(self.soc_info.id)
+ }
+ fn machine(&self) -> Result<Option<CString>> {
+ for soc in SOC_IDS {
+ if soc.id == self.id() {
+ return Ok(Some(soc.name.to_cstring()?));
+ }
+ }
+ Ok(None)
+ }
+ fn device_attribute(&self) -> Result<soc::DeviceAttribute> {
+ Ok(soc::DeviceAttribute {
+ family: Some(c_str!("Snapdragon").to_cstring()?),
+ machine: self.machine()?,
+ revision: Some(CString::try_from_fmt(fmt!(
+ "{}.{}",
+ self.version().0,
+ self.version().1
+ ))?),
+ serial_number: Some(CString::try_from_fmt(fmt!("{}", self.serial()))?),
+ soc_id: Some(CString::try_from_fmt(fmt!("{}", self.id()))?),
+ })
+ }
+}
+
+macro_rules! u32_le_versioned {
+ { $params:expr, $self:ident,
+ [ $( { $major:expr, $minor:expr, { $( $dst:ident: $src:ident ),* } } ),* ] } => {$(
+ if $params.info_fmt >= SocInfo::version_fuse($major, $minor) {
+ $( $params.$dst = Some(u32::from_le($self.soc_info.$src).into()) );*
+ }
+ )*}
+}
+
+impl SocInfo<'static> {
+ fn build_params(&self) -> Result<Params> {
+ let mut params = Params {
+ build_id: self.soc_info.build_id,
+ info_fmt: u32::from_le(self.soc_info.fmt),
+ raw_version: None,
+ hardware_platform: None,
+ platform_version: None,
+ accessory_chip: None,
+ hardware_platform_subtype: None,
+ pmic_model: None,
+ pmic_die_rev: None,
+ foundry_id: None,
+ pmic_model_array: None,
+ chip_family: None,
+ raw_device_family: None,
+ raw_device_number: None,
+ nproduct_id: None,
+ chip_id: None,
+ num_clusters: None,
+ ncluster_array_offset: None,
+ num_subset_parts: None,
+ nsubset_parts_array_offset: None,
+ nmodem_supported: None,
+ feature_code: None,
+ pcode: None,
+ oem_variant: None,
+ boot_core: None,
+ boot_cluster: None,
+ num_func_clusters: None,
+ versions: None,
+ };
+ u32_le_versioned! { params, self, [
+ {0, 2, { raw_version: raw_ver }},
+ {0, 3, { hardware_platform: hw_plat }},
+ {0, 4, { platform_version: plat_ver }},
+ {0, 5, { accessory_chip: accessory_chip }},
+ {0, 6, { hardware_platform_subtype: hw_plat_subtype }},
+ {0, 7, { pmic_model: pmic_model, pmic_die_rev: pmic_die_rev }},
+ {0, 9, { foundry_id: foundry_id }},
+ {0, 12, {
+ chip_family: chip_family,
+ raw_device_family: raw_device_family,
+ raw_device_number: raw_device_num
+ }},
+ {0, 13, { nproduct_id: nproduct_id }},
+ {0, 14, {
+ num_clusters: num_clusters,
+ ncluster_array_offset: ncluster_array_offset,
+ num_subset_parts: num_subset_parts,
+ nsubset_parts_array_offset: nsubset_parts_array_offset
+ }},
+ {0, 15, { nmodem_supported: nmodem_supported }},
+ {0, 16, { feature_code: feature_code, pcode: pcode }},
+ {0, 17, { oem_variant: oem_variant }},
+ {0, 19, {
+ boot_core: boot_core,
+ boot_cluster: boot_cluster,
+ num_func_clusters: num_func_clusters
+ }}
+ ]};
+ if params.info_fmt >= SocInfo::version_fuse(0, 11) {
+ let offset = u32::from_le(self.soc_info.pmic_array_offset) as usize;
+ let num_pmics = u32::from_le(self.soc_info.num_pmics) as usize;
+ let size = num_pmics * core::mem::size_of::<PmicEntry>();
+ params.pmic_model_array =
+ PmicArray::from_bytes(&self.soc_info_mem[offset..offset + size]);
+ }
+ if params.info_fmt >= SocInfo::version_fuse(0, 13) {
+ params.chip_id = Some(self.soc_info.chip_id);
+ }
+ params.versions = ImageVersions::from_bytes(self.version_mem);
+ Ok(params)
+ }
+}
+
+fn no_quirk<const SIZE: usize>(buf: &[u8; SIZE], f: &mut Formatter<'_>) -> fmt::Result {
+ if buf[0] == 0 {
+ writeln!(f)
+ } else {
+ nul_array(buf, f)
+ }
+}
+
+fn nul_array<const SIZE: usize>(buf: &[u8; SIZE], f: &mut Formatter<'_>) -> fmt::Result {
+ if let Some(end) = buf.iter().position(|x| *x == 0) {
+ if end == 0 {
+ // Match original driver quirk - empty strings don't have a trailing newline
+ return Ok(());
+ }
+ let Ok(c_str) = CStr::from_bytes_with_nul(&buf[0..=end]) else {
+ pr_warn!("Creating CStr from bytes with known first NUL failed?");
+ return Ok(());
+ };
+ writeln!(f, "{c_str}")
+ } else {
+ writeln!(f, "Missing NUL: {buf:?}")
+ }
+}
+
+fn hex(x: &u32, f: &mut Formatter<'_>) -> fmt::Result {
+ writeln!(f, "{x:#010x}")
+}
+
+#[pin_data]
+struct ImageVersionFiles {
+ _dir: Dir,
+ #[pin]
+ name: File<[u8; 75]>,
+ #[pin]
+ variant: File<[u8; 20]>,
+ #[pin]
+ oem: File<[u8; 32]>,
+}
+
+impl ImageVersionFiles {
+ fn create<'a>(
+ parent: &'a Dir,
+ image_name: &'a CStr,
+ version: &'a ImageVersion,
+ ) -> impl PinInit<Self> + 'a {
+ let dir = parent.subdir(image_name);
+ pin_init! {
+ Self {
+ _dir: dir.clone(),
+ name <- dir.read_callback_file(c_str!("name"), version.name, &nul_array),
+ variant <- dir.read_callback_file(c_str!("variant"), version.variant, &nul_array),
+ oem <- dir.read_callback_file(c_str!("oem"), version.oem, &nul_array),
+ }
+ }
+ }
+}
+
+#[pin_data]
+struct QcomSocInfoDebugFs {
+ _dir: Dir,
+ #[pin]
+ info_fmt: File<u32>,
+ #[pin]
+ build_id: File<[u8; 32]>,
+ #[pin]
+ raw_version: Option<File<u32>>,
+ #[pin]
+ hardware_platform: Option<File<u32>>,
+ #[pin]
+ platform_version: Option<File<u32>>,
+ #[pin]
+ accessory_chip: Option<File<u32>>,
+ #[pin]
+ hardware_platform_subtype: Option<File<u32>>,
+ #[pin]
+ pmic_model: Option<File<PmicModel>>,
+ #[pin]
+ pmic_die_rev: Option<File<PmicDieRev>>,
+ #[pin]
+ foundry_id: Option<File<u32>>,
+ #[pin]
+ pmic_model_array: Option<File<PmicArray>>,
+ #[pin]
+ chip_family: Option<File<u32>>,
+ #[pin]
+ raw_device_family: Option<File<u32>>,
+ #[pin]
+ raw_device_number: Option<File<u32>>,
+ #[pin]
+ nproduct_id: Option<File<u32>>,
+ #[pin]
+ chip_id: Option<File<[u8; 32]>>,
+ #[pin]
+ num_clusters: Option<File<u32>>,
+ #[pin]
+ ncluster_array_offset: Option<File<u32>>,
+ #[pin]
+ num_subset_parts: Option<File<u32>>,
+ #[pin]
+ nsubset_parts_array_offset: Option<File<u32>>,
+ #[pin]
+ nmodem_supported: Option<File<u32>>,
+ #[pin]
+ feature_code: Option<File<u32>>,
+ #[pin]
+ pcode: Option<File<u32>>,
+ #[pin]
+ oem_variant: Option<File<u32>>,
+ #[pin]
+ boot_core: Option<File<u32>>,
+ #[pin]
+ boot_cluster: Option<File<u32>>,
+ #[pin]
+ num_func_clusters: Option<File<u32>>,
+ versions: KVec<Pin<KBox<ImageVersionFiles>>>,
+}
+
+// TODO this doesn't seem like we should be defining it here, but without it,
+// it's hard to construct an `Option<File<u32>>`, which is required for this driver.
+fn pin_init_option<T, R, F, E, PI>(val: Option<T>, f: F) -> impl PinInit<Option<R>, E>
+where
+ F: FnOnce(T) -> PI,
+ PI: PinInit<R, E>,
+{
+ let init = move |slot: *mut Option<R>| -> Result<(), E> {
+ let Some(val) = val else {
+ // SAFETY: The constructor guarantees we're called with a writable pointer
+ unsafe { core::ptr::write(slot, None) };
+ return Ok(());
+ };
+
+ let slot_uninit: *mut Option<MaybeUninit<R>> = slot.cast();
+ // SAFETY: The constructor guarantees we're called with a writable pointer
+ // *mut Option<MaybeUninit<R>> is layout compatible with `Option<R>`
+ unsafe { core::ptr::write(slot_uninit, Some(MaybeUninit::uninit())) };
+ // SAFETY: We just initialized it, and we have unique access
+ let inner_slot: *mut R = unsafe { (*slot_uninit).as_mut().unwrap().as_mut_ptr() };
+ // SAFETY: `inner_slot` is a pointer to the uninitialized portion of the option.
+ if let Err(e) = unsafe { f(val).__pinned_init(inner_slot) } {
+ // Wipe out slot with a safe default
+ // SAFETY: The slot is still writable, and the other initializer has cleared out
+ // the payload.
+ unsafe { core::ptr::write(slot, None) };
+ return Err(e);
+ }
+
+ // Since we've initialized the `MaybeUninit` portion, we're fully initialized.
+ Ok(())
+ };
+
+ // SAFETY: If val is None, we always fully initialize it. If we successfully run the
+ // initializer, we fully initialize the Some arm. If we fail to run the initializer, we wipe it
+ // out with None before returning.
+ unsafe { pin_init_from_closure(init) }
+}
+
+macro_rules! create_optional_file {
+ ($dir:expr, $name:expr, $value:expr) => {
+ pin_init_option($value, |v| $dir.read_only_file(c_str!($name), v))
+ };
+ ($dir:expr, $name:expr, $value:expr, $callback:expr) => {
+ pin_init_option($value, |v| {
+ $dir.read_callback_file(c_str!($name), v, $callback)
+ })
+ };
+}
+
+fn create_version_files(
+ parent: &Dir,
+ versions: Option<ImageVersions>,
+) -> Result<KVec<Pin<KBox<ImageVersionFiles>>>> {
+ let mut res = KVec::new();
+ if let Some(v) = versions.as_ref() {
+ for (image_name, idx) in IMAGE_NAMES {
+ if let Some(version) = v.0.get(*idx) {
+ res.push(
+ KBox::pin_init(
+ ImageVersionFiles::create(parent, image_name, version),
+ GFP_KERNEL,
+ )?,
+ GFP_KERNEL,
+ )?;
+ }
+ }
+ }
+ Ok(res)
+}
+
+impl QcomSocInfoDebugFs {
+ fn create(mut params: Params) -> impl PinInit<Self, Error> {
+ let dir = Dir::new(c_str!("qcom_socinfo_rs_file"));
+ let versions_data = params.versions.take();
+ try_pin_init! {
+ Self {
+ _dir: dir.clone(),
+ info_fmt <- dir.read_callback_file(c_str!("info_fmt"), params.info_fmt, &hex),
+ build_id <- dir.read_callback_file(c_str!("build_id"), params.build_id, &no_quirk),
+ raw_version <- create_optional_file!(dir, "raw_version", params.raw_version),
+ hardware_platform <- create_optional_file!(dir, "hardware_platform",
+ params.hardware_platform),
+ platform_version <- create_optional_file!(dir, "platform_version",
+ params.platform_version),
+ accessory_chip <- create_optional_file!(dir, "accessory_chip",
+ params.accessory_chip),
+ hardware_platform_subtype <- create_optional_file!(dir,
+ "hardware_platform_subtype", params.hardware_platform_subtype),
+ raw_device_number <- create_optional_file!(dir, "raw_device_number",
+ params.raw_device_number, &hex),
+ raw_device_family <- create_optional_file!(dir, "raw_device_family",
+ params.raw_device_family, &hex),
+ chip_family <- create_optional_file!(dir, "chip_family", params.chip_family, &hex),
+ chip_id <- create_optional_file!(dir, "chip_id", params.chip_id, &nul_array),
+ nproduct_id <- create_optional_file!(dir, "nproduct_id", params.nproduct_id),
+ nsubset_parts_array_offset <- create_optional_file!(dir,
+ "nsubset_parts_array_offset", params.nsubset_parts_array_offset),
+ num_subset_parts <- create_optional_file!(dir, "num_subset_parts",
+ params.num_subset_parts),
+ ncluster_array_offset <- create_optional_file!(dir, "ncluster_array_offset",
+ params.ncluster_array_offset),
+ num_clusters <- create_optional_file!(dir, "num_clusters", params.num_clusters),
+ nmodem_supported <- create_optional_file!(dir, "nmodem_supported",
+ params.nmodem_supported),
+ pcode <- create_optional_file!(dir, "pcode", params.pcode),
+ feature_code <- create_optional_file!(dir, "feature_code", params.feature_code),
+ oem_variant <- create_optional_file!(dir, "oem_variant", params.oem_variant),
+ boot_core <- create_optional_file!(dir, "boot_core", params.boot_core),
+ boot_cluster <- create_optional_file!(dir, "boot_cluster", params.boot_cluster),
+ num_func_clusters <- create_optional_file!(dir, "num_func_clusters",
+ params.num_func_clusters),
+ foundry_id <- create_optional_file!(dir, "foundry_id", params.foundry_id),
+ pmic_model <- create_optional_file!(dir, "pmic_model", params.pmic_model),
+ pmic_die_rev <- create_optional_file!(dir, "pmic_die_rev", params.pmic_die_rev),
+ pmic_model_array <- create_optional_file!(dir, "pmic_model_array",
+ params.pmic_model_array),
+ versions: create_version_files(&dir, versions_data)?,
+ } ? Error
+ }
+ }
+}
+
+impl platform::Driver for QcomSocInfo {
+ type IdInfo = ();
+ const OF_ID_TABLE: Option<kernel::of::IdTable<Self::IdInfo>> = None;
+ fn probe(_dev: &Device<Core>, _id_info: Option<&Self::IdInfo>) -> Result<Pin<KBox<Self>>> {
+ let soc_info_mem = qcom_smem_get(
+ kernel::bindings::QCOM_SMEM_HOST_ANY,
+ kernel::bindings::SMEM_HW_SW_BUILD_ID,
+ )?;
+ let version_mem = qcom_smem_get(
+ kernel::bindings::QCOM_SMEM_HOST_ANY,
+ bindings::SMEM_IMAGE_VERSION_TABLE,
+ )?;
+ let info = SocInfo::from_mem(soc_info_mem, version_mem);
+ let params = info.build_params()?;
+ let soc_info = KBox::pin_init(
+ try_pin_init!(
+ Self {
+ registration <- soc::DeviceRegistration::register(info.device_attribute()?),
+ _debugfs <- QcomSocInfoDebugFs::create(params),
+ } ? Error
+ ),
+ GFP_KERNEL,
+ )?;
+
+ kernel::rand::add_device_randomness(soc_info_mem);
+
+ Ok(soc_info)
+ }
+}
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index afe8be55a6db1d29706c19a91dc51c1ae3494ed4..f22320757e76a77df0e95bdb5bb43afc8cd51616 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -80,6 +80,12 @@
#include <linux/xarray.h>
#include <trace/events/rust_sample.h>
+#if defined(CONFIG_QCOM_SOCINFO_RUST)
+#include <dt-bindings/arm/qcom,ids.h>
+#include <linux/soc/qcom/smem.h>
+#include <linux/soc/qcom/socinfo.h>
+#endif
+
#if defined(CONFIG_DRM_PANIC_SCREEN_QR_CODE)
// Used by `#[export]` in `drivers/gpu/drm/drm_panic_qr.rs`.
#include <drm/drm_panic.h>
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
* [PATCH WIP 5/5] soc: qcom: socinfo: `Scoped`-based example
2025-08-19 23:12 [PATCH WIP 0/5] qcom-socinfo Rust Implementation Matthew Maurer
` (3 preceding siblings ...)
2025-08-19 23:12 ` [PATCH WIP 4/5] soc: qcom: socinfo: `File`-based example Matthew Maurer
@ 2025-08-19 23:12 ` Matthew Maurer
4 siblings, 0 replies; 6+ messages in thread
From: Matthew Maurer @ 2025-08-19 23:12 UTC (permalink / raw)
To: Miguel Ojeda, Alex Gaynor, Boqun Feng, Gary Guo,
Björn Roy Baron, Andreas Hindborg, Alice Ryhl, Trevor Gross,
Danilo Krummrich, Greg Kroah-Hartman, Rafael J. Wysocki,
Sami Tolvanen, Timur Tabi, Benno Lossin, Dirk Beheme
Cc: linux-kernel, rust-for-linux, Matthew Maurer
Re-implements qcom-socinfo driver in Rust, using `Scoped`-based DebugFS
bindings.
Signed-off-by: Matthew Maurer <mmaurer@google.com>
---
drivers/soc/qcom/Makefile | 1 +
drivers/soc/qcom/smem.c | 7 +-
.../soc/qcom/socinfo_rust/socinfo_rust_scoped.rs | 367 +++++++++++++++++++++
3 files changed, 374 insertions(+), 1 deletion(-)
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 6453a4f4f7da89d4e1677ad8f75257e94cec90f4..ab76fc1d6ae10bef74adbc1489518379b7d5c00b 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -41,3 +41,4 @@ obj-$(CONFIG_QCOM_INLINE_CRYPTO_ENGINE) += qcom_ice.o
obj-$(CONFIG_QCOM_PBS) += qcom-pbs.o
obj-$(CONFIG_QCOM_UBWC_CONFIG) += ubwc_config.o
obj-$(CONFIG_QCOM_SOCINFO_RUST) += socinfo_rust/socinfo_rust_file.o
+obj-$(CONFIG_QCOM_SOCINFO_RUST) += socinfo_rust/socinfo_rust_scoped.o
diff --git a/drivers/soc/qcom/smem.c b/drivers/soc/qcom/smem.c
index 5279db46a1e0dd7b2994b79d131367175e35290f..6810f0cdd37d7f393977ffeae5836f47132988c7 100644
--- a/drivers/soc/qcom/smem.c
+++ b/drivers/soc/qcom/smem.c
@@ -1230,13 +1230,18 @@ static int qcom_smem_probe(struct platform_device *pdev)
__smem = smem;
- // TODO don't double load, this is just to diff the debugfs
+ // TODO don't triple load, this is just to diff the debugfs
smem->socinfo = platform_device_register_data(&pdev->dev, "qcom-socinfo",
PLATFORM_DEVID_NONE, NULL, 0);
// TODO MODULE_ALIAS
smem->socinfo = platform_device_register_data(&pdev->dev, "qcom_socinfo_driver_file_rust",
PLATFORM_DEVID_NONE, NULL,
0);
+ // TODO MODULE_ALIAS
+ smem->socinfo = platform_device_register_data(&pdev->dev, "qcom_socinfo_driver_scoped_rust",
+ PLATFORM_DEVID_NONE, NULL,
+ 0);
+
if (IS_ERR(smem->socinfo))
dev_dbg(&pdev->dev, "failed to register socinfo device\n");
diff --git a/drivers/soc/qcom/socinfo_rust/socinfo_rust_scoped.rs b/drivers/soc/qcom/socinfo_rust/socinfo_rust_scoped.rs
new file mode 100644
index 0000000000000000000000000000000000000000..845e91560a7879d4cf13c6b62fd9ad8a15c79826
--- /dev/null
+++ b/drivers/soc/qcom/socinfo_rust/socinfo_rust_scoped.rs
@@ -0,0 +1,367 @@
+// SPDX-License-Identifier: GPL-2.0
+
+// Copyright (C) 2025 Google LLC.
+
+//! Re-implementation of Qualcomm's Socinfo driver in Rust
+use core::convert::From;
+use core::fmt;
+use core::fmt::{Debug, Formatter};
+use kernel::c_str;
+use kernel::debugfs::{Render, Scope, ScopedDir};
+use kernel::device::Core;
+use kernel::module_platform_driver;
+use kernel::platform::{self, Device};
+use kernel::prelude::*;
+use kernel::soc;
+use kernel::str::CString;
+
+mod bindings;
+mod data;
+
+use bindings::{qcom_smem_get, ImageVersion, ImageVersions, PmicArray, PmicEntry, RawSocInfo};
+use data::{IMAGE_NAMES, PMIC_MODELS, SOC_IDS};
+
+module_platform_driver! {
+ type: QcomSocInfo,
+ name: "qcom_socinfo_driver_scoped_rust",
+ authors: ["Matthew Maurer"],
+ description: "Rust re-implementation of Qualcomm's Socinfo driver",
+ license: "GPL",
+}
+
+#[pin_data]
+struct QcomSocInfo {
+ #[pin]
+ registration: soc::DeviceRegistration,
+ #[pin]
+ params: Scope<Params>,
+}
+
+#[derive(Default)]
+#[repr(transparent)]
+struct PmicModel(u32);
+
+impl From<u32> for PmicModel {
+ fn from(x: u32) -> Self {
+ Self(x)
+ }
+}
+
+impl Debug for PmicModel {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ let model = SocInfo::version_split(self.0).1;
+ if let Some(Some(model)) = PMIC_MODELS.get(model as usize) {
+ write!(f, "{model}")
+ } else {
+ write!(f, "unknown ({})", model)
+ }
+ }
+}
+
+#[derive(Default)]
+#[repr(transparent)]
+struct PmicDieRev(u32);
+
+impl From<u32> for PmicDieRev {
+ fn from(x: u32) -> Self {
+ Self(x)
+ }
+}
+
+impl Debug for PmicDieRev {
+ fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ let (major, minor) = SocInfo::version_split(self.0);
+ write!(f, "{major}.{minor}")
+ }
+}
+
+impl Render for PmicArray {
+ fn render(&self, f: &mut Formatter<'_>) -> fmt::Result {
+ for pmic_entry in self.0 {
+ let (die_rev_major, die_rev_minor) =
+ SocInfo::version_split(u32::from_le(pmic_entry.die_rev));
+ let model_idx = SocInfo::version_split(pmic_entry.model).1 as usize;
+ if let Some(Some(model)) = PMIC_MODELS.get(model_idx) {
+ writeln!(f, "{model} {die_rev_major}.{die_rev_minor}")?
+ } else {
+ writeln!(f, "unknown ({})", pmic_entry.model)?
+ }
+ }
+ Ok(())
+ }
+}
+
+#[derive(Default)]
+struct Params {
+ info_fmt: u32,
+ build_id: [u8; 32],
+ raw_version: Option<u32>,
+ hardware_platform: Option<u32>,
+ platform_version: Option<u32>,
+ accessory_chip: Option<u32>,
+ hardware_platform_subtype: Option<u32>,
+ pmic_model: Option<PmicModel>,
+ pmic_die_rev: Option<PmicDieRev>,
+ foundry_id: Option<u32>,
+ pmic_model_array: Option<PmicArray>,
+ chip_family: Option<u32>, // x32
+ raw_device_family: Option<u32>, // x32
+ raw_device_number: Option<u32>, // x32
+ nproduct_id: Option<u32>,
+ chip_id: Option<[u8; 32]>,
+ num_clusters: Option<u32>,
+ ncluster_array_offset: Option<u32>,
+ num_subset_parts: Option<u32>,
+ nsubset_parts_array_offset: Option<u32>,
+ nmodem_supported: Option<u32>,
+ feature_code: Option<u32>,
+ pcode: Option<u32>,
+ oem_variant: Option<u32>,
+ boot_core: Option<u32>,
+ boot_cluster: Option<u32>,
+ num_func_clusters: Option<u32>,
+ versions: Option<ImageVersions>,
+}
+
+#[derive(Copy, Clone)]
+struct SocInfo<'a> {
+ soc_info: RawSocInfo,
+ soc_info_mem: &'a [u8],
+ version_mem: &'a [u8],
+}
+
+impl<'a> SocInfo<'a> {
+ fn from_mem(soc_info_mem: &'a [u8], version_mem: &'a [u8]) -> Self {
+ Self {
+ soc_info: RawSocInfo::from_partial_bytes(soc_info_mem),
+ soc_info_mem,
+ version_mem,
+ }
+ }
+ fn id(&self) -> u32 {
+ u32::from_le(self.soc_info.id)
+ }
+ fn version_split(ver: u32) -> (u16, u16) {
+ let major = (ver >> 16) as u16;
+ let minor = (ver & 0xFFFF) as u16;
+ (major, minor)
+ }
+ fn version_fuse(major: u16, minor: u16) -> u32 {
+ (u32::from(major) << 16) | u32::from(minor)
+ }
+ fn version(&self) -> (u16, u16) {
+ Self::version_split(self.soc_info.ver)
+ }
+ fn serial(&self) -> u32 {
+ u32::from_le(self.soc_info.id)
+ }
+ fn machine(&self) -> Result<Option<CString>> {
+ for soc in SOC_IDS {
+ if soc.id == self.id() {
+ return Ok(Some(soc.name.to_cstring()?));
+ }
+ }
+ Ok(None)
+ }
+ fn device_attribute(&self) -> Result<soc::DeviceAttribute> {
+ Ok(soc::DeviceAttribute {
+ family: Some(c_str!("Snapdragon").to_cstring()?),
+ machine: self.machine()?,
+ revision: Some(CString::try_from_fmt(fmt!(
+ "{}.{}",
+ self.version().0,
+ self.version().1
+ ))?),
+ serial_number: Some(CString::try_from_fmt(fmt!("{}", self.serial()))?),
+ soc_id: Some(CString::try_from_fmt(fmt!("{}", self.id()))?),
+ })
+ }
+}
+
+macro_rules! u32_le_versioned {
+ { $params:expr, $self:ident,
+ [ $( { $major:expr, $minor:expr, { $( $dst:ident: $src:ident ),* } } ),* ] } => {$(
+ if $params.info_fmt >= SocInfo::version_fuse($major, $minor) {
+ $( $params.$dst = Some(u32::from_le($self.soc_info.$src).into()) );*
+ }
+ )*}
+}
+
+impl SocInfo<'static> {
+ fn build_params(&self) -> Result<Params> {
+ let mut params = Params {
+ build_id: self.soc_info.build_id,
+ info_fmt: u32::from_le(self.soc_info.fmt),
+ ..Default::default()
+ };
+ u32_le_versioned! { params, self, [
+ {0, 2, { raw_version: raw_ver }},
+ {0, 3, { hardware_platform: hw_plat }},
+ {0, 4, { platform_version: plat_ver }},
+ {0, 5, { accessory_chip: accessory_chip }},
+ {0, 6, { hardware_platform_subtype: hw_plat_subtype }},
+ {0, 7, { pmic_model: pmic_model, pmic_die_rev: pmic_die_rev }},
+ {0, 9, { foundry_id: foundry_id }},
+ {0, 12, {
+ chip_family: chip_family,
+ raw_device_family: raw_device_family,
+ raw_device_number: raw_device_num
+ }},
+ {0, 13, { nproduct_id: nproduct_id }},
+ {0, 14, {
+ num_clusters: num_clusters,
+ ncluster_array_offset: ncluster_array_offset,
+ num_subset_parts: num_subset_parts,
+ nsubset_parts_array_offset: nsubset_parts_array_offset
+ }},
+ {0, 15, { nmodem_supported: nmodem_supported }},
+ {0, 16, { feature_code: feature_code, pcode: pcode }},
+ {0, 17, { oem_variant: oem_variant }},
+ {0, 19, {
+ boot_core: boot_core,
+ boot_cluster: boot_cluster,
+ num_func_clusters: num_func_clusters
+ }}
+ ]};
+ if params.info_fmt >= SocInfo::version_fuse(0, 11) {
+ let offset = u32::from_le(self.soc_info.pmic_array_offset) as usize;
+ let num_pmics = u32::from_le(self.soc_info.num_pmics) as usize;
+ let size = num_pmics * core::mem::size_of::<PmicEntry>();
+ params.pmic_model_array =
+ PmicArray::from_bytes(&self.soc_info_mem[offset..offset + size]);
+ }
+ if params.info_fmt >= SocInfo::version_fuse(0, 13) {
+ params.chip_id = Some(self.soc_info.chip_id);
+ }
+ params.versions = ImageVersions::from_bytes(self.version_mem);
+ Ok(params)
+ }
+}
+
+macro_rules! value_attrs {
+ ($builder:ident, $params:ident, @) => {};
+ ($builder:ident, $params:ident, @ $s:ident $($rest:tt)*) => {
+ if let Some(v) = $params.$s.as_ref() {
+ $builder.read_only_file(c_str!(stringify!($s)), v);
+ }
+ value_attrs!($builder, $params, @ $($rest)*)
+ };
+ ($builder:ident, $params:ident, @ {$s:ident, $cb:expr} $($rest:tt)*) => {
+ if let Some(v) = $params.$s.as_ref() {
+ $builder.read_callback_file(c_str!(stringify!($s)), v, $cb);
+ }
+ value_attrs!($builder, $params, @ $($rest)*)
+ };
+ ($builder:ident, $params:ident, {$($items:tt),*}) => {
+ value_attrs!($builder, $params, @ $($items)*)
+ };
+}
+
+fn no_quirk<const SIZE: usize>(buf: &[u8; SIZE], f: &mut Formatter<'_>) -> fmt::Result {
+ if buf[0] == 0 {
+ writeln!(f)
+ } else {
+ nul_array(buf, f)
+ }
+}
+
+fn nul_array<const SIZE: usize>(buf: &[u8; SIZE], f: &mut Formatter<'_>) -> fmt::Result {
+ if let Some(end) = buf.iter().position(|x| *x == 0) {
+ if end == 0 {
+ // Match original driver quirk - empty strings don't have a trailing newline
+ return Ok(());
+ }
+ let Ok(c_str) = CStr::from_bytes_with_nul(&buf[0..=end]) else {
+ pr_warn!("Creating CStr from bytes with known first NUL failed?");
+ return Ok(());
+ };
+ writeln!(f, "{c_str}")
+ } else {
+ writeln!(f, "Missing NUL: {buf:?}")
+ }
+}
+
+impl ImageVersion {
+ fn build_debugfs<'a>(&'a self, dir: &ScopedDir<'a, '_>, image_name: &CStr) {
+ let subdir = dir.dir(image_name);
+ subdir.read_callback_file(c_str!("name"), &self.name, &nul_array);
+ subdir.read_callback_file(c_str!("variant"), &self.variant, &nul_array);
+ subdir.read_callback_file(c_str!("oem"), &self.oem, &nul_array);
+ }
+}
+
+fn hex(x: &u32, f: &mut Formatter<'_>) -> fmt::Result {
+ writeln!(f, "{x:#010x}")
+}
+
+impl Params {
+ fn build_debugfs<'data>(&'data self, dir: &ScopedDir<'data, '_>) {
+ dir.read_callback_file(c_str!("info_fmt"), &self.info_fmt, &hex);
+ dir.read_callback_file(c_str!("build_id"), &self.build_id, &no_quirk);
+ value_attrs!(dir, self, {
+ raw_version,
+ hardware_platform,
+ platform_version,
+ accessory_chip,
+ hardware_platform_subtype,
+ {raw_device_number, &hex},
+ {raw_device_family, &hex},
+ {chip_family, &hex},
+ {chip_id, &nul_array},
+ nproduct_id,
+ nsubset_parts_array_offset,
+ num_subset_parts,
+ ncluster_array_offset,
+ num_clusters,
+ nmodem_supported,
+ pcode,
+ feature_code,
+ oem_variant,
+ boot_core,
+ boot_cluster,
+ num_func_clusters,
+ foundry_id,
+ pmic_model,
+ pmic_die_rev,
+ pmic_model_array
+ });
+ if let Some(versions) = self.versions.as_ref() {
+ for (image_name, idx) in IMAGE_NAMES {
+ if let Some(version) = versions.0.get(*idx) {
+ version.build_debugfs(dir, image_name);
+ }
+ }
+ }
+ }
+}
+
+impl platform::Driver for QcomSocInfo {
+ type IdInfo = ();
+ const OF_ID_TABLE: Option<kernel::of::IdTable<Self::IdInfo>> = None;
+ fn probe(_dev: &Device<Core>, _id_info: Option<&Self::IdInfo>) -> Result<Pin<KBox<Self>>> {
+ let soc_info_mem = qcom_smem_get(
+ kernel::bindings::QCOM_SMEM_HOST_ANY,
+ kernel::bindings::SMEM_HW_SW_BUILD_ID,
+ )?;
+ let version_mem = qcom_smem_get(
+ kernel::bindings::QCOM_SMEM_HOST_ANY,
+ bindings::SMEM_IMAGE_VERSION_TABLE,
+ )?;
+ let info = SocInfo::from_mem(soc_info_mem, version_mem);
+ let backing = info.build_params()?;
+ let soc_info = KBox::pin_init(
+ try_pin_init!(
+ Self {
+ registration <- soc::DeviceRegistration::register(info.device_attribute()?),
+ params <- Scope::dir(backing, c_str!("qcom_socinfo_rs_scoped"),
+ Params::build_debugfs),
+ }
+ ),
+ GFP_KERNEL,
+ )?;
+
+ kernel::rand::add_device_randomness(soc_info_mem);
+
+ Ok(soc_info)
+ }
+}
--
2.51.0.rc1.167.g924127e9c0-goog
^ permalink raw reply related [flat|nested] 6+ messages in thread
end of thread, other threads:[~2025-08-19 23:12 UTC | newest]
Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-08-19 23:12 [PATCH WIP 0/5] qcom-socinfo Rust Implementation Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 1/5] rust: Add soc_device support Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 2/5] rust: transmute: Cleanup + Fixes Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 3/5] rust: Add support for feeding entropy to randomness pool Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 4/5] soc: qcom: socinfo: `File`-based example Matthew Maurer
2025-08-19 23:12 ` [PATCH WIP 5/5] soc: qcom: socinfo: `Scoped`-based example Matthew Maurer
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).