[PATCH 13/16] hw/misc: add i.MX 95 DPU command-sequencer stub (headless)
Kyle Fox <[email protected]>
| Newsgroups | org.nongnu.qemu-arm,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <[email protected]> |
A minimal stub for the i.MX 95 DPU (display controller) command sequencer. This machine is headless, but Linux's dpu95 driver busy-waits with no timeout on the sequencer status register during probe, so a reads-as-zero stub would hang userspace bring-up. The stub reports the sequencer idle with FIFO space so the probe completes. Real display scanout is left to a follow-on series. Signed-off-by: Kyle Fox <[email protected]> --- hw/misc/Kconfig | 3 ++ hw/misc/imx95_dpu.c | 117 +++++++++++++++++++++++++++++++++++++++++++ hw/misc/meson.build | 1 + hw/misc/trace-events | 1 + 4 files changed, 122 insertions(+) create mode 100644 hw/misc/imx95_dpu.c diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index e1b8c390312..59c55cb5b83 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -290,3 +290,6 @@ config IMX95_PMIC config IMX95_XCACHE bool + +config IMX95_DPU + bool diff --git a/hw/misc/imx95_dpu.c b/hw/misc/imx95_dpu.c new file mode 100644 index 00000000000..4e09b399d85 --- /dev/null +++ b/hw/misc/imx95_dpu.c @@ -0,0 +1,117 @@ +/* + * NXP i.MX 95 DPU (Display Processing Unit) command-sequencer status stub + * + * Copyright (c) 2026, Kyle Fox + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * The DPU is not modelled: this base machine is headless, and real display + * scanout is a follow-on series. With the real System Manager powering the + * display mix, however, Linux's dpu95 driver stops deferring and probes - and + * its blit-engine bring-up busy-waits with NO + * timeout on the command sequencer status register: + * dpu95_cs_wait_idle() spins until CMDSEQ_STATUS.IDLE is set + * dpu95_cs_wait_fifo_space() spins until CMDSEQ_STATUS.FIFOSPACE >= 192 + * A plain zero-returning stub never satisfies either, so the probe kthread + * hangs forever, which in turn wedges wait_for_device_probe() in + * kernel_init() and userspace never starts. + * + * This stub returns CMDSEQ_STATUS with IDLE set and a full FIFOSPACE so both + * polls pass instantly; every other register reads back zero and writes are + * dropped. That is enough to let the probe complete (the actual command + * submission / fence path runs only at runtime, which we never reach). A + * follow-on series that models the DPU for real (adding display scanout and + * Wayland output) removes this stub. + */ + +#include "qemu/osdep.h" +#include "qemu/module.h" +#include "hw/core/sysbus.h" +#include "qom/object.h" +#include "trace.h" + +#define TYPE_IMX95_DPU "imx95.dpu" +OBJECT_DECLARE_SIMPLE_TYPE(IMX95DPUState, IMX95_DPU) + +#define IMX95_DPU_REG_SIZE 0x400000 + +struct IMX95DPUState { + SysBusDevice parent_obj; + MemoryRegion iomem; +}; + +/* Command-sequencer status, from the dpu95 blit register map. */ +#define DPU_CMDSEQ_STATUS 0x1019c +#define DPU_CMDSEQ_STATUS_IDLE 0x40000000u +#define DPU_CMDSEQ_STATUS_FIFOSPACE 0x0001ffffu + +static uint64_t imx95_dpu_read(void *opaque, hwaddr offset, unsigned size) +{ + if (offset == DPU_CMDSEQ_STATUS) { + /* + * Report the sequencer idle with a full FIFO. This is deliberately + * NOT the reset value (0x41000080: IDLE set but FIFOSPACE = 128, below + * the driver's threshold of 192), because the dpu95 probe polls + * FIFOSPACE before the enable handshake that would raise it on real + * silicon. Returning a constant full FIFO is sound only because the + * model is probe-time-only: no actual command sequence is ever + * submitted. If real DPU command submission is ever modelled, + * FIFOSPACE must track the command FIFO occupancy instead. + */ + uint32_t status = DPU_CMDSEQ_STATUS_IDLE | DPU_CMDSEQ_STATUS_FIFOSPACE; + trace_imx95_dpu_cmdseq_status(status); + return status; + } + return 0; +} + +static void imx95_dpu_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ +} + +static const MemoryRegionOps imx95_dpu_ops = { + .read = imx95_dpu_read, + .write = imx95_dpu_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl = { + .min_access_size = 4, + .max_access_size = 4, + }, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + }, +}; + +static void imx95_dpu_init(Object *obj) +{ + IMX95DPUState *s = IMX95_DPU(obj); + + memory_region_init_io(&s->iomem, obj, &imx95_dpu_ops, s, + TYPE_IMX95_DPU, IMX95_DPU_REG_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); +} + +static void imx95_dpu_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_DISPLAY, dc->categories); + dc->desc = "NXP i.MX 95 DPU (status stub)"; +} + +static const TypeInfo imx95_dpu_info = { + .name = TYPE_IMX95_DPU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMX95DPUState), + .instance_init = imx95_dpu_init, + .class_init = imx95_dpu_class_init, +}; + +static void imx95_dpu_register_types(void) +{ + type_register_static(&imx95_dpu_info); +} + +type_init(imx95_dpu_register_types) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index d7d283a191b..9d629c8efef 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -180,3 +180,4 @@ system_ss.add(when: 'CONFIG_IMX95_GPC', if_true: files('imx95_gpc.c')) system_ss.add(when: 'CONFIG_IMX95_SRC', if_true: files('imx95_src.c')) system_ss.add(when: 'CONFIG_IMX95_PMIC', if_true: files('imx95_pmic.c')) system_ss.add(when: 'CONFIG_IMX95_XCACHE', if_true: files('imx95_xcache.c')) +system_ss.add(when: 'CONFIG_IMX95_DPU', if_true: files('imx95_dpu.c')) diff --git a/hw/misc/trace-events b/hw/misc/trace-events index 0555b15a1f3..e921b5f290a 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -466,3 +466,4 @@ pcal6408a_reg_read(uint8_t reg, uint8_t val) "PCAL6408A reg 0x%02x -> 0x%02x" pcal6408a_reg_write(uint8_t reg, uint8_t val) "PCAL6408A reg 0x%02x <- 0x%02x" pf53_reg_read(uint8_t reg, uint8_t val) "PF53 reg 0x%02x -> 0x%02x" pf53_reg_write(uint8_t reg, uint8_t val) "PF53 reg 0x%02x <- 0x%02x" +imx95_dpu_cmdseq_status(uint32_t value) "CMDSEQ_STATUS -> 0x%08x" -- 2.34.1