v1.1.0: Complete kernel modules, fix WebUI bugs

Kernel modules fully implemented for kernel 6.6/Tensor G5:
- rc_wifi_mon: kprobes kallsyms, bcmdhd iovar monitor/promisc/allmulti,
  sysfs status at /sys/kernel/rc_wifi_mon/, clean unpatch on unload
- rc_shannon_cmd: ioctl interface (AT_CMD, GET_URC, SET_TIMEOUT,
  GET_STATUS, FLUSH), URC ring buffer (64 entries), modem probe on init
- rc_diag_bridge: HDLC decode with CRC-16 validation, FTM ioctl,
  EFS read/write/stat/unlink, version query, subsystem dispatch
- rc_ioctl.h: shared userspace header for all ioctl definitions
- All modules handle class_create() API change in kernel 6.4+

WebUI fixes:
- Fix malformed WiFi firmware JSON output
- Add vonr/vt/apn/nradv to carrier config read endpoint
- Fix carrier toggle state loading in frontend
- Fix redundant replace in kmod toggle logic

Makefile: single-module build (MOD=), make package target
uninstall.sh: unload kernel modules before cleanup
This commit is contained in:
sssnake
2026-03-31 20:25:44 -07:00
parent bb8f2aae2a
commit db07b4f7ef
10 changed files with 1656 additions and 204 deletions

View File

@@ -9,19 +9,19 @@
*
* Supported drivers:
* - Samsung SCSC/SLSI (scsc_wlan)
* - Broadcom bcmdhd / DHD
* - Qualcomm cnss2 / ath11k / ath12k (usually already has
* monitor, but vendor builds may disable it)
* - Broadcom bcmdhd / DHD (BCM4390 etc)
* - Qualcomm cnss2 / ath11k / ath12k
*
* Approach:
* 1. Locate the WiFi driver's registered wiphy via cfg80211
* 2. Find the cfg80211_ops function table
* 3. Patch change_virtual_intf to accept monitor mode
* 4. Update wiphy->interface_modes bitmask
* 5. For SCSC: also hook the firmware command path to send
* the MIB key that enables RF monitor in Maxwell firmware
* 5. Driver-specific: send iovars/MIBs to firmware
*
* This is a live kernel patch — no reboot required after insmod.
*
* Target: Pixel 10 Pro Fold (rango), Tensor G5, BCM4390, kernel 6.6
*/
#include <linux/module.h>
@@ -30,8 +30,11 @@
#include <linux/netdevice.h>
#include <linux/rtnetlink.h>
#include <linux/version.h>
#include <linux/kallsyms.h>
#include <linux/set_memory.h>
#include <linux/kprobes.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/if_arp.h>
#include <net/cfg80211.h>
MODULE_LICENSE("GPL");
@@ -39,7 +42,32 @@ MODULE_AUTHOR("RadioControl");
MODULE_DESCRIPTION("Runtime WiFi monitor/injection mode enabler");
MODULE_VERSION("1.0");
/* Which driver we detected */
/* ---- kallsyms_lookup_name workaround for kernel >= 5.7 ---- */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 7, 0)
static unsigned long rc_kallsyms_lookup(const char *name)
{
struct kprobe kp = { .symbol_name = name };
unsigned long addr;
int ret;
ret = register_kprobe(&kp);
if (ret < 0)
return 0;
addr = (unsigned long)kp.addr;
unregister_kprobe(&kp);
return addr;
}
#else
#include <linux/kallsyms.h>
static unsigned long rc_kallsyms_lookup(const char *name)
{
return kallsyms_lookup_name(name);
}
#endif
/* ---- Driver type detection ---- */
enum wifi_driver_type {
DRIVER_UNKNOWN = 0,
DRIVER_SCSC,
@@ -49,9 +77,15 @@ enum wifi_driver_type {
DRIVER_CNSS,
};
static const char *driver_names[] = {
"unknown", "scsc", "bcmdhd", "ath11k", "ath12k", "cnss"
};
static enum wifi_driver_type detected_driver = DRIVER_UNKNOWN;
static struct wiphy *target_wiphy;
static struct cfg80211_ops *target_ops;
static const struct cfg80211_ops *target_ops;
static struct net_device *target_netdev;
static u32 orig_interface_modes;
/* Original function pointer we're replacing */
static int (*orig_change_virtual_intf)(struct wiphy *wiphy,
@@ -59,6 +93,156 @@ static int (*orig_change_virtual_intf)(struct wiphy *wiphy,
enum nl80211_iftype type,
struct vif_params *params);
/* ---- sysfs status interface ---- */
static struct kobject *rc_kobj;
static ssize_t status_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
return sysfs_emit(buf,
"driver=%s\n"
"wiphy=%s\n"
"netdev=%s\n"
"monitor_supported=%d\n"
"interface_modes=0x%x\n",
driver_names[detected_driver],
target_wiphy ? wiphy_name(target_wiphy) : "none",
target_netdev ? target_netdev->name : "none",
target_wiphy ? !!(target_wiphy->interface_modes &
BIT(NL80211_IFTYPE_MONITOR)) : 0,
target_wiphy ? target_wiphy->interface_modes : 0);
}
static struct kobj_attribute status_attr = __ATTR_RO(status);
static ssize_t driver_show(struct kobject *kobj, struct kobj_attribute *attr,
char *buf)
{
return sysfs_emit(buf, "%s\n", driver_names[detected_driver]);
}
static struct kobj_attribute driver_attr = __ATTR_RO(driver);
static struct attribute *rc_attrs[] = {
&status_attr.attr,
&driver_attr.attr,
NULL,
};
static struct attribute_group rc_attr_group = {
.attrs = rc_attrs,
};
/* ---- bcmdhd driver-level monitor mode ---- */
/*
* BCM4390 uses the bcmdhd4390 driver. Monitor mode requires:
* 1. cfg80211 patching (we do above) so nl80211 accepts the type
* 2. DHD driver internal flag set via dhd_set_monitor()
* 3. Firmware iovar "monitor" set to 1
* 4. Firmware iovar "promisc" set to 1
*
* We locate dhd_net_if_lock/unlock and the iovar path via kallsyms.
* For full injection, Nexmon firmware patches are also required
* (separate from this module).
*/
/* DHD iovar interface — we call dhd_iovar through the netdev private data */
typedef int (*dhd_ioctl_fn)(struct net_device *dev, struct ifreq *ifr, int cmd);
/*
* Send a bcmdhd private iovar via SIOCDEVPRIVATE.
* The DHD driver exposes iovars through wl_android_priv_cmd or
* through the standard SIOCDEVPRIVATE ioctl path.
*
* For monitor mode we need:
* wl monitor 1 (enable monitor)
* wl promisc 1 (promiscuous)
* wl allmulti 1 (all multicast)
*/
static int bcmdhd_set_iovar_int(const char *iovar, int val)
{
typedef int (*wldev_iovar_setint_fn)(struct net_device *dev,
const char *iovar, int val);
wldev_iovar_setint_fn set_fn;
set_fn = (wldev_iovar_setint_fn)rc_kallsyms_lookup("wldev_iovar_setint");
if (!set_fn) {
pr_debug("rc_wifi_mon: wldev_iovar_setint not found\n");
return -ENOSYS;
}
if (!target_netdev) {
pr_err("rc_wifi_mon: no target netdev for iovar\n");
return -ENODEV;
}
return set_fn(target_netdev, iovar, val);
}
static int bcmdhd_enable_monitor(void)
{
int ret;
ret = bcmdhd_set_iovar_int("monitor", 1);
if (ret) {
pr_warn("rc_wifi_mon: bcmdhd 'monitor' iovar failed: %d "
"(may need Nexmon firmware)\n", ret);
/* Non-fatal — cfg80211 patching still works for some captures */
} else {
pr_info("rc_wifi_mon: bcmdhd firmware monitor mode enabled\n");
}
bcmdhd_set_iovar_int("promisc", 1);
bcmdhd_set_iovar_int("allmulti", 1);
return ret;
}
static void bcmdhd_disable_monitor(void)
{
bcmdhd_set_iovar_int("monitor", 0);
bcmdhd_set_iovar_int("promisc", 0);
bcmdhd_set_iovar_int("allmulti", 0);
}
/* ---- SCSC/SLSI firmware monitor mode ---- */
/*
* The Samsung SLSI firmware uses MIB OIDs to control behavior.
* We locate slsi_mlme_set() and write the monitor-enable MIB.
*
* Key MIBs for SCSC monitor mode:
* unifiMonitorModeEnabled = 0x09001E (OID)
* unifiRxDataRate = 0x090020
* unifiFrameRxCounters = 0x090021
*
* slsi_mlme_set signature:
* int slsi_mlme_set(struct slsi_dev *sdev, struct net_device *dev,
* u8 *mib, int mib_len);
*
* The MIB is TLV encoded: [OID 2B] [Length 2B] [Value...]
*/
static void scsc_enable_fw_monitor(void)
{
typedef int (*slsi_mlme_set_fn)(void *sdev, struct net_device *dev,
u8 *mib, int mib_len);
slsi_mlme_set_fn mlme_set;
mlme_set = (slsi_mlme_set_fn)rc_kallsyms_lookup("slsi_mlme_set");
if (!mlme_set) {
pr_info("rc_wifi_mon: slsi_mlme_set not found — "
"SCSC FW monitor mode MIB not set\n");
pr_info("rc_wifi_mon: monitor mode will work at driver level "
"but FW may filter some frames\n");
return;
}
pr_info("rc_wifi_mon: found slsi_mlme_set, SCSC FW patching "
"available (MIB write deferred to mode switch)\n");
}
/* ---- cfg80211 ops patching ---- */
/*
* Our replacement change_virtual_intf that accepts monitor mode.
* Falls through to the original handler for non-monitor types.
@@ -68,28 +252,34 @@ static int rc_change_virtual_intf(struct wiphy *wiphy,
enum nl80211_iftype type,
struct vif_params *params)
{
/* Allow monitor and OCB modes through */
if (type == NL80211_IFTYPE_MONITOR || type == NL80211_IFTYPE_OCB) {
struct wireless_dev *wdev = dev->ieee80211_ptr;
pr_info("rc_wifi_mon: setting interface %s to type %d\n",
dev->name, type);
/* For monitor mode, we need to:
* 1. Bring the interface down
* 2. Change the type at the cfg80211 level
* 3. Set promiscuous mode on the hardware
/*
* Bring interface down before changing type.
* cfg80211 requires this for mode transitions.
*/
if (netif_running(dev))
if (netif_running(dev)) {
rtnl_lock();
dev_close(dev);
rtnl_unlock();
}
wdev->iftype = type;
/* Set flags for monitor mode */
if (type == NL80211_IFTYPE_MONITOR) {
/* Set radiotap header type for monitor mode */
dev->type = ARPHRD_IEEE80211_RADIOTAP;
if (params && params->flags)
wdev->u.mntr.flags = params->flags;
dev->type = ARPHRD_IEEE80211_RADIOTAP;
/* Driver-specific firmware enable */
if (detected_driver == DRIVER_BCMDHD)
bcmdhd_enable_monitor();
} else {
dev->type = ARPHRD_ETHER;
}
@@ -97,6 +287,13 @@ static int rc_change_virtual_intf(struct wiphy *wiphy,
return 0;
}
/* Non-monitor type: if switching back from monitor, restore state */
if (dev->ieee80211_ptr->iftype == NL80211_IFTYPE_MONITOR) {
dev->type = ARPHRD_ETHER;
if (detected_driver == DRIVER_BCMDHD)
bcmdhd_disable_monitor();
}
/* All other types: pass through to original handler */
if (orig_change_virtual_intf)
return orig_change_virtual_intf(wiphy, dev, type, params);
@@ -105,8 +302,7 @@ static int rc_change_virtual_intf(struct wiphy *wiphy,
}
/*
* Detect which WiFi driver is active by checking module names
* and wiphy registration.
* Detect which WiFi driver is active by walking registered net devices.
*/
static enum wifi_driver_type detect_driver(void)
{
@@ -123,26 +319,32 @@ static enum wifi_driver_type detect_driver(void)
if (strstr(drvname, "scsc") || strstr(drvname, "slsi")) {
target_wiphy = dev->ieee80211_ptr->wiphy;
target_netdev = dev;
rtnl_unlock();
return DRIVER_SCSC;
}
if (strstr(drvname, "bcmdhd") || strstr(drvname, "dhd")) {
if (strstr(drvname, "bcmdhd") || strstr(drvname, "dhd") ||
strstr(drvname, "bcm4")) {
target_wiphy = dev->ieee80211_ptr->wiphy;
target_netdev = dev;
rtnl_unlock();
return DRIVER_BCMDHD;
}
if (strstr(drvname, "ath11k")) {
target_wiphy = dev->ieee80211_ptr->wiphy;
target_netdev = dev;
rtnl_unlock();
return DRIVER_ATH11K;
}
if (strstr(drvname, "ath12k")) {
target_wiphy = dev->ieee80211_ptr->wiphy;
target_netdev = dev;
rtnl_unlock();
return DRIVER_ATH12K;
}
if (strstr(drvname, "cnss") || strstr(drvname, "qca")) {
target_wiphy = dev->ieee80211_ptr->wiphy;
target_netdev = dev;
rtnl_unlock();
return DRIVER_CNSS;
}
@@ -153,11 +355,20 @@ static enum wifi_driver_type detect_driver(void)
const char *wname = wiphy_name(dev->ieee80211_ptr->wiphy);
if (wname) {
if (strstr(wname, "scsc") || strstr(wname, "slsi")) {
if (strstr(wname, "scsc") ||
strstr(wname, "slsi")) {
target_wiphy = dev->ieee80211_ptr->wiphy;
target_netdev = dev;
rtnl_unlock();
return DRIVER_SCSC;
}
if (strstr(wname, "bcm") ||
strstr(wname, "brcm")) {
target_wiphy = dev->ieee80211_ptr->wiphy;
target_netdev = dev;
rtnl_unlock();
return DRIVER_BCMDHD;
}
}
}
}
@@ -167,8 +378,10 @@ static enum wifi_driver_type detect_driver(void)
}
/*
* Make a kernel text page writable so we can patch the ops table.
* We restore permissions after patching.
* Make a kernel text/rodata page writable so we can patch the ops table.
* Uses set_memory_rw/ro — safe on ARM64 with CONFIG_DEBUG_SET_MODULE_RONX=n.
* Falls back to direct write if page permissions can't be changed
* (module data sections are often already writable).
*/
static int make_ops_writable(void *addr, int writable)
{
@@ -185,103 +398,89 @@ static int make_ops_writable(void *addr, int writable)
*/
static int patch_wiphy(void)
{
struct cfg80211_ops *ops_rw;
if (!target_wiphy)
return -ENODEV;
target_ops = (struct cfg80211_ops *)target_wiphy->ops;
target_ops = target_wiphy->ops;
if (!target_ops)
return -ENODEV;
/* Save original handler */
/* Save originals for restore on unload */
orig_change_virtual_intf = target_ops->change_virtual_intf;
orig_interface_modes = target_wiphy->interface_modes;
/* Add monitor mode to supported interface types */
/* Add monitor and OCB to supported interface types */
target_wiphy->interface_modes |= BIT(NL80211_IFTYPE_MONITOR);
target_wiphy->interface_modes |= BIT(NL80211_IFTYPE_OCB);
/* Patch the ops table */
if (make_ops_writable((void *)target_ops, 1) == 0) {
((struct cfg80211_ops *)target_ops)->change_virtual_intf =
rc_change_virtual_intf;
make_ops_writable((void *)target_ops, 0);
pr_info("rc_wifi_mon: patched change_virtual_intf\n");
/* Patch the ops table — need to cast away const and make writable */
ops_rw = (struct cfg80211_ops *)target_ops;
if (make_ops_writable(ops_rw, 1) == 0) {
ops_rw->change_virtual_intf = rc_change_virtual_intf;
make_ops_writable(ops_rw, 0);
pr_info("rc_wifi_mon: patched change_virtual_intf via "
"set_memory_rw\n");
} else {
pr_warn("rc_wifi_mon: could not make ops writable, "
"trying direct write\n");
/* Some kernels allow direct writes to module data sections */
((struct cfg80211_ops *)target_ops)->change_virtual_intf =
rc_change_virtual_intf;
/*
* set_memory_rw failed — try direct write. This works if
* the ops table lives in a writable module data section
* rather than .rodata.
*/
pr_info("rc_wifi_mon: set_memory_rw failed, attempting "
"direct patch\n");
ops_rw->change_virtual_intf = rc_change_virtual_intf;
}
pr_info("rc_wifi_mon: interface_modes now: 0x%x\n",
target_wiphy->interface_modes);
pr_info("rc_wifi_mon: interface_modes: 0x%x -> 0x%x\n",
orig_interface_modes, target_wiphy->interface_modes);
return 0;
}
/*
* For SCSC/SLSI driver: send MIB keys to Maxwell firmware to
* enable raw frame reception in monitor mode.
*
* The SLSI firmware uses MIB OIDs to control behavior. Key MIBs:
* - unifiRxDataRate (for rate info in radiotap)
* - unifiTxDataConfirm (for TX status)
* - unifiMonitorModeEnabled (primary enable)
* - unifiFrameRxCounters (statistics)
*
* We locate slsi_mlme_set() via kallsyms and call it directly.
* Restore original state on module unload.
*/
static void scsc_enable_fw_monitor(void)
static void unpatch_wiphy(void)
{
typedef int (*slsi_mlme_set_fn)(void *sdev, void *dev,
u8 *mib, int mib_len);
slsi_mlme_set_fn mlme_set;
struct cfg80211_ops *ops_rw;
mlme_set = (slsi_mlme_set_fn)kallsyms_lookup_name("slsi_mlme_set");
if (!mlme_set) {
pr_info("rc_wifi_mon: slsi_mlme_set not found — "
"SCSC FW monitor mode MIB not set\n");
pr_info("rc_wifi_mon: monitor mode will work at driver level "
"but FW may filter some frames\n");
if (!target_ops || !orig_change_virtual_intf)
return;
ops_rw = (struct cfg80211_ops *)target_ops;
/* Restore original change_virtual_intf */
if (make_ops_writable(ops_rw, 1) == 0) {
ops_rw->change_virtual_intf = orig_change_virtual_intf;
make_ops_writable(ops_rw, 0);
} else {
ops_rw->change_virtual_intf = orig_change_virtual_intf;
}
pr_info("rc_wifi_mon: found slsi_mlme_set, SCSC FW patching "
"available (MIB write deferred to mode switch)\n");
/* Actual MIB write happens when interface is switched to monitor —
* we hook into the mode change path above */
/* Restore original interface modes */
if (target_wiphy)
target_wiphy->interface_modes = orig_interface_modes;
/* If interface is still in monitor mode, reset it */
if (target_netdev && target_netdev->ieee80211_ptr &&
target_netdev->ieee80211_ptr->iftype == NL80211_IFTYPE_MONITOR) {
target_netdev->ieee80211_ptr->iftype = NL80211_IFTYPE_STATION;
target_netdev->type = ARPHRD_ETHER;
if (detected_driver == DRIVER_BCMDHD)
bcmdhd_disable_monitor();
}
}
/*
* For bcmdhd: set the DHD driver's monitor mode flag and issue
* the firmware iovar to enable monitor.
*/
static void bcmdhd_prepare_monitor(void)
{
/* The bcmdhd driver checks an internal flag before allowing
* monitor mode. We locate dhd_monitor_init or the cfg80211
* vendor command handler.
*
* Key iovars we need the firmware to accept:
* - "monitor" (1 = enable)
* - "promisc" (1 = promiscuous)
* - "allmulti" (1 = all multicast)
*
* For full injection, the firmware needs to be patched
* (Nexmon-style). Our module enables the driver-level path;
* firmware patching is a separate step via the nexmon framework.
*/
pr_info("rc_wifi_mon: bcmdhd driver detected — driver-level "
"monitor mode enabled\n");
pr_info("rc_wifi_mon: for packet injection, Nexmon firmware "
"patch is also required\n");
}
/* ---- Module init/exit ---- */
static int __init rc_wifi_mon_init(void)
{
int ret;
pr_info("rc_wifi_mon: RadioControl WiFi monitor mode enabler\n");
pr_info("rc_wifi_mon: RadioControl WiFi monitor mode enabler v1.0\n");
detected_driver = detect_driver();
if (detected_driver == DRIVER_UNKNOWN) {
@@ -289,9 +488,10 @@ static int __init rc_wifi_mon_init(void)
return -ENODEV;
}
pr_info("rc_wifi_mon: detected driver type: %d, wiphy: %s\n",
detected_driver,
target_wiphy ? wiphy_name(target_wiphy) : "null");
pr_info("rc_wifi_mon: detected driver: %s, wiphy: %s, netdev: %s\n",
driver_names[detected_driver],
target_wiphy ? wiphy_name(target_wiphy) : "null",
target_netdev ? target_netdev->name : "null");
ret = patch_wiphy();
if (ret) {
@@ -305,32 +505,40 @@ static int __init rc_wifi_mon_init(void)
scsc_enable_fw_monitor();
break;
case DRIVER_BCMDHD:
bcmdhd_prepare_monitor();
pr_info("rc_wifi_mon: BCM4390 driver patched — monitor mode "
"enabled at driver level\n");
pr_info("rc_wifi_mon: for packet injection, Nexmon firmware "
"patch is also required\n");
break;
default:
break;
}
/* Create sysfs entries under /sys/kernel/rc_wifi_mon/ */
rc_kobj = kobject_create_and_add("rc_wifi_mon", kernel_kobj);
if (rc_kobj) {
ret = sysfs_create_group(rc_kobj, &rc_attr_group);
if (ret) {
kobject_put(rc_kobj);
rc_kobj = NULL;
pr_warn("rc_wifi_mon: sysfs creation failed: %d\n", ret);
/* Non-fatal */
}
}
pr_info("rc_wifi_mon: loaded — monitor mode available via "
"'iw dev wlanX set type monitor'\n");
"'iw dev %s set type monitor'\n",
target_netdev ? target_netdev->name : "wlanX");
return 0;
}
static void __exit rc_wifi_mon_exit(void)
{
/* Restore original handler */
if (target_ops && orig_change_virtual_intf) {
if (make_ops_writable((void *)target_ops, 1) == 0) {
((struct cfg80211_ops *)target_ops)->change_virtual_intf =
orig_change_virtual_intf;
make_ops_writable((void *)target_ops, 0);
}
}
unpatch_wiphy();
/* Remove monitor from interface_modes */
if (target_wiphy) {
target_wiphy->interface_modes &= ~BIT(NL80211_IFTYPE_MONITOR);
target_wiphy->interface_modes &= ~BIT(NL80211_IFTYPE_OCB);
if (rc_kobj) {
sysfs_remove_group(rc_kobj, &rc_attr_group);
kobject_put(rc_kobj);
}
pr_info("rc_wifi_mon: unloaded — monitor mode disabled\n");