Re: [PATCH RFC v3 6/6] samples/rust: Add sample virtio-rtc driver [WIP]
Stefano Garzarella <[email protected]> Fri, 7 Aug 2026 16:09:28 +0200
| Newsgroups | dev.linux.lists.virtualization,org.kernel.vger.linux-kernel,org.kernel.vger.rust-for-linux |
|---|---|
| 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 >