diff --git a/rust/helpers.c b/rust/helpers.c
index c3d80301185c..dc2405772b1a 100644
--- a/rust/helpers.c
+++ b/rust/helpers.c
@@ -34,6 +34,7 @@
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 #include <linux/pci.h>
+#include <linux/io.h>
 
 __noreturn void rust_helper_BUG(void)
 {
@@ -179,6 +180,111 @@ int rust_helper_devm_add_action(struct device *dev, void (*action)(void *), void
 	return devm_add_action(dev, action, data);
 }
 
+/* io.h */
+u8 rust_helper_readb(const volatile void __iomem *addr)
+{
+	return readb(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readb);
+
+u16 rust_helper_readw(const volatile void __iomem *addr)
+{
+	return readw(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readw);
+
+u32 rust_helper_readl(const volatile void __iomem *addr)
+{
+	return readl(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readl);
+
+#ifdef CONFIG_64BIT
+u64 rust_helper_readq(const volatile void __iomem *addr)
+{
+	return readq(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readq);
+#endif
+
+void rust_helper_writeb(u8 value, volatile void __iomem *addr)
+{
+	writeb(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writeb);
+
+void rust_helper_writew(u16 value, volatile void __iomem *addr)
+{
+	writew(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writew);
+
+void rust_helper_writel(u32 value, volatile void __iomem *addr)
+{
+	writel(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writel);
+
+#ifdef CONFIG_64BIT
+void rust_helper_writeq(u64 value, volatile void __iomem *addr)
+{
+	writeq(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writeq);
+#endif
+
+u8 rust_helper_readb_relaxed(const volatile void __iomem *addr)
+{
+	return readb_relaxed(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readb_relaxed);
+
+u16 rust_helper_readw_relaxed(const volatile void __iomem *addr)
+{
+	return readw_relaxed(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readw_relaxed);
+
+u32 rust_helper_readl_relaxed(const volatile void __iomem *addr)
+{
+	return readl_relaxed(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readl_relaxed);
+
+#ifdef CONFIG_64BIT
+u64 rust_helper_readq_relaxed(const volatile void __iomem *addr)
+{
+	return readq_relaxed(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readq_relaxed);
+#endif
+
+void rust_helper_writeb_relaxed(u8 value, volatile void __iomem *addr)
+{
+	writeb_relaxed(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writeb_relaxed);
+
+void rust_helper_writew_relaxed(u16 value, volatile void __iomem *addr)
+{
+	writew_relaxed(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writew_relaxed);
+
+void rust_helper_writel_relaxed(u32 value, volatile void __iomem *addr)
+{
+	writel_relaxed(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writel_relaxed);
+
+#ifdef CONFIG_64BIT
+void rust_helper_writeq_relaxed(u64 value, volatile void __iomem *addr)
+{
+	writeq_relaxed(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writeq_relaxed);
+#endif
+
 void rust_helper_pci_set_drvdata(struct pci_dev *pdev, void *data)
 {
 	pci_set_drvdata(pdev, data);
diff --git a/rust/kernel/iomem.rs b/rust/kernel/iomem.rs
new file mode 100644
index 000000000000..efb6cd0829b4
--- /dev/null
+++ b/rust/kernel/iomem.rs
@@ -0,0 +1,135 @@
+// SPDX-License-Identifier: GPL-2.0
+
+use crate::bindings;
+use crate::error::{code::EINVAL, Result};
+
+/// IO-mapped memory, starting at the base pointer @ioptr and spanning @malxen bytes.
+///
+/// The creator (usually a subsystem such as PCI) is responsible for creating the
+/// mapping, performing an additional region request etc.
+pub struct IoMem {
+    pub ioptr: usize,
+    maxlen: usize,
+}
+
+impl IoMem {
+    pub(crate) fn new(ioptr: usize, maxlen: usize) -> Result<Self> {
+        if ioptr == 0 || maxlen == 0 {
+            return Err(EINVAL);
+        }
+
+        Ok(Self { ioptr, maxlen })
+    }
+
+    fn get_io_addr(&self, offset: usize, len: usize) -> Result<usize> {
+        if offset + len > self.maxlen {
+            return Err(EINVAL);
+        }
+
+        Ok(self.ioptr + offset)
+    }
+
+    pub fn readb(&self, offset: usize) -> Result<u8> {
+        let ioptr: usize = self.get_io_addr(offset, 1)?;
+
+        Ok(unsafe { bindings::readb(ioptr as _) })
+    }
+
+    pub fn readw(&self, offset: usize) -> Result<u16> {
+        let ioptr: usize = self.get_io_addr(offset, 2)?;
+
+        Ok(unsafe { bindings::readw(ioptr as _) })
+    }
+
+    pub fn readl(&self, offset: usize) -> Result<u32> {
+        let ioptr: usize = self.get_io_addr(offset, 4)?;
+
+        Ok(unsafe { bindings::readl(ioptr as _) })
+    }
+
+    pub fn readq(&self, offset: usize) -> Result<u64> {
+        let ioptr: usize = self.get_io_addr(offset, 8)?;
+
+        Ok(unsafe { bindings::readq(ioptr as _) })
+    }
+
+    pub fn readb_relaxed(&self, offset: usize) -> Result<u8> {
+        let ioptr: usize = self.get_io_addr(offset, 1)?;
+
+        Ok(unsafe { bindings::readb_relaxed(ioptr as _) })
+    }
+
+    pub fn readw_relaxed(&self, offset: usize) -> Result<u16> {
+        let ioptr: usize = self.get_io_addr(offset, 2)?;
+
+        Ok(unsafe { bindings::readw_relaxed(ioptr as _) })
+    }
+
+    pub fn readl_relaxed(&self, offset: usize) -> Result<u32> {
+        let ioptr: usize = self.get_io_addr(offset, 4)?;
+
+        Ok(unsafe { bindings::readl_relaxed(ioptr as _) })
+    }
+
+    pub fn readq_relaxed(&self, offset: usize) -> Result<u64> {
+        let ioptr: usize = self.get_io_addr(offset, 8)?;
+
+        Ok(unsafe { bindings::readq_relaxed(ioptr as _) })
+    }
+
+    pub fn writeb(&self, byte: u8, offset: usize) -> Result {
+        let ioptr: usize = self.get_io_addr(offset, 1)?;
+
+        unsafe { bindings::writeb(byte, ioptr as _) }
+        Ok(())
+    }
+
+    pub fn writew(&self, word: u16, offset: usize) -> Result {
+        let ioptr: usize = self.get_io_addr(offset, 2)?;
+
+        unsafe { bindings::writew(word, ioptr as _) }
+        Ok(())
+    }
+
+    pub fn writel(&self, lword: u32, offset: usize) -> Result {
+        let ioptr: usize = self.get_io_addr(offset, 4)?;
+
+        unsafe { bindings::writel(lword, ioptr as _) }
+        Ok(())
+    }
+
+    pub fn writeq(&self, qword: u64, offset: usize) -> Result {
+        let ioptr: usize = self.get_io_addr(offset, 8)?;
+
+        unsafe { bindings::writeq(qword, ioptr as _) }
+        Ok(())
+    }
+
+    pub fn writeb_relaxed(&self, byte: u8, offset: usize) -> Result {
+        let ioptr: usize = self.get_io_addr(offset, 1)?;
+
+        unsafe { bindings::writeb_relaxed(byte, ioptr as _) }
+        Ok(())
+    }
+
+    pub fn writew_relaxed(&self, word: u16, offset: usize) -> Result {
+        let ioptr: usize = self.get_io_addr(offset, 2)?;
+
+        unsafe { bindings::writew_relaxed(word, ioptr as _) }
+        Ok(())
+    }
+
+    pub fn writel_relaxed(&self, lword: u32, offset: usize) -> Result {
+        let ioptr: usize = self.get_io_addr(offset, 4)?;
+
+        unsafe { bindings::writel_relaxed(lword, ioptr as _) }
+        Ok(())
+    }
+
+    pub fn writeq_relaxed(&self, qword: u64, offset: usize) -> Result {
+        let ioptr: usize = self.get_io_addr(offset, 8)?;
+
+        unsafe { bindings::writeq_relaxed(qword, ioptr as _) }
+        Ok(())
+    }
+}
