Open-source kernel modules and userspace libraries for Xiongmai (XM) IP camera SoCs. Most of the code targets the XM530. The kernel modules are set up to build against the XM510 vendor kernel.
The goal is to replace the proprietary SDK that Xiongmai ships with its camera boards. The code is intended for OpenIPC firmware, and you can also use it in your own projects.
| SoC | Status | What's here |
|---|---|---|
| XM530 | Primary target | Sensor libraries, ISP application library, MPI reimplementation |
| XM510 | Kernel modules | hx280enc and xm_i2c build against the XM510 vendor kernel tree |
| XM322 / XM350 / XM520 / XM540 / XM550 | Headers only | Register maps in libraries/isp/include/xm/ |
This section introduces the main concepts in the codebase for readers who haven't worked with Xiongmai SoCs.
Xiongmai's SDK is a set of closed-source userspace libraries plus kernel modules. The libraries are the MPI (media process interface: system, VI, VENC and region), the ISP, AE and AWB algorithms, and one sensor library for each family of sensors. Applications call XM_MPI_* functions. Those functions talk to the kernel through ioctls on device nodes such as /dev/mmz, /dev/vi and /dev/rgn, and through /dev/mem mappings of hardware registers. This repository rebuilds those pieces from source so that they can be compiled with current toolchains and C libraries such as musl.
MMZ (Media Memory Zone). The video pipeline needs large blocks of contiguous physical memory for DMA between hardware blocks: sensor to ISP to encoder. MMZ reserves that memory and hands it out through /dev/mmz. XM_MPI_SYS_MmzAlloc calls the MMZ allocation ioctl, then maps the returned physical address through /dev/mem.
Sensor libraries. Each libsns_<family>_XM530.so supports a group of image sensors. At startup the library reads the sensor's chip-ID registers over I2C to identify the sensor and sets gSensorChip to the matching SENSOR_CHIP_* value. sensor_register_callback() then registers that sensor's callbacks with the vendor ISP, AE and AWB libraries through XM_MPI_ISP/AE/AWB_SensorRegCallBack. The callbacks cover exposure and gain logic, mirror and flip, and the default tables (AWB calibration, colour matrix and AGC).
ISP application layer. libispapp_XM530 contains the code that sits between the sensor library and the main application: PLL and VI-window setup for each resolution, the ISP run loop, product info, UART and MCU communication, VDA, and the I2C access layer (XM_I2C_Ioctl) that the sensor libraries call.
libmpi (libopenmpi.a) reimplements part of the vendor MPI. It covers SYS/MMZ, VI, VENC and region (OSD). Its headers are in libraries/include/re_*.h. The ioctl numbers in this library come from the vendor binaries, so they must match the vendor kernel modules exactly.
├── drivers/ Kernel modules (out-of-tree kbuild)
│ ├── hx280enc/ Hantro 6280/7280/8270/8290 video encoder driver
│ ├── xm_i2c/ /dev/xm_i2c misc-device (stub)
│ └── xm_vi/ XM530/XM550 video input driver (/dev/vi)
│
└── libraries/ Userspace libraries
├── include/ Reverse-engineered MPI headers (re_*.h)
├── isp/include/ Vendor-style SDK headers
│ ├── isp/ ISP / AE / AWB / sensor interfaces
│ ├── mpi/ SYS / VI / VO / AIO interfaces
│ └── xm/ Camera.h (SENSOR_CHIP_* enum), per-SoC register maps
├── libmpi/ libopenmpi.a: MPI reimplementation
├── libxmvenc/ libxmvenc.a: open H.264/JPEG (Hantro) and H.265 (Mali-V550) encoding (MIT)
├── libispapp_XM530/ libispapp_XM530.so: ISP application layer
├── libsns_X123_XM530/ libsns_X123_XM530.so: "X123" sensor family
└── libsns_X50_XM530/ libsns_X50_XM530.so: "X50" sensor family
tools/
├── xm-vi-hang/ Reproduces the VI driver hang without a camera application (MIT)
├── xmhevc-test/ Raw /dev/mv500 H.265 session, the interface reference (MIT)
└── xmvenc-test/ Encodes H.264, H.265 or JPEG with libxmvenc (MIT)
Each component has its own Makefile and is built with an ARM cross toolchain. Using the OpenIPC SDK:
export PATH=/path/to/openipc_sdk/bin:$PATH
# Userspace libraries
make -C libraries/libsns_X50_XM530 CROSS_COMPILE=arm-openipc-linux-musleabi-
make -C libraries/libsns_X123_XM530 CROSS_COMPILE=arm-openipc-linux-musleabi-
make -C libraries/libispapp_XM530 CROSS_COMPILE=arm-openipc-linux-musleabi-
make -C libraries/libmpi CROSS_COMPILE=arm-openipc-linux-musleabi-
# Or the libraries OpenIPC firmware ships for one SoC family, in one go
make -C libraries CHIPARCH=xm530 CC=arm-openipc-linux-musleabi-gcc STRIP=arm-openipc-linux-musleabi-strip
# Kernel modules: point KDIR at a configured kernel tree
make -C drivers/hx280enc KDIR=/path/to/linux
make -C drivers/xm_i2c KDIR=/path/to/linux
make -C drivers/xm_vi KDIR=/path/to/linux # XM530 kernel onlyThe Makefiles set different default toolchains (arm-xm-linux-, arm-linux- or arm-openipc-linux-musleabi-), so pass CROSS_COMPILE explicitly. libmpi and the kernel module Makefiles also hard-code PATH to a vendor toolchain directory. Override it from the command line, for example make PATH="$PATH" ..., or give an absolute CROSS_COMPILE prefix. Most components build with -Werror.
OpenIPC firmware builds these libraries from source: its xiongmai-opensdk package fetches this repository at a pinned commit and runs make -C libraries CHIPARCH=<soc> with the Buildroot toolchain and CFLAGS. The library Makefiles append their own include paths and defines to the CFLAGS they are given.
| Manufacturer | Sensor | Library |
|---|---|---|
| Sony | IMX291 | X123 |
| IMX307 | X123 | |
| IMX323 | X123 | |
| IMX335 | X50 | |
| SmartSens | SC1235 | X123 |
| SC2145 / SC2145H | X123 | |
| SC2235 / SC2235E / SC2235P | X123 | |
| SC2239 | X123 | |
| SC2335 | X123 | |
| SC3035 | X123 | |
| SC307E | X123 | |
| SC3335 | X123 | |
| SC4236 | X123 | |
| SC335E | X50 | |
| SC5235 | X50 | |
| SC5239 | X50 | |
| SC5332 | X50 | |
| OmniVision | OV9732 | X123 |
| Silicon Optronics (JX) | H62 | X123 |
| H65 | X123 | |
| F37 | X123 | |
| K03 | X50 | |
| Imagedesign | MIS2003 / MIS2006 | X123 |
| SuperPix | SP140A | X123 |
| SP2305 | X123 | |
| Other | AUGE | X123 |
To add a sensor:
- Add a
<sensor>_cmos.cwith the ISP, AE and AWB callbacks and tuning tables. If the sensor needs an init register table, also add a<sensor>_sensor_ctl.c. - Add the chip-ID probe and a
caseinsensor_register_callback()in the family dispatcher (XAx_cmos.c/X50_cmos.c). - Add a
SENSOR_CHIP_*value inCamera.hif needed, and handle the new sensor inlibispapp_XM530/isp_sample.c. - Add the new object files to the Makefile's
OBJECTSlist.
| Module | Function | Status |
|---|---|---|
hx280enc |
Hantro 6280/7280/8270/8290 hardware video encoder | Same /dev/h264 ABI as the vendor xm530_h264.ko; ran majestic on an XM550 in place of it |
xm_i2c |
Registers /dev/xm_i2c and /dev/xm_i2c1 |
Stub: read, write and ioctl are not implemented yet |
xm_vi |
XM530/XM550 video input: capture window, scalers and frame buffers (/dev/vi) |
Same ABI as the vendor xm530_vi.ko, without its vmalloc leak; ran majestic on an XM550 in place of it |
libraries/libxmvenc drives the H.264/JPEG encoder of the XM530/XM550 and XM510 (a Hantro 8290) directly, with no vendor userspace (#13). It is a static library, libxmvenc.a, under the MIT license, so that closed applications can link it too. The API is in xmvenc.h:
xmvenc_openopens the encoder and identifies the chip.xmvenc_chn_createsets up a stream: H.264 (CAVLC or CABAC, fixed QP or frame-level rate control, IDR interval), H.265 (see below) or JPEG (quality).xmvenc_encodeencodes one NV12 picture, given by physical address, and returns the software-written headers (SPS and PPS before an IDR, the JPEG header) and the hardware-written slice or scan.xmvenc_chn_request_idrmakes the next frame an IDR.
Encodes on different channels can come from different threads; they take turns on the hardware.
A pipe runs a channel from a VI channel, on a thread of its own:
xmvenc_pipe_createsets one up. Withfpsset, VI frames in between are skipped.xmvenc_pipe_start/xmvenc_pipe_stopstart and stop it: continuously, or for a set number of pictures, such as one JPEG snapshot.xmvenc_pipe_get/xmvenc_pipe_releasetake the encoded frames in order: type, QP, headers, data, and a microsecond timestamp on theCLOCK_MONOTONICscale.- Frames queue up in the channel's stream buffer, which serves as a ring. When the reader falls behind and there is no room, a frame is skipped rather than encoded, so the stream stays decodable.
- The application enables the VI channel; the pipe only takes its frames.
tools/xmvenc-test exercises the library, and its output is checked with ffmpeg.
- Input: a synthetic NV12 picture, colour bars that move 8 pixels per frame, so P frames carry motion, or a VI channel.
- Output: a raw
.h264file, or concatenated JPEGs.
make -C tools/xmvenc-test CROSS_COMPILE=arm-openipc-linux-musleabi-
# on the camera: the encoder serves one user, so stop majestic first
/etc/init.d/S95majestic stop
./xmvenc-test -w 1920 -h 1080 -q 30 -n 60 -g 30 -c -o /tmp/test.h264-p runs the -v channel through a pipe. -J <quality> adds JPEG snapshots of VI channel 1 (about one a second, into <file>.jpg), -r <n> requests an IDR once n packets have been read, -S <ms> slows the reader so the queue fills, and -G <ms> sets the keyframe interval in capture time instead of -g frames. -B <KiB> sets the stream buffer size.
-v 0 (main) or -v 1 (sub) takes the pictures from a VI channel instead, encoding straight from the capture buffers. With majestic stopped the capture hardware keeps running, but majestic has turned frame delivery off, so the tool turns it on for the run and back off at the end. -d file.nv12 saves the first input picture, which you can compare against the decoded stream. -j <quality> encodes JPEG pictures (baseline, 4:2:0) instead of H.264; with more than one frame, the output is concatenated JPEGs.
The tool encodes IDR and P frames, at a fixed QP by default, with SPS and PPS before every IDR. -b <kbps> turns on frame-level rate control at -f <fps>, keeping the QP within -Q min:max. -g sets the IDR interval (-g 1 gives all IDR), and -c selects CABAC instead of CAVLC. It needs /dev/mmz and an encoder driver: the vendor xm530_h264.ko, drivers/hx280enc, or, on XM510, the vendor xm510_h264.ko. It runs on XM530/XM550 and XM510. On XM510 the vendor driver has no blocking wait, so the tool polls the status register, and the MMZ zone is too small for the tool's own buffers, so it reuses majestic's idle encoder buffers. VI on XM510 needs the vendor isp daemon, which majestic starts and stops with itself. The library's cabac_tbl.c holds the H.264 CABAC init tables in the layout from Rockchip MPP, under the Apache License 2.0.
Two cautions about MMZ:
- Blocks persist after the tool exits. MMZ has no free, so a later run reuses the named blocks.
- Never reset MMZ while the capture pipeline is configured. That includes when majestic is stopped: VI keeps writing into its old buffers.
Restarting majestic resets MMZ cleanly.
The XM530/XM550 encodes H.265 on a separate engine, an Arm Mali-V550 with its own firmware (hevcenc.fwb), behind the vendor kernel module mve_base.ko and its device /dev/mv500 (#27). libxmvenc drives that module without vendor libraries.
The interface: libraries/libxmvenc/mvb.h holds what the library uses of the module's interface: the command numbers, the buffer descriptor, the events, and the firmware messages that carry the encoder settings. It was recovered from the module and from a trace of the vendor library, and is written from scratch under MIT.
Using it: a channel created with XMVENC_H265 takes the same calls as H.264, xmvenc_encode or a pipe.
- Settings: IDR interval, fixed QP, or variable bitrate within a QP range; the firmware keeps the GOP and the rate control.
- Engine: each channel is a firmware session of its own. The sessions run alongside the H.264 encoder and alongside each other.
- Headers: the firmware sends VPS, SPS and PPS once. The library keeps them and puts them before every IDR.
- Input: NV12 with the chroma directly after the luma, as VI delivers it.
- Measured on an XM550: about 21 ms per 1080p frame, and a 1080p and a 704×576 pipe running together from VI.
tools/xmvenc-test -H encodes with it, from the synthetic picture or VI, directly or through a pipe:
./xmvenc-test -H -v 0 -p -n 250 -G 1000 -b 4096 -o /tmp/main.h265tools/xmhevc-test is the minimal session on /dev/mv500 that the interface was worked out with. It follows the vendor library's order of commands, which makes it the reference for that order.
- Input: a synthetic NV12 picture (a ramp with a square that moves 8 pixels per frame).
- Output: a raw
.h265file of parameter sets and IDR/P frames. - Settings: IDR interval (
-g), frame rate (-f), variable bitrate target (-b) and QP range (-Q).
It writes a synthetic NV12 picture (a ramp with a square that moves 8 pixels per frame) as a raw .h265 file. The settings are -g, -f, -b and -Q:
make -C tools/xmhevc-test CROSS_COMPILE=arm-openipc-linux-musleabi-
# on the camera, with majestic stopped
./xmhevc-test -w 1920 -h 1080 -n 100 -g 25 -o /tmp/test.h265Both tools take their MMZ through libxmvenc, so they also need /dev/h264.
On the XM530/XM550, re-initialising VI (_IO('V', 6) on /dev/vi, which an SDK start makes) now and then freezes the whole board, CPU and interrupts included, when the process before it left VI capturing. Camera applications see it as a hard hang at start, about one start in fifteen, that only a watchdog or a power cycle ends.
tools/xm-vi-hang reproduces it with no camera application, only raw ioctls. Each run:
- Resets MMZ and inits VI.
- Replays the ISP and MIPI writes an SDK start makes after VI init (
isp_writes.h, recorded on an XM550 + IMX335). Without them, VI init leaves capture stopped. - Sets VI up (channel 0, 1920×1080; ext channel 1, 704×576).
- Takes frames for a few seconds, then exits, leaving VI capturing.
Run it in a loop: the next run's VI init is the one that may freeze. Start the camera application once after boot so the sensor runs, then stop it.
make -C tools/xm-vi-hang CROSS_COMPILE=arm-openipc-linux-musleabi-
# on the camera, with the camera application stopped; expect to power-cycle
while ./xm-vi-hang; do sleep 1; doneMeasured on an XM550 + IMX335 (150-round limit per test):
| Run | Result |
|---|---|
| default | 6 freezes, after 4, 4, 9, 16, 19 and 30 runs |
-x: both channels disabled before exit |
froze at run 8 |
-p: both channels disabled before the VI init |
froze at run 15 |
-n: ext channel 1 enabled, no frames taken |
froze at run 4 |
-0: channel 0 only |
froze at run 3 |
-i: VI init only, VI never set up |
150 runs clean |
-N: 3D noise reduction off before the init (0x30040000 = 0, 200 ms) |
310 runs clean: 110 + 100 with devmem, 100 with -N, over three boots. The baseline froze at run 7 in between |
So the condition is VI capturing at the time of the init. Disabling the channels doesn't avoid it, and neither does leaving ext channel 1 out. Putting the sensor into standby doesn't stop VI either: its frame interrupts keep coming from the ISP's timing.
The way around it: turn 3D noise reduction off before the VI init.
- Why it works: VI init pulses a reset (
0x01590000, then 1, to the system controller at0x2000011c) that covers the ISP. 3D-NR is a bus master: it reads and writes reference frames in DDR for as long as it's on. With it off and its DMA given a few frames to stop, the reset no longer freezes the bus. - No lasting effect: the reset puts the 3D-NR control back to its default (
0x45, on), so the setup that follows is unchanged. - Using it:
-Ndoes this, through the ISP driver's register write.-Band-Arun commands right before and after the init, for testing other ways around it. - Leak limit: each run leaks about 7 MB of VI driver vmalloc. A boot lasts about 110 runs before setup fails with ENOMEM. Reboot between long tests.
The VI driver's vmalloc leak. Every VI init leaks the channels' buffer mappings.
-
Cause: channel setup (
0x400c5601) maps each capture buffer twice, for luma and chroma. It unmaps the previous mappings only if their pointers are still stored. VI init clears the driver's state table without unmapping anything. -
Way around it: channel setup does the unmapping before it checks the new size. So a setup with a size VI refuses ("too small", here 64×64), on disabled channels, before the MMZ reset and the init, leaves only tiny mappings to leak.
-Sdoes this. -
Measured (XM550):
vmalloc per run without -S7,316 kB with -S72 kB That's about 12,700 runs to exhaustion instead of about 110. 200 runs of
-N -Swere clean.
Every push and pull request to main runs these jobs, each using the OpenIPC musl toolchain for its SoC (toolchain.xiongmai-xm5x0).
- Libraries cross-compile (XM530): builds all four userspace libraries, checks that the expected entry points are exported (
sensor_register_callback,XM_I2C_Ioctl,XM_MPI_*, ...), and checks that the shared objects depend only on libc. - Kernel modules (xm510 / xm530): builds
xm_i2candhx280encagainst the OpenIPC kernelsopenipc/linux@xiongmai-xm510(3.0.101) and@xiongmai-xm530(3.10.103), the same way the firmware does: with the firmware's<soc>.generic.configand its generic kernel patches.
All external inputs are pinned: the kernel commits, the firmware commit, and the toolchain sha256 checksums. OpenIPC rebuilds its toolchain tarballs in place, so a checksum failure means upstream published a new build and the checksum in build.yml needs updating.
GPL v3. See LICENSE. The exceptions:
libraries/libxmvenc,tools/xmvenc-test,tools/xmhevc-testandtools/xm-vi-hangare under the MIT license, so that they can be linked into applications under any license. See theirLICENSEfiles.libraries/libxmvenc/cabac_tbl.candcabac_tbl.hare under the Apache License 2.0, from Rockchip MPP.