RadioControl v1.0.0 — KernelSU-Next module for radio engineering
Shannon 5400 AT command terminal, BCM4390 WiFi mode switching, carrier config override, debugfs browser, RF thermal monitoring, CP debug, Thread/Wonder radio, satellite/NTN test support. Verified on Pixel 10 Pro Fold (Tensor G5 / laguna).
This commit is contained in:
340
common/kmod/rc_wifi_mon.c
Normal file
340
common/kmod/rc_wifi_mon.c
Normal file
@@ -0,0 +1,340 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* rc_wifi_mon.ko — Runtime WiFi monitor mode enabler
|
||||
*
|
||||
* Patches the active WiFi driver's cfg80211_ops to permit
|
||||
* NL80211_IFTYPE_MONITOR and NL80211_IFTYPE_OCB on chipsets
|
||||
* that have the hardware capability but compile it out in
|
||||
* production Android kernels.
|
||||
*
|
||||
* Supported drivers:
|
||||
* - Samsung SCSC/SLSI (scsc_wlan)
|
||||
* - Broadcom bcmdhd / DHD
|
||||
* - Qualcomm cnss2 / ath11k / ath12k (usually already has
|
||||
* monitor, but vendor builds may disable it)
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* This is a live kernel patch — no reboot required after insmod.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <linux/version.h>
|
||||
#include <linux/kallsyms.h>
|
||||
#include <linux/set_memory.h>
|
||||
#include <net/cfg80211.h>
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("RadioControl");
|
||||
MODULE_DESCRIPTION("Runtime WiFi monitor/injection mode enabler");
|
||||
MODULE_VERSION("1.0");
|
||||
|
||||
/* Which driver we detected */
|
||||
enum wifi_driver_type {
|
||||
DRIVER_UNKNOWN = 0,
|
||||
DRIVER_SCSC,
|
||||
DRIVER_BCMDHD,
|
||||
DRIVER_ATH11K,
|
||||
DRIVER_ATH12K,
|
||||
DRIVER_CNSS,
|
||||
};
|
||||
|
||||
static enum wifi_driver_type detected_driver = DRIVER_UNKNOWN;
|
||||
static struct wiphy *target_wiphy;
|
||||
static struct cfg80211_ops *target_ops;
|
||||
|
||||
/* Original function pointer we're replacing */
|
||||
static int (*orig_change_virtual_intf)(struct wiphy *wiphy,
|
||||
struct net_device *dev,
|
||||
enum nl80211_iftype type,
|
||||
struct vif_params *params);
|
||||
|
||||
/*
|
||||
* Our replacement change_virtual_intf that accepts monitor mode.
|
||||
* Falls through to the original handler for non-monitor types.
|
||||
*/
|
||||
static int rc_change_virtual_intf(struct wiphy *wiphy,
|
||||
struct net_device *dev,
|
||||
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
|
||||
*/
|
||||
if (netif_running(dev))
|
||||
dev_close(dev);
|
||||
|
||||
wdev->iftype = type;
|
||||
|
||||
/* Set flags for monitor mode */
|
||||
if (type == NL80211_IFTYPE_MONITOR) {
|
||||
if (params && params->flags)
|
||||
wdev->u.mntr.flags = params->flags;
|
||||
dev->type = ARPHRD_IEEE80211_RADIOTAP;
|
||||
} else {
|
||||
dev->type = ARPHRD_ETHER;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* All other types: pass through to original handler */
|
||||
if (orig_change_virtual_intf)
|
||||
return orig_change_virtual_intf(wiphy, dev, type, params);
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/*
|
||||
* Detect which WiFi driver is active by checking module names
|
||||
* and wiphy registration.
|
||||
*/
|
||||
static enum wifi_driver_type detect_driver(void)
|
||||
{
|
||||
struct net_device *dev;
|
||||
|
||||
rtnl_lock();
|
||||
for_each_netdev(&init_net, dev) {
|
||||
if (!dev->ieee80211_ptr)
|
||||
continue;
|
||||
|
||||
/* Check driver/module name */
|
||||
if (dev->dev.driver) {
|
||||
const char *drvname = dev->dev.driver->name;
|
||||
|
||||
if (strstr(drvname, "scsc") || strstr(drvname, "slsi")) {
|
||||
target_wiphy = dev->ieee80211_ptr->wiphy;
|
||||
rtnl_unlock();
|
||||
return DRIVER_SCSC;
|
||||
}
|
||||
if (strstr(drvname, "bcmdhd") || strstr(drvname, "dhd")) {
|
||||
target_wiphy = dev->ieee80211_ptr->wiphy;
|
||||
rtnl_unlock();
|
||||
return DRIVER_BCMDHD;
|
||||
}
|
||||
if (strstr(drvname, "ath11k")) {
|
||||
target_wiphy = dev->ieee80211_ptr->wiphy;
|
||||
rtnl_unlock();
|
||||
return DRIVER_ATH11K;
|
||||
}
|
||||
if (strstr(drvname, "ath12k")) {
|
||||
target_wiphy = dev->ieee80211_ptr->wiphy;
|
||||
rtnl_unlock();
|
||||
return DRIVER_ATH12K;
|
||||
}
|
||||
if (strstr(drvname, "cnss") || strstr(drvname, "qca")) {
|
||||
target_wiphy = dev->ieee80211_ptr->wiphy;
|
||||
rtnl_unlock();
|
||||
return DRIVER_CNSS;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fallback: check wiphy name */
|
||||
if (dev->ieee80211_ptr->wiphy) {
|
||||
const char *wname = wiphy_name(dev->ieee80211_ptr->wiphy);
|
||||
|
||||
if (wname) {
|
||||
if (strstr(wname, "scsc") || strstr(wname, "slsi")) {
|
||||
target_wiphy = dev->ieee80211_ptr->wiphy;
|
||||
rtnl_unlock();
|
||||
return DRIVER_SCSC;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rtnl_unlock();
|
||||
|
||||
return DRIVER_UNKNOWN;
|
||||
}
|
||||
|
||||
/*
|
||||
* Make a kernel text page writable so we can patch the ops table.
|
||||
* We restore permissions after patching.
|
||||
*/
|
||||
static int make_ops_writable(void *addr, int writable)
|
||||
{
|
||||
unsigned long page_addr = (unsigned long)addr & PAGE_MASK;
|
||||
|
||||
if (writable)
|
||||
return set_memory_rw(page_addr, 1);
|
||||
else
|
||||
return set_memory_ro(page_addr, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
* Patch the wiphy to add monitor mode support.
|
||||
*/
|
||||
static int patch_wiphy(void)
|
||||
{
|
||||
if (!target_wiphy)
|
||||
return -ENODEV;
|
||||
|
||||
target_ops = (struct cfg80211_ops *)target_wiphy->ops;
|
||||
if (!target_ops)
|
||||
return -ENODEV;
|
||||
|
||||
/* Save original handler */
|
||||
orig_change_virtual_intf = target_ops->change_virtual_intf;
|
||||
|
||||
/* Add monitor mode 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");
|
||||
} 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;
|
||||
}
|
||||
|
||||
pr_info("rc_wifi_mon: interface_modes now: 0x%x\n",
|
||||
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.
|
||||
*/
|
||||
static void scsc_enable_fw_monitor(void)
|
||||
{
|
||||
typedef int (*slsi_mlme_set_fn)(void *sdev, void *dev,
|
||||
u8 *mib, int mib_len);
|
||||
slsi_mlme_set_fn mlme_set;
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
||||
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 */
|
||||
}
|
||||
|
||||
/*
|
||||
* 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");
|
||||
}
|
||||
|
||||
static int __init rc_wifi_mon_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
pr_info("rc_wifi_mon: RadioControl WiFi monitor mode enabler\n");
|
||||
|
||||
detected_driver = detect_driver();
|
||||
if (detected_driver == DRIVER_UNKNOWN) {
|
||||
pr_err("rc_wifi_mon: no supported WiFi driver found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pr_info("rc_wifi_mon: detected driver type: %d, wiphy: %s\n",
|
||||
detected_driver,
|
||||
target_wiphy ? wiphy_name(target_wiphy) : "null");
|
||||
|
||||
ret = patch_wiphy();
|
||||
if (ret) {
|
||||
pr_err("rc_wifi_mon: failed to patch wiphy: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Driver-specific post-patch setup */
|
||||
switch (detected_driver) {
|
||||
case DRIVER_SCSC:
|
||||
scsc_enable_fw_monitor();
|
||||
break;
|
||||
case DRIVER_BCMDHD:
|
||||
bcmdhd_prepare_monitor();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
pr_info("rc_wifi_mon: loaded — monitor mode available via "
|
||||
"'iw dev wlanX set type monitor'\n");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* Remove monitor from interface_modes */
|
||||
if (target_wiphy) {
|
||||
target_wiphy->interface_modes &= ~BIT(NL80211_IFTYPE_MONITOR);
|
||||
target_wiphy->interface_modes &= ~BIT(NL80211_IFTYPE_OCB);
|
||||
}
|
||||
|
||||
pr_info("rc_wifi_mon: unloaded — monitor mode disabled\n");
|
||||
}
|
||||
|
||||
module_init(rc_wifi_mon_init);
|
||||
module_exit(rc_wifi_mon_exit);
|
||||
Reference in New Issue
Block a user