diff --git a/rust/qemu-api/meson.build b/rust/qemu-api/meson.build
index 508986948883..5bf3c3dfab67 100644
--- a/rust/qemu-api/meson.build
+++ b/rust/qemu-api/meson.build
@@ -29,6 +29,7 @@ _qemu_api_rs = static_library(
       'src/qdev.rs',
       'src/qom.rs',
       'src/sysbus.rs',
+      'src/timer.rs',
       'src/vmstate.rs',
       'src/zeroable.rs',
     ],
diff --git a/rust/qemu-api/src/lib.rs b/rust/qemu-api/src/lib.rs
index e60c9ac16409..495261976dbc 100644
--- a/rust/qemu-api/src/lib.rs
+++ b/rust/qemu-api/src/lib.rs
@@ -17,6 +17,7 @@
 pub mod qdev;
 pub mod qom;
 pub mod sysbus;
+pub mod timer;
 pub mod vmstate;
 pub mod zeroable;
 
diff --git a/rust/qemu-api/src/timer.rs b/rust/qemu-api/src/timer.rs
new file mode 100644
index 000000000000..4f9e8c9277c6
--- /dev/null
+++ b/rust/qemu-api/src/timer.rs
@@ -0,0 +1,123 @@
+// Copyright (C) 2024 Intel Corporation.
+// Author(s): Zhao Liu <zhai1.liu@intel.com>
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use std::{
+    borrow::BorrowMut,
+    boxed::Box,
+    os::raw::{c_int, c_void},
+    ptr::NonNull,
+};
+
+pub use bindings::QEMUTimer;
+
+use crate::bindings::{self, *};
+
+impl QEMUTimer {
+    fn new() -> Self {
+        QEMUTimer {
+            expire_time: 0,
+            timer_list: ::core::ptr::null_mut(),
+            cb: None,
+            opaque: ::core::ptr::null_mut(),
+            next: ::core::ptr::null_mut(),
+            attributes: 0,
+            scale: 0,
+        }
+    }
+
+    // TODO: Consider how to avoid passing in C style callbacks directly.
+    fn timer_new_full<T: QEMUTimerImpl>(
+        timer_list_group: Option<&mut QEMUTimerListGroup>,
+        clk_type: QEMUClockType,
+        scale: u32,
+        attributes: u32,
+        opaque: &mut T::Opaque,
+    ) -> Self {
+        let mut ts: Box<QEMUTimer> = Box::new(QEMUTimer::new());
+        let group_ptr = if let Some(g) = timer_list_group {
+            g
+        } else {
+            ::core::ptr::null_mut()
+        };
+
+        // Safety:
+        // ts is a valid Box object which can borrow a valid mutable
+        // pointer, and opaque is converted from a reference so it's
+        // also valid.
+        unsafe {
+            timer_init_full(
+                ts.borrow_mut(),
+                group_ptr,
+                clk_type,
+                scale as c_int,
+                attributes as c_int,
+                Some(rust_timer_handler::<T>),
+                (opaque as *mut T::Opaque).cast::<c_void>(),
+            )
+        };
+
+        *ts
+    }
+
+    pub fn timer_mod(&mut self, expire_time: u64) {
+        unsafe { timer_mod(self as *mut QEMUTimer, expire_time as i64) }
+    }
+
+    pub fn timer_del(&mut self) {
+        unsafe { timer_del(self as *mut QEMUTimer) };
+    }
+}
+
+/// timer expiration callback
+unsafe extern "C" fn rust_timer_handler<T: QEMUTimerImpl>(opaque: *mut c_void) {
+    // SAFETY:
+    // the pointer is convertible to a reference
+    let para = unsafe { NonNull::new(opaque.cast::<T::Opaque>()).unwrap().as_mut() };
+
+    T::QEMU_TIMER_CB.unwrap()(para);
+}
+
+pub trait QEMUTimerImpl {
+    type Opaque;
+
+    // To be more general, opaque is mutable here. But it still should
+    // be protected by BqlCell/BqlRefCell.
+    //
+    // FIXME: limit opaque to immutable?
+    const QEMU_TIMER_CB: Option<fn(opaque: &mut Self::Opaque)> = None;
+
+    fn timer_new(clk_type: QEMUClockType, scale: u32, opaque: &mut Self::Opaque) -> QEMUTimer
+    where
+        Self: Sized,
+    {
+        QEMUTimer::timer_new_full::<Self>(None, clk_type, scale, 0, opaque)
+    }
+
+    fn timer_new_ns(clk_type: QEMUClockType, opaque: &mut Self::Opaque) -> QEMUTimer
+    where
+        Self: Sized,
+    {
+        Self::timer_new(clk_type, SCALE_NS, opaque)
+    }
+
+    fn timer_new_us(clk_type: QEMUClockType, opaque: &mut Self::Opaque) -> QEMUTimer
+    where
+        Self: Sized,
+    {
+        Self::timer_new(clk_type, SCALE_US, opaque)
+    }
+
+    fn timer_new_ms(clk_type: QEMUClockType, opaque: &mut Self::Opaque) -> QEMUTimer
+    where
+        Self: Sized,
+    {
+        Self::timer_new(clk_type, SCALE_MS, opaque)
+    }
+}
+
+pub fn qemu_clock_get_virtual_ns() -> u64 {
+    // SAFETY:
+    // Valid parameter.
+    (unsafe { qemu_clock_get_ns(QEMUClockType::QEMU_CLOCK_VIRTUAL) }) as u64
+}
diff --git a/rust/wrapper.h b/rust/wrapper.h
index 033f3e9cf32c..0da42e84933a 100644
--- a/rust/wrapper.h
+++ b/rust/wrapper.h
@@ -63,3 +63,4 @@ typedef enum memory_order {
 #include "migration/vmstate.h"
 #include "chardev/char-serial.h"
 #include "exec/memattrs.h"
+#include "qemu/timer.h"
