agents/openai.yaml
interface:
display_name: "QEMU Peripheral Modeling"
short_description: "Model and verify QEMU MMIO peripherals."
default_prompt: "Use $qemu-peripheral-modeling to model this QEMU peripheral from a source-cited register contract and focused qtests."
assets/framework-decision-template.md
# Register bank framework decision record
Write this record to `.oh-my-qemu/<task-slug>/output/framework-decision.md`
(link it from `audit.md`), replace every `<placeholder>`, and keep the
semantic rule ids with the implementation mapping. Run
`scripts/check-framework-decision.sh` on it before review.
## Bank
- Device: `<device-name-and-type>`
- Window: `<window-name-base-size>`
- Purpose: `<control-status-or-data>`
- Bank kind: `<control-status | data-command | hybrid>`
- RegisterInfo default applies: `<yes/no>`
## Decision inputs (apply in order, stop at the first decisive input)
| Input | Evidence | Decisive? |
| --- | --- | --- |
| Target-workspace policy | `<path:line>` or `none found` | `<yes/no>` |
| User direction (compatible) | `<quote or request path>` | `<yes/no>` |
| Nearby maintained convention | `<file:line>` | `<yes/no>` |
## Chosen framework
- Implementation: `<RegisterInfo | manual MMIO | hybrid>`
- Decisive input: `<row from the table above, or "none: RegisterInfo default">`
- Rationale: `<one or two sentences>`
- Structural reason (required for manual/hybrid): `<which command/FIFO/DMA/timing/reentrancy behavior cannot be expressed by RegisterAccessInfo hooks>`
- Hybrid split: `<RegisterInfo banks and manual apertures, or none>`
- Semantic rule ids mapped here: `<rule-id>, <rule-id>`
- Accepted limits: `<access sizes, side effects, reentrancy>`
- Review gate: `<reviewer/date/verdict; missing or vague record is a BLOCKER>`
## Implementation mapping
| IR trigger and hook | Implementation site |
| --- | --- |
| `<trigger>` / `<hook>` | `<RegisterInfo access hook or callback>` |
| `<transition-operation>` | `<side-effect site>` |
## Unbounded surfaces
- FIFO or data aperture: `<explicit MemoryRegionOps name>` or `none`
- Reentrancy guard: `<explicit flag in device state>` or `core guard only`
- Other manual callback: `<reason>` or `none`
## Evidence links
- Register contract: `.oh-my-qemu/<task-slug>/output/register-contract.md`
- Semantic IR (RS-IR): `.oh-my-qemu/<task-slug>/output/register-semantics.ir`
- IR validation: `<PASS line or log path>`
- Source citations: `<path:line, manual page/section>`
references/field-lessons.md
# Peripheral modeling field lessons
Use this reference at planning and review time. The requirements below were
abstracted from captured QEMU agent sessions (Codex CLI/desktop, DeepSeek
Harness, legacy DeepSeek TUI, Claude Code), a remote firmware-reproduction
workspace, and review-comment adjustments. Names, paths, and customer details
are removed; the evidence stays in the task audit, not in this reference.
## Modeling requirements (PM)
| ID | Requirement | Anti-pattern it prevents |
| --- | --- | --- |
| PM-01 | Model the real hardware behavior; never add a convenience device (for example virtio) that the hardware does not have. | "It boots" models that do not match the physical device. |
| PM-02 | Every reset value, access policy, and side effect needs a cited source; verify user-supplied register/field premises against manual page and section before modeling. | Bits written from memory; a wrong premise becomes a false-positive qtest. |
| PM-03 | Record the register-bank framework decision (workspace policy -> compatible user direction -> maintained nearby convention -> RegisterInfo default) with inputs and rationale. | Framework choice with no evidence; manual MMIO treated as an automatic failure. |
| PM-04 | Use RegisterInfo for control/status banks when the gate selects it; keep layout, storage, tables, and hooks in the device `.c` file; use explicit MemoryRegionOps for FIFO/data apertures. | Register definitions in headers; FIFO behavior hidden in RegisterInfo arrays. |
| PM-05 | Keep unimplemented paths visible with trace events and `LOG_UNIMP`; use `LOG_GUEST_ERROR` for invalid guest access. | Silent zero-return sinks counted as modeled behavior. |
| PM-06 | Implement the error matrix: guest error vs unimplemented vs internal invariant vs host failure; never fake success to keep boot moving. | Fake ready bits; crashes or assertions used as normal control flow. |
| PM-07 | Give material transitions stable trace events; correlate them with qtest or runtime evidence. Trace counts alone are not correctness proof. | Logs that cannot be tied to a rule or a test. |
| PM-08 | A qtest proves only what it asserts: check the real offset, access width, reset, mask, W1C, IRQ, timer, and DMA effect. | A test reading the wrong address hides the model bug. |
| PM-09 | Block devices: exclude `BLK_PERM_RESIZE` when capacity is cached; respect 32-bit LBA capacity limits; verify against the checked-out tree. | Silent capacity divergence; oversized sparse backends. |
| PM-10 | DMA/descriptor models validate guest addresses, separate parsing from execution, and trace milestones; unknown descriptors stay visible. | Unchecked DMA ranges; skipped descriptor counts hidden. |
| PM-11 | Guest-visible migratable state belongs in `VMStateDescription`; local caches do not. | Migration drift or "works only before savevm". |
| PM-12 | Keep rule ids from the RS-IR in model comments, trace events, qtests, and documentation. | Contract, model, and test impossible to cross-check. |
| PM-13 | For a guest-visible control/status bank, RegisterInfo is the default; a nearby manual convention is decisive only when it is same-subsystem, maintained, semantically comparable, and cited by file:line. | Agents citing "hw/net mostly uses manual MMIO" or "many siblings are manual" to skip RegisterInfo. |
| PM-14 | Manual MMIO for a control/status bank needs a structural reason (command/FIFO/DMA/timing/reentrancy) that RegisterInfo hooks cannot express; prefer a hybrid split over whole-window manual. | Whole-window manual callbacks where a RegisterInfo bank plus a data aperture would be clearer. |
| PM-15 | The framework decision record must state bank kind, decisive input, rationale, structural reason, hybrid split, and accepted limits; missing/vague records are review BLOCKERs. | A one-line "manual MMIO justified" note accepted without evidence or review. |
| PM-16 | Source header/body comments describe hardware and public references only; never cite local task artifacts (`.oh-my-qemu/...`, `output/...`, `audit.md`, evidence paths, review IDs, host paths). | Evidence paths leaking into upstream-style source comments and stale task-local references. |
| PM-17 | Keep file headers minimal (SPDX/copyright plus at most a one-line hardware identity); body comments only on key code, 1-3 lines, at most one brief public source reference; never put implementation strategy, framework choice/defense, "modeled contract" pointers, or neighboring-convention rationale in source. | A .c header explaining "no policy in this tree; neighboring devices use manual style" instead of keeping the decision in the audit. |
| PM-18 | Generated source is not a work log: never record rounds, milestones, AC status, audit/review/finding notes, commands, evidence trails, session or task identifiers, or progress in source comments. | Source headers narrating "round 2 review passed", "M1 done", or "next blocker" that become stale immediately. |
## Review-comment adjustments (RV)
These are recurring adjustment patterns from review feedback; apply them to a
model, a patch, or a handoff:
| ID | Adjustment |
| --- | --- |
| RV-01 | Verify each reviewer claim against the checked-out code and QOM semantics with `file:line`; do not accept a claim because it is confident. |
| RV-02 | Classify a finding as pre-existing or introduced by the change set before fixing it; do not silently "fix" a pre-existing bug inside an unrelated change. |
| RV-03 | Reject unsupported debug surfaces (for example a `-global` override unreachable from normal machine/guest paths) as correctness evidence. |
| RV-04 | Re-derive expected values (status codes, markers, DTB properties, thresholds) from the same evidence class as the artifact; stale literals are a known false-negative source. |
| RV-05 | Keep patch-series adjustments (subject/body, split/order, DCO, checkpatch) in the workflow patch-series reference; do not mix commit hygiene into the device behavior record. |
| RV-06 | Tests are requested scope: add them when asked or required by acceptance criteria; when the user removes a requested test, remove it and its registration cleanly. |
| RV-07 | Report unexplained numeric differences (IRQ counts, register deltas, RMSE-like metrics) as explicit handoff gaps; do not hide them behind a summary. |
| RV-08 | Do not treat QEMU diagnostics on stderr or a debug log as guest serial output; console markers come from the serial chardev. |
| RV-09 | For documentation review, prefer the short sibling-page shape, drop out-of-tree repository links and local recipes, and document only implemented or explicitly limited behavior. |
| RV-10 | When a review asks for a narrow fix, do exactly that scope; do not fold in unrelated cleanups without recording the decision. |
## Use in the workflow
1. Before implementing, scan PM-01..PM-18 for the device class and record the
applicable IDs in `audit.md`.
2. Translate the RS-IR rules with `ir-to-registerinfo.md` and map each rule to a
mechanism, trace/log policy, and qtest.
3. During review, apply RV-01..RV-10 to reviewer claims and to your own evidence.
4. After a review round, update the translation record and the gap list; add a
new PM/RV entry only when the requirement is reusable beyond the task.
## Boundaries
These are local modeling requirements, not upstream contribution guidance.
Never turn an entry into upstream-ready code, commit messages, or patches.
references/ir-to-registerinfo.md
# RS-IR to RegisterInfo translation
Use this reference after the register-semantics IR (RS-IR) validates and before
writing a device model. It defines how each rule becomes QEMU implementation
code, how errors and unimplemented paths are handled, and what trace/log evidence
the model must produce.
The translation is a design step, not code generation. Inspect the checked-out
QEMU tree for the current RegisterInfo API and the local device conventions; do
not copy remembered signatures.
## Translation record
Record one row per rule in `.oh-my-qemu/<task-slug>/audit.md`:
| Rule | Mechanism | Hook | State/migration | Trace event | Log/error policy | qtest |
| --- | --- | --- | --- | --- | --- | --- |
- **Mechanism** comes from the rule's `framework` line: `registerinfo`,
`manual-mmio`, or `helper`.
- **Hook** comes from the same line: `pre_write`, `post_write`, `post_read`,
`manual`, or `reset`.
- **Trace event** and **log/error policy** come from the rule's `observe` line.
- **qtest** names the narrow test that proves the rule; the rule id is kept in a
comment or the test name.
## RegisterInfo mapping
For a `registerinfo` rule:
1. Define register offsets, field macros, and reset values in the device `.c`
file from the RS-IR symbols; do not put them in a header.
2. Build the `RegisterInfo` array and `RegisterAccessInfo` entries for the
bank, using the checked-out tree's current API and nearby devices.
3. Map the trigger:
- `register_read` / `register_write`: the access type and allowed widths;
- `field_write`: the field mask and `pre_write` decode where needed;
- `register_read` with read side effects: `post_read`, never a pre-read
mutation;
- `reset`: reset hook or `register_reset`.
4. Map each condition to the guard that must hold before the transition, using
the same operand order as the rule. A condition that cannot be evaluated in
the hook is a gap, not an invented assumption.
5. Map each transition operation to its side-effect site:
- field/state updates: post-write or reset hook;
- IRQ raise/lower: after updating status, through the wired `qemu_irq`;
- timers: the checked-out timer API;
- FIFO/data apertures: explicit `MemoryRegionOps`, not the RegisterInfo
array;
- DMA: address-space helpers with validated ranges;
- exceptions/events/logging: the documented device path.
6. Put guest-visible state in `VMStateDescription` when the rule declares
`MIGRATABLE`; keep local caches out of migration.
## Manual MMIO mapping
For a `manual-mmio` or `helper` rule:
- One `MemoryRegionOps` per register window or data aperture; reject
unsupported access widths explicitly.
- Keep callbacks allocation-free and bounded; long work belongs in timers,
bottom halves, or workers.
- Reentrancy: a leaf region owned by `RegisterInfoArray` may not expose a
`DeviceState` through `mr->dev`; use an explicit reentrancy guard in device
state for FIFO or DMA callbacks.
- A FIFO or streaming aperture is always an explicit `MemoryRegionOps`
aperture even when the control/status bank uses `RegisterInfo`.
## Error handling matrix
Classify every error path before writing code:
| Situation | Required handling |
| --- | --- |
| Guest writes a read-only or reserved field | ignore the write and `qemu_log_mask(LOG_GUEST_ERROR, ...)` with the access details |
| Guest uses an unsupported access width or alignment | reject or handle per the register contract; log `LOG_GUEST_ERROR` |
| Guest reaches an unimplemented window or placeholder device | trace the access and `LOG_UNIMP`; never fake ready status silently |
| Model invariant violated (corrupt state, impossible FIFO transition) | `error_report` or assert with the rule id; do not continue with corrupted state |
| Guest-triggered DMA/address error | report through the documented device error path and log the guest error |
| Internal host/resource failure | `error_report` / `error_setg`, return the failure; never hide it |
- `LOG_UNIMP` means "this path is deliberately not modeled"; it is visibility,
not modeled behavior.
- `LOG_GUEST_ERROR` means "the guest did something the hardware would reject";
use it for invalid access, not for internal model errors.
- A diagnostic sink that returns zeros or a fake status to keep boot moving is a
placeholder. Record it as a gap and never count it as a PASS basis.
## Trace events
The `observe` line names the evidence path. For a material rule:
- define a stable trace event named from the device and rule id, for example
`<device>.<rule-id>` or the local subsystem's naming convention;
- place the event at the side-effect site, after status is updated and before
the IRQ line change when ordering matters;
- emit one event per material transition, not per byte or per poll unless the
trace contract says otherwise;
- correlate the event with guest-visible output in the qtest or runtime
evidence; trace counts alone are not correctness proof;
- keep the trace-event definition in the device's trace-events file and record
the event name in the translation table.
If a rule is guest-visible and has no trace event, record `log=...` and the qtest
that proves it; if neither trace nor log exists, add a `gap` entry.
## qtest mapping
Each rule gets at least one narrow qtest assertion:
- reset value and access masks first;
- condition false/true boundaries;
- each material transition (IRQ, timer, FIFO, DMA, clear-on-read);
- invalid-access and unimplemented-path behavior when the rule declares it;
- rule id in a comment or test name so the test can be traced back to the
contract.
Do not treat a boot banner or trace count as a substitute for the rule qtest.
Add tests only within the requested scope; if the user removes a requested test,
remove its registration cleanly and record the change.
## Translation checklist
- [ ] Every rule has a mechanism, hook, and qtest row.
- [ ] Every guest-visible rule has an `observe` line naming a trace event or log.
- [ ] Every `UNSUPPORTED` or `LOGGING_ONLY` transition has `log=unimp` (or
`both`) and a gap.
- [ ] Every unimplemented address symbol has a board-level placeholder name.
- [ ] Register layout, storage, tables, and hooks live in the device `.c` file.
- [ ] Error paths follow the matrix above; no silent sink is counted as modeled.
- [ ] The RS-IR validation result and translation table are recorded in the audit.
## Worked mapping (fictional demo timer)
From `register-semantics-example.ir`:
| Rule | Mechanism | Hook | Trace | Log/error | qtest |
| --- | --- | --- | --- | --- | --- |
| R1 | registerinfo | post_write | demo_timer.r1 | none | write CTRL with EN=1 and MODE=PERIODIC, then check the timer is scheduled |
| R2 | manual-mmio | manual | demo_timer.r2 | error=guest_error | advance the virtual clock past PERIOD; check counter and irq0 |
The address symbol `UNMODELED_WINDOW` is `status=unimplemented` with
`placeholder=board_unimpl`; the board-level placeholder is handled by
`qemu-board-modeling/references/unimplemented-device.md`, and it must be removed
when the real device replaces it.
## Boundaries
- Local-only modeling and verification; do not prepare upstream-ready code,
commit messages, or patches from this translation.
- Do not generate C templates or upstream patches. The consumer writes the model
by hand and keeps the translation table as the reviewable mapping.
references/patterns.md
# Peripheral modeling patterns
Reusable patterns and review checklists for device models. Verify every API and
path in the checked-out tree; this file deliberately avoids version-specific
signatures.
## Unimplemented register block with logging
Use one placeholder region per unimplemented hardware block:
- read callback: emit `qemu_log_mask(LOG_UNIMP, ...)` with device name, address,
and access size, then return zero;
- write callback: emit `qemu_log_mask(LOG_UNIMP, ...)` and ignore the value;
- invalid width or out-of-range access: `LOG_GUEST_ERROR`;
- do not store register values, do not fake status bits, and do not raise IRQs;
- keep the region out of modeled-register qtests and list it in the
unimplemented map with the log mechanism noted.
A placeholder that satisfies a guest poll loop is a false positive: the guest
sees a completed operation the hardware never performed.
## Trace-event split for wide summaries
Use two event classes instead of one mixed event:
- a summary event for state transitions and aggregate counters (command,
descriptor count, total bytes, completion status);
- a detail event for individual operations worth keeping in bounded volume;
- never emit one event per payload byte or per DMA beat;
- include the instance id in the event name or payload;
- keep guest-visible state in device state, and treat trace output as evidence,
not as the source of truth.
## IRQ wiring table
Record the wiring in `audit.md` before coding:
| Output | Source register/bit | Level or edge | Mask/enable | Clear path | Routed to |
| --- | --- | --- | --- | --- | --- |
- one `qemu_irq` per real output line; do not merge unrelated sources;
- update status before raising or lowering the line;
- clear or acknowledge in the documented order (status, mask, controller);
- cover assert, mask, and W1C-clear paths in qtests.
## Multi-instance devices
- keep per-instance state in the device structure; no file-scope mutable state;
- expose an instance id or count property only when the hardware varies;
- one `MemoryRegion` per instance, named from the instance id;
- prefix trace events with the instance id;
- qtest with two instances to catch shared-state bugs.
## FIFO, queue, and status patterns
- data aperture through explicit `MemoryRegionOps`; control/status registers
may stay in the selected register framework;
- keep occupancy, full, empty, and overflow bits derived from one source of
truth, not mirrored ad hoc;
- watermark IRQ updates after every push or pop; overflow and underflow have
explicit clear behavior;
- qtest: fill, drain, overflow, underflow, watermark IRQ, reset while busy.
## Reentrancy
- MMIO callbacks can trigger DMA, timers, or bottom halves that re-enter the
same device region; set a guard flag in device state and document the
allowed reentrant paths;
- do not rely on the generic memory reentrancy guard for `RegisterInfo` leaf
regions; their owner is the internal register array, not the device;
- avoid long synchronous backend operations inside a callback; defer to a
bottom half or coroutine;
- qtest: issue the trigger write while an operation is in flight and verify
state integrity.
## Block permission and capacity review checklist
- permissions and shared permissions are explicit, not `BLK_PERM_ALL` by
default; verify the constant set in `include/block/block-common.h`;
- capacity fixed after initialization? then do not hold `BLK_PERM_RESIZE`;
- capacity dynamic? implement the resize path and test it, including cached
size fields;
- 32-bit LBA CDBs cap capacity at 2 TiB with 512-byte sectors; use 16-byte
CDBs when the model must address more;
- test a sparse backend beyond the advertised boundary and confirm the device
rejects or exposes it deliberately;
- record backend format, permissions, and boundary cases in `audit.md`.
## DMA and address validation
- validate guest DMA addresses with QEMU address-space or DMA helpers before
use;
- record descriptor ownership and completion order;
- qtest: program a DMA window, verify guest memory effects, and verify an
invalid address produces the documented error, not a host abort.
## qtest candidates per pattern
| Pattern | Candidate coverage |
| --- | --- |
| Unimplemented block | read/write logs under `-d unimp`; no fake success |
| Trace split | summary counters match guest-visible status |
| IRQ wiring | assert, mask, W1C clear, controller route |
| Multi-instance | two instances stay independent |
| FIFO/queue | fill, drain, overflow, watermark IRQ |
| Reentrancy | trigger during in-flight work, no corruption |
| Block capacity | sparse boundary, permission rejection, resize path |
| DMA | guest-visible memory effects and invalid-address error |
Add only the tests the requested scope needs; remove one only with a recorded
reason.
## From IR conditions and transitions to qtest assertions
When the RS-IR (`register-semantics.ir`) is available, each rule gives the test list: one
narrow assertion per material condition and transition. Name the rule id in the
test comment or evidence note so the model comment, trace event, and test can be
cross-checked without reading the IR.
| IR condition or transition | qtest assertion |
| --- | --- |
| W1C condition (`bit_set` on status with masked enable) | write the trigger, read status, verify clear and line state |
| RO or reserved field (`eq`/`ne`) | write is ignored, read returns the documented value, reset value is checked |
| FIFO status (`fifo_empty`, `fifo_full`, `fifo_level`) | fill and drain to each boundary, read the status bits |
| Timer expiry (`timer_expiry`, `counter_cmp`) | program the timer, advance the virtual clock, check expiry status and IRQ |
| IRQ assert/deassert (`assert_irq`, `deassert_irq`) | intercept the line and verify assert, mask, and W1C clear paths |
| DMA effect (`dma_read`, `dma_write`) | program the DMA, verify guest memory effects and completion |
| Clear-on-read (`clear_on_read`) | read twice and verify the documented one-shot behavior |
| Logging only (`log_unimp`, `logging_only`) | guest access logs under `-d unimp`; never accepted as modeled success |
Traceability rule: keep the rule id in the qtest comment and the evidence path.
A test that cannot name the rule it proves is probably testing an implementation
detail; add the rule to the IR or drop the assertion.
references/register-framework.md
# Register bank framework reference
Read this when designing or reviewing a guest-visible control/status register
bank. The skill keeps the ordered summary; this file holds the decision record,
the checked-out-tree inspection steps, reference families, `.c` ownership, and
known framework limitations.
## Decision gate
Apply these inputs in order and stop at the first decisive one:
1. Policy present in the target QEMU workspace. Treat checked-out `docs/devel/`
pages that govern device modeling (for example `qom.rst`, `memory.rst`,
`secure-coding-practices.rst`, and `style.rst`), a repository-local
`AGENTS.md` or `README`, and subsystem documentation as candidate policy.
Record the exact `path:line` you treated as decisive. A MAINTAINERS entry
names reviewers; it does not decide a modeling framework.
2. The user's explicit compatible framework direction. Record the quote or
request reference and why it is compatible. Conflicting direction is not
decisive.
3. A clear maintained convention in a nearby device of the same subsystem.
Cite `path:line` and prefer recently maintained code.
When none decides, use `RegisterInfo` from the checked-out tree. A nearby
convention is decisive only when it is the **same subsystem**, actively
maintained, and has a comparable **control/status** bank; cite the exact
`path:line` and explain the semantic match. "Most nearby devices use manual
MMIO" is not a decision input.
Manual MMIO for a control/status bank is exceptional. It is permitted only when
the bank has a command, FIFO, DMA, timing, or reentrant structure that
`RegisterInfo` cannot express without distortion. Split hybrid models instead
of choosing manual for the whole window: control/status fields on
`RegisterInfo`, plus explicit `MemoryRegionOps` for the command/data aperture.
Time pressure, round budget, register count, and "every register has side
effects" are not decisive inputs.
A missing, vague, or unsupported framework decision is a review BLOCKER. The
decision must state the bank kind, the decisive input (or "none, default"),
whether the bank is hybrid, and the structural reason when manual MMIO is
selected.
## Decision record for `audit.md`
Record one row per bank, for example:
| Field | Value |
| --- | --- |
| Bank | `<device> <window name/base>` |
| Purpose | `<control/status or data>` |
| Target-workspace policy | `<path:line>` or `none found` |
| User direction | `<quote or request path; compatible?>` |
| Nearby convention | `<file:line of maintained sibling>` |
| Selected framework | `RegisterInfo` or `manual MMIO` |
| Accepted limits | `<access sizes, side effects, reentrancy>` |
| Rationale | `<one or two sentences>` |
| Verification | `<qtests or trace evidence>` |
## From semantic IR to the model
When qemu-register-extraction hands off a validated RS-IR
`.oh-my-qemu/<task-slug>/output/register-semantics.ir`, use it as the model
worklist: each rule names one trigger, condition, and transition to implement and
verify. The rule/operation vocabulary is that skill's `assets/ir-ops.def`
(DEF table with arity and flags); read its semantic IR reference for the grammar
and operand classes. This section maps the DSL vocabulary to the checked-out
QEMU model. Do not generate C from the IR: write the model by hand in the device
`.c` file and keep the IR as the reviewable mapping source.
### Trigger and hook mapping
| IR trigger | `RegisterInfo` access or implementation site |
| --- | --- |
| `register_read` | read access; `post_read` only for guest-visible read side effects |
| `register_write` | write access with mask/reserved-bit handling; `pre_write` filters, `post_write` applies side effects |
| `field_write` | write access with field mask; `pre_write` decodes the field |
| `reset` | device reset hook or `register_reset` |
| `timer_expiry` | timer callback, not a register access |
| `irq_in` | `qemu_irq` input handler, not `RegisterInfo` |
| `dma_complete` | DMA completion callback or bottom half |
| `command` | manual `MemoryRegionOps` callback or a helper entry point |
| `enable_change` | `pre_write`/`post_write` on the enable register |
| `guest_action` | branch in the manual MMIO callback or device helper |
### Operation mapping
| IR operation | Side-effect site |
| --- | --- |
| `assign_field`, `set_bits`, `clear_bits`, `toggle_bits`, `inc_field`, `dec_field` | post-write hook or backing-storage update |
| `fifo_push`, `fifo_pop` | explicit `MemoryRegionOps` data aperture plus FIFO state |
| `assert_irq`, `deassert_irq` | post-write or timer/DMA callback; update status first |
| `schedule_timer`, `cancel_timer` | timer API from the checked-out tree |
| `dma_read`, `dma_write` | address-space or DMA helper |
| `raise_exception` | documented device error path |
| `emit_event`, `log_unimp` | trace event or `LOG_UNIMP`; logging only, not modeled behavior |
| `set_state` | device state field, included in `VMState` when migratable |
| `clear_on_read` | post-read hook, guest-visible only |
A rule whose only side effect is `logging_only` provides visibility, not
behavior; do not count it as modeled success or as qtest coverage.
### Traceability
Preserve each rule id when implementing: use it in a comment beside the
`RegisterAccessInfo` entry or callback, in the trace event name or payload, and
in the qtest that asserts the rule. A model change that has no matching rule id
either needs a new IR rule or is implementation detail outside the contract. When
the IR and the code disagree, fix one deliberately and record the change in
`audit.md`.
## Inspect the checked-out tree
Do not use remembered API signatures. In the tree under test:
- `include/hw/core/register.h` for the current `RegisterAccessInfo`,
`RegisterInfo`, `RegisterInfoArray`, and `register_init_block*` API;
- `include/hw/core/registerfields.h` for the register and field macros;
- `hw/core/register.c` for ownership and reset behavior;
- search the tree by symbol for `RegisterAccessInfo`, `register_init_block8`,
`register_init_block32`, `register_init_block64`, `register_read_memory`,
`register_write_memory`, `register_reset`, and `register_array_get_owner`.
Inspect same-subsystem examples before generic ones. Reference families to
start from:
- simple register bank with callbacks and VMState:
`hw/dma/xlnx-zynq-devcfg.c`;
- IRQ/status register side effects: `hw/intc/xlnx-zynqmp-ipi.c`;
- wrapped read/write handlers that still delegate to `RegisterInfo`:
`hw/misc/xlnx-versal-trng.c`;
- broader banks: search `hw/misc`, `hw/dma`, `hw/intc`, `hw/nvram`, and
`hw/rtc` for `register_init_block32`.
If a named path is absent in another QEMU version, re-search by symbol instead
of assuming the old path. Record the version or commit inspected.
## `.c`-file ownership
Keep the complete register definition in the device `.c` file:
- register offsets and field definitions from the registerfields macros;
- backing storage sized from the register map;
- the matching `RegisterInfo` array and `RegisterAccessInfo` table when selected;
- manual read/write callbacks and `MemoryRegionOps` when selected;
- register-local read, write, and reset hooks;
- reset logic for the selected framework;
- `VMState` for guest-visible register storage and other migratable state.
Do not put register offsets, field macros, access tables, backing storage, or
register semantics in a header. A header may expose only non-register-layout
declarations genuinely required outside the device translation unit, such as a
public QOM type or a cross-unit helper prototype.
When using `RegisterInfo`, use its hooks deliberately:
- pre-write hooks filter or transform a write before storage;
- post-write hooks apply IRQ, status, timer, or DMA side effects after storage;
- post-read hooks are only for guest-visible read side effects;
- register reset can run write hooks, so callbacks must be reset-safe.
## Known framework and device limitations
Verify each in the checked-out tree before relying on it.
- `RegisterInfoArray` leaf ownership: `register_init_block*` creates an
internal `TYPE_REGISTER_ARRAY` child and initializes the access
`MemoryRegion` with that object as owner. `MemoryRegion.dev` therefore is not
the `DeviceState`, so the generic memory reentrancy guard in
`system/memory.c` (which checks `mr->dev`) does not cover these callbacks.
Guard reentrancy explicitly in the callback when it can re-enter.
- FIFO, queue, and other streaming data apertures need explicit
`MemoryRegionOps`; `RegisterInfo` tables fit indexed control/status storage.
- Block-device permissions: `BLK_PERM_ALL` includes `BLK_PERM_RESIZE`
(`include/block/block-common.h`). If capacity is fixed, exclude
`BLK_PERM_RESIZE` from the permissions you request so an online resize cannot
silently diverge from cached state.
- Cached capacity: many devices cache capacity at initialization, reset, or on
a capacity query (a SCSI disk's `max_lba` is refreshed by READ CAPACITY and
reset). If the model can hold stale capacity, decide whether resize is
supported and reject it when it is not; cover the boundary with a qtest.
- 32-bit LBA: the LBA field in 10-byte and 12-byte READ/WRITE CDBs is 32 bits
(`scsi_cmd_lba` masks to 32 bits in `scsi/utils.c`). With 512-byte sectors
that caps usable capacity at 2 TiB; 16-byte CDBs carry the 64-bit LBA. Claim
only the capacity the CDB set can address in the tree under test.
## When manual MMIO is the right choice
Prefer manual `MemoryRegionOps` only for streaming, command, configuration
view, or timing apertures whose access semantics the register framework cannot
express without distortion, or where explicit reentrancy control is required.
Record the **structural reason** in the decision record: which behavior cannot
be expressed by `RegisterAccessInfo` hooks, and which limits are accepted.
For a mixed block, keep control/status registers on `RegisterInfo` and put the
command/FIFO/data aperture in manual `MemoryRegionOps`. A whole-window manual
choice needs an explicit reason why splitting is not possible. "Neighboring
devices are manual", "the register bank is large", and "we are in a hurry" are
not structural reasons.
scripts/check-framework-decision.sh
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 Process Mission
# SPDX-License-Identifier: MIT
#
# check-framework-decision.sh - read-only check of a register framework decision.
# Portable bash 3.2; no writes.
# Exit: 0 pass, 1 check failure, 2 usage.
set -u
usage() {
cat <<'EOF'
usage: check-framework-decision.sh <decision.md>
Checks a qemu-peripheral-modeling framework decision record:
bank kind, implementation, decisive input, rationale, structural reason,
hybrid split, accepted limits, and review gate.
Manual/hybrid control/status banks must name a structural command/FIFO/DMA/
timing/reentrancy reason; placeholders and "nearby devices are manual" are
rejected.
exit codes: 0 pass, 1 check failed, 2 usage
EOF
}
log() { printf '%s\n' "$*"; }
pass() { log "PASS $*"; }
fail() { log "FAIL $*"; return 1; }
field() {
awk -v key="- $2:" 'index($0, key) == 1 { line = $0; sub(/^- [^:]+:[[:space:]]*/, "", line); print line; exit }' "$1"
}
placeholder() {
_ph_value="$(printf '%s' "$1" | tr -d '\140' | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
case "$_ph_value" in
''|'<'*|*'<placeholder>'*) return 0 ;;
*) return 1 ;;
esac
}
main() {
if [ "$#" -ne 1 ]; then usage >&2; return 2; fi
_file="$1"
if [ ! -f "$_file" ]; then fail "decision file not found: $_file"; return 1; fi
_status=0
_kind="$(field "$_file" 'Bank kind')"
_impl="$(field "$_file" 'Implementation')"
_decisive="$(field "$_file" 'Decisive input')"
_rationale="$(field "$_file" 'Rationale')"
_structural="$(field "$_file" 'Structural reason (required for manual/hybrid)')"
_hybrid="$(field "$_file" 'Hybrid split')"
_limits="$(field "$_file" 'Accepted limits')"
_review="$(field "$_file" 'Review gate')"
case "$_kind" in
control-status|data-command|hybrid) pass "bank kind: $_kind" ;;
*) fail "bank kind must be control-status, data-command, or hybrid"; _status=1 ;;
esac
case "$_impl" in
RegisterInfo|'manual MMIO'|hybrid) pass "implementation: $_impl" ;;
*) fail "implementation must be RegisterInfo, manual MMIO, or hybrid"; _status=1 ;;
esac
if placeholder "$_decisive"; then fail "decisive input is missing or a placeholder"; _status=1
else pass "decisive input recorded"; fi
if placeholder "$_rationale"; then fail "rationale is missing or a placeholder"; _status=1
else pass "rationale recorded"; fi
if placeholder "$_limits"; then fail "accepted limits are missing or a placeholder"; _status=1
else pass "accepted limits recorded"; fi
if placeholder "$_review"; then fail "review gate is missing or a placeholder"; _status=1
else pass "review gate recorded"; fi
case "$_impl" in
RegisterInfo)
pass "RegisterInfo selected; structural reason not required"
;;
'manual MMIO'|hybrid)
if placeholder "$_structural"; then
fail "manual/hybrid choice needs a structural reason"; _status=1
elif ! printf '%s' "$_structural" | grep -Eiq 'fifo|command|dma|tim(e|er|ing)|reentr|stream|aperture|queue|doorbell|view'; then
fail "structural reason must name a command/FIFO/DMA/timing/reentrancy structure, not nearby convention or schedule"; _status=1
else
pass "structural reason names a framework limit"
fi
if [ "$_impl" = hybrid ] && placeholder "$_hybrid"; then
fail "hybrid choice needs a RegisterInfo/manual split"; _status=1
elif [ "$_impl" = hybrid ]; then
pass "hybrid split recorded"
fi
;;
esac
if [ "$_status" -eq 0 ]; then
log "RESULT PASS: $_file"
return 0
fi
log "RESULT FAIL: $_file"
return 1
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
exit $?
fi
SKILL.md
---
# SPDX-FileCopyrightText: Copyright (c) 2026 Process Mission
# SPDX-License-Identifier: MIT
name: qemu-peripheral-modeling
description: Use for QEMU peripheral, accelerator, MMIO, qdev, or SysBusDevice modeling with register contracts, an explicit register framework decision, and qtest-backed verification.
---
# QEMU Peripheral Modeling
## Audit workflow
For a non-trivial task that writes to the workspace, use only the needed entries under
`.oh-my-qemu/<task-slug>/`: `audit.md` (baseline, plan, decisions, evidence, verification, and
gaps), `commands.md` (redacted commands, working directories, and results), `logs/` (decisive
build, test, and runtime output), `scripts/` (probes, parsers, and harnesses), and `output/`
(generated deliverables, third-party dependencies, and non-QEMU binaries). Keep QEMU's own build
tree at source-root `builds/build-<target>/`, never an unqualified `build/`. In a Git worktree,
add `.agents/`, `.oh-my-qemu/`, and `builds/` to the file from
`git rev-parse --git-path info/exclude` before writing artifacts or configuring QEMU; preserve
existing entries and avoid duplicates. Never stage or commit these paths; stage source only when
the user explicitly requests that separate action. At handoff, reconcile `git status --short`
with the baseline and report the task directory, build directory, source changes, pre-existing
changes, and unresolved gaps.
## Workflow
1. Freeze the register, IRQ, DMA, reset, migration, and allowed-path contract.
2. When facts come from drivers, datasheets, or regfiles, extract a source-cited
register contract first; do not fill gaps from memory.
3. Work in small source-change rounds; record paths, verification, and gaps.
4. Build the affected target and map each modeled behavior to focused qtests.
5. Use traces or workload evidence for integration claims and state what remains unproven.
## QEMU upstream boundary
QEMU's official GitLab and mailing lists are upstream channels. A patch becomes an upstream
contribution when it is sent to the maintainers selected through `MAINTAINERS`. Do not prepare or
send agent-generated code, documentation, commit messages, or patches for upstream submission; see
the checked-out tree's `docs/devel/code-provenance.rst`. Research, debugging, analysis, local
experiments, verification, and workflow guidance remain allowed. Local branches, commits, patch
files, pushes, and pull requests are not by themselves upstream contributions; perform those Git
actions only when requested.
## Device modeling contract
A real hardware block should be a first-class device: board code wires topology,
device code owns behavior. Define in `audit.md` before modeling:
- MMIO windows, endianness, and accepted access widths;
- reset, masks, W1C, aliases, reserved bits, and cross-register flows;
- IRQ outputs and level/edge semantics; clock, reset, DMA, and bus dependencies;
migration-visible state versus local caches;
- the unimplemented boundary and its visibility mechanism; reference evidence:
datasheet, SDK driver, firmware trace, sibling device.
When the source of truth is external to QEMU, require the source-cited register
contract; never model from memory or a partial driver skim. Once the RS-IR
validates, read [the translation reference](references/ir-to-registerinfo.md)
before writing code and [the field lessons](references/field-lessons.md) at plan
and review time.
## QEMU object shape
Default shape when the framework decision and nearby code do not select
otherwise: `SysBusDevice` or the subsystem base class; one owned `MemoryRegion`
per window plus explicit `MemoryRegionOps` for a FIFO or data aperture; explicit
`qemu_irq` outputs; local-style realize and reset hooks; `VMStateDescription` for
guest-visible state; named properties only for real variation; do not put
register side effects in board files.
## Register bank framework decision
Before designing a guest-visible control/status register bank, apply in order
and stop at the first decisive input:
1. policy present in the target QEMU workspace;
2. the user's explicit compatible framework direction;
3. a clear maintained convention in a nearby device of the same subsystem.
`RegisterInfo` is the default for every guest-visible control/status bank.
A nearby manual convention alone is not decisive: it must be the same
subsystem, actively maintained, and semantically comparable, with a cited
`file:line`. Manual MMIO for a control/status bank is exceptional and needs a
structural reason (command, FIFO, DMA, timing, or reentrancy behavior that
`RegisterInfo` cannot express without distortion); use a hybrid split
(control/status on RegisterInfo, aperture on MemoryRegionOps) rather than
manual for the whole window. Time/budget/register count and "most siblings are
manual" are invalid reasons.
Record bank kind, inputs, choice, hybrid split, rationale, structural reason,
accepted limits, and review gate in
`.oh-my-qemu/<task-slug>/output/framework-decision.md` with
[the framework decision template](assets/framework-decision-template.md); the
decision schema, API inspection steps, reference families, and `.c` placement
rules are in [the register framework reference](references/register-framework.md).
A missing, vague, or unsupported decision is a review BLOCKER. Keep register
layout, storage, tables, and hooks in the device `.c` file. Run
[check-framework-decision.sh](scripts/check-framework-decision.sh) on the
record before review; it rejects placeholders and manual rationales that name
nearby convention or schedule instead of a structural limit.
When qemu-register-extraction hands off the RS-IR `output/register-semantics.ir`,
read the framework reference for the trigger/hook/operation mapping and preserve
rule ids in comments, trace names, and qtests. The vocabulary is that skill's
`assets/ir-ops.def`; do not invent operations outside it.
## Source comment hygiene
Keep the file-header comment minimal: SPDX/copyright plus at most a one-line
hardware identity when the filename is insufficient. Do **not** put design or
modeling rationale in source comments: no implementation strategy, framework
choice or defense, "modeled contract" pointer, neighboring-device convention,
code walkthrough, review/audit decision, or justification ("no policy in this
tree", "neighboring devices use manual style"). Those belong in
`audit.md` / `output/framework-decision.md`.
Comment only **key code** where the behavior is non-obvious, and keep each
comment short: one to three lines, with at most one brief reference to a public
source (datasheet/TRM section, vendor HAL path, checked-in QEMU doc, or stable
public reference). Do not expand the reference into a contract summary, register
table, design essay, or multi-paragraph explanation; put that material in
`audit.md` / `output/`. Never write local task artifacts or process
information into source: `.oh-my-qemu/...`, `output/...`, `audit.md`, evidence
paths, round/milestone/AC status, review/finding notes, commands, progress, or
absolute host paths. The source must stand on its own and remain reviewable
outside the local workspace.
When `qemu-workflow` is installed, run
`bash <qemu-workflow-dir>/scripts/gates.sh source-comments <changed-source-files...>`
before review. A source diff that leaks task paths or design rationale into
comments is a review blocker.
## Unimplemented visibility
- Every intentionally unimplemented guest-visible path must emit a trace event
or `qemu_log_mask(LOG_UNIMP, ...)`; invalid accesses use `LOG_GUEST_ERROR` so
they appear under `-d guest_errors`. Keep both mechanisms in the model and
record the reachable addresses in `audit.md`.
- A diagnostic sink that logs and returns zero or fake status is a placeholder,
not modeled behavior; never count it as PASS evidence or qtest coverage.
## MMIO rules
- Implement every control/status register through the selected framework; use
constants or macros for offsets, masks, shifts, reset values, and IDs.
- Keep callbacks allocation-free and long-running work out of them; use timers,
bottom halves, workers, or staged execution.
- Represent RO, W1C, reserved, clear-on-read, and unimplemented bits without
duplicating semantics across callbacks; update status before IRQ changes.
- Validate guest DMA addresses with QEMU address-space or DMA helpers; never
edit generated files under `builds/build-<target>/`.
- With `RegisterInfo`, use pre-write hooks to filter or transform writes,
post-write hooks for IRQ/status/timer/DMA effects, and post-read hooks only
for guest-visible effects; callbacks must be reset-safe.
Known limits (leaf `MemoryRegion` ownership and reentrancy, FIFO apertures,
block permissions and cached capacity, 32-bit LBA) are summarized in
[the register framework reference](references/register-framework.md).
## qtest expectations
Every material peripheral change needs narrow qtest coverage: reset values;
read/write masks, reserved bits, and unsupported widths; W1C and status-clear
behavior; IRQ assert and deassert; framework reset and side-effect callbacks;
unimplemented-path logging; timer virtual-clock behavior; DMA effects. Map each
semantic-IR condition and transition to one assertion; see
[modeling patterns](references/patterns.md).
## Accelerator addendum
For command-stream or accelerator blocks: separate descriptor parsing from
execution; record command ranges, DMA windows, and output buffers; validate
skipped or unknown counts; correlate trace milestones with guest-visible
output; record image hashes in `audit.md`.
## Anti-patterns
- Generic scratch register banks for real devices, or a framework choice
without a complete decision record.
- Manual MMIO for a control/status bank justified only by neighboring devices,
schedule, or register count; a hybrid split was possible but skipped.
- File-header implementation/framework rationale or defenses ("no policy",
"neighboring devices use manual style", "modeled contract"); decisions belong
in `audit.md` / `output/framework-decision.md`.
- Fake success paths that only make firmware boot; board behavior hidden in
MMIO callbacks.
- Register definitions, storage, tables, or hooks in a header; trace-count-only
success claims.
## Upstream references
- QEMU code provenance: `docs/devel/code-provenance.rst`.
- QOM/qdev: `docs/devel/qom.rst`; tracing: `docs/devel/tracing.rst`.
- QEMU qtest: `docs/devel/testing/qtest.rst`.
- RegisterInfo API: `include/hw/core/register.h` and
`include/hw/core/registerfields.h`.
Paths can differ between QEMU versions; locate them in the checked-out tree.