Adds the ability to register SoC devices.
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 c5a7cda26c600e49c7ab0d547306d3281333f672..4ff01fb0f1bda27002094113c0bf9d074d28fdb6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -7700,6 +7700,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_debugfs_scoped.rs
F: samples/rust/rust_driver_platform.rs
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index a067038b4b422b4256f4a2b75fe644d47e6e82c8..9fdf76ca630e00715503e2a3a809bedc895697fd 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -80,6 +80,7 @@
#include <linux/sched.h>
#include <linux/security.h>
#include <linux/slab.h>
+#include <linux/sys_soc.h>
#include <linux/task_work.h>
#include <linux/tracepoint.h>
#include <linux/usb.h>
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index f812cf12004286962985a068665443dc22c389a2..6d637e2fed1b605e2dfc2e7b2247179439a90ba9 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -138,6 +138,8 @@
pub mod seq_file;
pub mod sizes;
pub mod slice;
+#[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.52.0.305.g3fc767764a-goog
On Sat Dec 13, 2025 at 12:14 AM CET, Matthew Maurer wrote:
> Adds the ability to register SoC devices.
Please use imperative mood and add at least one sentence for motivation.
> 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;
Please use kernel vertical style [1].
[1] https://docs.kernel.org/rust/coding-guidelines.html#imports
> +
> +/// Attributes for a SoC device
NIT: Please end with a period.
Also, can we slightly expand the documentation please? I mean, it is fairly
obvious what it is, but maybe one brief sentence what those attributes are about
does not hurt. :)
> +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>,
Please also expand on the documentation of the fields, only repeating the name
of the field does not seem overly useful.
For instance, the revision field could point out what kind of revision, and
whether the revision format is specific to the spcific SoC device, etc.
> +}
> +
> +// 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 {}
Here and in a few more places below, please end with a period.
> +#[pin_data]
> +struct BuiltDeviceAttribute {
> + #[pin]
> + backing: DeviceAttribute,
> + inner: bindings::soc_device_attribute,
> + // Since `inner` has pointers to `backing`, we are !Unpin
> + #[pin]
> + _pin: PhantomPinned,
It's not strictly required in this case, but I think it would be better to make
inner an Opaque<bindings::soc_device_attribute> and drop the PhantomPinned in
return.
> +}
> +
> +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 {
You can use Opaque::new() here.
> + 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))
> + }
> +}
I think the Device structure is neither used in the sample nor in your qcom
socinfo driver, and I don't see it being used in the near future either.
The only thing it does is to provide an unsafe register() function that calls
soc_device_register() which should rather be called by Registration::new()
instead. Hence, let's drop the Device struct entirely.
When moving the struct soc_device pointer to Registration, please use NonNull
instead of a raw pointer.
> +#[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 {
For consistency (DRM, auxiliary, i2c, PWM, etc.), please call this just Registration.
> + #[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))? },
Please prefer &raw.
> + _pin: PhantomPinned,
> + }? Error)
> + }
> +}
>
> --
> 2.52.0.305.g3fc767764a-goog
© 2016 - 2026 Red Hat, Inc.