Boot, Kernel, and Device Tree¶
This page explains the boot image, kernel, and device tree used by an openwifi board. The device tree declares where the FPGA blocks live, so it is the main thing you edit when porting to a new board.
If you only want to flash a card and run, see Getting Started. If you want to rebuild the driver or a full SD image, see Software Development Workflow. This page is for understanding and modifying the boot chain itself. All paths below are in the openwifi repo under kernel_boot/ unless noted.
To move a working board onto a newly built kernel and set of modules, go straight to Updating a running board. It covers the whole path from a PC-side build to sdr0 showing up in ifconfig -a, with the scripts or by hand.
The boot chain at a glance¶
A Zynq board boots from the SD card's BOOT partition, which holds three things openwifi cares about:
BOOT partition
├── BOOT.BIN # FSBL + FPGA bitstream + U-Boot (+ ATF/PMUFW on 64-bit)
├── uImage / Image # the Linux kernel
└── devicetree.dtb # the hardware description Linux parses at boot
(rootfs lives on the second partition)
The SoC's boot ROM first loads BOOT.BIN. Its FSBL initializes DDR and clocks, programs the FPGA bitstream, and hands off to U-Boot. U-Boot loads the kernel and the device tree and starts Linux. Linux then reads the device tree to discover the FPGA's AXI peripherals (including openwifi's cores) and binds drivers to them.
BOOT.BIN stage list grows, adding PMUFW before the bitstream and ATF (BL31) after it, as detailed just below.32-bit vs 64-bit boot¶
The two SoC families build BOOT.BIN differently, so the ZCU102 needs a separate build path:
| Zynq-7000 (32-bit) | Zynq UltraScale+ / MPSoC (64-bit, for example ZCU102) | |
|---|---|---|
| Build script | kernel_boot/build_boot_bin.sh |
kernel_boot/build_zynqmp_boot_bin.sh |
| BOOT.BIN stages | FSBL → bitstream → U-Boot | FSBL → PMUFW → bitstream → ATF (BL31) → U-Boot |
| Kernel image | uImage (U-Boot format) |
Image |
| Device tree file | devicetree.dtb |
system.dtb |
| Extra firmware | none | PMU firmware + ARM Trusted Firmware |
build_boot_bin.shtakessystem_top.<hdf|xsa>andu-boot.elf, uses Xilinxxsctto build the FSBL from the hardware description, andbootgento pack FSBL + bitstream + U-Boot intoBOOT.BIN.build_zynqmp_boot_bin.shalso builds or collects the PMU firmware and the ARM Trusted Firmware BL31 stage. It candownloadand build ATF to match your Vitis version. It then packs the stages with per-stage attributes (a53-0,el-3/trustzone,el-2,pl) into a ZynqMPBOOT.BIN.
Both scripts are invoked for you by the higher-level image and build helpers. You rarely call them directly.
The kernel¶
openwifi runs the Analog Devices Linux kernel (a fork of the Xilinx kernel with AD9361 support), branch 2026_R1 (Linux v6.12). The driver builds against this kernel. The AD9361 is driven by ADI's in-tree IIO driver, which openwifi patches lightly.
user_space/prepare_kernel.sh $XILINX_DIR <32|64> does the whole thing:
- Checks out the ADI kernel submodule (
adi-linuxfor 32-bit,adi-linux-64for 64-bit) at branch2026_R1. - Applies the openwifi patches (below).
- Copies
kernel_boot/kernel_config(32-bit) orkernel_boot/kernel_config_zynqmp(64-bit) in as.config. - Builds
uImage(32-bit,UIMAGE_LOADADDR=0x8000) orImage(64-bit), plus modules.
Output lands at adi-linux/arch/arm/boot/uImage or adi-linux-64/arch/arm64/boot/Image.
The kernel patches¶
Four small patches (in kernel_boot/, documented in kernel_patch_readme.md) adapt the ADI kernel for openwifi:
| Patch | What it does |
|---|---|
ad9361_v6_12.patch |
Exports AD9361 functions the openwifi driver calls (ad9361_set_tx_atten, ad9361_get_tx_atten, ad9361_do_calib_run) and parses a new AGC device-tree property. This is the current patch for kernel 6.12, and ad9361.patch is the older equivalent. |
ad9361_private.patch |
Adds the f_agc_dig_sat_ovrg_en field to struct gain_control that the AGC change above needs. |
ad9361_conv.patch |
Removes the 61.44 MHz LVDS-interface self-timing calibration point, which is unreliable on some low-end or marginal hardware. |
axi_hdmi_crtc.patch |
Comments out one VDMA call to avoid an AXI-HDMI build error that appears once Xilinx AXI DMA is enabled. |
kernel_config and kernel_config_zynqmp are full defconfig-style .config files for Linux 6.12, for 32-bit and 64-bit ARM respectively, with the ADI driver bundles enabled.
Updating a running board¶
Everything above describes how a board is built and what it boots. This part covers the routine operation of moving a board that already runs openwifi onto a kernel, module set, or driver you have built. The layout section comes first, since the three procedures after it all get the same files into the same two directories.
- Updating a board to a newly built kernel: the normal path, using the transfer and populate scripts. New kernel, reboot needed.
- The same update by hand: the identical set of copies with plain
scp, for when the scripts' hard-coded addresses or their all-or-nothing behavior do not suit you. - Replacing a single module on a running board: the light case. Same kernel, one rebuilt
.ko, no reboot.
The usual mistake is to pick the third when the kernel has changed. This shows up as invalid module format at insmod.
Where the kernel and its modules live on the board¶
The kernel build produces the image and a tree of .ko modules, but openwifi does not use make modules_install. The modules are staged by hand instead.
update_sdcard.sh (the "rebuild SD card" script, see Building SD Images) does the staging when you write a fresh card. It copies the files once per architecture (ARCH = 32 and 64), so a single card could carry both a 32-bit and a 64-bit set:
| What | From (on the host) | To (on the card) |
|---|---|---|
| Kernel image | adi-linux/arch/arm/boot/uImage (32) or adi-linux-64/arch/arm64/boot/Image (64) |
BOOT/ |
openwifi driver .kos |
driver/ (built by make_all.sh) |
rootfs/root/openwifi<ARCH>/ |
In-tree kernel modules (all .ko, via find) |
adi-linux[-64]/ |
rootfs/root/kernel_modules<ARCH>/ |
Module metadata: Module.symvers, modules.builtin, modules.builtin.modinfo, modules.order |
adi-linux[-64]/ |
rootfs/root/kernel_modules<ARCH>/ |
<ARCH> is 32 or 64, so the rootfs partition holds openwifi32, openwifi64, kernel_modules32, and kernel_modules64 side by side. At this point the modules are only staged. Nothing is under /lib/modules yet.
The board-side script populate_kernel_image_module_reboot.sh finishes the job. The architecture was already selected on the host, since transfer_kernel_image_module_to_board.sh packs only the matching set. The script uses uname -m only to pick the kernel image and device tree filenames (uImage and devicetree.dtb on 32-bit, Image and system.dtb on 64-bit). It does the following:
- moves the board-support modules (
ad9361_drv.ko,adi_axi_hdmi.ko,axidmatest.ko,lcd.ko,xilinx_dma.ko) out ofkernel_modulesand intoopenwifi/, next to the driver, - symlinks the staged directory into the module path (
ln -s /root/kernel_modules /lib/modules/$(uname -r)) and runsdepmod, somodprobecan resolve dependencies for the running kernel, - copies the kernel image,
BOOT.BINand the device tree into theBOOTpartition, then reboots.
The end state on a running board is this layout, and both halves of it have to be right before sdr0 can appear:
/root/openwifi/ # openwifi driver .ko + board-support .ko (wgd.sh insmods these by path)
/root/kernel_modules/ # every in-tree .ko, plus Module.symvers and the modules.* metadata
/lib/modules/$(uname -r) -> /root/kernel_modules # the only path modprobe searches
wgd.sh loads the openwifi stack with insmod from /root/openwifi/, and loads mac80211 with modprobe, which only works through that symlink. A missing or stale symlink is a common reason a freshly updated board comes up without sdr0.
Updating a board to a newly built kernel¶
This is the full procedure for moving a board that already boots openwifi onto a kernel you have built. It applies whether you changed the kernel config, changed a patch, or moved to a new ADI branch. Steps 1 to 4 run on your PC, steps 5 to 8 on the board. The end state is sdr0 listed by ifconfig -a.
The commands assume the environment variables are set. They also assume the board is at 192.168.10.122 and your PC is at 192.168.10.1, which the transfer scripts hard-code. Bulk update helpers lists the same scripts in short form. It also describes an FTP-based alternative (sdcard_boot_update.sh plus wgd.sh remote) that pulls from the board instead of pushing from the PC.
1. Build the kernel on the PC.
cd openwifi/user_space
./prepare_kernel.sh $XILINX_DIR $ARCH_BIT
Write down the kernel release string it produced, because everything below depends on whether it changed:
cat ../adi-linux-64/include/config/kernel.release # 64-bit, use ../adi-linux/... for 32-bit
If that string is the same as the uname -r the board reports today, this is a config or patch change only and step 6 below becomes a no-op. If it differs, the board is getting a new kernel version and step 6 matters.
2. Rebuild the driver against that same kernel.
cd openwifi/driver
./make_all.sh $XILINX_DIR $ARCH_BIT
Do not skip this. A .ko can only be loaded by the exact kernel build it was compiled against. A new kernel therefore always means a new sdr.ko and a new set of sub-core modules. Reusing the old driver .kos is a common way to end up with a board that boots fine and still has no sdr0.
3. Send the kernel, the modules and the boot files to the board.
cd openwifi/user_space
./transfer_kernel_image_module_to_board.sh ../adi-linux-64 $BOARD_NAME # ../adi-linux for 32-bit
The first argument is the built kernel tree, the second is one of the supported board names (zed_fmcs2, zcu102_fmcs2, antsdr, e310v2, sdrpi, and so on). The script collects every .ko from that tree and the module metadata (Module.symvers, modules.builtin, modules.builtin.modinfo, modules.order). It also takes the kernel image (Image for zcu102_fmcs2, uImage otherwise), and BOOT.BIN and the .dtb if they exist under kernel_boot/. It packs all of that into kernel_modules.tar.gz and scps it, plus populate_kernel_image_module_reboot.sh, into /root on the board.
If you also changed the FPGA or the device tree, generate the new BOOT.BIN first with boot_bin_gen.sh (see FPGA Development) so that this step picks it up. Otherwise the board keeps its existing BOOT.BIN and .dtb, which is what you want for a kernel-only change.
4. Send the rebuilt driver.
./transfer_driver_userspace_to_board.sh
This packs the driver .kos into openwifi.tar.gz and copies it, along with populate_driver_userspace.sh, into /root on the board.
5. Install the kernel and modules on the board.
ssh root@192.168.10.122
./populate_kernel_image_module_reboot.sh
The script unpacks the archive into /root/kernel_modules and moves the board-support modules into /root/openwifi/. It creates the /lib/modules/$(uname -r) symlink and runs depmod. It then copies the kernel image, BOOT.BIN, and the device tree into the BOOT partition, and reboots. Expect the ssh session to drop.
6. After the reboot, check the module symlink, and run the populate script a second time if it is wrong.
The symlink in step 5 was created for the kernel that was running at that moment, which is the old one. If the new kernel has a different release string, the board now boots a kernel with no /lib/modules entry at all. modprobe mac80211 then fails, and wgd.sh cannot bring up sdr0. Check it:
uname -r # should be the release string from step 1
ls -l /lib/modules/$(uname -r) # should point at /root/kernel_modules
If that directory is missing, run ./populate_kernel_image_module_reboot.sh again (it reboots again, and this time the symlink is made for the new kernel), or fix it directly:
ln -s /root/kernel_modules /lib/modules/$(uname -r)
depmod -a
If the kernel release string did not change, the existing symlink is still correct and one run is enough.
7. Install the driver.
./populate_driver_userspace.sh
This puts the freshly built .kos into /root/openwifi/.
8. Load everything and confirm sdr0.
cd /root/openwifi
./wgd.sh
ifconfig -a | grep sdr0
ifconfig sdr0 up
wgd.sh first loads the FPGA image if system_top.bit.bin is present in the directory. It then insmods ad9361_drv and xilinx_dma and modprobes mac80211. Finally it insmods tx_intf, rx_intf, openofdm_tx, openofdm_rx, xpu, and sdr. The sdr0 interface appears when sdr.ko registers itself with mac80211.
Use ifconfig -a rather than plain ifconfig. wgd.sh leaves the interface down, so it does not show in the short listing. ip link works as well if net-tools is not installed. Once ifconfig sdr0 up succeeds the board is back to a normal openwifi state and you can continue with Getting Started or Operating Modes.
Rebuild sdrctl and side_ch_ctl too
After a kernel update, these tools can end up out of sync with the driver they talk to. This can show up as a segmentation fault instead of a clean error (details). Rebuild both on the board while you are here: Rebuilding sdrctl and Building side_ch_ctl.
If sdr0 does not appear¶
Read dmesg | tail -40 right after ./wgd.sh. The message shows you which step above did not take:
| What you see | What it means | Fix |
|---|---|---|
insmod: ERROR: could not insert module ...: Invalid module format, and dmesg shows version magic ... should be ... |
The .ko was built against a different kernel than the one running |
Rebuild the driver (step 2) against the kernel that is actually booted, and re-copy it. Compare modinfo /root/openwifi/sdr.ko \| grep vermagic with uname -r |
modprobe: FATAL: Module mac80211 not found in directory /lib/modules/<version> |
The /lib/modules symlink is missing or still points at the old kernel version |
Step 6 |
insmod: ERROR: ... Unknown symbol in module, with dmesg naming ad9361_set_tx_atten or ad9361_do_calib_run |
The loaded ad9361_drv.ko is an unpatched one, so the exported functions the driver needs are absent |
Confirm prepare_kernel.sh applied the kernel patches, then redo steps 1, 3, and 5 |
Modules load with no error, but no sdr0 and dmesg shows no openwifi probe lines at all |
The driver never bound, because nothing in the device tree matches compatible = "sdr,sdr", or the .dtb in the BOOT partition is not the openwifi one |
The device tree below, and check that step 5 wrote the .dtb into BOOT |
Unsupported PRODUCT_ID 0xFF or 0x00 at AD9361 probe |
The AD9361 is not responding over SPI, which is a hardware or FMC-connection problem, not a kernel one | Troubleshooting → hardware quirks |
A quick full-state check, useful to paste into a bug report:
uname -r
ls -l /lib/modules/$(uname -r)
modinfo /root/openwifi/sdr.ko | grep vermagic
lsmod | grep -E 'sdr|mac80211|ad9361|xilinx_dma'
dmesg | grep -i -E 'sdr|ad9361|openwifi'
Updating the device tree or BOOT.BIN only
Steps 3 and 5 already carry BOOT.BIN and the .dtb along with the kernel, so a device-tree change alone can use the same flow. The only requirement is a power-cycle or reboot, because those two are read at boot and cannot be reloaded live. The alternative is to mount the BOOT partition on your PC and copy the files in directly.
The same update by hand, without the scripts¶
Steps 3 to 7 above are only file copies and a symlink. Once steps 1 and 2 are done (build the kernel, rebuild the driver against it), you can do them by hand. This helps when your board is not at the hard-coded 192.168.10.122, when you only need part of the update, or when you want to see which files are touched. Everything below is what transfer_kernel_image_module_to_board.sh, transfer_driver_userspace_to_board.sh, populate_kernel_image_module_reboot.sh and populate_driver_userspace.sh do with the tar step dropped and the paths spelled out.
On the PC: pick the arch-dependent names.
cd openwifi/user_space
BOARD_IP=192.168.10.122
BOARD_NAME=zcu102_fmcs2
# 64-bit board (zcu102_fmcs2)
KDIR=../adi-linux-64
KERNEL_IMAGE=$KDIR/arch/arm64/boot/Image
DTB_NAME=system.dtb
# 32-bit board (everything else)
# KDIR=../adi-linux
# KERNEL_IMAGE=$KDIR/arch/arm/boot/uImage
# DTB_NAME=devicetree.dtb
On the PC: collect the in-tree modules.
rm -rf kernel_modules && mkdir -p kernel_modules
find $KDIR/ -name \*.ko -exec cp {} ./kernel_modules/ \;
cp $KDIR/Module.symvers $KDIR/modules.builtin $KDIR/modules.builtin.modinfo \
$KDIR/modules.order ./kernel_modules/
The find flattens the whole kernel tree into one directory, so there is no kernel/drivers/... hierarchy under it. /lib/modules/$(uname -r) points at this flat layout on the board, so the four metadata files have to travel with the .kos. Without modules.order and modules.builtin, depmod cannot build a usable modules.dep and modprobe mac80211 fails.
On the PC: add the boot files.
cp $KERNEL_IMAGE ./kernel_modules/
Add BOOT.BIN and the device tree only if you rebuilt them, for example after an FPGA or device-tree change. For a kernel-only update leave both out and the board keeps the ones it already has.
cp ../kernel_boot/boards/$BOARD_NAME/output_boot_bin/BOOT.BIN ./kernel_modules/
cp ../kernel_boot/boards/$BOARD_NAME/$DTB_NAME ./kernel_modules/
On the PC: collect the driver modules.
rm -rf openwifi && mkdir -p openwifi
find ../driver/ -name \*.ko -exec cp {} ./openwifi/ \;
That picks up sdr.ko and the sub-core modules (tx_intf, rx_intf, openofdm_tx, openofdm_rx, xpu) plus side_ch.ko.
Copy both sets to the board.
ssh root@$BOARD_IP 'rm -rf /root/kernel_modules && mkdir -p /root/kernel_modules /root/openwifi'
scp kernel_modules/* root@$BOARD_IP:/root/kernel_modules/
scp openwifi/*.ko root@$BOARD_IP:/root/openwifi/
Wiping /root/kernel_modules first is not optional. Any .ko left over from the previous kernel stays visible to depmod and modprobe. A stale one either loads with the wrong version magic or shadows the new module of the same name. On a slow link, the scripts' approach is faster: tar -zcf kernel_modules.tar.gz kernel_modules, one scp, and tar -zxf on the board.
On the board: put the files where wgd.sh expects them.
ssh root@$BOARD_IP
cd /root
# board-support modules belong next to the driver, wgd.sh insmods them by path
for m in ad9361_drv adi_axi_hdmi axidmatest lcd xilinx_dma ; do
mv -f ./kernel_modules/$m.ko ./openwifi/ 2>/dev/null
done
# point the module search path of the running kernel at the staged directory
rm -rf /lib/modules/$(uname -r)
ln -s /root/kernel_modules /lib/modules/$(uname -r)
depmod
# keep wgd.sh from reprogramming the FPGA with the old bitstream
mv -f ./openwifi/system_top.bit.bin ./openwifi/system_top.bit.bin.bak
Not every board has all five board-support modules, so mv failing on one of them is normal. The system_top.bit.bin rename only matters if you copied a new BOOT.BIN. The new bitstream is then already loaded at boot, and letting wgd.sh push the old .bit.bin on top of it would undo the update. Skip that line if you did not touch BOOT.BIN.
On the board: write the boot files and reboot.
umount /mnt 2>/dev/null
mount /dev/mmcblk0p1 /mnt
cp ./kernel_modules/Image /mnt/ # uImage on a 32-bit board
cp ./kernel_modules/BOOT.BIN /mnt/ # only if you copied a new one
cp ./kernel_modules/system.dtb /mnt/ # devicetree.dtb on 32-bit, only if new
sync
umount /mnt
reboot now
/dev/mmcblk0p1 is the first partition of the SD card, which is the FAT BOOT partition. Keep the sync before umount. A board with a half-written kernel image on its FAT partition does not boot again.
After the reboot: check the symlink, exactly as in step 6.
uname -r
ls -l /lib/modules/$(uname -r)
If the release string changed, remake the symlink for the reason given in step 6:
rm -rf /lib/modules/$(uname -r)
ln -s /root/kernel_modules /lib/modules/$(uname -r)
depmod -a
Then load the stack and confirm the interface, the same as step 8:
cd /root/openwifi
./wgd.sh
ifconfig -a | grep sdr0
If it does not show up, the same table applies: If sdr0 does not appear.
Replacing a single module on a running board¶
Sometimes you have only rebuilt one or more modules against the same kernel the board is running. In that case you need neither of the two procedures above, nor update_sdcard.sh, nor a reboot. You can push the .kos over the network and reload them live. Put each module in the directory from which wgd.sh loads it. The live reload also depends on the /lib/modules symlink described above.
- openwifi driver stack (
sdr,tx_intf,rx_intf,openofdm_tx,openofdm_rx,xpu) and the board-support modules (ad9361_drv,xilinx_dma, …):wgd.shinsmods these by explicit path from its own directory, so they go into/root/openwifi/. Putting them inkernel_modules/does not makewgd.shfind them. - Base kernel modules (
mac80211,cfg80211, other in-tree.kos): these are the only oneswgd.shpulls withmodprobe, so they go into/root/kernel_modules/(the/lib/modules/$(uname -r)target).
The openwifi driver .kos live in driver/ on the host after make_all.sh. In-tree modules come from the built adi-linux[-64]/ tree:
BOARD_IP=192.168.10.122
# openwifi driver and board-support modules -> /root/openwifi, where wgd.sh insmods them by path
scp driver/sdr.ko root@$BOARD_IP:/root/openwifi/
scp adi-linux-64/drivers/iio/adc/ad9361_drv.ko root@$BOARD_IP:/root/openwifi/
# a base in-tree module that wgd.sh loads with modprobe -> the staged module tree
scp adi-linux-64/net/mac80211/mac80211.ko root@$BOARD_IP:/root/kernel_modules/
Then reload on the board. The usual way is to re-run ./wgd.sh in /root/openwifi/, which rmmods and insmods sdr plus its five sub-core modules from that directory in the right order. To reload a single module by hand, insmod it by path (the openwifi stack) or modprobe it by name after depmod -a (a base module):
rmmod sdr 2>/dev/null # unload the old one if it's live
insmod /root/openwifi/sdr.ko # openwifi stack: insmod by path, like wgd.sh
# depmod -a && modprobe mac80211 # base stack: resolved via /lib/modules -> kernel_modules
The module must match the running kernel
A .ko is only loadable by the exact kernel it was built against. insmod rejects it (version magic or invalid module format) if you changed the kernel config or bumped the kernel version. Copying modules live only works when you rebuilt just the module against the same kernel that is booted. If you changed the kernel itself, you have to install the new image and reboot, so use the full update procedure instead of scping the .kos.
The device tree¶
The device tree is the central piece of a board port. The device tree is a data structure that describes the hardware, including every peripheral with its register address, interrupts, and clocks. Linux reads it at boot to know what exists. openwifi's driver is a Linux platform driver that binds to a device-tree node with compatible = "sdr,sdr". It learns the AXI addresses and interrupts of every FPGA core from the device tree. If the device tree doesn't match the FPGA build, the driver does not find the hardware, or binds to the wrong addresses.
How openwifi builds a board's device tree¶
Rather than hand-maintain a full .dts per board, openwifi layers overlays onto a stock board device tree. The script kernel_boot/boards/construct_device_tree.sh does the merge:
construct_device_tree.sh $BOARD_NAME $ARCH # ARCH = 32 or 64
# optional 3rd/4th args: custom stock-dts folder, custom dtsi include folder
Three ingredients go in:
- The stock board device tree: the ordinary ADI or Xilinx
.dtsfor the board (for examplezynq-zed.dts,zynqmp-zcu102-rev1.1.dts). This describes the ARM SoC, DDR, Ethernet, UART, SD, etc. (everything except openwifi). openwifi_32_ad9361.dtsoandopenwifi_64_ad9361.dtso: the architecture-wide openwifi overlay. It adds the openwifi FPGA IP blocks and the AD9361 binding, and is shared by all boards of that architecture.overlays/<board_name>.dtso: the board-specific overlay. It sets the AD9361 reference-clock frequency, the board LEDs and GPIO, and any other settings unique to the board.
The script compiles each overlay with dtc, preprocesses and compiles the stock .dts, then fuses them with fdtoverlay:
construct_device_tree.sh compiles the stock board tree (cpp + dtc) and the two openwifi overlays (dtc), then fdtoverlay fuses all three into the board's final devicetree.dtb (and decompiles a full_devicetree.dts so you can inspect the merged result).Why it's built this way
This overlay-based device-tree system came out of the NLnet project Extensive openwifi support for OpenWRT. That project set out to modularize openwifi's hardware description and to remove its dependency on ADI Kuiper Linux, so that openwifi can target OpenWrt.
Most shipped boards include a fixed devicetree.dts
If a board directory already contains a prebuilt devicetree.dts, construct_device_tree.sh only recompiles the overlays and stops, trusting the shipped tree. The stock-.dts-plus-fdtoverlay path is what you use when bringing up a new board that doesn't have a prebuilt tree yet. The script keeps a board_name → stock .dts map internally (for example zed_fmcs2 → zynq-zed.dts, adrv9364z7020 → zynq-adrv9364.dts).
What the openwifi overlay adds¶
The shared openwifi_32_ad9361.dtso inserts (as device-tree fragments):
- A 24 MHz fixed clock. The overlay enables the FPGA fabric clocks (
fclk-enable = <0xf>on&clkc) because the kernel'szynq-7000.dtsiotherwise gates them off. It also sets the defaultinterrupt-parentto&intc, the Zynq-7000 interrupt controller. -
An
fpga-axi@0simple-bus holding all the AXI peripherals, including openwifi's cores. This is the address map the driver relies on:Node Address compatibleInterrupts sdr(no reg, the driver's bind node) sdr,sdr29, 30, 33, 34 tx_intf0x83c00000sdr,tx_intf34 openofdm_tx0x83c10000sdr,openofdm_txnone rx_intf0x83c20000sdr,rx_intf29, 30 openofdm_rx0x83c30000sdr,openofdm_rxnone xpu0x83c40000sdr,xpunone side_ch0x83c50000sdr,side_ch(DMA) tx_dma0x80400000xlnx,axi-dma-1.00.a35, 36 rx_dma0x80410000xlnx,axi-dma-1.00.a31, 32 cf-ad9361-lpc0x79020000adi,axi-ad9361none cf-ad9361-dds-core-lpc0x79024000adi,axi-ad9361-ddsnone The
sdrnode ties the driver to the DMA engines (dmas = <&rx_dma 1 &tx_dma 0>) and interrupts.side_chhas its own DMA pair. Ani2c@41600000bus (power monitor, ADC, EEPROM) is also declared. -
An
ad9361-phy@0SPI device onspi0(spi@e0006000), withcompatible = "adi,ad9361". It carries the long list ofadi,*RF and AGC tuning properties (LVDS mode, RX and TX bandwidths, synthesizer frequencies, gain-control tables, control GPIOs).
The 64-bit overlay (openwifi_64_ad9361.dtso) declares the same set of blocks for ZynqMP, with these differences:
interrupt-parent = <&gic>instead of&intc- clocks via
&zynqmp_clk, withfclk0..3declared explicitly - a different AXI address range (roughly
0xa00xxxxx) - an extra BRAM controller node
What a board overlay adds¶
Here is the complete overlays/zed_fmcs2.dtso, which is a good template:
/dts-v1/;
/plugin/;
/{
// 1. Board's AD9361 reference clock (40 MHz on this board)
fragment@0 {
target = <&clocks>;
__overlay__ {
clk_40M_fixed: clock@0 {
#clock-cells = <0x0>;
compatible = "fixed-clock";
clock-frequency = <40000000>;
clock-output-names = "ad9361_ext_refclk";
};
};
};
// 2. Point the AD9361 at that clock + set the DCXO tuning
fragment@1 {
target = <&ad9361_phy>;
__overlay__ {
clocks = <&clk_40M_fixed 0x0>;
adi,dcxo-coarse-and-fine-tune = <0x8 0x1720>;
};
};
// 3. Board LEDs wired to PS GPIO
fragment@2 {
target-path = "/";
__overlay__ {
leds {
compatible = "gpio-leds";
ld0 { label = "ld0:red"; gpios = <&gpio0 0x49 0x0>; };
/* ...ld1..ld7... */
};
};
};
};
A board overlay therefore usually supplies the AD9361 external reference clock frequency (40 MHz here, though boards with a VCXO or GPS may differ) and the DCXO tuning. It also declares the board LEDs and GPIO, and any peripherals or RF-switch controls unique to the board.
Porting the device tree to a new board¶
The address and interrupt of every FPGA block on the AXI bus must match between your FPGA build and your device tree. The FPGA build is the source of truth for those numbers, and the device tree has to agree.
A practical sequence:
-
Get the address map from your FPGA build. In Vivado, open your board's openwifi-hw project (
openwifi-hw/boards/<board_name>/), Open Block Design → Address Editor. This lists the base address of every AXI peripheral (thesdr,*cores, the DMA engines, the AD9361 cores) and, in the block diagram, their interrupt connections. See FPGA Development → Porting to a new board. -
Reuse the shared openwifi overlay if your addresses are standard. If your FPGA places the openwifi cores at the usual
0x83c0_xxxx(Zynq-7000) or0xa00x_xxxx(ZynqMP) addresses with the standard interrupts, you can useopenwifi_32_ad9361.dtsooropenwifi_64_ad9361.dtsounchanged. If you moved any block, edit that block'sreg = <...>andinterrupts = <...>in the overlay to match Vivado. -
Write your board overlay
overlays/<board_name>.dtso. Start from the closest existing overlay (zed_fmcs2.dtsofor a plain FMCOMMS board, ore310v2.dtsoorsdrpi.dtsofor boards with a VCXO, GPS, or extra GPIO). Set:- the AD9361 reference clock frequency (
clk_*_fixed→ad9361_ext_refclk) to your board's crystal or VCXO, - the DCXO tuning if applicable,
- LEDs and GPIO, and any RF-switch or port control your board needs (for example the ANTSDR RF-switch caveat noted in Supported Boards lives here).
- the AD9361 reference clock frequency (
-
Provide the stock board
.dts. Add aboard_name → stock .dtsentry to the map inconstruct_device_tree.shand place the matching stock ADI or Xilinx.dts(plus its.dtsiincludes) in the defaults folder. openwifi obtains stock trees by decompiling the ADI Linux image's.dtbwithdtc, then editing. -
Generate and sanity-check the tree. You need
dtcandfdtoverlay(from thedevice-tree-compilerpackage, see Building SD Images → Prerequisites):cd kernel_boot/boards ./construct_device_tree.sh <board_name> <32|64> # inspect the decompiled result: less <board_name>/full_devicetree.dtsConfirm the
fpga-axi@0block shows yoursdr,*nodes at the right addresses and thatad9361-phy@0has your clock. -
Boot and verify. After building
BOOT.BIN+ kernel + thisdevicetree.dtbinto an SD image (see Software Development Workflow), boot with a UART console attached. On a good boot you'll see the AD9361 probe and thesdr,sdrdriver bind. If it doesn't, the device-tree addresses or interrupts almost certainly disagree with the FPGA. Checkdmesgagainst thesdr0troubleshooting table before rechecking the addresses in step 1. Common failures (SPI flash environment, wrong DDR size, ZCU102 SD card or SODIMM, no UART) are in Troubleshooting.
The device tree is where the FPGA meets Linux
A board port has two halves that must agree. The FPGA side is the openwifi-hw Vivado project, which fixes the addresses and interrupts (see FPGA Development). The device-tree side, described on this page, declares those same addresses and interrupts to Linux. Once the two match, the rest of openwifi (driver, sdrctl, everything above) works unchanged. All of it is keyed off the sdr,* compatible strings, not the board.
Related pages¶
- Software Development Workflow: rebuilding the kernel, transferring images, and building a full SD card.
- FPGA Development → Porting to a new board: the FPGA half of a board port.
- Supported Boards: per-board hardware notes and the 32-bit vs 64-bit boot differences.
- Architecture: why the driver is a device-tree platform driver.
- Troubleshooting → Boot and networking: boot failures and fixes.