Boot, Kernel, and Device Tree¶
This page explains how an openwifi board boots: the boot image, the kernel and its patches, and, in the most detail, the device tree. The device tree declares where the FPGA blocks live, and 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.
If you already have a working board and only want to move it onto a newly built kernel and set of modules, go straight to Updating a running board, which covers the whole path from a PC-side build to sdr0 showing up in ifconfig -a. You can follow it 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 sequence: the SoC's boot ROM loads BOOT.BIN, whose FSBL initializes DDR and clocks, programs the FPGA bitstream, and hands off to U-Boot, which 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, which is why 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.shadditionally builds/collects the PMU firmware and the ARM Trusted Firmware BL31 stage (it candownloadand build ATF, matched to your Vitis version), then packs them 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/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/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 / kernel_config_zynqmp are full defconfig-style .config files (Linux 6.12, 32-bit ARM vs 64-bit ARM) with the ADI driver bundles enabled.
Updating a running board¶
Everything above is how a board is built and what it boots. This part is the routine operation: taking a board that already runs openwifi and moving it onto a kernel, a module set, or a driver you just built. The layout section comes first, because all three procedures after it are variations on getting the same files into the same two directories.
- Updating a board to a newly built kernel: the normal path: 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.
Picking the third when the kernel actually changed is the usual mistake, and it 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, giving openwifi32/openwifi64 and kernel_modules32/kernel_modules64 directories side by side on the rootfs partition. At this point the modules are only staged. Nothing is under /lib/modules yet.
The board-side script populate_kernel_image_module_reboot.sh is what finishes the job. The architecture selection already happened host-side (transfer_kernel_image_module_to_board.sh packs only the matching set), and the script uses uname -m only to pick the kernel image and device tree filenames (uImage + devicetree.dtb on 32-bit, Image + system.dtb on 64-bit). It:
- 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 a board that already boots openwifi and that you want to move onto a kernel you just built, 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, the board is reachable at 192.168.10.122 and your PC is at 192.168.10.1, which is what the transfer scripts hard-code. The same scripts are listed in short form under Bulk update helpers, and there is an FTP-based alternative there (sdcard_boot_update.sh plus wgd.sh remote) if you prefer to pull 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, so a new kernel 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, the module metadata (Module.symvers, modules.builtin, modules.builtin.modinfo, modules.order), 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
It unpacks the archive into /root/kernel_modules, moves the board-support modules into /root/openwifi/, creates the /lib/modules/$(uname -r) symlink, runs depmod, 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 that has no /lib/modules entry at all, modprobe mac80211 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 loads the FPGA image first if system_top.bit.bin is present in the directory, then insmods ad9361_drv and xilinx_dma, modprobes mac80211, and finally insmods tx_intf, rx_intf, openofdm_tx, openofdm_rx, xpu and sdr. sdr.ko registering itself with mac80211 is what creates the sdr0 interface.
Use ifconfig -a rather than plain ifconfig, because 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.
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, so you can do them by hand once steps 1 and 2 (build the kernel, rebuild the driver against it) are done. That is worth doing when your board is not at the hard-coded 192.168.10.122, when you only want part of the update, or when you want to see exactly 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. That flat layout is what /lib/modules/$(uname -r) points at on the board, and it is why 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, and a stale one loads with the wrong version magic or shadows the new module of the same name. On a slow link, tar -zcf kernel_modules.tar.gz kernel_modules, one scp, and tar -zxf on the board is the faster equivalent, which is what the scripts do.
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. The sync before umount is worth keeping, because a kernel image half-written to a FAT partition is a board that does not come back.
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¶
If a board is already up and you just rebuilt one or more modules against the same kernel it is running, 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. The one thing to get right is which directory each module lands in, and that follows directly from how wgd.sh loads it (this still relies 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:
# openwifi driver / board-support module -> the openwifi dir wgd.sh insmods from
scp driver/sdr.ko root@<board-ip>:/root/openwifi/
# a base in-tree kernel module (modprobe'd) -> the staged module tree
scp adi-linux-64/drivers/iio/adc/ad9361_drv.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 / 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¶
This is the central piece of a board port. The device tree is a data structure describing the hardware (every peripheral, its register address, its interrupts, its clocks) that Linux reads 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", and 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/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.dtso/openwifi_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: the AD9361 reference-clock frequency, board LEDs/GPIO, and any board-unique glue.
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, which set out to modularize openwifi's hardware description and to break its dependency on ADI Kuiper Linux so it 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: it trusts 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, enables the FPGA fabric clocks (
fclk-enable = <0xf>on&clkc, which the kernel'szynq-7000.dtsiotherwise gates off), and 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),compatible = "adi,ad9361", carrying the long list ofadi,*RF/AGC tuning properties (LVDS mode, RX/TX bandwidths, synthesizer frequencies, gain-control tables, control GPIOs).
The 64-bit overlay (openwifi_64_ad9361.dtso) declares the same conceptual set of blocks but for ZynqMP: interrupt-parent = <&gic> instead of &intc, clocks via &zynqmp_clk (with fclk0..3 declared explicitly), a different AXI address range (roughly 0xa00xxxxx), and 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... */
};
};
};
};
So a board overlay typically supplies: the AD9361 external reference clock frequency (40 MHz here, though boards with a VCXO/GPS may differ), the DCXO tuning, board LEDs/GPIO, and any board-unique peripherals or RF-switch controls.
Porting the device tree to a new board¶
The guiding principle: 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.dtso/openwifi_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.dtso/sdrpi.dtsofor boards with a VCXO/GPS/extra GPIO). Set:- the AD9361 reference clock frequency (
clk_*_fixed→ad9361_ext_refclk) to your board's crystal/VCXO, - the DCXO tuning if applicable,
- LEDs/GPIO and any RF-switch/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/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/interrupts almost certainly disagree with the FPGA. Checkdmesgagainst thesdr0troubleshooting table before rechecking the addresses in step 1. Common failures (SPI-flash env, wrong DDR size, ZCU102 SD/SODIMM, no UART) are in Troubleshooting.
The device tree is where the FPGA meets Linux
A board port is really two halves that must agree: the FPGA side (the openwifi-hw Vivado project, which fixes addresses/interrupts, see FPGA Development) and the device-tree side (this page, which declares those same addresses/interrupts to Linux). Get the two to match and the rest of openwifi (driver, sdrctl, everything above) works unchanged, because it's all 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.