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:
726
system/bin/radiocontrol
Executable file
726
system/bin/radiocontrol
Executable file
@@ -0,0 +1,726 @@
|
||||
#!/system/bin/sh
|
||||
# RadioControl WebUI — HTTP server with hardware interface APIs
|
||||
|
||||
WEBROOT="/data/adb/modules/radiocontrol/webroot"
|
||||
CONFIG_DIR="/data/adb/radiocontrol"
|
||||
CONFIG_FILE="$CONFIG_DIR/config.sh"
|
||||
PORT=8088
|
||||
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [webui] $1"; }
|
||||
|
||||
###########################
|
||||
# Hardware interface helpers
|
||||
###########################
|
||||
|
||||
# Send AT command to modem — tries rc_shannon first, then raw devices
|
||||
send_at_cmd() {
|
||||
local cmd="$1"
|
||||
local timeout="${2:-3}"
|
||||
local result=""
|
||||
|
||||
# Try our kernel module device first
|
||||
if [ -c /dev/rc_shannon ]; then
|
||||
echo -e "${cmd}\r" > /dev/rc_shannon
|
||||
result=$(timeout "$timeout" cat /dev/rc_shannon 2>/dev/null)
|
||||
[ -n "$result" ] && echo "$result" && return 0
|
||||
fi
|
||||
|
||||
# Try available AT devices — umts_router is confirmed working on Tensor/Shannon 5400
|
||||
for dev in /dev/umts_router /dev/umts_atc0 /dev/nr_atc0 /dev/umts_atc1 /dev/smd7 /dev/ttyHS0; do
|
||||
if [ -c "$dev" ]; then
|
||||
result=$(
|
||||
exec 3<>"$dev" 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
printf "${cmd}\r\n" >&3
|
||||
sleep "$timeout"
|
||||
local buf=""
|
||||
while read -t 1 line <&3 2>/dev/null; do
|
||||
buf="${buf}${line}\n"
|
||||
done
|
||||
echo -e "$buf"
|
||||
exec 3>&-
|
||||
fi
|
||||
)
|
||||
[ -n "$result" ] && echo "$result" && return 0
|
||||
fi
|
||||
done
|
||||
|
||||
echo "ERROR: No modem AT interface available"
|
||||
return 1
|
||||
}
|
||||
|
||||
# Read a debugfs or sysfs path safely
|
||||
read_sys_path() {
|
||||
local path="$1"
|
||||
local max_bytes="${2:-4096}"
|
||||
|
||||
# Validate path — only allow /sys and /proc
|
||||
case "$path" in
|
||||
/sys/kernel/debug/*|/sys/class/*|/sys/devices/*|/sys/module/*|/sys/bus/*|/proc/*)
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: path not allowed"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -f "$path" ]; then
|
||||
head -c "$max_bytes" "$path" 2>/dev/null
|
||||
elif [ -d "$path" ]; then
|
||||
ls -la "$path" 2>/dev/null
|
||||
else
|
||||
echo "ERROR: not found"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Write to a sysfs path
|
||||
write_sys_path() {
|
||||
local path="$1"
|
||||
local value="$2"
|
||||
|
||||
case "$path" in
|
||||
/sys/class/*|/sys/devices/*|/sys/module/*)
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: write path not allowed"
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -w "$path" ]; then
|
||||
echo "$value" > "$path" 2>/dev/null && echo "OK" || echo "ERROR: write failed"
|
||||
else
|
||||
echo "ERROR: not writable"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# List directory contents as JSON
|
||||
list_dir_json() {
|
||||
local dir="$1"
|
||||
local result="["
|
||||
local first=1
|
||||
|
||||
if [ ! -d "$dir" ]; then
|
||||
echo "[]"
|
||||
return
|
||||
fi
|
||||
|
||||
for entry in "$dir"/*; do
|
||||
[ ! -e "$entry" ] && continue
|
||||
local name=$(basename "$entry")
|
||||
local type="file"
|
||||
[ -d "$entry" ] && type="dir"
|
||||
local readable="false"
|
||||
[ -r "$entry" ] && readable="true"
|
||||
local writable="false"
|
||||
[ -w "$entry" ] && writable="true"
|
||||
|
||||
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
|
||||
result="$result{\"name\":\"$name\",\"type\":\"$type\",\"readable\":$readable,\"writable\":$writable}"
|
||||
done
|
||||
|
||||
echo "${result}]"
|
||||
}
|
||||
|
||||
# Get WiFi interface details
|
||||
get_wifi_details() {
|
||||
cat "$CONFIG_DIR/wifi_info" 2>/dev/null
|
||||
}
|
||||
|
||||
# Get loaded kernel modules status
|
||||
get_kmod_status() {
|
||||
local result="{"
|
||||
local mon="false" shan="false" diag="false"
|
||||
|
||||
lsmod 2>/dev/null | grep -q rc_wifi_mon && mon="true"
|
||||
lsmod 2>/dev/null | grep -q rc_shannon_cmd && shan="true"
|
||||
lsmod 2>/dev/null | grep -q rc_diag_bridge && diag="true"
|
||||
|
||||
echo "{\"wifi_mon\":$mon,\"shannon_cmd\":$shan,\"diag_bridge\":$diag}"
|
||||
}
|
||||
|
||||
# Get thermal zones related to radios
|
||||
get_thermal_info() {
|
||||
local result="["
|
||||
local first=1
|
||||
|
||||
for tz in /sys/class/thermal/thermal_zone*; do
|
||||
local type=$(cat "$tz/type" 2>/dev/null)
|
||||
case "$type" in
|
||||
*modem*|*pa*|*wlan*|*wifi*|*rf*|*mdm*|*qfe*|*battery*)
|
||||
local temp=$(cat "$tz/temp" 2>/dev/null)
|
||||
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
|
||||
result="$result{\"zone\":\"$(basename $tz)\",\"type\":\"$type\",\"temp\":$temp}"
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "${result}]"
|
||||
}
|
||||
|
||||
# Get current config
|
||||
get_current_config() {
|
||||
source "$CONFIG_FILE" 2>/dev/null
|
||||
local soc=$(cat "$CONFIG_DIR/detected_soc" 2>/dev/null)
|
||||
cat << CONF
|
||||
{
|
||||
"engineering_mode": "${ENGINEERING_MODE:-0}",
|
||||
"factory_test_mode": "${FACTORY_TEST_MODE:-0}",
|
||||
"usb_diag_mode": "${USB_DIAG_MODE:-0}",
|
||||
"hidden_menus": "${HIDDEN_MENUS:-0}",
|
||||
"modem_log": "${MODEM_LOG:-0}",
|
||||
"wifi_mode": "${WIFI_MODE:-managed}",
|
||||
"load_modules": "${LOAD_MODULES:-}",
|
||||
"detected_soc": "$soc"
|
||||
}
|
||||
CONF
|
||||
}
|
||||
|
||||
# Get available modem interfaces
|
||||
get_modem_interfaces() {
|
||||
local result="["
|
||||
local first=1
|
||||
|
||||
for dev in /dev/umts_atc0 /dev/umts_atc1 /dev/umts_router0 /dev/umts_ipc0 \
|
||||
/dev/umts_dm0 /dev/umts_boot0 /dev/umts_ramdump0 /dev/umts_rfs0 \
|
||||
/dev/nr_atc0 /dev/nr_ipc0 \
|
||||
/dev/diag /dev/smd7 /dev/ttyHS0 /dev/ttyMSM0 /dev/at_mdm0 \
|
||||
/dev/rc_shannon /dev/rc_diag; do
|
||||
if [ -c "$dev" ]; then
|
||||
local perms=$(ls -la "$dev" 2>/dev/null | awk '{print $1}')
|
||||
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
|
||||
result="$result{\"path\":\"$dev\",\"perms\":\"$perms\"}"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "${result}]"
|
||||
}
|
||||
|
||||
# Get radio info from props and modem
|
||||
get_radio_info() {
|
||||
local baseband=$(getprop gsm.version.baseband 2>/dev/null)
|
||||
local ril=$(getprop gsm.version.ril-impl 2>/dev/null)
|
||||
local nettype=$(getprop gsm.network.type 2>/dev/null)
|
||||
local simstate=$(getprop gsm.sim.state 2>/dev/null)
|
||||
local operator=$(getprop gsm.operator.alpha 2>/dev/null)
|
||||
local chipset=$(getprop ro.board.platform 2>/dev/null)
|
||||
local hardware=$(getprop ro.hardware 2>/dev/null)
|
||||
local soc=$(cat "$CONFIG_DIR/detected_soc" 2>/dev/null)
|
||||
|
||||
cat << INFO
|
||||
{
|
||||
"baseband":"$baseband",
|
||||
"ril":"$ril",
|
||||
"network_type":"$nettype",
|
||||
"sim_state":"$simstate",
|
||||
"operator":"$operator",
|
||||
"chipset":"$chipset",
|
||||
"hardware":"$hardware",
|
||||
"soc_family":"$soc"
|
||||
}
|
||||
INFO
|
||||
}
|
||||
|
||||
# Get system flags
|
||||
get_system_flags() {
|
||||
local result="["
|
||||
local first=1
|
||||
for prop in \
|
||||
ro.build.type ro.debuggable ro.secure ro.adb.secure \
|
||||
persist.sys.usb.config persist.sys.factorytest ro.factorytest \
|
||||
persist.radio.fieldtest ro.telephony.hidden_menu \
|
||||
persist.radio.hidden_menu persist.mtk.engineer \
|
||||
persist.vendor.radio.adb_log_on persist.radio.ramdump \
|
||||
persist.vendor.radio.enableadvanced persist.radio.field_test \
|
||||
persist.vendor.radio.ca_info persist.vendor.radio.nr5g \
|
||||
persist.vendor.radio.modem_log persist.cp.log persist.cp.rat \
|
||||
ro.board.platform ro.hardware ro.hardware.chipname \
|
||||
gsm.version.baseband gsm.version.ril-impl; do
|
||||
val=$(getprop "$prop" 2>/dev/null)
|
||||
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
|
||||
result="$result{\"prop\":\"$prop\",\"value\":\"$val\"}"
|
||||
done
|
||||
echo "${result}]"
|
||||
}
|
||||
|
||||
# Update config key
|
||||
update_config() {
|
||||
local key="$1" val="$2"
|
||||
|
||||
case "$key" in
|
||||
ENGINEERING_MODE|FACTORY_TEST_MODE|USB_DIAG_MODE|HIDDEN_MENUS|MODEM_LOG)
|
||||
case "$val" in 0|1) ;; *) echo '{"ok":false,"error":"invalid value"}'; return ;; esac ;;
|
||||
WIFI_MODE)
|
||||
case "$val" in managed|monitor|injection|mesh|ap) ;; *) echo '{"ok":false,"error":"invalid mode"}'; return ;; esac ;;
|
||||
LOAD_MODULES)
|
||||
;; # freeform
|
||||
*) echo '{"ok":false,"error":"invalid key"}'; return ;;
|
||||
esac
|
||||
|
||||
if grep -q "^${key}=" "$CONFIG_FILE" 2>/dev/null; then
|
||||
sed -i "s|^${key}=.*|${key}=${val}|" "$CONFIG_FILE"
|
||||
else
|
||||
echo "${key}=${val}" >> "$CONFIG_FILE"
|
||||
fi
|
||||
echo "{\"ok\":true}"
|
||||
}
|
||||
|
||||
# Set WiFi mode immediately
|
||||
set_wifi_mode() {
|
||||
local mode="$1"
|
||||
local iface=""
|
||||
|
||||
for candidate in wlan0 wlan1 wifi0; do
|
||||
[ -d "/sys/class/net/$candidate" ] && iface="$candidate" && break
|
||||
done
|
||||
[ -z "$iface" ] && echo '{"ok":false,"error":"no wifi interface"}' && return
|
||||
|
||||
ip link set "$iface" down 2>/dev/null
|
||||
case "$mode" in
|
||||
monitor) iw dev "$iface" set type monitor 2>/dev/null ;;
|
||||
injection) iw dev "$iface" set type monitor 2>/dev/null
|
||||
iw dev "$iface" set monitor fcsfail otherbss 2>/dev/null ;;
|
||||
mesh) iw dev "$iface" set type mesh 2>/dev/null ;;
|
||||
ap) iw dev "$iface" set type __ap 2>/dev/null ;;
|
||||
managed|*) iw dev "$iface" set type managed 2>/dev/null; mode="managed" ;;
|
||||
esac
|
||||
ip link set "$iface" up 2>/dev/null
|
||||
|
||||
sed -i "s/^WIFI_MODE=.*/WIFI_MODE=$mode/" "$CONFIG_FILE" 2>/dev/null
|
||||
echo "{\"ok\":true,\"mode\":\"$mode\",\"iface\":\"$iface\"}"
|
||||
}
|
||||
|
||||
# Get BCM4390 driver parameters
|
||||
get_wifi_params() {
|
||||
local result="["
|
||||
local first=1
|
||||
local pdir="/sys/module/bcmdhd4390/parameters"
|
||||
|
||||
[ ! -d "$pdir" ] && echo "[]" && return
|
||||
|
||||
for p in "$pdir"/*; do
|
||||
local name=$(basename "$p")
|
||||
local val=$(cat "$p" 2>/dev/null)
|
||||
local writable="false"
|
||||
[ -w "$p" ] && writable="true"
|
||||
# Escape val for JSON
|
||||
val=$(echo "$val" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
|
||||
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
|
||||
result="$result{\"name\":\"$name\",\"value\":\"$val\",\"writable\":$writable}"
|
||||
done
|
||||
echo "${result}]"
|
||||
}
|
||||
|
||||
# Set BCM4390 driver parameter
|
||||
set_wifi_param() {
|
||||
local name="$1" val="$2"
|
||||
local path="/sys/module/bcmdhd4390/parameters/$name"
|
||||
|
||||
# Validate name (no path traversal)
|
||||
case "$name" in */*|*..*) echo '{"ok":false,"error":"invalid param name"}'; return ;; esac
|
||||
|
||||
if [ -w "$path" ]; then
|
||||
echo "$val" > "$path" 2>/dev/null
|
||||
local newval=$(cat "$path" 2>/dev/null)
|
||||
echo "{\"ok\":true,\"value\":\"$newval\"}"
|
||||
else
|
||||
echo "{\"ok\":false,\"error\":\"param not writable\"}"
|
||||
fi
|
||||
}
|
||||
|
||||
# Get WiFi firmware info
|
||||
get_wifi_firmware() {
|
||||
local result="["
|
||||
local first=1
|
||||
|
||||
for fw in /vendor/firmware/fw_bcmdhd* /vendor/firmware/bcmdhd*; do
|
||||
[ ! -f "$fw" ] && continue
|
||||
local name=$(basename "$fw")
|
||||
local size=$(stat -c%s "$fw" 2>/dev/null || ls -la "$fw" | awk '{print $5}')
|
||||
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
|
||||
result="$result{\"path\":\"$fw\",\"name\":\"$name\",\"size\":$size}"
|
||||
done
|
||||
|
||||
# Current loaded firmware
|
||||
local cur_fw=$(cat /sys/module/bcmdhd4390/parameters/firmware_path 2>/dev/null)
|
||||
local cur_nv=$(cat /sys/module/bcmdhd4390/parameters/nvram_path 2>/dev/null)
|
||||
local info=$(cat /sys/module/bcmdhd4390/parameters/info_string 2>/dev/null | sed 's/"/\\"/g' | tr '\n' ' ')
|
||||
|
||||
echo "${result}],\"current_fw\":\"$cur_fw\",\"current_nvram\":\"$cur_nv\",\"info\":\"$info\"}"
|
||||
}
|
||||
|
||||
# Get CP (modem processor) debug info
|
||||
get_cp_debug() {
|
||||
local state=$(cat /sys/devices/platform/cpif/modem_state 2>/dev/null)
|
||||
local pcie=""
|
||||
local sbb=""
|
||||
|
||||
[ -f /sys/devices/platform/cpif/modem/pcie_event_stats ] && \
|
||||
pcie=$(cat /sys/devices/platform/cpif/modem/pcie_event_stats 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')
|
||||
[ -f /sys/devices/platform/cpif/modem/sbb_debug ] && \
|
||||
sbb=$(cat /sys/devices/platform/cpif/modem/sbb_debug 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')
|
||||
[ -f /sys/devices/platform/cpif/modem/power_stats ] && \
|
||||
power=$(cat /sys/devices/platform/cpif/modem/power_stats 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')
|
||||
|
||||
echo "{\"state\":\"$state\",\"pcie_stats\":\"$pcie\",\"sbb_debug\":\"$sbb\",\"power_stats\":\"$power\"}"
|
||||
}
|
||||
|
||||
# Trigger CP crash dump
|
||||
trigger_cp_crash() {
|
||||
if [ -f /sys/devices/platform/cpif/do_cp_crash ]; then
|
||||
echo 1 > /sys/devices/platform/cpif/do_cp_crash 2>/dev/null
|
||||
echo '{"ok":true,"msg":"CP crash triggered — ramdump will be saved to /data/vendor/log/cbd/"}'
|
||||
else
|
||||
echo '{"ok":false,"error":"do_cp_crash not available"}'
|
||||
fi
|
||||
}
|
||||
|
||||
# Carrier config — read current settings
|
||||
get_carrier_config() {
|
||||
local volte=$(settings get global enhanced_4g_mode_enabled 2>/dev/null)
|
||||
local wfc=$(settings get global wifi_calling_enabled 2>/dev/null)
|
||||
local netsel=$(getprop persist.dbg.hide_preferred_network_type 2>/dev/null)
|
||||
|
||||
echo "{\"volte\":\"$volte\",\"wfc\":\"$wfc\",\"hide_network_type\":\"$netsel\"}"
|
||||
}
|
||||
|
||||
# Carrier config — set a carrier override
|
||||
set_carrier_flag() {
|
||||
local flag="$1" val="$2"
|
||||
|
||||
case "$flag" in
|
||||
volte)
|
||||
settings put global enhanced_4g_mode_enabled "$val" 2>/dev/null
|
||||
echo '{"ok":true}'
|
||||
;;
|
||||
vonr)
|
||||
settings put global vonr_enabled "$val" 2>/dev/null
|
||||
resetprop persist.vendor.radio.vonr_enabled "$val" 2>/dev/null
|
||||
echo '{"ok":true}'
|
||||
;;
|
||||
wfc)
|
||||
settings put global wifi_calling_enabled "$val" 2>/dev/null
|
||||
echo '{"ok":true}'
|
||||
;;
|
||||
vt)
|
||||
settings put global vt_ims_enabled "$val" 2>/dev/null
|
||||
echo '{"ok":true}'
|
||||
;;
|
||||
apn)
|
||||
resetprop persist.dbg.allow_adding_apns 1 2>/dev/null
|
||||
settings put global allow_adding_apns "$val" 2>/dev/null
|
||||
echo '{"ok":true}'
|
||||
;;
|
||||
nradv)
|
||||
resetprop persist.vendor.radio.nr_advanced "$val" 2>/dev/null
|
||||
echo '{"ok":true}'
|
||||
;;
|
||||
nettype)
|
||||
# val=1 means SHOW (so hide=0)
|
||||
resetprop persist.dbg.hide_preferred_network_type "$( [ "$val" = "1" ] && echo 0 || echo 1)" 2>/dev/null
|
||||
echo '{"ok":true}'
|
||||
;;
|
||||
*) echo '{"ok":false,"error":"unknown flag"}' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# List carrier settings protobuf files
|
||||
get_carrier_files() {
|
||||
local result="["
|
||||
local first=1
|
||||
local csdir="/product/etc/CarrierSettings"
|
||||
|
||||
if [ -d "$csdir" ]; then
|
||||
for f in "$csdir"/*; do
|
||||
[ ! -f "$f" ] && continue
|
||||
local name=$(basename "$f")
|
||||
local size=$(stat -c%s "$f" 2>/dev/null || ls -la "$f" | awk '{print $5}')
|
||||
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
|
||||
result="$result{\"name\":\"$name\",\"size\":$size}"
|
||||
done
|
||||
fi
|
||||
echo "${result}]"
|
||||
}
|
||||
|
||||
# Dump carrier_config service
|
||||
dump_carrier_config() {
|
||||
dumpsys carrier_config 2>/dev/null | head -500
|
||||
}
|
||||
|
||||
# Thread radio (Wonder) info
|
||||
get_thread_info() {
|
||||
local result="{"
|
||||
local has_wonder="false"
|
||||
|
||||
if [ -d /sys/class/ieee80211/wonder ]; then
|
||||
has_wonder="true"
|
||||
local mac=$(cat /sys/class/ieee80211/wonder/macaddress 2>/dev/null)
|
||||
local name=$(cat /sys/class/ieee80211/wonder/name 2>/dev/null)
|
||||
local idx=$(cat /sys/class/ieee80211/wonder/index 2>/dev/null)
|
||||
result="$result\"present\":true,\"mac\":\"$mac\",\"name\":\"$name\",\"index\":\"$idx\""
|
||||
|
||||
# debugfs
|
||||
local dbg_entries=""
|
||||
if [ -d /sys/kernel/debug/wonder ]; then
|
||||
dbg_entries=$(ls /sys/kernel/debug/wonder/ 2>/dev/null | tr '\n' ',' | sed 's/,$//')
|
||||
fi
|
||||
result="$result,\"debugfs_entries\":\"$dbg_entries\""
|
||||
|
||||
# force_stop_tx status
|
||||
if [ -f /sys/kernel/debug/wonder/force_stop_tx ]; then
|
||||
local fstx=$(cat /sys/kernel/debug/wonder/force_stop_tx 2>/dev/null)
|
||||
result="$result,\"force_stop_tx\":\"$fstx\""
|
||||
fi
|
||||
|
||||
# thread-wpan interface
|
||||
if [ -d /sys/class/net/thread-wpan ]; then
|
||||
result="$result,\"wpan_iface\":true"
|
||||
else
|
||||
result="$result,\"wpan_iface\":false"
|
||||
fi
|
||||
else
|
||||
result="$result\"present\":false"
|
||||
fi
|
||||
|
||||
echo "${result}}"
|
||||
}
|
||||
|
||||
# Load/unload kernel module
|
||||
manage_kmod() {
|
||||
local action="$1" mod="$2"
|
||||
local kmod_dir="/data/adb/modules/radiocontrol/common/kmod"
|
||||
|
||||
case "$mod" in
|
||||
rc_wifi_mon|rc_shannon_cmd|rc_diag_bridge) ;;
|
||||
*) echo '{"ok":false,"error":"unknown module"}'; return ;;
|
||||
esac
|
||||
|
||||
if [ "$action" = "load" ]; then
|
||||
if lsmod 2>/dev/null | grep -q "$mod"; then
|
||||
echo '{"ok":true,"status":"already loaded"}'
|
||||
elif [ -f "$kmod_dir/${mod}.ko" ]; then
|
||||
insmod "$kmod_dir/${mod}.ko" 2>/dev/null
|
||||
if [ $? -eq 0 ]; then
|
||||
echo '{"ok":true,"status":"loaded"}'
|
||||
else
|
||||
echo '{"ok":false,"error":"insmod failed — kernel version mismatch?"}'
|
||||
fi
|
||||
else
|
||||
echo '{"ok":false,"error":"module .ko not found"}'
|
||||
fi
|
||||
elif [ "$action" = "unload" ]; then
|
||||
rmmod "$mod" 2>/dev/null
|
||||
echo '{"ok":true,"status":"unloaded"}'
|
||||
fi
|
||||
}
|
||||
|
||||
###########################
|
||||
# HTTP server
|
||||
###########################
|
||||
|
||||
get_mime() {
|
||||
case "$1" in
|
||||
*.html) echo "text/html" ;; *.css) echo "text/css" ;;
|
||||
*.js) echo "text/javascript" ;; *.json) echo "application/json" ;;
|
||||
*) echo "text/plain" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
send_response() {
|
||||
local code="$1" ct="$2" body="$3"
|
||||
printf "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET,POST,OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\n\r\n%s" \
|
||||
"$code" "$ct" "${#body}" "$body"
|
||||
}
|
||||
|
||||
send_file() {
|
||||
local f="$1"
|
||||
if [ -f "$f" ]; then
|
||||
send_response "200 OK" "$(get_mime "$f")" "$(cat "$f")"
|
||||
else
|
||||
send_response "404 Not Found" "text/plain" "Not Found"
|
||||
fi
|
||||
}
|
||||
|
||||
# JSON body parser — extract value for key
|
||||
json_val() {
|
||||
echo "$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p"
|
||||
}
|
||||
|
||||
handle_request() {
|
||||
local method path query body content_length
|
||||
|
||||
read -r request_line
|
||||
method=$(echo "$request_line" | awk '{print $1}')
|
||||
local full_path=$(echo "$request_line" | awk '{print $2}')
|
||||
path=$(echo "$full_path" | cut -d'?' -f1)
|
||||
query=$(echo "$full_path" | grep -o '?.*' | cut -c2-)
|
||||
|
||||
while read -r header; do
|
||||
header=$(echo "$header" | tr -d '\r')
|
||||
[ -z "$header" ] && break
|
||||
case "$header" in Content-Length:*) content_length=$(echo "$header" | awk '{print $2}') ;; esac
|
||||
done
|
||||
|
||||
body=""
|
||||
if [ "$method" = "POST" ] && [ -n "$content_length" ] && [ "$content_length" -gt 0 ] 2>/dev/null; then
|
||||
body=$(dd bs=1 count="$content_length" 2>/dev/null)
|
||||
fi
|
||||
|
||||
# Handle OPTIONS for CORS
|
||||
if [ "$method" = "OPTIONS" ]; then
|
||||
send_response "200 OK" "text/plain" ""
|
||||
return
|
||||
fi
|
||||
|
||||
log "$method $path"
|
||||
|
||||
case "$method $path" in
|
||||
# Static files
|
||||
"GET /") send_file "$WEBROOT/index.html" ;;
|
||||
"GET /css/"*|"GET /js/"*) send_file "$WEBROOT$path" ;;
|
||||
|
||||
# Status & info APIs
|
||||
"GET /api/status") send_response "200 OK" "application/json" "$(get_current_config)" ;;
|
||||
"GET /api/radio") send_response "200 OK" "application/json" "$(get_radio_info)" ;;
|
||||
"GET /api/flags") send_response "200 OK" "application/json" "$(get_system_flags)" ;;
|
||||
"GET /api/thermal") send_response "200 OK" "application/json" "$(get_thermal_info)" ;;
|
||||
"GET /api/kmod") send_response "200 OK" "application/json" "$(get_kmod_status)" ;;
|
||||
"GET /api/modem/interfaces") send_response "200 OK" "application/json" "$(get_modem_interfaces)" ;;
|
||||
"GET /api/wifi/info") send_response "200 OK" "text/plain" "$(get_wifi_details)" ;;
|
||||
"GET /api/wifi/params") send_response "200 OK" "application/json" "$(get_wifi_params)" ;;
|
||||
"GET /api/wifi/firmware")
|
||||
local fwdata=$(get_wifi_firmware)
|
||||
send_response "200 OK" "application/json" "{\"files\":$fwdata}"
|
||||
;;
|
||||
"GET /api/cp") send_response "200 OK" "application/json" "$(get_cp_debug)" ;;
|
||||
"GET /api/carrier/config") send_response "200 OK" "application/json" "$(get_carrier_config)" ;;
|
||||
"GET /api/carrier/files") send_response "200 OK" "application/json" "$(get_carrier_files)" ;;
|
||||
"GET /api/carrier/dump")
|
||||
local dump=$(dump_carrier_config | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' '|' | sed 's/|/\\n/g')
|
||||
send_response "200 OK" "application/json" "{\"dump\":\"$dump\"}"
|
||||
;;
|
||||
"GET /api/thread") send_response "200 OK" "application/json" "$(get_thread_info)" ;;
|
||||
|
||||
# AT command terminal
|
||||
"POST /api/at")
|
||||
local cmd=$(json_val "$body" "cmd")
|
||||
local timeout=$(json_val "$body" "timeout")
|
||||
[ -z "$timeout" ] && timeout=3
|
||||
local resp=$(send_at_cmd "$cmd" "$timeout")
|
||||
# Escape for JSON
|
||||
resp=$(echo "$resp" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' '|' | sed 's/|/\\n/g')
|
||||
send_response "200 OK" "application/json" "{\"ok\":true,\"response\":\"$resp\"}"
|
||||
;;
|
||||
|
||||
# debugfs/sysfs browser
|
||||
"POST /api/fs/read")
|
||||
local fspath=$(json_val "$body" "path")
|
||||
if [ -d "$fspath" ]; then
|
||||
local listing=$(list_dir_json "$fspath")
|
||||
send_response "200 OK" "application/json" "{\"type\":\"dir\",\"entries\":$listing}"
|
||||
else
|
||||
local content=$(read_sys_path "$fspath" 8192)
|
||||
content=$(echo "$content" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' '|' | sed 's/|/\\n/g')
|
||||
send_response "200 OK" "application/json" "{\"type\":\"file\",\"content\":\"$content\"}"
|
||||
fi
|
||||
;;
|
||||
|
||||
"POST /api/fs/write")
|
||||
local fspath=$(json_val "$body" "path")
|
||||
local fsval=$(json_val "$body" "value")
|
||||
local result=$(write_sys_path "$fspath" "$fsval")
|
||||
send_response "200 OK" "application/json" "{\"ok\":true,\"result\":\"$result\"}"
|
||||
;;
|
||||
|
||||
# debugfs paths list
|
||||
"GET /api/debugfs")
|
||||
local paths=$(cat "$CONFIG_DIR/debugfs_paths" 2>/dev/null)
|
||||
local result="["
|
||||
local first=1
|
||||
for p in $paths; do
|
||||
[ -z "$p" ] && continue
|
||||
if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
|
||||
result="$result\"$p\""
|
||||
done
|
||||
send_response "200 OK" "application/json" "${result}]"
|
||||
;;
|
||||
|
||||
# Config update
|
||||
"POST /api/config")
|
||||
local key=$(json_val "$body" "key")
|
||||
local val=$(json_val "$body" "value")
|
||||
send_response "200 OK" "application/json" "$(update_config "$key" "$val")"
|
||||
;;
|
||||
|
||||
# WiFi mode switch
|
||||
"POST /api/wifi/mode")
|
||||
local mode=$(json_val "$body" "mode")
|
||||
send_response "200 OK" "application/json" "$(set_wifi_mode "$mode")"
|
||||
;;
|
||||
|
||||
# Kernel module management
|
||||
"POST /api/kmod/load")
|
||||
local mod=$(json_val "$body" "module")
|
||||
send_response "200 OK" "application/json" "$(manage_kmod load "$mod")"
|
||||
;;
|
||||
"POST /api/kmod/unload")
|
||||
local mod=$(json_val "$body" "module")
|
||||
send_response "200 OK" "application/json" "$(manage_kmod unload "$mod")"
|
||||
;;
|
||||
|
||||
# Carrier config flag
|
||||
"POST /api/carrier/set")
|
||||
local flag=$(json_val "$body" "flag")
|
||||
local val=$(json_val "$body" "value")
|
||||
send_response "200 OK" "application/json" "$(set_carrier_flag "$flag" "$val")"
|
||||
;;
|
||||
|
||||
# WiFi driver param
|
||||
"POST /api/wifi/param")
|
||||
local pname=$(json_val "$body" "name")
|
||||
local pval=$(json_val "$body" "value")
|
||||
send_response "200 OK" "application/json" "$(set_wifi_param "$pname" "$pval")"
|
||||
;;
|
||||
|
||||
# CP crash dump
|
||||
"POST /api/cp/crash")
|
||||
send_response "200 OK" "application/json" "$(trigger_cp_crash)"
|
||||
;;
|
||||
|
||||
# Set prop at runtime
|
||||
"POST /api/prop")
|
||||
local prop=$(json_val "$body" "prop")
|
||||
local val=$(json_val "$body" "value")
|
||||
case "$prop" in
|
||||
persist.*|ro.debuggable|ro.build.type|ro.secure|ro.adb.secure|service.adb.root)
|
||||
resetprop "$prop" "$val" 2>/dev/null
|
||||
send_response "200 OK" "application/json" "{\"ok\":true}"
|
||||
;;
|
||||
*) send_response "200 OK" "application/json" "{\"ok\":false,\"error\":\"not in allowlist\"}" ;;
|
||||
esac
|
||||
;;
|
||||
|
||||
# Reboot
|
||||
"POST /api/reboot")
|
||||
send_response "200 OK" "application/json" '{"ok":true}'
|
||||
sleep 1; reboot ;;
|
||||
|
||||
*) send_response "404 Not Found" "application/json" '{"error":"not found"}' ;;
|
||||
esac
|
||||
}
|
||||
|
||||
###########################
|
||||
# Main
|
||||
###########################
|
||||
|
||||
log "Starting HTTP server on port $PORT"
|
||||
|
||||
while true; do
|
||||
handle_request | busybox nc -ll -p "$PORT" -s 127.0.0.1 2>/dev/null \
|
||||
|| handle_request | toybox nc -L -p "$PORT" -s 127.0.0.1 2>/dev/null \
|
||||
|| { log "ERROR: No network listener available"; exit 1; }
|
||||
done
|
||||
Reference in New Issue
Block a user