Re: [PATCH RFC v3 6/6] samples/rust: Add sample virtio-rtc driver [WIP]

Stefano Garzarella <[email protected]>
Newsgroups org.kernel.vger.rust-for-linux,dev.linux.lists.virtualization,org.kernel.vger.linux-kernel
Message-ID <anXiIAJelE4aj3wq@sgarzare-redhat>
On Sun, May 10, 2026 at 04:38:19PM +0300, Manos Pitsidianakis wrote:
>While the driver queries clocks and capabilities for each clock, it
>doesn't actually register them yet (TODO).
>
>Until I implement missing functionality, there is some dead code and
>some missing SAFETY comments.
>
>Signed-off-by: Manos Pitsidianakis <[email protected]>
>---
> MAINTAINERS                     |   1 +
> samples/rust/Kconfig            |  15 ++
> samples/rust/Makefile           |   1 +
> samples/rust/rust_virtio_rtc.rs | 403 ++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 420 insertions(+)
>
>diff --git a/MAINTAINERS b/MAINTAINERS
>index e8012f708df5d4ee858c82aec3269e615fc8caad..3ed579e8d3cc64d1749cf261cd68f6338a830c4d 100644
>--- a/MAINTAINERS
>+++ b/MAINTAINERS
>@@ -27937,6 +27937,7 @@ S:	Maintained
> F:	rust/helpers/virtio.c
> F:	rust/kernel/virtio.rs
> F:	rust/kernel/virtio/
>+F:	samples/rust/rust_virtio_rtc.rs
>
> VIRTIO CRYPTO DRIVER
> M:	Gonglei <[email protected]>
>diff --git a/samples/rust/Kconfig b/samples/rust/Kconfig
>index c49ab910634596aea4a1a73dac87585e084f420a..96a16aecc27198fd99f4ffd0ecdf0bc0876860c6 100644
>--- a/samples/rust/Kconfig
>+++ b/samples/rust/Kconfig
>@@ -179,4 +179,19 @@ config SAMPLE_RUST_HOSTPROGS
>
> 	  If unsure, say N.
>
>+config SAMPLE_RUST_VIRTIO_RTC
>+	tristate "Rust Virtio RTC driver"
>+	depends on VIRTIO
>+	depends on PTP_1588_CLOCK_OPTIONAL
>+	help
>+	 This driver provides current time from a Virtio RTC device. The driver
>+	 provides the time through one or more clocks. The Virtio RTC PTP
>+	 clocks and/or the Real Time Clock driver for Virtio RTC must be
>+	 enabled to expose the clocks to userspace.
>+
>+	 To compile this code as a module, choose M here: the module will be
>+	 called rust_virtio_rtc.
>+
>+	 If unsure, say M.
>+
> endif # SAMPLES_RUST
>diff --git a/samples/rust/Makefile b/samples/rust/Makefile
>index 6c0aaa58ccccfd12ef019f68ca784f6d977bc668..0142fd8656bb8cdc95b7ef54e3183b5e51358954 100644
>--- a/samples/rust/Makefile
>+++ b/samples/rust/Makefile
>@@ -16,6 +16,7 @@ obj-$(CONFIG_SAMPLE_RUST_DRIVER_FAUX)		+= rust_driver_faux.o
> obj-$(CONFIG_SAMPLE_RUST_DRIVER_AUXILIARY)	+= rust_driver_auxiliary.o
> obj-$(CONFIG_SAMPLE_RUST_CONFIGFS)		+= rust_configfs.o
> obj-$(CONFIG_SAMPLE_RUST_SOC)			+= rust_soc.o
>+obj-$(CONFIG_SAMPLE_RUST_VIRTIO_RTC)		+= rust_virtio_rtc.o
>
> rust_print-y := rust_print_main.o rust_print_events.o
>
>diff --git a/samples/rust/rust_virtio_rtc.rs b/samples/rust/rust_virtio_rtc.rs
>new file mode 100644
>index 0000000000000000000000000000000000000000..81f8b377f6c92716c17ea11542fb4262cfd90e1c
>--- /dev/null
>+++ b/samples/rust/rust_virtio_rtc.rs
>@@ -0,0 +1,403 @@
>+// SPDX-License-Identifier: GPL-2.0
>+
>+//! Rust virtio driver sample.
>+
>+use core::{
>+    ptr::NonNull, //
>+    sync::atomic::{
>+        AtomicU16,
>+        Ordering, //
>+    },
>+};
>+
>+use kernel::{
>+    device::{
>+        Bound,
>+        Core, //
>+    },
>+    new_mutex,    //
>+    new_spinlock, //
>+    prelude::*,
>+    sync::{
>+        Completion,
>+        Mutex,
>+        SpinLock, //
>+    },
>+    virtio::{
>+        self,
>+        utils::*,
>+        virtqueue::*, //
>+    },
>+};
>+
>+use pin_init::{
>+    stack_pin_init,
>+    stack_try_pin_init, //
>+};
>+
>+#[pin_data]
>+struct Token {
>+    resp_actual_size: u32,
>+    #[pin]
>+    responded: Completion,
>+}
>+
>+#[derive(Copy, Clone, Debug, Zeroable)]
>+#[repr(C)]
>+#[doc(alias = "virtio_rtc_req_head")]
>+struct ReqHead {
>+    msg_type: Le16,
>+    reserved: [u8; 6],
>+}
>+
>+#[derive(Copy, Clone, Debug, Zeroable)]
>+#[repr(C)]
>+#[doc(alias = "virtio_rtc_resp_head")]
>+struct RespHead {
>+    status: u8,
>+    reserved: [u8; 7],
>+}
>+
>+#[derive(Copy, Clone, Debug, Zeroable)]
>+#[repr(C)]
>+#[doc(alias = "virtio_rtc_resp_cfg")]
>+struct RespCfg {
>+    head: RespHead,
>+    /** # of clocks -> clock ids < num_clocks are valid */
>+    num_clocks: Le16,
>+    reserved: [u8; 6],
>+}
>+
>+#[derive(Debug, Zeroable)]
>+#[repr(C)]
>+#[doc(alias = "virtio_rtc_req_clock_cap")]
>+struct ReqClockCap {
>+    head: ReqHead,
>+    clock_id: Le16,
>+    reserved: [u8; 6],
>+}
>+
>+#[derive(Copy, Clone, Debug, Zeroable)]
>+#[repr(C)]
>+#[doc(alias = "virtio_rtc_resp_clock_cap")]
>+struct RespClockCap {
>+    head: RespHead,
>+    clock_type: u8,
>+    leap_second_smearing: u8,
>+    flags: u8,
>+    reserved: [u8; 5],
>+}
>+
>+#[derive(Debug, Zeroable)]
>+#[repr(C)]
>+#[doc(alias = "virtio_rtc_req_read")]
>+struct ReqRead {
>+    head: ReqHead,
>+    clock_id: Le16,
>+    reserved: [u8; 6],
>+}
>+
>+#[derive(Copy, Clone, Debug, Zeroable)]
>+#[repr(C)]
>+#[doc(alias = "virtio_rtc_resp_read")]
>+struct RespRead {
>+    head: RespHead,
>+    clock_reading: Le64,
>+}
>+
>+#[repr(u8)]
>+enum ClockType {
>+    #[doc(alias = "VIRTIO_RTC_CLOCK_UTC")]
>+    Utc = 0,
>+    #[doc(alias = "VIRTIO_RTC_CLOCK_TAI")]
>+    Tai = 1,
>+    #[doc(alias = "VIRTIO_RTC_CLOCK_MONOTONIC")]
>+    Monotonic = 2,
>+    #[doc(alias = "VIRTIO_RTC_CLOCK_UTC_SMEARED")]
>+    UtcSmeared = 3,
>+    #[doc(alias = "VIRTIO_RTC_CLOCK_UTC_MAYBE_SMEARED")]
>+    UtcMaybeSmeared = 4,
>+}
>+
>+/// Send a message and receive reply
>+fn send<Request: Zeroable + 'static, Response: Zeroable + Copy + 'static>(
>+    req_data: Request,
>+    vq: &SpinLock<VirtioRtcVq>,
>+    timeout_jiffies: c_ulong,
>+) -> Result<Response> {
>+    // FIXME: This lock should also disable irqs.
>+    let guard = vq.lock();
>+
>+    let req = VBox::<Request>::new(req_data, GFP_KERNEL)?;

I think we need to be careful with GFP_KERNEL allocations and spinlock, 
since IIRC GFP_KERNEL allocations may sleep.

>+    let mut resp = VBox::<Response>::new_uninit(GFP_KERNEL)?;
>+    let resp_ptr = NonNull::new(resp.as_mut_ptr()).unwrap();
>+
>+    stack_pin_init!(let token = pin_init!(Token {
>+        resp_actual_size: 0,
>+        responded <- Completion::new(),
>+    }));
>+    stack_try_pin_init!(let req_sgs = guard.reqvq().new_readable_sgtable(req, GFP_KERNEL));
>+    let req_sgs: Pin<&mut _> = req_sgs?;
>+    stack_try_pin_init!(let resp_sgs = guard.reqvq().new_writable_sgtable(resp, GFP_KERNEL));
>+    let resp_sgs: Pin<&mut _> = resp_sgs?;
>+
>+    guard
>+        .reqvq()
>+        .add_sgs(&req_sgs, &resp_sgs, token.as_ref(), GFP_ATOMIC)?;
>+
>+    if guard.reqvq().kick_prepare() {
>+        guard.reqvq().notify();
>+    }
>+    drop(guard);
>+
>+    if timeout_jiffies > 0 {
>+        token
>+            .responded
>+            .wait_for_completion_interruptible_timeout(timeout_jiffies)?;
>+    } else {
>+        token.responded.wait_for_completion_interruptible()?;
>+    }
>+
>+    if token.resp_actual_size as usize >= core::mem::size_of::<RespHead>() {
>+        // SAFETY: all response types contain a `RespHead` header at the start.
>+        let head: &RespHead = unsafe { resp_ptr.cast().as_ref() };
>+        match head.status {
>+            0 => {
>+                // OK, do nothing.
>+            }
>+            1 => return Err(ENOTSUPP),
>+            2 => return Err(ENODEV),
>+            3 => return Err(EINVAL),
>+            4 | 5_u8..=u8::MAX => return Err(EIO),
>+        }
>+    } else if token.resp_actual_size as usize != core::mem::size_of::<Response>() {

I think we should remove the else here, or check this condition first.

>+        return Err(EINVAL);
>+    }
>+    // SAFETY: we have checked that the device wrote the correct amount of bytes for this type and
>+    // has returned a successful status code.
>+    let resp = unsafe { *resp_ptr.as_ref() };
>+
>+    Ok(resp)
>+}
>+
>+const VIRTIO_RTC_REQ_READ: u16 = 0x0001;
>+const VIRTIO_RTC_REQ_CFG: u16 = 0x1000;
>+const VIRTIO_RTC_REQ_CLOCK_CAP: u16 = 0x1001;
>+
>+struct VirtioRtcVq {
>+    inner: Virtqueues,
>+}
>+
>+// SAFETY: `VirtioRtcVq` is safe to be send to any task.
>+unsafe impl Send for VirtioRtcVq {}
>+
>+impl VirtioRtcVq {
>+    fn new(inner: Virtqueues) -> impl PinInit<SpinLock<Self>> {
>+        new_spinlock!(Self { inner })
>+    }
>+
>+    fn reqvq(&self) -> &Virtqueue {
>+        unsafe { self.inner[0].as_ref() }
>+    }
>+}
>+
>+#[pin_data(PinnedDrop)]
>+struct VirtioRtcDriver {
>+    #[pin]
>+    virtqueues: SpinLock<VirtioRtcVq>,
>+    num_clocks: AtomicU16,
>+    #[pin]
>+    registered_clocks: Mutex<KVec<()>>,
>+}
>+
>+#[pinned_drop]
>+impl PinnedDrop for VirtioRtcDriver {
>+    fn drop(self: Pin<&mut Self>) {
>+        pr_info!("Remove Rust virtio driver sample.\n");
>+    }
>+}
>+
>+extern "C" fn vq_requestq_callback(vq: *mut kernel::bindings::virtqueue) {
>+    // SAFETY: The kernel called this virtqueue callback and it must have provided a valid `vq`
>+    // pointer
>+    let vq = unsafe { Virtqueue::from_raw(vq) };
>+    let dev: &virtio::Device<Bound> = vq.dev().expect("Could not get device");
>+    let data = dev
>+        .as_ref()
>+        .drvdata::<VirtioRtcDriver>()
>+        .expect("Could not borrow drvdata");
>+    data.process_requestq();
>+}
>+
>+impl VirtioRtcDriver {
>+    /// Submit `VIRTIO_RTC_REQ_CFG` and return response (`num_clocks`)
>+    fn req_cfg(&self) -> Result<u16> {
>+        let head = ReqHead {
>+            msg_type: VIRTIO_RTC_REQ_CFG.into(),
>+            reserved: [0; 6],
>+        };
>+        let response: RespCfg = send(head, &self.virtqueues, 0)?;
>+        pr_info!("Got response! {response:?}\n");
>+
>+        Ok(response.num_clocks.into())
>+    }
>+
>+    fn process_requestq(&self) {
>+        let mut cb_enabled = true;
>+        loop {
>+            // FIXME: This lock should also disable irqs.
>+            let guard = self.virtqueues.lock();
>+            if cb_enabled {
>+                guard.reqvq().disable_cb();
>+                cb_enabled = false;
>+            }
>+            if let Some((token, len)) = guard.reqvq().get_buf() {
>+                drop(guard);
>+                pr_info!("process_requestq got buf {len} bytes\n");
>+                let mut token = token.cast::<Token>();
>+                // SAFETY: pointer points to a valid Token that we have added to the virtqueue.
>+                let token_ref = unsafe { token.as_mut() };

Could this be an UB?

IIUC send() still holds a Pin<&mut Token> on its stack, blocked in the 
completion wait, so we will have 2 exclusive references to the same 
object.


>+                token_ref.resp_actual_size = len;
>+                token_ref.responded.complete_all();
>+                pr_info!("process_requestq ok\n");
>+            } else {
>+                if guard.reqvq().enable_cb() {
>+                    return;
>+                }
>+                cb_enabled = true;
>+            }
>+        }
>+    }
>+
>+    fn clock_cap(&self, clock_id: u16) -> Result<RespClockCap> {
>+        let req = ReqClockCap {
>+            head: ReqHead {
>+                msg_type: VIRTIO_RTC_REQ_CLOCK_CAP.into(),
>+                reserved: [0; 6],
>+            },
>+            clock_id: clock_id.into(),
>+            reserved: [0; 6],
>+        };
>+        let response: RespClockCap = send(req, &self.virtqueues, 0)?;
>+        pr_info!("Got response: {response:?}\n");
>+        Ok(response)
>+    }
>+
>+    fn read(&self, clock_id: u16) -> Result<u64> {
>+        let req = ReqRead {
>+            head: ReqHead {
>+                msg_type: VIRTIO_RTC_REQ_READ.into(),
>+                reserved: [0; 6],
>+            },
>+            clock_id: clock_id.into(),
>+            reserved: [0; 6],
>+        };
>+        let response: RespRead = send(req, &self.virtqueues, 0)?;
>+        pr_info!("Got response: {response:?}\n");
>+        Ok(response.clock_reading.into())
>+    }
>+}
>+
>+impl virtio::Driver for VirtioRtcDriver {
>+    type IdInfo = ();
>+
>+    /// The table of device ids supported by the driver.
>+    const ID_TABLE: virtio::IdTable<Self::IdInfo> = &VIRTIO_RTC_TABLE;
>+
>+    fn probe(vdev: &virtio::Device<Core>) -> impl PinInit<Self, Error> {
>+        let vqs_info: [VirtqueueInfo; 1] = [
>+            VirtqueueInfo::new(c"requestq", false, Some(vq_requestq_callback)),
>+            //VirtqueueInfo::new(c"alarmq", false, vq_callback),
>+        ];
>+        try_pin_init!(Self {
>+            num_clocks: AtomicU16::new(0),
>+            virtqueues <- {
>+                pr_info!("Probe Rust virtio driver sample.\n");
>+                let vqs = match vdev.find_vqs(&vqs_info) {
>+                    Ok(vqs) => {
>+                        pr_info!("Found {} vqs.\n", vqs.len());
>+                        vqs
>+                    }
>+                    Err(err) => {
>+                        pr_info!("Could not find vqs: {err:?}.\n");
>+
>+                        return Err(err);
>+                    }
>+                };
>+
>+                VirtioRtcVq::new(vqs)
>+            },
>+            registered_clocks <- new_mutex!(KVec::with_capacity(0, GFP_KERNEL)?),
>+        })
>+    }
>+
>+    fn init(&self, vdev: &virtio::Device<Bound>) -> Result {
>+        let num_clocks = self.req_cfg()?;
>+        self.num_clocks.store(num_clocks, Ordering::SeqCst);
>+        for i in 0..num_clocks {
>+            let mut is_exposed = false;
>+
>+            let resp = self.clock_cap(i)?;
>+            let (clock_type, leap_second_smearing, flags) =
>+                (resp.clock_type, resp.leap_second_smearing, resp.flags);
>+            if cfg!(CONFIG_VIRTIO_RTC_CLASS)
>+                && (clock_type == ClockType::Utc as u8
>+                    || clock_type == ClockType::UtcSmeared as u8
>+                    || clock_type == ClockType::UtcMaybeSmeared as u8)
>+            {
>+                // TODO:
>+
>+                // 	ret = viortc_init_rtc_class_clock(viortc, vio_clk_id,
>+                // 					  clock_type, flags);
>+                // 	if (ret < 0)
>+                // 		return ret;
>+                // 	if (ret > 0)
>+                // 		is_exposed = true;
>+                dev_warn!(vdev.as_ref(), "CONFIG_VIRTIO_RTC_CLASS TODO ");
>+            }
>+
>+            if cfg!(CONFIG_VIRTIO_RTC_PTP) {
>+                // TODO:
>+
>+                // 	ret = viortc_init_ptp_clock(viortc, vio_clk_id, clock_type,
>+                // 				    leap_second_smearing);
>+                // 	if (ret < 0)
>+                // 		return ret;
>+                // 	if (ret > 0)
>+                // 		is_exposed = true;
>+                // todo!()
>+                dev_warn!(vdev.as_ref(), "CONFIG_VIRTIO_RTC_PTP TODO ");
>+            }
>+
>+            if !is_exposed {
>+                dev_warn!(
>+                    vdev.as_ref(),
>+                    "cannot expose clock {i} (type {clock_type}, variant {leap_second_smearing}, \
>+                    flags {flags}) to userspace\n"
>+                );
>+            }
>+            let clock_reading = self.read(i)?;
>+            pr_info!("#{i} clock reading = {clock_reading}\n");
>+        }
>+        Ok(())
>+    }
>+
>+    fn remove(_: &virtio::Device<Core>, _this: Pin<&Self>) {
>+        pr_info!("Removing Rust virtio driver sample.\n");
>+    }
>+}
>+
>+kernel::virtio_device_table!(
>+    VIRTIO_RTC_TABLE,
>+    MODULE_VIRTIO_RTC_TABLE,
>+    <VirtioRtcDriver as virtio::Driver>::IdInfo,
>+    [(virtio::DeviceId::new(virtio::VirtioID::Clock), ())]
>+);
>+
>+kernel::module_virtio_driver! {
>+    type: VirtioRtcDriver,
>+    name: "rust_virtio_rtc",
>+    authors: ["Manos Pitsidianakis"],
>+    description: "Rust virtio driver",
>+    license: "GPL v2",
>+}
>
>-- 
>2.47.3
>
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.