* [PATCH 0/3] hw/misc: QUP UART device
@ 2026-10-27 18:38 Brian Cain
2026-10-27 18:38 ` [PATCH 1/3] hw/misc: add the QUP wrapper device Brian Cain
` (3 more replies)
0 siblings, 4 replies; 11+ messages in thread
From: Brian Cain @ 2026-10-27 18:38 UTC (permalink / raw)
To: qemu-devel
Cc: Manos Pitsidianakis, qemu-rust, Brian Cain, Paolo Bonzini,
Pierrick Bouvier
Add the QUP + GENI uart device models. The QUP is a "Universal Peripheral"
present on Qualcomm SoCs, with connections for SPI, UART, I2C, I3C. For
now we're only interested in UART.
Will follow up with a v2 including wiring into machines + qtest after
https://lore.kernel.org/qemu-devel/20261027143721.4125584-1-brian.cain@oss.qualcomm.com/
lands.
Brian Cain (3):
hw/misc: add the QUP wrapper device
rust/chardev: fix write errors and add write watches
rust/hw/char: add QUP GENI UART device model
MAINTAINERS | 3 +
docs/system/device-emulation.rst | 1 +
docs/system/devices/qup.rst | 10 +
include/hw/misc/qup-wrapper.h | 27 +
hw/misc/qup-wrapper.c | 90 ++
Cargo.lock | 15 +
Cargo.toml | 1 +
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 3 +
rust/chardev/src/chardev.rs | 82 +-
rust/hw/char/Kconfig | 4 +
rust/hw/char/meson.build | 1 +
rust/hw/char/qup_geni_uart/Cargo.toml | 27 +
rust/hw/char/qup_geni_uart/meson.build | 28 +
rust/hw/char/qup_geni_uart/src/device.rs | 1293 +++++++++++++++++++
rust/hw/char/qup_geni_uart/src/lib.rs | 9 +
rust/hw/char/qup_geni_uart/src/registers.rs | 160 +++
17 files changed, 1741 insertions(+), 16 deletions(-)
create mode 100644 docs/system/devices/qup.rst
create mode 100644 include/hw/misc/qup-wrapper.h
create mode 100644 hw/misc/qup-wrapper.c
create mode 100644 rust/hw/char/qup_geni_uart/Cargo.toml
create mode 100644 rust/hw/char/qup_geni_uart/meson.build
create mode 100644 rust/hw/char/qup_geni_uart/src/device.rs
create mode 100644 rust/hw/char/qup_geni_uart/src/lib.rs
create mode 100644 rust/hw/char/qup_geni_uart/src/registers.rs
--
2.34.1
^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH 1/3] hw/misc: add the QUP wrapper device
2026-10-27 18:38 [PATCH 0/3] hw/misc: QUP UART device Brian Cain
@ 2026-10-27 18:38 ` Brian Cain
2026-10-27 18:38 ` [PATCH 2/3] rust/chardev: fix write errors and add write watches Brian Cain
` (2 subsequent siblings)
3 siblings, 0 replies; 11+ messages in thread
From: Brian Cain @ 2026-10-27 18:38 UTC (permalink / raw)
To: qemu-devel
Cc: Manos Pitsidianakis, qemu-rust, Brian Cain, Paolo Bonzini,
Pierrick Bouvier
The Linux qcom_geni_serial driver expects a GENI UART to be a child of
a QUP wrapper.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
MAINTAINERS | 2 +
include/hw/misc/qup-wrapper.h | 27 +++++++++++
hw/misc/qup-wrapper.c | 90 +++++++++++++++++++++++++++++++++++
hw/misc/Kconfig | 3 ++
hw/misc/meson.build | 3 ++
5 files changed, 125 insertions(+)
create mode 100644 include/hw/misc/qup-wrapper.h
create mode 100644 hw/misc/qup-wrapper.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 4ca1751e30f..064d9531d3e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1383,7 +1383,9 @@ M: Brian Cain <brian.cain@oss.qualcomm.com>
R: Pierrick Bouvier <pierrick.bouvier@oss.qualcomm.com>
S: Supported
F: hw/hexagon/
+F: hw/misc/qup-wrapper.c
F: include/hw/hexagon/
+F: include/hw/misc/qup-wrapper.h
F: configs/devices/hexagon-softmmu/default.mak
F: docs/system/hexagon/
F: tests/functional/hexagon/
diff --git a/include/hw/misc/qup-wrapper.h b/include/hw/misc/qup-wrapper.h
new file mode 100644
index 00000000000..6180092b799
--- /dev/null
+++ b/include/hw/misc/qup-wrapper.h
@@ -0,0 +1,27 @@
+/*
+ * QUP wrapper (GENI SE QUP) device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_QUP_WRAPPER_H
+#define HW_MISC_QUP_WRAPPER_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_QUP_WRAPPER "qup-wrapper"
+OBJECT_DECLARE_SIMPLE_TYPE(QupWrapperState, QUP_WRAPPER)
+
+#define QUP_WRAPPER_MMIO_SIZE 0x6000
+
+struct QupWrapperState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+
+ uint32_t hw_version;
+};
+
+#endif /* HW_MISC_QUP_WRAPPER_H */
diff --git a/hw/misc/qup-wrapper.c b/hw/misc/qup-wrapper.c
new file mode 100644
index 00000000000..40c79dc66e6
--- /dev/null
+++ b/hw/misc/qup-wrapper.c
@@ -0,0 +1,90 @@
+/*
+ * QUP wrapper device
+ *
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/core/sysbus.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/misc/qup-wrapper.h"
+#include "qemu/log.h"
+
+#define REG_QUPV3_HW_VER 0x4
+
+static uint64_t qup_wrapper_read(void *opaque, hwaddr offset, unsigned size)
+{
+ QupWrapperState *s = QUP_WRAPPER(opaque);
+ uint64_t value = 0;
+
+ switch (offset) {
+ case REG_QUPV3_HW_VER:
+ value = s->hw_version;
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "qup-wrapper: read from unknown offset 0x%"
+ HWADDR_PRIx "\n", offset);
+ break;
+ }
+
+ return value;
+}
+
+static void qup_wrapper_write(void *opaque, hwaddr offset, uint64_t value,
+ unsigned size)
+{
+ qemu_log_mask(LOG_UNIMP, "qup-wrapper: write to offset 0x%"
+ HWADDR_PRIx " value 0x%" PRIx64 "\n", offset, value);
+}
+
+static const MemoryRegionOps qup_wrapper_ops = {
+ .read = qup_wrapper_read,
+ .write = qup_wrapper_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ },
+};
+
+static void qup_wrapper_init(Object *obj)
+{
+ QupWrapperState *s = QUP_WRAPPER(obj);
+
+ memory_region_init_io(&s->iomem, obj, &qup_wrapper_ops, s,
+ TYPE_QUP_WRAPPER, QUP_WRAPPER_MMIO_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static const Property qup_wrapper_properties[] = {
+ DEFINE_PROP_UINT32("hw-version", QupWrapperState, hw_version,
+ 0x30100000),
+};
+
+static void qup_wrapper_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+
+ device_class_set_props(dc, qup_wrapper_properties);
+ dc->desc = "Qualcomm QUP wrapper";
+}
+
+static const TypeInfo qup_wrapper_info = {
+ .name = TYPE_QUP_WRAPPER,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(QupWrapperState),
+ .instance_init = qup_wrapper_init,
+ .class_init = qup_wrapper_class_init,
+};
+
+static void qup_wrapper_register_types(void)
+{
+ type_register_static(&qup_wrapper_info);
+}
+
+type_init(qup_wrapper_register_types)
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 7abd22869a4..aa76a2398a9 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -266,4 +266,7 @@ config K230_DECOMP_GZIP
config SOPHGO_CV1800B_CLK
bool
+config QUP_WRAPPER
+ bool
+
source macio/Kconfig
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 90f3f89de70..9cf897abc76 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -181,3 +181,6 @@ system_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa_ec.c'))
system_ss.add(when: 'CONFIG_LASI', if_true: files('lasi.c'))
system_ss.add(when: 'CONFIG_AXIADO_CLK', if_true: files('axiado_clk.c'))
+
+# Qualcomm QUP devices
+system_ss.add(when: 'CONFIG_QUP_WRAPPER', if_true: files('qup-wrapper.c'))
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 2/3] rust/chardev: fix write errors and add write watches
2026-10-27 18:38 [PATCH 0/3] hw/misc: QUP UART device Brian Cain
2026-10-27 18:38 ` [PATCH 1/3] hw/misc: add the QUP wrapper device Brian Cain
@ 2026-10-27 18:38 ` Brian Cain
2026-10-28 7:03 ` Philippe Mathieu-Daudé
2026-10-27 18:38 ` [PATCH 3/3] rust/hw/char: add QUP GENI UART device model Brian Cain
2026-10-27 18:43 ` [PATCH 0/3] hw/misc: QUP UART device Pierrick Bouvier
3 siblings, 1 reply; 11+ messages in thread
From: Brian Cain @ 2026-10-27 18:38 UTC (permalink / raw)
To: qemu-devel
Cc: Manos Pitsidianakis, qemu-rust, Brian Cain, Paolo Bonzini,
Pierrick Bouvier
qemu_chr_fe_write() reports failure as -1 with the cause in errno, not
as a negated errno, so errno::into_io_result() turned every failure into
EPERM and a frontend could never see EAGAIN. Build the error from
errno instead, in both CharFrontend::write() and the BQL-free variant.
Also add CharFrontend::add_write_watch() and remove_watch(), so that a
device whose backend would block can retry once it is writable again.
remove_watch() tolerates a source that the backend has already torn
down.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
rust/chardev/src/chardev.rs | 82 +++++++++++++++++++++++++++++--------
1 file changed, 66 insertions(+), 16 deletions(-)
diff --git a/rust/chardev/src/chardev.rs b/rust/chardev/src/chardev.rs
index fb9674b3fb4..8a3836d78e4 100644
--- a/rust/chardev/src/chardev.rs
+++ b/rust/chardev/src/chardev.rs
@@ -54,7 +54,11 @@ fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
let len = buf.len().try_into().unwrap();
let r = unsafe { bindings::qemu_chr_fe_write(addr_of_mut!(*chr), buf.as_ptr(), len) };
- errno::into_io_result(r).map(|cnt| cnt as usize)
+ if r < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(r as usize)
+ }
}
fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
@@ -62,13 +66,13 @@ fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
let len = buf.len().try_into().unwrap();
let r = unsafe { bindings::qemu_chr_fe_write_all(addr_of_mut!(*chr), buf.as_ptr(), len) };
- errno::into_io_result(r).and_then(|cnt| {
- if cnt as usize == buf.len() {
- Ok(())
- } else {
- Err(ErrorKind::WriteZero.into())
- }
- })
+ if r < 0 {
+ Err(io::Error::last_os_error())
+ } else if r as usize == buf.len() {
+ Ok(())
+ } else {
+ Err(ErrorKind::WriteZero.into())
+ }
}
}
@@ -193,6 +197,48 @@ pub fn accept_input(&self) {
unsafe { bindings::qemu_chr_fe_accept_input(addr_of_mut!(*chr)) }
}
+ /// Watch for output becoming writable or disconnected.
+ ///
+ /// # Safety
+ /// The caller must keep `opaque` valid until the source is removed, and
+ /// must remove the source before the frontend is destroyed.
+ pub unsafe fn add_write_watch(
+ &self,
+ callback: bindings::FEWatchFunc,
+ opaque: *mut c_void,
+ ) -> u32 {
+ let mut chr = self.inner.borrow_mut();
+ unsafe {
+ bindings::qemu_chr_fe_add_watch(
+ addr_of_mut!(*chr),
+ glib_sys::G_IO_OUT | glib_sys::G_IO_HUP,
+ callback,
+ opaque,
+ )
+ }
+ }
+
+ /// Remove a watch previously returned by `add_write_watch`.
+ pub fn remove_watch(&self, tag: u32) {
+ if tag == 0 {
+ return;
+ }
+ let chr = self.inner.borrow();
+ if chr.chr.is_null() {
+ return;
+ }
+ // SAFETY: the backend and its context remain valid while borrowed.
+ // Looking up the source handles watches already removed by the backend
+ // and also works when NULL selects the default context.
+ unsafe {
+ let context = (*chr.chr).gcontext;
+ let source = glib_sys::g_main_context_find_source_by_id(context, tag);
+ if !source.is_null() {
+ glib_sys::g_source_destroy(source);
+ }
+ }
+ }
+
/// Temporarily borrow the character device, allowing it to be used
/// as an implementor of `Write`. Note that it is not valid to drop
/// the big QEMU lock while the character device is borrowed, as
@@ -229,7 +275,11 @@ pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
let len = buf.len().try_into().unwrap();
// SAFETY: qemu_chr_fe_write is thread-safe
let r = unsafe { bindings::qemu_chr_fe_write(self.inner.as_ptr(), buf.as_ptr(), len) };
- errno::into_io_result(r).map(|cnt| cnt as usize)
+ if r < 0 {
+ Err(io::Error::last_os_error())
+ } else {
+ Ok(r as usize)
+ }
}
/// Write data to a character backend from the front end. This function
@@ -243,13 +293,13 @@ pub fn write_all(&self, buf: &[u8]) -> io::Result<()> {
let len = buf.len().try_into().unwrap();
// SAFETY: qemu_chr_fe_write_all is thread-safe
let r = unsafe { bindings::qemu_chr_fe_write_all(self.inner.as_ptr(), buf.as_ptr(), len) };
- errno::into_io_result(r).and_then(|cnt| {
- if cnt as usize == buf.len() {
- Ok(())
- } else {
- Err(ErrorKind::WriteZero.into())
- }
- })
+ if r < 0 {
+ Err(io::Error::last_os_error())
+ } else if r as usize == buf.len() {
+ Ok(())
+ } else {
+ Err(ErrorKind::WriteZero.into())
+ }
}
}
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* [PATCH 3/3] rust/hw/char: add QUP GENI UART device model
2026-10-27 18:38 [PATCH 0/3] hw/misc: QUP UART device Brian Cain
2026-10-27 18:38 ` [PATCH 1/3] hw/misc: add the QUP wrapper device Brian Cain
2026-10-27 18:38 ` [PATCH 2/3] rust/chardev: fix write errors and add write watches Brian Cain
@ 2026-10-27 18:38 ` Brian Cain
2026-10-27 19:05 ` Philippe Mathieu-Daudé
2026-10-27 18:43 ` [PATCH 0/3] hw/misc: QUP UART device Pierrick Bouvier
3 siblings, 1 reply; 11+ messages in thread
From: Brian Cain @ 2026-10-27 18:38 UTC (permalink / raw)
To: qemu-devel
Cc: Manos Pitsidianakis, qemu-rust, Brian Cain, Paolo Bonzini,
Pierrick Bouvier
Add a model of the QUP GENI serial engine in UART mode.
Supports FIFO, DMA, loopback modes - DMA via memory link.
Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
---
MAINTAINERS | 1 +
docs/system/device-emulation.rst | 1 +
docs/system/devices/qup.rst | 10 +
Cargo.lock | 15 +
Cargo.toml | 1 +
rust/hw/char/Kconfig | 4 +
rust/hw/char/meson.build | 1 +
rust/hw/char/qup_geni_uart/Cargo.toml | 27 +
rust/hw/char/qup_geni_uart/meson.build | 28 +
rust/hw/char/qup_geni_uart/src/device.rs | 1293 +++++++++++++++++++
rust/hw/char/qup_geni_uart/src/lib.rs | 9 +
rust/hw/char/qup_geni_uart/src/registers.rs | 160 +++
12 files changed, 1550 insertions(+)
create mode 100644 docs/system/devices/qup.rst
create mode 100644 rust/hw/char/qup_geni_uart/Cargo.toml
create mode 100644 rust/hw/char/qup_geni_uart/meson.build
create mode 100644 rust/hw/char/qup_geni_uart/src/device.rs
create mode 100644 rust/hw/char/qup_geni_uart/src/lib.rs
create mode 100644 rust/hw/char/qup_geni_uart/src/registers.rs
diff --git a/MAINTAINERS b/MAINTAINERS
index 064d9531d3e..24f7b127c77 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1386,6 +1386,7 @@ F: hw/hexagon/
F: hw/misc/qup-wrapper.c
F: include/hw/hexagon/
F: include/hw/misc/qup-wrapper.h
+F: rust/hw/char/qup_geni_uart/
F: configs/devices/hexagon-softmmu/default.mak
F: docs/system/hexagon/
F: tests/functional/hexagon/
diff --git a/docs/system/device-emulation.rst b/docs/system/device-emulation.rst
index a7d247a270e..e26e6aef3bc 100644
--- a/docs/system/device-emulation.rst
+++ b/docs/system/device-emulation.rst
@@ -95,6 +95,7 @@ Emulated Devices
devices/keyboard.rst
devices/net.rst
devices/nvme.rst
+ devices/qup.rst
devices/scsi/index.rst
devices/ufs.rst
devices/usb-u2f.rst
diff --git a/docs/system/devices/qup.rst b/docs/system/devices/qup.rst
new file mode 100644
index 00000000000..2e12be233a3
--- /dev/null
+++ b/docs/system/devices/qup.rst
@@ -0,0 +1,10 @@
+.. Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Qualcomm Universal Peripheral
+=============================
+
+QEMU provides models for the QUP wrapper and its GENI UART serial engine.
+See the `Qualcomm QUP documentation
+<https://docs.qualcomm.com/doc/80-70014-8/>`_ for the hardware programming
+model.
diff --git a/Cargo.lock b/Cargo.lock
index 0dd16cdb68a..a9fa55539d3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -280,6 +280,21 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "qup_geni_uart"
+version = "0.1.0"
+dependencies = [
+ "bql",
+ "chardev",
+ "common",
+ "hwcore",
+ "migration",
+ "qemu_macros",
+ "qom",
+ "system",
+ "util",
+]
+
[[package]]
name = "rustc_version"
version = "0.4.1"
diff --git a/Cargo.toml b/Cargo.toml
index b6b6d97dfec..a91a21c539c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
resolver = "2"
members = [
"rust/hw/char/pl011",
+ "rust/hw/char/qup_geni_uart",
"rust/hw/timer/hpet",
"rust/tests",
]
diff --git a/rust/hw/char/Kconfig b/rust/hw/char/Kconfig
index 5fe800c4806..488ec0a40c9 100644
--- a/rust/hw/char/Kconfig
+++ b/rust/hw/char/Kconfig
@@ -1,2 +1,6 @@
config X_PL011_RUST
bool
+
+config X_QUP_GENI_UART_RUST
+ bool
+ select QUP_WRAPPER
diff --git a/rust/hw/char/meson.build b/rust/hw/char/meson.build
index 5716dc43ef6..a220a74a37b 100644
--- a/rust/hw/char/meson.build
+++ b/rust/hw/char/meson.build
@@ -1 +1,2 @@
subdir('pl011')
+subdir('qup_geni_uart')
diff --git a/rust/hw/char/qup_geni_uart/Cargo.toml b/rust/hw/char/qup_geni_uart/Cargo.toml
new file mode 100644
index 00000000000..1285a18d5b6
--- /dev/null
+++ b/rust/hw/char/qup_geni_uart/Cargo.toml
@@ -0,0 +1,27 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+[package]
+name = "qup_geni_uart"
+version = "0.1.0"
+authors = ["Brian Cain <brian.cain@oss.qualcomm.com>"]
+description = "Qualcomm QUP GENI UART device model for QEMU"
+publish = false
+
+edition.workspace = true
+homepage.workspace = true
+license.workspace = true
+repository.workspace = true
+rust-version.workspace = true
+
+[dependencies]
+common = { path = "../../../common" }
+util = { path = "../../../util" }
+bql = { path = "../../../bql" }
+migration = { path = "../../../migration" }
+qom = { path = "../../../qom" }
+chardev = { path = "../../../chardev" }
+system = { path = "../../../system" }
+hwcore = { path = "../../../hw/core" }
+qemu_macros = { path = "../../../qemu-macros" }
+
+[lints]
+workspace = true
diff --git a/rust/hw/char/qup_geni_uart/meson.build b/rust/hw/char/qup_geni_uart/meson.build
new file mode 100644
index 00000000000..e2530e6b4a9
--- /dev/null
+++ b/rust/hw/char/qup_geni_uart/meson.build
@@ -0,0 +1,28 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+_libqup_geni_uart_rs = static_library(
+ 'qup_geni_uart',
+ files('src/lib.rs'),
+ override_options: ['rust_std=2021', 'build.rust_std=2021'],
+ rust_abi: 'rust',
+ link_with: [
+ _util_rs,
+ _migration_rs,
+ _bql_rs,
+ _qom_rs,
+ _chardev_rs,
+ _system_rs,
+ _hwcore_rs,
+ ],
+ dependencies: [
+ common_rs,
+ qemu_macros,
+ ],
+)
+
+rust_devices_ss.add(when: 'CONFIG_X_QUP_GENI_UART_RUST', if_true: [declare_dependency(
+ link_whole: [_libqup_geni_uart_rs],
+ # Putting proc macro crates in `dependencies` is necessary for Meson to find
+ # them when compiling the root per-target static rust lib.
+ dependencies: [qemu_macros],
+ variables: {'crate': 'qup_geni_uart'},
+)])
diff --git a/rust/hw/char/qup_geni_uart/src/device.rs b/rust/hw/char/qup_geni_uart/src/device.rs
new file mode 100644
index 00000000000..759ce8cf595
--- /dev/null
+++ b/rust/hw/char/qup_geni_uart/src/device.rs
@@ -0,0 +1,1293 @@
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+use std::{
+ ffi::{c_void, CStr},
+ io::ErrorKind,
+ ptr,
+};
+
+use bql::prelude::*;
+use chardev::prelude::*;
+use common::{prelude::*, Opaque};
+use hwcore::{bindings::qdev_prop_allow_set_link_before_realize, prelude::*};
+use migration::{self, prelude::*};
+use qom::{
+ bindings::{object_property_add_link, object_ref, object_unref, OBJ_PROP_LINK_STRONG},
+ prelude::*,
+};
+use system::{
+ bindings::{
+ address_space_destroy, address_space_init, address_space_rw, get_system_memory,
+ AddressSpace,
+ },
+ prelude::*,
+ MEMTXATTRS_UNSPECIFIED,
+};
+use util::{self, log::Log, log_mask_ln};
+
+use crate::registers::{self, RegisterOffset, UART_FIFO_DEPTH_WORDS};
+
+pub const QUP_FIFO_DEPTH: u32 = UART_FIFO_DEPTH_WORDS;
+
+const QUP_GENI_UART_MMIO_SIZE: u64 = 0x6000;
+
+const DMA_CHUNK_SIZE: usize = 4096;
+
+#[repr(transparent)]
+#[derive(Debug)]
+pub struct Fifo([u32; QUP_FIFO_DEPTH as usize]);
+
+impl Default for Fifo {
+ fn default() -> Self {
+ Self([0u32; QUP_FIFO_DEPTH as usize])
+ }
+}
+impl_vmstate_forward!(Fifo);
+
+impl std::ops::IndexMut<u32> for Fifo {
+ fn index_mut(&mut self, idx: u32) -> &mut Self::Output {
+ &mut self.0[idx as usize]
+ }
+}
+
+impl std::ops::Index<u32> for Fifo {
+ type Output = u32;
+
+ fn index(&self, idx: u32) -> &Self::Output {
+ &self.0[idx as usize]
+ }
+}
+
+#[repr(C)]
+#[derive(Debug, Default)]
+pub struct QupGeniUartRegs {
+ // Common GENI registers
+ pub geni_status: u32,
+ pub geni_m_cmd0: u32,
+ pub geni_m_irq_status: u32,
+ pub geni_m_irq_en: u32,
+ pub geni_s_cmd0: u32,
+ pub geni_s_irq_status: u32,
+ pub geni_s_irq_en: u32,
+ pub geni_tx_watermark: u32,
+ pub geni_rx_watermark: u32,
+ pub geni_rx_rfr_watermark: u32,
+ pub geni_m_gp_length: u32,
+ pub geni_s_gp_length: u32,
+ pub geni_tx_packing_cfg0: u32,
+ pub geni_tx_packing_cfg1: u32,
+ pub geni_rx_packing_cfg0: u32,
+ pub geni_rx_packing_cfg1: u32,
+
+ // UART specific registers
+ pub uart_tx_trans_cfg: u32,
+ pub uart_rx_trans_cfg: u32,
+ pub uart_tx_word_len: u32,
+ pub uart_rx_word_len: u32,
+ pub uart_tx_stop_bit_len: u32,
+ pub uart_rx_stale_cnt: u32,
+ pub uart_tx_parity_cfg: u32,
+ pub uart_rx_parity_cfg: u32,
+ pub uart_loopback_cfg: u32,
+ pub uart_io_macro_ctrl: u32,
+ pub uart_manual_rfr: u32,
+ pub uart_tx_trans_len: u32,
+
+ // TX FIFO writes go directly to the backend, so only RX needs storage.
+ pub rx_fifo: Fifo,
+ pub rx_fifo_len: u32,
+ // Valid bytes in the final RX FIFO word; 0 means the word is full.
+ pub rx_last_byte_cnt: u32,
+ // Set once the guest observes the size of a short final word.
+ pub rx_tail_closed: bool,
+ pub tx_remaining: u32,
+
+ // DMA registers
+ pub dma_tx_ptr_l: u32,
+ pub dma_tx_ptr_h: u32,
+ pub dma_tx_attr: u32,
+ pub dma_tx_len: u32,
+ pub dma_tx_sent: u32,
+ pub dma_tx_irq_en: u32,
+ pub dma_tx_irq_stat: u32,
+ pub dma_rx_ptr_l: u32,
+ pub dma_rx_ptr_h: u32,
+ pub dma_rx_attr: u32,
+ pub dma_rx_len: u32,
+ pub dma_rx_len_in: u32,
+ pub dma_rx_irq_en: u32,
+ pub dma_rx_irq_stat: u32,
+
+ // Runtime state
+ pub tx_enabled: bool,
+ pub rx_enabled: bool,
+ pub dma_mode_enabled: bool,
+ // A TX DMA may be configured before its TX command is started.
+ pub dma_tx_armed: bool,
+ pub dma_tx_active: bool,
+ pub dma_rx_active: bool,
+}
+
+/// Target of the device's "memory" link property.
+#[derive(Clone, Copy)]
+#[repr(transparent)]
+pub struct DmaRegion(*mut system::bindings::MemoryRegion);
+
+// SAFETY: the pointer is only used under the BQL
+unsafe impl Send for DmaRegion {}
+
+struct DmaAddressSpace(Opaque<AddressSpace>);
+
+// SAFETY: address_space_rw() may be called from any thread
+unsafe impl Sync for DmaAddressSpace {}
+
+#[repr(C)]
+#[derive(qemu_macros::Object, hwcore::Device)]
+pub struct QupGeniUartState {
+ pub parent_obj: ParentField<SysBusDevice>,
+ pub iomem: MemoryRegion,
+ #[property(rename = "chardev")]
+ pub char_frontend: CharFrontend,
+ pub regs: BqlRefCell<QupGeniUartRegs>,
+ pub irq: InterruptSource,
+ pub tx_watch: BqlCell<u32>,
+ pub dma_mr: BqlCell<DmaRegion>,
+ dma_as: DmaAddressSpace,
+ dma_as_ready: BqlCell<bool>,
+ // Prevent DMA to this device's MMIO from recursively starting TX DMA.
+ dma_tx_busy: BqlCell<bool>,
+}
+
+qom_isa!(QupGeniUartState : SysBusDevice, DeviceState, Object);
+
+unsafe impl ObjectType for QupGeniUartState {
+ type Class = <SysBusDevice as ObjectType>::Class;
+ const TYPE_NAME: &'static CStr = crate::TYPE_QUP_GENI_UART;
+}
+
+impl ObjectImpl for QupGeniUartState {
+ type ParentType = SysBusDevice;
+
+ const INSTANCE_INIT: Option<unsafe fn(ParentInit<Self>)> = Some(Self::init);
+ const INSTANCE_POST_INIT: Option<fn(&Self)> = Some(Self::post_init);
+ const CLASS_INIT: fn(&mut Self::Class) = Self::Class::class_init::<Self>;
+}
+
+impl DeviceImpl for QupGeniUartState {
+ const VMSTATE: Option<VMStateDescription<Self>> = Some(VMSTATE_QUP_GENI_UART);
+ const REALIZE: Option<fn(&Self) -> util::Result<()>> = Some(Self::realize);
+}
+
+impl ResettablePhasesImpl for QupGeniUartState {
+ const HOLD: Option<fn(&Self, ResetType)> = Some(Self::reset_hold);
+}
+
+impl SysBusDeviceImpl for QupGeniUartState {}
+
+impl QupGeniUartRegs {
+ // The model has no TX FIFO, so any nonzero watermark is always reached.
+ fn tx_watermark_latch(&mut self) {
+ if self.geni_tx_watermark > 0 {
+ self.geni_m_irq_status |= registers::M_TX_FIFO_WATERMARK;
+ } else {
+ self.geni_m_irq_status &= !registers::M_TX_FIFO_WATERMARK;
+ }
+ }
+
+ pub fn read(&mut self, offset: RegisterOffset) -> (bool, u32) {
+ use RegisterOffset::*;
+
+ let mut update_irq = false;
+ let result = match offset {
+ SE_GENI_STATUS => self.geni_status,
+ GENI_FW_REVISION_RO => {
+ (registers::GENI_SE_UART << registers::FW_REV_PROTOCOL_SHFT) | 0x0001
+ }
+ SE_GENI_M_IRQ_STATUS => self.geni_m_irq_status,
+ SE_GENI_M_IRQ_EN => self.geni_m_irq_en,
+ SE_GENI_M_CMD0 => self.geni_m_cmd0,
+ SE_GENI_S_CMD0 => self.geni_s_cmd0,
+ SE_GENI_TX_WATERMARK_REG => self.geni_tx_watermark,
+ SE_GENI_RX_WATERMARK_REG => self.geni_rx_watermark,
+ SE_GENI_RX_RFR_WATERMARK_REG => self.geni_rx_rfr_watermark,
+ SE_UART_TX_WORD_LEN => self.uart_tx_word_len,
+ SE_UART_RX_WORD_LEN => self.uart_rx_word_len,
+ SE_UART_TX_STOP_BIT_LEN => self.uart_tx_stop_bit_len,
+ SE_UART_RX_STALE_CNT => self.uart_rx_stale_cnt,
+ SE_UART_TX_TRANS_CFG => self.uart_tx_trans_cfg,
+ SE_UART_RX_TRANS_CFG => self.uart_rx_trans_cfg,
+ SE_UART_LOOPBACK_CFG => self.uart_loopback_cfg,
+ SE_UART_IO_MACRO_CTRL => self.uart_io_macro_ctrl,
+ SE_UART_MANUAL_RFR => self.uart_manual_rfr,
+ SE_GENI_TX_PACKING_CFG0 => self.geni_tx_packing_cfg0,
+ SE_GENI_TX_PACKING_CFG1 => self.geni_tx_packing_cfg1,
+ SE_GENI_RX_PACKING_CFG0 => self.geni_rx_packing_cfg0,
+ SE_GENI_RX_PACKING_CFG1 => self.geni_rx_packing_cfg1,
+ SE_GENI_DMA_MODE_EN => self.dma_mode_enabled as u32,
+ SE_GENI_TX_FIFO_STATUS => 0,
+ SE_GENI_RX_FIFO_STATUS => {
+ let mut status = self.rx_fifo_len & registers::RX_FIFO_WC_MSK;
+ // Without RX_LAST, the guest treats a short word as four bytes.
+ if self.rx_fifo_len > 0 && self.rx_last_byte_cnt > 0 {
+ self.rx_tail_closed = true;
+ status |= registers::RX_LAST
+ | (self.rx_last_byte_cnt << registers::RX_LAST_BYTE_VALID_SHFT)
+ & registers::RX_LAST_BYTE_VALID_MSK;
+ }
+ status
+ }
+ SE_GENI_TX_FIFO_N => {
+ log_mask_ln!(Log::GuestError, "QUP GENI UART: TX FIFO read not supported");
+ 0
+ }
+ SE_GENI_RX_FIFO_N => {
+ if self.rx_fifo_len > 0 {
+ let data = self.rx_fifo[0];
+ for i in 0..self.rx_fifo_len - 1 {
+ self.rx_fifo[i] = self.rx_fifo[i + 1];
+ }
+ self.rx_fifo_len -= 1;
+ if self.rx_fifo_len < self.geni_rx_watermark {
+ self.geni_s_irq_status &= !registers::S_RX_FIFO_WATERMARK;
+ }
+ if self.rx_fifo_len == 0 {
+ self.geni_s_irq_status &= !registers::S_RX_FIFO_LAST;
+ self.rx_last_byte_cnt = 0;
+ self.rx_tail_closed = false;
+ }
+ update_irq = true;
+ data
+ } else {
+ 0
+ }
+ }
+ SE_GENI_S_IRQ_STATUS => self.geni_s_irq_status,
+ SE_GENI_S_IRQ_EN => self.geni_s_irq_en,
+ GENI_SER_M_CLK_CFG | GENI_SER_S_CLK_CFG | SE_GENI_CLK_SEL => 0,
+ GENI_OUTPUT_CTRL | GENI_FORCE_DEFAULT => 0,
+ GENI_IF_DISABLE_RO => 0,
+ SE_GENI_IOS => 0x3, // TX/RX lines idle high
+ SE_GENI_M_GP_LENGTH => self.geni_m_gp_length,
+ SE_GENI_S_GP_LENGTH => self.geni_s_gp_length,
+ SE_UART_TX_TRANS_LEN => self.uart_tx_trans_len,
+ SE_UART_TX_PARITY_CFG => self.uart_tx_parity_cfg,
+ SE_UART_RX_PARITY_CFG => self.uart_rx_parity_cfg,
+ SE_GSI_EVENT_EN | SE_IRQ_EN => 0,
+ SE_HW_PARAM0 => {
+ (registers::UART_FIFO_WIDTH_BITS << registers::TX_FIFO_WIDTH_SHFT)
+ | (registers::UART_FIFO_DEPTH_WORDS << registers::TX_FIFO_DEPTH_SHFT)
+ }
+ SE_HW_PARAM1 => {
+ (registers::UART_FIFO_WIDTH_BITS << registers::RX_FIFO_WIDTH_SHFT)
+ | (registers::UART_FIFO_DEPTH_WORDS << registers::RX_FIFO_DEPTH_SHFT)
+ }
+ SE_DMA_TX_PTR_L => self.dma_tx_ptr_l,
+ SE_DMA_TX_PTR_H => self.dma_tx_ptr_h,
+ SE_DMA_TX_ATTR => self.dma_tx_attr,
+ SE_DMA_TX_LEN => self.dma_tx_len,
+ SE_DMA_TX_IRQ_EN => self.dma_tx_irq_en,
+ SE_DMA_TX_IRQ_STAT => self.dma_tx_irq_stat,
+ SE_DMA_RX_PTR_L => self.dma_rx_ptr_l,
+ SE_DMA_RX_PTR_H => self.dma_rx_ptr_h,
+ SE_DMA_RX_ATTR => self.dma_rx_attr,
+ SE_DMA_RX_LEN => self.dma_rx_len,
+ SE_DMA_RX_LEN_IN => self.dma_rx_len_in,
+ SE_DMA_RX_IRQ_EN => self.dma_rx_irq_en,
+ SE_DMA_RX_IRQ_STAT => self.dma_rx_irq_stat,
+ _ => {
+ log_mask_ln!(
+ Log::Unimp,
+ "QUP GENI UART: Unimplemented read from offset {:x}",
+ offset as u32
+ );
+ 0
+ }
+ };
+ (update_irq, result)
+ }
+
+ pub fn write(
+ &mut self,
+ offset: RegisterOffset,
+ value: u32,
+ char_frontend: &CharFrontend,
+ ) -> bool {
+ use RegisterOffset::*;
+
+ match offset {
+ SE_GENI_M_IRQ_CLEAR => {
+ self.geni_m_irq_status &= !value;
+ return true;
+ }
+ SE_GENI_M_IRQ_EN => {
+ self.geni_m_irq_en = value;
+ return true;
+ }
+ SE_GENI_TX_WATERMARK_REG => {
+ self.geni_tx_watermark = value;
+ self.tx_watermark_latch();
+ return true;
+ }
+ SE_GENI_RX_WATERMARK_REG => {
+ self.geni_rx_watermark = value;
+ if value > 0 && self.rx_fifo_len >= value {
+ self.geni_s_irq_status |= registers::S_RX_FIFO_WATERMARK;
+ } else {
+ self.geni_s_irq_status &= !registers::S_RX_FIFO_WATERMARK;
+ }
+ return true;
+ }
+ SE_GENI_RX_RFR_WATERMARK_REG => {
+ self.geni_rx_rfr_watermark = value;
+ }
+ SE_UART_TX_WORD_LEN => {
+ self.uart_tx_word_len = value;
+ }
+ SE_UART_RX_WORD_LEN => {
+ self.uart_rx_word_len = value;
+ }
+ SE_UART_TX_STOP_BIT_LEN => {
+ self.uart_tx_stop_bit_len = value;
+ }
+ SE_UART_RX_STALE_CNT => {
+ self.uart_rx_stale_cnt = value;
+ }
+ SE_UART_TX_TRANS_CFG => {
+ self.uart_tx_trans_cfg = value;
+ }
+ SE_UART_RX_TRANS_CFG => {
+ self.uart_rx_trans_cfg = value;
+ }
+ SE_UART_LOOPBACK_CFG => {
+ self.uart_loopback_cfg = value;
+ }
+ SE_UART_IO_MACRO_CTRL => {
+ self.uart_io_macro_ctrl = value;
+ }
+ SE_UART_MANUAL_RFR => {
+ self.uart_manual_rfr = value;
+ }
+ SE_GENI_TX_PACKING_CFG0 => {
+ self.geni_tx_packing_cfg0 = value;
+ }
+ SE_GENI_TX_PACKING_CFG1 => {
+ self.geni_tx_packing_cfg1 = value;
+ }
+ SE_GENI_RX_PACKING_CFG0 => {
+ self.geni_rx_packing_cfg0 = value;
+ }
+ SE_GENI_RX_PACKING_CFG1 => {
+ self.geni_rx_packing_cfg1 = value;
+ }
+ SE_UART_TX_TRANS_LEN => {
+ self.uart_tx_trans_len = value;
+ }
+ GENI_SER_M_CLK_CFG | GENI_SER_S_CLK_CFG | SE_GENI_CLK_SEL | GENI_OUTPUT_CTRL
+ | GENI_FORCE_DEFAULT | SE_GSI_EVENT_EN | SE_IRQ_EN => {}
+ SE_GENI_M_IRQ_EN_SET => {
+ self.geni_m_irq_en |= value;
+ return true;
+ }
+ SE_GENI_M_IRQ_EN_CLEAR => {
+ self.geni_m_irq_en &= !value;
+ return true;
+ }
+ SE_GENI_S_IRQ_STATUS => {}
+ SE_GENI_S_IRQ_EN => {
+ self.geni_s_irq_en = value;
+ return true;
+ }
+ SE_GENI_S_IRQ_CLEAR => {
+ self.geni_s_irq_status &= !value;
+ return true;
+ }
+ SE_GENI_S_IRQ_EN_SET => {
+ self.geni_s_irq_en |= value;
+ return true;
+ }
+ SE_GENI_S_IRQ_EN_CLEAR => {
+ self.geni_s_irq_en &= !value;
+ return true;
+ }
+ SE_GENI_M_GP_LENGTH => {
+ self.geni_m_gp_length = value;
+ }
+ SE_GENI_S_GP_LENGTH => {
+ self.geni_s_gp_length = value;
+ }
+ SE_UART_TX_PARITY_CFG => {
+ self.uart_tx_parity_cfg = value;
+ }
+ SE_UART_RX_PARITY_CFG => {
+ self.uart_rx_parity_cfg = value;
+ }
+ SE_GENI_CFG_SEQ_START => {
+ if value & 1 != 0 {
+ log_mask_ln!(
+ Log::Unimp,
+ "QUP GENI UART: Configuration sequence start attempted - protocol \
+ switching not supported"
+ );
+ }
+ }
+ SE_GENI_DMA_MODE_EN => {
+ self.dma_mode_enabled = (value & 1) != 0;
+ }
+ SE_GENI_M_CMD0 => {
+ self.geni_m_cmd0 = value;
+ let opcode = (value & registers::M_OPCODE_MSK) >> registers::M_OPCODE_SHFT;
+
+ match opcode {
+ registers::UART_START_TX => {
+ self.geni_status |= registers::M_GENI_CMD_ACTIVE;
+ self.tx_enabled = true;
+ self.tx_watermark_latch();
+ self.tx_remaining = self.uart_tx_trans_len;
+ if self.tx_remaining == 0 {
+ self.geni_status &= !registers::M_GENI_CMD_ACTIVE;
+ self.tx_enabled = false;
+ self.geni_m_irq_status |= registers::M_CMD_DONE;
+ } else if self.dma_tx_armed {
+ self.dma_tx_active = true;
+ }
+ return true;
+ }
+ registers::UART_ABORT => {
+ self.m_cmd_stop(registers::M_CMD_DONE);
+ return true;
+ }
+ _ => {
+ log_mask_ln!(
+ Log::Unimp,
+ "QUP GENI UART: Unsupported command opcode 0x{:x} - device \
+ only supports UART mode",
+ opcode
+ );
+ }
+ }
+ }
+ SE_GENI_M_CMD_CTRL_REG => {
+ if value & registers::M_GENI_CMD_ABORT != 0 {
+ self.m_cmd_stop(registers::M_CMD_ABORT);
+ return true;
+ }
+ if value & registers::M_GENI_CMD_CANCEL != 0 {
+ self.m_cmd_stop(registers::M_CMD_CANCEL);
+ return true;
+ }
+ }
+ SE_GENI_S_CMD_CTRL_REG => {
+ if value & registers::S_GENI_CMD_ABORT != 0 {
+ self.s_cmd_stop(registers::S_CMD_ABORT);
+ return true;
+ }
+ if value & registers::S_GENI_CMD_CANCEL != 0 {
+ self.s_cmd_stop(registers::S_CMD_CANCEL);
+ if self.dma_mode_enabled {
+ self.dma_rx_irq_stat |= registers::DMA_EOT;
+ }
+ return true;
+ }
+ }
+ SE_GENI_S_CMD0 => {
+ self.geni_s_cmd0 = value;
+ let opcode = (value & registers::M_OPCODE_MSK) >> registers::M_OPCODE_SHFT;
+
+ match opcode {
+ registers::UART_START_READ => {
+ self.geni_status |= registers::S_GENI_CMD_ACTIVE;
+ self.rx_enabled = true;
+ }
+ registers::UART_ABORT_READ => {
+ self.s_cmd_stop(registers::S_CMD_DONE);
+ return true;
+ }
+ _ => {
+ log_mask_ln!(
+ Log::Unimp,
+ "QUP GENI UART: Unsupported secondary command opcode 0x{:x} - \
+ device only supports UART mode",
+ opcode
+ );
+ }
+ }
+ }
+ SE_GENI_TX_FIFO_N => {
+ if self.tx_enabled {
+ let tx_data = value.to_le_bytes();
+ let len = self.tx_remaining.min(registers::BYTES_PER_FIFO_WORD) as usize;
+ if self.uart_loopback_cfg & 0x1 != 0 {
+ let mut pushed = false;
+ for &byte in &tx_data[..len] {
+ if !self.rx_enabled {
+ break;
+ }
+ if !self.rx_push_byte(byte) {
+ self.geni_s_irq_status |= registers::S_RX_FIFO_WR_ERR;
+ break;
+ }
+ pushed = true;
+ }
+ if pushed {
+ self.rx_data_pushed();
+ }
+ } else {
+ // FIFO writes cannot wait for a blocked backend while
+ // holding the BQL, so drop any unwritten bytes.
+ match char_frontend.write(&tx_data[..len]) {
+ Ok(n) if n == len => {}
+ _ => log_mask_ln!(
+ Log::GuestError,
+ "QUP GENI UART: backend busy, TX FIFO data dropped"
+ ),
+ }
+ }
+ self.tx_remaining -= len as u32;
+ self.tx_watermark_latch();
+ if self.tx_remaining == 0 {
+ self.geni_status &= !registers::M_GENI_CMD_ACTIVE;
+ self.tx_enabled = false;
+ self.geni_m_irq_status |= registers::M_CMD_DONE;
+ }
+ return true;
+ } else {
+ log_mask_ln!(
+ Log::GuestError,
+ "QUP GENI UART: TX FIFO write with no TX command active"
+ );
+ }
+ }
+ SE_DMA_TX_PTR_L => {
+ self.dma_tx_ptr_l = value;
+ }
+ SE_DMA_TX_PTR_H => {
+ self.dma_tx_ptr_h = value;
+ }
+ SE_DMA_TX_ATTR => {
+ self.dma_tx_attr = value;
+ }
+ SE_DMA_TX_LEN => {
+ self.dma_tx_len = value;
+ self.dma_tx_sent = 0;
+ self.dma_tx_armed = self.dma_mode_enabled && value > 0;
+ self.dma_tx_active = self.dma_tx_armed && self.tx_enabled;
+ }
+ SE_DMA_TX_IRQ_EN => {
+ self.dma_tx_irq_en = value;
+ return true;
+ }
+ SE_DMA_TX_IRQ_EN_SET => {
+ self.dma_tx_irq_en |= value;
+ return true;
+ }
+ SE_DMA_TX_IRQ_EN_CLR => {
+ self.dma_tx_irq_en &= !value;
+ return true;
+ }
+ SE_DMA_TX_IRQ_CLR => {
+ self.dma_tx_irq_stat &= !value;
+ return true;
+ }
+ SE_DMA_TX_FSM_RST => {
+ if value & 1 != 0 {
+ self.dma_tx_stop();
+ self.dma_tx_irq_stat |= registers::DMA_RESET_DONE;
+ return true;
+ }
+ }
+ SE_DMA_RX_PTR_L => {
+ self.dma_rx_ptr_l = value;
+ }
+ SE_DMA_RX_PTR_H => {
+ self.dma_rx_ptr_h = value;
+ }
+ SE_DMA_RX_ATTR => {
+ self.dma_rx_attr = value;
+ }
+ SE_DMA_RX_LEN => {
+ self.dma_rx_len = value;
+ self.dma_rx_len_in = 0;
+ if self.dma_mode_enabled && value > 0 {
+ self.dma_rx_active = true;
+ }
+ }
+ SE_DMA_RX_FSM_RST => {
+ if value & 1 != 0 {
+ self.dma_rx_active = false;
+ self.dma_rx_irq_stat |= registers::DMA_RESET_DONE;
+ return true;
+ }
+ }
+ SE_DMA_RX_IRQ_EN => {
+ self.dma_rx_irq_en = value;
+ return true;
+ }
+ SE_DMA_RX_IRQ_EN_SET => {
+ self.dma_rx_irq_en |= value;
+ return true;
+ }
+ SE_DMA_RX_IRQ_EN_CLR => {
+ self.dma_rx_irq_en &= !value;
+ return true;
+ }
+ SE_DMA_RX_IRQ_CLR => {
+ self.dma_rx_irq_stat &= !value;
+ return true;
+ }
+ _ => {
+ log_mask_ln!(
+ Log::Unimp,
+ "QUP GENI UART: Unimplemented write to offset {:x} value {:x}",
+ offset as u32,
+ value
+ );
+ }
+ }
+ false
+ }
+
+ // Guest memory may map this device, so the caller performs the access
+ // after releasing the register borrow.
+ fn dma_tx_plan(&mut self) -> Option<(u64, usize)> {
+ let len = self.dma_tx_len.min(self.uart_tx_trans_len);
+
+ if !self.dma_tx_active {
+ return None;
+ }
+ if self.dma_tx_sent >= len {
+ self.dma_tx_stop();
+ self.dma_tx_irq_stat |= registers::DMA_DONE;
+ if self.tx_remaining == 0 {
+ self.tx_enabled = false;
+ self.geni_status &= !registers::M_GENI_CMD_ACTIVE;
+ self.geni_m_irq_status |= registers::M_CMD_DONE;
+ }
+ return None;
+ }
+
+ let mut count = (len - self.dma_tx_sent).min(DMA_CHUNK_SIZE as u32) as usize;
+ if self.uart_loopback_cfg & 1 != 0 && self.rx_enabled && self.dma_rx_active {
+ count = count.min(self.dma_rx_len as usize);
+ }
+ let base = (u64::from(self.dma_tx_ptr_h) << 32) | u64::from(self.dma_tx_ptr_l);
+ match base.checked_add(u64::from(self.dma_tx_sent)) {
+ Some(addr) if count > 0 => Some((addr, count)),
+ Some(_) => None,
+ None => {
+ self.dma_tx_irq_stat |= registers::DMA_AHB_ERR;
+ self.dma_tx_stop();
+ None
+ }
+ }
+ }
+
+ fn dma_tx_stop(&mut self) {
+ self.dma_tx_armed = false;
+ self.dma_tx_active = false;
+ }
+
+ fn dma_rx_addr(&self) -> u64 {
+ (u64::from(self.dma_rx_ptr_h) << 32) | u64::from(self.dma_rx_ptr_l)
+ }
+
+ fn dma_rx_finish(&mut self, len: usize, ok: bool) {
+ self.dma_rx_active = false;
+ if ok {
+ self.dma_rx_len_in = len as u32;
+ self.dma_rx_irq_stat |= registers::DMA_DONE;
+ } else {
+ self.dma_rx_irq_stat |= registers::DMA_AHB_ERR;
+ }
+ }
+
+ fn m_cmd_stop(&mut self, irq: u32) {
+ self.geni_status &= !registers::M_GENI_CMD_ACTIVE;
+ self.tx_enabled = false;
+ self.dma_tx_stop();
+ self.tx_remaining = 0;
+ self.geni_m_irq_status |= irq;
+ }
+
+ fn s_cmd_stop(&mut self, irq: u32) {
+ self.geni_status &= !registers::S_GENI_CMD_ACTIVE;
+ self.rx_enabled = false;
+ self.dma_rx_active = false;
+ self.rx_fifo_len = 0;
+ self.rx_last_byte_cnt = 0;
+ self.rx_tail_closed = false;
+ self.geni_s_irq_status &= !(registers::S_RX_FIFO_WATERMARK | registers::S_RX_FIFO_LAST);
+ self.geni_s_irq_status |= irq;
+ }
+
+ // RX_LAST_BYTE_VALID can describe only the last FIFO word, so fill a
+ // partial word before appending another one.
+ fn rx_push_byte(&mut self, byte: u8) -> bool {
+ if self.rx_last_byte_cnt > 0 && self.rx_fifo_len > 0 {
+ if self.rx_tail_closed {
+ return false;
+ }
+ let idx = self.rx_fifo_len - 1;
+ self.rx_fifo[idx] |= u32::from(byte) << (self.rx_last_byte_cnt * 8);
+ self.rx_last_byte_cnt = (self.rx_last_byte_cnt + 1) % registers::BYTES_PER_FIFO_WORD;
+ return true;
+ }
+ if self.rx_fifo_len >= QUP_FIFO_DEPTH {
+ return false;
+ }
+ let idx = self.rx_fifo_len;
+ self.rx_fifo[idx] = u32::from(byte);
+ self.rx_fifo_len += 1;
+ self.rx_last_byte_cnt = 1;
+ true
+ }
+
+ fn rx_data_pushed(&mut self) {
+ if self.geni_rx_watermark > 0 && self.rx_fifo_len >= self.geni_rx_watermark {
+ self.geni_s_irq_status |= registers::S_RX_FIFO_WATERMARK;
+ }
+ if self.rx_last_byte_cnt > 0 {
+ self.geni_s_irq_status |= registers::S_RX_FIFO_LAST;
+ }
+ }
+
+ pub fn reset(&mut self) {
+ self.geni_status = 0;
+ self.geni_m_cmd0 = 0;
+ self.geni_m_irq_status = 0;
+ self.geni_m_irq_en = 0;
+ self.geni_s_cmd0 = 0;
+ self.geni_s_irq_status = 0;
+ self.geni_s_irq_en = 0;
+ self.geni_tx_watermark = registers::DEF_TX_WM;
+ self.geni_rx_watermark = registers::UART_RX_WM;
+ self.geni_rx_rfr_watermark = registers::UART_RX_WM;
+ self.tx_watermark_latch();
+ self.geni_m_gp_length = 0;
+ self.geni_s_gp_length = 0;
+ self.geni_tx_packing_cfg0 = 0;
+ self.geni_tx_packing_cfg1 = 0;
+ self.geni_rx_packing_cfg0 = 0;
+ self.geni_rx_packing_cfg1 = 0;
+
+ self.uart_tx_trans_cfg = 0;
+ self.uart_rx_trans_cfg = 0;
+ self.uart_tx_word_len = 0;
+ self.uart_rx_word_len = 0;
+ self.uart_tx_stop_bit_len = 0;
+ self.uart_rx_stale_cnt = registers::STALE_TIMEOUT;
+ self.uart_tx_parity_cfg = 0;
+ self.uart_rx_parity_cfg = 0;
+ self.uart_loopback_cfg = 0;
+ self.uart_io_macro_ctrl = 0;
+ self.uart_manual_rfr = 0;
+ self.uart_tx_trans_len = 0;
+
+ self.rx_fifo_len = 0;
+ self.rx_last_byte_cnt = 0;
+ self.rx_tail_closed = false;
+ self.tx_remaining = 0;
+ self.tx_enabled = false;
+ self.rx_enabled = false;
+ self.dma_mode_enabled = false;
+
+ self.dma_tx_ptr_l = 0;
+ self.dma_tx_ptr_h = 0;
+ self.dma_tx_attr = 0;
+ self.dma_tx_len = 0;
+ self.dma_tx_sent = 0;
+ self.dma_tx_irq_en = 0;
+ self.dma_tx_irq_stat = 0;
+ self.dma_rx_ptr_l = 0;
+ self.dma_rx_ptr_h = 0;
+ self.dma_rx_attr = 0;
+ self.dma_rx_len = 0;
+ self.dma_rx_len_in = 0;
+ self.dma_rx_irq_en = 0;
+ self.dma_rx_irq_stat = 0;
+ self.dma_tx_armed = false;
+ self.dma_tx_active = false;
+ self.dma_rx_active = false;
+ }
+}
+
+impl QupGeniUartState {
+ /// # Safety
+ ///
+ /// `this` must refer to a new, correctly sized and aligned instance whose
+ /// fields other than `parent_obj` are uninitialized.
+ unsafe fn init(mut this: ParentInit<Self>) {
+ static QUP_GENI_UART_OPS: MemoryRegionOps<QupGeniUartState> =
+ MemoryRegionOpsBuilder::<QupGeniUartState>::new()
+ .read(&QupGeniUartState::read)
+ .write(&QupGeniUartState::write)
+ .little_endian()
+ .impl_sizes(4, 4)
+ .build();
+
+ // SAFETY: this and this.iomem are guaranteed to be valid at this point
+ MemoryRegion::init_io(
+ &mut uninit_field_mut!(*this, iomem),
+ &QUP_GENI_UART_OPS,
+ "qup-geni-uart",
+ QUP_GENI_UART_MMIO_SIZE,
+ );
+
+ uninit_field_mut!(*this, regs).write(Default::default());
+ uninit_field_mut!(*this, tx_watch).write(Default::default());
+ uninit_field_mut!(*this, dma_mr).write(BqlCell::new(DmaRegion(ptr::null_mut())));
+ // SAFETY: address_space_init() fills it in when the device is realized
+ uninit_field_mut!(*this, dma_as).write(unsafe { DmaAddressSpace(Opaque::zeroed()) });
+ uninit_field_mut!(*this, dma_as_ready).write(Default::default());
+ uninit_field_mut!(*this, dma_tx_busy).write(Default::default());
+ }
+
+ fn post_init(&self) {
+ self.init_mmio(&self.iomem);
+ self.init_irq(&self.irq);
+
+ // SAFETY: dma_mr lives as long as the object, and DmaRegion is a
+ // transparent wrapper of the pointer that the link property stores.
+ // The property keeps a reference to what it is set to.
+ unsafe {
+ object_property_add_link(
+ self.as_object_mut_ptr(),
+ c"memory".as_ptr(),
+ c"memory-region".as_ptr(),
+ self.dma_mr.as_ptr().cast(),
+ Some(qdev_prop_allow_set_link_before_realize),
+ OBJ_PROP_LINK_STRONG,
+ );
+ }
+ }
+
+ fn read(&self, offset: hwaddr, _size: u32) -> u64 {
+ match RegisterOffset::try_from(offset) {
+ Ok(reg) => {
+ let (update_irq, result) = self.regs.borrow_mut().read(reg);
+ if update_irq {
+ self.update_irq();
+ self.char_frontend.accept_input();
+ }
+ result.into()
+ }
+ Err(_) => {
+ log_mask_ln!(
+ Log::GuestError,
+ "QupGeniUartState::read: Bad offset {:x}",
+ offset
+ );
+ 0
+ }
+ }
+ }
+
+ fn write(&self, offset: hwaddr, value: u64, _size: u32) {
+ if let Ok(reg) = RegisterOffset::try_from(offset) {
+ let update_irq = self
+ .regs
+ .borrow_mut()
+ .write(reg, value as u32, &self.char_frontend);
+ if update_irq {
+ self.update_irq();
+ }
+ if matches!(
+ reg,
+ RegisterOffset::SE_GENI_M_CMD0
+ | RegisterOffset::SE_DMA_TX_LEN
+ | RegisterOffset::SE_DMA_RX_LEN
+ ) {
+ self.run_dma_tx();
+ self.update_irq();
+ }
+ self.update_tx_watch();
+ if matches!(
+ reg,
+ RegisterOffset::SE_GENI_S_CMD0
+ | RegisterOffset::SE_GENI_S_CMD_CTRL_REG
+ | RegisterOffset::SE_DMA_RX_LEN
+ ) {
+ self.char_frontend.accept_input();
+ }
+ } else {
+ log_mask_ln!(
+ Log::GuestError,
+ "QupGeniUartState::write: Bad offset {:x} value {:x}",
+ offset,
+ value
+ );
+ }
+ }
+
+ fn dma_read(&self, addr: u64, buf: &mut [u8]) -> bool {
+ // SAFETY: buf is writable for its length and dma_as was initialized
+ // by realize, before any register could be accessed.
+ let result = unsafe {
+ address_space_rw(
+ self.dma_as.0.as_ptr(),
+ addr,
+ MEMTXATTRS_UNSPECIFIED,
+ buf.as_mut_ptr().cast(),
+ buf.len() as u64,
+ false,
+ )
+ };
+ result == 0
+ }
+
+ fn dma_write(&self, addr: u64, data: &[u8]) -> bool {
+ // SAFETY: data is readable for its length and dma_as was initialized
+ // by realize, before any register could be accessed.
+ let result = unsafe {
+ address_space_rw(
+ self.dma_as.0.as_ptr(),
+ addr,
+ MEMTXATTRS_UNSPECIFIED,
+ data.as_ptr().cast_mut().cast(),
+ data.len() as u64,
+ true,
+ )
+ };
+ result == 0
+ }
+
+ // Complete RX DMA on each delivery, as if the stale timeout expired.
+ // Release the register borrow in case the DMA target is this device.
+ fn dma_rx_deliver(&self, data: &[u8]) {
+ let (addr, data) = {
+ let mut regs = self.regs.borrow_mut();
+ regs.dma_rx_active = false;
+ let len = data.len().min(regs.dma_rx_len as usize);
+ (regs.dma_rx_addr(), &data[..len])
+ };
+ let ok = self.dma_write(addr, data);
+ self.regs.borrow_mut().dma_rx_finish(data.len(), ok);
+ }
+
+ fn run_dma_tx(&self) {
+ if self.dma_tx_busy.replace(true) {
+ return;
+ }
+ self.run_dma_tx_chunks();
+ self.dma_tx_busy.set(false);
+ }
+
+ fn run_dma_tx_chunks(&self) {
+ let mut buf = [0u8; DMA_CHUNK_SIZE];
+
+ loop {
+ let plan = self.regs.borrow_mut().dma_tx_plan();
+ let Some((addr, count)) = plan else {
+ break;
+ };
+ let ok = self.dma_read(addr, &mut buf[..count]);
+
+ let mut regs = self.regs.borrow_mut();
+ if !regs.dma_tx_active {
+ break;
+ }
+ if !ok {
+ regs.dma_tx_irq_stat |= registers::DMA_AHB_ERR;
+ regs.dma_tx_stop();
+ break;
+ }
+ let sent = if regs.uart_loopback_cfg & 1 != 0 {
+ let deliver = regs.rx_enabled && regs.dma_rx_active;
+ drop(regs);
+ if deliver {
+ self.dma_rx_deliver(&buf[..count]);
+ }
+ regs = self.regs.borrow_mut();
+ count
+ } else {
+ match self.char_frontend.write(&buf[..count]) {
+ // There is no source to wake us when no backend is present.
+ Ok(0) => count,
+ Ok(sent) => sent,
+ Err(err) if err.kind() == ErrorKind::WouldBlock => break,
+ Err(_) => count,
+ }
+ };
+ regs.dma_tx_sent = regs.dma_tx_sent.saturating_add(sent as u32);
+ regs.tx_remaining = regs.tx_remaining.saturating_sub(sent as u32);
+ }
+ }
+
+ fn can_receive(&self) -> u32 {
+ let regs = self.regs.borrow();
+ if regs.uart_loopback_cfg & 1 != 0 {
+ 0
+ } else if regs.rx_enabled && regs.dma_mode_enabled {
+ if regs.dma_rx_active {
+ regs.dma_rx_len.min(DMA_CHUNK_SIZE as u32)
+ } else {
+ 0
+ }
+ } else if regs.rx_enabled {
+ if regs.rx_tail_closed {
+ 0
+ } else {
+ let words = QUP_FIFO_DEPTH - regs.rx_fifo_len;
+ let tail = if regs.rx_last_byte_cnt > 0 {
+ registers::BYTES_PER_FIFO_WORD - regs.rx_last_byte_cnt
+ } else {
+ 0
+ };
+ words * registers::BYTES_PER_FIFO_WORD + tail
+ }
+ } else {
+ 0
+ }
+ }
+
+ fn receive(&self, buf: &[u8]) {
+ let mut regs = self.regs.borrow_mut();
+ if !regs.rx_enabled || (regs.uart_loopback_cfg & 0x1 != 0) {
+ return;
+ }
+
+ if regs.dma_mode_enabled {
+ let dma_rx_active = regs.dma_rx_active;
+ drop(regs);
+ if dma_rx_active {
+ self.dma_rx_deliver(buf);
+ }
+ self.update_irq();
+ return;
+ }
+
+ let mut pushed = false;
+ let mut overflow = false;
+ for &byte in buf {
+ if !regs.rx_push_byte(byte) {
+ overflow = true;
+ break;
+ }
+ pushed = true;
+ }
+ if overflow {
+ regs.geni_s_irq_status |= registers::S_RX_FIFO_WR_ERR;
+ }
+ if pushed {
+ regs.rx_data_pushed();
+ }
+
+ drop(regs);
+ if pushed || overflow {
+ self.update_irq();
+ }
+ }
+
+ fn event(&self, _event: Event) {}
+
+ fn realize(&self) -> util::Result<()> {
+ let mut dma_mr = self.dma_mr.get().0;
+ if dma_mr.is_null() {
+ // SAFETY: the system memory exists for the life of the machine.
+ // The link property is strong and unrefs its target when the
+ // device is finalized, so it needs a reference of its own here.
+ dma_mr = unsafe {
+ let mr = get_system_memory();
+ object_ref(mr.cast());
+ mr
+ };
+ self.dma_mr.set(DmaRegion(dma_mr));
+ }
+ // SAFETY: dma_as is not initialized yet and dma_mr is a valid region.
+ // The link property drops its reference before Drop runs, so keep
+ // one that lasts as long as the address space.
+ unsafe {
+ object_ref(dma_mr.cast());
+ address_space_init(
+ self.dma_as.0.as_mut_ptr(),
+ dma_mr,
+ c"qup-geni-uart-dma".as_ptr(),
+ );
+ }
+ self.dma_as_ready.set(true);
+
+ self.char_frontend
+ .enable_handlers(self, Self::can_receive, Self::receive, Self::event);
+ Ok(())
+ }
+
+ fn reset_hold(&self, _type: ResetType) {
+ self.clear_tx_watch();
+ self.dma_tx_busy.set(false);
+ self.regs.borrow_mut().reset();
+ self.update_irq();
+ self.char_frontend.accept_input();
+ }
+
+ fn clear_tx_watch(&self) {
+ let tag = self.tx_watch.replace(0);
+ self.char_frontend.remove_watch(tag);
+ }
+
+ fn update_tx_watch(&self) {
+ if !self.regs.borrow().dma_tx_active || self.regs.borrow().uart_loopback_cfg & 1 != 0 {
+ self.clear_tx_watch();
+ } else if self.tx_watch.get() == 0 {
+ // SAFETY: the source is removed on reset and before object drop.
+ let tag = unsafe {
+ self.char_frontend.add_write_watch(
+ Some(Self::tx_ready),
+ (self as *const Self).cast_mut().cast(),
+ )
+ };
+ self.tx_watch.set(tag);
+ if tag == 0 {
+ log_mask_ln!(
+ Log::Unimp,
+ "QUP GENI UART: backend cannot watch for writability, \
+ assuming the TX DMA data was accepted"
+ );
+ let mut regs = self.regs.borrow_mut();
+ let pending = regs
+ .dma_tx_len
+ .min(regs.uart_tx_trans_len)
+ .saturating_sub(regs.dma_tx_sent);
+ regs.tx_remaining = regs.tx_remaining.saturating_sub(pending);
+ regs.dma_tx_stop();
+ regs.dma_tx_irq_stat |= registers::DMA_DONE;
+ if regs.tx_remaining == 0 {
+ regs.tx_enabled = false;
+ regs.geni_status &= !registers::M_GENI_CMD_ACTIVE;
+ regs.geni_m_irq_status |= registers::M_CMD_DONE;
+ }
+ drop(regs);
+ self.update_irq();
+ }
+ }
+ }
+
+ unsafe extern "C" fn tx_ready(
+ _unused: *mut c_void,
+ _condition: u32,
+ opaque: *mut c_void,
+ ) -> i32 {
+ // SAFETY: the watch holds a pointer to this live QOM object.
+ let this = unsafe { &*opaque.cast::<Self>() };
+ this.tx_watch.set(0);
+ this.run_dma_tx();
+ this.update_irq();
+ this.update_tx_watch();
+ 0
+ }
+
+ fn post_load(&self, _version: u8) -> Result<(), migration::InvalidError> {
+ {
+ // Reject invalid array indices and shift counts before use.
+ let regs = self.regs.borrow();
+ if regs.rx_fifo_len > QUP_FIFO_DEPTH
+ || regs.rx_last_byte_cnt >= registers::BYTES_PER_FIFO_WORD
+ || (regs.rx_last_byte_cnt > 0 && regs.rx_fifo_len == 0)
+ || (regs.rx_tail_closed && regs.rx_last_byte_cnt == 0)
+ || regs.dma_tx_sent > regs.dma_tx_len
+ || regs.dma_rx_len_in > regs.dma_rx_len
+ {
+ return Err(migration::InvalidError);
+ }
+ }
+ self.update_irq();
+ self.update_tx_watch();
+ Ok(())
+ }
+
+ fn update_irq(&self) {
+ let regs = self.regs.borrow();
+ let irq_active = (regs.geni_m_irq_status & regs.geni_m_irq_en) != 0
+ || (regs.geni_s_irq_status & regs.geni_s_irq_en) != 0
+ || (regs.dma_tx_irq_stat & regs.dma_tx_irq_en) != 0
+ || (regs.dma_rx_irq_stat & regs.dma_rx_irq_en) != 0;
+ self.irq.set(irq_active);
+ }
+}
+
+impl Drop for QupGeniUartState {
+ fn drop(&mut self) {
+ self.clear_tx_watch();
+ if self.dma_as_ready.get() {
+ // SAFETY: the address space was initialized by realize
+ unsafe {
+ address_space_destroy(self.dma_as.0.as_mut_ptr());
+ object_unref(self.dma_mr.get().0.cast());
+ }
+ }
+ }
+}
+
+impl_vmstate_struct!(
+ QupGeniUartRegs,
+ VMStateDescriptionBuilder::<QupGeniUartRegs>::new()
+ .name(c"qup_geni_uart/regs")
+ .version_id(1)
+ .minimum_version_id(1)
+ .fields(vmstate_fields! {
+ vmstate_of!(QupGeniUartRegs, geni_status),
+ vmstate_of!(QupGeniUartRegs, geni_m_cmd0),
+ vmstate_of!(QupGeniUartRegs, geni_m_irq_status),
+ vmstate_of!(QupGeniUartRegs, geni_m_irq_en),
+ vmstate_of!(QupGeniUartRegs, geni_s_cmd0),
+ vmstate_of!(QupGeniUartRegs, geni_s_irq_status),
+ vmstate_of!(QupGeniUartRegs, geni_s_irq_en),
+ vmstate_of!(QupGeniUartRegs, geni_tx_watermark),
+ vmstate_of!(QupGeniUartRegs, geni_rx_watermark),
+ vmstate_of!(QupGeniUartRegs, geni_rx_rfr_watermark),
+ vmstate_of!(QupGeniUartRegs, geni_m_gp_length),
+ vmstate_of!(QupGeniUartRegs, geni_s_gp_length),
+ vmstate_of!(QupGeniUartRegs, geni_tx_packing_cfg0),
+ vmstate_of!(QupGeniUartRegs, geni_tx_packing_cfg1),
+ vmstate_of!(QupGeniUartRegs, geni_rx_packing_cfg0),
+ vmstate_of!(QupGeniUartRegs, geni_rx_packing_cfg1),
+ vmstate_of!(QupGeniUartRegs, uart_tx_trans_cfg),
+ vmstate_of!(QupGeniUartRegs, uart_rx_trans_cfg),
+ vmstate_of!(QupGeniUartRegs, uart_tx_word_len),
+ vmstate_of!(QupGeniUartRegs, uart_rx_word_len),
+ vmstate_of!(QupGeniUartRegs, uart_tx_stop_bit_len),
+ vmstate_of!(QupGeniUartRegs, uart_rx_stale_cnt),
+ vmstate_of!(QupGeniUartRegs, uart_tx_parity_cfg),
+ vmstate_of!(QupGeniUartRegs, uart_rx_parity_cfg),
+ vmstate_of!(QupGeniUartRegs, uart_loopback_cfg),
+ vmstate_of!(QupGeniUartRegs, uart_io_macro_ctrl),
+ vmstate_of!(QupGeniUartRegs, uart_manual_rfr),
+ vmstate_of!(QupGeniUartRegs, uart_tx_trans_len),
+ vmstate_of!(QupGeniUartRegs, rx_fifo),
+ vmstate_of!(QupGeniUartRegs, rx_fifo_len),
+ vmstate_of!(QupGeniUartRegs, rx_last_byte_cnt),
+ vmstate_of!(QupGeniUartRegs, rx_tail_closed),
+ vmstate_of!(QupGeniUartRegs, tx_remaining),
+ vmstate_of!(QupGeniUartRegs, tx_enabled),
+ vmstate_of!(QupGeniUartRegs, rx_enabled),
+ vmstate_of!(QupGeniUartRegs, dma_mode_enabled),
+ vmstate_of!(QupGeniUartRegs, dma_tx_ptr_l),
+ vmstate_of!(QupGeniUartRegs, dma_tx_ptr_h),
+ vmstate_of!(QupGeniUartRegs, dma_tx_attr),
+ vmstate_of!(QupGeniUartRegs, dma_tx_len),
+ vmstate_of!(QupGeniUartRegs, dma_tx_sent),
+ vmstate_of!(QupGeniUartRegs, dma_tx_irq_en),
+ vmstate_of!(QupGeniUartRegs, dma_tx_irq_stat),
+ vmstate_of!(QupGeniUartRegs, dma_rx_ptr_l),
+ vmstate_of!(QupGeniUartRegs, dma_rx_ptr_h),
+ vmstate_of!(QupGeniUartRegs, dma_rx_attr),
+ vmstate_of!(QupGeniUartRegs, dma_rx_len),
+ vmstate_of!(QupGeniUartRegs, dma_rx_len_in),
+ vmstate_of!(QupGeniUartRegs, dma_rx_irq_en),
+ vmstate_of!(QupGeniUartRegs, dma_rx_irq_stat),
+ vmstate_of!(QupGeniUartRegs, dma_tx_armed),
+ vmstate_of!(QupGeniUartRegs, dma_tx_active),
+ vmstate_of!(QupGeniUartRegs, dma_rx_active),
+ })
+ .build()
+);
+
+pub const VMSTATE_QUP_GENI_UART: VMStateDescription<QupGeniUartState> =
+ VMStateDescriptionBuilder::<QupGeniUartState>::new()
+ .name(c"qup_geni_uart")
+ .post_load(&QupGeniUartState::post_load)
+ .version_id(1)
+ .minimum_version_id(1)
+ .fields(vmstate_fields! {
+ vmstate_of!(QupGeniUartState, regs),
+ })
+ .build();
diff --git a/rust/hw/char/qup_geni_uart/src/lib.rs b/rust/hw/char/qup_geni_uart/src/lib.rs
new file mode 100644
index 00000000000..209739867f6
--- /dev/null
+++ b/rust/hw/char/qup_geni_uart/src/lib.rs
@@ -0,0 +1,9 @@
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Qualcomm QUP GENI UART device model
+
+mod device;
+mod registers;
+
+pub const TYPE_QUP_GENI_UART: &::std::ffi::CStr = c"qup-geni-uart";
diff --git a/rust/hw/char/qup_geni_uart/src/registers.rs b/rust/hw/char/qup_geni_uart/src/registers.rs
new file mode 100644
index 00000000000..571b357ea24
--- /dev/null
+++ b/rust/hw/char/qup_geni_uart/src/registers.rs
@@ -0,0 +1,160 @@
+// Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+//! Register offsets and bit fields of the QUP GENI UART
+
+#[allow(non_camel_case_types)]
+#[repr(u64)]
+#[derive(Debug, Clone, Copy, PartialEq, Eq, common::TryInto)]
+pub enum RegisterOffset {
+ // Common SE registers
+ GENI_FORCE_DEFAULT = 0x020,
+ GENI_OUTPUT_CTRL = 0x024,
+ SE_GENI_STATUS = 0x040,
+ GENI_SER_M_CLK_CFG = 0x048,
+ GENI_SER_S_CLK_CFG = 0x04c,
+ GENI_IF_DISABLE_RO = 0x064,
+ GENI_FW_REVISION_RO = 0x068,
+ SE_GENI_CLK_SEL = 0x07c,
+ SE_GENI_CFG_SEQ_START = 0x084,
+ SE_GENI_DMA_MODE_EN = 0x258,
+ SE_GENI_TX_PACKING_CFG0 = 0x260,
+ SE_GENI_TX_PACKING_CFG1 = 0x264,
+ SE_GENI_RX_PACKING_CFG0 = 0x284,
+ SE_GENI_RX_PACKING_CFG1 = 0x288,
+ SE_GENI_M_CMD0 = 0x600,
+ SE_GENI_M_CMD_CTRL_REG = 0x604,
+ SE_GENI_M_IRQ_STATUS = 0x610,
+ SE_GENI_M_IRQ_EN = 0x614,
+ SE_GENI_M_IRQ_CLEAR = 0x618,
+ SE_GENI_M_IRQ_EN_SET = 0x61c,
+ SE_GENI_M_IRQ_EN_CLEAR = 0x620,
+ SE_GENI_S_CMD0 = 0x630,
+ SE_GENI_S_CMD_CTRL_REG = 0x634,
+ SE_GENI_S_IRQ_STATUS = 0x640,
+ SE_GENI_S_IRQ_EN = 0x644,
+ SE_GENI_S_IRQ_CLEAR = 0x648,
+ SE_GENI_S_IRQ_EN_SET = 0x64c,
+ SE_GENI_S_IRQ_EN_CLEAR = 0x650,
+ SE_GENI_TX_FIFO_N = 0x700,
+ SE_GENI_RX_FIFO_N = 0x780,
+ SE_GENI_TX_FIFO_STATUS = 0x800,
+ SE_GENI_RX_FIFO_STATUS = 0x804,
+ SE_GENI_TX_WATERMARK_REG = 0x80c,
+ SE_GENI_RX_WATERMARK_REG = 0x810,
+ SE_GENI_RX_RFR_WATERMARK_REG = 0x814,
+ SE_GENI_IOS = 0x908,
+ SE_GENI_M_GP_LENGTH = 0x910,
+ SE_GENI_S_GP_LENGTH = 0x914,
+ SE_GSI_EVENT_EN = 0xe18,
+ SE_IRQ_EN = 0xe1c,
+ SE_HW_PARAM0 = 0xe24,
+ SE_HW_PARAM1 = 0xe28,
+
+ // DMA registers
+ SE_DMA_TX_PTR_L = 0xc30,
+ SE_DMA_TX_PTR_H = 0xc34,
+ SE_DMA_TX_ATTR = 0xc38,
+ SE_DMA_TX_LEN = 0xc3c,
+ SE_DMA_TX_IRQ_EN = 0xc48,
+ SE_DMA_TX_IRQ_EN_SET = 0xc4c,
+ SE_DMA_TX_IRQ_EN_CLR = 0xc50,
+ SE_DMA_TX_IRQ_STAT = 0xc40,
+ SE_DMA_TX_IRQ_CLR = 0xc44,
+ SE_DMA_TX_FSM_RST = 0xc58,
+ SE_DMA_RX_PTR_L = 0xd30,
+ SE_DMA_RX_PTR_H = 0xd34,
+ SE_DMA_RX_ATTR = 0xd38,
+ SE_DMA_RX_LEN = 0xd3c,
+ SE_DMA_RX_IRQ_EN = 0xd48,
+ SE_DMA_RX_IRQ_EN_SET = 0xd4c,
+ SE_DMA_RX_IRQ_EN_CLR = 0xd50,
+ SE_DMA_RX_IRQ_STAT = 0xd40,
+ SE_DMA_RX_IRQ_CLR = 0xd44,
+ SE_DMA_RX_LEN_IN = 0xd54,
+ SE_DMA_RX_FSM_RST = 0xd58,
+
+ // UART specific registers
+ SE_UART_LOOPBACK_CFG = 0x22c,
+ SE_UART_IO_MACRO_CTRL = 0x240,
+ SE_UART_TX_TRANS_CFG = 0x25c,
+ SE_UART_TX_WORD_LEN = 0x268,
+ SE_UART_TX_STOP_BIT_LEN = 0x26c,
+ SE_UART_TX_TRANS_LEN = 0x270,
+ SE_UART_RX_TRANS_CFG = 0x280,
+ SE_UART_RX_WORD_LEN = 0x28c,
+ SE_UART_RX_STALE_CNT = 0x294,
+ SE_UART_TX_PARITY_CFG = 0x2a4,
+ SE_UART_RX_PARITY_CFG = 0x2a8,
+ SE_UART_MANUAL_RFR = 0x2ac,
+}
+
+// Status register fields
+pub const M_GENI_CMD_ACTIVE: u32 = 1 << 0;
+pub const S_GENI_CMD_ACTIVE: u32 = 1 << 12;
+
+// GENI_M_CMD_CTRL_REG fields
+pub const M_GENI_CMD_ABORT: u32 = 1 << 1;
+pub const M_GENI_CMD_CANCEL: u32 = 1 << 2;
+
+// GENI_S_CMD_CTRL_REG fields
+pub const S_GENI_CMD_ABORT: u32 = 1 << 1;
+pub const S_GENI_CMD_CANCEL: u32 = 1 << 2;
+
+// FW Revision fields
+pub const FW_REV_PROTOCOL_SHFT: u32 = 8;
+
+// Protocol constants
+pub const GENI_SE_UART: u32 = 2;
+
+// M_CMD0 fields
+pub const M_OPCODE_MSK: u32 = 0xf800_0000;
+pub const M_OPCODE_SHFT: u32 = 27;
+
+// UART M_CMD OP codes
+pub const UART_START_TX: u32 = 0x1;
+pub const UART_ABORT: u32 = 0x2;
+
+// UART S_CMD OP codes
+pub const UART_START_READ: u32 = 0x1;
+pub const UART_ABORT_READ: u32 = 0x2;
+
+// Main sequencer interrupt bits (same positions in IRQ_STATUS, IRQ_EN and
+// IRQ_CLEAR)
+pub const M_CMD_DONE: u32 = 1 << 0;
+pub const M_CMD_CANCEL: u32 = 1 << 4;
+pub const M_CMD_ABORT: u32 = 1 << 5;
+pub const M_TX_FIFO_WATERMARK: u32 = 1 << 30;
+
+// Secondary sequencer interrupt bits
+pub const S_CMD_DONE: u32 = 1 << 0;
+pub const S_CMD_CANCEL: u32 = 1 << 4;
+pub const S_CMD_ABORT: u32 = 1 << 5;
+pub const S_RX_FIFO_WR_ERR: u32 = 1 << 25;
+pub const S_RX_FIFO_WATERMARK: u32 = 1 << 26;
+pub const S_RX_FIFO_LAST: u32 = 1 << 27;
+
+// RX FIFO status fields
+pub const RX_LAST: u32 = 1 << 31;
+pub const RX_LAST_BYTE_VALID_MSK: u32 = 0x7000_0000;
+pub const RX_LAST_BYTE_VALID_SHFT: u32 = 28;
+pub const RX_FIFO_WC_MSK: u32 = 0x01ff_ffff;
+
+// Hardware parameter register fields
+pub const TX_FIFO_WIDTH_SHFT: u32 = 24;
+pub const TX_FIFO_DEPTH_SHFT: u32 = 16;
+pub const RX_FIFO_WIDTH_SHFT: u32 = 24;
+pub const RX_FIFO_DEPTH_SHFT: u32 = 16;
+
+pub const BYTES_PER_FIFO_WORD: u32 = 4;
+pub const UART_FIFO_DEPTH_WORDS: u32 = 64;
+pub const UART_FIFO_WIDTH_BITS: u32 = 32;
+pub const STALE_TIMEOUT: u32 = 160;
+pub const DEF_TX_WM: u32 = 2;
+pub const UART_RX_WM: u32 = 2;
+
+// DMA interrupt status fields
+pub const DMA_DONE: u32 = 1 << 0;
+pub const DMA_EOT: u32 = 1 << 1;
+pub const DMA_AHB_ERR: u32 = 1 << 2;
+pub const DMA_RESET_DONE: u32 = 1 << 3;
--
2.34.1
^ permalink raw reply related [flat|nested] 11+ messages in thread
* Re: [PATCH 0/3] hw/misc: QUP UART device
2026-10-27 18:38 [PATCH 0/3] hw/misc: QUP UART device Brian Cain
` (2 preceding siblings ...)
2026-10-27 18:38 ` [PATCH 3/3] rust/hw/char: add QUP GENI UART device model Brian Cain
@ 2026-10-27 18:43 ` Pierrick Bouvier
2026-10-27 20:02 ` Brian Cain
3 siblings, 1 reply; 11+ messages in thread
From: Pierrick Bouvier @ 2026-10-27 18:43 UTC (permalink / raw)
To: Brian Cain, qemu-devel; +Cc: Manos Pitsidianakis, qemu-rust, Paolo Bonzini
On 10/27/2026 10:38 AM, Brian Cain wrote:
> Add the QUP + GENI uart device models. The QUP is a "Universal Peripheral"
> present on Qualcomm SoCs, with connections for SPI, UART, I2C, I3C. For
> now we're only interested in UART.
>
> Will follow up with a v2 including wiring into machines + qtest after
> https://lore.kernel.org/qemu-devel/20261027143721.4125584-1-brian.cain@oss.qualcomm.com/
> lands.
>
> Brian Cain (3):
> hw/misc: add the QUP wrapper device
> rust/chardev: fix write errors and add write watches
> rust/hw/char: add QUP GENI UART device model
>
> MAINTAINERS | 3 +
> docs/system/device-emulation.rst | 1 +
> docs/system/devices/qup.rst | 10 +
> include/hw/misc/qup-wrapper.h | 27 +
> hw/misc/qup-wrapper.c | 90 ++
> Cargo.lock | 15 +
> Cargo.toml | 1 +
> hw/misc/Kconfig | 3 +
> hw/misc/meson.build | 3 +
> rust/chardev/src/chardev.rs | 82 +-
> rust/hw/char/Kconfig | 4 +
> rust/hw/char/meson.build | 1 +
> rust/hw/char/qup_geni_uart/Cargo.toml | 27 +
> rust/hw/char/qup_geni_uart/meson.build | 28 +
> rust/hw/char/qup_geni_uart/src/device.rs | 1293 +++++++++++++++++++
> rust/hw/char/qup_geni_uart/src/lib.rs | 9 +
> rust/hw/char/qup_geni_uart/src/registers.rs | 160 +++
> 17 files changed, 1741 insertions(+), 16 deletions(-)
> create mode 100644 docs/system/devices/qup.rst
> create mode 100644 include/hw/misc/qup-wrapper.h
> create mode 100644 hw/misc/qup-wrapper.c
> create mode 100644 rust/hw/char/qup_geni_uart/Cargo.toml
> create mode 100644 rust/hw/char/qup_geni_uart/meson.build
> create mode 100644 rust/hw/char/qup_geni_uart/src/device.rs
> create mode 100644 rust/hw/char/qup_geni_uart/src/lib.rs
> create mode 100644 rust/hw/char/qup_geni_uart/src/registers.rs
>
Any specific reason to have this implemented in Rust?
Not a bad thing in itself, but it means it's only available with rust
builds, which can be a problem if a machine defined in C wants to use it
unconditionally. Rust is not (yet) a mandatory dep for QEMU.
Regards,
Pierrick
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 3/3] rust/hw/char: add QUP GENI UART device model
2026-10-27 18:38 ` [PATCH 3/3] rust/hw/char: add QUP GENI UART device model Brian Cain
@ 2026-10-27 19:05 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 11+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-10-27 19:05 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: Manos Pitsidianakis, qemu-rust, Paolo Bonzini, Pierrick Bouvier
On 27/10/26 19:38, Brian Cain wrote:
> Add a model of the QUP GENI serial engine in UART mode.
>
> Supports FIFO, DMA, loopback modes - DMA via memory link.
>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> MAINTAINERS | 1 +
> docs/system/device-emulation.rst | 1 +
> docs/system/devices/qup.rst | 10 +
> Cargo.lock | 15 +
> Cargo.toml | 1 +
> rust/hw/char/Kconfig | 4 +
> rust/hw/char/meson.build | 1 +
> rust/hw/char/qup_geni_uart/Cargo.toml | 27 +
> rust/hw/char/qup_geni_uart/meson.build | 28 +
> rust/hw/char/qup_geni_uart/src/device.rs | 1293 +++++++++++++++++++
Too big for me to review (not sure if easily splittable), I'll skip.
> rust/hw/char/qup_geni_uart/src/lib.rs | 9 +
> rust/hw/char/qup_geni_uart/src/registers.rs | 160 +++
> 12 files changed, 1550 insertions(+)
> create mode 100644 docs/system/devices/qup.rst
> create mode 100644 rust/hw/char/qup_geni_uart/Cargo.toml
> create mode 100644 rust/hw/char/qup_geni_uart/meson.build
> create mode 100644 rust/hw/char/qup_geni_uart/src/device.rs
> create mode 100644 rust/hw/char/qup_geni_uart/src/lib.rs
> create mode 100644 rust/hw/char/qup_geni_uart/src/registers.rs
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 0/3] hw/misc: QUP UART device
2026-10-27 18:43 ` [PATCH 0/3] hw/misc: QUP UART device Pierrick Bouvier
@ 2026-10-27 20:02 ` Brian Cain
2026-10-27 20:12 ` Pierrick Bouvier
0 siblings, 1 reply; 11+ messages in thread
From: Brian Cain @ 2026-10-27 20:02 UTC (permalink / raw)
To: Pierrick Bouvier, qemu-devel
Cc: Manos Pitsidianakis, qemu-rust, Paolo Bonzini
On 10/27/2026 1:43 PM, Pierrick Bouvier wrote:
> On 10/27/2026 10:38 AM, Brian Cain wrote:
>> Add the QUP + GENI uart device models. The QUP is a "Universal Peripheral"
>> present on Qualcomm SoCs, with connections for SPI, UART, I2C, I3C. For
>> now we're only interested in UART.
>>
>> Will follow up with a v2 including wiring into machines + qtest after
>> https://lore.kernel.org/qemu-devel/20261027143721.4125584-1-brian.cain@oss.qualcomm.com/
>> lands.
>>
>> Brian Cain (3):
>> hw/misc: add the QUP wrapper device
>> rust/chardev: fix write errors and add write watches
>> rust/hw/char: add QUP GENI UART device model
>>
>> MAINTAINERS | 3 +
>> docs/system/device-emulation.rst | 1 +
>> docs/system/devices/qup.rst | 10 +
>> include/hw/misc/qup-wrapper.h | 27 +
>> hw/misc/qup-wrapper.c | 90 ++
>> Cargo.lock | 15 +
>> Cargo.toml | 1 +
>> hw/misc/Kconfig | 3 +
>> hw/misc/meson.build | 3 +
>> rust/chardev/src/chardev.rs | 82 +-
>> rust/hw/char/Kconfig | 4 +
>> rust/hw/char/meson.build | 1 +
>> rust/hw/char/qup_geni_uart/Cargo.toml | 27 +
>> rust/hw/char/qup_geni_uart/meson.build | 28 +
>> rust/hw/char/qup_geni_uart/src/device.rs | 1293 +++++++++++++++++++
>> rust/hw/char/qup_geni_uart/src/lib.rs | 9 +
>> rust/hw/char/qup_geni_uart/src/registers.rs | 160 +++
>> 17 files changed, 1741 insertions(+), 16 deletions(-)
>> create mode 100644 docs/system/devices/qup.rst
>> create mode 100644 include/hw/misc/qup-wrapper.h
>> create mode 100644 hw/misc/qup-wrapper.c
>> create mode 100644 rust/hw/char/qup_geni_uart/Cargo.toml
>> create mode 100644 rust/hw/char/qup_geni_uart/meson.build
>> create mode 100644 rust/hw/char/qup_geni_uart/src/device.rs
>> create mode 100644 rust/hw/char/qup_geni_uart/src/lib.rs
>> create mode 100644 rust/hw/char/qup_geni_uart/src/registers.rs
>>
> Any specific reason to have this implemented in Rust?
>
> Not a bad thing in itself, but it means it's only available with rust
> builds, which can be a problem if a machine defined in C wants to use it
> unconditionally. Rust is not (yet) a mandatory dep for QEMU.
A new device model -> a brand new day -> a new frontier?
It was an interesting challenge and seemed appropriate given the nice
reference pl011 device. But I will concede it can be impractical.
Making this device conditionally enabled in the machine that uses it
felt kinda "lame" and made me long for a day when it *was* a mandatory
dependency.
Seeing as how we have a pl011 device in C, maybe it's better to start
with a C version of the QUP? If so, that's okay with me.
> Regards,
> Pierrick
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 0/3] hw/misc: QUP UART device
2026-10-27 20:02 ` Brian Cain
@ 2026-10-27 20:12 ` Pierrick Bouvier
2026-10-29 0:21 ` Brian Cain
0 siblings, 1 reply; 11+ messages in thread
From: Pierrick Bouvier @ 2026-10-27 20:12 UTC (permalink / raw)
To: Brian Cain, qemu-devel; +Cc: Manos Pitsidianakis, qemu-rust, Paolo Bonzini
On 10/27/2026 12:02 PM, Brian Cain wrote:
>
> On 10/27/2026 1:43 PM, Pierrick Bouvier wrote:
>> On 10/27/2026 10:38 AM, Brian Cain wrote:
>>> Add the QUP + GENI uart device models. The QUP is a "Universal
>>> Peripheral"
>>> present on Qualcomm SoCs, with connections for SPI, UART, I2C, I3C. For
>>> now we're only interested in UART.
>>>
>>> Will follow up with a v2 including wiring into machines + qtest after
>>> https://lore.kernel.org/qemu-devel/20261027143721.4125584-1-
>>> brian.cain@oss.qualcomm.com/
>>> lands.
>>>
>>> Brian Cain (3):
>>> hw/misc: add the QUP wrapper device
>>> rust/chardev: fix write errors and add write watches
>>> rust/hw/char: add QUP GENI UART device model
>>>
>>> MAINTAINERS | 3 +
>>> docs/system/device-emulation.rst | 1 +
>>> docs/system/devices/qup.rst | 10 +
>>> include/hw/misc/qup-wrapper.h | 27 +
>>> hw/misc/qup-wrapper.c | 90 ++
>>> Cargo.lock | 15 +
>>> Cargo.toml | 1 +
>>> hw/misc/Kconfig | 3 +
>>> hw/misc/meson.build | 3 +
>>> rust/chardev/src/chardev.rs | 82 +-
>>> rust/hw/char/Kconfig | 4 +
>>> rust/hw/char/meson.build | 1 +
>>> rust/hw/char/qup_geni_uart/Cargo.toml | 27 +
>>> rust/hw/char/qup_geni_uart/meson.build | 28 +
>>> rust/hw/char/qup_geni_uart/src/device.rs | 1293 +++++++++++++++++++
>>> rust/hw/char/qup_geni_uart/src/lib.rs | 9 +
>>> rust/hw/char/qup_geni_uart/src/registers.rs | 160 +++
>>> 17 files changed, 1741 insertions(+), 16 deletions(-)
>>> create mode 100644 docs/system/devices/qup.rst
>>> create mode 100644 include/hw/misc/qup-wrapper.h
>>> create mode 100644 hw/misc/qup-wrapper.c
>>> create mode 100644 rust/hw/char/qup_geni_uart/Cargo.toml
>>> create mode 100644 rust/hw/char/qup_geni_uart/meson.build
>>> create mode 100644 rust/hw/char/qup_geni_uart/src/device.rs
>>> create mode 100644 rust/hw/char/qup_geni_uart/src/lib.rs
>>> create mode 100644 rust/hw/char/qup_geni_uart/src/registers.rs
>>>
>> Any specific reason to have this implemented in Rust?
>>
>> Not a bad thing in itself, but it means it's only available with rust
>> builds, which can be a problem if a machine defined in C wants to use it
>> unconditionally. Rust is not (yet) a mandatory dep for QEMU.
>
> A new device model -> a brand new day -> a new frontier?
>
> It was an interesting challenge and seemed appropriate given the nice
> reference pl011 device. But I will concede it can be impractical.
> Making this device conditionally enabled in the machine that uses it
> felt kinda "lame" and made me long for a day when it *was* a mandatory
> dependency.
>
> Seeing as how we have a pl011 device in C, maybe it's better to start
> with a C version of the QUP? If so, that's okay with me.
>
If it's a mandatory device as part of a machine definition, it would
probably be better to stick to C for now.
I hope we can get Rust as a main dep at some point. I'm not sure what is
the criteria to do this change, out of deciding "let's do this".
Regards,
Pierrick
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 2/3] rust/chardev: fix write errors and add write watches
2026-10-27 18:38 ` [PATCH 2/3] rust/chardev: fix write errors and add write watches Brian Cain
@ 2026-10-28 7:03 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 11+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-10-28 7:03 UTC (permalink / raw)
To: Brian Cain, qemu-devel
Cc: Manos Pitsidianakis, qemu-rust, Paolo Bonzini, Pierrick Bouvier
On 27/10/26 19:38, Brian Cain wrote:
> qemu_chr_fe_write() reports failure as -1 with the cause in errno, not
> as a negated errno, so errno::into_io_result() turned every failure into
> EPERM and a frontend could never see EAGAIN. Build the error from
> errno instead, in both CharFrontend::write() and the BQL-free variant.
>
> Also add CharFrontend::add_write_watch() and remove_watch(), so that a
> device whose backend would block can retry once it is writable again.
> remove_watch() tolerates a source that the backend has already torn
> down.
Please split as 2 distinct patches. With that:
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
> Signed-off-by: Brian Cain <brian.cain@oss.qualcomm.com>
> ---
> rust/chardev/src/chardev.rs | 82 +++++++++++++++++++++++++++++--------
> 1 file changed, 66 insertions(+), 16 deletions(-)
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 0/3] hw/misc: QUP UART device
2026-10-27 20:12 ` Pierrick Bouvier
@ 2026-10-29 0:21 ` Brian Cain
2026-10-29 0:31 ` Pierrick Bouvier
0 siblings, 1 reply; 11+ messages in thread
From: Brian Cain @ 2026-10-29 0:21 UTC (permalink / raw)
To: Pierrick Bouvier, qemu-devel
Cc: Manos Pitsidianakis, qemu-rust, Paolo Bonzini
On 10/27/2026 3:12 PM, Pierrick Bouvier wrote:
> On 10/27/2026 12:02 PM, Brian Cain wrote:
>> On 10/27/2026 1:43 PM, Pierrick Bouvier wrote:
>>> On 10/27/2026 10:38 AM, Brian Cain wrote:
>>>> Add the QUP + GENI uart device models. The QUP is a "Universal
>>>> Peripheral"
>>>> present on Qualcomm SoCs, with connections for SPI, UART, I2C, I3C. For
>>>> now we're only interested in UART.
>>>>
>>>> Will follow up with a v2 including wiring into machines + qtest after
>>>> https://lore.kernel.org/qemu-devel/20261027143721.4125584-1-
>>>> brian.cain@oss.qualcomm.com/
>>>> lands.
>>>>
>>>> Brian Cain (3):
>>>> hw/misc: add the QUP wrapper device
>>>> rust/chardev: fix write errors and add write watches
>>>> rust/hw/char: add QUP GENI UART device model
>>>>
>>>> MAINTAINERS | 3 +
>>>> docs/system/device-emulation.rst | 1 +
>>>> docs/system/devices/qup.rst | 10 +
>>>> include/hw/misc/qup-wrapper.h | 27 +
>>>> hw/misc/qup-wrapper.c | 90 ++
>>>> Cargo.lock | 15 +
>>>> Cargo.toml | 1 +
>>>> hw/misc/Kconfig | 3 +
>>>> hw/misc/meson.build | 3 +
>>>> rust/chardev/src/chardev.rs | 82 +-
>>>> rust/hw/char/Kconfig | 4 +
>>>> rust/hw/char/meson.build | 1 +
>>>> rust/hw/char/qup_geni_uart/Cargo.toml | 27 +
>>>> rust/hw/char/qup_geni_uart/meson.build | 28 +
>>>> rust/hw/char/qup_geni_uart/src/device.rs | 1293 +++++++++++++++++++
>>>> rust/hw/char/qup_geni_uart/src/lib.rs | 9 +
>>>> rust/hw/char/qup_geni_uart/src/registers.rs | 160 +++
>>>> 17 files changed, 1741 insertions(+), 16 deletions(-)
>>>> create mode 100644 docs/system/devices/qup.rst
>>>> create mode 100644 include/hw/misc/qup-wrapper.h
>>>> create mode 100644 hw/misc/qup-wrapper.c
>>>> create mode 100644 rust/hw/char/qup_geni_uart/Cargo.toml
>>>> create mode 100644 rust/hw/char/qup_geni_uart/meson.build
>>>> create mode 100644 rust/hw/char/qup_geni_uart/src/device.rs
>>>> create mode 100644 rust/hw/char/qup_geni_uart/src/lib.rs
>>>> create mode 100644 rust/hw/char/qup_geni_uart/src/registers.rs
>>>>
>>> Any specific reason to have this implemented in Rust?
>>>
>>> Not a bad thing in itself, but it means it's only available with rust
>>> builds, which can be a problem if a machine defined in C wants to use it
>>> unconditionally. Rust is not (yet) a mandatory dep for QEMU.
>> A new device model -> a brand new day -> a new frontier?
>>
>> It was an interesting challenge and seemed appropriate given the nice
>> reference pl011 device. But I will concede it can be impractical.
>> Making this device conditionally enabled in the machine that uses it
>> felt kinda "lame" and made me long for a day when it *was* a mandatory
>> dependency.
>>
>> Seeing as how we have a pl011 device in C, maybe it's better to start
>> with a C version of the QUP? If so, that's okay with me.
>>
> If it's a mandatory device as part of a machine definition, it would
> probably be better to stick to C for now.
> I hope we can get Rust as a main dep at some point. I'm not sure what is
> the criteria to do this change, out of deciding "let's do this".
Hmm - so ... can I put in a vote for this, then? Can "at some point" be
now? What are the criteria?
> Regards,
> Pierrick
^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH 0/3] hw/misc: QUP UART device
2026-10-29 0:21 ` Brian Cain
@ 2026-10-29 0:31 ` Pierrick Bouvier
0 siblings, 0 replies; 11+ messages in thread
From: Pierrick Bouvier @ 2026-10-29 0:31 UTC (permalink / raw)
To: Brian Cain, qemu-devel; +Cc: Manos Pitsidianakis, qemu-rust, Paolo Bonzini
On 10/28/2026 4:21 PM, Brian Cain wrote:
>
> On 10/27/2026 3:12 PM, Pierrick Bouvier wrote:
>> On 10/27/2026 12:02 PM, Brian Cain wrote:
>>> On 10/27/2026 1:43 PM, Pierrick Bouvier wrote:
>>>> On 10/27/2026 10:38 AM, Brian Cain wrote:
>>>>> Add the QUP + GENI uart device models. The QUP is a "Universal
>>>>> Peripheral"
>>>>> present on Qualcomm SoCs, with connections for SPI, UART, I2C,
>>>>> I3C. For
>>>>> now we're only interested in UART.
>>>>>
>>>>> Will follow up with a v2 including wiring into machines + qtest after
>>>>> https://lore.kernel.org/qemu-devel/20261027143721.4125584-1-
>>>>> brian.cain@oss.qualcomm.com/
>>>>> lands.
>>>>>
>>>>> Brian Cain (3):
>>>>> hw/misc: add the QUP wrapper device
>>>>> rust/chardev: fix write errors and add write watches
>>>>> rust/hw/char: add QUP GENI UART device model
>>>>>
>>>>> MAINTAINERS | 3 +
>>>>> docs/system/device-emulation.rst | 1 +
>>>>> docs/system/devices/qup.rst | 10 +
>>>>> include/hw/misc/qup-wrapper.h | 27 +
>>>>> hw/misc/qup-wrapper.c | 90 ++
>>>>> Cargo.lock | 15 +
>>>>> Cargo.toml | 1 +
>>>>> hw/misc/Kconfig | 3 +
>>>>> hw/misc/meson.build | 3 +
>>>>> rust/chardev/src/chardev.rs | 82 +-
>>>>> rust/hw/char/Kconfig | 4 +
>>>>> rust/hw/char/meson.build | 1 +
>>>>> rust/hw/char/qup_geni_uart/Cargo.toml | 27 +
>>>>> rust/hw/char/qup_geni_uart/meson.build | 28 +
>>>>> rust/hw/char/qup_geni_uart/src/device.rs | 1293 +++++++++++++
>>>>> ++++++
>>>>> rust/hw/char/qup_geni_uart/src/lib.rs | 9 +
>>>>> rust/hw/char/qup_geni_uart/src/registers.rs | 160 +++
>>>>> 17 files changed, 1741 insertions(+), 16 deletions(-)
>>>>> create mode 100644 docs/system/devices/qup.rst
>>>>> create mode 100644 include/hw/misc/qup-wrapper.h
>>>>> create mode 100644 hw/misc/qup-wrapper.c
>>>>> create mode 100644 rust/hw/char/qup_geni_uart/Cargo.toml
>>>>> create mode 100644 rust/hw/char/qup_geni_uart/meson.build
>>>>> create mode 100644 rust/hw/char/qup_geni_uart/src/device.rs
>>>>> create mode 100644 rust/hw/char/qup_geni_uart/src/lib.rs
>>>>> create mode 100644 rust/hw/char/qup_geni_uart/src/registers.rs
>>>>>
>>>> Any specific reason to have this implemented in Rust?
>>>>
>>>> Not a bad thing in itself, but it means it's only available with rust
>>>> builds, which can be a problem if a machine defined in C wants to
>>>> use it
>>>> unconditionally. Rust is not (yet) a mandatory dep for QEMU.
>>> A new device model -> a brand new day -> a new frontier?
>>>
>>> It was an interesting challenge and seemed appropriate given the nice
>>> reference pl011 device. But I will concede it can be impractical.
>>> Making this device conditionally enabled in the machine that uses it
>>> felt kinda "lame" and made me long for a day when it *was* a mandatory
>>> dependency.
>>>
>>> Seeing as how we have a pl011 device in C, maybe it's better to start
>>> with a C version of the QUP? If so, that's okay with me.
>>>
>> If it's a mandatory device as part of a machine definition, it would
>> probably be better to stick to C for now.
>> I hope we can get Rust as a main dep at some point. I'm not sure what is
>> the criteria to do this change, out of deciding "let's do this".
>
> Hmm - so ... can I put in a vote for this, then? Can "at some point" be
> now? What are the criteria?
>
@Manos, Paolo: any idea on this?
Could 12.0 be the first version to make rust a mandatory dep?
Regards,
Pierrick
^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-10-29 0:31 UTC | newest]
Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-27 18:38 [PATCH 0/3] hw/misc: QUP UART device Brian Cain
2026-10-27 18:38 ` [PATCH 1/3] hw/misc: add the QUP wrapper device Brian Cain
2026-10-27 18:38 ` [PATCH 2/3] rust/chardev: fix write errors and add write watches Brian Cain
2026-10-28 7:03 ` Philippe Mathieu-Daudé
2026-10-27 18:38 ` [PATCH 3/3] rust/hw/char: add QUP GENI UART device model Brian Cain
2026-10-27 19:05 ` Philippe Mathieu-Daudé
2026-10-27 18:43 ` [PATCH 0/3] hw/misc: QUP UART device Pierrick Bouvier
2026-10-27 20:02 ` Brian Cain
2026-10-27 20:12 ` Pierrick Bouvier
2026-10-29 0:21 ` Brian Cain
2026-10-29 0:31 ` Pierrick Bouvier
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.