Skip to content

RDK S100: Set up third-party drivers

The D-Robotics RDK S100 includes a set of drivers for a limited set of devices. This guide covers how to set up drivers that aren’t included (out-of-tree kernel modules), including building, signing, installing, and automating the process with a script. For example, our initial setup used a USB Wi-Fi adapter that is Linux compatible but not initially recognized because the system didn’t include a driver for it, so we set up an out-of-tree module.

The RDK S100 comes with Ubuntu preinstalled, but it runs on a custom Linux kernel (6.1.112-rt43) that includes a limited set of prebuilt drivers, also referred to as kernel modules. Devices that use an included module are plug-and-play, while you must manually set up drivers for other devices as out-of-tree kernel modules.

The S100P’s custom Linux kernel differs from stock Ubuntu in these important ways:

CharacteristicImplication
Custom kernel (6.1.x)Ubuntu’s prebuilt modules (targeting 5.15) won’t work.
Mandated module signingAll kernel modules must be signed or they will be rejected.
Limited apt repositoryMany standard Ubuntu packages aren’t included.
Kernel headers availableDrivers can be compiled on-board without a cross-compiler.

Before you begin, run the following terminal commands on the S100.

Install build dependencies:

Terminal window
sudo apt install -y build-essential flex bison

Verify kernel headers are installed:

Terminal window
ls /lib/modules/$(uname -r)/build/Makefile

If the file exists, headers are installed. If you get No such file or directory, install them:

Terminal window
sudo apt install linux-headers-6.1.112-rt43

Before compiling any module, run the D-Robotics prepare target to set up build scripts and tools (see Kernel Headers documentation):

Terminal window
sudo make -C /lib/modules/$(uname -r)/build prepare

This only needs to be done once per kernel version, or after a kernel update.

Linux drivers are typically distributed as source code you must compile on the target device.

Driver source code usually comes from one of these places:

  • Vendor website (e.g., Panda Wireless, PEAK-System)
  • GitHub repository (e.g., openwrt/mt76)
  • Kernel source tree (if the driver exists in-tree but wasn’t enabled)

Download the source code or clone it using Git.

When using upstream repositories like GitHub, make sure the code is compatible with Linux kernel 6.1. The latest main Linux branch often targets newer kernels and may fail to compile against the S100P’s 6.1 kernel.

Compile instructions are typically found in a README, INSTALL, or Makefile file included with the driver source code, or on the driver’s download page or GitHub repository.

Follow the driver’s own build instructions. For most drivers, this involves running the make command to compile the source code into a loadable kernel module (.ko file).

For more information about using the make command, see Building External Modules.

The S100P ships with GCC (GNU Compiler Collection) version 11.4.0, which is the compiler used to build kernel modules. The kernel itself was compiled with version 11.3.1. You’ll see a warning about mismatching versions during builds, but it doesn’t cause issues.

If compilation fails with errors about missing functions, changed function signatures, or missing header files, the driver source likely targets a different kernel version. Look for a branch, tag, or release of the driver that targets kernel 6.1, or check if the vendor provides a compatible version.

The S100P kernel only allows drivers to load if they have been cryptographically signed. This is a security measure that prevents unauthorized code from running inside the kernel. If you try to load an unsigned driver, the system displays this error:

ERROR: could not insert module: Key was rejected by service

The simplest way to handle signing is to use the kernel’s modules_install command, which signs the driver, copies it to the correct system directory, and registers it with the kernel in one step:

Terminal window
sudo make -C /lib/modules/$(uname -r)/build KERNELRELEASE=$(uname -r) M=$(pwd) modules_install

If you need to sign a driver manually (for example, when modules_install isn’t available), use the kernel’s sign-file tool:

Terminal window
sudo /lib/modules/$(uname -r)/build/scripts/sign-file sha512 \
/lib/modules/$(uname -r)/build/certs/module_signature.pem \
/lib/modules/$(uname -r)/build/certs/module_signature.x509 \
your_module.ko

After signing and installing the driver, load it into the running kernel using the modprobe command as follows. This step only loads the driver once and does not reload it when you reboot the OS.

Terminal window
sudo modprobe <your_module_name>

If the driver loads successfully, there is no output. If it fails, you’ll see an error such as FATAL: Module your_module_name not found or Key was rejected by service.

Verify the driver is loaded using the lsmod command, which lists all currently loaded kernel modules:

Terminal window
lsmod | grep <your_module_name>

If the driver is loaded, you’ll see a line with its name and memory size. No output means the driver isn’t loaded.

To automatically load the driver when the OS starts, create a configuration file in the /lib/modules-load.d/ directory with your driver name:

Terminal window
sudo bash -c 'echo <your_module_name> > /lib/modules-load.d/<your_module_name>.conf'

Reboot and verify the driver automatically loads.

When you perform a system update on the S100, it may update the kernel and then fail to load your out-of-tree modules. Kernel updates break out-of-tree modules on the system because they overwrite the configuration files in the module directory, which produces a Module not found error.

As a result, the old modules remain loaded in memory during the update but won’t load when the OS reboots into the new kernel. If you intentionally update the kernel, you must rebuild all out-of-tree modules before rebooting.

To avoid accidental kernel updates due to a system upgrade, you can pin the kernel. Kernel pinning prevents the kernel package from updating when upgrading the system.

To pin the kernel on the S100, run this command:

Terminal window
sudo apt-mark hold linux-headers-6.1.112-rt43 linux-image-rdk-s100

You can still safely upgrade all other packages:

Terminal window
sudo apt upgrade

To unpin the kernel and allow it to update:

Terminal window
sudo apt-mark unhold linux-headers-6.1.112-rt43 linux-image-rdk-s100

After updating the kernel, rebuild all out-of-tree modules before rebooting, then pin the kernel again. You can rebuild the modules manually, but it is more efficient to do so using a script, such as the driver rebuild script.

It’s faster and easier to rebuild an out-of-tree module using a script instead of rebuilding it manually.

You can configure the following s100p-module-rebuild.sh script to automate the rebuild process for an out-of-tree module on an S100. The script handles the full workflow necessary to rebuild the specified module: prepare, compile, sign, install, auto-load configuration, and loading the module.

Save this script on your RDK S100 as s100p-module-rebuild.sh.

s100p-module-rebuild.sh
#!/bin/bash
# s100p-module-rebuild.sh
# General-purpose kernel module rebuild script for D-Robotics RDK S100P.
# Handles: prepare, build, sign, install, depmod, and auto-load configuration.
#
# Usage:
# sudo ./s100p-module-rebuild.sh --build-dir <path> --build-cmd <command> --module-name <name>
#
# Example:
# sudo ./s100p-module-rebuild.sh \
# --build-dir ~/my-driver/src \
# --build-cmd "make -f Makefile.x86 MTK_COMBO_CHIP=MT7961 hif=usb" \
# --module-name wlan_mt7961_usb
set -euo pipefail
# --- Defaults ---
BUILD_DIR=""
BUILD_CMD=""
MODULE_NAME=""
CLEAN_CMD=""
AUTO_LOAD=true
SKIP_PREPARE=false
# --- Colors ---
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
usage() {
cat <<EOF
Usage: sudo $0 [OPTIONS]
Required:
--build-dir <path> Directory containing the driver source
--build-cmd <command> Command to compile the driver (run from build-dir)
--module-name <name> Name of the .ko module (without .ko extension)
Optional:
--clean-cmd <command> Command to clean before building (run from build-dir)
--no-auto-load Skip adding module to auto-load at boot
--skip-prepare Skip kernel build environment preparation
-h, --help Show this help message
Example:
sudo $0 \\
--build-dir ~/Panda-Wireless/wlan_driver_1e4c921/gen4m \\
--build-cmd "make -f Makefile.x86 MTK_COMBO_CHIP=MT7961 hif=usb" \\
--module-name wlan_mt7961_usb
EOF
exit 1
}
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# --- Parse arguments ---
while [[ $# -gt 0 ]]; do
case "$1" in
--build-dir) BUILD_DIR="$2"; shift 2 ;;
--build-cmd) BUILD_CMD="$2"; shift 2 ;;
--module-name) MODULE_NAME="$2"; shift 2 ;;
--clean-cmd) CLEAN_CMD="$2"; shift 2 ;;
--no-auto-load) AUTO_LOAD=false; shift ;;
--skip-prepare) SKIP_PREPARE=true; shift ;;
-h|--help) usage ;;
*) log_error "Unknown option: $1"; usage ;;
esac
done
# --- Validate required arguments ---
if [[ -z "$BUILD_DIR" || -z "$BUILD_CMD" || -z "$MODULE_NAME" ]]; then
log_error "Missing required arguments."
usage
fi
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run with sudo."
exit 1
fi
if [[ ! -d "$BUILD_DIR" ]]; then
log_error "Build directory does not exist: $BUILD_DIR"
exit 1
fi
KERN_VER=$(uname -r)
KERN_BUILD="/lib/modules/${KERN_VER}/build"
MODULE_FILE="${MODULE_NAME}.ko"
if [[ ! -d "$KERN_BUILD" ]]; then
log_error "Kernel headers not found at ${KERN_BUILD}"
log_error "Install with: sudo apt install linux-headers-6.1.112-rt43"
exit 1
fi
log_info "Kernel version: ${KERN_VER}"
log_info "Build directory: ${BUILD_DIR}"
log_info "Module name: ${MODULE_NAME}"
# --- Step 1: Prepare kernel build environment ---
if [[ "$SKIP_PREPARE" = false ]]; then
log_info "Preparing kernel build environment..."
make -C "$KERN_BUILD" prepare > /dev/null 2>&1
log_info "Kernel build environment ready."
else
log_info "Skipping kernel build environment preparation."
fi
# --- Step 2: Clean (optional) ---
if [[ -n "$CLEAN_CMD" ]]; then
log_info "Cleaning previous build..."
cd "$BUILD_DIR"
eval "$CLEAN_CMD" > /dev/null 2>&1 || true
fi
# --- Step 3: Compile ---
log_info "Compiling driver..."
cd "$BUILD_DIR"
# Allow build to "fail" due to firmware copy permission errors
eval "$BUILD_CMD" 2>&1 | tail -5 || true
if [[ ! -f "$BUILD_DIR/$MODULE_FILE" ]]; then
log_error "Build failed: ${MODULE_FILE} not found in ${BUILD_DIR}"
exit 1
fi
log_info "Compiled successfully: ${MODULE_FILE}"
# --- Step 4: Sign and install via modules_install ---
log_info "Signing and installing module..."
make -C "$KERN_BUILD" KERNELRELEASE="$KERN_VER" M="$BUILD_DIR" modules_install
log_info "Module signed and installed."
# --- Step 5: Configure auto-load ---
if [[ "$AUTO_LOAD" = true ]]; then
AUTOLOAD_CONF="/lib/modules-load.d/${MODULE_NAME}.conf"
echo "$MODULE_NAME" > "$AUTOLOAD_CONF"
log_info "Auto-load configured: ${AUTOLOAD_CONF}"
fi
# --- Step 6: Load the module ---
# Unload if already loaded
if lsmod | grep -q "^${MODULE_NAME}"; then
log_info "Unloading existing module..."
modprobe -r "$MODULE_NAME" 2>/dev/null || rmmod "$MODULE_NAME" 2>/dev/null || true
fi
log_info "Loading module..."
modprobe "$MODULE_NAME"
if lsmod | grep -q "^${MODULE_NAME}"; then
log_info "Module ${MODULE_NAME} loaded successfully."
else
log_error "Module ${MODULE_NAME} failed to load. Check dmesg for details."
exit 1
fi
echo ""
log_info "Done! Module ${MODULE_NAME} is installed and will auto-load on boot."

Before running the script, make it executable on the system using the chmod command:

Terminal window
chmod +x s100p-module-rebuild.sh

To run the script, set the required options in the following command and then execute the command. It’s best to run the script before rebooting the system after updating the kernel. However, if you accidentally reboot the system, you can still run the script to restore your out-of-tree modules.

Terminal window
sudo ./s100p-module-rebuild.sh \
--build-dir <path-to-driver-source> \
--build-cmd "<compile command>" \
--module-name <module-name>

The script has additional options that you should avoid in most cases. Here is the complete options list.

OptionRequiredDescription
--build-dirYesDirectory containing the driver source
--build-cmdYesCommand to compile the driver
--module-nameYesName of the .ko file (without extension)
--clean-cmdNoCommand to clean before building
--no-auto-loadNoSkip adding module to boot auto-load
--skip-prepareNoSkip kernel build environment preparation

Here’s an example that runs the module rebuild script for the Panda PAU0F WiFi driver:

Terminal window
sudo ./s100p-module-rebuild.sh \
--build-dir ~/Panda-Wireless/wlan_driver_1e4c921/gen4m \
--build-cmd "make -f Makefile.x86 MTK_COMBO_CHIP=MT7961 hif=usb" \
--clean-cmd "make -f Makefile.x86 MTK_COMBO_CHIP=MT7961 hif=usb clean" \
--module-name wlan_mt7961_usb

Here’s an example that runs the module rebuild script for PCAN driver based on the D-Robotics third-party driver documentation ↗:

Terminal window
sudo ./s100p-module-rebuild.sh \
--build-dir ~/peak-linux-driver-8.20.0/driver \
--build-cmd "make" \
--module-name pcan