Build Thread: HaLow for Raspberry Pi OS

Hi All,

We’ve been getting a number of requests in the community for help integrating Morse Micro HaLow onto a number of Raspberry Pi boards - specifically those running the Raspberry Pi OS. While some users have been successful with these integrations, and contributed their efforts back to the community (for example HaLow Integration on Raspberry Pi 5 and Successful bring up of HaLow on a Raspberry Pi Zero 2W with NAT forwarding!), we wanted to create a more specific build thread so we can highlight particular tips, and add some application specific detail to porting HaLow than what is not in the scope of App Note 24.

Before You Begin

Morse Micro ships an evaluation kit, the EKH01, based on a Raspberry Pi 4B. These evaluation kits run the Morse Micro OpenWrt fork out of the box. As one of our primary test platforms, we recommend users use Morse Micro’s OpenWrt fork where possible.

If you still wish to proceed with a Debian OS such as the Raspberry Pi OS. Continue reading!

This guide is based on some of the steps described in App Note 24. For at least this first post, we’re focused on only bringing the interface up as a client to an existing network, leveraging the hosts existing user space software for connection management.

Hardware Used

For this guide we’re using the following hardware:

Note that the Morse Micro MMECH06 is the hat used in the EKH01 mentioned earlier. While not purchasable on its own, the design files are publicly available. This guide will additionally mention the software configuration required for the SeeedStudio WM6108 and SeeedStudio WM1302 Pi Hat as a carrier.

Preparing the Raspberry Pi

Prepare an SD card with Raspberry Pi OS.

For the purposes of this guide we will be using the last bookworm release of the Raspberry Pi OS. This can also be installed with the Raspberry Pi imager.

📝Kernel Support

:memo:The 1.16.4 Morse Micro software release supports the following kernel versions:

  • 4.9.80
  • 4.14.98
  • 4.19.66
  • 5.4.83
  • 5.10.11
  • 5.15.61
  • 6.1.21
  • 6.6.19
  • 6.12.21

From 1.15.3, patching the kernel is practically required, so sticking as close to one of these versions as possible will make integration significantly smoother. In a subsequent post to this thread, we will walk through applying the patches to a more recent bugfix version of the kernel, and modifying the Morse driver to suit an API change in later versions of 6.12.

Install Software Dependencies

To keep this guide simple, we will be compiling the Linux kernel, and the Morse Micro driver on the Raspberry Pi. Boot the Pi, and install necessary tools for compiling the kernel.

sudo apt install fakeroot build-essential libncurses-dev xz-utils libssl-dev flex libelf-dev bison git dtc

Get Required Sources

Linux Kernel

As mentioned, we will be patching the kernel. So instead of fetching sources from apt, we will be cloning them from GitHub. The release notes for 2025-05-13 indicate that we need commit hash 3dd2c2c, tagged as stable_20250428. Clone this with

cd ~
git clone --branch stable_20250428 https://github.com/raspberrypi/linux
cd linux

This will take some time to clone, as the kernel is large. Once complete we can add the Morse Micro remote containing necessary patches with:

git remote add morse https://github.com/MorseMicro/rpi-linux.git
git fetch morse

Once fetched, we can cherry-pick the 6.12.21 Morse Micro patches onto the kernel version we need with

git cherry-pick b37a3f6bb..mm/rpi-6.12.21/1.16.x

In this instance, two patches will fail due to changes between 6.12.21 and 6.12.25! These patches won’t be too difficult to fix. On failure use git status to see what has failed, and resolve the conflicts as required.

Morse Micro Driver

Simply get this from the Morse Micro GitHub

cd ~
git clone https://github.com/MorseMicro/morse_driver.git
cd morse_driver
git submodule --init --recursive

Morse Micro Firmware Binaries

When downloading Morse Micro firmware, make sure it matches the driver you are using!

cd ~
git clone https://github.com/MorseMicro/morse-firmware.git --branch 1.16

Building and Installing the Kernel

Now that we have everything prepared. Compile the kernel.

cd ~/linux
make bcm2712_defconfig
sed -r -i 's/CONFIG_LOCALVERSION=\"(.*)\"/CONFIG_LOCALVERSION=\"\1-morse\"/g' .config
make -j6 Image.gz modules dtbs

Note the CONFIG_LOCALVERSION will be -v8-16k-morse.

This kernel compilation will take some time, especially when running it on a Raspberry Pi.

Then install the kernel modules, and place the kernel into the boot partition.

sudo make -j6 modules_install
sudo cp arch/arm64/boot/Image.gz /boot/firmware/kernel_2712-morse.img

Building and Installing the Morse Micro driver

Run make in the Morse Micro source, pointing at the kernel you just compiled. See the Kconfig file for descriptions of other configuration options available to the driver.
In our case, we’re only compiling the SPI driver.

cd morse_driver
make KERNEL_SRC=~/linux/ CONFIG_WLAN_VENDOR_MORSE=m CONFIG_MORSE_SPI=y CONFIG_MORSE_USER_ACCESS=y CONFIG_MORSE_VENDOR_COMMAND=y

Then install into the modules directory with

sudo make KERNEL_SRC=~/linux modules_install

Installing Morse Micro Chip Firmware

The morse-firmware repository contains both Morse Micro chip firmware, and module configuration files for our module partners who have agreed to allow us to distribute the files.

cd ~/morse-firmware
sudo make install
📝 Board Configuration Files (BCFs)

:memo: A note on Board Configuration Files (BCFs)

The driver will attempt to read the “board type” from the chip, if the module vendor has programmed it. This enables automatic selection via the bcf_boardtype_ symbolic links installed into /lib/firmware/morse/.

If the module vendor has not programmed OTP, you will need to specify it. For example, for the Quectel FGH100MHAAMD used on the SeeedStudio WM6108, you will have to create the following file to /etc/modprobe.d

echo  "options morse bcf=bcf_fgh100mhaamd.bin" | sudo tee -a /etc/modprobe.d/morse.conf

Creating a Device Tree Node

Device Trees are the biggest challenge for most users! For SPI and SDIO devices, they are unavoidable. If using USB (with MM8108), you can skip this section! Before constructing a device tree, a user should find the schematics for any carrier board used and map the Host Processor GPIO pins to the Morse Micro Wi-Fi HaLow module pins.

In our case, we’re using a Morse Micro MMECH06 from an EKH01 configured for SPI. These two schematic snippets should be sufficient to build the device tree:

From this, we can build the following table:

MM6108 Raspberry Pi
RESET GPIO5
BUSY GPIO7
WAKE GPIO3
INTERRUPT GPIO25
SCLK GPIO11
MOSI GPIO10
MISO GPIO9
CS GPIO8

Note that this carrier board has a JTAG RESET connected (GPIO4), which we should verify is in the correct deasserted state to ensure the chip boots.

Now that we have the table, we can construct a devicetree overlay file. For the MMECH06 on a Raspberry Pi 5 we create the following, morse-spi.dts:

morse-spi.dts
/dts-v1/;
/plugin/;

/ {
        compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709", "brcm,bcm2711";

        fragment@0 {
                target = <&spi0>;
                frag0: __overlay__ {
                        pinctrl-0 = <&rp1_spi0_gpio9 &rp1_spi0_cs_gpio7 &morse_wake &morse_trst &morse_busy &morse_irq &morse_reset>;
                        cs-gpios = <&gpio 8 1>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "okay";

                        mm6108: mm6108@0 {
                                compatible = "morse,mm610x-spi";
                                reg = <0>;
                                reset-gpios = <&gpio 5 0>;
                                power-gpios = <&gpio 3 0>,
                                              <&gpio 7 0>;
                                spi-irq-gpios = <&gpio 25 0>;
                                spi-max-frequency = <50000000>;
                                status = "okay";
                        };
                        spidev@0 {
                                reg = <0>;
                                status = "disabled";
                        };
                        spidev@1 {
                                reg = <1>;
                                status = "disabled";
                        };

                };
        };

        fragment@1 {
                target = <&rp1_spi0_cs_gpio7>;
                frag1: __overlay__ {
                        function = "gpio";
                        pins = "gpio8";
                        bias-pull-up;
                };
        };

        fragment@2 {
                target = <&gpio>;
                frag2: __overlay__ {
                        morse_wake: morse_wake {
                                function = "gpio";
                                pins = "gpio3";
                                output-high;
                                bias-disable;
                        };

                        morse_busy: morse_busy {
                                function = "gpio";
                                pins = "gpio7";
                                input;
                                bias-pull-down;
                        };

                        morse_irq: morse_irq {
                                function = "gpio";
                                pins = "gpio25", "gpio26";
                                bias-pull-up;
                                input;
                        };

                        morse_trst: morse_trst {
                                function = "gpio";
                                pins = "gpio4";
                                bias-disable;
                                input;
                        };

                        morse_reset: morse_reset {
                                function = "gpio";
                                pins = "gpio5";
                                output-high;
                                bias-disable;
                        };
                };
        };

        fragment@3 {
                target = <&rp1_spi0_gpio9>;
                frag3: __overlay__ {
                        bias-disable;
                };
        };
};

This file contains device tree “fragments” which are used to modify, at boot time, the flattened device tree handed to the kernel by the bootloader. The base devicetree file for the Raspberry Pi 5 can be found at arch/arm64/boot/dts/broadcom/bcm2712-rpi-5-b.dts in the Linux kernel source.

The first overlay fragment, fragment@0, modifies the spi0 node to claim a number of pin configurations with the pin controller (see pinctrl-0); sets the cs-gpios as required for the board; creates a new mm6108@0 child node; and disables the other spidev nodes configured by default on the Raspberry Pi DTS.

The mm6108 node first sets the compatible, morse,mm610x-spi to correctly match against our driver; sets reset, wake, busy, and interrupt gpios; and enables the node with status="okay". The reg = <0> line in this node tells the SPI host controller that the MM6108 is on the first chip select line!

The second fragment overrides the default SPI CS definition, rp1_spi0_cs0_gpio7, to point to GPIO 8 instead.

The third fragment creates all additional required GPIO configurations as their own nodes. Note that these are the labels claimed by the spi0 pinctrl-0 property. This sets functions to GPIO and pullup/pulldown states as required for each pin.

On the RPi5 we noticed rpi1_spi0_gpio9, a node which sets GPIO9, 10, 11 to the correct “alternate function” for SPI has pull downs enabled by default. This conflicts with the pull ups on most carrier boards. We disable this internal bias so high and low states can be correctly defined.

Wow, bit of a mouthful! Now that we have morse-spi.dts, we can compile it into a binary overlay file as required by the kernel.

dtc -I dts -O dtb morse-spi.dts -o morse-spi.dtbo

Then, install this into the overlays folder in the bootloader partition.

sudo cp morse-spi.dtbo /boot/overlays/morse-spi.dtbo

Configuring the Bootloader

Now that everything is installed, we can configure the Raspberry Pi bootloader to start out new kernel instead, and enable the device tree overlays we created.

echo "dtoverlay=morse-spi" | sudo tee -a /boot/firmware/config.txt
echo "dtparam=spi-on" | sudo tee -a /boot/firmware/config.txt
echo "kernel=kernel_2712-morse.img" | sudo tee -a /boot/firmware/config.txt

Now we can reboot the device, and if everything is configured properly we should have a second Wiphy show up in iw phy.

Connecting to a HaLow network

Due to a lack of 802.11ah support in the Linux kernel, the Morse Micro driver pretends to be an 802.11ac device. This means it shows up in the kernel with 5 GHz frequencies!
For basic functionality, this allows it to work out of the box with most user space tooling! Now that we have a driver loaded, we can connect the Raspberry Pi to a monitor (or via VNC) and first set the country from the top right
set-country

Most MM6108 devices only support USA, Australia, and Canada.

We can open the connections dropdown again, and expose the scan list (note the 5G)

Allowing us to select a network and connect.

Type in the password, and it should connect!

I hope this is helpful for some of you in the community!

Additional Notes

Additional Notes

  • The 5G channel faking is convenient, especially for older systems, but it can get very confusing. The Morse Micro OpenWrt fork has a large amount of changes included to unwind that channel faking, and present the right channel information.
  • We are fixing this properly! But it is a slow process. Morse Micro has been very active in the Linux kernel over the last 6 months submitting patches for 802.11ah support. Soon, we will submit a “native” 802.11ah driver to the upstream kernel - though it will be minimally AP + station only at first.
  • Patching the Linux kernel is challenging, but necessary for many features. You may not need all of the kernel patches if you’re not using particular features of the Morse driver (eg Mesh, channel switch announcements, SPI). In this case, you could opt for dropping the corresponding patches, particularly if it makes integration simpler. Read the patch commit messages for when they are required.

Other Hardware

At the start, I promised the device tree for the SeeedStudio WM6108 on a WM1302 carrier. From the WM6108 schematics and the WM1302 hardware overview we can construct the following table

MM6108 WM1680 WM1320 RPi
SPI Clock 45 23 GPIO11
SPI MOSI 49 19 GPIO10
SPI MISO 47 21 GPIO9
SPI CS 51 24 GPIO8
SPI IRQ 10 29 GPIO5
RESETN 22 11 GPIO17
WAKE 33 16 GPIO23
BUSY 31 18 GPIO24

This allows us to create the following wm6108-spi.dts for the Raspberry Pi 5.

wm6108-spi.dts
/dts-v1/;
/plugin/;

/ {
        compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709", "brcm,bcm2711";

        fragment@0 {
                target = <&spi0>;
                frag0: __overlay__ {
                        pinctrl-0 = <&rp1_spi0_gpio9 &rp1_spi0_cs_gpio7 &morse_wake &morse_trst &morse_busy &morse_irq &morse_reset>;
                        cs-gpios = <&gpio 8 1>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "okay";

                        mm6108: mm6108@0 {
                                compatible = "morse,mm610x-spi";
                                reg = <0>;
                                reset-gpios = <&gpio 17 0>;
                                power-gpios = <&gpio 23 0>,
                                              <&gpio 24 0>;
                                spi-irq-gpios = <&gpio 25 0>;
                                spi-max-frequency = <50000000>;
                                status = "okay";
                        };
                        spidev@0 {
                                reg = <0>;
                                status = "disabled";
                        };
                        spidev@1 {
                                reg = <1>;
                                status = "disabled";
                        };

                };
        };

        fragment@1 {
                target = <&rp1_spi0_cs_gpio7>;
                frag1: __overlay__ {
                        function = "gpio";
                        pins = "gpio8";
                        bias-pull-up;
                };
        };

        fragment@2 {
                target = <&gpio>;
                frag2: __overlay__ {
                        morse_wake: morse_wake {
                                function = "gpio";
                                pins = "gpio23";
                                output-high;
                                bias-disable;
                        };

                        morse_busy: morse_busy {
                                function = "gpio";
                                pins = "gpio24";
                                input;
                                bias-pull-down;
                        };

                        morse_irq: morse_irq {
                                function = "gpio";
                                pins = "gpio5";
                                bias-pull-up;
                                input;
                        };

                        morse_reset: morse_reset {
                                function = "gpio";
                                pins = "gpio17";
                                output-high;
                                bias-disable;
                        };
                };
        };

        fragment@3 {
                target = <&rp1_spi0_gpio9>;
                frag3: __overlay__ {
                        bias-disable;
                };
        };
};

Make sure to set the BCF options in modprobe.d when using this hardware!

We will follow up shortly with a post for any changes for the latest Raspberry Pi OS based on Trixie!

7 Likes

This is incredible!! Can’t wait to build this and start playing with it. Thank you so much for releasing this guide!

2 Likes

A post was split to a new topic: SPI bringup on Raspberry Pi

Hi

We’ve been working on getting this to work and i believe we now have a functioning STA (we can’t really test it because we don’t have a halow AP) Is there a way to create an Access Point using this Raspberry Pi configuration? We have tried getting an access point to work with no success.

Thanks

5 posts were split to a new topic: Kernel compilation errors after patching Raspberry Pi OS kernel

System: Raspberry Pi 4B, rasberry PI OS Trixie

Id like to point a couple of errors in the initial post in case someone gets stuck just like i did.

When cherry-picking, use the command git cherry-pick b37a3f6bb..morse/mm/rpi-6.12.21/1.16.x
The morse is missing from the instructions.
In addition to this, there seem to be some issues with the morse-driver , which fails to build because function declarations have inputs of ints while the definitions have inputs of an enum. While i fixed this for morse_log_is_enabled in debug.h, there are more issues in the morse_firmware_init function in firmware.h. Here, it does not seem to be solvable by just changing the uint to enum morse_config_test_mode and including morse.h, because then I get the error

morse_driver/firmware.h:150:50: error: ‘enum morse_config_test_mode’ declared inside parameter list will not be visible outside of this definition or declaration [-Werror]
  150 | int morse_firmware_init(struct morse *mors, enum morse_config_test_mode test_mode);

This is happening because the compiler is treating warnings as errors as specified in the makefile, but before I go ahead and start making changes to the code, I want to ask if anyone has been able to compile this just based off the the instructions and if I am doing anything wrong

1 Like

Hey @akshatd

Thanks for the correction with the ref range. I’ll modify the original post.

I see you’re using Trixie. This post was specifically targeting a Bookworm release, though I will put a Trixie version up soon as a follow up to this thread.

By default, Trixie will pull a newer compiler version which is stricter on elevating some of the warnings as errors. If you’re targeting the default Trixie kernel, you would have also come across some areas where patches didn’t apply cleanly

Thank you so much for the quick response! Ill wait for a trixie post then because we cant really fall back to bookworm, a lot of our code depends on some C++20 features being available in the compiler.

btw theres another typo in the config.txt file when turning SPI on, it should be dtparam=spi=on instead of dtparam=spi-on

looking forward to the Trixie post!

2 Likes

Hi @alwym

For building an AP, you might want to consider using OpenWrt.

If you must use bookworm, then you’ll need to follow the compilation guide for hostap_s1g from the Linux Porting Guide. You might also find this thread to be helpful for configs and controlling NetworkManager.

I’m having compile issues but with kernel. I’m using full sha1 commit hash so I think my cherry-pick commands are still correct. I did that because the commit hash with repo paths and .. wasn’t working.

Hi ajudge

Happy new year! Any updates on the trixie build guide?

Haven’t had a chance to bring it up yet. Will get around to it as soon as I can!

Can i do similarly on NVIDIA Jetson Orin NX/nano using the MM8108-EKH19 USB Adapter?

@nasihabdulnazer yes you should be able too! You won’t need to worry about device tree either.

Thanks @ajudge for confirming that it should work on Jetson and that device tree changes aren’t required.

Hi, I’m trying to get MM8108-EKH19 HaLow working on a Jetson Orin Nano (JetPack 6 / Ubuntu 22.04 / kernel 5.15.148-tegra), but I haven’t had any luck yet.

I’m new to this and would really appreciate step-by-step instructions, similar to the Raspberry Pi guide, but specifically for Jetson.

In particular, I’m not sure how to:

  1. Prepare the Jetson environment for building the kernel driver

  2. Install the firmware

  3. Handle any Jetson-specific differences

Any guidance, examples, or links would be extremely helpful. Thank you so much!

Buddy! We are live with meshing between my pi5 and OPENManet. The final bit of info I found was setting BATMAN routing algorithm to match OPENManet which is running V instead of IV. Additional steps to join mesh were:

batctl ra BATMAN_V # set routing algo to V instead of IV
batctl if add wlan1 # adds wlan1 to batman and creates bat0
ip link set bat0 up
ip addr add 10.41.x.x/16 # match OPENManet subnet

OPENManet verify

root@BCM2711-2067:~# batctl n
[B.A.T.M.A.N. adv 2024.3-openwrt-6, MainIF/MAC: wlan0/11:22:33:44:55:66 (bat0/11:22:33:44:55:66 BATMAN_V)]
         Neighbor   last-seen      speed           IF
11:22:33:44:55:66    0.420s (        7.1) [     wlan0]

Pi5 verify

root@pi5-halow-1:~# batctl n
[B.A.T.M.A.N. adv 2025.5-1-g35208d12, MainIF/MAC: wlan1/11:22:33:44:55:66 (bat0/11:22:33:44:55:66 BATMAN_V)]
IF             Neighbor              last-seen
11:22:33:44:55:66    1.492s (        7.1) [     wlan1]

I will now proceed with some field testing for range. I need to order another seeedstudio hat so I can actually have 3 node and testing mesh routing :winking_face_with_tongue:

I got the wpa_supplicant format from OPENManet and it works well on the pi5. I got all the opclass and channel matchups for easy switching of the wpa_supplicant_s1g file after you make a change on OPENManet.

Channel Width Channels
1 Mhz 3 - 39 (increments of 2)
op_class=68
s1g_prim_chwidth=0
s1g_prim_1mhz_chan_index=0
[u]
2 Mhz[/u] 6 - 46 (increments of 4)
op_class=69
s1g_prim_chwidth=0
s1g_prim_1mhz_chan_index=0
4 Mhz 8 / 16 / 24 / 32 / 40 / 48
op_class=70
s1g_prim_chwidth=1
s1g_prim_1mhz_chan_index=1
8 Mhz 12 / 28 / 44
op_class=71
s1g_prim_chwidth=1
s1g_prim_1mhz_chan_index=3

Final script I used

Final working script. I did not look in to further optimization of the wpa_supplicant_s1g parameters and instead stuck with what worked after copying from OPENManet
####################################
# Hardware:                        # 
# - Raspberrypi 5                  # 
# - Seeedstudio WM1302 and WM6108  # 
# OS:        Bookworm lite latest  #
####################################

# Install deps   
sudo apt install fakeroot build-essential libncurses-dev xz-utils libssl-dev flex libelf-dev bison git device-tree-compiler bc screen -y

git clone --branch stable_20250428 https://github.com/raspberrypi/linux
cd linux
git remote add morse https://github.com/MorseMicro/rpi-linux.git
git fetch morse
git cherry-pick b37a3f6bb..morse/mm/rpi-6.12.21/1.16.x

# I ran in to issues on occasion with possibly messing up my merge conflict resolutions and opted to just entirely copy/paste mesh_hwmp.c from the morse/mm/rpi-6.12.21/1.16.x branch after resolving all other conflicts

cd ~ 
git clone https://github.com/MorseMicro/morse_driver.git
cd morse_driver
git submodule update --init --recursive
cd ~
git clone https://github.com/MorseMicro/morse-firmware.git --branch 1.16


cd ~/linux
make bcm2712_defconfig
sed -r -i 's/CONFIG_LOCALVERSION=\"(.*)\"/CONFIG_LOCALVERSION=\"\1-morse\"/g' .config
make -j6 Image.gz modules dtbs

sudo make -j6 modules_install
sudo cp arch/arm64/boot/Image.gz /boot/firmware/kernel_2712-morse.img

cd ~/morse_driver
make KERNEL_SRC=~/linux/ CONFIG_WLAN_VENDOR_MORSE=m CONFIG_MORSE_SPI=y CONFIG_MORSE_USER_ACCESS=y CONFIG_MORSE_VENDOR_COMMAND=y
sudo make KERNEL_SRC=~/linux modules_install

cd ~/morse-firmware
sudo make install

cat << EOF >> wm6108-spi.dts
/dts-v1/;
/plugin/;

/ {
        compatible = "brcm,bcm2835", "brcm,bcm2836", "brcm,bcm2708", "brcm,bcm2709", "brcm,bcm2711", "brcm,bcm2712";

        fragment@0 {
                target = <&spi0>;
                frag0: __overlay__ {
                        pinctrl-0 = <&rp1_spi0_gpio9 &rp1_spi0_cs_gpio7 &morse_wake &morse_busy &morse_irq &morse_reset>;
                        cs-gpios = <&gpio 8 1>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "okay";

                        mm6108: mm6108@0 {
                                compatible = "morse,mm610x-spi";
                                reg = <0>;
                                reset-gpios = <&gpio 17 0>;
                                power-gpios = <&gpio 23 0>,
                                              <&gpio 24 0>;
                                spi-irq-gpios = <&gpio 25 0>;
                                spi-max-frequency = <50000000>;
                                status = "okay";
                        };
                        spidev@0 {
                                reg = <0>;
                                status = "disabled";
                        };
                        spidev@1 {
                                reg = <1>;
                                status = "disabled";
                        };

                };
        };

        fragment@1 {
                target = <&rp1_spi0_cs_gpio7>;
                frag1: __overlay__ {
                        function = "gpio";
                        pins = "gpio8";
                        bias-pull-up;
                };
        };

        fragment@2 {
                target = <&gpio>;
                frag2: __overlay__ {
                        morse_wake: morse_wake {
                                function = "gpio";
                                pins = "gpio23";
                                output-high;
                                bias-disable;
                        };

                        morse_busy: morse_busy {
                                function = "gpio";
                                pins = "gpio24";
                                input;
                                bias-pull-down;
                        };

                        morse_irq: morse_irq {
                                function = "gpio";
                                pins = "gpio5";
                                bias-pull-up;
                                input;
                        };

                        morse_reset: morse_reset {
                                function = "gpio";
                                pins = "gpio17";
                                output-high;
                                bias-disable;
                        };
                };
        };

        fragment@3 {
                target = <&rp1_spi0_gpio9>;
                frag3: __overlay__ {
                        bias-disable;
                };
        };
};
EOF

dtc -I dts -O dtb wm6108-spi.dts -o wm6108-spi.dtbo
cp wm6108-spi.dtbo /boot/overlays/

echo "dtoverlay=wm6108-spi" | sudo tee -a /boot/firmware/config.txt
echo "dtparam=spi=on" | sudo tee -a /boot/firmware/config.txt
echo "kernel=kernel_2712-morse.img" | sudo tee -a /boot/firmware/config.txt
echo  "options morse bcf=bcf_fgh100mhaamd.bin country=US" | sudo tee -a /etc/modprobe.d/morse.conf

reboot

sudo apt update
sudo apt install -y libnl-3-dev libnl-genl-3-dev libnl-route-3-dev libusb-dev libusb-1.0-0-dev

cd ~
git clone https://github.com/MorseMicro/hostap.git
cd hostap
git checkout 1.15.3
cp wpa_supplicant/defconfig wpa_supplicant/.config
cp hostapd/defconfig hostapd/.config

# Modify wpa_supplicant/.config to enabled MESH
# Modify wpa_supplicant/.config to enabled MESH
# Modify wpa_supplicant/.config to enabled MESH
# Modify wpa_supplicant/.config to enabled MESH
# Modify wpa_supplicant/.config to enabled MESH
# Modify wpa_supplicant/.config to enabled MESH
# Modify wpa_supplicant/.config to enabled MESH

# Build wpa_supplicant
## Make
CFLAGS="-Wno-error=sign-compare -Wno-error=deprecated-declarations -I $STAGING_DIR/include/libnl3 -I $STAGING_DIR/include/" LDFLAGS="-L $STAGING_DIR/lib/ -L $STAGING_DIR/lib64/" DESTDIR="$STAGING_DIR" BINDIR=/usr/sbin make MORSE_VERSION=rel_1_15_3_2025_Apr_16 -C wpa_supplicant/
## Make install
CFLAGS="-Wno-error=sign-compare -Wno-error=deprecated-declarations -I $STAGING_DIR/include/libnl3 -I $STAGING_DIR/include/" LDFLAGS="-L $STAGING_DIR/lib/ -L $STAGING_DIR/lib64/" DESTDIR="$STAGING_DIR" BINDIR=/usr/sbin make install MORSE_VERSION=rel_1_15_3_2025_Apr_16 -C wpa_supplicant/

# Build hostapd
## Make
CFLAGS="-Wno-error=sign-compare -I $STAGING_DIR/include/libnl3 -I $STAGING_DIR/include/" \
LDFLAGS="-L $STAGING_DIR/lib/ -L $STAGING_DIR/lib64/" \
DESTDIR="$STAGING_DIR" BINDIR=/usr/sbin \
make -j6 MORSE_VERSION=rel_1_15_3_2025_Apr_16 -C hostapd/
## Make install
CFLAGS="-Wno-error=sign-compare -I $STAGING_DIR/include/libnl3 -I $STAGING_DIR/include/" \
LDFLAGS="-L $STAGING_DIR/lib/ -L $STAGING_DIR/lib64/" \
DESTDIR="$STAGING_DIR" BINDIR=/usr/sbin \
make install -j6 MORSE_VERSION=rel_1_15_3_2025_Apr_16 -C hostapd/


# morse_cli
cd ~
git clone https://github.com/MorseMicro/morse_cli.git
cd morse_cli
git checkout 1.15.3
make -j6 CONFIG_MORSE_TRANS_NL80211=1
cp morse_cli /usr/sbin/

# Start wpa_supplicant
cat << EOF >> /root/wpa_supplicant_s1g.conf
network={
        ssid="YOUR_SSID"
        sae_password="YOUR_PASSWORD"
        key_mgmt=SAE
        mode=5
        channel=46 #CHANGE AS NEEDED
        op_class=69 #CHANGE AS NEEDED
        country="US"
        s1g_prim_chwidth=0 #CHANGE AS NEEDED
        s1g_prim_1mhz_chan_index=0 #CHANGE AS NEEDED
        dtim_period=1
        mesh_rssi_threshold=-85
        dot11MeshHWMPRootMode=0
        dot11MeshGateAnnouncements=1
        mbca_config=1
        mbca_min_beacon_gap_ms=25
        mbca_tbtt_adj_interval_sec=60
        dot11MeshBeaconTimingReportInterval=10
        mbss_start_scan_duration_ms=2048
        mesh_beaconless_mode=0
        mesh_dynamic_peering=0
        pairwise=CCMP
        ieee80211w=2
        beacon_int=1000

}
EOF

# -B to background
wpa_supplicant_s1g -B -D nl80211 -i wlan1 -c /root/wpa_supplicant_s1g.conf

# BATMAN-ADV
batctl ra BATMAN_V
batctl if add wlan1
ip link set bat0 up
ip addr add 10.41.x.x/16 (IP from openmanet subnet)

# Verify neighbors
# Should see openmanet as neighbor
batctl n

    [B.A.T.M.A.N. adv 2025.5-1-g35208d12, MainIF/MAC: wlan1/11:22:33:44:55:66 (bat0/11:22:33:44:55:66 BATMAN_V)]
    IF             Neighbor              last-seen
    11:22:33:44:55:66   0.524s (        7.1) [     wlan1]

Testing results

About 20 feet of separation with no obstructions. Both devices on external battery and ever so slightly under powered.

| Channel Width | Test 1 Throughput | Test 2 Throughput | Average Throughput |

| ------------- | ----------------- | ----------------- | ------------------ |

| 1 MHz | 0.94 Mbps | 0.71 Mbps | 0.82 Mbps |

| 2 MHz | 3.53 Mbps | 3.82 Mbps | 3.68 Mbps |

| 4 MHz | 8.28 Mbps | 8.38 Mbps | 8.33 Mbps |

| 8 MHz | 4.95 Mbps | 5.36 Mbps | 5.16 Mbps |

3 Likes

@castironclay
Are you having kernel panics whenever a node leaves (cleanly/power loss/reboot/etc) the mesh? I’ve followed your steps (same version of batman-adv too) but get kernel panics whenever I use BATMAN_V but it works fine with BATMAN_IV, and works fine without BATMAN at all.

My test setup is 3 pi5’s with the same build. Regardless of which one I reboot, the other 2 always kernel panic in 10-15 seconds.

Or @ajudge any ideas?

Appreciate any guidance!

uname -r
6.12.25-v8-16k-morse+

Bring up steps (failed troubleshooting steps commented out):

# nmcli dev set wlan1 managed no
wpa_supplicant_s1g -B -D nl80211 -i wlan1 -c /root/wpa_supplicant_s1g.conf -C /var/run/wpa_supplicant_s1g
# ip link set dev wlan1 mtu 1532
modprobe batman-adv
batctl ra BATMAN_V
batctl if add wlan1
# batctl hardif wlan1 elp_interval 5000
# batctl -m bat0 fragmentation 0
# batctl hardif wlan1 throughput_override 2mbit
# ip link set dev bat0 mtu 1500
ip link set bat0 up
ip addr add 169.254.99.1/16 dev bat0

And my wpa_supplicant_s1g.conf:

cat wpa_supplicant_s1g.conf
ctrl_interface=/var/run/wpa_supplicant_s1g
country=US
sae_pwe=1
max_peer_links=10
mesh_fwding=1
network={
    ssid="test"
    sae_password="test"
    key_mgmt=SAE
    mode=5
    channel=26
    op_class=69
    country="US"
    s1g_prim_chwidth=0
    s1g_prim_1mhz_chan_index=0
    dtim_period=1
    mesh_rssi_threshold=-85
    dot11MeshHWMPRootMode=0
    dot11MeshGateAnnouncements=1
    mbca_config=1
    mbca_min_beacon_gap_ms=25
    mbca_tbtt_adj_interval_sec=60
    dot11MeshBeaconTimingReportInterval=10
    mbss_start_scan_duration_ms=2048
    mesh_beaconless_mode=0
    mesh_dynamic_peering=0
    pairwise=CCMP
    ieee80211w=2
    beacon_int=1000
}

I’m not sure if I resolved the merge conflict on mesh_hwmp.c correctly, either. I tried to keep the intent of both HEAD and the morse commit:

// HEAD
if (SN_GT(mpath->sn, orig_sn) || (mpath->sn == orig_sn && (rcu_access_pointer(mpath->next_hop) != sta ? !is_metric_better(new_metric, mpath->metric) : new_metric >= mpath->metric))) {
  process = false;
  fresh_info = false;
}

// MORSE (inferred from conflict)
if (SN_GT(mpath->sn, orig_sn) || (mpath->metric && (rcu_access_pointer(mpath->next_hop) != sta) && (mult_frac(new_metric, 10, 9) >= mpath->metric))) {
   mhwmp_dbg(sdata, "skip long path orig:%pM sa:%pM \n", orig_addr, mgmt->sa);
   process = false;
   fresh_info = false;
}

// MY MERGED LOGIC
if (SN_GT(mpath->sn, orig_sn) || (mpath->metric && (rcu_access_pointer(mpath->next_hop) != sta) && (mult_frac(new_metric, 10, 9) >= mpath->metric)) || (mpath->sn == orig_sn && (rcu_access_pointer(mpath->next_hop) != sta ? !is_metric_better(new_metric, mpath->metric) : new_metric >= mpath->metric))) {
    if (mpath->metric && (rcu_access_pointer(mpath->next_hop) != sta) && (mult_frac(new_metric, 10, 9) >= mpath->metric))
      mhwmp_dbg(sdata, "skip long path orig:%pM sa:%pM \n", orig_addr, mgmt->sa);
    process = false;
    fresh_info = false;
}

I actually have 2 of my pi5s running now and will test this although I’m not aware of any kernel panics.

I will say I struggled with the merging of that same file and ultimately grabbed the entire file from the mm branch and wholesale replaced the mainline version of it. Maybe try that in the event that’s messing it up. I’ll report back if I see kernel panics and will share some additional scripts I used. Also I’ll update the guide above with my hailmary troubleshooting attempt.

Also the only reason I did BATMAN_V was interoperability with OPENManet. Not a requirement.

I can definitely give this a try!

The kernel panic only came up when I introduced a third node. Everything was smooth with BATMAN_V with two nodes.

1 Like

I saw no kernel panic and even monitored with watch -n 1 batctl n to see when the neighbor came back online after I power cycled a node. I have done up to 4 nodes so far without issue.

Below are some files I ended up creating and work rather well. The bat0.sh can of course be modified for a different IP scheme.

# /root/bat0.sh

#!/bin/bash
sleep 3
batctl ra BATMAN_V
batctl if add wlan1
ip addr add 10.0.0.1/24 dev bat0
ip link set bat0 up

# /etc/systemd/system/mesh.service
[Unit]
Description=Run wpa_supplicant_s1g as the final boot service
Documentation=man:wpa_supplicant(8)

# Wait for the whole system to be up before starting
After=network.target network-online.target
After=local-fs.target remote-fs.target
After=systemd-modules-load.service
After=systemd-remount-fs.service
After=basic.target
After=multi-user.target

Wants=multi-user.target

[Service]
# The command itself daemonises itself with “-B”, so “simple” works.
Type=simple

# Start the process
ExecStart=/usr/sbin/wpa_supplicant_s1g -D nl80211 -i wlan1 -c /root/wpa_supplicant_s1g.conf
ExecStartPost=/root/bat0.sh

# When the unit is stopped (or restarted) kill any lingering wpa_supplicant_s1g
ExecStop=/usr/bin/pkill -f wpa_supplicant_s1g

# If you want the service to be restarted automatically on failure, uncomment:
Restart=on-failure

# Ensure systemd does not try to send SIGTERM to the (already‑backgrounded) process
KillMode=none

[Install]
WantedBy=multi-user.target


1 Like