uqmi: add qmap proto master
authorDavid Bauer <mail@david-bauer.net>
Sun, 16 Feb 2025 13:34:08 +0000 (14:34 +0100)
committerDavid Bauer <mail@david-bauer.net>
Tue, 22 Jul 2025 22:25:35 +0000 (00:25 +0200)
Signed-off-by: David Bauer <mail@david-bauer.net>
package/network/utils/uqmi/Makefile
package/network/utils/uqmi/files/lib/netifd/proto/qmap.sh [new file with mode: 0755]
package/network/utils/uqmi/files/lib/netifd/proto/qmap_session.sh [new file with mode: 0755]

index 34749c77f3126ad79ce7749a4c7470cfec2c7a35..83a99f7c4c006ca98e898e81c6dc9a42eec9b3bf 100644 (file)
@@ -23,7 +23,7 @@ define Package/uqmi
   SECTION:=net
   CATEGORY:=Network
   SUBMENU:=WWAN
-  DEPENDS:=+libubox +libblobmsg-json +kmod-usb-net +kmod-usb-net-qmi-wwan +wwan
+  DEPENDS:=+libubox +libblobmsg-json +kmod-usb-net +kmod-usb-net-qmi-wwan +wwan +ethtool-full +kmod-rmnet
   TITLE:=Control utility for mobile broadband modems
 endef
 
diff --git a/package/network/utils/uqmi/files/lib/netifd/proto/qmap.sh b/package/network/utils/uqmi/files/lib/netifd/proto/qmap.sh
new file mode 100755 (executable)
index 0000000..e565713
--- /dev/null
@@ -0,0 +1,838 @@
+#!/bin/sh
+
+
+[ -n "$INCLUDE_ONLY" ] || {
+       . /lib/functions.sh
+       . ../netifd-proto.sh
+       init_proto "$@"
+}
+
+
+QMI_TIMEOUT=${QMI_TIMEOUT:-1000}
+QMI_SPECIAL_APN="ims sos hos"
+
+# JSON namespaces
+JSON_NAMESPACE_PDP_LIST="pdp_list"     # List of all configured PDP sessions
+
+proto_qmap_init_config() {
+       available=1
+       no_device=1
+       proto_config_add_string "device:device"
+       proto_config_add_int "uim_pin"
+       proto_config_add_array "multiplex_channel"
+
+       proto_config_add_string "ul_aggregation_proto"
+       proto_config_add_int "ul_aggregation_max_size"
+       proto_config_add_int "ul_aggregation_max_packets"
+       proto_config_add_string "dl_aggregation_proto"
+       proto_config_add_int "dl_aggregation_max_size"
+       proto_config_add_int "dl_aggregation_max_packets"
+}
+
+qmi_get_ifname() {
+       local device="$1"
+       local devname devpath ifname
+
+       device="$(readlink -f $device)"
+       [ -c "$device" ] || {
+               return 1
+       }
+
+       devname="$(basename "$device")"
+       devpath="$(readlink -f /sys/class/usbmisc/$devname/device/)"
+       ifname="$( ls "$devpath"/net )"
+       [ -n "$ifname" ] || {
+               echo "The interface could not be found."
+       }
+
+       echo "$ifname"
+}
+
+qmi_uim_unlock() {
+       local device="$1"
+       local uim_pin="$2"
+
+       json_load "$(uqmi -s -d "$device" -t $QMI_TIMEOUT --uim-get-sim-state)"
+       json_get_var card_application_state card_application_state
+
+       [ "$card_application_state" = "ready" ] && {
+               echo "UIM PIN not required"
+               return 0
+       }
+
+       [ "$card_application_state" != "pin-required" ] && {
+               echo "Unknown UIM state $card_application_state"
+               return 1
+       }
+
+       [ -z "$uim_pin" ] && {
+               echo "No UIM PIN provided"
+               return 1
+       }
+
+       uqmi -s -d "$device" -t $QMI_TIMEOUT --uim-verify-pin1 "$uim_pin" || {
+               echo "Failed to unlock UIM PIN ($uim_pin)"
+               return 1
+       }
+
+       echo "Unlock UIM PIN successful"
+       return 0
+}
+
+qmi_uim_init() {
+       local interfinterfaceace="$1"
+
+       local uim_state_timeout=5
+       local uim_state_iterations=0
+
+       while true; do
+               json_load "$(uqmi -s -d "$device" -t $QMI_TIMEOUT --uim-get-sim-state)"
+               json_get_var card_application_state card_application_state
+
+               # SIM card is eithemultiplex_channelsr completely absent or state is labeled as illegal
+               # Try to power-cycle the SIM card to recover from this state
+               if [ -z "$card_application_state" -o "$card_application_state" = "illegal" ]; then
+                       echo "SIM in illegal state - Power-cycling SIM"
+
+                       # Try to reset SIM application
+                       uqmi -d "$device" -t $QMI_TIMEOUT --uim-power-off --uim-slot 1
+                       sleep 3
+                       uqmi -d "$device" -t $QMI_TIMEOUT --uim-power-on --uim-slot 1
+
+                       if [ "$uim_state_iterations" -lt "$uim_state_timeout" ] || [ "$uim_state_timeout" = "0" ]; then
+                               let uim_state_iterations++
+                               sleep 5
+                               continue
+                       fi
+
+                       # Recovery failed
+                       return 1
+               else
+                       return 0
+               fi
+       done
+}
+
+qmap_load_pdp_session() {
+       local session="$1"
+       local config_parent="$2"
+
+       local parent
+       local ifname
+       local apn
+       local username
+       local password
+       local auth_type
+       local pdp_type
+       local proto
+       
+       config_get proto        "$session"      proto
+       config_get parent       "$session" parent
+       config_get ifname       "$session" ifname
+       config_get apn          "$session" apn
+       config_get username     "$session" username
+       config_get password     "$session" password
+       config_get auth_type    "$session" auth_type    "none"
+       config_get pdp_type     "$session" pdp_type     "ipv4"
+       config_get attach_pdn   "$session" attach_pdn   "1"
+
+       [ "$proto" = "qmap_session" ] || return 0
+
+       if [ "$config_parent" != "$parent" ]; then
+               echo "PDH session $session with parent $parent is not child of $config_parent"
+               return 0
+       fi
+
+       [ -n "$apn" ] || {
+               echo "APN not set for PDH session $session"
+               return 1
+       }
+
+       if [ -z "$ifname" ]; then
+               ifname="${parent}_${pdh_id}"
+       fi
+
+       json_add_object ""
+       json_add_string "config" "$session"
+       json_add_string "pdh_id" "$pdh_id"
+       json_add_string "ifname" "$ifname"
+       [ -n "$parent" ] &&     json_add_string "parent"        "$parent"
+       [ -n "$apn" ] &&        json_add_string "apn"           "$apn"
+       [ -n "$username" ] &&   json_add_string "username"      "$username"
+       [ -n "$password" ] &&   json_add_string "password"      "$password"
+       [ -n "$auth_type" ] &&  json_add_string "auth_type"     "$auth_type"
+       json_add_string "pdp_type" "$pdp_type"
+       json_add_string "attach_pdn" "$attach_pdn"
+       json_close_object ""
+
+       pdh_id=$((pdh_id + 1))
+}
+
+qmap_load_pdh_config() {
+       local config="$1"
+       local pdh_config_ns="pdhconf"
+       local old_ns
+       local pdh_id=1
+
+       # Save old namespace
+       json_set_namespace "$pdh_config_ns" old_ns
+
+       # Prepare output JSON
+       json_init
+
+       # Load PDH configurations from UCI
+       config_load network
+
+       # Load all PDH configurations
+       json_add_array "pdh_sessions"
+       config_foreach qmap_load_pdp_session interface "$config"
+       json_close_array
+       
+       # Dump JSON
+       json_dump
+
+       # Restore old namespace
+       json_set_namespace "$old_ns"
+}
+
+qmap_netifd_get_ip_settings() {
+       local device="$1"
+       local cid="$2"
+       uqmi -s -d $device -t 1000 --set-client-id wds,$cid --get-current-settings
+}
+
+qmap_netifd_add_ip_settings() {
+       local config_child="$1"
+       local ifname="$2"
+       local device="$3"
+       local cid_4="$4"
+       local cid_6="$5"
+
+       local json_ns_cur="json_ns_add_ipv4"
+       local json_ns_old
+
+       local ip_4 gateway_4 dns1_4 dns2_4 subnet_4
+       local ip_6 gateway_6 dns1_6 dns2_6 ip_prefix_length_6
+
+       local zone="$(fw3 -q network "$config_child" 2>/dev/null)"
+
+       json_set_namespace "$json_ns_cur" json_ns_old
+
+       if [ -n "$cid_4" ]; then
+               json_init
+               json_load "$(qmap_netifd_get_ip_settings "$device" "$cid_4")"
+
+               # IPv4 settings
+               json_select ipv4
+               json_get_var ip_4 ip
+               json_get_var gateway_4 gateway
+               json_get_var dns1_4 dns1
+               json_get_var dns2_4 dns2
+               json_get_var subnet_4 subnet
+
+               echo "Adding IPv4 address $ip_4/$subnet_4 gateway $gateway_4 dns1 $dns1_4 dns2 $dns2_4 zone $zone to $ifname"
+       fi
+
+       if [ -n "$cid_6" ]; then
+               json_init
+               json_load "$(qmap_netifd_get_ip_settings "$device" "$cid_6")"
+
+               # IPv6 settings
+               json_select ipv6
+               json_get_var ip_6 ip
+               json_get_var gateway_6 gateway
+               json_get_var dns1_6 dns1
+               json_get_var dns2_6 dns2
+               json_get_var ip_prefix_length_6 ip-prefix-length
+
+               echo "Adding IPv6 address $ip_6/$ip_prefix_length_6 gateway $gateway_6 dns1 $dns1_6 dns2 $dns2_6 zone $zone to $ifname"
+       fi
+
+       json_set_namespace "$json_ns_old"
+
+       json_add_object "ipv4"
+       json_add_string "ip" "$ip_4"
+       json_add_string "gateway" "$gateway_4"
+       json_add_string "dns1" "$dns1_4"
+       json_add_string "dns2" "$dns2_4"
+       json_add_string "subnet" "$subnet_4"
+       json_close_object
+
+       json_add_object "ipv6"
+       json_add_string "ip" "$ip_6"
+       json_add_string "gateway" "$gateway_6"
+       json_add_string "dns1" "$dns1_6"
+       json_add_string "dns2" "$dns2_6"
+       json_add_string "ip_prefix_length" "$ip_prefix_length_6"
+       json_close_object
+
+       json_set_namespace "$json_ns_old"
+}
+
+qmap_create_data_session() {
+       local key="$1"
+       local config_parent="$3"
+       local device="$4"
+       local parent_ifname="$5"
+
+       local config
+       local pdh_id
+       local apn
+       local username
+       local password
+       local auth_type
+       local pdp_type
+       local attach_pdn
+
+       local wds_id_ipv4
+       local wds_id_ipv6
+
+       local pdh_id_ipv4
+       local pdh_id_ipv6
+
+       local connect_v4
+       local connect_v6
+
+       local ifname
+
+       json_select "$2"
+       json_get_vars config pdh_id apn username password auth_type pdp_type attach_pdn ifname
+
+       if [ "$pdp_type" == "ipv4" ]; then
+               connect_v4=1
+       elif [ "$pdp_type" == "ipv6" ]; then
+               connect_v6=1
+       elif [ "$pdp_type" == "ipv4v6" ]; then
+               connect_v4=1
+               connect_v6=1
+       else
+               echo "Invalid PDP type $pdp_type"
+               json_select ".."
+               return 1
+       fi
+
+       echo "Creating multiplex channel $pdh_id on $parent_ifname: $ifname"
+
+       # Create PDH session
+       json_add_object "wds_clients"
+       if [ "$connect_v4" == "1" ]; then
+               wds_id_ipv4="$(uqmi -d "/dev/cdc-wdm0" --get-client-id wds)"
+               json_add_int "ipv4" "$wds_id_ipv4"
+       fi
+       if [ "$connect_v6" == "1" ]; then
+               wds_id_ipv6="$(uqmi -d "/dev/cdc-wdm0" --get-client-id wds)"
+               json_add_int "ipv6" "$wds_id_ipv6"
+       fi
+       json_close_object
+
+       # Bind clients to multiplex channel
+       if [ "$connect_v4" == "1" ]; then
+               uqmi -d "/dev/cdc-wdm0" --bind-mux "$pdh_id" --endpoint-type hsusb --endpoint-iface 4 --set-client-id wds,$wds_id_ipv4
+               uqmi -d "/dev/cdc-wdm0" --set-ip-family ipv4 --set-client-id wds,$wds_id_ipv4
+       fi
+       if [ "$connect_v6" == "1" ]; then
+               uqmi -d "/dev/cdc-wdm0" --bind-mux "$pdh_id" --endpoint-type hsusb --endpoint-iface 4 --set-client-id wds,$wds_id_ipv6
+               uqmi -d "/dev/cdc-wdm0" --set-ip-family ipv6 --set-client-id wds,$wds_id_ipv6
+       fi
+
+       # Store APN profile settings
+       uqmi -d "/dev/cdc-wdm0" --modify-profile 3gpp,$pdh_id --apn "$apn" --auth-type "$auth_type" --pdp-type "$pdp_type"
+       if [ $? -ne 0 ]; then
+               uqmi -d "/dev/cdc-wdm0" --create-profile 3gpp
+               uqmi -d "/dev/cdc-wdm0" --modify-profile 3gpp,$pdh_id --apn "$apn" --auth-type "$auth_type" --pdp-type "$pdp_type"
+       fi
+
+       echo "Create PDH handles for $ifname"
+
+       # Create PDH handles
+       if [ "$connect_v4" == "1" ]; then
+               pdh_id_ipv4="$(uqmi -d "/dev/cdc-wdm0" --set-client-id wds,$wds_id_ipv4 --start-network --profile $pdh_id)"
+               if [ $? -ne 0 ]; then
+                       echo "Failed to start IPv4 network for PDH session $pdh_id"
+                       json_select ".."
+                       return 1
+               fi
+       fi
+
+       if [ "$connect_v6" == "1" ]; then
+               pdh_id_ipv6="$(uqmi -d "/dev/cdc-wdm0" --set-client-id wds,$wds_id_ipv6 --start-network --profile $pdh_id)"
+               if [ $? -ne 0 ]; then
+                       echo "Failed to start IPv6 network for PDH session $pdh_id"
+                       json_select ".."
+                       return 1
+               fi
+       fi
+
+       json_add_object "pdh_handles"
+       [ -n "$pdh_id_ipv4" ] && json_add_int "ipv4" "$pdh_id_ipv4"
+       [ -n "$pdh_id_ipv6" ] && json_add_int "ipv6" "$pdh_id_ipv6"
+       json_close_object
+       
+
+       echo "Start netifd interface configuration for $ifname"
+       # Create netifd interface configuration
+       qmap_netifd_add_ip_settings "$config" "$ifname" "$device" "$wds_id_ipv4" "$wds_id_ipv6"
+       json_select ".."
+}
+
+qmap_create_rmnet() {
+       local key="$1"
+       local config_parent="$3"
+       local device="$4"
+       local parent_ifname="$5"
+
+       local json_ns_cur="json_ns_create_rmnet"
+       local json_ns_old
+
+       local pdh_id
+       local ifname
+       local config
+
+       json_select "$2"
+       json_get_vars pdh_id ifname config
+
+
+       # Remove interface if it already exists
+       ip link del dev "$ifname" 2>/dev/null
+
+       ip link add \
+               link "$parent_ifname" \
+               name "$ifname" \
+               type rmnet mux_id "${pdh_id}" \
+               ingress-deaggregation on \
+               ingress-mapv5-checksum on \
+               egress-mapv5-checksum on \
+               ingress-commands on
+       if [ $? -ne 0 ]; then
+               echo "Failed to create multiplex channel"
+       fi
+
+       json_select ".."
+
+       json_set_namespace "$json_ns_cur" json_ns_old
+       json_init
+
+       proto_set_available "$config" 1
+
+       json_set_namespace "$json_ns_old"
+}
+
+qmap_create_pdp_get_session_apn() {
+       local current_pdp_idx="$1"
+       local device="$2"
+       local apn_settings
+       local json_ns_cur="json_ns_pdp_session_apn_name"
+       local json_ns_old
+
+       apn_settings="$(uqmi -d "$device" --get-profile-settings 3gpp,$current_pdp_idx)"
+       if [ $? -ne 0 ]; then
+               uqmi -d "$device" --create-profile 3gpp
+               apn_settings="$(uqmi -d "$device" --get-profile-settings 3gpp,$current_pdp_idx)"
+       fi
+
+       json_set_namespace "$json_ns_cur" json_ns_old
+       json_load "$apn_settings"
+       json_get_var apn apn
+       json_set_namespace "$json_ns_old"
+}
+
+qmap_create_pdp_session_write_profile() {
+       local current_pdp_idx="$1"
+       local device="$2"
+       local apn
+
+       # Get JSON Values
+       json_get_vars apn username password auth_type pdp_type
+       echo "Create PDP context $current_pdp_idx apn=$apn username=$username password=$password auth_type=$auth_type pdp_type=$pdp_type"
+       uqmi -d "$device" --modify-profile 3gpp,$current_pdp_idx \
+               ${apn:+--apn $apn} \
+               ${username:+--username $username} \
+               ${password:+--password $password} \
+               --auth-type none --pdp-type ipv4
+}
+
+qmap_create_pdp_session() {
+       local session="$1"
+       local js_idx="$2"
+       local device="$3"
+       local apn
+
+       pdp_idx_found=0
+
+       json_select "$js_idx"
+
+       while [ "$pdp_idx_found" == 0 ]; do
+               # Get profile settings of current APN
+               qmap_create_pdp_get_session_apn "$current_pdp_idx" "$device"
+
+               # Check if the current PDP index is a special APN
+               for special_apn in $QMI_SPECIAL_APN; do
+                       if [ "$apn" != "$special_apn" ]; then
+                               pdp_idx_found=1
+                       else
+                               echo "Found special APN $special_apn at PDP index $current_pdp_idx, skipping"
+                               current_pdp_idx=$((current_pdp_idx + 1))
+                       fi
+               done
+       done
+
+       qmap_create_pdp_session_write_profile "$current_pdp_idx" "$device"
+       current_pdp_idx=$((current_pdp_idx + 1))
+       json_select ".."
+}
+
+qmap_remove_pdp_session() {
+       local index
+       local val="$1"
+       local js_idx="$2"
+       local delete_start_idx="$3"
+
+       # Get Profile Index
+       json_select "$js_idx"
+       json_get_vars index
+       json_select ".."
+
+       # Delete Profile if it is not in use
+       [ "$index" -ge "$delete_start_idx" ] || return 0
+       uqmi -d "$device" --delete-profile 3gpp,$index
+}
+
+qmap_create_pdp_profiles() {
+       local device="$1"
+       local pdh_configs="$2"
+       local current_pdp_idx=1
+       local pdp_delete_profile_start
+       local pdp_idx_found
+       local apn_settings
+       local json_ns_cur="json_ns_create_pdp_profiles"
+       local json_ns_old
+       local all_pdn_profiles
+
+       # Now it gets tricky. If modem has VoLTE enabled, we have APNs
+       # we are not allowed to touch. We need to remove all others we
+       # are allowed to touch.
+       json_set_namespace "$json_ns_cur" json_ns_old
+       json_init
+       json_load "$pdh_configs"
+       json_for_each_item qmap_create_pdp_session "pdh_sessions" "$device"
+
+
+       # Get all PDN profiles
+       all_pdn_profiles="$(uqmi -s -d "$device" --get-profile-list 3gpp)"
+       json_init
+       json_load "$all_pdn_profiles"
+
+       # Delete all profiles starting from 'current_pdp_idx+1'
+       current_pdp_idx=$((current_pdp_idx + 1))
+       json_for_each_item qmap_remove_pdp_session "profiles" "$current_pdp_idx"
+       json_set_namespace "$json_ns_old"
+}
+
+qmi_modem_setup() {
+       local success_iterations=5
+       local success=0
+
+       echo "Setting up modem"
+
+       while [ $success_iterations -ne 0 ]; do
+               uqmi -s -d "$1" -t "$QMI_TIMEOUT" --sync > /dev/null 2>&1
+               if [ $? -eq 0 ]; then
+                       success=$((success + 1))
+               else
+                       success=0
+                       sleep 10
+               fi
+               success_iterations=$((success_iterations - 1))
+               sleep 1
+       done
+
+       if [ $success -eq 0 ]; then
+               echo "Failed to sync device"
+               exit 1
+       fi
+
+       echo "Disconnect from network"
+       uqmi -s -d "$1" -t "$QMI_TIMEOUT" ---set-device-operating-mode low_power > /dev/null 2>&1
+       sleep 1
+
+       echo "Power-cycle SIM card"
+       uqmi -s -d "$1" -t "$QMI_TIMEOUT" --uim-power-off --uim-slot 1 > /dev/null 2>&1
+       sleep 1
+       uqmi -s -d "$1" -t "$QMI_TIMEOUT" --uim-power-on --uim-slot 1 > /dev/null 2>&1
+       sleep 3
+}
+
+qmpi_modem_connect() {
+       local connected=0
+       local connection_tries=12
+
+       local json_ns_cur="json_ns_modem_setup"
+       local json_ns_old
+
+       local registration_status
+
+
+       echo "Connect to network"
+       uqmi -s -d "$1" -t "$QMI_TIMEOUT" --set-device-operating-mode online > /dev/null 2>&1
+       sleep 1
+
+       json_set_namespace "$json_ns_cur" json_ns_old
+       while [ $connected -ne 1 ] && [ $connection_tries -ne 0 ]; do
+               connection_tries=$((connection_tries - 1))
+               json_init
+               json_load "$(uqmi -s -d "$1" -t "$QMI_TIMEOUT" --get-serving-system)"
+               if [ $? -eq 1 ]; then
+                       continue
+               fi
+
+               json_get_var registration_status registration
+
+               if [ "$registration_status" = "registered" ]; then
+                       connected=1
+               else
+                       echo "Waiting for network registration status=$registration_status"
+                       sleep 5
+               fi
+       done
+       json_set_namespace "$json_ns_old"
+
+       if [ $connected -ne 1 ]; then
+               echo "Failed to connect to network"
+               exit 1
+       fi
+
+       echo "Connected to cellular network"
+
+}
+
+proto_qmap_setup() {
+       local config="$1"
+       local proto_config_json_ns
+
+       local device
+       local ifname
+
+       local mtu
+       local multiplex_channel
+       local multiplex_channels
+
+       local ul_aggregation_proto
+       local ul_aggregation_max_size
+       local ul_aggregation_max_packets
+       local dl_aggregation_proto
+       local dl_aggregation_max_size
+       local dl_aggregation_max_packets
+
+       local uim_pin
+
+       local pdh_configs
+
+       json_get_vars device
+       json_get_vars uim_pin
+
+       json_get_vars ul_aggregation_proto ul_aggregation_max_size ul_aggregation_max_packets
+       json_get_vars dl_aggregation_proto dl_aggregation_max_size dl_aggregation_max_packets
+
+       json_set_namespace "$config" proto_config_json_ns
+
+       # Load all PDH configurations
+       pdh_configs="$(qmap_load_pdh_config "$config")"
+
+       # Resolve interface name
+       ifname="$(qmi_get_ifname "${device}")"
+       [ -n "$ifname" ] || {
+               echo "The interface could not be found."
+               exit 1
+       }
+
+       # Setup device
+       qmi_modem_setup "${device}"
+       if [ $? -ne 0 ]; then
+               echo "Failed to setup modem"
+               exit 1
+       fi
+
+       # Wait for UIM init
+       qmi_uim_init "${device}"
+       if [ $? -ne 0 ]; then
+               echo "Failed to initialize UIM"
+               return 1
+       fi
+
+       # Unlock SIM if necessary
+       qmi_uim_unlock "${device}" "${uim_pin}"
+       if [ $? -ne 0 ]; then
+               echo "Failed to unlock UIM PIN"
+               return 1
+       fi
+
+       # Connect to network
+       qmpi_modem_connect "${device}"
+       if [ $? -ne 0 ]; then
+               echo "Failed to connect to network"
+               return 1
+       fi
+
+       # Cleanup state
+       uqmi -s -d "$device" -t "$QMI_TIMEOUT" --stop-network 0xffffffff --autoconnect > /dev/null 2>&1
+
+       # Go online
+       uqmi -s -d "$device" -t "$QMI_TIMEOUT" --set-device-operating-mode online 2>&1
+       if [ $? -ne 0 ]; then
+               echo "Failed to set device operating mode"
+               return 1
+       fi
+
+       # Enable raw-ip
+       echo "Y" > /sys/class/net/$ifname/qmi/raw_ip
+
+       # Enable pass-through
+       echo "Y" > /sys/class/net/$ifname/qmi/pass_through
+
+       # Sync state
+       uqmi -s -d "$device" -t "$QMI_TIMEOUT" --sync > /dev/null 2>&1
+       if [ $? -ne 0 ]; then
+               echo "Failed to sync device"
+               return 1
+       fi
+
+       # Setup datapath aggregation parameters
+       uqmi -d "$device" -t "$QMI_TIMEOUT" \
+               --wda-set-data-format raw-ip \
+               --dl-aggregation-protocol "$dl_aggregation_proto" \
+               --dl-datagram-max-count "$dl_aggregation_max_packets" \
+               --dl-datagram-max-size "$dl_aggregation_max_size" \
+               --ul-aggregation-protocol "$ul_aggregation_proto" \
+               --ul-datagram-max-count "$ul_aggregation_max_packets" \
+               --ul-datagram-max-size "$ul_aggregation_max_size"
+       if [ $? -ne 0 ]; then
+               echo "Failed to set aggregation parameters"
+               return 1
+       fi
+
+
+       # Set MTU
+       mtu=1500
+       [ "$mtu" -lt "$ul_aggregation_max_size" ] && mtu="$ul_aggregation_max_size"
+       [ "$mtu" -lt "$dl_aggregation_max_size" ] && mtu="$dl_aggregation_max_size"
+       ip link set dev "$ifname" mtu "$mtu"
+       if [ $? -ne 0 ]; then
+               echo "Failed to set MTU"
+               return 1
+       fi
+
+       # Enable interface
+       ip link set dev "$ifname" up
+       if [ $? -ne 0 ]; then
+               echo "Failed to bring up interface"
+               return 1
+       fi
+
+       # Create PDP connection profiles
+       qmap_create_pdp_profiles "$device" "$pdh_configs"
+
+       # Create PDH channels
+       json_set_namespace "pdhconf"
+       json_init
+       json_load "$pdh_configs"
+       echo "$pdh_configs"
+       json_for_each_item qmap_create_data_session "pdh_sessions" "$config" "$device" "$ifname"
+       pdh_configs="$(json_dump)"
+
+       json_set_namespace "$proto_config_json_ns"
+
+       echo "$ifname - $config"
+
+       proto_init_update "$ifname" 1
+       proto_set_keep 1
+       proto_add_data
+       json_add_string "pdh_config" "$pdh_configs"
+       proto_close_data
+       proto_send_update "$config"
+
+       # Create multiplex channels
+       json_set_namespace "pdhconf"
+       json_init
+       json_load "$pdh_configs"
+       json_for_each_item qmap_create_rmnet "pdh_sessions" "$config" "$device" "$ifname"
+
+       echo "END HERE"
+
+       return 0
+}
+
+qmap_teardown_pdh_and_client() {
+       local pdh_id="$1"
+       local wds_cid="$2"
+       local device="$3"
+
+       # Stop PDH session
+       uqmi -s -d "$device" -t 1000 --set-client-id wds,"$wds_cid" \
+               --stop-network "$pdh_id" > /dev/null 2>&1
+
+       # Release WDS client ID
+       uqmi -s -d "$device" -t 1000 --set-client-id wds,"$wds_cid" \
+               --release-client-id wds > /dev/null 2>&1
+}
+
+qmap_teardown_child_interface() {
+       local js_idx="$2"
+       local device="$3"
+       local ifname
+
+       local wds_cid_ipv4
+       local wds_cid_ipv6
+
+       local pdh_id_ipv4
+       local pdh_id_ipv6
+
+       json_select "$js_idx"
+       json_get_vars ifname
+
+       echo "Remove interface $ifname"
+       ip link del dev "$ifname"
+
+       json_select "wds_clients"
+       json_get_var wds_cid_ipv4 ipv4
+       json_get_var wds_cid_ipv6 ipv6
+       json_select ".."
+
+       json_select "pdh_handles"
+       json_get_var pdh_id_ipv4 ipv4
+       json_get_var pdh_id_ipv6 ipv6
+       json_select ".."
+
+       [ -n "$pdh_id_ipv4" ] && qmap_teardown_pdh_and_client "$pdh_id_ipv4" "$wds_cid_ipv4" "$device"
+       [ -n "$pdh_id_ipv6" ] && qmap_teardown_pdh_and_client "$pdh_id_ipv6" "$wds_cid_ipv6" "$device"
+
+       json_select ".."
+}
+
+proto_qmap_teardown() {
+       local interface="$1"
+       local device
+       local pdh_config
+
+       json_get_vars device
+
+       json_load "$(ubus call network.interface.$interface status)"
+       json_select data
+       json_get_vars pdh_config
+
+       json_load "$pdh_config"
+
+       json_for_each_item qmap_teardown_child_interface "pdh_sessions" "$device"
+
+       # Remove multiplex channels
+       for multiplex_channel in $(json_get_keys "$interface" "multiplex_channels"); do
+               ip link del dev "$interface"_"$multiplex_channel"
+       done
+
+
+       # Shut down modem
+       # uqmi -s -d "$device" -t "$QMI_TIMEOUT" --set-device-operating-mode low_power 2>&1
+}
+
+[ -n "$INCLUDE_ONLY" ] || {
+       add_protocol qmap
+}
diff --git a/package/network/utils/uqmi/files/lib/netifd/proto/qmap_session.sh b/package/network/utils/uqmi/files/lib/netifd/proto/qmap_session.sh
new file mode 100755 (executable)
index 0000000..379d1fc
--- /dev/null
@@ -0,0 +1,184 @@
+#!/bin/sh
+
+
+[ -n "$INCLUDE_ONLY" ] || {
+       . /lib/functions.sh
+       . ../netifd-proto.sh
+       init_proto "$@"
+}
+
+qmap_session_configure_from_parent_session()  {
+       local iter_key="$2"
+       local config="$3"
+       local iter_config
+
+       json_select "$iter_key"
+       json_get_var iter_config "config"
+       json_get_var qmap_ifname "ifname"
+       [ "$iter_config" != "$config" ] && {
+               echo "iter_config: $iter_config"
+               echo "config: $config"
+               json_select ".."
+               return
+       }
+
+       if json_is_a "ipv4" "object"; then
+               json_select "ipv4"
+               json_get_var ipv4_addr "ip"
+               json_get_var ipv4_gateway "gateway"
+               json_get_var ipv4_dns1 "dns1"
+               json_get_var ipv4_dns2 "dns2"
+               json_get_var ipv4_subnet "subnet"
+               json_select ".."
+       fi
+
+       if json_is_a "ipv6" "object"; then
+               json_select "ipv6"
+               json_get_var ipv6_addr "ip"
+               json_get_var ipv6_gateway "gateway"
+               json_get_var ipv6_dns1 "dns1"
+               json_get_var ipv6_dns2 "dns2"
+               json_get_var ipv6_prefix_length "ip_prefix_length"
+               json_select ".."
+       fi
+
+       json_select ".."
+}
+
+qmap_session_configure_from_parent() {
+       local child_config="$1"
+       local parent_data="$2"
+       local json_ns_cur="json_ns_get_settings"
+       local json_ns_old
+
+       local config
+       local ifname
+
+       json_set_namespace "$json_ns_cur" json_ns_old
+       json_init
+       json_load "$parent_data"
+       json_for_each_item qmap_session_configure_from_parent_session "pdh_sessions" "$child_config"
+}
+
+qmap_session_get_parent_data() {
+       local config="$1"
+       local parent_status="$2"
+       local json_ns_cur="json_ns_get_settings"
+       local json_ns_old
+
+       local pdh_config
+       local pdh_sessions
+
+       json_set_namespace "$json_ns_cur" json_ns_old
+
+       json_init
+       json_load "$parent_status"
+       json_select "data"
+       json_get_vars "pdh_config"
+
+       json_init
+       json_load "$pdh_config"
+       json_get_vars pdh_sessions
+       json_dump
+
+       json_set_namespace "$json_ns_old"
+}
+
+proto_qmap_session_init_config() {
+       available=1
+       no_device=1
+       proto_config_add_string "parent"
+}
+
+proto_qmap_session_setup() {
+       local config="$1"
+       local parent_status
+       local parent_data
+       local session_data
+       local parent
+       local up
+       local settings
+
+       local qmap_ifname
+
+       local ipv4_addr
+       local ipv4_gateway
+       local ipv4_subnet
+       local ipv4_dns1
+       local ipv4_dns2
+
+       local ipv6_addr
+       local ipv6_gateway
+       local ipv6_dns1
+       local ipv6_dns2
+       local ipv6_prefix_length
+
+       echo "proto_qmap_session_setup"
+
+       json_get_vars parent
+
+       echo "parent: $parent"
+
+       [ -z "$parent" ] && {
+               proto_notify_error "$config" "MISSING_PARENT"
+               proto_set_available "$config" 0
+               return
+       }
+
+       parent_status="$(ubus call network.interface.$parent status)"
+       json_init
+       json_load "$parent_status"
+       json_get_var up up
+
+       echo "up: $up"
+
+       [ "$up" != "1" ] && {
+               proto_notify_error "$config" "PARENT_DOWN"
+               proto_set_available "$config" 0
+               return
+       }
+
+       parent_data=$(qmap_session_get_parent_data "$config" "$parent_status")
+       echo "settings: $parent_data"
+       [ -z "$parent_status" ] && {
+               proto_notify_error "$config" "NO_PARENT_SETTINGS"
+               proto_set_available "$config" 0
+               return
+       }
+
+       qmap_session_configure_from_parent "$config" "$parent_data"
+
+       ip link set dev "$qmap_ifname" up
+       ethtool -C "$qmap_ifname" tx-aggr-max-bytes 4000 tx-aggr-max-frames 20 tx-aggr-time-usecs 10000
+
+       proto_init_update "$qmap_ifname" 1
+       proto_set_keep 1
+
+       # IPv4
+       if [ -n "$ipv4_addr" ]; then
+               proto_add_ipv4_address "$ipv4_addr" "$ipv4_subnet" "" "$ipv4_gateway"
+               proto_add_ipv4_route "0.0.0.0" 0 "$ipv4_gateway"
+               proto_add_dns_server "$ipv4_dns1"
+               proto_add_dns_server "$ipv4_dns2"
+       fi
+
+       # IPv6
+       if [ -n "$ipv6_addr" ]; then
+               proto_add_ipv6_address "$ipv6_addr" "128"
+               proto_add_ipv6_prefix "${ipv6_addr}/${ipv6_prefix_length}"
+               proto_add_ipv6_route "$ipv6_gateway" "128"
+               proto_add_ipv6_route "::0" 0 "$ipv6_gateway" "" "" "${ipv6_addr}/${ipv6_prefix_length}"
+               proto_add_dns_server "$ipv6_dns1"
+               proto_add_dns_server "$ipv6_dns2"
+       fi
+
+       proto_send_update "$config"
+}
+
+proto_qmap_session_teardown() {
+       local config="$1"
+}
+
+[ -n "$INCLUDE_ONLY" ] || {
+       add_protocol qmap_session
+}