Jetson Xavier NX and Laird ST60-2230C WiFi module

I like to use a Laird ST60-2230C WiFi module connected to a Jetson Xavier NX (custom carrier board) and need some help to get the driver working.

Driver software is available here (laird-sterling60-firmware-usb-usb-8.6.0.12.tar.bz2 and backports-laird-8.6.0.12.tar.bz2).

Instructions from Laird to integrate the WiFi module in Linux from here.

Steps which I executed are:

export KLIB_BUILD=/lib/modules/4.9.140-tegra/build
export KLIB=/lib/modules/4.9.140-tegra/kernel
tar xf backports-laird-8.6.0.12.tar.bz2  -C projects/
cd ~/projects/laird-backport-8.6.0.12/
make defconfig-sterling60

The output of the Makefile is:

Your kernel headers are incomplete/not installed.
Please install kernel headers, including a .config
file or use the KLIB/KLIB_BUILD make variables to
set the kernel to build against, e.g.
make KLIB=/lib/modules/3.1.7/
to compile/install for the installed kernel 3.1.7
(that isn't currently running.)

The Makefile checks if $(KLIB_BUILD)/.config exists (KLIB_BUILD is /lib/modules/4.9.140-tegra/build). On my environment there is no /build directory in /lib/modules/4.9.140-tegra, thus the output above.

To install the kernel headers, I tried to follow the instructions from the Nvidia guide.

Unfortunately I don’t manage to put the things together right. Could you please give me instructions or try to point me in the right direction? Thanks.

I have no experience with the particular driver or hardware, but some information which might help follows…

Normally, when a driver is compiled, it must be compiled using the same headers as the running system. Also, the location of finding modules depends on the output from the command “uname -r”. The two are connected in a subtle way, and I will assume for the sake of example that “uname -r” replies with “4.9.140-tegra” in what follows.

Go to this directory:
cd /lib/modules/$(uname -r)

That location is where a custom (or standard) module build would end up placing the module (depends on “CONFIG_LOCALVERSION” being set correctly, and in this example’s case, that would be set to “-tegra”, the suffix of “4.9.140-tegra”…"uname -r" depends on the base kernel source version and the CONFIG_LOCALVERSION).

Within this module directory the actually modules are within the “kernel/” subdirectory, basically mirroring the location in kernel source where a freshly built module would have appeared.

The part which is subtle is the file “build”. This is a symbolic link and not a real directory. Check the output of this from the “/lib/modules/$(uname -r)” directory:
ls -l build

Note that on a PC this points to headers which are part of a standard Ubuntu package. Check the “ls -l build” on both your PC and the Jetson. Note that Jetsons may not have standard headers due to customization. You could install the full kernel source (which has headers) via downloading from the web site and unpacking, and then point the “build/” symbolic link to the actual source.

I personally download the kernel source for the running release, and (on a Jetson when natively compiling) unpack it at “/usr/src”. This results in new content within “/usr/src”:

sources
├── hardware
│   └── nvidia
└── kernel
    ├── kernel-4.9
    ├── nvgpu
    └── nvidia

The actual source would now be at “/usr/src/sources/kernel/kernel-4.9”. This is where I aim the symbolic link. Example:

cd /lib/modules/$(uname -r)
sudo ln -sf /usr/src/sources/kernel/kernel-4.9 build

There are other things you could consider, e.g., configuring the full source to match your running system, but technically I think that since you only need headers, that it wouldn’t matter. In reality though you have to make sure your “CONFIG_LOCALVERSION” has the “-tegra” even though you are only building an out-of-tree module. There might be other issues too for configuration, but you can always ask about those as you run into the issues.

If you are cross-compiling from a host PC, then you would not want to alter the PC’s idea of its own “/lib/modules/$(uname -r)/build”. However, even on a PC you could install full kernel source instead of just headers. Whatever location you unpack from you’d have the subdirectory “sources/kernel/kernel-4.9/” to treat as if it were headers. Once again, there may be configuration changes needed based on the configuration of the Jetson (you don’t want to use the PC’s configuration). If you run into issues related to this you can always ask more questions.

Personally I prefer native compile since Jetsons are so powerful, but you do have to be careful about running out of disk space since a kernel build can use up a lot. Sometimes I use a thumb drive mounted somewhere for the kernel temporary output if I am doing something taking a lot of space, but I’ve never had issues with the kernel source itself being too large.

Thank you for your detailed explanation @linuxdev.

I now have a question about the kernel configuration file: To build the driver (on the Jetson), a configuration file named “.config” is required in the directory “/lib/modules/$(uname -r)/build” which in my case points to the kernel source directory “/usr/src/Linux_for_Tegra/source/public/kernel/kernel-4.9”. In that directory there is no file named “.config”, but I can find some similarly named files:

build.config.cuttlefish.x86_64
build.config.goldfish.x86
build.config.goldfish.arm
build.config.goldfish.x86_64 
build.config.aarch64
build.config.goldfish.arm64
build.config.x86_64 
build.config.common
build.config.goldfish.mips 
build.config.cuttlefish.aarch64
build.config.goldfish.mips64

Is the required file one of the above (build.config.aarch64?) or do I need to generate the configuration file and if yes, how?

Edit: I might found the solution: copy the archive config.gz from /proc/config.gz to /lib/modules/$(uname -r)/build, unzip and rename file to .config.

The “.config” file is one of the more interesting configuration files. You don’t really need a “.config” file there, but you do need that file somewhere that the config system searches for during the kernel build. The use in “/lib/modules/$(uname -r)/build” is just one case typically used on a desktop PC. Note that if you are cross-compiling that you cannot use this since anything in a host PC’s “/lib/modules” is for it and not for the Jetson.

Jetsons have another way to get this file. On a running Jetson note that you have file “/proc/config.gz”. For a correctly running Jetson, by default, this file is basically the same as the configuration you get from “make tegra_defconfig”. Note that the tegra_defconfig make target generates a “.config”. This (either from tegra_defconfig or “/proc/config.gz”) should be your initial configuration when building a Jetson kernel. More explanation follows on using these, but always write down the output of “uname -r” and save that with the config you use.

One can use “/proc/config.gz” by saving it to some outside location and using gunzip to decompress it. Let’s just say that you want to keep a safe copy of this, in which case you might create directory “~/Jetson/kernel-4.9/archive” (either on host PC or on the Jetson, or both). On the Jetson you could do this:

mkdir -p ~/Jetson/kernel-4.9/archive
mkdir -p ~/Jetson/kernel-4.9/archive/$(uname -r)`
# As an example, you now have directory "~/Jetson/kernel-4.9/archive/4.9.140-tegra/"
cd ~/Jetson/kernel-4.9/archive/$(uname -r)
cp /proc/config.gz .
gunzip config.gz
# This creates a nearly exact replica of the config file for your running Jetson:
mv config config-$(uname -r)
# Now edit config-4.9.140-tegra such that:
CONFIG_LOCALVERSION="-tegra"
# The above makes an exact match of the existing Jetson config, including
# module directory search location...there are times you would change this,
# but this is good most of the time on a Jetson.

Any time you then use your config-4.9.140-tegra file, but rename it to “.config”, you have a perfect starting config. On the Jetson itself you could put this in “/lib/modules/$(uname -r)/build/.config”, but I would not bother unless you’ve create a custom “.deb” package which rebuilds against this.

If you build directly in the “kernel-4.9” directory, then simply copy the “.config” here. Or you could “make tegra_defconfig”, followed by editing CONFIG_LOCALVERSION. So long as your Jetson runs a default configuration both of these have the same result.

If you put your temporary content into a separate location using the “make O=...some/location... ”, then this location is where you want the “.config”. If you cross-compile, then you cannot use the “/lib/modules” content since this is for the wrong computer.

I’ll add this question for you: Are you compiling natively on the Jetson, or are you cross-compiling from the host PC? It sounds like you are building from the host PC. Can you show me your current exact compile command, and the directory you are building from?

I am compiling natively on the Jetson. The kernel source is located at /usr/src/Linux_for_Tegra/source/public/kernel/kernel-4.9 and the symbolic link /lib/modules/4.9.140-tegra/build points to it.

I created a replica of the /proc/config.gz and edited the CONFIG_LOCALVERSION variable as you described above. I copied it to the kernel-4.9 directory and renamed it to .config. (Because the Makefile of the laird backport driver requires the .config file in the directory /lib/modules/$(uname -r)/build which in my case points to the kernel-4.9 directory.)

Then in the kernel-4.9 directory I called make prepare (if not executing that command, one get the message: ERROR: Kernel configuration is invalid. include/generated/autoconf.h or include/config/auto.conf are missing. Run 'make oldconfig && make prepare' on kernel src to fix it. when later calling the Laird backport driver Makefile.)

When calling the make prepare command, I was asked to choose modules with y,n or m option (example: Three-wire UART (H5) protocol support (BT_HCIUART_3WIRE) [N/y/?] (NEW)). Not sure what to do here.

Afterwards I called make modules_prepare (not sure if good or bad idea).

After that I run the Makefile of the laird backport driver (which is in the directory ~/laird-backport-8.6.0.12) using make defconfig-sterling60. Then I called make which threw the message WARNING: Symbol version dump ./Module.symvers is missing; modules will have no dependencies and modversions.

Is this procedure right and how can I get the .symvers file?

This (copy of config plus “make prepare”) is probably correct, I see no flaw in it. I tried this on a TX2 and it worked correctly, but may have some (unlikely) differences compared to the NX. One possibility is that the .config itself is actually wrong, but since you got this from “/proc/config.gz” it is pretty much guaranteed to be a valid config.

As a test, in that particular directory (the “kernel-4.9” directory), does this work?

  1. sudo make mrproper (beware that this will delete your “.config”, so you probably want to save it outside somewhere).
  2. sudo make tegra_defconfig
  3. sudo make prepare

If the above works, then try exactly the same, except replace the “sudo make tegra_defconfig” with a copy of your “.config” (don’t add the “.config” until after the “make mrproper” step since this will erase it).

Another possibility is that you simply need some other package on the NX to compile, and that the compile error is just a symptom of something else missing. The test above should probably answer that.

Note that being asked to select features implies that no “.config” was found. This tends to say that something was actually wrong with the copy of config.gz step, but given the above, I don’t see what.

As a test, in that particular directory (the “ kernel-4.9 ” directory), does this work?

  1. sudo make mrproper (beware that this will delete your “ .config ”, so you probably want to save it outside somewhere).
  2. sudo make tegra_defconfig
  3. sudo make prepare

If the above works, then try exactly the same, except replace the “ sudo make tegra_defconfig ” with a copy of your “ .config ” (don’t add the “ .config ” until after the “ make mrproper ” step since this will erase it).

This worked.

I managed to build the Laird driver with

cd /lib/modules/$(uname -r)/build
sudo make prepare
sudo make modules_prepare

cd laird-backport-8.6.0.12/
sudo make defconfig-sterling60
sudo make

make prepare is necessary because otherweise it will complain about invalid kernel configuration (include/generated/autoconf.h or include/config/auto.conf are missing). make modules_prepare is necessary because otherwise it will complain about missing symbol version dump (./Module.symvers).

This process generated the following kernel driver modules:

lrdmwl.ko at ./drivers/net/wireless/laird/lrdmwl/
lrdmwl_sdio.ko at ./drivers/net/wireless/laird/lrdmwl/
mac80211.ko at ./net/mac80211/
cfg80211.ko at ./net/wireless/
compat.ko at ./compat/

They need to be loaded with:

cd laird-backport-8.6.0.12/
insmod ./compat/compat.ko
insmod ./net/wireless/cfg80211.ko
insmod ./net/mac80211/mac80211.ko
insmod ./drivers/net/wireless/laird/lrdmwl/lrdmwl.ko
insmod ./drivers/net/wireless/laird/lrdmwl/lrdmwl_pcie.ko

Doing so, I get the error:

insmod: ERROR: could not insert module ./compat/compat.ko: Invalid module format

Do you have any idea what could have gone wrong?

I’m guessing that the running kernel and the kernel source used to build from are incompatible. I couldn’t say for sure, but an example would be if you have a 4.9.140 kernel, but compile from source which is newer. I’m not sure what the error message would be if the CONFIG_LOCALVERSION is set wrong, but it would need to be the suffix of the output from command “uname -r”. A typical Jetson would have a “uname -r” of something like 4.9.140-tegra, which implies a CONFIG_LOCALVERSION of “-tegra”. Between using the “/proc/config.gz” and setting the CONFIG_LOCALVERSION to match it should work unless the kernel source itself is the wrong version.

I started again and this time it inserted the modules. Probably I was missing the CONFIG_LOCALVERSION="-tegra".

Unfortunately the WiFi module does not show up yet even I have built and loaded the driver modules. Do you have some (general) advice on what possible reasons could be and how I could check them? Is there any configuration I am missing? The WiFi module is on an external board and connected via USB to the jetson carrier board.

If you monitor the logs via “dmesg --follow”, and only then insert the USB cable, what logs show up? It should indicate USB enumeration succeeding or failing, and then whether a driver was bound. Knowing at which stage progress halts would help.

Here is the log:

[  242.726484] tegra-xusb 3610000.xhci: exiting ELPG
[  242.731224] tegra-xusb 3610000.xhci: Firmware timestamp: 2019-07-24 05:47:34 UTC, Version: 60.06 release
[  242.731861] tegra-xusb 3610000.xhci: exiting ELPG done
[  242.956274] usb 1-3: new high-speed USB device number 10 using tegra-xusb
[  242.977726] usb 1-3: New USB device found, idVendor=05e3, idProduct=0608
[  242.977737] usb 1-3: New USB device strings: Mfr=0, Product=1, SerialNumber=0
[  242.977743] usb 1-3: Product: USB2.0 Hub
[  242.978927] hub 1-3:1.0: USB hub found
[  242.979238] hub 1-3:1.0: 4 ports detected
[  243.274018] usb 1-3.2: new high-speed USB device number 11 using tegra-xusb
[  243.305082] usb 1-3.2: New USB device found, idVendor=1286, idProduct=2052
[  243.305092] usb 1-3.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  243.305098] usb 1-3.2: Product: Marvell Wireless Device
[  243.305103] usb 1-3.2: Manufacturer: Marvell
[  243.305107] usb 1-3.2: SerialNumber: 0000000000000000
[  243.329810] mwifiex: disagrees about version of symbol cfg80211_sched_scan_results
[  243.334009] mwifiex: Unknown symbol cfg80211_sched_scan_results (err -22)
[  243.337572] mwifiex: disagrees about version of symbol cfg80211_chandef_dfs_required
[  243.340978] mwifiex: Unknown symbol cfg80211_chandef_dfs_required (err -22)
[  243.344361] mwifiex: disagrees about version of symbol cfg80211_scan_done
[  243.347665] mwifiex: Unknown symbol cfg80211_scan_done (err -22)
[  243.351015] mwifiex: disagrees about version of symbol cfg80211_sched_scan_stopped
[  243.354414] mwifiex: Unknown symbol cfg80211_sched_scan_stopped (err -22)
[  243.357857] mwifiex: disagrees about version of symbol cfg80211_remain_on_channel_expired
[  243.361307] mwifiex: Unknown symbol cfg80211_remain_on_channel_expired (err -22)
[  243.364704] mwifiex: disagrees about version of symbol cfg80211_cac_event
[  243.368161] mwifiex: Unknown symbol cfg80211_cac_event (err -22)
[  243.372684] mwifiex: disagrees about version of symbol ieee80211_amsdu_to_8023s
[  243.376633] mwifiex: Unknown symbol ieee80211_amsdu_to_8023s (err -22)
[  243.379917] mwifiex: disagrees about version of symbol regulatory_hint
[  243.383220] mwifiex: Unknown symbol regulatory_hint (err -22)
[  243.387114] mwifiex: disagrees about version of symbol cfg80211_new_sta
[  243.390401] mwifiex: Unknown symbol cfg80211_new_sta (err -22)
[  243.393708] mwifiex: disagrees about version of symbol cfg80211_disconnected
[  243.396978] mwifiex: Unknown symbol cfg80211_disconnected (err -22)
[  243.400328] mwifiex: disagrees about version of symbol wiphy_new_nm
[  243.403539] mwifiex: Unknown symbol wiphy_new_nm (err -22)
[  243.406797] mwifiex: Unknown symbol cfg80211_connect_bss (err 0)
[  243.410699] mwifiex: disagrees about version of symbol cfg80211_ready_on_channel
[  243.413963] mwifiex: Unknown symbol cfg80211_ready_on_channel (err -22)
[  243.417222] mwifiex: disagrees about version of symbol cfg80211_classify8021d
[  243.420504] mwifiex: Unknown symbol cfg80211_classify8021d (err -22)
[  243.425505] mwifiex: disagrees about version of symbol wiphy_register
[  243.431872] mwifiex: Unknown symbol wiphy_register (err -22)
[  243.437492] mwifiex: disagrees about version of symbol cfg80211_put_bss
[  243.444314] mwifiex: Unknown symbol cfg80211_put_bss (err -22)
[  243.450097] mwifiex: disagrees about version of symbol cfg80211_ch_switch_notify
[  243.457960] mwifiex: Unknown symbol cfg80211_ch_switch_notify (err -22)
[  243.464601] mwifiex: disagrees about version of symbol cfg80211_tdls_oper_request
[  243.471962] mwifiex: Unknown symbol cfg80211_tdls_oper_request (err -22)
[  243.478869] mwifiex: disagrees about version of symbol ieee80211_bss_get_ie
[  243.485518] mwifiex: Unknown symbol ieee80211_bss_get_ie (err -22)
[  243.491672] mwifiex: disagrees about version of symbol cfg80211_ibss_joined
[  243.498903] mwifiex: Unknown symbol cfg80211_ibss_joined (err -22)
[  243.505177] mwifiex: disagrees about version of symbol cfg80211_michael_mic_failure
[  243.512834] mwifiex: Unknown symbol cfg80211_michael_mic_failure (err -22)
[  243.519756] mwifiex: disagrees about version of symbol wiphy_apply_custom_regulatory
[  243.527435] mwifiex: Unknown symbol wiphy_apply_custom_regulatory (err -22)
[  243.534106] mwifiex: disagrees about version of symbol cfg80211_del_sta_sinfo
[  243.541382] mwifiex: Unknown symbol cfg80211_del_sta_sinfo (err -22)
[  243.548092] mwifiex: disagrees about version of symbol wiphy_unregister
[  243.554844] mwifiex: Unknown symbol wiphy_unregister (err -22)
[  243.560783] mwifiex: disagrees about version of symbol cfg80211_sched_scan_stopped_rtnl
[  243.568572] mwifiex: Unknown symbol cfg80211_sched_scan_stopped_rtnl (err -22)
[  243.576100] mwifiex: disagrees about version of symbol cfg80211_get_bss
[  243.582753] mwifiex: Unknown symbol cfg80211_get_bss (err -22)
[  243.588406] mwifiex: Unknown symbol __ieee80211_get_channel (err 0)
[  243.595011] mwifiex: disagrees about version of symbol cfg80211_mgmt_tx_status
[  243.601880] mwifiex: Unknown symbol cfg80211_mgmt_tx_status (err -22)
[  243.608545] mwifiex: disagrees about version of symbol cfg80211_rx_mgmt
[  243.614855] mwifiex: Unknown symbol cfg80211_rx_mgmt (err -22)
[  243.621040] mwifiex: disagrees about version of symbol cfg80211_report_wowlan_wakeup
[  243.629047] mwifiex: Unknown symbol cfg80211_report_wowlan_wakeup (err -22)
[  243.635700] mwifiex: disagrees about version of symbol cfg80211_inform_bss_data
[  243.642923] mwifiex: Unknown symbol cfg80211_inform_bss_data (err -22)
[  243.649353] mwifiex: disagrees about version of symbol cfg80211_radar_event
[  243.656241] mwifiex: Unknown symbol cfg80211_radar_event (err -22)
[  243.662582] mwifiex: disagrees about version of symbol wiphy_free
[  243.669062] mwifiex: Unknown symbol wiphy_free (err -22)
[  243.674048] mwifiex: disagrees about version of symbol cfg80211_cqm_rssi_notify
[  243.681403] mwifiex: Unknown symbol cfg80211_cqm_rssi_notify (err -22)

This text I have found on stackexchange might explains the reason:

With kernels built with the CONFIG_MODVERSIONS , the version number can differ, but the layout of the data structures must be the same. This option is activated in the Ubuntu kernels. With this option, in addition to the headers, modules need to be compiled against the proper Module.symvers file. Ubuntu, like most distributions, includes this file in the same package as the kernel headers resulting from the compilation. The Ubuntu kernel header package is called linux-headers-VERSION-VARIANT , e.g. linux-headers-3.8.0-38-generic .

With kernels built without the CONFIG_MODVERSIONS (which may be the case if you compiled your own kernel), the check when loading a module is a simple version check. You can take the unpacked kernel source, copy the .config that was used during the compilation of your running kernel, and run make modules_prepare . The onus is on you to verify that any patch you’ve made to the kernel doesn’t affect binary compatibility.

In my .config file it is CONFIG_MODVERSIONS=y. If I understand the text above correctly, it should help to set ONFIG_MODVERSIONS=n. Do you think that is a good solution or is there a better one?

Do not (at least yet) change CONFIG_MODVERSIONS. Instead I suggest compiling that driver against the source code of the running kernel configuration. This also means not directly editing the “.config” file (other than perhaps CONFIG_LOCALVERSION=-tegra).

For a more detailed explanation, consider this line:
[ 243.329810] mwifiex: disagrees about version of symbol cfg80211_sched_scan_results

This is a difference in versions of something. This is related to making the code you compile against being the same as the running system or not.

Then there is this error:
[ 243.334009] mwifiex: Unknown symbol cfg80211_sched_scan_results (err -22)

This latter error means you enabled some feature which had dependencies, and the dependencies were not also enabled. This should never happen if using a config editor, e.g., "make nconfig’ or “make menuconfig” since editors understand dependencies. When compiling out of tree code you might need to note dependencies and manually enable those (one reason I like “nconfig” over “menuconfig” is the symbol search option).

Basically USB is working, but the module won’t load due to version conflicts plus unknown symbols (meaning things which should be enabled but are not).

If you compile against this kernel (including matching configuration), then you shouldn’t have to worry about the CONFIG_MODVERSIONS. If you do modify CONFIG_MODVERSIONS, then chances are a chain of broken functions will occur and be more trouble than it is worth. If you have a binary-only driver which has no ability to be compiled, then you might be out of luck and have to deal with this, but it should be avoided and be considered a last resort only.

Tried to use one of the config editors, but unfortunately I did not really know what to configure / which menu options to use.

I was also confused by the text in the title of the config editor which is .config - Linux/arm64 4.9.253 Kernel Configuration. If the term 4.9.253 specifies the kernel version, then I would have expected 4.9.140, since that is what uname -r returns.

Ok. Seems that I was using the wrong kernel source version. Had to download the sources of L4T 32.4.4 which includes kernel 4.9.140 instead of L4T 32.61 which includes kernel 4.9.253. Not really obvious when looking on the download page. Was not asked anymore about configuring modules. Nevertheless, still running into same issue

Tried a new approach: Extracted the kernel headers from the L4T R32.4.4 archive (directory linux-headers-4.9.140-tegra-ubuntu18.04_aarch64) to the target.

Let the symbolic link /lib/modules/$(uname -r)/build point to the kernel headers ~/Linux_for_Tegra/kernel/kernel_headers/linux-headers-4.9.140-tegra-ubuntu18.04_aarch64/kernel-4.9

In the kernel header directory, there is already a .config file and a Modules.symvers file included, so no need to generate them this time. I edited the .config file such that CONFIG_LOCALVERSION="-tegra".

Then I built the driver of the WiFi module using sudo make defconfig-sterling60 and sudo makein the driver directory, which worked seamlessly.

Unfortunately same messages show up after loading the driver and rebooting:

[   10.585240] compat: loading out-of-tree module taints kernel.
[   10.590574] Loading modules backported from Laird Linux version LRD-REL-7.1.0.9-0-g48ea769c7d93
[   10.590577] Backport generated by backports.git v7.1.0.9
[   10.599525] usbcore: registered new interface driver btusb
[   10.710515] mwifiex: disagrees about version of symbol cfg80211_sched_scan_results
[   10.718327] mwifiex: Unknown symbol cfg80211_sched_scan_results (err -22)

The above is why I like “nconfig”. “nconfig” has a symbol search function. Then the “m” key to enable as a module (if and only if the content can be created as a module), or the “y” key to integrate directly into a kernel (not suggested unless you have to or have a reason), or the “n” key to disable the feature. Then save before exit.

If you did not use a config editor, then if there were any dependencies which you failed to set, then I would expect the module or integrated feature to fail (and possibly compile).

Your header copy to native machine seems to have been valid.

A “tainted” kernel is just one where the Linux kernel people won’t support debugging. Either the license is incompatible, or not visible, and there is no way for the general public to know what actually went in to the tainted module. This does not necessarily mean anything failed. Basically, if you add third party code to the kernel which isn’t available to everyone and licensed as available, then the kernel is “tainted”. Perhaps the code for the module is properly licensed, but the out-of-tree nature prevented the kernel from knowing this.

Consider these “symbols”:

This means that there is a CONFIG_something which is a dependency which is missing for the one about “ mwifiex: Unknown symbol cfg80211_sched_scan_results (err -22)”. Perhaps this could be compiled as a module from the native kernel source code and inserted as a module prior to inserting your module. This next though is a bit more problematic:
[ 10.710515] mwifiex: disagrees about version of symbol cfg80211_sched_scan_results

There may be more than one version of the cfg80211_sched_scan_results symbol, e.g., one kernel release may have used one call specification to the function, while a newer kernel uses a different set of arguments to call the function with. The two would be incompatible. This would be a case of trying to compile code from an incompatible module source into a kernel which does not have the correct support to use the older or newer version of the given symbol.

Problem is fixed. There was already a wrong module loaded when loading the new modules. Blacklisting the mwifiex module in /etc/modprobe.d solved it. wlan0 now shows up.