| // 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, |
| error, |
| prelude::*, |
| str::CString, |
| types::Opaque, // |
| }; |
| use core::ptr::NonNull; |
| |
| /// Attributes for a SoC device. |
| /// |
| /// These are both exported to userspace under /sys/devices/socX and provided to other drivers to |
| /// match against via `soc_device_match` (not yet available in Rust) to enable quirks or |
| /// device-specific support where necessary. |
| /// |
| /// All fields are freeform - they have no specific formatting, just defined meanings. |
| /// For example, the [`machine`](`Attributes::machine`) field could be "DB8500" or |
| /// "Qualcomm Technologies, Inc. SM8560 HDK", but regardless it should identify a board or product. |
| pub struct Attributes { |
| /// Should generally be a board ID or product ID. Examples |
| /// include DB8500 (ST-Ericsson) or "Qualcomm Technologies, inc. SM8560 HDK". |
| /// |
| /// If this field is not populated, the SoC infrastructure will try to populate it from |
| /// `/model` in the device tree. |
| pub machine: Option<CString>, |
| /// The broader class this SoC belongs to. Examples include ux500 |
| /// (for DB8500) or Snapdragon (for SM8650). |
| /// |
| /// On chips with ARM firmware supporting SMCCC v1.2+, this may be a JEDEC JEP106 manufacturer |
| /// identification. |
| pub family: Option<CString>, |
| /// The manufacturing revision of the part. Frequently this is MAJOR.MINOR, but not always. |
| pub revision: Option<CString>, |
| /// Serial Number - uniquely identifies a specific SoC. If present, should be unique (buying a |
| /// replacement part should change it if present). This field cannot be matched on and is |
| /// solely present to export through /sys. |
| pub serial_number: Option<CString>, |
| /// SoC ID - identifies a specific SoC kind in question, sometimes more specifically than |
| /// `machine` if the same SoC is used in multiple products. Some devices use this to specify a |
| /// SoC name, e.g. "I.MX??", and others just print an ID number (e.g. Tegra and Qualcomm). |
| /// |
| /// On chips with ARM firmware supporting SMCCC v1.2+, this may be a JEDEC JEP106 manufacturer |
| /// identification (the family value) followed by a colon and then a 4-digit ID value. |
| pub soc_id: Option<CString>, |
| } |
| |
| struct BuiltAttributes { |
| // While `inner` has pointers to `_backing`, it is to the interior of the `CStrings`, not |
| // `backing` itself, so it does not need to be pinned. |
| _backing: Attributes, |
| // `Opaque` makes us `!Unpin`, as the registration holds a pointer to `inner` when used. |
| inner: Opaque<bindings::soc_device_attribute>, |
| } |
| |
| 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 BuiltAttributes { |
| fn as_mut_ptr(&self) -> *mut bindings::soc_device_attribute { |
| self.inner.get() |
| } |
| } |
| |
| impl Attributes { |
| fn build(self) -> BuiltAttributes { |
| BuiltAttributes { |
| inner: Opaque::new(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_data(PinnedDrop)] |
| /// Registration handle for your soc_dev. If you let it go out of scope, your soc_dev will be |
| /// unregistered. |
| pub struct Registration { |
| #[pin] |
| attr: BuiltAttributes, |
| soc_dev: NonNull<bindings::soc_device>, |
| } |
| |
| // SAFETY: We provide no operations through `&Registration`. |
| unsafe impl Sync for Registration {} |
| |
| // SAFETY: All pointers are normal allocations, not thread-specific. |
| unsafe impl Send for Registration {} |
| |
| #[pinned_drop] |
| impl PinnedDrop for Registration { |
| 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.as_ptr()) } |
| } |
| } |
| |
| impl Registration { |
| /// Register a new SoC device |
| pub fn new(attr: Attributes) -> impl PinInit<Self, Error> { |
| try_pin_init!(Self { |
| attr: attr.build(), |
| soc_dev: { |
| // 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. |
| // * `attr` is pinned, so the pinned data won't move. |
| // * 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. |
| let raw_soc = error::from_err_ptr(unsafe { |
| bindings::soc_device_register(attr.as_mut_ptr()) |
| })?; |
| |
| NonNull::new(raw_soc).ok_or(EINVAL)? |
| }, |
| }? Error) |
| } |
| } |