diff mbox series

[RFC,v3,4/5] rust: add RTC core abstractions and data structures

Message ID 20260116162203.296844-5-sunke@kylinos.cn
State Under Review
Headers show
Series rust: Add RTC driver support | expand

Commit Message

Ke Sun Jan. 16, 2026, 4:22 p.m. UTC
Add Rust abstractions for RTC subsystem, including RtcDevice,
RtcTime, RtcWkAlrm data structures, RtcOps trait for driver
operations, and devm-managed device registration support.

Signed-off-by: Ke Sun <sunke@kylinos.cn>
---
 rust/helpers/helpers.c |    1 +
 rust/helpers/rtc.c     |    9 +
 rust/kernel/lib.rs     |    2 +
 rust/kernel/rtc.rs     | 1008 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 1020 insertions(+)
 create mode 100644 rust/helpers/rtc.c
 create mode 100644 rust/kernel/rtc.rs
diff mbox series

Patch

diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 79c72762ad9c4..1a5c103fb24ba 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -48,6 +48,7 @@ 
 #include "rcu.c"
 #include "refcount.c"
 #include "regulator.c"
+#include "rtc.c"
 #include "scatterlist.c"
 #include "security.c"
 #include "signal.c"
diff --git a/rust/helpers/rtc.c b/rust/helpers/rtc.c
new file mode 100644
index 0000000000000..862cd61670bfc
--- /dev/null
+++ b/rust/helpers/rtc.c
@@ -0,0 +1,9 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/rtc.h>
+
+int rust_helper_devm_rtc_register_device(struct rtc_device *rtc)
+{
+	return __devm_rtc_register_device(THIS_MODULE, rtc);
+}
+
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 3e557335fc5fe..1390073e4ae27 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -135,6 +135,8 @@ 
 pub mod rbtree;
 pub mod regulator;
 pub mod revocable;
+#[cfg(CONFIG_RTC_CLASS)]
+pub mod rtc;
 pub mod scatterlist;
 pub mod security;
 pub mod seq_file;
diff --git a/rust/kernel/rtc.rs b/rust/kernel/rtc.rs
new file mode 100644
index 0000000000000..27066ef900f0a
--- /dev/null
+++ b/rust/kernel/rtc.rs
@@ -0,0 +1,1008 @@ 
+// SPDX-License-Identifier: GPL-2.0
+
+//! RTC (Real-Time Clock) device support.
+//!
+//! C header: [`include/linux/rtc.h`](srctree/include/linux/rtc.h).
+use crate::{
+    bindings,
+    bitmap::Bitmap,
+    container_of,
+    device, //
+    error::{
+        from_err_ptr,
+        to_result,
+        Error,
+        VTABLE_DEFAULT_ERROR, //
+    },
+    prelude::*,
+    seq_file::SeqFile,
+    sync::aref::{
+        ARef,
+        AlwaysRefCounted, //
+    },
+    types::{
+        ForeignOwnable,
+        Opaque, //
+    },
+};
+
+use core::{
+    ffi::c_void,
+    marker::PhantomData,
+    ptr::NonNull, //
+};
+
+/// RTC time structure.
+/// Mirrors [`struct rtc_time`](srctree/include/uapi/linux/rtc.h).
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct RtcTime(pub bindings::rtc_time);
+
+impl RtcTime {
+    /// Creates a new `RtcTime` from a time64_t value.
+    pub fn from_time64(time: i64) -> Self {
+        let mut tm = Self(pin_init::zeroed());
+        // SAFETY: `rtc_time64_to_tm` is a pure function that only writes to
+        // the provided `struct rtc_time` pointer. The pointer is valid
+        // because `tm.0` is a valid mutable reference, and the function does
+        // not retain any references to it.
+        unsafe { bindings::rtc_time64_to_tm(time, &mut tm.0) };
+
+        tm
+    }
+
+    /// Converts this `RtcTime` to a time64_t value.
+    pub fn to_time64(&self) -> i64 {
+        // SAFETY: `rtc_tm_to_time64` is a pure function that only reads from
+        // the provided `struct rtc_time` pointer. The pointer is valid because
+        // `self.0` is a valid reference, and the function does not retain any
+        // references to it.
+        unsafe { bindings::rtc_tm_to_time64(core::ptr::from_ref(&self.0).cast_mut()) }
+    }
+
+    /// Sets this `RtcTime` from a time64_t value.
+    pub fn set_from_time64(&mut self, time: i64) {
+        // SAFETY: `rtc_time64_to_tm` is a pure function that only writes to
+        // the provided `struct rtc_time` pointer. The pointer is valid
+        // because `self.0` is a valid mutable reference, and the function
+        // does not retain any references to it.
+        unsafe { bindings::rtc_time64_to_tm(time, &mut self.0) };
+    }
+
+    /// Converts a time64_t value to an RTC time structure.
+    #[inline]
+    pub fn time64_to_tm(time: i64, tm: &mut Self) {
+        tm.set_from_time64(time);
+    }
+
+    /// Converts an RTC time structure to a time64_t value.
+    #[inline]
+    pub fn tm_to_time64(tm: &Self) -> i64 {
+        tm.to_time64()
+    }
+
+    /// Calculates the day of the year (0-365) from the date components.
+    #[inline]
+    pub fn year_days(&self) -> i32 {
+        // SAFETY: `rtc_year_days` is a pure function that only performs
+        // calculations based on the provided parameters. The values should be
+        // from valid RTC time structures and non-negative, but the function
+        // itself is safe to call with any values.
+        unsafe {
+            bindings::rtc_year_days(
+                self.0.tm_mday as u32,
+                self.0.tm_mon as u32,
+                self.0.tm_year as u32,
+            )
+        }
+    }
+
+    /// Returns the seconds field (0-59).
+    #[inline]
+    pub fn tm_sec(&self) -> i32 {
+        self.0.tm_sec
+    }
+
+    /// Sets the seconds field (0-59).
+    #[inline]
+    pub fn set_tm_sec(&mut self, sec: i32) {
+        self.0.tm_sec = sec;
+    }
+
+    /// Returns the minutes field (0-59).
+    #[inline]
+    pub fn tm_min(&self) -> i32 {
+        self.0.tm_min
+    }
+
+    /// Sets the minutes field (0-59).
+    #[inline]
+    pub fn set_tm_min(&mut self, min: i32) {
+        self.0.tm_min = min;
+    }
+
+    /// Returns the hours field (0-23).
+    #[inline]
+    pub fn tm_hour(&self) -> i32 {
+        self.0.tm_hour
+    }
+
+    /// Sets the hours field (0-23).
+    #[inline]
+    pub fn set_tm_hour(&mut self, hour: i32) {
+        self.0.tm_hour = hour;
+    }
+
+    /// Returns the day of the month (1-31).
+    #[inline]
+    pub fn tm_mday(&self) -> i32 {
+        self.0.tm_mday
+    }
+
+    /// Sets the day of the month (1-31).
+    #[inline]
+    pub fn set_tm_mday(&mut self, mday: i32) {
+        self.0.tm_mday = mday;
+    }
+
+    /// Returns the month (0-11, where 0 is January).
+    #[inline]
+    pub fn tm_mon(&self) -> i32 {
+        self.0.tm_mon
+    }
+
+    /// Sets the month (0-11, where 0 is January).
+    #[inline]
+    pub fn set_tm_mon(&mut self, mon: i32) {
+        self.0.tm_mon = mon;
+    }
+
+    /// Returns the year (years since 1900).
+    #[inline]
+    pub fn tm_year(&self) -> i32 {
+        self.0.tm_year
+    }
+
+    /// Sets the year (years since 1900).
+    #[inline]
+    pub fn set_tm_year(&mut self, year: i32) {
+        self.0.tm_year = year;
+    }
+
+    /// Returns the day of the week (0-6, where 0 is Sunday).
+    #[inline]
+    pub fn tm_wday(&self) -> i32 {
+        self.0.tm_wday
+    }
+
+    /// Sets the day of the week (0-6, where 0 is Sunday).
+    #[inline]
+    pub fn set_tm_wday(&mut self, wday: i32) {
+        self.0.tm_wday = wday;
+    }
+
+    /// Returns the day of the year (0-365).
+    #[inline]
+    pub fn tm_yday(&self) -> i32 {
+        self.0.tm_yday
+    }
+
+    /// Sets the day of the year (0-365).
+    #[inline]
+    pub fn set_tm_yday(&mut self, yday: i32) {
+        self.0.tm_yday = yday;
+    }
+
+    /// Returns the daylight saving time flag.
+    #[inline]
+    pub fn tm_isdst(&self) -> i32 {
+        self.0.tm_isdst
+    }
+
+    /// Sets the daylight saving time flag.
+    #[inline]
+    pub fn set_tm_isdst(&mut self, isdst: i32) {
+        self.0.tm_isdst = isdst;
+    }
+}
+
+impl Default for RtcTime {
+    fn default() -> Self {
+        Self(pin_init::zeroed())
+    }
+}
+
+/// RTC wake alarm structure.
+/// Mirrors [`struct rtc_wkalrm`](srctree/include/uapi/linux/rtc.h).
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct RtcWkAlrm(pub bindings::rtc_wkalrm);
+
+impl Default for RtcWkAlrm {
+    fn default() -> Self {
+        Self(pin_init::zeroed())
+    }
+}
+
+impl RtcWkAlrm {
+    /// Returns a reference to the alarm time.
+    #[inline]
+    pub fn get_time(&self) -> &RtcTime {
+        // SAFETY: `RtcTime` is `#[repr(transparent)]` over
+        // `bindings::rtc_time`, so the memory layout is identical. We can
+        // safely reinterpret the reference.
+        unsafe { &*(&raw const self.0.time).cast::<RtcTime>() }
+    }
+
+    /// Returns a mutable reference to the alarm time.
+    #[inline]
+    pub fn get_time_mut(&mut self) -> &mut RtcTime {
+        // SAFETY: `RtcTime` is `#[repr(transparent)]` over
+        // `bindings::rtc_time`, so the memory layout is identical. We can
+        // safely reinterpret the reference.
+        unsafe { &mut *(&raw mut self.0.time).cast::<RtcTime>() }
+    }
+
+    /// Sets the alarm time.
+    #[inline]
+    pub fn set_time(&mut self, time: RtcTime) {
+        self.0.time = time.0;
+    }
+
+    /// Returns the enabled field.
+    #[inline]
+    pub fn enabled(&self) -> bool {
+        self.0.enabled != 0
+    }
+
+    /// Sets the `enabled` field.
+    #[inline]
+    pub fn set_enabled(&mut self, enabled: bool) {
+        self.0.enabled = u8::from(enabled);
+    }
+
+    /// Returns the pending field.
+    #[inline]
+    pub fn pending(&self) -> bool {
+        self.0.pending != 0
+    }
+
+    /// Sets the `pending` field.
+    #[inline]
+    pub fn set_pending(&mut self, pending: bool) {
+        self.0.pending = u8::from(pending);
+    }
+}
+
+/// RTC parameter structure.
+/// Mirrors [`struct rtc_param`](srctree/include/uapi/linux/rtc.h).
+#[repr(transparent)]
+#[derive(Clone, Copy)]
+pub struct RtcParam(pub bindings::rtc_param);
+
+impl Default for RtcParam {
+    fn default() -> Self {
+        Self(pin_init::zeroed())
+    }
+}
+
+/// Wrapper for an RTC device
+/// [`struct rtc_device`](srctree/include/linux/rtc.h).
+///
+/// # Invariants
+///
+/// A [`RtcDevice`] instance holds a pointer to a valid
+/// [`struct rtc_device`] that is registered and managed by the kernel.
+///
+#[repr(transparent)]
+pub struct RtcDevice<T: 'static = ()>(Opaque<bindings::rtc_device>, PhantomData<T>);
+
+impl<T: 'static> RtcDevice<T> {
+    /// Returns a raw pointer to the underlying `rtc_device`.
+    #[inline]
+    pub fn as_raw(&self) -> *mut bindings::rtc_device {
+        self.0.get()
+    }
+
+    /// Sets the minimum time range for the RTC device.
+    #[inline]
+    pub fn set_range_min(&self, min: i64) {
+        // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+        // `struct rtc_device`, and we're only writing to the `range_min` field.
+        unsafe { (*self.as_raw()).range_min = min };
+    }
+
+    /// Sets the maximum time range for the RTC device.
+    #[inline]
+    pub fn set_range_max(&self, max: u64) {
+        // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+        // `struct rtc_device`, and we're only writing to the `range_max` field.
+        unsafe { (*self.as_raw()).range_max = max };
+    }
+
+    /// Gets the minimum time range for the RTC device.
+    #[inline]
+    pub fn range_min(&self) -> i64 {
+        // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+        // `struct rtc_device`, and we're only reading the `range_min` field.
+        unsafe { (*self.as_raw()).range_min }
+    }
+
+    /// Gets the maximum time range for the RTC device.
+    #[inline]
+    pub fn range_max(&self) -> u64 {
+        // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+        // `struct rtc_device`, and we're only reading the `range_max` field.
+        unsafe { (*self.as_raw()).range_max }
+    }
+
+    /// Notifies the RTC framework that an interrupt has occurred.
+    #[inline]
+    pub fn update_irq(&self, num: usize, events: usize) {
+        // SAFETY: By the type invariants, self.as_raw() is a valid pointer
+        // to a `struct rtc_device`. The rtc_update_irq function handles
+        // NULL/ERR checks internally.
+        unsafe { bindings::rtc_update_irq(self.as_raw(), num, events) };
+    }
+
+    /// Clears a feature bit in the RTC device.
+    #[inline]
+    pub fn clear_feature(&self, feature: u32) {
+        // SAFETY: By the type invariants, self.as_raw() is a valid pointer to a
+        // `struct rtc_device`, and features is a valid bitmap array with
+        // RTC_FEATURE_CNT bits.
+        let features_bitmap = unsafe {
+            Bitmap::from_raw_mut(
+                (*self.as_raw()).features.as_mut_ptr().cast::<usize>(),
+                bindings::RTC_FEATURE_CNT as usize,
+            )
+        };
+        features_bitmap.clear_bit(feature as usize);
+    }
+}
+
+impl<T: 'static> AsRef<device::Device> for RtcDevice<T> {
+    fn as_ref(&self) -> &device::Device {
+        let raw = self.as_raw();
+        // SAFETY: By the type invariant of `Self`, `self.as_raw()` is a
+        // pointer to a valid `struct rtc_device`.
+        let dev = unsafe { &raw mut (*raw).dev };
+
+        // SAFETY: `dev` points to a valid `struct device`.
+        unsafe { device::Device::from_raw(dev) }
+    }
+}
+
+// SAFETY: Instances of `RtcDevice` are always reference-counted via the
+// underlying `device`. The `struct rtc_device` contains a `struct device dev`
+// as its first field, and the reference counting is managed through
+// `get_device`/`put_device` on the `dev` field.
+unsafe impl<T: 'static> AlwaysRefCounted for RtcDevice<T> {
+    fn inc_ref(&self) {
+        let dev: &device::Device = self.as_ref();
+        // SAFETY: The existence of a shared reference guarantees that the
+        // refcount is non-zero. `dev.as_raw()` is a valid pointer to a
+        // `struct device` with a non-zero refcount.
+        unsafe { bindings::get_device(dev.as_raw()) };
+    }
+
+    unsafe fn dec_ref(obj: NonNull<Self>) {
+        let rtc: *mut bindings::rtc_device = obj.cast().as_ptr();
+
+        // SAFETY: By the type invariant of `Self`, `rtc` is a pointer to a
+        // valid `struct rtc_device`. The `dev` field is the first field of
+        // `struct rtc_device`, so we can safely access it.
+        let dev = unsafe { &raw mut (*rtc).dev };
+
+        // SAFETY: The safety requirements guarantee that the refcount is
+        // non-zero.
+        unsafe { bindings::put_device(dev) };
+    }
+}
+
+// SAFETY: `RtcDevice` is reference-counted and can be released from any thread.
+unsafe impl<T: 'static> Send for RtcDevice<T> {}
+
+// SAFETY: `RtcDevice` can be shared among threads because all immutable
+// methods are protected by the synchronization in `struct rtc_device`
+// (via `ops_lock` mutex).
+unsafe impl<T: 'static> Sync for RtcDevice<T> {}
+
+impl<T: RtcOps> RtcDevice<T> {
+    /// Allocates a new RTC device managed by devres.
+    pub fn new(
+        parent_dev: &device::Device<device::Bound>,
+        init: impl PinInit<T, Error>,
+    ) -> Result<ARef<Self>> {
+        // Allocate RTC device.
+        // SAFETY: `devm_rtc_allocate_device` returns a pointer to a
+        // devm-managed rtc_device. We use `dev_internal.as_raw()` which is
+        // `pub(crate)`, but we can access it through the same device pointer.
+        let rtc: *mut bindings::rtc_device =
+            from_err_ptr(unsafe { bindings::devm_rtc_allocate_device(parent_dev.as_raw()) })?;
+
+        // Set the RTC device ops.
+        // SAFETY: We just allocated the RTC device, so it's safe to set
+        // the ops.
+        unsafe { (*rtc).ops = Adapter::<T>::VTABLE.as_raw() };
+
+        // SAFETY: `rtc` is a valid pointer to a newly allocated rtc_device.
+        // `RtcDevice` is `#[repr(transparent)]` over `Opaque<rtc_device>`, so
+        // we can safely cast.
+        let rtcdev = unsafe { &*rtc.cast::<Self>() };
+        // SAFETY: `rtc_device.as_raw()` is a valid pointer to a `struct
+        // rtc_device`.
+        let flags = unsafe {
+            Bitmap::from_raw_mut(
+                &mut (*rtcdev.as_raw()).flags,
+                bindings::RTC_FEATURE_CNT as usize,
+            )
+        };
+        flags.set_bit(bindings::RTC_OPS_USE_RTC_DEV as usize);
+
+        rtcdev.set_drvdata(init)?;
+        Ok(rtcdev.into())
+    }
+
+    /// Store a pointer to the bound driver's private data.
+    fn set_drvdata(&self, data: impl PinInit<T, Error>) -> Result {
+        let data = KBox::pin_init(data, GFP_KERNEL)?;
+
+        // SAFETY: `self.as_raw()` is a valid pointer to a `struct rtc_device`.
+        unsafe { bindings::dev_set_drvdata(self.as_ref().as_raw(), data.into_foreign().cast()) };
+        Ok(())
+    }
+
+    /// Borrows the driver's private data bound to this [`RtcDevice`].
+    pub fn drvdata(&self) -> Result<Pin<&T>> {
+        // SAFETY: `self.as_raw()` is a valid pointer to a `struct device`.
+        let ptr = unsafe { bindings::dev_get_drvdata(self.as_ref().as_raw()) };
+
+        if ptr.is_null() {
+            return Err(ENOENT);
+        }
+
+        // SAFETY: The caller ensures that `ptr` is valid and writable.
+        Ok(unsafe { Pin::<KBox<T>>::borrow(ptr.cast()) })
+    }
+
+    /// Registers this RTC device with the RTC subsystem.
+    pub fn register(&self) -> Result {
+        // Registers an RTC device with the RTC subsystem.
+        // SAFETY: The device will be automatically unregistered when the parent
+        // device is removed (devm cleanup). The helper function uses
+        // `THIS_MODULE` internally.
+        to_result(unsafe { bindings::devm_rtc_register_device(self.as_raw()) })
+    }
+
+    /// Returns a reference to the parent device of this RTC device.
+    ///
+    /// # Safety
+    ///
+    /// The caller must guarantee that the parent device exists and is bound.
+    /// This is guaranteed by the RTC core during `RtcOps` callbacks.
+    pub unsafe fn bound_parent_device(&self) -> &device::Device<device::Bound> {
+        // SAFETY: Per the function's safety contract, the parent device exists.
+        let parent = unsafe { self.as_ref().parent().unwrap_unchecked() };
+
+        // SAFETY: Per the function's safety contract, the parent device is
+        // bound. This is guaranteed by the RTC core during `RtcOps`
+        // callbacks.
+        unsafe { parent.as_bound() }
+    }
+}
+
+impl<T: 'static> Drop for RtcDevice<T> {
+    fn drop(&mut self) {
+        let dev = self.as_ref().as_raw();
+
+        // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to
+        // a `struct device`.
+        let ptr: *mut c_void = unsafe { bindings::dev_get_drvdata(dev) };
+
+        // SAFETY: By the type invariants, `self.as_raw()` is a valid pointer to
+        // a `struct device`.
+        unsafe { bindings::dev_set_drvdata(dev, core::ptr::null_mut()) };
+
+        if !ptr.is_null() {
+            // SAFETY: `ptr` comes from a previous call to `into_foreign()`,
+            // and `dev_get_drvdata()` guarantees to return the same pointer
+            // given to `dev_set_drvdata()`.
+            unsafe { drop(Pin::<KBox<T>>::from_foreign(ptr.cast())) };
+        }
+    }
+}
+
+/// Options for creating an RTC device.
+#[derive(Copy, Clone)]
+pub struct RtcDeviceOptions {
+    /// The name of the RTC device.
+    pub name: &'static CStr,
+}
+
+/// Trait defining the operations for an RTC driver.
+#[vtable]
+pub trait RtcOps: Sized + 'static {
+    /// Reads the current time from the RTC.
+    fn read_time(
+        _rtcdev: &RtcDevice<Self>,
+        _tm: &mut RtcTime,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets the time in the RTC.
+    fn set_time(
+        _rtcdev: &RtcDevice<Self>,
+        _tm: &RtcTime,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Reads the alarm time from the RTC.
+    fn read_alarm(
+        _rtcdev: &RtcDevice<Self>,
+        _alarm: &mut RtcWkAlrm,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets the alarm time in the RTC.
+    fn set_alarm(
+        _rtcdev: &RtcDevice<Self>,
+        _alarm: &RtcWkAlrm,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Enables or disables the alarm interrupt.
+    fn alarm_irq_enable(
+        _rtcdev: &RtcDevice<Self>,
+        _enabled: bool,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Handles custom ioctl commands.
+    fn ioctl(
+        _rtcdev: &RtcDevice<Self>,
+        _cmd: u32,
+        _arg: c_ulong,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result<c_int> {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Shows information in /proc/driver/rtc.
+    fn proc(
+        _rtcdev: &RtcDevice<Self>,
+        _seq: &mut SeqFile,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Reads the time offset.
+    fn read_offset(
+        _rtcdev: &RtcDevice<Self>,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result<i64> {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets the time offset.
+    fn set_offset(
+        _rtcdev: &RtcDevice<Self>,
+        _offset: i64,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Gets an RTC parameter.
+    fn param_get(
+        _rtcdev: &RtcDevice<Self>,
+        _param: &mut RtcParam,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+
+    /// Sets an RTC parameter.
+    fn param_set(
+        _rtcdev: &RtcDevice<Self>,
+        _param: &RtcParam,
+        _parent_dev: &device::Device<device::Bound>,
+    ) -> Result {
+        build_error!(VTABLE_DEFAULT_ERROR)
+    }
+}
+
+struct Adapter<T: RtcOps> {
+    _p: PhantomData<T>,
+}
+
+impl<T: RtcOps> Adapter<T> {
+    const VTABLE: RtcOpsVTable = create_rtc_ops::<T>();
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `tm` must be a valid pointer to a `struct rtc_time`.
+    unsafe extern "C" fn read_time(
+        dev: *mut bindings::device,
+        tm: *mut bindings::rtc_time,
+    ) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `tm` is valid and writable.
+        // `RtcTime` is `#[repr(transparent)]` over `bindings::rtc_time`, so we
+        // can safely cast.
+        let rtc_tm = unsafe { &mut *tm.cast::<RtcTime>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::read_time(rtc_dev, rtc_tm, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `tm` must be a valid pointer to a `struct rtc_time`.
+    unsafe extern "C" fn set_time(
+        dev: *mut bindings::device,
+        tm: *mut bindings::rtc_time,
+    ) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `tm` is valid.
+        // `RtcTime` is `#[repr(transparent)]` over `bindings::rtc_time`, so we
+        // can safely cast.
+        let rtc_tm = unsafe { &*tm.cast::<RtcTime>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::set_time(rtc_dev, rtc_tm, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `alarm` must be a valid pointer to a `struct rtc_wkalrm`.
+    unsafe extern "C" fn read_alarm(
+        dev: *mut bindings::device,
+        alarm: *mut bindings::rtc_wkalrm,
+    ) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `alarm` is valid and writable.
+        // `RtcWkAlrm` is `#[repr(transparent)]` over `bindings::rtc_wkalrm`, so
+        // we can safely cast.
+        let rtc_alarm = unsafe { &mut *alarm.cast::<RtcWkAlrm>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::read_alarm(rtc_dev, rtc_alarm, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `alarm` must be a valid pointer to a `struct rtc_wkalrm`.
+    unsafe extern "C" fn set_alarm(
+        dev: *mut bindings::device,
+        alarm: *mut bindings::rtc_wkalrm,
+    ) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `alarm` is valid.
+        // `RtcWkAlrm` is `#[repr(transparent)]` over `bindings::rtc_wkalrm`, so
+        // we can safely cast.
+        let rtc_alarm = unsafe { &*alarm.cast::<RtcWkAlrm>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::set_alarm(rtc_dev, rtc_alarm, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    unsafe extern "C" fn alarm_irq_enable(dev: *mut bindings::device, enabled: c_uint) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::alarm_irq_enable(rtc_dev, enabled != 0, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    unsafe extern "C" fn ioctl(dev: *mut bindings::device, cmd: c_uint, arg: c_ulong) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::ioctl(rtc_dev, cmd, arg, parent_dev) {
+            Ok(ret) => ret,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `seq` must be a valid pointer to a `struct seq_file`.
+    unsafe extern "C" fn proc(dev: *mut bindings::device, seq: *mut bindings::seq_file) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `seq` is valid and writable.
+        let seq_file = unsafe { &mut *seq.cast::<SeqFile>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::proc(rtc_dev, seq_file, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `offset` must be a valid pointer to a `long`.
+    unsafe extern "C" fn read_offset(dev: *mut bindings::device, offset: *mut c_long) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::read_offset(rtc_dev, parent_dev) {
+            Ok(offset_val) => {
+                // SAFETY: The caller ensures that `offset` is valid and
+                // writable.
+                unsafe { *offset.cast() = offset_val as c_long };
+                0
+            }
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    unsafe extern "C" fn set_offset(dev: *mut bindings::device, offset: c_long) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::set_offset(rtc_dev, offset as i64, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `param` must be a valid pointer to a `struct rtc_param`.
+    unsafe extern "C" fn param_get(
+        dev: *mut bindings::device,
+        param: *mut bindings::rtc_param,
+    ) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `param` is valid and writable.
+        // `RtcParam` is `#[repr(transparent)]` over `bindings::rtc_param`,
+        // so we can safely cast.
+        let rtc_param = unsafe { &mut *param.cast::<RtcParam>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::param_get(rtc_dev, rtc_param, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+
+    /// # Safety
+    ///
+    /// `dev` must be a valid pointer to the `struct device` embedded in a
+    /// `struct rtc_device`.
+    /// `param` must be a valid pointer to a `struct rtc_param`.
+    unsafe extern "C" fn param_set(
+        dev: *mut bindings::device,
+        param: *mut bindings::rtc_param,
+    ) -> c_int {
+        // SAFETY: `dev` is embedded in a `struct rtc_device`, so we can use
+        // `AsBusDevice` to get it.
+        let rtc_dev =
+            unsafe { &*container_of!(dev, bindings::rtc_device, dev).cast::<RtcDevice<T>>() };
+        // SAFETY: The caller ensures that `param` is valid.
+        // `RtcParam` is `#[repr(transparent)]` over `bindings::rtc_param`,
+        // so we can safely cast.
+        let rtc_param = unsafe { &*param.cast::<RtcParam>() };
+        // SAFETY: The RTC core guarantees that the parent device exists and is
+        // bound during `RtcOps` callbacks.
+        let parent_dev = unsafe { rtc_dev.bound_parent_device() };
+
+        match T::param_set(rtc_dev, rtc_param, parent_dev) {
+            Ok(()) => 0,
+            Err(err) => err.to_errno(),
+        }
+    }
+}
+
+/// VTable structure wrapper for RTC operations.
+/// Mirrors [`struct rtc_class_ops`](srctree/include/linux/rtc.h).
+#[repr(transparent)]
+pub struct RtcOpsVTable(bindings::rtc_class_ops);
+
+// SAFETY: RtcOpsVTable is Send. The vtable contains only function pointers,
+// which are simple data types that can be safely moved across threads. The
+// thread-safety of calling these functions is handled by the kernel's locking
+// mechanisms.
+unsafe impl Send for RtcOpsVTable {}
+
+// SAFETY: RtcOpsVTable is Sync. The vtable is immutable after it is created,
+// so it can be safely referenced and accessed concurrently by multiple threads
+// e.g. to read the function pointers.
+unsafe impl Sync for RtcOpsVTable {}
+
+impl RtcOpsVTable {
+    /// Returns a raw pointer to the underlying `rtc_class_ops` struct.
+    pub(crate) const fn as_raw(&self) -> *const bindings::rtc_class_ops {
+        &self.0
+    }
+}
+
+/// Creates an RTC operations vtable for a type `T` that implements
+/// `RtcOps`.
+pub const fn create_rtc_ops<T: RtcOps>() -> RtcOpsVTable {
+    let mut ops: bindings::rtc_class_ops = pin_init::zeroed();
+
+    ops.read_time = if T::HAS_READ_TIME {
+        Some(Adapter::<T>::read_time)
+    } else {
+        None
+    };
+    ops.set_time = if T::HAS_SET_TIME {
+        Some(Adapter::<T>::set_time)
+    } else {
+        None
+    };
+    ops.read_alarm = if T::HAS_READ_ALARM {
+        Some(Adapter::<T>::read_alarm)
+    } else {
+        None
+    };
+    ops.set_alarm = if T::HAS_SET_ALARM {
+        Some(Adapter::<T>::set_alarm)
+    } else {
+        None
+    };
+    ops.alarm_irq_enable = if T::HAS_ALARM_IRQ_ENABLE {
+        Some(Adapter::<T>::alarm_irq_enable)
+    } else {
+        None
+    };
+    ops.ioctl = if T::HAS_IOCTL {
+        Some(Adapter::<T>::ioctl)
+    } else {
+        None
+    };
+    ops.proc_ = if T::HAS_PROC {
+        Some(Adapter::<T>::proc)
+    } else {
+        None
+    };
+    ops.read_offset = if T::HAS_READ_OFFSET {
+        Some(Adapter::<T>::read_offset)
+    } else {
+        None
+    };
+    ops.set_offset = if T::HAS_SET_OFFSET {
+        Some(Adapter::<T>::set_offset)
+    } else {
+        None
+    };
+    ops.param_get = if T::HAS_PARAM_GET {
+        Some(Adapter::<T>::param_get)
+    } else {
+        None
+    };
+    ops.param_set = if T::HAS_PARAM_SET {
+        Some(Adapter::<T>::param_set)
+    } else {
+        None
+    };
+
+    RtcOpsVTable(ops)
+}
+
+/// Declares a kernel module that exposes a single RTC driver.
+///
+/// # Examples
+///
+///```ignore
+/// kernel::module_rtc_platform_driver! {
+///     type: MyDriver,
+///     name: "Module name",
+///     authors: ["Author name"],
+///     description: "Description",
+///     license: "GPL v2",
+/// }
+///```
+#[macro_export]
+macro_rules! module_rtc_platform_driver {
+    ($($user_args:tt)*) => {
+        $crate::module_platform_driver! {
+            $($user_args)*
+            imports_ns: ["RTC"],
+        }
+    };
+}