Skip to content

grainmedia: GM8135S, GM8136 and GM8136S machines - #208

Merged
dimerr merged 2 commits into
OpenIPC:masterfrom
dimerr:grainmedia
Oct 26, 2026
Merged

dimerr merged 2 commits into
OpenIPC:masterfrom
dimerr:grainmedia

Conversation

@dimerr

@dimerr dimerr commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator

grainmedia: GM8135S, GM8136 and GM8136S machines

Three machines in hw/arm/grainmedia.c: a Faraday FA626TE (arm926 with
the FA626TE's MIDR) among Faraday peripherals, Grain Media's own devices
as hw//gm-.c:

  • gm-intc (FTINTC030), gm-timer (FTTMR010 on the PMU's PCLK), gm-wdt,
    gm-gpio, gm-i2c (with an OmniVision sensor ID for sensor=ov2715 /
    ov9715), gm-pmu (chip IDs, strap, PLLs, frequency change interrupt),
    gm-ddrc, gm-dmac (FTDMAC020, hardware handshake, link lists, abort),
    gm-spi020 (FTSPI020 with its RX FIFO), gm-otg (FOTG210 host); the
    FTGMAC100 and FTSDC021 on QEMU's ftgmac100 / generic-sdhci;
  • a mask ROM stand-in: a camera's NOR dump (flash-file=, written back)
    boots nsboot in SRAM, the vendor U-Boot and the 3.3 kernel; -kernel
    boots ATAGs, machine 758;
  • the video pipeline the vendor graph runs: gm-vcap (VCAP300 link lists,
    painting the OpenIPC logo), gm-3dnr, gm-scaler, gm-lcd (the graph's
    frame clock), gm-h264e (MCP280: real I_16x16 slices behind the driver's
    own headers, CAVLC and CABAC, P slices coding changed macroblocks I_PCM
    against the reference in the REC buffer), gm-isp (ISP328 frames, the
    DMA tables and AE / AWB / AF statistics), gm-2d (GM2D command lists:
    lines, fills, keyed / rotated / scaled blits across 14 formats),
    gm-mcp100 (MJPEG through the embedded CPU's mailbox, with restart
    markers);
  • audio: gm-ssp (FTSSP010 I2S + the ADDA308 codec), capturing a tone or
    playing to -audiodev; gm-saradc, gm-rtc, gm-pwm, gm-irdet (keys from
    qom-set), gm-pmonitor, and the FTAES020 on fh-aes (new "advance").

Shared code: hantro-h264.c gains I16 DC / P_Skip / I_PCM slice coders for
CAVLC and CABAC; nvt_pixel_jpeg_scan_rst() adds restart markers and
separate chroma planes.

A Longse GM8136 dump boots from flash to its application, whose RTSP
server streams decodable 1080p / D1 H.264 (qemu-boot/test-grainmedia-
rtsp.sh) and, with audio enabled in its config, the tone as PCMU. The
OpenIPC gm8136 / gm8135 releases boot to a login with a DHCP lease and a
JFFS2 overlay (qemu-boot/test-grainmedia-boot.sh, in CI);
qemu/tests/test-grainmedia.py checks the devices register by register
(in CI), decoding the H.264 and JPEG output with ffmpeg when present.

Three machines in hw/arm/grainmedia.c: a Faraday FA626TE (arm926 with
the FA626TE's MIDR) among Faraday peripherals, Grain Media's own devices
as hw/*/gm-*.c:

* gm-intc (FTINTC030), gm-timer (FTTMR010 on the PMU's PCLK), gm-wdt,
  gm-gpio, gm-i2c (with an OmniVision sensor ID for sensor=ov2715 /
  ov9715), gm-pmu (chip IDs, strap, PLLs, frequency change interrupt),
  gm-ddrc, gm-dmac (FTDMAC020, hardware handshake, link lists, abort),
  gm-spi020 (FTSPI020 with its RX FIFO), gm-otg (FOTG210 host); the
  FTGMAC100 and FTSDC021 on QEMU's ftgmac100 / generic-sdhci;
* a mask ROM stand-in: a camera's NOR dump (flash-file=, written back)
  boots nsboot in SRAM, the vendor U-Boot and the 3.3 kernel; -kernel
  boots ATAGs, machine 758;
* the video pipeline the vendor graph runs: gm-vcap (VCAP300 link lists,
  painting the OpenIPC logo), gm-3dnr, gm-scaler, gm-lcd (the graph's
  frame clock), gm-h264e (MCP280: real I_16x16 slices behind the driver's
  own headers, CAVLC and CABAC, P slices coding changed macroblocks I_PCM
  against the reference in the REC buffer), gm-isp (ISP328 frames, the
  DMA tables and AE / AWB / AF statistics), gm-2d (GM2D command lists:
  lines, fills, keyed / rotated / scaled blits across 14 formats),
  gm-mcp100 (MJPEG through the embedded CPU's mailbox, with restart
  markers);
* audio: gm-ssp (FTSSP010 I2S + the ADDA308 codec), capturing a tone or
  playing to -audiodev; gm-saradc, gm-rtc, gm-pwm, gm-irdet (keys from
  qom-set), gm-pmonitor, and the FTAES020 on fh-aes (new "advance").

Shared code: hantro-h264.c gains I16 DC / P_Skip / I_PCM slice coders for
CAVLC and CABAC; nvt_pixel_jpeg_scan_rst() adds restart markers and
separate chroma planes.

A Longse GM8136 dump boots from flash to its application, whose RTSP
server streams decodable 1080p / D1 H.264 (qemu-boot/test-grainmedia-
rtsp.sh) and, with audio enabled in its config, the tone as PCMU. The
OpenIPC gm8136 / gm8135 releases boot to a login with a DHCP lease and a
JFFS2 overlay (qemu-boot/test-grainmedia-boot.sh, in CI);
qemu/tests/test-grainmedia.py checks the devices register by register
(in CI), decoding the H.264 and JPEG output with ffmpeg when present.
@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

Add Grain Media GM8135S, GM8136, and GM8136S camera machines

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Add three GM813x machines with flash and direct-kernel boot paths for camera firmware.
• Model peripherals, video encoders, and audio needed by vendor and OpenIPC software.
• Add device, boot, and RTSP tests, plus setup and operating documentation.
Diagram

graph TD
  Flash["SPI NOR"] --> Machine["GM813x machines"] --> CPU["FA626TE CPU"]
  Machine --> Devices["MMIO devices"] --> INTC["Interrupt controller"] --> CPU
  Devices --> Video["Video engines"] --> Codecs["Codec helpers"]
  Flash --> Devices
Loading
High-Level Assessment

Shared machine initialization avoids duplicating three closely related boards. Reusing QEMU's Ethernet, SD, USB-host, AES, and codec facilities where applicable is preferable to parallel implementations; the remaining devices require vendor-specific register behavior.

Files changed (38) +11363 / -50

Enhancement (30) +9516 / -47
grainmedia.cDefine three GM813x boards and their boot wiring +744/-0

Define three GM813x boards and their boot wiring

• Creates the FA626TE-based machines with variant RAM and chip IDs, MMIO mappings, clocks, IRQs, DMA handshakes, and optional I2C sensors. Supports writable SPI NOR boot through an nsboot staging stub and direct ATAG kernel boot, including XIP uImage and command-line handling.

qemu/hw/arm/grainmedia.c

gm-ssp.cModel FTSSP010 I2S and ADDA308 audio +541/-0

Model FTSSP010 I2S and ADDA308 audio

• Adds codec-rate-aware capture of a generated tone and playback to an optional QEMU audiodev. Implements FIFOs and DMA request signaling for the two SSP instances.

qemu/hw/audio/gm-ssp.c

gm-2d.cExecute GM2D drawing and blit command lists +717/-0

Execute GM2D drawing and blit command lists

• Implements lines, fills, and keyed, blended, rotated, or scaled blits across supported pixel formats. Reports command-list completion through status and IRQ.

qemu/hw/display/gm-2d.c

gm-lcd.cProvide the LCD vertical-interrupt frame clock +186/-0

Provide the LCD vertical-interrupt frame clock

• Models LCD registers and a periodic vertical interrupt. This lets the vendor video graph advance jobs on its display-frame clock.

qemu/hw/display/gm-lcd.c

gm-dmac.cImplement FTDMAC020 transfers and peripheral handshakes +570/-0

Implement FTDMAC020 transfers and peripheral handshakes

• Adds channel transfers, linked descriptors, completion and abort status, and hardware request lines. Runs transfers from a bottom half so requests raised during peripheral register writes can be serviced.

qemu/hw/dma/gm-dmac.c

gm-gpio.cModel FTGPIO010 pins and interrupts +332/-0

Model FTGPIO010 pins and interrupts

• Adds input and output register behavior, configurable default input levels, and pin-driven interrupt handling for both GPIO banks.

qemu/hw/gpio/gm-gpio.c

gm-i2c.cAdd the byte-oriented FTI2C010 master +319/-0

Add the byte-oriented FTI2C010 master

• Implements start, stop, byte transfers, ACK/NAK reporting, and interrupt status through a QEMU I2C bus. This supports sensor-ID probing by the guest.

qemu/hw/i2c/gm-i2c.c

gm-intc.cRoute FTINTC030 interrupts to CPU IRQ and FIQ +289/-0

Route FTINTC030 interrupts to CPU IRQ and FIQ

• Models grouped level and edge inputs, enable and status registers, and per-CPU interrupt targets. Routes pending sources to the processor.

qemu/hw/intc/gm-intc.c

fh-aes.cSupport advancing AES DMA addresses between chunks +18/-0

Support advancing AES DMA addresses between chunks

• Adds an optional advance property that preserves chunked-transfer progress and consumes the first-block IV flag. The Grain Media machine enables it for its FTAES020 instance.

qemu/hw/misc/fh-aes.c

gm-3dnr.cComplete FT3DNR200 linked video jobs +296/-0

Complete FT3DNR200 linked video jobs

• Processes command lists, copies source frames to destination buffers, and reports per-job completion. Supports the vendor graph's queued denoising work.

qemu/hw/misc/gm-3dnr.c

gm-ddrc.cExpose DDR initialization and capacity registers +134/-0

Expose DDR initialization and capacity registers

• Adds DDR controller register storage, the initialization-done bit, and a machine-derived memory-size code used during boot.

qemu/hw/misc/gm-ddrc.c

gm-h264e.cModel the MCP280 H.264 encoder and header packer +577/-0

Model the MCP280 H.264 encoder and header packer

• Accepts guest-packed headers, converts packed source frames, and appends CAVLC or CABAC slice data using shared coders. Maintains reference buffers and reports bitstream size, completion, and interrupts.

qemu/hw/misc/gm-h264e.c

gm-irdet.cInject IR remote keys through a QOM property +206/-0

Inject IR remote keys through a QOM property

• Models FTIRDET registers and NEC-style key and repeat delivery. Exposes a runtime-settable key for guest input testing.

qemu/hw/misc/gm-irdet.c

gm-isp.cGenerate ISP frames and camera-control statistics +407/-0

Generate ISP frames and camera-control statistics

• Runs ISP frame-start and frame-end events and guest-programmed DMA tables. Supplies synthetic AE, AWB, and AF statistics so vendor control threads can progress.

qemu/hw/misc/gm-isp.c

gm-mcp100.cImplement mailbox-driven MCP100 MJPEG encoding +514/-0

Implement mailbox-driven MCP100 MJPEG encoding

• Emulates the embedded-CPU handshake, guest-written JPEG headers, and the firmware mailbox. Encodes packed or planar input into JPEG scans with restart markers and signals job completion.

qemu/hw/misc/gm-mcp100.c

gm-pmonitor.cModel timed pMonitor measurement windows +174/-0

Model timed pMonitor measurement windows

• Adds register and PCLK-driven window timing with completion interrupt behavior. Measurement counters remain zero.

qemu/hw/misc/gm-pmonitor.c

gm-pmu.cProvide chip identity, PLL clocks, and frequency-change IRQ +264/-0

Provide chip identity, PLL clocks, and frequency-change IRQ

• Models PMU chip IDs, boot straps, PLL registers, and a derived PCLK output for timed peripherals. Reports frequency-change completion through its interrupt line.

qemu/hw/misc/gm-pmu.c

gm-pwm.cExpose FTPWM010 programming registers +144/-0

Expose FTPWM010 programming registers

• Adds register handling and transfers programmed counter values on update. PWM output timing is not modeled.

qemu/hw/misc/gm-pwm.c

gm-saradc.cAdd monitor-settable SAR ADC inputs +141/-0

Add monitor-settable SAR ADC inputs

• Models three eight-bit ADC channels and exposes their values through QOM properties. This supports guest cable-detection and key-input reads.

qemu/hw/misc/gm-saradc.c

gm-scaler.cRun scaler command lists and produce UYVY output +386/-0

Run scaler command lists and produce UYVY output

• Executes queued scaler jobs, including nearest-neighbor scaling and fixed-color output paths. Writes completion status and raises the device interrupt.

qemu/hw/misc/gm-scaler.c

gm-vcap.cGenerate capture frames through VCAP300 link lists +510/-0

Generate capture frames through VCAP300 link lists

• Implements capture-list processing and periodic synchronization. Paints a moving-logo test image into DMA buffers and completes guest status slots.

qemu/hw/misc/gm-vcap.c

hantro-h264.cAdd reusable CAVLC and CABAC slice coders +415/-40

Add reusable CAVLC and CABAC slice coders

• Extracts macroblock reconstruction into a shared helper and adds slice-data generation for I_16x16, P_Skip, and changed-macroblock I_PCM pictures. These coders let MCP280 append video data after guest-written headers.

qemu/hw/misc/hantro-h264.c

hantro-h264.hDeclare the new H.264 slice-data interfaces +33/-0

Declare the new H.264 slice-data interfaces

• Exposes CAVLC and CABAC I- and P-slice functions, including reference-buffer parameters and bit-offset conventions.

qemu/hw/misc/hantro-h264.h

nvt-pixel.cExtend JPEG scans with planar chroma and restart markers +37/-7

Extend JPEG scans with planar chroma and restart markers

• Adds configurable chroma-plane layout and periodic RST markers with predictor resets. Retains the existing scan entry point as a wrapper.

qemu/hw/misc/nvt-pixel.c

gm-rtc.cModel FTRTC011 timekeeping and alarms +246/-0

Model FTRTC011 timekeeping and alarms

• Provides RTC-clock-backed calendar registers, guest time loading, periodic and alarm status, and interrupts.

qemu/hw/rtc/gm-rtc.c

gm-spi020.cImplement FTSPI020 flash commands and RX FIFO +411/-0

Implement FTSPI020 flash commands and RX FIFO

• Translates controller commands into SSI flash transfers and models status polling, buffered reads, and completion interrupts. Exposes the FIFO's DMA request to the FTDMAC020.

qemu/hw/ssi/gm-spi020.c

gm-timer.cClock FTTMR010 timers from PMU PCLK +408/-0

Clock FTTMR010 timers from PMU PCLK

• Adds three programmable channels with countdown, reload, one-shot, and interrupt behavior. Responds to PMU clock changes.

qemu/hw/timer/gm-timer.c

gm-otg.cWrap QEMU's EHCI host with FOTG210 OTG registers +205/-0

Wrap QEMU's EHCI host with FOTG210 OTG registers

• Adds host-role, port-speed, and global interrupt registers around a FUSBH200-compatible EHCI controller.

qemu/hw/usb/gm-otg.c

gm-wdt.cImplement FTWDT010 expiry and reset behavior +279/-0

Implement FTWDT010 expiry and reset behavior

• Models load and restart registers, clock selection, expiry interrupt, and machine reset. Uses the PMU-derived clock when selected.

qemu/hw/watchdog/gm-wdt.c

nvt-pixel.hDeclare the extended JPEG scan API +13/-0

Declare the extended JPEG scan API

• Adds the public scan function accepting separate chroma planes, sample spacing, and a restart interval.

qemu/include/hw/misc/nvt-pixel.h

Tests (3) +1221 / -0
test-grainmedia-boot.shTest OpenIPC boot and writable NOR overlay +125/-0

Test OpenIPC boot and writable NOR overlay

• Builds a synthetic NOR containing an OpenIPC kernel and root filesystem, then boots the selected machine. Checks login, DHCP, absence of kernel panic, and JFFS2 writes.

qemu-boot/test-grainmedia-boot.sh

test-grainmedia-rtsp.shValidate camera-dump RTSP video end to end +113/-0

Validate camera-dump RTSP video end to end

• Boots a copy of a vendor NOR dump and connects to the camera application's RTSP server. Uses ffprobe and ffmpeg to verify stream dimensions, decodable frames, and completed video jobs.

qemu-boot/test-grainmedia-rtsp.sh

test-grainmedia.pyExercise GM813x registers, media output, and migration +983/-0

Exercise GM813x registers, media output, and migration

• Uses qtest to drive boot staging, clocks, interrupts, flash and DMA, sensors, video, audio, and small peripherals. Optionally decodes H.264 and JPEG with ffmpeg and checks machine variants and a migration round trip.

qemu/tests/test-grainmedia.py

Documentation (3) +510 / -3
CLAUDE.mdDocument Grain Media implementation and project conventions +42/-1

Document Grain Media implementation and project conventions

• Records boot, peripheral, media, and audio behaviors relevant to working on the models. Adds grainmedia to the commit-area conventions.

CLAUDE.md

README.mdList and describe the GM813x machine family +58/-2

List and describe the GM813x machine family

• Adds the three machines to the supported-family and machine counts. Summarizes their boot paths, modeled devices, streaming support, and detailed documentation.

README.md

grainmedia.mdDocument GM813x hardware, boot paths, and model limits +410/-0

Document GM813x hardware, boot paths, and model limits

• Provides the memory map, firmware boot chain, device behavior, and media pipeline. Includes OpenIPC and camera-dump usage examples and identifies functionality not modeled.

docs/grainmedia.md

Other (2) +116 / -0
ci.ymlExercise Grain Media machines and OpenIPC boots in CI +51/-0

Exercise Grain Media machines and OpenIPC boots in CI

• Checks that all three machines are registered and runs the device test suite. Adds a boot matrix for the GM8136 and GM8135 OpenIPC releases.

.github/workflows/ci.yml

setup.shInstall and build the Grain Media QEMU models +65/-0

Install and build the Grain Media QEMU models

• Copies the new machine and device sources into the QEMU tree. Adds GRAINMEDIA Kconfig and Meson entries and wires required shared models and host-controller dependencies.

qemu/setup.sh

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Flash boot loses access to its stack 🐞 Bug ≡ Correctness
Description
grainmedia_common_init() maps SRAM and then adds an unimplemented peripheral region that covers
the same address range. During flash boot, the nsboot image occupies only the first 24 KiB of SRAM,
while its stack uses the overlapping range above it.
Code

qemu/hw/arm/grainmedia.c[R474-475]

+    create_unimplemented_device("grainmedia.periph", GM_PERIPH_BASE,
+                                GM_PERIPH_SIZE);
Evidence
The fallback range is 0x90000000–0x9fffffff, which contains the SRAM mapped at 0x92000000; the boot
setup explicitly places nsboot's stack above its 0x6000-byte image.

qemu/hw/arm/grainmedia.c[102-105]
qemu/hw/arm/grainmedia.c[308-311]
qemu/hw/arm/grainmedia.c[343-348]
qemu/hw/arm/grainmedia.c[468-475]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The broad unimplemented peripheral mapping overlaps boot SRAM and its stack.
## Fix Focus Areas
- qemu/hw/arm/grainmedia.c[468-475]
## Recommended Fix
Split the unimplemented peripheral range around SRAM, or give SRAM an explicitly higher-priority mapping, and verify reads and writes throughout SRAM during flash boot.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Odd-width video blits overread memory ✓ Resolved
Description
gm_2d_get() reads a full four-byte UYVY pair for the last pixel of an odd-width source. The blit
validation accepts a row stride of exactly sw * 2, so that final pair extends beyond the allocated
source on the last row.
Code

qemu/hw/display/gm-2d.c[R228-231]

+    case F_UYVY: {
+        const uint8_t *pair = row + (x & ~1u) * 2;
+
+        return yuv_to_rgb(pair[(x & 1) ? 3 : 1], pair[0], pair[2]);
Evidence
Validation checks byte count against stride but not even width; the decoder accesses the second
chroma byte of a pair, and the blit can select the last source pixel.

qemu/hw/display/gm-2d.c[228-231]
qemu/hw/display/gm-2d.c[499-514]
qemu/hw/display/gm-2d.c[530-558]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Odd-width UYVY blits read beyond the source allocation.
## Fix Focus Areas
- qemu/hw/display/gm-2d.c[228-231]
- qemu/hw/display/gm-2d.c[499-514]
## Recommended Fix
Validate that UYVY source rows contain complete pixel pairs, or handle a final unpaired pixel without accessing bytes outside its row.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Guest can make the encoder read past host memory ✓ Resolved
Description
gm_h264e_encode_slice takes the source stride from PARM0[7:0] and the macroblock width from
PARM0[15:8] separately, then allocates stride * h bytes but reads w * 2 bytes per row from
that buffer during YUYV→NV12 conversion. A guest that programs a small stride field (e.g. 0 → 32
bytes) with a wide picture (mbw=256 → 8192 bytes per row) makes QEMU read far past the heap
allocation, which can crash the emulator or leak host memory into the encoded bitstream.
Code

qemu/hw/misc/gm-h264e.c[R292-294]

+    const int mbw = extract32(p0, 8, 8) + 1;
+    const int first = extract32(p0, 16, 8), last = extract32(p0, 24, 8);
+    const int stride = (extract32(p0, 0, 8) + 1) * 16 * 2;
Evidence
Line 294 derives stride independently of mbw (line 292). Line 314 allocates stride * h. The
loop at lines 320-331 indexes l + x*2 up to w*2 bytes into each row. Nothing checks `stride >=
w*2`.

qemu/hw/misc/gm-h264e.c[288-331]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
In gm_h264e_encode_slice the guest-programmed source stride can be smaller than the row width that gets read (w*2), so the conversion loop reads past the `src` heap buffer.
## Fix Focus Areas
- qemu/hw/misc/gm-h264e.c[292-331]
## Recommended Fix
After computing `stride` and `w`, add `if (stride < w * 2) { qemu_log_mask(LOG_GUEST_ERROR, ...); return; }`, or allocate `MAX(stride, w*2) * h` and read with the real stride.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View action required (1)
4. Odd source widths read past the scaler buffer ✓ Resolved
Description
gm_scaler_path rounds sx down to an even pixel but always copies four bytes from `src +
sy*sstride + sx*2`, even when an odd-width source row has only one two-byte pixel left. With an odd
R_SRC_SIZE width, including width one, and zero row gap, an enabled output path can read two bytes
past the final row’s sstride * sh allocation in gm_scaler_job and DMA those host bytes into
guest memory.
Code

qemu/hw/misc/gm-scaler.c[R148-149]

+                const int sx = MIN((int64_t)(x + x0) * sw / ow, sw - 1) & ~1;
+                memcpy(d, src + (size_t)sy * sstride + sx * 2, 4);
Evidence
gm_scaler_job sets sstride = (sw + gap) * 2 and allocates sstride * sh bytes. For sw=1 and
gap=0, each row is two bytes; gm_scaler_path computes sx=0 and unconditionally copies four
bytes, reading two bytes beyond the allocation on the last row.

qemu/hw/misc/gm-scaler.c[138-152]
qemu/hw/misc/gm-scaler.c[158-175]
qemu/hw/misc/gm-scaler.c[120-150]
qemu/hw/misc/gm-scaler.c[158-178]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`gm_scaler_path` can read beyond an odd-width source row because it copies a four-byte pixel pair even when only one two-byte pixel remains; on the final row with zero gap, the read extends beyond the allocated source buffer.
## Fix Focus Areas
- qemu/hw/misc/gm-scaler.c[141-150]
- qemu/hw/misc/gm-scaler.c[158-175]
## Recommended Fix
Reject unsupported odd source widths in `gm_scaler_job`, or make `gm_scaler_path` construct the final pixel pair without reading beyond the source buffer. If retaining the four-byte copy, ensure the source allocation has two bytes of zero padding, for example by allocating `sstride * sh + 2` with `g_malloc0`; alternatively, clamp `sx` to `(sw & ~1) - 2` for odd widths of at least two and handle `sw == 1` separately.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. CABAC B slices decode incorrectly 🐞 Bug ≡ Correctness
Description
gm_h264e_encode_slice() sends B slices to h264_slice_pskip_cabac(), which initializes and emits
P-slice skip flags using context 11. When the configured slice uses CABAC and is neither I nor P,
its B-slice header and P-slice skip coding disagree.
Code

qemu/hw/misc/gm-h264e.c[R369-373]

+    } else {                                /* B: nothing changes */
+        if (cabac) {
+            n = h264_slice_pskip_cabac(qp, idc, nmb, out, cap);
+        } else {
+            n = h264_slice_pskip_cavlc(nmb, off, out, cap);
Evidence
The caller's B branch invokes a helper documented as P-only, and that helper encodes every skip flag
using its initialized P-slice context.

qemu/hw/misc/gm-h264e.c[296-296]
qemu/hw/misc/gm-h264e.c[369-373]
qemu/hw/misc/hantro-h264.c[883-906]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
CABAC B slices are encoded with P-slice skip contexts.
## Fix Focus Areas
- qemu/hw/misc/gm-h264e.c[369-373]
- qemu/hw/misc/hantro-h264.c[883-906]
## Recommended Fix
Implement and call a B-slice CABAC skip encoder using the B-slice contexts, or reject unsupported B-slice requests rather than emitting invalid slices.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Reversed rectangles lose their width ✓ Resolved
Description
gm_2d_draw() replaces x0 with the smaller endpoint before using x0 to calculate the larger
endpoint. A filled rectangle requested from x=5 to x=2 therefore renders only at x=2 rather than
across the requested range.
Code

qemu/hw/display/gm-2d.c[R428-429]

+        x0 = MAX(MIN(x0, x1), t.x0);
+        x1 = MIN(MAX(x0, x1), t.x1);
Evidence
The second endpoint calculation uses the already modified first endpoint, and the resulting
endpoints determine the rendered span.

qemu/hw/display/gm-2d.c[424-446]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Reversed X endpoints collapse a filled rectangle to one column.
## Fix Focus Areas
- qemu/hw/display/gm-2d.c[424-440]
## Recommended Fix
Swap or save the original X endpoints before clipping either one to the target bounds.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


7. GPIO outputs stay high after release ✓ Resolved
Description
gm_gpio_drive() masks changed pins by the new output-direction bits, so an output-to-input change
never updates the connected GPIO line. Reset also clears the direction and output registers without
lowering previously driven lines.
Code

qemu/hw/gpio/gm-gpio.c[R101-109]

+static void gm_gpio_drive(GmGpioState *s, uint32_t old_pins)
+{
+    uint32_t pins = gm_gpio_pins(s);
+    uint32_t changed = (old_pins ^ pins) & s->dir;
+    int i;
+
+    for (i = 0; i < GM_GPIO_PINS; i++) {
+        if (changed & (1u << i)) {
+            qemu_set_irq(s->out[i], !!(pins & (1u << i)));
Evidence
Direction is replaced before the drive check, which excludes pins no longer configured as outputs;
reset likewise does not send an output update.

qemu/hw/gpio/gm-gpio.c[101-110]
qemu/hw/gpio/gm-gpio.c[185-191]
qemu/hw/gpio/gm-gpio.c[236-237]
qemu/hw/gpio/gm-gpio.c[248-266]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Connected GPIO consumers retain an asserted level after a pin becomes input or the device resets.
## Fix Focus Areas
- qemu/hw/gpio/gm-gpio.c[101-110]
- qemu/hw/gpio/gm-gpio.c[185-191]
- qemu/hw/gpio/gm-gpio.c[248-266]
## Recommended Fix
Notify connected outputs when their direction changes from output to input, and lower driven output lines during reset.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View review recommended (4)
8. Running timers retain an old period ✓ Resolved
Description
gm_timer_write() changes a running timer's limit only when the converted new LOAD is nonzero.
Writing a zero LOAD while it runs therefore leaves its previous periodic limit active instead of
switching to the model's one-shot behavior.
Code

qemu/hw/timer/gm-timer.c[R269-277]

+    case 0x4:
+        /* a running channel takes the new value at its next reload */
+        c->load = val;
+        if ((s->cr & CR_EN(c->id)) && gm_timer_to_reg(c, val)) {
+            ptimer_transaction_begin(c->pt);
+            ptimer_set_limit(c->pt, gm_timer_to_reg(c, val), 0);
+            ptimer_run(c->pt, 0);
+            ptimer_transaction_commit(c->pt);
+        }
Evidence
Timer start explicitly implements zero LOAD as one-shot, but the running LOAD-write path skips the
ptimer update for that value.

qemu/hw/timer/gm-timer.c[130-145]
qemu/hw/timer/gm-timer.c[269-277]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A running timer ignores a zero LOAD and continues using its old period.
## Fix Focus Areas
- qemu/hw/timer/gm-timer.c[130-145]
- qemu/hw/timer/gm-timer.c[269-277]
## Recommended Fix
Handle a zero converted LOAD in the running-timer write path, updating ptimer state to the intended one-shot behavior while preserving the specified counter transition.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


9. Small RAM settings misreport DDR size ✓ Resolved
Description
grainmedia_common_init() accepts any RAM size up to 512 MiB, but gm-ddrc rounds the reported
controller size up to its next supported size code. With -m 32M, the machine allocates and
advertises 32 MiB for kernel boot while the controller reports 64 MiB to software reading its
registers.
Code

qemu/hw/arm/grainmedia.c[R447-450]

+    if (machine->ram_size > 512 * MiB) {
+        error_report("grainmedia: at most 512 MiB of DDR");
+        exit(1);
+    }
Evidence
The machine has only an upper-bound check, passes the arbitrary size to DDRC, and uses that actual
size for boot information; DDRC starts at a 64 MiB code and rounds upward.

qemu/hw/arm/grainmedia.c[447-450]
qemu/hw/arm/grainmedia.c[492-495]
qemu/hw/arm/grainmedia.c[652-664]
qemu/hw/misc/gm-ddrc.c[73-84]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Accepted RAM sizes can disagree with the DDR controller's reported geometry.
## Fix Focus Areas
- qemu/hw/arm/grainmedia.c[447-450]
- qemu/hw/misc/gm-ddrc.c[73-84]
## Recommended Fix
Accept only RAM sizes representable by the DDR controller and reject unsupported values with a clear startup error.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


10. A looping DMA chain freezes the whole emulator ✓ Resolved
Description
gm_dmac_run_chan follows each nonzero guest-supplied LLP inside an unbounded enabled-channel
loop without yielding or limiting descriptor work. A self-linked descriptor or cyclic chain keeps
the bottom half occupied indefinitely for unpaced memory-to-memory transfers or zero-cycle
descriptors even with handshaking, blocking the guest, monitor, and other devices; cyclic chains are
also used by audio drivers for ring buffers.
Code

qemu/hw/dma/gm-dmac.c[R314-318]

+        }
+        if (!c->llp) {
+            gm_dmac_finish(s, n, false);
+            return;
+        }
Evidence
The loop at line 267 continues while the channel is enabled, and lines 314–321 load a nonzero
guest-supplied LLP after each block before repeating. Because the channel runs directly from a
bottom half, handshake-free transfers do not wait for a request; a self-linked unpaced or zero-cycle
descriptor also avoids the available exits of LLP==0, bus error, abort, or waiting on a request.

qemu/hw/dma/gm-dmac.c[236-258]
qemu/hw/dma/gm-dmac.c[263-323]
qemu/hw/dma/gm-dmac.c[210-212]
qemu/hw/dma/gm-dmac.c[242-256]
qemu/hw/dma/gm-dmac.c[264-323]
qemu/hw/dma/gm-dmac.c[326-337]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A cyclic linked-descriptor chain can keep `gm_dmac_run_chan` in an unbounded synchronous loop inside the DMA bottom half, preventing other emulator activity from running.
## Fix Focus Areas
- qemu/hw/dma/gm-dmac.c[263-323]
- qemu/hw/dma/gm-dmac.c[326-340]
## Recommended Fix
Give each invocation a descriptor or transfer-byte budget (for example, 64 descriptors or 64 KiB), counting zero-cycle descriptors against it. When the budget is reached, preserve channel progress and reschedule unfinished work through the bottom half or a short timer so the main loop can run.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


11. Multi-slice pictures overwrite the reference top 🐞 Bug ≡ Correctness
Description
For P pictures, gm_h264e_encode_slice reads the reference at ME_REF + first*w*16 (luma) and `+
first*w*8 (chroma), but writes the reconstruction to rec_y / rec_uv with no first` offset. In
a picture coded as several slices, every slice after the first writes its rows over the top of the
REC frame. The next P picture then compares against the wrong reference, so its skip/PCM decisions
and the decoded output are wrong.
Code

qemu/hw/misc/gm-h264e.c[R383-386]

+    if (ry && rec_y && rec_y != GM_H264E_NO_REC) {
+        dma_memory_write(&address_space_memory, rec_y, ry, (size_t)w * h,
+                         MEMTXATTRS_UNSPECIFIED);
+        dma_memory_write(&address_space_memory, rec_uv, ruv,
Evidence
Lines 356-361 add first-row offsets when reading ME_REF. Lines 383-387 write ry/ruv to the REC
base addresses unchanged, even though the header comment and the test use REC as the backing store
for the next picture's ME_REF.

qemu/hw/misc/gm-h264e.c[352-388]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The REC write ignores the slice's first macroblock row, while the ME_REF read honours it.
## Fix Focus Areas
- qemu/hw/misc/gm-h264e.c[383-387]
## Recommended Fix
Write to `rec_y + (hwaddr)first * w * 16` and `rec_uv + (hwaddr)first * w * 8`, matching the ME_REF read offsets.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

12. RTSP test leaves its temp dir on early failure ✓ Resolved
Description
The EXIT trap in test-grainmedia-rtsp.sh expands QPID under set -u, although QPID is not
assigned until QEMU starts. If an earlier command fails, such as copying a missing --flash file,
the trap exits on the unbound variable before reaching rm -rf "$WORK", leaving the temporary
directory behind.
Code

qemu-boot/test-grainmedia-rtsp.sh[R57-59]

+WORK=$(mktemp -d)
+trap 'kill $QPID 2>/dev/null || true; rm -rf "$WORK"' EXIT
+cp "$FLASH" "$WORK/flash.bin"
Evidence
Line 24 enables set -euo pipefail; the EXIT trap at line 58 references $QPID before the flash
copy at line 59 and the first QPID assignment at line 66. A failed copy therefore invokes the trap
while QPID is unset, and nounset prevents the trap from reaching work-directory removal.

qemu-boot/test-grainmedia-rtsp.sh[24-66]
qemu-boot/test-grainmedia-rtsp.sh[24-24]
qemu-boot/test-grainmedia-rtsp.sh[57-66]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The EXIT trap references an unset `QPID` under `set -u`, so failures before QEMU starts can skip removal of `WORK`.
## Fix Focus Areas
- qemu-boot/test-grainmedia-rtsp.sh[57-66]
## Recommended Fix
Initialize `QPID=` before installing the EXIT trap. Guard the kill operation when `QPID` is empty, for example with `[ -n "$QPID" ] && kill "$QPID" 2>/dev/null || true`, and ensure the trap always runs `rm -rf "$WORK"`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread qemu/hw/arm/grainmedia.c
Comment on lines +474 to +475
create_unimplemented_device("grainmedia.periph", GM_PERIPH_BASE,
GM_PERIPH_SIZE);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Flash boot loses access to its stack 🐞 Bug ≡ Correctness

grainmedia_common_init() maps SRAM and then adds an unimplemented peripheral region that covers
the same address range. During flash boot, the nsboot image occupies only the first 24 KiB of SRAM,
while its stack uses the overlapping range above it.
Agent Prompt
## Issue description
The broad unimplemented peripheral mapping overlaps boot SRAM and its stack.
## Fix Focus Areas
- qemu/hw/arm/grainmedia.c[468-475]
## Recommended Fix
Split the unimplemented peripheral range around SRAM, or give SRAM an explicitly higher-priority mapping, and verify reads and writes throughout SRAM during flash boot.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread qemu/hw/display/gm-2d.c
Comment thread qemu/hw/misc/gm-h264e.c
Comment on lines +369 to +373
} else { /* B: nothing changes */
if (cabac) {
n = h264_slice_pskip_cabac(qp, idc, nmb, out, cap);
} else {
n = h264_slice_pskip_cavlc(nmb, off, out, cap);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Cabac b slices decode incorrectly 🐞 Bug ≡ Correctness

gm_h264e_encode_slice() sends B slices to h264_slice_pskip_cabac(), which initializes and emits
P-slice skip flags using context 11. When the configured slice uses CABAC and is neither I nor P,
its B-slice header and P-slice skip coding disagree.
Agent Prompt
## Issue description
CABAC B slices are encoded with P-slice skip contexts.
## Fix Focus Areas
- qemu/hw/misc/gm-h264e.c[369-373]
- qemu/hw/misc/hantro-h264.c[883-906]
## Recommended Fix
Implement and call a B-slice CABAC skip encoder using the B-slice contexts, or reject unsupported B-slice requests rather than emitting invalid slices.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread qemu/hw/display/gm-2d.c Outdated
Comment thread qemu/hw/gpio/gm-gpio.c Outdated
Comment thread qemu/hw/misc/gm-h264e.c
Comment thread qemu/hw/misc/gm-scaler.c
Comment thread qemu/hw/dma/gm-dmac.c
Comment thread qemu/hw/misc/gm-h264e.c
Comment on lines +383 to +386
if (ry && rec_y && rec_y != GM_H264E_NO_REC) {
dma_memory_write(&address_space_memory, rec_y, ry, (size_t)w * h,
MEMTXATTRS_UNSPECIFIED);
dma_memory_write(&address_space_memory, rec_uv, ruv,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

11. Multi-slice pictures overwrite the reference top 🐞 Bug ≡ Correctness

For P pictures, gm_h264e_encode_slice reads the reference at ME_REF + first*w*16 (luma) and `+
first*w*8 (chroma), but writes the reconstruction to rec_y / rec_uv with no first` offset. In
a picture coded as several slices, every slice after the first writes its rows over the top of the
REC frame. The next P picture then compares against the wrong reference, so its skip/PCM decisions
and the decoded output are wrong.
Agent Prompt
## Issue description
The REC write ignores the slice's first macroblock row, while the ME_REF read honours it.

## Fix Focus Areas
- qemu/hw/misc/gm-h264e.c[383-387]

## Recommended Fix
Write to `rec_y + (hwaddr)first * w * 16` and `rec_uv + (hwaddr)first * w * 8`, matching the ME_REF read offsets.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread qemu-boot/test-grainmedia-rtsp.sh
* gm-h264e: refuse a source stride narrower than the picture (read past
  the row buffer); log CABAC B slices as unimplemented; note that the
  driver gives REC per slice (slice.c), ME_REF per picture.
* gm-scaler, gm-2d: pad the source copy so an odd width's last UYVY pair
  stays inside it.
* gm-2d: order reversed rectangle corners before clipping.
* gm-gpio: drive a pin low when it turns input, and release every line
  at reset.
* gm-timer: a zero LOAD written while running turns the channel one-shot.
* gm-dmac: at most 256 descriptors per channel per bottom half, so a
  looping chain lets the main loop run.
* grainmedia: accept only the DDR sizes the controller can describe
  (64-512 MiB, powers of two).
* test-grainmedia-rtsp.sh: the exit trap no longer trips on an unset
  QPID under set -u.
@dimerr

dimerr commented Oct 26, 2026

Copy link
Copy Markdown
Collaborator Author

Qodo findings, addressed in 4808b72:

  • 1 (SRAM vs. the unimplemented region): not a bug. create_unimplemented_device() maps at priority -1000, so the SRAM wins; flash boots run nsboot from it.
  • 2, 4 (odd-width UYVY overread in gm-2d / gm-scaler): fixed, the source copy is padded.
  • 3 (h264e stride below the picture width): fixed, the job is refused with a guest error.
  • 5 (CABAC B slices): logged as unimplemented; the camera driver only codes I and P.
  • 6 (reversed rectangle): fixed, with a test.
  • 7 (GPIO lines after output→input / reset): fixed.
  • 8 (zero LOAD on a running timer): fixed, goes one-shot.
  • 9 (RAM sizes the DDRC cannot describe): now rejected (64–512 MiB, powers of two).
  • 10 (looping DMA chain): fixed, 256 descriptors per channel per bottom half, then rescheduled.
  • 11 (multi-slice REC offset): not a bug. The vendor driver programs REC at the slice's first row (slice.c: recpic_luma_phy + start_Y_pos * frame_width * 16) and ME_REF at the picture base. A comment now says so.
  • 12 (RTSP test trap under set -u): fixed.

Re-run: test-grainmedia.py 0 failures, the OpenIPC gm8136 / gm8135 boots, and a camera dump's RTSP stream decodes.

@dimerr
dimerr merged commit c90b6d2 into OpenIPC:master Oct 26, 2026
27 checks passed
@dimerr
dimerr deleted the grainmedia branch October 26, 2026 12:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant