Building Kernel Modules for the Omega2



  • During a new project of mine, I needed a new kernel module for a device driver. I saw that there wasn't yet a condensed write-up on how to manage, compile and run your own kernel modules for the Omega2. So I decided to write one as a side-project. Here it is.

    Update 09.04.2018: Contains new section about reading hardware registers.

    Building Kernel Modules for the Omega2

    Introduction

    This is a tutorial on how to build and run Linux kernel modules (.ko files) for the Omega2.

    There are several related topics in this forum (e.g. here, here, here, here). However I didn't find any conclusive step-by-step tutorial on here that explains how to build a kernel module from the ground up. This is why I've created this tutorial to share what I've learned over the past few days which led me to a functioning kernel module compilation.

    Prerequisites

    This tutorial is working with the newest firwmare version b178, kernel version 4.4.74. You should also have kmod. Do opkg update && opkg install kmod if it isn't already there. Other prerequisites will be installed during the tutorial.

    Kernel modules

    What are kernel modules and why should you care?

    Kernel modules are pieces of code that can be loaded and unloaded into the kernel upon demand. They extend the functionality of the kernel without the need to reboot the system.

    Things like USB drivers / systems, interface drivers, input drivers, can be distributed in the form of the kernel modules. A kernel module also runs with higher priviledges (kernel space vs user space). You may only have direct hardware register access to e.g. GPIO, PWM, SPI, etc. from there. Kernel modules then expose certain functionality or device files to the user space. Examples of kernel modules are:

    • kmod-spi-dev - exposes linux SPI device
    • kmod-fs-ext4 - EXT4 filesystem support
    • kmod-usb-audio - Audio over USB devices
    • kmod-bluetooth - Bluetooth support

    You can look at your loaded kernel modules by executing lsmod and get more info on a module by executing modinfo <module name>. The .ko files are stored in /lib/modules/4.4.74.

    root@Omega-17FD:~# lsmod
    aead                    3489  0
    bluetooth             258081  8 rfcomm,hidp,hci_uart,btusb,btintel,bnep
    ext4                  319010  1
    ..
    root@Omega-17FD:~# modinfo bluetooth
    module:         /lib/modules/4.4.74/bluetooth.ko
    alias:          net-pf-31
    license:        GPL
    depends:        crc16
    
    root@Omega-17FD:/lib/modules/4.4.74# ls -l
    -rw-r--r--    1 root     root          7192 Apr  2 23:19 aead.ko
    -rw-r--r--    1 root     root         28520 Apr  2 23:19 autofs4.ko
    -rw-r--r--    1 root     root        325244 Apr  2 23:19 bluetooth.ko
    ...
    

    For a project you might need a kernel module that is not included or distributed for the Omega2. As a first reference, you should look into Onion's package repository. This tutorial is for the case when you don't find your wanted module there or you want to build something new (e.g., peripheral drivers, ..).

    Understanding the LEDE build system for kernel modules

    Where can we see what stock kernel modules and libraries we can build? For this, you need the cross-compilation environment first. This includes cloning Onion's source repository for LEDE-17.01 and building it. A tutorial can be found here. You should also have a basic idea on how to cross-compile a program for the Omega2. I built the toolchain within a Xubuntu VM.

    When we execute make menuconfig in the source folder, we get a menu where we can configure stuff:

    kernelconfig

    We can select a kernel module there and mark it with "M" -- meaning that this will build into a kernel module file. After exiting and saving the configuration you can build the module by executing make (with optional -j4 for 4-core multithreading).

    Where do these packages come from? They come from the package feed. The sources are defined in the feeds.conf and feeds.conf.default file.

    max@max-VirtualBox:~/source$ cat feeds.conf
    src-git packages https://git.lede-project.org/feed/packages.git;lede-17.01
    src-git luci https://git.lede-project.org/project/luci.git;lede-17.01
    src-git routing https://git.lede-project.org/feed/routing.git;lede-17.01
    src-git telephony https://git.lede-project.org/feed/telephony.git;lede-17.01
    src-git onion https://github.com/OnionIoT/OpenWRT-Packages.git
    

    So they are just a bunch of links to git repositories where packages are stored. You can visit the links to see what's inside them.

    There are two main steps for adding a new kernel module package:

    • Update the index files in feeds/ of available packages defined in feeds.conf
    • Install symlinks to the packages in package/feeds/ so they become available in make menuconfig

    There are scripts inside the script/ folder to help you manage your feeds.

    max@max-VirtualBox:~/source$ ./scripts/feeds -h
    Usage: ./scripts/feeds <command> [options]
    
    Commands:
    	list [options]: List feeds, their content and revisions (if installed)
    	Options:
    	    -n :            List of feed names.
    ...
    

    As described in Onion's repository README:

    Run ./scripts/feeds update -a to get all the latest package definitions defined in feeds.conf / feeds.conf.default respectively and ./scripts/feeds install -a to install symlinks of all of them into package/feeds/.

    More specifically, you can also selectively update and install feeds and packages by using:

    • ./scripts/feeds update -i to recreate the index file
    • ./scripts/feeds update <feed name> to update only one feed
    • ./scripts/feeds install <package name> to install only one package

    Let's build a 'hello world' kernel module!

    Time to build the first actual kernel module.

    For security reasons you should first comment out every other feed in your feeds.conf and feeds.conf.default to prevent accidently updating everything (and to a new kernel version), using the # character.

    We will create a new src-link type feed using a folder on our host system as described here. We will take code and modified Makefiles from a tutorial here.

    max@max-VirtualBox:~$ mkdir omega2_kmods && cd omega2_kmods/
    max@max-VirtualBox:~/omega2_kmods$ mkdir hello-world && cd hello-world/
    max@max-VirtualBox:~/omega2_kmods/hello-world$ touch Makefile
    max@max-VirtualBox:~/omega2_kmods/hello-world$ mkdir src && cd src
    max@max-VirtualBox:~/omega2_kmods/hello-world/src$ touch Makefile
    max@max-VirtualBox:~/omega2_kmods/hello-world/src$ touch hello-world.c
    

    Fill in the contents of the files:

    src/hello-world.c:

    #include <linux/module.h>	/* Needed by all modules */
    #include <linux/kernel.h>	/* Needed for KERN_INFO */
    #include <linux/init.h>		/* Needed for the macros */
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Maximilian Gerhardt");
    MODULE_DESCRIPTION("Basic kernel module for tutorial");
    
    static int __init hello_2_init(void)
    {
    	printk(KERN_INFO "Hello, world! We're inside the Omega2's kernel.\n");
    	return 0;
    }
    
    static void __exit hello_2_exit(void)
    {
    	printk(KERN_INFO "Goodbye, world!\n");
    }
    
    module_init(hello_2_init);
    module_exit(hello_2_exit);
    

    Makefile:

    # Copyright (C) 2006-2012 OpenWrt.org
    #
    # This is free software, licensed under the GNU General Public License v2.
    # See /LICENSE for more information.
    
    include $(TOPDIR)/rules.mk
    include $(INCLUDE_DIR)/kernel.mk
    
    # name
    PKG_NAME:=hello-world
    # version of what we are downloading
    PKG_VERSION:=1.1
    # version of this makefile
    PKG_RELEASE:=4
    
    PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME)
    PKG_CHECK_FORMAT_SECURITY:=0
    
    include $(INCLUDE_DIR)/package.mk
    
    define KernelPackage/$(PKG_NAME)
    	SUBMENU:=Other modules
    	DEPENDS:=@(TARGET_ramips_mt76x8||TARGET_ramips_mt7688)
    	TITLE:=Hello world kernel module
    	FILES:= $(PKG_BUILD_DIR)/hello-world.ko
    endef
    
    define KernelPackage/$(PKG_NAME)/description
    	A "hello world" test module for a tutorial.
    endef
    
    define Build/Prepare
    	mkdir -p $(PKG_BUILD_DIR)
    	$(CP) ./src/* $(PKG_BUILD_DIR)/
    endef
    
    MAKE_OPTS:= \
    	ARCH="$(LINUX_KARCH)" \
    	CROSS_COMPILE="$(TARGET_CROSS)" \
    	SUBDIRS="$(PKG_BUILD_DIR)"
    
    define Build/Compile
    	$(MAKE) -C "$(LINUX_DIR)" \
    		$(MAKE_OPTS) \
    		modules
    endef
    
    $(eval $(call KernelPackage,$(PKG_NAME)))
    

    src/Makefile:

    obj-m := hello-world.o
    

    The top-level Makefile declares how to build our kernel module package. You declare the name, its files, a description, dependencies and other things there. See the documentation links at the bottom of this tutorial. The src/Makefile then adds all to-be compiled object files to the build process.

    Add a line to your feeds.conf declaring the new feed and the path:

    src-link omega2_kmods /home/max/omega2_kmods
    

    Update this new feed:

    max@max-VirtualBox:~/source$ ./scripts/feeds update omega2_kmods
    Updating feed 'omega2_kmods' from '/home/max/omega2_kmods' ...
    Create index file './feeds/omega2_kmods.index' 
    Collecting package info: done
    Collecting target info: done
    

    Install the new kmod-hello-world package. Here we just install everything:

    max@max-VirtualBox:~/source$ ./scripts/feeds install -a
    Installing all packages from feed omega2_kmods.
    Installing package 'hello-world' from omega2_kmods
    

    The new package is installed. You can list all available modules for a feed by executing:

    max@max-VirtualBox:~/source$ ./scripts/feeds list -r omega2_kmods
    kmod-hello-world                	Hello world kernel module
    

    Execute make menuconfig and navigate to Kernel Modules -> Other Modules (this submenu is declared in the Makefile😞

    newmod

    We see our new kernel module! Navigate to it and press M to mark it for compilation to a kernel module. Exit and save.

    Now build everything:

    max@max-VirtualBox:~/source$ make -j4
     make[1] world
     make[2] package/cleanup
     make[2] target/compile
     make[3] -C target/linux compile
     make[2] package/compile
     make[3] -C package/libs/ncurses host-compile
    ..
     make[3] -C /home/max/omega2_kmods/hello-world compile
    ..
    

    You can also choose to just compile your module:

    max@max-VirtualBox:~/source$ make package/hello-world/compile -j4
     make[1] package/hello-world/compile
     make[2] -C /home/max/omega2_kmods/hello-world compile
    

    Either way, you should now have a hello-world.ko. Search for it:

    max@max-VirtualBox:~/source$ find . -name "*hello-world*"
    ./staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/lib/modules/4.4.74/hello-world.ko
    ./staging_dir/packages/ramips/kmod-hello-world_4.4.74+1.1-4_mipsel_24kc.ipk
    ./bin/targets/ramips/mt7688/packages/kmod-hello-world_4.4.74+1.1-4_mipsel_24kc.ipk
    ./build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/hello-world.ko
    

    It conveniently compiled the hello-world.ko file and even created an ipk package. Unfortunetly, trying to installing that package will give you a kernel mismatch error because the kernel hashes differ (see topic links at start). Loading the .ko file directly will still work though.

    Transfer the file to your Omega2 over SSH/SCP.

    max@max-VirtualBox:~/source$ scp ./staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/lib/modules/4.4.74/hello-world.ko root@192.168.1.202:/root/.
    root@192.168.1.202's password: 
    hello-world.ko                                                          100% 2504     2.5KB/s   00:00    
    

    SSH to your Omega2 and insert the module.

    root@Omega-17FD:~# insmod hello-world.ko
    

    The module should now be loaded and should have printed the messge to the debug kernel log:

    root@Omega-17FD:~# lsmod | grep "hello"
    hello_world              592  0
    root@Omega-17FD:~# dmesg
    [...]
    [11813.120524] Hello, world! We're inside the Omega2's kernel.
    

    Success! The kernel module was loaded and has executed the module init function successfully!

    You can unload the module by doing rmmod. This should trigger the module's exit/cleanup function:

    root@Omega-17FD:~# rmmod hello_world
    root@Omega-17FD:~# dmesg
    [..]
    [11813.120524] Hello, world! We're inside the Omega2's kernel.
    [11957.855870] Goodbye, world!
    

    Works as expected.

    Note that modinfo only seems to work when the module is inside the /lib/modues/4.4.71 folder:

    root@Omega-17FD:~# cp hello-world.ko /lib/modules/4.4.74/.
    root@Omega-17FD:~# modinfo hello_world
    module:         /lib/modules/4.4.74/hello-world.ko
    license:        GPL
    depends:
    

    At the end of this section you were now able to compile a simple kernel module from scratch and load it into the Omega2.

    Let's read a hardware register!

    For a quick additional demo, we can modify the code of the hello-world kernel module to read a register from the Omega2's CPU, which is a MT7688. For a reference, you will need the Mediatek MT7688 datasheet: https://labs.mediatek.com/en/chipset/MT7688

    Starting at page 48, the register addresses and meanings are listed in their respective blocks (system config, timer, GPIO, SPI,..). Let's have a look at the SYSCTL block.

    datasheet

    Starting at address 0x10000000, there are several interesting registers we can read out. Let's read out CHIPID0_3 and CHIPID4_7, which contains the ASCII chip name in these two 32-bit registers.

    If you were programming a microcontroller without a OS and memory management unit (MMU), you would just set a C pointer variable to that address and start reading from it. However, to read data from this physical memory address in the kernel, we first have to do a memory re-mapping. In an OS, you mostly deal with "virtual addresses", which are remapped by an MMU to real physical address within a memory chip. If we just set a C-pointer to 0x10000000 and read it, we would read this virtual address, which is no good. Virtual addresses are a whole topic on its own. You should consult a lecture on operating systems if you want to know more about this.

    The kernel provides in linux/io.h functions with which we can accomplish this:

    • void* ioremap(address, size) will return a pointer to the virtual memory which is now mapped to the physical address address. A total of size bytes are mapped to this location
    • ioread32(mem)/ioread16(mem)/ioread8(mem) Reads 32/16/8 bits from specified I/O memory, with pointer previously acquired by ioremap
    • iowrite32/iowrite16/iowrite8 writes value to memory
    • iounmap(mem) for unmapping when you're done

    Knowing these function and addresses, we can read CHIPID0_3, CHIPID4_7 and CHIP_REV_ID using the following modified hello-world.c code:

    #include <linux/module.h>	/* Needed by all modules */
    #include <linux/kernel.h>	/* Needed for KERN_INFO */
    #include <linux/init.h>		/* Needed for the macros */
    #include <linux/io.h>		/* Needed for I/O operations */
    
    /* Addresses of base registers and offsets for certain fields (see datasheet) */
    #define MT7688_SYSCTL_BASE			0x10000000
    #define MT7688_SYSCTL_CHIPID0_3		0x0
    #define MT7688_SYSCTL_CHIPID4_7		0x4
    #define MT7688_SYSCTL_CHIP_REV_ID	0xC
    
    /* Physical address in, 32 bit integer out. */
    static unsigned int ReadReg32(unsigned long addr) {
    	//Remap 4 bytes starting at that address
    	void* virtualMem = ioremap(addr, 4);
    	if(!virtualMem) {
    		printk(KERN_WARNING "Failed to remap register memory");
    		return 0;
    	}
    	//Read 32 bits
    	unsigned int val = ioread32(virtualMem);
    	printk(KERN_INFO "ReadReg32(0x%08x) = 0x%08x\n", (int)addr, val);
    	//Unmap memory again
    	iounmap(virtualMem);
    	//return read value
    	return val;
    }
    
    static int __init hello_2_init(void)
    {
    	printk(KERN_INFO "Hello, world! We're inside the Omega2's kernel.\n");
    
    	unsigned int chipid0_3 = ReadReg32(MT7688_SYSCTL_BASE + MT7688_SYSCTL_CHIPID0_3);
    	//Recover the initial characters
    	//Datasheet says leftmost bits is the last character
    	char id_0 = (char)(chipid0_3 >> 0);
    	char id_1 = (char)(chipid0_3 >> 8);
    	char id_2 = (char)(chipid0_3 >> 16);
    	char id_3 = (char)(chipid0_3 >> 24);
    	//Do the same for chipid 4 to 7
    	unsigned int chipid4_7 = ReadReg32(MT7688_SYSCTL_BASE + MT7688_SYSCTL_CHIPID4_7);
    	char id_4 = (char)(chipid4_7 >> 0);
    	char id_5 = (char)(chipid4_7 >> 8);
    	char id_6 = (char)(chipid4_7 >> 16);
    	char id_7 = (char)(chipid4_7 >> 24);
    
    	//Get revision ID
    	unsigned int chiprev = ReadReg32(MT7688_SYSCTL_BASE + MT7688_SYSCTL_CHIP_REV_ID);
    	//Parse the bits out of the structure
    	int packageId = chiprev >> 16;
    	int verId = (chiprev >> 8) & 0xf;
    	int ecoId = chiprev & 0xf;
    
    	printk(KERN_INFO "CHIP ID IS: '%c%c%c%c%c%c%c%c'\n",
    			id_0, id_1, id_2, id_3, id_4, id_5, id_6, id_7);
    	printk(KERN_INFO "CHIP REV ID: PKG_ID=%d, VER_ID=%d, ECO_ID=%d\n",
    			packageId, verId, ecoId);
    
    	return 0;
    }
    
    static void __exit hello_2_exit(void)
    {
    	printk(KERN_INFO "Goodbye, world!\n");
    }
    
    module_init(hello_2_init);
    module_exit(hello_2_exit);
    
    MODULE_LICENSE("GPL");
    MODULE_AUTHOR("Maximilian Gerhardt");
    MODULE_DESCRIPTION("Basic kernel module for tutorial with register reading");
    

    Compiling this and loading it into the kernel results in:

    max@max-VirtualBox:~/source$ make package/hello-world/compile -j4 && scp ./build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/hello-world.ko root@192.168.1.202:/root/.
    
    root@Omega-17FD:~# insmod hello-world.ko
    root@Omega-17FD:~# dmesg
    [ 8402.279877] Hello, world! We're inside the Omega2's kernel.
    [ 8402.285635] ReadReg32(0x10000000) = 0x3637544d
    [ 8402.290142] ReadReg32(0x10000004) = 0x20203832
    [ 8402.294669] ReadReg32(0x1000000c) = 0x00010102
    [ 8402.299175] CHIP ID IS: 'MT7628  '
    [ 8402.302623] CHIP REV ID: PKG_ID=1, VER_ID=1, ECO_ID=2
    

    Great, we've read out the chip ID as "MT7628", just as the datasheet said. Convert the binary string from the datasheet to ASCII to confirm this. We also read out the expected values for the CHIP_REV_ID fields.

    This simple example showed you how to read a hardware register as specified in the MT7688's datasheet from the kernel space.

    Let's build a XBox360 USB gamepad driver!

    Now let's do some more serious stuff.

    I have this Logitech Wireless Gamepad F710 laying around here.

    gamepad

    Let's plug in into a PC running Linux and look at dmesg:

    [13688.633245] usb 2-2: new full-speed USB device number 3 using ohci-pci
    [13689.164936] usb 2-2: New USB device found, idVendor=046d, idProduct=c21f
    [13689.164939] usb 2-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
    [13689.164940] usb 2-2: Product: Wireless Gamepad F710
    [13689.164941] usb 2-2: Manufacturer: Logitech
    [13689.164942] usb 2-2: SerialNumber: ADED9351
    [13690.978059] input: Logitech Gamepad F710 as /devices/pci0000:00/0000:00:06.0/usb2/2-2/2-2:1.0/input/input8
    [13690.978517] usbcore: registered new interface driver xpad
    max@max-VirtualBox:~/source$ ls /dev/input/
    event0  event1  js0   mouse0 
    

    The USB device is detected, the driver xpad takes over and creates new device files /dev/input/event1 and /dev/input/js0 (Joystick #0). These are device files for the event-based API (new) and the older Linux Joystick API.

    What happens when we plug this into the Omega2?

    root@Omega-17FD:~# logread -f
    [14602.640692] usb 2-1: new full-speed USB device number 7 using ohci-platform
    [14602.889249] hid-generic 0003:046D:C22F.0009: hiddev0,hidraw0: USB HID v1.1 1 Device [Logitech Logitech Cordless RumblePad 2] on usb-101c1000.ohci-1/input0
    [14602.941098] hid-generic 0003:046D:C22F.000A: hiddev0,hidraw1: USB HID v1.1 1 Device [Logitech Logitech Cordless RumblePad 2] on usb-101c1000.ohci-1/input1
    

    The USB device is detected as a Human Interface Device (HID). However, you will notice that there are no /dev/input/* devices:

    root@Omega-17FD:~# ls /dev/input/
    root@Omega-17FD:~#
    

    We can't really use the device this way. We're missing the xpad driver here, as well es the entire kmod-input-joydev package for the Joystick API.

    Luckily we can use our newly gained knowledge about kernel modules to get this thing working. A search for "linux xpad driver" leads to https://github.com/paroj/xpad and https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c. I will use the code in the first repository, but the code for the newer Linux kernel should also work and support even more devices (206 of them).

    Following up on the structure of our first kernel module, let's create a new package folder xpad and add the required C files and Makefiles:

    max@max-VirtualBox:~/omega2_kmods$ mkdir xpad && cd xpad
    max@max-VirtualBox:~/omega2_kmods/xpad$ touch Makefile
    max@max-VirtualBox:~/omega2_kmods/xpad$ mkdir src && cd src
    max@max-VirtualBox:~/omega2_kmods/xpad/src$ wget https://raw.githubusercontent.com/paroj/xpad/master/xpad.c
    xpad.c     100%[==============================================>]  61,24K   267KB/s    in 0,2s    
    2018-04-08 13:03:33 (267 KB/s) - ‘xpad.c’ saved [62705/62705]
    max@max-VirtualBox:~/omega2_kmods/xpad/src$ touch Makefile
    

    Makefile:

    # Copyright (C) 2006-2012 OpenWrt.org
    #
    # This is free software, licensed under the GNU General Public License v2.
    # See /LICENSE for more information.
    
    include $(TOPDIR)/rules.mk
    include $(INCLUDE_DIR)/kernel.mk
    
    # name
    PKG_NAME:=xpad
    # version of what we are downloading
    PKG_VERSION:=1.1
    # version of this makefile
    PKG_RELEASE:=4
    
    PKG_BUILD_DIR:=$(KERNEL_BUILD_DIR)/$(PKG_NAME)
    PKG_CHECK_FORMAT_SECURITY:=0
    
    include $(INCLUDE_DIR)/package.mk
    
    define KernelPackage/$(PKG_NAME)
    	SUBMENU:=Input modules
    	DEPENDS:=kmod-usb-core kmod-input-core
    	TITLE:=XBox360-like gamepad support
    	FILES:= $(PKG_BUILD_DIR)/xpad.ko
    endef
    
    define KernelPackage/$(PKG_NAME)/description
    	Adds support for various XBox360-layout USB gamepads. Creates /dev/input/eventX devices. If kmod-input-joydev is loaded, also creates /dev/input/jsX device.
    endef
    
    define Build/Prepare
    	mkdir -p $(PKG_BUILD_DIR)
    	$(CP) ./src/* $(PKG_BUILD_DIR)/
    endef
    
    
    MAKE_OPTS:= \
    	ARCH="$(LINUX_KARCH)" \
    	CROSS_COMPILE="$(TARGET_CROSS)" \
    	SUBDIRS="$(PKG_BUILD_DIR)"
    
    define Build/Compile
    	$(MAKE) -C "$(LINUX_DIR)" \
    		$(MAKE_OPTS) \
    		modules
    endef
    
    $(eval $(call KernelPackage,$(PKG_NAME)))
    

    src/Makefile:

    obj-m := xpad.o
    

    Update the feed and install new packages:

    max@max-VirtualBox:~/source$ ./scripts/feeds update -a
    Updating feed 'omega2_kmods' from '/home/max/omega2_kmods' ...
    Create index file './feeds/omega2_kmods.index' 
    Collecting package info: done
    max@max-VirtualBox:~/source$ ./scripts/feeds install -a
    Installing all packages from feed omega2_kmods.
    Installing package 'xpad' from omega2_kmods
    

    Go into make menuconfig -> Kernel Modules -> Input modules and select kmod-xpad and kmod-input-joydev with "M".

    xpad

    Exit and save, then make. Find the kernel modules xpad.ko and joydev.ko and transfer them to your Omega2.

    max@max-VirtualBox:~/source$ make -j4
    [..]
     make[3] -C /home/max/omega2_kmods/xpad compile
    max@max-VirtualBox:~/source$ find . -name "xpad*" 
    ./staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/lib/modules/4.4.74/xpad.ko
    max@max-VirtualBox:~/source$ find . -name "joydev*" 
    ./staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/lib/modules/4.4.74/joydev.ko
    max@max-VirtualBox:~/source$ scp ./staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/lib/modules/4.4.74/xpad.ko root@192.168.1.202:/root/.
    max@max-VirtualBox:~/source$ scp ./staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/lib/modules/4.4.74/ root@192.168.1.202:/root/.
    

    Let's load it in and look at the debug log when we plug it in now:

    root@Omega-17FD:~# insmod joydev.ko
    root@Omega-17FD:~# insmod xpad.ko
    root@Omega-17FD:~# dmesg
    [..]
    [16506.228539] usbcore: registered new interface driver xpad
    [16539.408850] usb 2-1: new full-speed USB device number 9 using ohci-platform
    [16539.663825] hid-generic 0003:046D:C22F.000B: hiddev0,hidraw0: USB HID v1.11 Device [Logitech Logitech Cordless RumblePad 2] on usb-101c1000.ohci-1/input0
    [16539.713896] hid-generic 0003:046D:C22F.000C: hiddev0,hidraw1: USB HID v1.11 Device [Logitech Logitech Cordless RumblePad 2] on usb-101c1000.ohci-1/input1
    [16540.890125] usb 2-1: USB disconnect, device number 9
    [16541.708861] usb 2-1: new full-speed USB device number 10 using ohci-platform
    [16541.941219] input: Logitech Gamepad F710 as /devices/platform/101c1000.ohci/usb2/2-1/2-1:1.0/input/input7
    

    Looks good. Let's examine /dev/input.

    root@Omega-17FD:~# ls /dev/input/
    event0  js0
    

    Awesome :)! We have /dev/input/js0 and /dev/input/event0 now!

    Now we just need a small demo program to interact with the joystick device. Let's compile evtest.c, which is a test program from to test /dev/input/eventX devices:

    max@max-VirtualBox:~/evtest$ wget https://raw.githubusercontent.com/georgeredinger/evtest/master/evtest.c
    max@max-VirtualBox:~/evtest$ /home/max/source/staging_dir/toolchain-mipsel_24kc_gcc-5.4.0_musl-1.1.16/bin/mipsel-openwrt-linux-gcc evtest.c -o evtest
    max@max-VirtualBox:~/evtest$ scp evtest root@192.168.1.202:/root/.
    

    Transfering the evtest to the Omega and executing it gives:

    root@Omega-17FD:~# ./evtest  /dev/input/event0
    Input driver version is 1.0.1
    Input device ID: bus 0x3 vendor 0x46d product 0xc21f version 0x305
    Input device name: "Logitech Gamepad F710"
    Supported events:
      Event type 0 (Sync)
      Event type 1 (Key)
        Event code 304 (BtnA)
    [...]
    Testing ... (interrupt to exit)
    Event: time 1523189709.996223, type 3 (Absolute), code 1 (Y), value 899
    Event: time 1523189709.996223, -------------- Report Sync ------------
    Event: time 1523189710.000224, type 3 (Absolute), code 1 (Y), value 1413
    

    This works and shows every gamepad event on the screen. Now lets use a library which uses the Joystick API.

    Let's use the library libgamepad made for Windows and Linux alike: https://github.com/elanthis/gamepad

    We download the gamepad.c, main.c and gamepad.h files in a new folder and modify the Makefile with which we cross-compile the program. Note that this uses libncurses and libudev. Select libncurses in the menuconfig and build. Getting libudev and libevdev is more complicated because you actually have to update to the newest LEDE feeds to select that package. Just download a binary version and from here. Also don't forget to install the dependencies on the Omega 2 by doin opkg update && opkg install libncurses libudev kmod-input-evdev . Download the Makefile from here.

    Building gives you libgamepad.so (with symlink to libgamepad.so.1) and gamepadtest. Transfer them and libevdev.so.2 to /usr/lib and /root respectively.

    max@max-VirtualBox:~/gamepadlib$ scp libevdev.so.2 root@192.168.1.202:/usr/lib/.
    max@max-VirtualBox:~/gamepadlib$ scp libgamepad.so root@192.168.1.202:/usr/lib/libgamepad.so.1
    max@max-VirtualBox:~/gamepadlib$ scp gamepadtest root@192.168.1.202:/root/.
    

    Executing the program gives you:

    workinggamepad

    Joystick 0 is correctly detected and all sticks and buttons are working flawlessly! Now we can work with this library to program an application that uses an Xbox360 USB gamepad.

    This concludes the practical part of the tutorial.

    Examples and references

    @luz has a repository for self-built packages and kernel modules at https://github.com/plan44/plan44-feed. The Makefile are very useful.

    Resources used in this tutorial:

    My Github repository with example code and libraries:

    Conclusion

    This tutorial showed you what kernel modules are, why we might need them and how to build them. We built a 'hello world' minimal kernel module for a start, then expanded the code to read a hardware regster. Finally, we built kmod-input-joydev and xpad to get a USB Xbox360 gamepad working. We then compiled a test program and a library to confirm that we gamepad works.



  • Excellent!...thanks for sharing!

    My toolchain environment has only feeds.conf.default file but feeds.conf not exist. It's ok?.



  • @Oximoron Thanks! That's weird, but should be fine too. If you cloned https://github.com/OnionIoT/source you should have that file. Anyways, I think I saw the update process etc. only pulling info from feeds.conf. I said to modify both files just to be certain you don't do an accidental upgrade and upgrade to a higher kernel. For the modules to work you should use that toolchain and kernel version. I experienced problems when compiling for a higher version kernel and running it on an older one.



  • @Maximilian-Gerhardt you sorta out did even yourself with this post 🙂
    thank you. it will be very helpful.



  • @Maximilian-Gerhardt Awesome! Thank you so much for putting this together!



  • @Maximilian-Gerhardt Outstanding work!



  • Great series, and great tutorial!

    I am trying speed up make by just compiling the hello-world module, using the command:

    make package/hello-world/compile -j4

    However, I get the following error (when running with "-j1 V=s" for additional output):

    make[1]: Entering directory '/root/source'
    make[1]: *** No rule to make target 'package/hello-world/compile'. Stop.

    I have successfully done a full build and installed this module on my Omega2+, but I am stuck at this point.

    Thanks again for all the great work!



  • @Jackson-Wilson Thanks for the feedback! Unfortunately I cannot reproduce that.

    My full build log with -j1 V=s, when the package was previously compiled:

    max@max-VirtualBox:~/source$ make package/hello-world/compile -j1 V=s
    make[1]: Entering directory '/home/max/source'
    make[2]: Entering directory '/home/max/omega2_kmods/hello-world'
    if [ -f /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo/hello-world.default.install.clean ]; then rm -f /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo/hello-world.default.install /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo/hello-world.default.install.clean; fi
    make[2]: Leaving directory '/home/max/omega2_kmods/hello-world'
    make[1]: Leaving directory '/home/max/source'
    

    And I execute make package/hello-world/clean and then compile (with code from the "Read Hardware Register" Section):

    max@max-VirtualBox:~/source$ make package/hello-world/compile -j1 V=s
    make[1]: Entering directory '/home/max/source'
    make[2]: Entering directory '/home/max/omega2_kmods/hello-world'
    if [ -f /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo/hello-world.default.install.clean ]; then rm -f /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo/hello-world.default.install /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo/hello-world.default.install.clean; fi
    make[2]: Leaving directory '/home/max/omega2_kmods/hello-world'
    make[1]: Leaving directory '/home/max/source'
    max@max-VirtualBox:~/source$ make -C package/hello-world compile -j1 V=s
    make: *** package/hello-world: No such file or directory.  Stop.
    max@max-VirtualBox:~/source$ make -C /home/max/omega2_kmods/hello-world compile -j1 V=s
    make: Entering directory '/home/max/omega2_kmods/hello-world'
    Makefile:6: /rules.mk: No such file or directory
    Makefile:7: /kernel.mk: No such file or directory
    Makefile:19: /package.mk: No such file or directory
    make: *** No rule to make target '/package.mk'.  Stop.
    make: Leaving directory '/home/max/omega2_kmods/hello-world'
    max@max-VirtualBox:~/source$ make package/hello-world/clean -j1 V=s
    make[1]: Entering directory '/home/max/source'
    make[2]: Entering directory '/home/max/omega2_kmods/hello-world'
    rm -f /home/max/source/bin/targets/ramips/mt7688/packages/kmod-hello-world_4.4.74+1.1-4_mipsel_24kc.ipk
    rm -f /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/stamp/.hello-world_installed
    rm -f /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/packages/hello-world.list /home/max/source/staging_dir/host/packages/hello-world.list
    rm -rf /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world
    make[2]: Leaving directory '/home/max/omega2_kmods/hello-world'
    make[1]: Leaving directory '/home/max/source'
    max@max-VirtualBox:~/source$ make package/hello-world/compile -j1 V=s
    make[1]: Entering directory '/home/max/source'
    make[2]: Entering directory '/home/max/omega2_kmods/hello-world'
    mkdir -p /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world
    cp -fpR ./src/* /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/
    touch /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/.prepared_80ddc19575c0da8a918fdc92694a93eb
    rm -f /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/stamp/.hello-world_installed
    (cd /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/./; if [ -x ./configure ]; then find /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ -name config.guess | xargs -r chmod u+w; find /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ -name config.guess | xargs -r -n1 cp --remove-destination /home/max/source/scripts/config.guess; find /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ -name config.sub | xargs -r chmod u+w; find /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ -name config.sub | xargs -r -n1 cp --remove-destination /home/max/source/scripts/config.sub; AR="mipsel-openwrt-linux-musl-gcc-ar" AS="mipsel-openwrt-linux-musl-gcc -c -Os -pipe -mno-branch-likely -mips32r2 -mtune=24kc -fno-caller-saves -fno-plt -fhonour-copts -Wno-error=unused-but-set-variable -Wno-error=unused-result -msoft-float -iremap /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world:hello-world -fstack-protector -D_FORTIFY_SOURCE=1 -Wl,-z,now -Wl,-z,relro" LD=mipsel-openwrt-linux-musl-ld NM="mipsel-openwrt-linux-musl-gcc-nm" CC="mipsel-openwrt-linux-musl-gcc" GCC="mipsel-openwrt-linux-musl-gcc" CXX="mipsel-openwrt-linux-musl-g++" RANLIB="mipsel-openwrt-linux-musl-gcc-ranlib" STRIP=mipsel-openwrt-linux-musl-strip OBJCOPY=mipsel-openwrt-linux-musl-objcopy OBJDUMP=mipsel-openwrt-linux-musl-objdump SIZE=mipsel-openwrt-linux-musl-size CFLAGS="-Os -pipe -mno-branch-likely -mips32r2 -mtune=24kc -fno-caller-saves -fno-plt -fhonour-copts -Wno-error=unused-but-set-variable -Wno-error=unused-result -msoft-float -mips16 -minterlink-mips16 -iremap /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world:hello-world -fstack-protector -D_FORTIFY_SOURCE=1 -Wl,-z,now -Wl,-z,relro " CXXFLAGS="-Os -pipe -mno-branch-likely -mips32r2 -mtune=24kc -fno-caller-saves -fno-plt -fhonour-copts -Wno-error=unused-but-set-variable -Wno-error=unused-result -msoft-float -mips16 -minterlink-mips16 -iremap /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world:hello-world -fstack-protector -D_FORTIFY_SOURCE=1 -Wl,-z,now -Wl,-z,relro " CPPFLAGS="-I/home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/usr/include -I/home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/include -I/home/max/source/staging_dir/toolchain-mipsel_24kc_gcc-5.4.0_musl-1.1.16/usr/include -I/home/max/source/staging_dir/toolchain-mipsel_24kc_gcc-5.4.0_musl-1.1.16/include/fortify -I/home/max/source/staging_dir/toolchain-mipsel_24kc_gcc-5.4.0_musl-1.1.16/include " LDFLAGS="-L/home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/usr/lib -L/home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/lib -L/home/max/source/staging_dir/toolchain-mipsel_24kc_gcc-5.4.0_musl-1.1.16/usr/lib -L/home/max/source/staging_dir/toolchain-mipsel_24kc_gcc-5.4.0_musl-1.1.16/lib -znow -zrelro "   ./configure --target=mipsel-openwrt-linux --host=mipsel-openwrt-linux --build=x86_64-linux-gnu --program-prefix="" --program-suffix="" --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --libexecdir=/usr/lib --sysconfdir=/etc --datadir=/usr/share --localstatedir=/var --mandir=/usr/man --infodir=/usr/info --disable-nls   ; fi; )
    rm -f /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/.configured_*
    touch /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/.configured_yyyy
    make -C "/home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/linux-4.4.74" ARCH="mips" CROSS_COMPILE="mipsel-openwrt-linux-musl-" SUBDIRS="/home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world" modules
    make[3]: Entering directory '/home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/linux-4.4.74'
      CC [M]  /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/hello-world.o
    /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/hello-world.c: In function 'ReadReg32':
    /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/hello-world.c:21:2: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
      unsigned int val = ioread32(virtualMem);
      ^
    /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/hello-world.c: In function 'hello_2_init':
    /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/hello-world.c:33:2: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement]
      unsigned int chipid0_3 = ReadReg32(MT7688_SYSCTL_BASE + MT7688_SYSCTL_CHIPID0_3);
      ^
      Building modules, stage 2.
      MODPOST 1 modules
      CC      /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/hello-world.mod.o
      LD [M]  /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/hello-world.ko
    make[3]: Leaving directory '/home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/linux-4.4.74'
    touch /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/.built
    mkdir -p /home/max/source/bin/targets/ramips/mt7688/packages /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/CONTROL /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo
    . /home/max/source/include/shell.sh; export modules=; probe_module() { local mods="$1"; local boot="$2"; local mod; shift 2; for mod in $mods; do mkdir -p /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules.d; echo "$mod" >> /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules.d/hello-world; done; if [ -e /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules.d/hello-world ]; then if [ "$boot" = "1" -a ! -e /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules-boot.d/hello-world ]; then mkdir -p /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules-boot.d; ln -s ../modules.d/hello-world /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules-boot.d/; fi; modules="${modules:+$modules }$mods"; fi; }; add_module() { local priority="$1"; local mods="$2"; local boot="$3"; local mod; shift 3; for mod in $mods; do mkdir -p /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules.d; echo "$mod" >> /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules.d/$priority-hello-world; done; if [ -e /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules.d/$priority-hello-world ]; then if [ "$boot" = "1" -a ! -e /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules-boot.d/$priority-hello-world ]; then mkdir -p /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules-boot.d; ln -s ../modules.d/$priority-hello-world /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules-boot.d/; fi; modules="${modules:+$modules }$priority-hello-world"; fi; };  if [ -n "$modules" ]; then modules="$(echo "$modules" | tr ' ' '\n' | sort | uniq | paste -s -d' ' -)"; mkdir -p /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/etc/modules.d; mkdir -p /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/CONTROL; echo "#!/bin/sh" > /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/CONTROL/postinst-pkg; echo "[ -z \"\$IPKG_INSTROOT\" ] || exit 0" >> /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/CONTROL/postinst-pkg; echo ". /lib/functions.sh" >> /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/CONTROL/postinst-pkg; echo "insert_modules $modules" >> /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/CONTROL/postinst-pkg; chmod 0755 /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/CONTROL/postinst-pkg; fi
    find /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world -name 'CVS' -o -name '.svn' -o -name '.#*' -o -name '*~'| xargs -r rm -rf
    export CROSS="mipsel-openwrt-linux-musl-"  NO_RENAME=1 ; NM="mipsel-openwrt-linux-musl-nm" STRIP="/home/max/source/staging_dir/host/bin/sstrip" STRIP_KMOD="/home/max/source/scripts/strip-kmod.sh" PATCHELF="/home/max/source/staging_dir/host/bin/patchelf" /home/max/source/scripts/rstrip.sh /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world
    rstrip.sh: /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/lib/modules/4.4.74/hello-world.ko: relocatable
    (cd /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world/CONTROL; ( echo "$CONTROL"; printf "Description: "; echo "$DESCRIPTION" | sed -e 's,^[[:space:]]*, ,g'; ) > control; chmod 644 control; ( echo "#!/bin/sh"; echo "[ \"\${IPKG_NO_SCRIPT}\" = \"1\" ] && exit 0"; echo "[ -x "\${IPKG_INSTROOT}/lib/functions.sh" ] || exit 0"; echo ". \${IPKG_INSTROOT}/lib/functions.sh"; echo "default_postinst \$0 \$@"; ) > postinst; ( echo "#!/bin/sh"; echo "[ -x "\${IPKG_INSTROOT}/lib/functions.sh" ] || exit 0"; echo ". \${IPKG_INSTROOT}/lib/functions.sh"; echo "default_prerm \$0 \$@"; ) > prerm; chmod 0755 postinst prerm;  )
    install -d -m0755 /home/max/source/bin/targets/ramips/mt7688/packages
    /home/max/source/scripts/ipkg-build -c -o 0 -g 0 /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world /home/max/source/bin/targets/ramips/mt7688/packages
    Packaged contents of /home/max/source/build_dir/target-mipsel_24kc_musl-1.1.16/linux-ramips_mt7688/hello-world/ipkg-mipsel_24kc/kmod-hello-world into /home/max/source/bin/targets/ramips/mt7688/packages/kmod-hello-world_4.4.74+1.1-4_mipsel_24kc.ipk
    rm -rf /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world
    mkdir -p /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/stamp /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world
    . /home/max/source/include/shell.sh; export modules=; probe_module() { local mods="$1"; local boot="$2"; local mod; shift 2; for mod in $mods; do mkdir -p /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules.d; echo "$mod" >> /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules.d/hello-world; done; if [ -e /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules.d/hello-world ]; then if [ "$boot" = "1" -a ! -e /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules-boot.d/hello-world ]; then mkdir -p /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules-boot.d; ln -s ../modules.d/hello-world /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules-boot.d/; fi; modules="${modules:+$modules }$mods"; fi; }; add_module() { local priority="$1"; local mods="$2"; local boot="$3"; local mod; shift 3; for mod in $mods; do mkdir -p /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules.d; echo "$mod" >> /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules.d/$priority-hello-world; done; if [ -e /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules.d/$priority-hello-world ]; then if [ "$boot" = "1" -a ! -e /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules-boot.d/$priority-hello-world ]; then mkdir -p /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules-boot.d; ln -s ../modules.d/$priority-hello-world /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules-boot.d/; fi; modules="${modules:+$modules }$priority-hello-world"; fi; };  if [ -n "$modules" ]; then modules="$(echo "$modules" | tr ' ' '\n' | sort | uniq | paste -s -d' ' -)"; mkdir -p /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/etc/modules.d; mkdir -p /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/CONTROL; echo "#!/bin/sh" > /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/CONTROL/postinst-pkg; echo "[ -z \"\$IPKG_INSTROOT\" ] || exit 0" >> /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/CONTROL/postinst-pkg; echo ". /lib/functions.sh" >> /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/CONTROL/postinst-pkg; echo "insert_modules $modules" >> /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/CONTROL/postinst-pkg; chmod 0755 /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/CONTROL/postinst-pkg; fi
    SHELL= flock /home/max/source/tmp/.root-copy.flock -c 'cp -fpR /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world/. /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/'
    rm -rf /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/tmp-kmod-hello-world
    touch /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/root-ramips/stamp/.kmod-hello-world_installed
    if [ -f /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo/hello-world.default.install.clean ]; then rm -f /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo/hello-world.default.install /home/max/source/staging_dir/target-mipsel_24kc_musl-1.1.16/pkginfo/hello-world.default.install.clean; fi
    make[2]: Leaving directory '/home/max/omega2_kmods/hello-world'
    make[1]: Leaving directory '/home/max/source'
    

    Could it be that you set-up the paths the wrong way (src-link)? Are you executing the command from the from the right path (top-level in source)? Very weird that normal make -j4 works but this doesn't.. Any differences to the OnionIoT's source repository setup or the package folder structure here? What does make package/hello-world/prepare -j1 V=s do?



  • @Jackson-Wilson

    I found the issue. The following command works:

    make omega2_kmods/hello_world -j4

    However, when I make a change to the source file, and try to re-build, make says the package is "up to date."

    Maybe someone more savvy with the innards of the build system can tell me how to get a quick re-build of this package as I make changes along with this guide.

    Thanks!



  • @Maximilian-Gerhardt

    Hi!

    I suspect the src-link, since I get different output when I issue make package/hello-world/compile -j1 V=s:

    root@1fbc8ce194c0:~/source# make package/hello-world/clean -j1 V=s
    make[1]: Entering directory '/root/source'
    make[1]: *** No rule to make target 'package/hello-world/clean'. Stop.

    My added line to feeds.conf is:

    src-link omega2_kmods /root/source/omega2_kmods

    I'll mess around with this for a bit...



  • Good that you figured out another way. The only difference I see is that I placed my omega2_kmods folder outside of source in some other directory (/home/max/).



  • Well, my work-around doesn't allow me to clean and rebuild this single package, so I'm still struggling to get this to work. I have tried to replicate the structure you describe in this guide.

    I get the same "No rule..." error for /clean and /prepare commands as well.



  • Got it! I just had to get my directories and paths right. I was rushing a bit, and things were not lined up exactly correctly. I think it builds for the entire project because the recursive make found my package on its own, but I wasn't specifying it exactly right when I tried to build it individually.

    Great fun!



  • @Maximilian-Gerhardt I believe we both Geophoenixed in the early 2000's. Thanks for the article, it helped me migrate a water heater control unit to a newer linux kernel (with MMU)



Looks like your connection to Community was lost, please wait while we try to reconnect.