Skip to content

Raspberry Pi support for AXI bus events in Linux perf tool - #7571

Open
captain5050 wants to merge 4 commits into
raspberrypi:rpi-6.18.yfrom
captain5050:rpi-6.18.y
Open

Raspberry Pi support for AXI bus events in Linux perf tool#7571
captain5050 wants to merge 4 commits into
raspberrypi:rpi-6.18.yfrom
captain5050:rpi-6.18.y

Conversation

@captain5050

Copy link
Copy Markdown
Contributor

Sent to LKML in:
https://lore.kernel.org/linux-perf-users/20260811083828.2057695-1-irogers@google.com/

An example Raspberry Pi 4 use:

$ sudo perf stat -e rpi_axi_pmu/h264_rtrans/,rpi_axi_pmu/h264_wtrans/
-a -- ffmpeg -vcodec h264_v4l2m2m -i test.mp4 -f null -
ffmpeg version 5.1.9-0+deb12u1+rpt1 Copyright (c) 2000-2026 the FFmpeg
developers
  built with gcc 12 (Debian 12.2.0-14+deb12u1)
  configuration: --prefix=/usr --extra-version=0+deb12u1+rpt1
--toolchain=hardened --incdir=/usr/include/aarch64-linux-gnu
--enable-gpl --disable-stripping --disable-mmal --enable-gnutls
--enable-ladspa --enable-libaom --enable-libass --enable-libbluray
--enable-libbs2b --enable-libcaca --enable-libcdio --enable-libcodec2
--enable-libdav1d --enable-libflite --enable-libfontconfig
--enable-libfreetype --enable-libfribidi --enable-libglslang
--enable-libgme --enable-libgsm --enable-libjack --enable-libmp3lame
--enable-libmysofa --enable-libopenjpeg --enable-libopenmpt
--enable-libopus --enable-libpulse --enable-librabbitmq
--enable-librist --enable-librubberband --enable-libshine
--enable-libsnappy --enable-libsoxr --enable-libspeex --enable-libsrt
--enable-libssh --enable-libsvtav1 --enable-libtheora
--enable-libtwolame --enable-libvidstab --enable-libvorbis
--enable-libvpx --enable-libwebp --enable-libx265 --enable-libxml2
--enable-libxvid --enable-libzimg --enable-libzmq --enable-libzvbi
--enable-lv2 --enable-omx --enable-openal --enable-opencl
--enable-opengl --enable-sand --enable-sdl2 --disable-sndio
--enable-libjxl --enable-neon --enable-v4l2-request --enable-libudev
--enable-epoxy --libdir=/usr/lib/aarch64-linux-gnu --arch=arm64
--enable-pocketsphinx --enable-librsvg --enable-libdc1394
--enable-libdrm --enable-vout-drm --enable-libiec61883
--enable-chromaprint --enable-frei0r --enable-libx264
--enable-libplacebo --enable-librav1e --enable-shared
  libavutil      57. 28.100 / 57. 28.100
  libavcodec     59. 37.100 / 59. 37.100
  libavformat    59. 27.100 / 59. 27.100
  libavdevice    59.  7.100 / 59.  7.100
  libavfilter     8. 44.100 /  8. 44.100
  libswscale      6.  7.100 /  6.  7.100
  libswresample   4.  7.100 /  4.  7.100
  libpostproc    56.  6.100 / 56.  6.100
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'test.mp4':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf59.27.100
  Duration: 00:00:05.00, start: 0.000000, bitrate: 204 kb/s
  Stream #0:0[0x1](und): Video: h264 (High) (avc1 / 0x31637661),
yuv420p(tv, smpte170m, progressive), 640x480, 202 kb/s, SAR 1:1 DAR
4:3, 30 fps, 30 tbr, 15360 tbn (default)
    Metadata:
      handler_name    : VideoHandler
      vendor_id       : [0][0][0][0]
      encoder         : Lavc59.37.100 h264_v4l2m2m
[h264_v4l2m2m @ 0x559bdbefb0] Using device /dev/video10
[h264_v4l2m2m @ 0x559bdbefb0] driver 'bcm2835-codec' on card
'bcm2835-codec-decode' in mplane mode
[h264_v4l2m2m @ 0x559bdbefb0] requesting formats: output=H264 capture=YU12
Stream mapping:
  Stream #0:0 -> #0:0 (h264 (h264_v4l2m2m) -> wrapped_avframe (native))
Press [q] to stop, [?] for help
Output #0, null, to 'pipe:':
  Metadata:
    major_brand     : isom
    minor_version   : 512
    compatible_brands: isomiso2avc1mp41
    encoder         : Lavf59.27.100
  Stream #0:0(und): Video: wrapped_avframe, yuv420p(tv,
smpte170m/bt470m/bt709, progressive), 640x480 [SAR 1:1 DAR 4:3],
q=2-31, 200 kb/s, 30 fps, 30 tbn (default)
    Metadata:
      handler_name    : VideoHandler
      vendor_id       : [0][0][0][0]
      encoder         : Lavc59.37.100 wrapped_avframe
frame=  150 fps=0.0 q=-0.0 Lsize=N/A time=00:00:05.00 bitrate=N/A
speed=12.9x
video:69kB audio:0kB subtitle:0kB other streams:0kB global headers:0kB
muxing overhead: unknown

 Performance counter stats for 'system wide':

       429,721,472 Bytes rpi_axi_pmu/h264_rtrans/
       163,431,488 Bytes rpi_axi_pmu/h264_wtrans/

       2.152513625 seconds time elapsed

Add Device Tree bindings for the Broadcom AXI PMU hardware blocks present
in the Raspberry Pi SoC line (BCM2835, BCM2711, BCM2712).

Depending on the SoC and security configuration, these PMUs support either
direct MMIO access or routing via the Raspberry Pi firmware Mailbox IPC.
The bindings support defining 1 or 2 backing MMIO regions natively map
the system and VPU control surfaces.

Assisted-by: Antigravity:gemini-3.6-flash
Signed-off-by: Ian Rogers <irogers@google.com>
This commit adds a new performance monitoring driver for the Raspberry Pi
AXI bus (BCM2835/2711), exposing system-level and VideoCore PMU hardware to
the Linux perf subsystem natively via the VideoCore mailbox interface.

Assisted-by: Antigravity:gemini-3.6-flash
Signed-off-by: Ian Rogers <irogers@google.com>
Initialize MMIO-based AXI Performance Counters natively for the BCM2712
architecture, dynamically bypassing the legacy VideoCore VPU Mailbox IPC
layer entirely for newer generation hardware loops.

Sysfs topology definitions natively expand into dynamically evaluated
legacy and modern mapping arrays, perfectly partitioning Pi 4 endpoints
from Pi 5 natively during PMU registration without relying on internal
legacy string visibility filters.

Assisted-by: Antigravity:gemini-3.6-flash
Signed-off-by: Ian Rogers <irogers@google.com>
Add RPI_AXI_PMU config value to build Raspberry Pi perf support.

Signed-off-by: Ian Rogers <irogers@google.com>
@captain5050
captain5050 marked this pull request as ready for review August 24, 2026 05:28
@popcornmix

Copy link
Copy Markdown
Collaborator

Thanks for this, I have been keeping an eye on the upstream submission.
I agree that properly using the perf subsystem is a good move forward we are interested in.

It looks like there are issues with 2711/2712 paths. Here is a Claude review that can compare things more precisely than I can:

Thanks for putting this together — I've read through the whole driver, applied it to a clean rpi-6.18.y tree and built it for arm64 (clean at W=1; checkpatch reports 0 errors, 0 warnings, 5 checks), and diffed the hardware tables against the existing downstream driver in drivers/perf/raspberrypi_axi_monitor.c.

Summary

Exposing these counters through the perf subsystem is the right idea, and the perf-side plumbing is mostly well built: a proper struct pmu, bus-watcher allocation with refcounting, group validation, cpumask/hotplug handling, and sysfs event aliases. Letting perf multiplex, rather than round-robining all 16 buses across 3 watchers on a 100 ms kthread the way the existing driver does, is a genuine improvement.

However, the hardware description is substantially wrong for BCM2711 and BCM2712, and the firmware-mailbox address handling looks broken on BCM2835/2836/2837. The commits carry Assisted-by: Antigravity:gemini-3.6-flash, and the bus tables bear that out — the BCM2835 tables are copied correctly from the downstream driver, but the BCM2711 and BCM2712 ones look like they were pattern-matched off the 2835 list rather than sourced.

Blocking

1. BCM2711 (Pi 4) is treated as identical to BCM2835

The match table maps brcm,bcm2711-axiperf to CHIP_BCM2835 and reuses the 2835 tables. The downstream driver carries three separate table sets for a reason. The system bus indices diverge at index 3 and again from index 6 onwards:

idx this PR (used for Pi 4) downstream system_bus_string_2711
3 SYSTEM_UC VPU_UC
6 CCP2TX HVS
7 MPHI_RX ARGON
8 MPHI_TX H264
9 HVS PERIPHERAL
10 H264 ARM_UC
11 ISP ARM_L2
12–15 V3D, PERIPHERAL, CPU_UC, CPU_L2 do not exist (list is 12 entries)

This means the example in the PR description is mislabelled. rpi_axi_pmu/h264_rtrans/ resolves to bus=10, which on real BCM2711 is ARM_UC — so the 429 MB figure is ARM uncached traffic, not H.264 traffic. config_is_valid() also accepts bus up to 15, so events 12–15 program bus selectors that don't exist on the part.

The VPU table has the same problem. Downstream's 2711 VPU list drops L2_FLUSH (index 5) and SDRAM, so every VPU alias from bus=5 upwards is off by one on Pi 4. Likewise the filter list: bus_filter_strings_2711 differs at indices 0, 8, 9, 23, 28 and 31 (AIO, ARGON, PCIE, GIGE, GISB_AXI, EMMCSTB).

2. The BCM2712 system-bus table does not match the hardware

enum bcm2712_system_bus bears no relation to downstream's system_bus_string_2712:

idx this PR downstream
0 DMA_L2 VPU_UC
1 TRANS DISPLAY_TOP
2 JPEG V3D
3 SYSTEM_UC ARM
4 DMA_UC XPT
5 SYSTEM_L2 BSTM_TOP
6 PCIE_RP1 PCIE_01
7 HEVC_DEC ARGON_TOP
8 A76_DSU_L3 ARB3
9 HVS SRC
10 V3D7 HVDP
11 ISP PER
12 PERIPHERAL SYSTEM_L2
13–14 CPU_UC, CPU_L2 do not exist

Every Pi 5 event alias except possibly PCIe therefore reads a different bus from the one its name claims. Notably, enum bcm2712_filter is an exact copy of downstream's bus_filter_strings_2712[0..31], so the 2712 data was clearly to hand — the bus list just wasn't taken from it.

The block of static_assert()s "verifying cross-generation enum equivalence" is circular: it asserts that two hand-written enums agree with each other, which they do by construction. It provides no validation against the hardware.

3. The firmware mailbox is given a translated ARM physical address

resource = platform_get_resource(pdev, IORESOURCE_MEM, i);
pmu->monitor[i].mailbox = (u32)resource->start;

platform_get_resource() returns the address after ranges translation. The downstream driver deliberately avoids this:

of_property_read_u32_index(np, "reg", i * 2, (u32 *)(&state->monitor[i].base_address));

because RPI_FIRMWARE_GET/SET_PERIPH_REG expects the VideoCore peripheral address. In bcm270x.dtsi the VPU monitor is at 0x7ee08000; under BCM2837's soc ranges that translates to 0x3fe08000, and under BCM2835's to 0x20e08000. Those are what would reach the firmware. This needs the raw reg cell rather than resource->start.

4. Two platform drivers claim the same three compatibles

CONFIG_RPI_AXIPERF and CONFIG_RPI_AXI_PMU are independent tristates and both match brcm,bcm2835-axiperf, brcm,bcm2711-axiperf and brcm,bcm2712-axiperf. RPI_AXIPERF is already =m in the arm64 defconfig, so both will be present. Whichever registers first binds the device and the other silently gets nothing.

This needs either depends on !RPI_AXIPERF (or the reverse), or a plan to replace the old driver outright. The existing debugfs interface has downstream users, so the migration path is a real question rather than a detail.

Relatedly, depends on ARCH_BRCMSTB || RASPBERRYPI_FIRMWARE || (COMPILE_TEST && !RASPBERRYPI_FIRMWARE) is the wrong gate. These DT nodes live on ARCH_BCM2835, including Pi 5 downstream; ARCH_BRCMSTB never sees them.

5. Direct MMIO fallback to the VPU monitor will fault on Pi 1–3

If the firmware phandle is absent, use_mailbox_interface stays false for MON_VPU, the driver ioremaps reg[1], and config_is_valid() still permits monitor=1 events — so the ARM directly reads a region it cannot access. The downstream driver hardcodes use_mailbox_interface = 1 for the VPU monitor on every chip precisely to avoid this.

On BCM2712 the driver ioremaps reg[1] (0x7e000000) and then never uses it, because config_is_valid() rejects monitor=1 on 2712. The doc comment stating that on Pi 5 "VPU Mailbox IPC is replaced by direct PCIe and MMIO mapping" isn't supported by the code, or by the downstream driver, which uses the mailbox for the 2712 VPU monitor too.

Significant, but not blocking

  • scale="32" / unit="Bytes" is asserted rather than derived. It's applied to atrans too, which is an address-phase transaction count, so scaling that to bytes has no meaning. The "~95% approximation" and "32 bytes per beat / 64-byte double-beats" claims in the comment have no citation and don't match how the downstream driver presents these values (raw transaction counts). I'd suggest dropping scale/unit until the beat width per bus can be confirmed.
  • Counters are masked to 31 bits. readl(addr) & 0x7FFFFFFF, and delta = (new - prev) & 0x7FFFFFFF, justified only by a comment asserting the counters are 31-bit. Downstream reads the full u32. If they are in fact 32-bit, every value at or above 2^31 is silently corrupted and each wrap loses 2^31 counts. Worth confirming against the hardware.
  • The filter field is truncated to 5 bits on 2712. BCM2712_FLT_MAX = 32, but downstream lists 51 master IDs for 2712, which suggests the control-register filter field is wider there. (Downstream masks to 0x1f as well, so this is a shared open question rather than a regression — but this PR bakes 32 into the ABI via config:10-14.)
  • Some filter aliases are mislabelled. cpu0_icache_rtrans uses filter=2 (ICACHE0), which on BCM2835 is VideoCore core 0's I-cache, not the ARM — the ARM is CPU, index 30. It's also paired with bus=15 (CPU_L2), so cpu0/1_{i,d}cache_* will most likely read zero.
  • Functionality dropped relative to the existing driver. Only 6 of the 11 counters are exposed; AMAX, WMAX, RMAX, RPEND and RATRANS are gone. RPEND and RATRANS in particular are useful and are read today.
  • VPU counts lag by up to 100 ms. pmu->read() returns cached data for mailbox monitors, so perf stat loses the tail of every run. That's an unavoidable consequence of not being able to sleep in read(), and it is documented in a comment — but it belongs in the Kconfig help or an admin-guide/perf/ document so users aren't caught out.
  • No Documentation/admin-guide/perf/ entry. Upstream generally expects one for a new PMU driver.
  • The DT binding doesn't match the actual device tree. Both examples use invented addresses — the real ones are 0x7e009800 / 0x7ee08000 on 2835–2711 and 0x7c012800 / 0x7e000000 on 2712. The 2835 example supplies only one reg, and a node like that fails probe with -EINVAL, since the driver requires reg[1] whenever firmware is present; reg should be minItems: 2 in the 2835/2711 branch. The binding also has no Raspberry Pi maintainer listed, and doesn't describe the two regions' distinct roles (MMIO versus a VideoCore address used for mailbox access), which is the main subtlety here.

Minor

  • bool monitor_running; sits at column 0 inside struct rpi_axi_hw_events (checkpatch doesn't catch it).
  • There's a literal \n in the kernel-doc block above @is_registered.
  • #include <linux/version.h> and <linux/vmalloc.h> are unused.
  • watcher_offset() ignores both its pmu and mon parameters.
  • err_firmware_put: is a bare return retdevm_rpi_firmware_get() needs no put.
  • Dead branch at the end of rpi_axi_pmu_del(): both arms unlock and return.
  • The new_count == U32_MAX check in rpi_axi_pmu_read() is unreachable — the MMIO path masks to 31 bits, and the mailbox path returns early above it.
  • rpi_axi_hw_events__init() doesn't clear vpu_disable_pending[].
  • BW_CTRL_BUS_WATCH_MASK and BW_CTRL_BUS_FILTER_MASK are defined but the code uses bare 0x3F / 0x1F.
  • of_match_ptr() alongside MODULE_DEVICE_TABLE — convention upstream is to drop it.
  • The comment in rpi_axi_pmu__exit() claiming "the work queue is permanently sealed. No new work can be scheduled" isn't true of workqueues generally; it holds here only because nothing else schedules that work.
  • Assisted-by: isn't a recognised kernel trailer.

Suggested path forward

The structural work is worth keeping. What I'd want to see before this can go in:

  1. The three per-SoC table sets taken verbatim from raspberrypi_axi_monitor.c, with a distinct CHIP_BCM2711 alongside CHIP_BCM2835 and CHIP_BCM2712.
  2. The mailbox address read from the raw reg property rather than resource->start.
  3. use_mailbox_interface = true for MON_VPU unconditionally, matching the existing driver.
  4. Kconfig mutual exclusion with RPI_AXIPERF, and depends on ARCH_BCM2835.
  5. scale / unit dropped pending confirmation of the beat width, and the 31-bit masking justified or removed.
  6. Binding examples corrected against the in-tree DT, with reg required to have 2 entries on 2835/2711.

Given that the tables appear to have been generated rather than sourced, I'd suggest re-deriving everything hardware-facing from the downstream driver or the vendor documentation, rather than spot-checking the current values.

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.

2 participants