Many of the 4G/LTE and 3G modems utilize the QMI-protocol to control the modem. At the moment there is no support for them in OpenWrt. This patch adds support for them in the form of a netifd script and a control utility. Tested with Huawei E398 and ZTE MF820D (which requires a delay of ~30 s before responding to QMI commands). I put myself up as the maintainer, feel free to change this if you desire.
Changes to RFC version: - Patch split into two - Improved netifd script - Minor fixes to uqmi command line help - Fixed autoconnect in uqmi - Fixed printing service versions in uqmi v2: - Uses subprotocols instead of including entire dhcp.sh, therefore removed netifd patch - Makes while loops less nasty by looping on expected conditions only, avoids infinite loops in unexpected conditions Signed-off-by: Matti Laakso <malaa...@elisanet.fi> --- diff --git a/package/network/utils/uqmi/Makefile b/package/network/utils/uqmi/Makefile new file mode 100644 index 0000000..c66de85 --- /dev/null +++ b/package/network/utils/uqmi/Makefile @@ -0,0 +1,48 @@ +include $(TOPDIR)/rules.mk + +PKG_NAME:=uqmi +PKG_VERSION:=2013-06-23 +PKG_RELEASE=$(PKG_SOURCE_VERSION) + +PKG_SOURCE_PROTO:=git +PKG_SOURCE_URL:=git://nbd.name/uqmi.git +PKG_SOURCE_SUBDIR:=$(PKG_NAME)-$(PKG_VERSION) +PKG_SOURCE_VERSION:=35201737484008ac802649cbe9fb5f7ab38a4ad2 +PKG_SOURCE:=$(PKG_NAME)-$(PKG_VERSION)-$(PKG_SOURCE_VERSION).tar.gz +PKG_MAINTAINER:=Matti Laakso <malaa...@elisanet.fi> +# PKG_MIRROR_MD5SUM:= +# CMAKE_INSTALL:=1 + +PKG_LICENSE:=GPLv2 +PKG_LICENSE_FILES:= + +PKG_BUILD_PARALLEL:=1 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +define Package/uqmi + SECTION:=net + CATEGORY:=Network + DEPENDS:=+libubox +libblobmsg-json + TITLE:=Control utility for mobile broadband modems +endef + +define Package/uqmi/description + uqmi is a command line tool for controlling mobile broadband modems using + the QMI-protocol. +endef + +TARGET_CFLAGS += \ + -I$(STAGING_DIR)/usr/include + +CMAKE_OPTIONS += \ + -DDEBUG=1 + +define Package/uqmi/install + $(INSTALL_DIR) $(1)/sbin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/uqmi $(1)/sbin/ + $(CP) ./files/* $(1)/ +endef + +$(eval $(call BuildPackage,uqmi)) diff --git a/package/network/utils/uqmi/files/lib/netifd/proto/qmi.sh b/package/network/utils/uqmi/files/lib/netifd/proto/qmi.sh new file mode 100755 index 0000000..971adeb --- /dev/null +++ b/package/network/utils/uqmi/files/lib/netifd/proto/qmi.sh @@ -0,0 +1,137 @@ +#!/bin/sh + +. /lib/functions.sh +. ../netifd-proto.sh +init_proto "$@" + +proto_qmi_init_config() { + proto_config_add_string "device:device" + proto_config_add_string apn + proto_config_add_string auth + proto_config_add_string username + proto_config_add_string password + proto_config_add_string pincode + proto_config_add_string delay + proto_config_add_string modes +} + +proto_qmi_setup() { + local interface="$1" + + local device apn auth username password pincode delay modes cid pdh + json_get_vars device apn auth username password pincode delay modes + + [ -n "$device" ] || { + logger -p daemon.err -t "qmi[$$]" "No control device specified" + proto_notify_error "$interface" NO_DEVICE + proto_block_restart "$interface" + return 1 + } + [ -c "$device" ] || { + logger -p daemon.err -t "qmi[$$]" "The specified control device does not exist" + proto_notify_error "$interface" NO_DEVICE + proto_block_restart "$interface" + return 1 + } + + [ -n "$delay" ] && sleep "$delay" + + while uqmi -s -d "$device" --get-pin-status | grep '"UIM uninitialized"' > /dev/null; do + sleep 1; + done + + [ -n "$pincode" ] && { + uqmi -s -d "$device" --verify-pin1 "$pincode" || { + logger -p daemon.err -t "qmi[$$]" "Unable to verify PIN" + proto_notify_error "$interface" PIN_FAILED + proto_block_restart "$interface" + return 1 + } + } + + [ -n "$apn" ] || { + logger -p daemon.err -t "qmi[$$]" "No APN specified" + proto_notify_error "$interface" NO_APN + proto_block_restart "$interface" + return 1 + } + + logger -p daemon.info -t "qmi[$$]" "Waiting for network registration" + while uqmi -s -d "$device" --get-serving-system | grep '"searching"' > /dev/null; do + sleep 5; + done + + [ -n "$modes" ] && uqmi -s -d "$device" --set-network-modes "$modes" + + logger -p daemon.info -t "qmi[$$]" "Starting network $apn" + cid=`uqmi -s -d "$device" --get-client-id wds` + [ $? -ne 0 ] && { + logger -p daemon.err -t "qmi[$$]" "Unable to obtain client ID" + proto_notify_error "$interface" NO_CID + proto_block_restart "$interface" + return 1 + } + uci_set_state network $interface cid "$cid" + + pdh=`uqmi -s -d "$device" --set-client-id wds,"$cid" --start-network "$apn" \ + ${auth:+--auth-type $auth} \ + ${username:+--username $username} \ + ${password:+--password $password}` + [ $? -ne 0 ] && { + logger -p daemon.err -t "qmi[$$]" "Unable to connect, check APN and authentication" + proto_notify_error "$interface" NO_PDH + proto_block_restart "$interface" + return 1 + } + uci_set_state network $interface pdh "$pdh" + + if ! uqmi -s -d "$device" --get-data-status | grep '"connected"' > /dev/null; then + logger -p daemon.err -t "qmi[$$]" "Connection lost" + proto_notify_error "$interface" NOT_CONNECTED + proto_block_restart "$interface" + return 1 + fi + + logger -p daemon.info -t "qmi[$$]" "Connected, starting DHCP" + proto_init_update "*" 1 + proto_send_update "$interface" + + json_init + json_add_string name "${interface}_dhcp" + json_add_string ifname "@$interface" + json_add_string proto "dhcp" + json_close_object + ubus call network add_dynamic "$(json_dump)" + + json_init + json_add_string name "${interface}_dhcpv6" + json_add_string ifname "@$interface" + json_add_string proto "dhcpv6" + json_close_object + ubus call network add_dynamic "$(json_dump)" +} + +proto_qmi_teardown() { + local interface="$1" + + local device + json_get_vars device + local cid=$(uci_get_state network $interface cid) + local pdh=$(uci_get_state network $interface pdh) + + logger -p daemon.info -t "qmi[$$]" "Stopping network" + [ -n "$cid" ] && { + [ -n "$pdh" ] && { + uqmi -s -d "$device" --set-client-id wds,"$cid" --stop-network "$pdh" + uci_revert_state network $interface pdh + } + uqmi -s -d "$device" --set-client-id wds,"$cid" --release-client-id wds + uci_revert_state network $interface cid + } + + proto_init_update "*" 0 + proto_send_update "$interface" +} + +add_protocol qmi + diff --git a/package/network/utils/uqmi/patches/001-improve-documentation.patch b/package/network/utils/uqmi/patches/001-improve-documentation.patch new file mode 100644 index 0000000..13b9c87 --- /dev/null +++ b/package/network/utils/uqmi/patches/001-improve-documentation.patch @@ -0,0 +1,28 @@ +--- a/commands-wds.h ++++ b/commands-wds.h +@@ -13,6 +13,6 @@ + " --auth-type pap|chap|both|none: Use network authentication type\n" \ + " --username <name>: Use network username\n" \ + " --password <password>: Use network password\n" \ +- " --autconnect: Enable automatic connect/reconnect\n" \ ++ " --autoconnect: Enable automatic connect/reconnect\n" \ + " --get-data-status: Get current data access status\n" \ + +--- a/main.c ++++ b/main.c +@@ -33,7 +33,6 @@ static int usage(const char *progname) + " --single, -s: Print output as a single line (for scripts)\n" + " --device=NAME, -d NAME: Set device name to NAME (required)\n" + " --keep-client-id <name>: Keep Client ID for service <name>\n" +- " (implies --keep-client-id)\n" + " --release-client-id <name>: Release Client ID after exiting\n" + "\n" + "Services: dms, nas, pds, wds, wms\n" +@@ -41,6 +40,7 @@ static int usage(const char *progname) + "Actions:\n" + " --get-versions: Get service versions\n" + " --set-client-id <name>,<id>: Set Client ID for service <name> to <id>\n" ++ " (implies --keep-client-id)\n" + " --get-client-id <name>: Connect and get Client ID for service <name>\n" + " (implies --keep-client-id)\n" + wds_helptext diff --git a/package/network/utils/uqmi/patches/002-fix-autoconnect.patch b/package/network/utils/uqmi/patches/002-fix-autoconnect.patch new file mode 100644 index 0000000..e303fd4 --- /dev/null +++ b/package/network/utils/uqmi/patches/002-fix-autoconnect.patch @@ -0,0 +1,11 @@ +--- a/commands-wds.c ++++ b/commands-wds.c +@@ -52,7 +52,7 @@ cmd_wds_set_password_prepare(struct qmi_ + static enum qmi_cmd_result + cmd_wds_set_autoconnect_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg) + { +- qmi_set_ptr(&wds_sn_req, enable_autoconnect, true); ++ qmi_set(&wds_sn_req, enable_autoconnect, true); + return QMI_CMD_DONE; + } + diff --git a/package/network/utils/uqmi/patches/003-stop-network.patch b/package/network/utils/uqmi/patches/003-stop-network.patch new file mode 100644 index 0000000..f5deac7 --- /dev/null +++ b/package/network/utils/uqmi/patches/003-stop-network.patch @@ -0,0 +1,59 @@ +--- a/commands-wds.c ++++ b/commands-wds.c +@@ -1,9 +1,12 @@ ++#include <stdlib.h> ++ + #include "qmi-message.h" + + static struct qmi_wds_start_network_request wds_sn_req = { + QMI_INIT(authentication_preference, + QMI_WDS_AUTHENTICATION_PAP | QMI_WDS_AUTHENTICATION_CHAP), + }; ++static struct qmi_wds_stop_network_request wds_stn_req; + + #define cmd_wds_set_auth_cb no_cb + static enum qmi_cmd_result +@@ -53,6 +56,7 @@ static enum qmi_cmd_result + cmd_wds_set_autoconnect_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg) + { + qmi_set(&wds_sn_req, enable_autoconnect, true); ++ qmi_set(&wds_stn_req, disable_autoconnect, true); + return QMI_CMD_DONE; + } + +@@ -74,6 +78,17 @@ cmd_wds_start_network_prepare(struct qmi + return QMI_CMD_REQUEST; + } + ++#define cmd_wds_stop_network_cb no_cb ++static enum qmi_cmd_result ++cmd_wds_stop_network_prepare(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg, char *arg) ++{ ++ uint32_t pdh = strtoul(arg, NULL, 0); ++ ++ qmi_set(&wds_stn_req, packet_data_handle, pdh); ++ qmi_set_wds_stop_network_request(msg, &wds_stn_req); ++ return QMI_CMD_REQUEST; ++} ++ + static void + cmd_wds_get_packet_service_status_cb(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg) + { +--- a/commands-wds.h ++++ b/commands-wds.h +@@ -4,6 +4,7 @@ + __uqmi_command(wds_set_username, username, required, CMD_TYPE_OPTION), \ + __uqmi_command(wds_set_password, password, required, CMD_TYPE_OPTION), \ + __uqmi_command(wds_set_autoconnect, autoconnect, no, CMD_TYPE_OPTION), \ ++ __uqmi_command(wds_stop_network, stop-network, required, QMI_SERVICE_WDS), \ + __uqmi_command(wds_get_packet_service_status, get-data-status, no, QMI_SERVICE_WDS), \ + __uqmi_command(wds_reset, reset-wds, no, QMI_SERVICE_WDS) \ + +@@ -14,5 +15,7 @@ + " --username <name>: Use network username\n" \ + " --password <password>: Use network password\n" \ + " --autoconnect: Enable automatic connect/reconnect\n" \ ++ " --stop-network <pdh>: Stop network connection (use with option below)\n" \ ++ " --autoconnect: Disable automatic connect/reconnect\n" \ + " --get-data-status: Get current data access status\n" \ + diff --git a/package/network/utils/uqmi/patches/004-fix-service-versions.patch b/package/network/utils/uqmi/patches/004-fix-service-versions.patch new file mode 100644 index 0000000..7f99f58 --- /dev/null +++ b/package/network/utils/uqmi/patches/004-fix-service-versions.patch @@ -0,0 +1,23 @@ +--- a/commands.c ++++ b/commands.c +@@ -20,16 +20,20 @@ static void no_cb(struct qmi_dev *qmi, s + static void cmd_version_cb(struct qmi_dev *qmi, struct qmi_request *req, struct qmi_msg *msg) + { + struct qmi_ctl_get_version_info_response res; ++ void *c; + char name_buf[16]; + int i; + + qmi_parse_ctl_get_version_info_response(msg, &res); ++ ++ c = blobmsg_open_table(&status, NULL); + for (i = 0; i < res.data.service_list_n; i++) { + sprintf(name_buf, "service_%d", res.data.service_list[i].service); + blobmsg_printf(&status, name_buf, "%d,%d", + res.data.service_list[i].major_version, + res.data.service_list[i].minor_version); + } ++ blobmsg_close_table(&status, c); + } + + static enum qmi_cmd_result _______________________________________________ openwrt-devel mailing list openwrt-devel@lists.openwrt.org https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel