Keeping the documentation correct
This page is hw/femu/docs/development/docs-maintenance.md at FEMU 39a55eeb6 (2026-10-02), licensed GPL-2.0-or-later. Send corrections to the FEMU repository.
Four checks keep the documentation in step with the code. CI runs all of them, and so does
make -C hw/femu/tests check-docs QEMU=$PWD/build/qemu-system-x86_64 \
QOS_TEST=$PWD/build/tests/qtest/qos-test
| Check | Script | Fails when |
|---|---|---|
| Property reference | hw/femu/scripts/gen-property-docs.py --check | a property has no description, or reference/properties.md or reference/runtime-properties.md differs from the binary |
| Mode table | hw/femu/scripts/gen-mode-table.py --check | a mode table differs from modes.py, or modes.py disagrees with the tree |
| Links | hw/femu/scripts/check-doc-links.py | a relative link or heading anchor does not exist |
| Examples | hw/femu/scripts/check-doc-examples.py | a code block is not tagged, or a tagged example does not work |
Only two reference pages are generated: reference/properties.md and
reference/runtime-properties.md, from the built binary plus
reference/property-topics.py and reference/environment.inc.md. The
others, reference/log-pages-and-counters.md and reference/scripts.md,
are written by hand and no check compares them with the code, so update
them in the same commit as the change they describe. A user-visible change
also needs an entry in CHANGELOG.md.
Per-mode facts: modes.py
hw/femu/docs/modes.py holds one entry per mode or feature:
how to turn it on, a minimal example, the guest kernel and tools it needs,
host requirements, its launcher and the page that documents it. Change a
fact there, never in a table, then run
python3 hw/femu/scripts/gen-mode-table.py
The script rewrites the text between and in README.md and in any page under
hw/femu/docs/ that has the two markers. To put the table on a new page, add
the markers and run the script. README.md must keep its markers.
--check also compares modes.py with the code. Each symbol must have the
stated femu_mode value in hw/femu/nvme.h, every mode in that enum must
have an entry, each launcher must exist in hw/femu/scripts/, and each guide
link must resolve. Each entry's example is run by the example check below,
so the "Checked" column states what CI really does with it.
Code blocks in the documentation
Every fenced code block in README.md and hw/femu/docs/** falls into one of
these classes:
| Block | Meaning | Checked |
|---|---|---|
| preceded by `` | a QEMU command line, -device options or a run-*.sh launcher | run under qtest |
| preceded by `` | a command that cannot run in CI | the reason must have at least three words |
```sh | shell commands that do not start FEMU: git, apt, configure, commands inside the guest | must not start FEMU |
```bash, ```shell, ```console, ```zsh | not allowed untagged | fails |
| any other info string, or none | output, configuration files, code | must not start FEMU |
A block "starts FEMU" when, outside shell comments, it runs
qemu-system-* as a command, passes -device femu..., or names a run-*.sh
launcher other than run-guest-ssh.sh. The tag comment goes on the line right before
the fence. GitHub does not show it and still highlights the block.
Writing an example
-device femu,devsz_mb=1024,femu_mode=3
- One example per block, or several separated by blank lines; each is named
NAME,NAME.2, and so on in the report. ...stands for options left out and is dropped, so-device femu,...,femu_mode=1,read_reclaim_limit=100000is tested as-device femu,femu_mode=1,read_reclaim_limit=100000.- A launcher such as
./run-zns.sh(with optionalVAR=valuesettings in front) is run with stand-ins forsudoand QEMU that record the command line it builds, so the launcher's own options are what gets tested.OSIMGFis replaced by an empty file, and lines that runrun-guest-ssh.share skipped. - A list of
key=valuelines can be tested as one device: ``. io: rw,io: kv,io: identifyorio: noneoverrides what the I/O stage does; by default it follows the mode, frommodes.py.allow-warning: TEXTallows one expected warning, for exampleallow-warning: FEMU CXL DER unavailableforder=cylon, which falls back to MMIO without a Cylon host.
What the example check does
- QEMU starts with every
-deviceof the example, in its order, plus the memory backends and-machineoptions (q35 when none is given), under-accel qtest -S. Guest disks and NICs are realized too, so one the machine cannot plug where it lands fails here: on acxl=onmachine avirtio-net-pciorvirtio-blk-pciwithoutbus=pcie.0fails with "Only PCI/PCIe bridges can be plugged into pxb-cxl". Their backends are replaced by stand-ins that touch nothing on the host: each-driveby anull-coblock device with the sameidandif, each-netdevand-net userby user networking with no forwarded ports (so the QEMU under test needs--enable-slirp). The other options are dropped, among them-enable-kvm,-cpu,-smp,-m,-numa,-nameand-qmp. QMP must show everyfemu,femu-subsysandfemu-cxl-ssdcreated, andquery-pcimust list eachfemuas an NVMe controller. Anything on stderr fails the example, except FEMU's[FEMU] Log:lines, the notice that the memory backend could not be pinned (the check lowersRLIMIT_MEMLOCKso that it never pins), and allowed warnings. Test-only properties (x-...) are refused. - For an example with an NVMe controller, the
doc-examplescase inhw/femu/tests/qtest/femu-test.cstarts the FEMU devices, the CXL topology and the memory backends from step 1, without the guest's disks and NICs, enables each controller, sends Identify, and writes and reads back one block of namespace 1 (stores and retrieves one value in KV mode). Open-Channel controllers stop after Identify. A controller whose namespace 1 is not attached stops after Identify, but at least one controller in the example must move data.
The check does not boot a guest. Guest kernel versions, guest tools and anything done inside the guest are outside what it can see.
--self-test runs planted mistakes (an untagged block, a misspelled
property, femu_mode=9, a warning-only der=cylon, an I/O-only failure, a
test-only property, a virtio-net-pci without bus= on a cxl=on machine,
a guest disk naming a drive that does not exist) and requires each to fail
with the expected message, and two good examples, one with guest devices on
a CXL machine, to pass, so
a check that stopped catching them fails rather than passing everything.
python3 hw/femu/scripts/check-doc-examples.py --lint # tags only
python3 hw/femu/scripts/check-doc-examples.py --list # what would run
python3 hw/femu/scripts/check-doc-examples.py \
--qemu build/qemu-system-x86_64 --qos-test build/tests/qtest/qos-test \
--only quick-start-bbssd