The platform abstraction can register a driver (platform::Driver) but not create
a device for it to bind to. Virtual drivers that spawn their own devices -- for
instance DisplayLink's evdi, which creates one platform device per virtual
display card from a sysfs attribute or at module load -- need the provider side.

Add platform::RegisteredDevice, a thin owner around
platform_device_register_full() whose Drop calls platform_device_unregister()
(which unbinds any bound driver first), plus DEVID_AUTO/DEVID_NONE constants and
a name/id/parent/dma_mask constructor. The non-generic methods are #[inline] so
out-of-tree modules can use them without an exported symbol.

Signed-off-by: Mike Lothian <[email protected]>
Assisted-by: Claude:claude-opus-4-8 [Claude-Code]
---
 rust/kernel/platform.rs | 77 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 77 insertions(+)

diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs
index 9b362e0495d3..4c2ded2e264f 100644
--- a/rust/kernel/platform.rs
+++ b/rust/kernel/platform.rs
@@ -571,3 +571,80 @@ unsafe impl Sync for Device {}
 // SAFETY: Same as `Device<Normal>` -- the underlying `struct platform_device` 
is the same;
 // `Bound` is a zero-sized type-state marker that does not affect thread 
safety.
 unsafe impl Sync for Device<device::Bound> {}
+
+/// Auto-assign a platform device instance id (`PLATFORM_DEVID_AUTO`).
+pub const DEVID_AUTO: i32 = bindings::PLATFORM_DEVID_AUTO;
+/// Create a platform device with no instance id (`PLATFORM_DEVID_NONE`).
+pub const DEVID_NONE: i32 = bindings::PLATFORM_DEVID_NONE;
+
+/// An owned platform device created and registered at runtime.
+///
+/// This is the *provider* side of the platform bus: whereas [`Driver`] binds 
to devices the bus
+/// hands it, [`RegisteredDevice`] *creates* a `struct platform_device` and 
adds it to the bus,
+/// which then binds any matching [`Driver`] (calling its `probe`). Virtual 
drivers such as
+/// DisplayLink's `evdi` use this to spawn their DRM card devices on demand 
(from a sysfs `add`
+/// attribute or at module init).
+///
+/// [`RegisteredDevice::new`] wraps `platform_device_register_full()`; 
dropping the handle calls
+/// `platform_device_unregister()`, which unbinds the bound driver and 
releases the device.
+pub struct RegisteredDevice {
+    pdev: NonNull<bindings::platform_device>,
+}
+
+// SAFETY: `platform_device_register_full()`/`platform_device_unregister()` 
are internally
+// synchronized by the driver core and safe to call from any thread; the 
handle only unregisters.
+unsafe impl Send for RegisteredDevice {}
+// SAFETY: See `Send`; `&RegisteredDevice` exposes only the thread-safe 
`Device<Normal>`.
+unsafe impl Sync for RegisteredDevice {}
+
+impl RegisteredDevice {
+    /// Create and register a new platform device.
+    ///
+    /// `name` is the platform device name, which the platform bus also uses 
to match a [`Driver`].
+    /// `id` is the instance id: [`DEVID_AUTO`] to auto-assign a unique one, 
[`DEVID_NONE`] for an
+    /// unnumbered device, or a fixed non-negative value. If `parent` is 
given, the new device is
+    /// created as its child. `dma_mask` requests a coherent DMA mask of that 
many bits (pass `0`
+    /// to leave the platform default).
+    #[inline]
+    pub fn new(
+        name: &CStr,
+        id: i32,
+        parent: Option<&device::Device>,
+        dma_mask: u64,
+    ) -> Result<Self> {
+        let info = bindings::platform_device_info {
+            parent: parent.map_or(core::ptr::null_mut(), |p| p.as_raw()),
+            name: name.as_char_ptr(),
+            id,
+            dma_mask,
+            ..Default::default()
+        };
+
+        // SAFETY: `info` is fully initialized (all remaining fields zeroed) 
and only borrowed for
+        // the duration of the call, which copies out the fields it needs.
+        let pdev = unsafe { bindings::platform_device_register_full(&info) };
+        let pdev = crate::error::from_err_ptr(pdev)?;
+
+        Ok(Self {
+            // A successful `platform_device_register_full()` returns a valid, 
non-null pointer.
+            pdev: NonNull::new(pdev).ok_or(ENOMEM)?,
+        })
+    }
+
+    /// Return a reference to the created platform [`Device`].
+    #[inline]
+    pub fn device(&self) -> &Device {
+        // SAFETY: `self.pdev` points to a valid, registered `struct 
platform_device` owned by
+        // `self`; `Device` is a transparent wrapper over `struct 
platform_device`.
+        unsafe { &*self.pdev.as_ptr().cast::<Device>() }
+    }
+}
+
+impl Drop for RegisteredDevice {
+    #[inline]
+    fn drop(&mut self) {
+        // SAFETY: `self.pdev` was returned by 
`platform_device_register_full()` and has not been
+        // unregistered yet; this consumes that registration exactly once.
+        unsafe { bindings::platform_device_unregister(self.pdev.as_ptr()) };
+    }
+}
-- 
2.55.0

Reply via email to