diff --git a/application/modem_scan/Makefile b/application/modem_scan/Makefile new file mode 100644 index 00000000..29c62a2c --- /dev/null +++ b/application/modem_scan/Makefile @@ -0,0 +1,41 @@ +include $(TOPDIR)/rules.mk + +# Include unified version +include ../../version.mk + +PKG_NAME:=modem_scan +PKG_RELEASE:=$(QMODEM_RELEASE) +PKG_VERSION:=$(QMODEM_VERSION) + +include $(INCLUDE_DIR)/package.mk + +define Package/$(PKG_NAME) + SECTION:=utils + CATEGORY:=Utilities + TITLE:=QModem scan daemon and client + DEPENDS:=+libjson-c +tom_modem +sms-tool_q +ubus-at-daemon +endef + +define Package/$(PKG_NAME)/description + C daemon and client for QModem device scan event scheduling. +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) src/* $(PKG_BUILD_DIR)/ +endef + +define Build/Compile + $(MAKE) -C $(PKG_BUILD_DIR) \ + $(TARGET_CONFIGURE_OPTS) \ + CFLAGS="$(TARGET_CFLAGS) -Wall -Wextra -std=c99 -D_GNU_SOURCE -I$(STAGING_DIR)/usr/include -I$(STAGING_DIR)/usr/include/json-c" \ + LDFLAGS="$(TARGET_LDFLAGS) -ljson-c -lpthread" +endef + +define Package/$(PKG_NAME)/install + $(INSTALL_DIR) $(1)/usr/bin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/modem_scand $(1)/usr/bin/ + $(INSTALL_BIN) $(PKG_BUILD_DIR)/modem_scanc $(1)/usr/bin/ +endef + +$(eval $(call BuildPackage,$(PKG_NAME))) diff --git a/application/modem_scan/src/Makefile b/application/modem_scan/src/Makefile new file mode 100644 index 00000000..bf3e5ac9 --- /dev/null +++ b/application/modem_scan/src/Makefile @@ -0,0 +1,29 @@ +CFLAGS ?= -Wall -Wextra -std=c99 -D_GNU_SOURCE +CFLAGS += -I$(STAGING_DIR)/usr/include +CFLAGS += -I$(STAGING_DIR)/usr/include/json-c +LDFLAGS ?= +LDFLAGS += -ljson-c -lpthread + +TARGETS = modem_scand modem_scanc + +.PHONY: all clean install + +all: $(TARGETS) + +modem_scand: modem_scand.o + $(CC) $(CFLAGS) $^ -o $@ $(LDFLAGS) + $(STRIP) $@ + +modem_scanc: modem_scanc.o + $(CC) $(CFLAGS) $^ -o $@ + $(STRIP) $@ + +%.o: %.c modem_scan_common.h + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f *.o $(TARGETS) + +install: all + install -D modem_scand $(DESTDIR)/usr/bin/modem_scand + install -D modem_scanc $(DESTDIR)/usr/bin/modem_scanc diff --git a/application/modem_scan/src/modem_scan_common.h b/application/modem_scan/src/modem_scan_common.h new file mode 100644 index 00000000..f62aee4f --- /dev/null +++ b/application/modem_scan/src/modem_scan_common.h @@ -0,0 +1,12 @@ +#ifndef QMODEM_MODEM_SCAN_COMMON_H +#define QMODEM_MODEM_SCAN_COMMON_H + +#define QMODEM_SCAND_SOCKET "/var/run/qmodem/modem_scand.sock" +#define QMODEM_RUN_DIR "/var/run/qmodem" +#define QMODEM_SUPPORT_JSON "/usr/share/qmodem/modem_support.json" +#define QMODEM_PORT_RULE_JSON "/usr/share/qmodem/modem_port_rule.json" + +#define QMODEM_MAX_LINE 512 +#define QMODEM_MAX_REPLY 4096 + +#endif diff --git a/application/modem_scan/src/modem_scanc.c b/application/modem_scan/src/modem_scanc.c new file mode 100644 index 00000000..c736c19d --- /dev/null +++ b/application/modem_scan/src/modem_scanc.c @@ -0,0 +1,111 @@ +#include "modem_scan_common.h" + +#include +#include +#include +#include +#include +#include +#include + +static void usage(FILE *out) +{ + fprintf(out, + "Usage:\n" + " modem_scanc add [delay_seconds]\n" + " modem_scanc remove
[delay_seconds]\n" + " modem_scanc disable [delay_seconds]\n" + " modem_scanc scan [usb|pcie|all] [delay_seconds]\n" + " modem_scanc set-log-level \n" + " modem_scanc status\n"); +} + +static int send_request(const char *line) +{ + int fd; + struct sockaddr_un addr; + char reply[QMODEM_MAX_REPLY]; + ssize_t n; + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) { + perror("socket"); + return 1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", QMODEM_SCAND_SOCKET); + + for (int i = 0; i < 20; i++) { + if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == 0) + break; + if (i == 19) { + fprintf(stderr, "modem_scand unavailable: %s\n", strerror(errno)); + close(fd); + return 2; + } + usleep(100000); + } + + if (write(fd, line, strlen(line)) < 0 || write(fd, "\n", 1) < 0) { + perror("write"); + close(fd); + return 1; + } + + while ((n = read(fd, reply, sizeof(reply) - 1)) > 0) { + reply[n] = '\0'; + fputs(reply, stdout); + } + + close(fd); + return 0; +} + +int main(int argc, char **argv) +{ + char line[QMODEM_MAX_LINE]; + + if (argc < 2) { + usage(stderr); + return 1; + } + + if (!strcmp(argv[1], "add")) { + if (argc != 4 && argc != 5) { + usage(stderr); + return 1; + } + snprintf(line, sizeof(line), "add %s %s %s", argv[2], argv[3], argc == 5 ? argv[4] : "0"); + } else if (!strcmp(argv[1], "remove")) { + if (argc != 3 && argc != 4) { + usage(stderr); + return 1; + } + snprintf(line, sizeof(line), "remove %s %s", argv[2], argc == 4 ? argv[3] : "0"); + } else if (!strcmp(argv[1], "disable")) { + if (argc != 3 && argc != 4) { + usage(stderr); + return 1; + } + snprintf(line, sizeof(line), "disable %s %s", argv[2], argc == 4 ? argv[3] : "0"); + } else if (!strcmp(argv[1], "scan")) { + const char *type = argc >= 3 ? argv[2] : "all"; + const char *delay = argc >= 4 ? argv[3] : "0"; + snprintf(line, sizeof(line), "scan %s %s", type, delay); + } else if (!strcmp(argv[1], "set-log-level")) { + if (argc != 3) { + usage(stderr); + return 1; + } + snprintf(line, sizeof(line), "set-log-level %s", argv[2]); + } else if (!strcmp(argv[1], "status")) { + snprintf(line, sizeof(line), "status"); + } else { + usage(stderr); + return 1; + } + + return send_request(line); +} diff --git a/application/modem_scan/src/modem_scand.c b/application/modem_scan/src/modem_scand.c new file mode 100644 index 00000000..6f47a475 --- /dev/null +++ b/application/modem_scan/src/modem_scand.c @@ -0,0 +1,1653 @@ +#include "modem_scan_common.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum event_type { + EV_ADD, + EV_REMOVE, + EV_DISABLE, + EV_SCAN, +}; + +enum log_level { + LOG_L_DEBUG = 0, + LOG_L_INFO, + LOG_L_NOTICE, + LOG_L_WARN, + LOG_L_ERR, +}; + +struct str_list { + char **items; + size_t len; + size_t cap; +}; + +struct event { + enum event_type type; + char slot[128]; + char slot_type[16]; + char section[128]; + char key[256]; + time_t not_before; + int attempts; + struct event *next; +}; + +struct port_rule { + int has_rule; + int option_driver; + struct str_list include; +}; + +struct modem_profile { + char name[128]; + char manufacturer[64]; + char platform[64]; + char pdp_index[32]; + char wcdma_band[512]; + char lte_band[512]; + char nsa_band[512]; + char sa_band[512]; + struct str_list modes; +}; + +struct port_candidate { + char dev[128]; + int is_pcie; +}; + +struct scan_result { + struct str_list net_devices; + struct str_list at_ports; + struct str_list pcie_at_ports; + struct str_list valid_at_ports; + char preferred_at[128]; + char modem_path[256]; + char vid[16]; + char pid[16]; + int option_driver; +}; + +struct daemon_state { + pthread_mutex_t queue_lock; + pthread_cond_t queue_cond; + struct event *queue_head; + struct event *queue_tail; + struct str_list active_keys; + int queue_len; + int active_jobs; + int stop; + int scan_workers; + int at_probe_workers; + int at_timeout_fast; + int at_timeout_model; + int add_retry_delay; + int add_retry_max; + enum log_level log_level; + json_object *support_json; + json_object *port_rule_json; +}; + +static struct daemon_state g; +static pthread_mutex_t uci_lock = PTHREAD_MUTEX_INITIALIZER; + +static void sl_init(struct str_list *l) +{ + memset(l, 0, sizeof(*l)); +} + +static void sl_free(struct str_list *l) +{ + for (size_t i = 0; i < l->len; i++) + free(l->items[i]); + free(l->items); + memset(l, 0, sizeof(*l)); +} + +static int sl_contains(const struct str_list *l, const char *s) +{ + for (size_t i = 0; i < l->len; i++) { + if (!strcmp(l->items[i], s)) + return 1; + } + return 0; +} + +static int sl_add(struct str_list *l, const char *s) +{ + char **n; + + if (!s || !*s || sl_contains(l, s)) + return 0; + if (l->len == l->cap) { + size_t cap = l->cap ? l->cap * 2 : 8; + n = realloc(l->items, cap * sizeof(*n)); + if (!n) + return -1; + l->items = n; + l->cap = cap; + } + l->items[l->len] = strdup(s); + if (!l->items[l->len]) + return -1; + l->len++; + return 0; +} + +static void sl_truncate(struct str_list *l, size_t len) +{ + if (len >= l->len) + return; + for (size_t i = len; i < l->len; i++) + free(l->items[i]); + l->len = len; +} + +static void log_msg(enum log_level lvl, const char *fmt, ...) +{ + int prio = LOG_INFO; + va_list ap; + + if (lvl < g.log_level) + return; + switch (lvl) { + case LOG_L_DEBUG: prio = LOG_DEBUG; break; + case LOG_L_INFO: prio = LOG_INFO; break; + case LOG_L_NOTICE: prio = LOG_NOTICE; break; + case LOG_L_WARN: prio = LOG_WARNING; break; + case LOG_L_ERR: prio = LOG_ERR; break; + } + + va_start(ap, fmt); + vsyslog(prio, fmt, ap); + va_end(ap); +} + +static enum log_level parse_log_level(const char *s) +{ + if (!s) + return LOG_L_INFO; + if (!strcmp(s, "debug")) return LOG_L_DEBUG; + if (!strcmp(s, "notice")) return LOG_L_NOTICE; + if (!strcmp(s, "warn") || !strcmp(s, "warning")) return LOG_L_WARN; + if (!strcmp(s, "err") || !strcmp(s, "error")) return LOG_L_ERR; + return LOG_L_INFO; +} + +static void trim(char *s) +{ + size_t n; + while (*s && isspace((unsigned char)*s)) + memmove(s, s + 1, strlen(s)); + n = strlen(s); + while (n && isspace((unsigned char)s[n - 1])) + s[--n] = '\0'; +} + +static int read_file_trim(const char *path, char *buf, size_t len) +{ + int fd; + ssize_t n; + + if (!len) + return -1; + fd = open(path, O_RDONLY); + if (fd < 0) + return -1; + n = read(fd, buf, len - 1); + close(fd); + if (n < 0) + return -1; + buf[n] = '\0'; + trim(buf); + return 0; +} + +static int path_exists(const char *path) +{ + struct stat st; + return stat(path, &st) == 0; +} + +static int is_dir(const char *path) +{ + struct stat st; + return stat(path, &st) == 0 && S_ISDIR(st.st_mode); +} + +static int capture_exec(char *const argv[], char *out, size_t out_len, int timeout_sec) +{ + int pipefd[2]; + pid_t pid; + size_t used = 0; + int status = 0; + time_t start; + + if (out_len) + out[0] = '\0'; + if (pipe(pipefd) < 0) + return -1; + + pid = fork(); + if (pid < 0) { + close(pipefd[0]); + close(pipefd[1]); + return -1; + } + if (pid == 0) { + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + close(pipefd[0]); + close(pipefd[1]); + execvp(argv[0], argv); + _exit(127); + } + + close(pipefd[1]); + fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL, 0) | O_NONBLOCK); + start = time(NULL); + + for (;;) { + fd_set rfds; + struct timeval tv = { .tv_sec = 0, .tv_usec = 100000 }; + char tmp[512]; + ssize_t n; + pid_t r; + + FD_ZERO(&rfds); + FD_SET(pipefd[0], &rfds); + if (select(pipefd[0] + 1, &rfds, NULL, NULL, &tv) > 0) { + while ((n = read(pipefd[0], tmp, sizeof(tmp))) > 0) { + if (out && out_len > 1 && used < out_len - 1) { + size_t copy = (size_t)n; + if (copy > out_len - 1 - used) + copy = out_len - 1 - used; + memcpy(out + used, tmp, copy); + used += copy; + out[used] = '\0'; + } + } + } + + r = waitpid(pid, &status, WNOHANG); + if (r == pid) + break; + if (timeout_sec > 0 && time(NULL) - start >= timeout_sec) { + kill(pid, SIGTERM); + usleep(200000); + if (waitpid(pid, &status, WNOHANG) == 0) + kill(pid, SIGKILL); + waitpid(pid, &status, 0); + close(pipefd[0]); + return -2; + } + } + + while (1) { + char tmp[512]; + ssize_t n = read(pipefd[0], tmp, sizeof(tmp)); + if (n <= 0) + break; + if (out && out_len > 1 && used < out_len - 1) { + size_t copy = (size_t)n; + if (copy > out_len - 1 - used) + copy = out_len - 1 - used; + memcpy(out + used, tmp, copy); + used += copy; + out[used] = '\0'; + } + } + close(pipefd[0]); + + if (WIFEXITED(status)) + return WEXITSTATUS(status); + return -1; +} + +static int run_exec(char *const argv[], int timeout_sec) +{ + char buf[256]; + return capture_exec(argv, buf, sizeof(buf), timeout_sec); +} + +static int uci_get(const char *key, char *out, size_t out_len) +{ + char *argv[] = { "uci", "-q", "get", (char *)key, NULL }; + int rc = capture_exec(argv, out, out_len, 3); + if (!rc) + trim(out); + return rc; +} + +static int uci_set(const char *key, const char *value) +{ + char arg[768]; + char *argv[] = { "uci", "-q", "set", arg, NULL }; + snprintf(arg, sizeof(arg), "%s=%s", key, value ? value : ""); + return run_exec(argv, 3); +} + +static int uci_del(const char *key) +{ + char *argv[] = { "uci", "-q", "del", (char *)key, NULL }; + return run_exec(argv, 3); +} + +static int uci_add_list(const char *key, const char *value) +{ + char arg[768]; + char *argv[] = { "uci", "-q", "add_list", arg, NULL }; + snprintf(arg, sizeof(arg), "%s=%s", key, value ? value : ""); + return run_exec(argv, 3); +} + +static void uci_commit(const char *config) +{ + char *argv[] = { "uci", "commit", (char *)config, NULL }; + run_exec(argv, 5); +} + +static int find_slot_section(const char *slot, char *section, size_t len) +{ + char out[16384]; + char *argv[] = { "uci", "-q", "show", "qmodem", NULL }; + char *save = NULL, *line; + int rc; + + section[0] = '\0'; + rc = capture_exec(argv, out, sizeof(out), 3); + if (rc != 0) + return -1; + + for (line = strtok_r(out, "\n", &save); line; line = strtok_r(NULL, "\n", &save)) { + char prefix[160], value[160]; + char *p; + + if (strncmp(line, "qmodem.", 7)) + continue; + p = strstr(line, ".slot="); + if (!p) + continue; + snprintf(value, sizeof(value), "%s", p + 6); + trim(value); + if ((value[0] == '\'' || value[0] == '"') && strlen(value) >= 2) { + size_t n = strlen(value); + memmove(value, value + 1, n); + value[n - 2] = '\0'; + } + if (strcmp(value, slot)) + continue; + *p = '\0'; + snprintf(prefix, sizeof(prefix), "%s", line + 7); + snprintf(section, len, "%s", prefix); + return 0; + } + + return -1; +} + +static void get_slot_option(const char *slot, const char *opt, char *out, size_t len) +{ + char section[128], key[256]; + out[0] = '\0'; + if (find_slot_section(slot, section, sizeof(section)) != 0) + return; + snprintf(key, sizeof(key), "qmodem.%s.%s", section, opt); + uci_get(key, out, len); +} + +static void section_from_slot(const char *slot, char *out, size_t len) +{ + size_t j = 0; + for (size_t i = 0; slot[i] && j + 1 < len; i++) { + char c = slot[i]; + out[j++] = (c == '.' || c == ':' || c == '-') ? '_' : c; + } + out[j] = '\0'; +} + +static int basename_of(const char *path, char *out, size_t len) +{ + const char *p = strrchr(path, '/'); + snprintf(out, len, "%s", p ? p + 1 : path); + return 0; +} + +static int readlink_basename(const char *path, char *out, size_t len) +{ + char buf[512]; + ssize_t n = readlink(path, buf, sizeof(buf) - 1); + if (n < 0) + return -1; + buf[n] = '\0'; + return basename_of(buf, out, len); +} + +static void list_net_devices(const char *net_path, struct str_list *nets) +{ + DIR *d = opendir(net_path); + struct dirent *de; + if (!d) + return; + while ((de = readdir(d))) { + if (de->d_name[0] == '.') + continue; + sl_add(nets, de->d_name); + } + closedir(d); +} + +static void list_child_matching(const char *path, const char *prefix, struct str_list *out) +{ + DIR *d = opendir(path); + struct dirent *de; + size_t n = strlen(prefix); + if (!d) + return; + while ((de = readdir(d))) { + if (!strncmp(de->d_name, prefix, n)) + sl_add(out, de->d_name); + } + closedir(d); +} + +static int json_get_obj(json_object *root, const char *key, json_object **out) +{ + return root && json_object_object_get_ex(root, key, out); +} + +static const char *json_get_string_default(json_object *obj, const char *key, const char *def) +{ + json_object *v; + if (json_get_obj(obj, key, &v) && json_object_is_type(v, json_type_string)) + return json_object_get_string(v); + return def; +} + +static int json_get_int_default(json_object *obj, const char *key, int def) +{ + json_object *v; + if (json_get_obj(obj, key, &v)) + return json_object_get_int(v); + return def; +} + +static int load_port_rule(const char *vid, const char *pid, struct port_rule *rule) +{ + json_object *a, *usb, *obj, *include; + char id[32]; + memset(rule, 0, sizeof(*rule)); + sl_init(&rule->include); + if (!vid[0] || !pid[0]) + return 0; + snprintf(id, sizeof(id), "%s:%s", vid, pid); + if (!json_get_obj(g.port_rule_json, "modem_port_rule", &a) || + !json_get_obj(a, "usb", &usb) || + !json_get_obj(usb, id, &obj)) + return 0; + rule->has_rule = 1; + rule->option_driver = json_get_int_default(obj, "option_driver", 0); + if (json_get_obj(obj, "include", &include) && json_object_is_type(include, json_type_array)) { + int n = json_object_array_length(include); + for (int i = 0; i < n; i++) { + json_object *v = json_object_array_get_idx(include, i); + if (v) + sl_add(&rule->include, json_object_get_string(v)); + } + } + return 0; +} + +static int interface_allowed(const struct port_rule *rule, const char *if_port) +{ + if (!rule->include.len) + return 1; + return sl_contains(&rule->include, if_port); +} + +static void apply_option_driver(const char *vid, const char *pid) +{ + int fd; + char line[64]; + if (!vid[0] || !pid[0]) + return; + fd = open("/sys/bus/usb-serial/drivers/option1/new_id", O_WRONLY); + if (fd < 0) { + log_msg(LOG_L_DEBUG, "option new_id unavailable for %s:%s", vid, pid); + return; + } + snprintf(line, sizeof(line), "%s %s\n", vid, pid); + if (write(fd, line, strlen(line)) < 0) + log_msg(LOG_L_WARN, "failed to bind option driver for %s:%s: %s", vid, pid, strerror(errno)); + close(fd); +} + +static void scan_usb_slot(const char *slot, struct scan_result *res) +{ + char slot_path[256], path[512], driver_path[512], driver[128]; + char vid_path[512], pid_path[512]; + struct port_rule rule; + DIR *d; + struct dirent *de; + + snprintf(slot_path, sizeof(slot_path), "/sys/bus/usb/devices/%s", slot); + if (!is_dir(slot_path)) + return; + snprintf(res->modem_path, sizeof(res->modem_path), "/sys/bus/usb/devices/%s/", slot); + snprintf(vid_path, sizeof(vid_path), "%s/idVendor", slot_path); + snprintf(pid_path, sizeof(pid_path), "%s/idProduct", slot_path); + read_file_trim(vid_path, res->vid, sizeof(res->vid)); + read_file_trim(pid_path, res->pid, sizeof(res->pid)); + load_port_rule(res->vid, res->pid, &rule); + if (rule.option_driver) { + res->option_driver = 1; + apply_option_driver(res->vid, res->pid); + } + + d = opendir(slot_path); + if (!d) { + sl_free(&rule.include); + return; + } + while ((de = readdir(d))) { + const char *suffix; + char if_port[32] = ""; + if (de->d_name[0] == '.') + continue; + if (strncmp(de->d_name, slot, strlen(slot)) || de->d_name[strlen(slot)] != ':') + continue; + suffix = strrchr(de->d_name, ':'); + if (suffix) + snprintf(if_port, sizeof(if_port), "%s", suffix + 1); + if (!interface_allowed(&rule, if_port)) { + log_msg(LOG_L_DEBUG, "skip usb %s interface %s by modem_port_rule", slot, if_port); + continue; + } + snprintf(driver_path, sizeof(driver_path), "%s/%s/driver", slot_path, de->d_name); + if (!path_exists(driver_path) || readlink_basename(driver_path, driver, sizeof(driver)) < 0) + continue; + + snprintf(path, sizeof(path), "%s/%s", slot_path, de->d_name); + if (!strcmp(driver, "option") || !strcmp(driver, "cdc_acm") || + !strcmp(driver, "qcserial") || !strcmp(driver, "usbserial_generic") || + !strcmp(driver, "usbserial")) { + struct str_list ttys; + sl_init(&ttys); + list_child_matching(path, "ttyUSB", &ttys); + list_child_matching(path, "ttyACM", &ttys); + for (size_t i = 0; i < ttys.len; i++) { + char dev[160]; + snprintf(dev, sizeof(dev), "/dev/%s", ttys.items[i]); + sl_add(&res->at_ports, dev); + } + sl_free(&ttys); + } else if (!strncmp(driver, "qmi_wwan", 8) || !strcmp(driver, "cdc_mbim") || + strstr(driver, "cdc_ncm") || !strcmp(driver, "cdc_ether") || + !strcmp(driver, "rndis_host")) { + char net_path[512]; + snprintf(net_path, sizeof(net_path), "%s/net", path); + list_net_devices(net_path, &res->net_devices); + } + } + closedir(d); + sl_free(&rule.include); +} + +static void scan_associated_usb(const char *pcie_slot, struct scan_result *res) +{ + char usb_slot[128]; + char modem_path[sizeof(res->modem_path)]; + size_t net_len = res->net_devices.len; + + get_slot_option(pcie_slot, "associated_usb", usb_slot, sizeof(usb_slot)); + if (usb_slot[0]) { + snprintf(modem_path, sizeof(modem_path), "%s", res->modem_path); + scan_usb_slot(usb_slot, res); + snprintf(res->modem_path, sizeof(res->modem_path), "%s", modem_path); + sl_truncate(&res->net_devices, net_len); + } +} + +static void scan_pcie_slot(const char *slot, struct scan_result *res) +{ + char slot_path[256], path[512], short_slot[128]; + DIR *d; + struct dirent *de; + + snprintf(slot_path, sizeof(slot_path), "/sys/bus/pci/devices/%s", slot); + if (!is_dir(slot_path)) + return; + snprintf(res->modem_path, sizeof(res->modem_path), "/sys/bus/pci/devices/%s/", slot); + + if (strlen(slot) > 4) { + snprintf(short_slot, sizeof(short_slot), "%s", slot + 2); + if (strlen(short_slot) > 2) + short_slot[strlen(short_slot) - 2] = '\0'; + for (char *p = short_slot; *p; p++) { + if (*p == ':') + *p = '.'; + } + } else { + snprintf(short_slot, sizeof(short_slot), "%s", slot); + } + + d = opendir(slot_path); + if (d) { + while ((de = readdir(d))) { + char driver_path[512], driver[128]; + if (!strstr(de->d_name, short_slot)) + continue; + snprintf(driver_path, sizeof(driver_path), "%s/%s/driver", slot_path, de->d_name); + if (!path_exists(driver_path) || readlink_basename(driver_path, driver, sizeof(driver)) < 0) + continue; + if (!strcmp(driver, "mhi_netdev")) { + snprintf(path, sizeof(path), "%s/%s/net", slot_path, de->d_name); + list_net_devices(path, &res->net_devices); + } else if (!strcmp(driver, "mhi_uci_q")) { + struct str_list duns; + snprintf(path, sizeof(path), "%s/%s/mhi_uci_q", slot_path, de->d_name); + sl_init(&duns); + list_child_matching(path, "mhi_DUN", &duns); + for (size_t i = 0; i < duns.len; i++) { + char dev[160]; + snprintf(dev, sizeof(dev), "/dev/%s", duns.items[i]); + sl_add(&res->at_ports, dev); + sl_add(&res->pcie_at_ports, dev); + } + sl_free(&duns); + } + } + closedir(d); + } + + snprintf(path, sizeof(path), "%s/mhi0/wwan/wwan0", slot_path); + if (is_dir(path)) { + struct str_list ats; + sl_init(&ats); + list_child_matching(path, "wwan0at", &ats); + for (size_t i = 0; i < ats.len; i++) { + char dev[160]; + snprintf(dev, sizeof(dev), "/dev/%s", ats.items[i]); + sl_add(&res->at_ports, dev); + sl_add(&res->pcie_at_ports, dev); + } + sl_free(&ats); + } + + snprintf(path, sizeof(path), "%s/wwan", slot_path); + if (is_dir(path)) { + DIR *wd = opendir(path); + if (wd) { + while ((de = readdir(wd))) { + char wwan_path[512]; + if (strncmp(de->d_name, "wwan", 4)) + continue; + sl_add(&res->net_devices, de->d_name); + snprintf(wwan_path, sizeof(wwan_path), "%s/%s", path, de->d_name); + struct str_list ats; + sl_init(&ats); + list_child_matching(wwan_path, de->d_name, &ats); + for (size_t i = 0; i < ats.len; i++) { + if (strstr(ats.items[i], "at")) { + char dev[160]; + snprintf(dev, sizeof(dev), "/dev/%s", ats.items[i]); + sl_add(&res->at_ports, dev); + sl_add(&res->pcie_at_ports, dev); + } + } + sl_free(&ats); + } + closedir(wd); + } + } +} + +static int at_command(const char *port, const char *cmd, int fast, int timeout, char *out, size_t out_len) +{ + int pipefd[2]; + pid_t pid; + size_t used = 0; + int status = 0; + time_t start; + const char *script = + ". /usr/share/qmodem/modem_util.sh; " + "if [ \"$1\" = 1 ]; then " + "fastat \"$2\" \"$3\"; " + "else at \"$2\" \"$3\"; fi"; + + if (out_len) + out[0] = '\0'; + if (pipe(pipefd) < 0) + return -1; + + pid = fork(); + if (pid < 0) { + close(pipefd[0]); + close(pipefd[1]); + return -1; + } + if (pid == 0) { + setpgid(0, 0); + dup2(pipefd[1], STDOUT_FILENO); + dup2(pipefd[1], STDERR_FILENO); + close(pipefd[0]); + close(pipefd[1]); + execl("/bin/sh", "sh", "-c", script, "qmodem-at", fast ? "1" : "0", port, cmd, NULL); + _exit(127); + } + + close(pipefd[1]); + fcntl(pipefd[0], F_SETFL, fcntl(pipefd[0], F_GETFL, 0) | O_NONBLOCK); + start = time(NULL); + for (;;) { + fd_set rfds; + struct timeval tv = { .tv_sec = 0, .tv_usec = 100000 }; + char tmp[512]; + ssize_t n; + pid_t r; + + FD_ZERO(&rfds); + FD_SET(pipefd[0], &rfds); + if (select(pipefd[0] + 1, &rfds, NULL, NULL, &tv) > 0) { + while ((n = read(pipefd[0], tmp, sizeof(tmp))) > 0) { + if (out_len > 1 && used < out_len - 1) { + size_t copy = (size_t)n; + if (copy > out_len - 1 - used) + copy = out_len - 1 - used; + memcpy(out + used, tmp, copy); + used += copy; + out[used] = '\0'; + } + } + } + r = waitpid(pid, &status, WNOHANG); + if (r == pid) + break; + if (timeout > 0 && time(NULL) - start >= timeout) { + kill(-pid, SIGTERM); + usleep(200000); + if (waitpid(pid, &status, WNOHANG) == 0) + kill(-pid, SIGKILL); + waitpid(pid, &status, 0); + close(pipefd[0]); + log_msg(LOG_L_WARN, "AT timeout port=%s cmd=%s", port, cmd); + return -2; + } + } + close(pipefd[0]); + return WIFEXITED(status) ? WEXITSTATUS(status) : -1; +} + +struct probe_ctx { + const char *port; + int ok; +}; + +static void *probe_port_thread(void *arg) +{ + struct probe_ctx *ctx = arg; + char res[QMODEM_MAX_REPLY]; + ctx->ok = 0; + if (!path_exists(ctx->port)) + return NULL; + { + char json[256]; + char *argv[] = { "ubus", "call", "at-daemon", "close", json, NULL }; + snprintf(json, sizeof(json), "{ \"at_port\": \"%s\" }", ctx->port); + run_exec(argv, 2); + } + if (at_command(ctx->port, "ATI", 1, g.at_timeout_fast, res, sizeof(res)) == 0 && + (strstr(res, "OK") || strstr(res, "ATI"))) + ctx->ok = 1; + return NULL; +} + +static void validate_ports(struct scan_result *res) +{ + size_t n = res->at_ports.len; + pthread_t *threads = calloc(n, sizeof(*threads)); + struct probe_ctx *ctx = calloc(n, sizeof(*ctx)); + if (!threads || !ctx) { + free(threads); + free(ctx); + return; + } + for (size_t start = 0; start < n; start += (size_t)g.at_probe_workers) { + size_t end = start + (size_t)g.at_probe_workers; + if (end > n) + end = n; + for (size_t i = start; i < end; i++) { + ctx[i].port = res->at_ports.items[i]; + pthread_create(&threads[i], NULL, probe_port_thread, &ctx[i]); + } + for (size_t i = start; i < end; i++) { + pthread_join(threads[i], NULL); + if (ctx[i].ok) { + sl_add(&res->valid_at_ports, res->at_ports.items[i]); + if (!res->preferred_at[0]) + snprintf(res->preferred_at, sizeof(res->preferred_at), "%s", res->at_ports.items[i]); + } + } + } + for (size_t i = 0; i < res->pcie_at_ports.len; i++) { + if (sl_contains(&res->valid_at_ports, res->pcie_at_ports.items[i])) { + snprintf(res->preferred_at, sizeof(res->preferred_at), "%s", res->pcie_at_ports.items[i]); + break; + } + } + free(threads); + free(ctx); +} + +static void normalize_model_name(char *name, size_t len) +{ + char lower[256]; + size_t j = 0; + for (size_t i = 0; name[i] && j + 1 < sizeof(lower); i++) + lower[j++] = (char)tolower((unsigned char)name[i]); + lower[j] = '\0'; + + if (strstr(lower, "nl668")) snprintf(name, len, "nl668"); + else if (strstr(lower, "nl678")) snprintf(name, len, "nl678"); + else if (strstr(lower, "em120k")) snprintf(name, len, "em120k"); + else if (strstr(lower, "fm350-gl")) snprintf(name, len, "fm350-gl"); + else if (strstr(lower, "fm190w-gl")) snprintf(name, len, "fm190w-gl"); + else if (strstr(lower, "rm500u-ea")) snprintf(name, len, "rm500u-ea"); + else if (strstr(lower, "mv31-w") || strstr(lower, "t99w175")) snprintf(name, len, "t99w175"); + else if (strstr(lower, "t99w373")) snprintf(name, len, "t99w373"); + else if (strstr(lower, "dp25-42843-47")) snprintf(name, len, "t99w640"); + else if (strstr(lower, "sim8380g")) snprintf(name, len, "SIM8380G-M2"); + else if (strstr(lower, "rg200u-cn")) snprintf(name, len, "rg200u-cn"); + else if (strstr(lower, "nu313-m2")) snprintf(name, len, "srm821"); + else if (strstr(lower, "m601")) snprintf(name, len, "n510m"); + else snprintf(name, len, "%s", lower); +} + +static int load_profile(const char *slot_type, const char *name, struct modem_profile *profile) +{ + json_object *support, *type_obj, *obj, *modes; + memset(profile, 0, sizeof(*profile)); + sl_init(&profile->modes); + if (!json_get_obj(g.support_json, "modem_support", &support) || + !json_get_obj(support, slot_type, &type_obj) || + !json_get_obj(type_obj, name, &obj)) + return -1; + snprintf(profile->name, sizeof(profile->name), "%s", name); + snprintf(profile->manufacturer, sizeof(profile->manufacturer), "%s", json_get_string_default(obj, "manufacturer", "")); + snprintf(profile->platform, sizeof(profile->platform), "%s", json_get_string_default(obj, "platform", "")); + snprintf(profile->pdp_index, sizeof(profile->pdp_index), "%s", json_get_string_default(obj, "pdp_index", "")); + snprintf(profile->wcdma_band, sizeof(profile->wcdma_band), "%s", json_get_string_default(obj, "wcdma_band", "")); + snprintf(profile->lte_band, sizeof(profile->lte_band), "%s", json_get_string_default(obj, "lte_band", "")); + snprintf(profile->nsa_band, sizeof(profile->nsa_band), "%s", json_get_string_default(obj, "nsa_band", "")); + snprintf(profile->sa_band, sizeof(profile->sa_band), "%s", json_get_string_default(obj, "sa_band", "")); + if (json_get_obj(obj, "modes", &modes) && json_object_is_type(modes, json_type_array)) { + int n = json_object_array_length(modes); + for (int i = 0; i < n; i++) { + json_object *v = json_object_array_get_idx(modes, i); + if (v) + sl_add(&profile->modes, json_object_get_string(v)); + } + } + return 0; +} + +static int match_profile_by_id(const char *slot_type, const char *vid, const char *pid, struct modem_profile *profile) +{ + json_object *support, *type_obj; + char id[32]; + if (!vid[0] || !pid[0]) + return -1; + snprintf(id, sizeof(id), "%s:%s", vid, pid); + if (!json_get_obj(g.support_json, "modem_support", &support) || + !json_get_obj(support, slot_type, &type_obj)) + return -1; + json_object_object_foreach(type_obj, key, val) { + const char *cfg_id = json_get_string_default(val, "id", ""); + if (!strcmp(cfg_id, id)) + return load_profile(slot_type, key, profile); + } + return -1; +} + +static int extract_candidate_lines(const char *res, struct str_list *names) +{ + char *copy = strdup(res ? res : ""); + char *save = NULL; + char *line; + if (!copy) + return -1; + for (line = strtok_r(copy, "\n", &save); line; line = strtok_r(NULL, "\n", &save)) { + trim(line); + if (!*line || !strcmp(line, "OK") || !strncmp(line, "AT", 2)) + continue; + if (strstr(line, "+CGMM:")) { + char *p = strchr(line, ':'); + if (p) { + p++; + trim(p); + if (*p == '"') + p++; + char *q = strchr(p, '"'); + if (q) + *q = '\0'; + sl_add(names, p); + } + } else { + sl_add(names, line); + } + } + free(copy); + return 0; +} + +static int detect_profile(const char *slot_type, struct scan_result *res, struct modem_profile *profile) +{ + const char *cmds[] = { "AT+CGMM", "AT+CGMM?", "AT+GMM" }; + for (size_t i = 0; i < res->valid_at_ports.len; i++) { + for (size_t c = 0; c < sizeof(cmds) / sizeof(cmds[0]); c++) { + char reply[QMODEM_MAX_REPLY]; + struct str_list names; + sl_init(&names); + if (at_command(res->valid_at_ports.items[i], cmds[c], 0, g.at_timeout_model, reply, sizeof(reply)) != 0) { + sl_free(&names); + continue; + } + extract_candidate_lines(reply, &names); + for (size_t n = 0; n < names.len; n++) { + char name[128]; + snprintf(name, sizeof(name), "%s", names.items[n]); + normalize_model_name(name, sizeof(name)); + if (!load_profile(slot_type, name, profile)) { + sl_free(&names); + return 0; + } + } + sl_free(&names); + } + } + return match_profile_by_id(slot_type, res->vid, res->pid, profile); +} + +static void join_list(const struct str_list *l, char *out, size_t len) +{ + size_t used = 0; + out[0] = '\0'; + for (size_t i = 0; i < l->len; i++) { + int n = snprintf(out + used, used < len ? len - used : 0, "%s%s", i ? " " : "", l->items[i]); + if (n < 0) + return; + used += (size_t)n; + if (used >= len) + return; + } +} + +static void exec_post_init(const char *section) +{ + char *argv[] = { "/usr/share/qmodem/modem_hook.sh", (char *)section, "post_init", NULL }; + run_exec(argv, 60); +} + +static void reload_network(void) +{ + char *argv[] = { "/etc/init.d/qmodem_network", "reload", NULL }; + run_exec(argv, 60); +} + +static int add_modem(const char *slot, const char *slot_type) +{ + struct scan_result res; + struct modem_profile profile; + char section[128], key[256], existing[64], fixed[16]; + char orig_network[512] = "", orig_at[128] = "", orig_state[64] = "", orig_name[128] = ""; + char net_join[512], default_alias[128] = "", default_metric[32] = "", led_script[128] = ""; + int existed; + + memset(&res, 0, sizeof(res)); + sl_init(&res.net_devices); + sl_init(&res.at_ports); + sl_init(&res.pcie_at_ports); + sl_init(&res.valid_at_ports); + section_from_slot(slot, section, sizeof(section)); + + snprintf(key, sizeof(key), "qmodem.%s.fixed_device", section); + uci_get(key, fixed, sizeof(fixed)); + if (!strcmp(fixed, "1")) { + log_msg(LOG_L_INFO, "skip fixed device slot=%s section=%s", slot, section); + exec_post_init(section); + goto out_success; + } + + if (!strcmp(slot_type, "usb")) { + scan_usb_slot(slot, &res); + } else if (!strcmp(slot_type, "pcie")) { + scan_pcie_slot(slot, &res); + scan_associated_usb(slot, &res); + } else { + goto out_fail; + } + + if (!res.net_devices.len) { + log_msg(LOG_L_INFO, "slot=%s type=%s has no net device yet", slot, slot_type); + goto out_fail; + } + validate_ports(&res); + if (!res.valid_at_ports.len) { + log_msg(LOG_L_INFO, "slot=%s type=%s has no valid AT port yet ports=%zu", slot, slot_type, res.at_ports.len); + goto out_fail; + } + if (detect_profile(slot_type, &res, &profile) != 0) { + log_msg(LOG_L_WARN, "slot=%s type=%s modem profile not matched", slot, slot_type); + goto out_fail; + } + + join_list(&res.net_devices, net_join, sizeof(net_join)); + pthread_mutex_lock(&uci_lock); + snprintf(key, sizeof(key), "qmodem.%s", section); + existed = !uci_get(key, existing, sizeof(existing)) && existing[0]; + if (existed) { + snprintf(key, sizeof(key), "qmodem.%s.network", section); + uci_get(key, orig_network, sizeof(orig_network)); + snprintf(key, sizeof(key), "qmodem.%s.at_port", section); + uci_get(key, orig_at, sizeof(orig_at)); + snprintf(key, sizeof(key), "qmodem.%s.state", section); + uci_get(key, orig_state, sizeof(orig_state)); + snprintf(key, sizeof(key), "qmodem.%s.name", section); + uci_get(key, orig_name, sizeof(orig_name)); + snprintf(key, sizeof(key), "qmodem.%s.modes", section); uci_del(key); + snprintf(key, sizeof(key), "qmodem.%s.valid_at_ports", section); uci_del(key); + snprintf(key, sizeof(key), "qmodem.%s.tty_devices", section); uci_del(key); + snprintf(key, sizeof(key), "qmodem.%s.net_devices", section); uci_del(key); + snprintf(key, sizeof(key), "qmodem.%s.ports", section); uci_del(key); + snprintf(key, sizeof(key), "qmodem.%s.state", section); uci_set(key, "enabled"); + } else { + char modem_count_s[32] = "", metric[32]; + int modem_count = 0; + get_slot_option(slot, "alias", default_alias, sizeof(default_alias)); + get_slot_option(slot, "default_metric", default_metric, sizeof(default_metric)); + get_slot_option(slot, "led_script", led_script, sizeof(led_script)); + uci_get("qmodem.main.modem_count", modem_count_s, sizeof(modem_count_s)); + if (modem_count_s[0]) + modem_count = atoi(modem_count_s); + modem_count++; + snprintf(modem_count_s, sizeof(modem_count_s), "%d", modem_count); + uci_set("qmodem.main.modem_count", modem_count_s); + snprintf(key, sizeof(key), "qmodem.%s", section); uci_set(key, "modem-device"); + if (default_alias[0]) { snprintf(key, sizeof(key), "qmodem.%s.alias", section); uci_set(key, default_alias); } + if (led_script[0]) { snprintf(key, sizeof(key), "qmodem.%s.led_script", section); uci_set(key, led_script); } + snprintf(metric, sizeof(metric), "%d", modem_count + 10); + if (default_metric[0]) + snprintf(metric, sizeof(metric), "%s", default_metric); + snprintf(key, sizeof(key), "qmodem.%s.path", section); uci_set(key, res.modem_path); + snprintf(key, sizeof(key), "qmodem.%s.data_interface", section); uci_set(key, slot_type); + snprintf(key, sizeof(key), "qmodem.%s.enable_dial", section); uci_set(key, "1"); + snprintf(key, sizeof(key), "qmodem.%s.soft_reboot", section); uci_set(key, "1"); + snprintf(key, sizeof(key), "qmodem.%s.extend_prefix", section); uci_set(key, "1"); + snprintf(key, sizeof(key), "qmodem.%s.pdp_type", section); uci_set(key, "ipv4v6"); + snprintf(key, sizeof(key), "qmodem.%s.state", section); uci_set(key, "enabled"); + snprintf(key, sizeof(key), "qmodem.%s.metric", section); uci_set(key, metric); + } + + snprintf(key, sizeof(key), "qmodem.%s.name", section); uci_set(key, profile.name); + snprintf(key, sizeof(key), "qmodem.%s.network", section); uci_set(key, net_join); + snprintf(key, sizeof(key), "qmodem.%s.manufacturer", section); uci_set(key, profile.manufacturer); + snprintf(key, sizeof(key), "qmodem.%s.platform", section); uci_set(key, profile.platform); + snprintf(key, sizeof(key), "qmodem.%s.suggest_pdp_index", section); uci_set(key, profile.pdp_index); + if (profile.wcdma_band[0]) { snprintf(key, sizeof(key), "qmodem.%s.wcdma_band", section); uci_set(key, profile.wcdma_band); } + if (profile.lte_band[0]) { snprintf(key, sizeof(key), "qmodem.%s.lte_band", section); uci_set(key, profile.lte_band); } + if (profile.nsa_band[0]) { snprintf(key, sizeof(key), "qmodem.%s.nsa_band", section); uci_set(key, profile.nsa_band); } + if (profile.sa_band[0]) { snprintf(key, sizeof(key), "qmodem.%s.sa_band", section); uci_set(key, profile.sa_band); } + for (size_t i = 0; i < profile.modes.len; i++) { + snprintf(key, sizeof(key), "qmodem.%s.modes", section); + uci_add_list(key, profile.modes.items[i]); + } + for (size_t i = 0; i < res.valid_at_ports.len; i++) { + snprintf(key, sizeof(key), "qmodem.%s.valid_at_ports", section); + uci_add_list(key, res.valid_at_ports.items[i]); + } + snprintf(key, sizeof(key), "qmodem.%s.at_port", section); + uci_set(key, res.preferred_at[0] ? res.preferred_at : res.valid_at_ports.items[0]); + for (size_t i = 0; i < res.at_ports.len; i++) { + snprintf(key, sizeof(key), "qmodem.%s.ports", section); + uci_add_list(key, res.at_ports.items[i]); + } + if (res.option_driver) { + snprintf(key, sizeof(key), "qmodem.%s.option_driver", section); + uci_set(key, "1"); + } + uci_commit("qmodem"); + pthread_mutex_unlock(&uci_lock); + + { + char rundir[256]; + snprintf(rundir, sizeof(rundir), QMODEM_RUN_DIR "/%s_dir", section); + mkdir(QMODEM_RUN_DIR, 0755); + mkdir(rundir, 0755); + } + exec_post_init(section); + if (!existed || strcmp(orig_network, net_join) || strcmp(orig_at, res.preferred_at) || + strcmp(orig_state, "enabled") || strcmp(orig_name, profile.name)) + reload_network(); + + log_msg(LOG_L_INFO, "added modem section=%s name=%s type=%s ports=%zu valid=%zu", + section, profile.name, slot_type, res.at_ports.len, res.valid_at_ports.len); + sl_free(&profile.modes); +out_success: + sl_free(&res.net_devices); + sl_free(&res.at_ports); + sl_free(&res.pcie_at_ports); + sl_free(&res.valid_at_ports); + return 0; + +out_fail: + sl_free(&res.net_devices); + sl_free(&res.at_ports); + sl_free(&res.pcie_at_ports); + sl_free(&res.valid_at_ports); + return 1; +} + +static void remove_modem(const char *section) +{ + char key[256], existing[64], count_s[32]; + int count = 0; + snprintf(key, sizeof(key), "qmodem.%s", section); + if (uci_get(key, existing, sizeof(existing)) || !existing[0]) + return; + pthread_mutex_lock(&uci_lock); + uci_get("qmodem.main.modem_count", count_s, sizeof(count_s)); + if (count_s[0]) + count = atoi(count_s); + if (count > 0) + count--; + snprintf(count_s, sizeof(count_s), "%d", count); + uci_set("qmodem.main.modem_count", count_s); + snprintf(key, sizeof(key), "qmodem.%s", section); uci_del(key); + snprintf(key, sizeof(key), "network.%s", section); uci_del(key); + snprintf(key, sizeof(key), "network.%sv6", section); uci_del(key); + snprintf(key, sizeof(key), "dhcp.%s", section); uci_del(key); + uci_commit("network"); + uci_commit("dhcp"); + uci_commit("qmodem"); + pthread_mutex_unlock(&uci_lock); + log_msg(LOG_L_INFO, "removed modem section=%s", section); +} + +static void disable_slot(const char *slot) +{ + char section[128], key[256]; + char *argv_reorder[] = { "uci", "-q", "reorder", key, NULL }; + section_from_slot(slot, section, sizeof(section)); + pthread_mutex_lock(&uci_lock); + snprintf(key, sizeof(key), "qmodem.%s=1", section); + run_exec(argv_reorder, 3); + snprintf(key, sizeof(key), "qmodem.%s.state", section); + uci_set(key, "disabled"); + uci_commit("qmodem"); + pthread_mutex_unlock(&uci_lock); + log_msg(LOG_L_INFO, "disabled slot=%s section=%s", slot, section); +} + +static void scan_usb_all(void) +{ + DIR *d = opendir("/sys/class/net"); + struct dirent *de; + struct str_list slots; + sl_init(&slots); + if (!d) + return; + while ((de = readdir(d))) { + char dev_path[512], real[512], slot[128] = ""; + if (de->d_name[0] == '.') + continue; + if (strncmp(de->d_name, "usb", 3) && strncmp(de->d_name, "eth", 3) && strncmp(de->d_name, "wwan", 4)) + continue; + snprintf(dev_path, sizeof(dev_path), "/sys/class/net/%s/device", de->d_name); + if (!realpath(dev_path, real)) + continue; + if (!strstr(real, "usb")) + continue; + { + char tmp[512], *save = NULL, *tok; + snprintf(tmp, sizeof(tmp), "%s", real); + for (tok = strtok_r(tmp, "/", &save); tok; tok = strtok_r(NULL, "/", &save)) { + if (strchr(tok, '-') && !strchr(tok, ':')) + snprintf(slot, sizeof(slot), "%s", tok); + } + } + if (slot[0]) + sl_add(&slots, slot); + } + closedir(d); + for (size_t i = 0; i < slots.len; i++) + add_modem(slots.items[i], "usb"); + sl_free(&slots); +} + +static void scan_pcie_all(void) +{ + DIR *d; + struct dirent *de; + struct str_list slots; + sl_init(&slots); + { + int fd = open("/sys/bus/pci/rescan", O_WRONLY); + if (fd >= 0) { + if (write(fd, "1\n", 2) < 0) + log_msg(LOG_L_DEBUG, "failed to trigger pci rescan: %s", strerror(errno)); + close(fd); + sleep(1); + } + } + d = opendir("/sys/class/net"); + if (!d) + return; + while ((de = readdir(d))) { + char dev_path[512], real[512], tmp[512], *save = NULL, *tok, last[128] = ""; + if (de->d_name[0] == '.') + continue; + if (strncmp(de->d_name, "rmnet", 5) && strncmp(de->d_name, "wwan", 4)) + continue; + snprintf(dev_path, sizeof(dev_path), "/sys/class/net/%s/device", de->d_name); + if (!realpath(dev_path, real)) + continue; + if (!strstr(real, "pci")) + continue; + snprintf(tmp, sizeof(tmp), "%s", real); + for (tok = strtok_r(tmp, "/", &save); tok; tok = strtok_r(NULL, "/", &save)) { + if (strchr(tok, ':') && strchr(tok, '.')) + snprintf(last, sizeof(last), "%s", tok); + } + if (last[0]) + sl_add(&slots, last); + } + closedir(d); + for (size_t i = 0; i < slots.len; i++) + add_modem(slots.items[i], "pcie"); + sl_free(&slots); +} + +static int event_key(enum event_type type, const char *a, const char *b, char *out, size_t len) +{ + switch (type) { + case EV_ADD: return snprintf(out, len, "add:%s:%s", b, a); + case EV_REMOVE: return snprintf(out, len, "remove:%s", a); + case EV_DISABLE: return snprintf(out, len, "disable:%s", a); + case EV_SCAN: return snprintf(out, len, "scan:%s", a && *a ? a : "all"); + } + return -1; +} + +static int queue_has_key(const char *key) +{ + for (struct event *e = g.queue_head; e; e = e->next) { + if (!strcmp(e->key, key)) + return 1; + } + return 0; +} + +static void queue_remove_pending_add_for_slot(const char *slot) +{ + struct event *prev = NULL, *e = g.queue_head; + + while (e) { + struct event *next = e->next; + if (e->type == EV_ADD && !strcmp(e->slot, slot)) { + if (prev) + prev->next = next; + else + g.queue_head = next; + if (g.queue_tail == e) + g.queue_tail = prev; + g.queue_len--; + free(e); + } else { + prev = e; + } + e = next; + } +} + +static int enqueue_event_ex(enum event_type type, const char *arg, const char *slot_type, int delay_sec, int attempts) +{ + struct event *e = calloc(1, sizeof(*e)); + if (!e) + return -1; + e->type = type; + if (arg) + snprintf(type == EV_REMOVE ? e->section : e->slot, + type == EV_REMOVE ? sizeof(e->section) : sizeof(e->slot), + "%s", arg); + if (slot_type) + snprintf(e->slot_type, sizeof(e->slot_type), "%s", slot_type); + if (delay_sec < 0) + delay_sec = 0; + e->not_before = time(NULL) + delay_sec; + e->attempts = attempts; + event_key(type, arg, slot_type, e->key, sizeof(e->key)); + + pthread_mutex_lock(&g.queue_lock); + if (type == EV_DISABLE) + queue_remove_pending_add_for_slot(arg); + if (queue_has_key(e->key) || (!attempts && sl_contains(&g.active_keys, e->key))) { + pthread_mutex_unlock(&g.queue_lock); + free(e); + return 1; + } + if (!g.queue_head || e->not_before < g.queue_head->not_before) { + e->next = g.queue_head; + g.queue_head = e; + if (!g.queue_tail) + g.queue_tail = e; + } else { + struct event *cur = g.queue_head; + while (cur->next && cur->next->not_before <= e->not_before) + cur = cur->next; + e->next = cur->next; + cur->next = e; + if (!e->next) + g.queue_tail = e; + } + g.queue_len++; + pthread_cond_signal(&g.queue_cond); + pthread_mutex_unlock(&g.queue_lock); + log_msg(LOG_L_INFO, "queued %s delay=%d attempts=%d", e->key, delay_sec, attempts); + return 0; +} + +static int enqueue_event(enum event_type type, const char *arg, const char *slot_type, int delay_sec) +{ + return enqueue_event_ex(type, arg, slot_type, delay_sec, 0); +} + +static struct event *dequeue_event(void) +{ + struct event *e; + pthread_mutex_lock(&g.queue_lock); + for (;;) { + time_t now; + struct timespec ts; + + while (!g.stop && !g.queue_head) + pthread_cond_wait(&g.queue_cond, &g.queue_lock); + if (g.stop) { + pthread_mutex_unlock(&g.queue_lock); + return NULL; + } + + e = g.queue_head; + now = time(NULL); + if (e->not_before <= now) + break; + ts.tv_sec = e->not_before; + ts.tv_nsec = 0; + pthread_cond_timedwait(&g.queue_cond, &g.queue_lock, &ts); + } + + g.queue_head = e->next; + if (!g.queue_head) + g.queue_tail = NULL; + g.queue_len--; + g.active_jobs++; + sl_add(&g.active_keys, e->key); + pthread_mutex_unlock(&g.queue_lock); + e->next = NULL; + return e; +} + +static void finish_event_key(const char *key) +{ + pthread_mutex_lock(&g.queue_lock); + if (g.active_jobs > 0) + g.active_jobs--; + for (size_t i = 0; i < g.active_keys.len; i++) { + if (!strcmp(g.active_keys.items[i], key)) { + free(g.active_keys.items[i]); + memmove(&g.active_keys.items[i], &g.active_keys.items[i + 1], + (g.active_keys.len - i - 1) * sizeof(g.active_keys.items[0])); + g.active_keys.len--; + break; + } + } + pthread_mutex_unlock(&g.queue_lock); +} + +static void process_event(struct event *e) +{ + log_msg(LOG_L_INFO, "processing %s attempts=%d", e->key, e->attempts); + switch (e->type) { + case EV_ADD: + if (add_modem(e->slot, e->slot_type) != 0) { + if (e->attempts < g.add_retry_max) { + log_msg(LOG_L_INFO, "retry add slot=%s type=%s next_delay=%d attempt=%d/%d", + e->slot, e->slot_type, g.add_retry_delay, e->attempts + 1, g.add_retry_max); + enqueue_event_ex(EV_ADD, e->slot, e->slot_type, g.add_retry_delay, e->attempts + 1); + } else { + log_msg(LOG_L_WARN, "give up add slot=%s type=%s after %d attempts", + e->slot, e->slot_type, e->attempts); + } + } + break; + case EV_REMOVE: + remove_modem(e->section); + break; + case EV_DISABLE: + disable_slot(e->slot); + break; + case EV_SCAN: + if (!strcmp(e->slot, "usb")) + scan_usb_all(); + else if (!strcmp(e->slot, "pcie")) + scan_pcie_all(); + else { + scan_pcie_all(); + scan_usb_all(); + } + break; + } +} + +static void *worker_thread(void *arg) +{ + (void)arg; + for (;;) { + struct event *e = dequeue_event(); + if (!e) + return NULL; + process_event(e); + finish_event_key(e->key); + free(e); + } +} + +static void handle_client(int fd) +{ + char buf[QMODEM_MAX_LINE]; + ssize_t n = read(fd, buf, sizeof(buf) - 1); + char cmd[64], a[128], b[128], c[128]; + int rc = -1; + int delay_sec = 0; + if (n <= 0) + return; + buf[n] = '\0'; + trim(buf); + cmd[0] = a[0] = b[0] = c[0] = '\0'; + sscanf(buf, "%63s %127s %127s %127s", cmd, a, b, c); + if (!strcmp(cmd, "add") && a[0] && b[0]) { + delay_sec = c[0] ? atoi(c) : 0; + rc = enqueue_event(EV_ADD, a, b, delay_sec); + } else if (!strcmp(cmd, "remove") && a[0]) { + delay_sec = b[0] ? atoi(b) : 0; + rc = enqueue_event(EV_REMOVE, a, NULL, delay_sec); + } else if (!strcmp(cmd, "disable") && a[0]) { + delay_sec = b[0] ? atoi(b) : 0; + rc = enqueue_event(EV_DISABLE, a, NULL, delay_sec); + } else if (!strcmp(cmd, "scan")) { + delay_sec = b[0] ? atoi(b) : 0; + rc = enqueue_event(EV_SCAN, a[0] ? a : "all", NULL, delay_sec); + } + else if (!strcmp(cmd, "set-log-level") && a[0]) { + g.log_level = parse_log_level(a); + rc = 0; + } else if (!strcmp(cmd, "status")) { + pthread_mutex_lock(&g.queue_lock); + dprintf(fd, "{\"queue\":%d,\"active\":%d,\"workers\":%d}\n", g.queue_len, g.active_jobs, g.scan_workers); + pthread_mutex_unlock(&g.queue_lock); + return; + } + if (rc == 0) + dprintf(fd, "{\"code\":0,\"message\":\"queued\"}\n"); + else if (rc == 1) + dprintf(fd, "{\"code\":0,\"message\":\"deduplicated\"}\n"); + else + dprintf(fd, "{\"code\":1,\"message\":\"invalid request\"}\n"); +} + +static int setup_socket(void) +{ + int fd; + struct sockaddr_un addr; + mkdir(QMODEM_RUN_DIR, 0755); + unlink(QMODEM_SCAND_SOCKET); + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd < 0) + return -1; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + snprintf(addr.sun_path, sizeof(addr.sun_path), "%s", QMODEM_SCAND_SOCKET); + if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + close(fd); + return -1; + } + chmod(QMODEM_SCAND_SOCKET, 0666); + if (listen(fd, 32) < 0) { + close(fd); + return -1; + } + return fd; +} + +static void load_config_defaults(void) +{ + char val[64]; + const char *env_level; + g.scan_workers = 4; + g.at_probe_workers = 4; + g.at_timeout_fast = 2; + g.at_timeout_model = 8; + g.add_retry_delay = 8; + g.add_retry_max = 5; + g.log_level = LOG_L_INFO; + if (!uci_get("qmodem.main.scan_workers", val, sizeof(val)) && atoi(val) > 0) + g.scan_workers = atoi(val); + if (!uci_get("qmodem.main.at_probe_workers", val, sizeof(val)) && atoi(val) > 0) + g.at_probe_workers = atoi(val); + if (!uci_get("qmodem.main.at_timeout_fast", val, sizeof(val)) && atoi(val) > 0) + g.at_timeout_fast = atoi(val); + if (!uci_get("qmodem.main.at_timeout_model", val, sizeof(val)) && atoi(val) > 0) + g.at_timeout_model = atoi(val); + if (!uci_get("qmodem.main.add_retry_delay", val, sizeof(val)) && atoi(val) >= 0) + g.add_retry_delay = atoi(val); + if (!uci_get("qmodem.main.add_retry_max", val, sizeof(val)) && atoi(val) >= 0) + g.add_retry_max = atoi(val); + if (!uci_get("qmodem.main.scan_log_level", val, sizeof(val)) && val[0]) + g.log_level = parse_log_level(val); + env_level = getenv("QMODEM_SCAN_LOG_LEVEL"); + if (env_level && *env_level) + g.log_level = parse_log_level(env_level); +} + +static void sig_handler(int signo) +{ + (void)signo; + g.stop = 1; + pthread_cond_broadcast(&g.queue_cond); +} + +int main(int argc, char **argv) +{ + int sockfd; + pthread_t *threads; + + (void)argc; + (void)argv; + openlog("modem_scand", LOG_PID, LOG_DAEMON); + memset(&g, 0, sizeof(g)); + pthread_mutex_init(&g.queue_lock, NULL); + pthread_cond_init(&g.queue_cond, NULL); + sl_init(&g.active_keys); + load_config_defaults(); + + g.support_json = json_object_from_file(QMODEM_SUPPORT_JSON); + g.port_rule_json = json_object_from_file(QMODEM_PORT_RULE_JSON); + if (!g.support_json || !g.port_rule_json) { + log_msg(LOG_L_ERR, "failed to load modem json files"); + return 1; + } + + signal(SIGTERM, sig_handler); + signal(SIGINT, sig_handler); + sockfd = setup_socket(); + if (sockfd < 0) { + syslog(LOG_ERR, "failed to setup socket %s: %s", QMODEM_SCAND_SOCKET, strerror(errno)); + return 1; + } + + threads = calloc((size_t)g.scan_workers, sizeof(*threads)); + if (!threads) + return 1; + for (int i = 0; i < g.scan_workers; i++) + pthread_create(&threads[i], NULL, worker_thread, NULL); + + log_msg(LOG_L_NOTICE, "modem_scand started workers=%d at_timeout_fast=%d at_timeout_model=%d add_retry_delay=%d add_retry_max=%d", + g.scan_workers, g.at_timeout_fast, g.at_timeout_model, g.add_retry_delay, g.add_retry_max); + + while (!g.stop) { + int cfd = accept(sockfd, NULL, NULL); + if (cfd < 0) { + if (errno == EINTR) + continue; + break; + } + handle_client(cfd); + close(cfd); + } + + close(sockfd); + unlink(QMODEM_SCAND_SOCKET); + pthread_mutex_lock(&g.queue_lock); + g.stop = 1; + pthread_cond_broadcast(&g.queue_cond); + pthread_mutex_unlock(&g.queue_lock); + for (int i = 0; i < g.scan_workers; i++) + pthread_join(threads[i], NULL); + free(threads); + if (g.support_json) + json_object_put(g.support_json); + if (g.port_rule_json) + json_object_put(g.port_rule_json); + sl_free(&g.active_keys); + closelog(); + return 0; +} diff --git a/application/qmodem/Makefile b/application/qmodem/Makefile index 95f7e220..570db7b5 100644 --- a/application/qmodem/Makefile +++ b/application/qmodem/Makefile @@ -11,7 +11,6 @@ PKG_VERSION:=$(QMODEM_VERSION) include $(INCLUDE_DIR)/package.mk define Package/$(PKG_NAME) - PKGARCH:=all SECTION:=utils CATEGORY:=Utilities TITLE:=QModem scripts @@ -20,11 +19,12 @@ define Package/$(PKG_NAME) +kmod-usb-serial +kmod-usb-serial-option +kmod-usb-serial-qualcomm \ +kmod-usb-net +kmod-usb-acm \ +kmod-usb-wdm \ - +kmod-usb-net-cdc-ether \ + +kmod-usb-net-cdc-ether \ +kmod-usb-net-cdc-mbim \ +kmod-usb-net-rndis \ +kmod-usb-net-cdc-ncm +kmod-usb-net-huawei-cdc-ncm \ +ubus-at-daemon +tom_modem +terminfo +sms-tool_q \ + +modem_scan \ +jq +bc\ +coreutils +coreutils-stat \ +usbutils \ diff --git a/application/qmodem/files/etc/config/qmodem b/application/qmodem/files/etc/config/qmodem index 8afe13c6..718c5d50 100644 --- a/application/qmodem/files/etc/config/qmodem +++ b/application/qmodem/files/etc/config/qmodem @@ -4,5 +4,13 @@ config main 'main' option try_preset_pcie "1" option enable_pcie_scan '1' option start_delay '0' + option scan_log_level 'info' + option scan_workers '4' + option at_probe_workers '4' + option at_timeout_fast '2' + option at_timeout_model '8' + option hotplug_add_delay '8' + option add_retry_delay '8' + option add_retry_max '5' option usage_stats_nvram_save '0' option usage_stats_nvram_interval '300' diff --git a/application/qmodem/files/etc/hotplug.d/net/20-modem-net b/application/qmodem/files/etc/hotplug.d/net/20-modem-net index a72c67a4..6845855d 100755 --- a/application/qmodem/files/etc/hotplug.d/net/20-modem-net +++ b/application/qmodem/files/etc/hotplug.d/net/20-modem-net @@ -4,6 +4,7 @@ manual=$(uci -q get qmodem.main.block_auto_probe) [ "${manual}" -eq 1 ] && exit logger -t modem_hotplug "net slot: ${DEVPATH} action: ${ACTION}" +hotplug_add_delay=$(uci -q get qmodem.main.hotplug_add_delay || echo 8) #网络设备名称不存在,退出 [ -z "${INTERFACE}" ] && exit #网络设备路径不存在,退出 @@ -22,38 +23,10 @@ else exit fi -if [ "${slot_type}" = "usb" ]; then - vendor_file="/sys/bus/usb/devices/${slot}/idVendor" - product_file="/sys/bus/usb/devices/${slot}/idProduct" - - if [ -f "${vendor_file}" ] && [ -f "${product_file}" ]; then - slot_vid=$(cat "${vendor_file}") - slot_pid=$(cat "${product_file}") - - if [ -n "$slot_vid" ] && [ -n "$slot_pid" ]; then - modem_port_rule=$(cat /usr/share/qmodem/modem_port_rule.json) - modem_port_config=$(echo $modem_port_rule | jq '.modem_port_rule."'$slot_type'"."'$slot_vid:$slot_pid'"') - - if [ "$modem_port_config" != "null" ] && [ -n "$modem_port_config" ]; then - config_modem_name=$(echo $modem_port_config | jq -r '.name') - include_ports=$(echo $modem_port_config | jq -r '.include[]') - - [ -n "$include_ports" ] && { - logger -t modem_hotplug "using special config for $config_modem_name($slot_vid:$slot_pid) with ports: $include_ports" - echo "$slot_vid $slot_pid" > /sys/bus/usb-serial/drivers/option1/new_id 2>/dev/null || - logger -t modem_hotplug "failed to set option driver" - } - fi - else - logger -t modem_hotplug "Unable to read VID/PID from device: $slot" - fi - fi -fi - logger -t modem_hotplug "net slot: ${slot} action: ${ACTION} slot_type: ${slot_type}" case "${ACTION}" in add|\ bind) - /usr/share/qmodem/modem_scan.sh add "${slot}" "${slot_type}" + (/usr/share/qmodem/modem_scan.sh add "${slot}" "${slot_type}" "${hotplug_add_delay}" >/dev/null 2>&1) & ;; esac diff --git a/application/qmodem/files/etc/hotplug.d/usb/20-modem-usb b/application/qmodem/files/etc/hotplug.d/usb/20-modem-usb index 62a1eee0..cb9cda5c 100755 --- a/application/qmodem/files/etc/hotplug.d/usb/20-modem-usb +++ b/application/qmodem/files/etc/hotplug.d/usb/20-modem-usb @@ -3,6 +3,7 @@ manual=$(uci -q get qmodem.main.block_auto_probe) [ "${manual}" -eq 1 ] && exit logger -t modem_hotplug "usb_event slot: ${DEVPATH} action: ${ACTION}" +hotplug_add_delay=$(uci -q get qmodem.main.hotplug_add_delay || echo 8) [ -z "${DEVNUM}" ] && exit @@ -11,9 +12,9 @@ logger -t modem_hotplug "usb_event run slot: ${slot} action: ${ACTION}" case "${ACTION}" in bind|\ add) - /usr/share/qmodem/modem_scan.sh add "${slot}" usb + (/usr/share/qmodem/modem_scan.sh add "${slot}" usb "${hotplug_add_delay}" >/dev/null 2>&1) & ;; remove) - /usr/share/qmodem/modem_scan.sh disable "${slot}" usb + ( /usr/share/qmodem/modem_scan.sh disable "${slot}" >/dev/null 2>&1 ) & ;; esac diff --git a/application/qmodem/files/etc/init.d/qmodem_init b/application/qmodem/files/etc/init.d/qmodem_init index 4a5a8bf1..05a8a9a5 100755 --- a/application/qmodem/files/etc/init.d/qmodem_init +++ b/application/qmodem/files/etc/init.d/qmodem_init @@ -7,6 +7,7 @@ USE_PROCD=1 start_service() { config_load qmodem + _start_scand config_foreach _mk_rundir modem-device config_get block_auto_probe main block_auto_probe 0 config_get enable_pcie_scan main enable_pcie_scan 0 @@ -37,8 +38,7 @@ boot() { local delay=$(uci -q get qmodem.main.start_delay || echo 0) if [ "$delay" -gt 0 ]; then logger "Delay QModem Boot init scan for $delay S" - sleep $delay - /usr/share/qmodem/modem_scan.sh scan >/dev/null 2>&1 & + ( sleep "$delay"; /usr/share/qmodem/modem_scan.sh scan >/dev/null 2>&1 ) & fi start } @@ -81,17 +81,23 @@ _try_pcie_device() _scan_usb() { - procd_open_instance "scan_usb" - #delay 15 second ,scan all usb device - procd_set_param command "ash" "/usr/share/qmodem/modem_scan.sh" "scan" "15" "usb" - procd_close_instance + ( sleep 15; /usr/bin/modem_scanc scan usb >/dev/null 2>&1 ) & } _scan_pcie() { - procd_open_instance "scan_pcie" - #delay 15 second ,scan all pcie device - procd_set_param command "ash" "/usr/share/qmodem/modem_scan.sh" "scan" "15" "pcie" + ( sleep 15; /usr/bin/modem_scanc scan pcie >/dev/null 2>&1 ) & +} + +_start_scand() +{ + config_get scan_log_level main scan_log_level info + procd_open_instance "scand" + procd_set_param command "/usr/bin/modem_scand" + procd_set_param respawn 3600 5 5 + procd_set_param stdout 1 + procd_set_param stderr 1 + procd_set_param env QMODEM_SCAN_LOG_LEVEL="$scan_log_level" procd_close_instance } @@ -116,11 +122,9 @@ _try_slot() if [ -d "$path" ]; then logger -t modem_init "try modem $1" - procd_open_instance "try_$1" - procd_set_param command "ash" "/usr/share/qmodem/modem_scan.sh" "add" "$slot" "$type" - procd_close_instance + ( sleep 1; /usr/share/qmodem/modem_scan.sh add "$slot" "$type" >/dev/null 2>&1 ) & else - /usr/share/qmodem/modem_scan.sh disable "$slot" + ( sleep 1; /usr/share/qmodem/modem_scan.sh disable "$slot" >/dev/null 2>&1 ) & fi } @@ -128,20 +132,22 @@ _try_device() { config_get path "$1" path config_get network "$1" network + config_get data_interface "$1" data_interface if [ ! -d "$path" ]; then - /usr/share/qmodem/modem_scan.sh disable "$1" + ( sleep 1; /usr/share/qmodem/modem_scan.sh disable "$1" >/dev/null 2>&1 ) & uci delete network.$1 uci delete network.${1}v6 uci commit network /etc/init.d/network reload return fi - # if device_path not parent of netdevice_path,then disable device path an scan again + # if device_path is not parent of netdevice_path, disable device path and scan again netdevice_path=`readlink -f /sys/class/net/$network/device/` device_path=`readlink -f $path` - is_parent=`echo "$device_path" | grep -q "^$netdevice_path"` - if [ -z "$is_parent" ] || [ ! -d "$netdevice_path" ]; then - /usr/share/qmodem/modem_scan.sh disable "$1" - /usr/share/qmodem/modem_scan.sh add "$path" "$slot" + echo "$netdevice_path" | grep -q "^$device_path" + is_parent=$? + if [ "$is_parent" -ne 0 ] || [ ! -d "$netdevice_path" ]; then + ( sleep 1; /usr/share/qmodem/modem_scan.sh disable "$1" >/dev/null 2>&1 ) & + ( sleep 1; /usr/share/qmodem/modem_scan.sh add "$(basename "$path")" "$data_interface" >/dev/null 2>&1 ) & fi } diff --git a/application/qmodem/files/usr/libexec/rpcd/qmodem b/application/qmodem/files/usr/libexec/rpcd/qmodem index 621661f4..3520ac94 100755 --- a/application/qmodem/files/usr/libexec/rpcd/qmodem +++ b/application/qmodem/files/usr/libexec/rpcd/qmodem @@ -1058,11 +1058,11 @@ EOF scan_pcie) json_init if [ -x "/usr/share/qmodem/modem_scan.sh" ]; then - output=$(/usr/share/qmodem/modem_scan.sh scan 0 pcie 2>&1) + output=$(/usr/share/qmodem/modem_scan.sh scan pcie 2>&1) scan_result=$? json_add_int "code" "$scan_result" if [ "$scan_result" -eq 0 ]; then - json_add_string "message" "PCIe scan completed successfully" + json_add_string "message" "PCIe scan queued successfully" else json_add_string "message" "PCIe scan failed with exit code: $scan_result" json_add_string "output" "$output" @@ -1077,11 +1077,11 @@ EOF scan_usb) json_init if [ -x "/usr/share/qmodem/modem_scan.sh" ]; then - output=$(/usr/share/qmodem/modem_scan.sh scan 0 usb 2>&1) + output=$(/usr/share/qmodem/modem_scan.sh scan usb 2>&1) scan_result=$? json_add_int "code" "$scan_result" if [ "$scan_result" -eq 0 ]; then - json_add_string "message" "USB scan completed successfully" + json_add_string "message" "USB scan queued successfully" else json_add_string "message" "USB scan failed with exit code: $scan_result" json_add_string "output" "$output" @@ -1096,11 +1096,11 @@ EOF scan_all) json_init if [ -x "/usr/share/qmodem/modem_scan.sh" ]; then - output=$(/usr/share/qmodem/modem_scan.sh scan 2>&1) + output=$(/usr/share/qmodem/modem_scan.sh scan all 2>&1) scan_result=$? json_add_int "code" "$scan_result" if [ "$scan_result" -eq 0 ]; then - json_add_string "message" "Full scan completed successfully" + json_add_string "message" "Full scan queued successfully" else json_add_string "message" "Full scan failed with exit code: $scan_result" json_add_string "output" "$output" diff --git a/application/qmodem/files/usr/share/qmodem/modem_scan.sh b/application/qmodem/files/usr/share/qmodem/modem_scan.sh index 0c48d4dc..f460673e 100755 --- a/application/qmodem/files/usr/share/qmodem/modem_scan.sh +++ b/application/qmodem/files/usr/share/qmodem/modem_scan.sh @@ -1,780 +1,71 @@ -#/bin/sh +#!/bin/sh -action=$1 -config=$2 -slot_type=$3 -modem_support=$(cat /usr/share/qmodem/modem_support.json) -debug_subject="modem_scan" -source /lib/functions.sh -source /usr/share/qmodem/modem_util.sh +action="$1" +config="$2" +slot_type="$3" +delay="$4" -exec_post_init() -{ - section_name=$1 - /usr/share/qmodem/modem_hook.sh $section_name post_init -} - -get_associate_usb() -{ - target_slot=$1 - config_load qmodem - config_foreach _get_associated_usb_by_path modem-slot -} - -get_default_alias() -{ - target_slot=$1 - config_load qmodem - config_foreach _get_default_alias_by_slot -} - -get_led_sript_by_slot() -{ - target_slot=$1 - config_load qmodem - config_foreach _get_led_sript_by_slot -} - -get_default_metric() -{ - target_slot=$1 - config_load qmodem - config_foreach _get_default_metric_by_slot -} - -get_slot_scan_priority() -{ - target_slot=$1 - scan_priority="pcie" - config_load qmodem - config_foreach _get_scan_priority_by_slot modem-slot - case "$scan_priority" in - usb|pcie) ;; - *) scan_priority="pcie" ;; - esac -} - -get_pcie_slot_by_associated_usb() -{ - target_slot=$1 - associated_pcie_slot="" - config_load qmodem - config_foreach _get_pcie_slot_by_associated_usb modem-slot -} - -_get_associated_usb_by_path() -{ - local cfg="$1" - m_debug $target_slot - config_get _get_slot $cfg slot - if [ "$target_slot" == "$_get_slot" ];then - config_get associated_usb $cfg associated_usb - m_debug \[$target_slot\]associated_usb:$associated_usb - fi - -} - -_get_default_alias_by_slot() -{ - local cfg="$1" - config_get _get_slot $cfg slot - if [ "$target_slot" == "$_get_slot" ];then - config_get default_alias $cfg alias - fi - -} - -_get_default_metric_by_slot() -{ - local cfg="$1" - config_get _get_slot $cfg slot - if [ "$target_slot" == "$_get_slot" ];then - config_get default_metric $cfg default_metric - fi - -} - -_get_led_sript_by_slot() -{ - local cfg="$1" - config_get _get_slot $cfg slot - if [ "$target_slot" == "$_get_slot" ];then - config_get led_script $cfg led_script - fi -} - -_get_scan_priority_by_slot() -{ - local cfg="$1" - config_get _get_slot $cfg slot - if [ "$target_slot" == "$_get_slot" ];then - config_get scan_priority $cfg scan_priority "pcie" - fi -} - -_get_pcie_slot_by_associated_usb() -{ - local cfg="$1" - local type - local cfg_associated_usb - - config_get type "$cfg" type - [ "$type" == "pcie" ] || return - config_get cfg_associated_usb "$cfg" associated_usb - [ "$target_slot" == "$cfg_associated_usb" ] || return - config_get associated_pcie_slot "$cfg" slot -} +case "$slot_type" in + --delay) + delay="$4" + slot_type="" + ;; +esac -scan() +scanc() { - local requested_slot_type=$1 - if [ "$requested_slot_type" == "pcie" ] || [ -z "$requested_slot_type" ];then - scan_pcie - pcie_slots=$(echo $pcie_slots | uniq ) - for slot in $pcie_slots; do - slot_type="pcie" - add $slot - done - fi - if [ "$requested_slot_type" == "usb" ] || [ -z "$requested_slot_type" ];then - scan_usb - usb_slots=$(echo $usb_slots | uniq ) - for slot in $usb_slots; do - slot_type="usb" - add $slot - done + /usr/bin/modem_scanc "$@" + rc=$? + if [ "$rc" -eq 2 ] && [ -x /etc/init.d/qmodem_init ]; then + /etc/init.d/qmodem_init start >/dev/null 2>&1 + sleep 1 + /usr/bin/modem_scanc "$@" + rc=$? fi + return "$rc" } -scan_usb() -{ - usb_net_device_prefixs="usb eth wwan" - usb_slots="" - for usb_net_device_prefix in $usb_net_device_prefixs; do - usb_netdev=$(ls /sys/class/net | grep -E "${usb_net_device_prefix}") - for netdev in $usb_netdev; do - netdev_path=$(readlink -f "/sys/class/net/$netdev/device/") - [ -z "$netdev_path" ] && continue - [ -z "$(echo $netdev_path | grep usb)" ] && continue - usb_slot=$(basename $(dirname $netdev_path)) - m_debug "netdev_path: $netdev_path usb slot: $usb_slot" - [ -z "$usb_slots" ] && usb_slots="$usb_slot" || usb_slots="$usb_slots $usb_slot" - done - done -} - -scan_pcie() -{ - #beta - m_debug "scan_pcie" - echo 1 > /sys/bus/pci/rescan - sleep 1 - pcie_net_device_prefixs="rmnet wwan" - pcie_slots="" - for pcie_net_device_prefix in $pcie_net_device_prefixs; do - pcie_netdev=$(ls /sys/class/net | grep -E "${pcie_net_device_prefix}") - for netdev in $pcie_netdev; do - netdev_path=$(readlink -f "/sys/class/net/$netdev/device/") - [ -z "$netdev_path" ] && continue - [ -z "$(echo $netdev_path | grep pci)" ] && continue - # pcie_slot=$(basename $(dirname $netdev_path)) - pcie_slot=$(echo "$netdev_path" | tr '/' '\n' | grep -E '^[0-9a-fA-F]{4}:[0-9a-fA-F:.]+$' | tail -n1) - [ "$pcie_slot" == "net" ] && continue - m_debug "netdev_path: $netdev_path pcie slot: $pcie_slot" - [ -z "$pcie_slots" ] && pcie_slots="$pcie_slot" || pcie_slots="$pcie_slots $pcie_slot" - done - done -} - -scan_pcie_slot_interfaces() -{ - local slot=$1 - local slot_path="/sys/bus/pci/devices/$slot" - net_devices="" - dun_devices="" - [ ! -d "$slot_path" ] && return - local short_slot_name=`echo ${slot:2:-2} |tr ":" "."` - local slot_interfaces=$(ls $slot_path | grep -E "_*${short_slot_name}_") - for interface in $slot_interfaces; do - unset device - unset dun_device - interface_driver_path="$slot_path/$interface/driver" - [ ! -d "$interface_driver_path" ] && continue - interface_driver=$(basename $(readlink $interface_driver_path)) - [ -z "$interface_driver" ] && continue - case $interface_driver in - mhi_netdev) - net_path="$slot_path/$interface/net" - [ ! -d "$net_path" ] && continue - device=$(ls $net_path) - [ -z "$net_devices" ] && net_devices="$device" || net_devices="$net_devices $device" - ;; - mhi_uci_q) - dun_device=$(ls "$slot_path/$interface/mhi_uci_q" | grep mhi_DUN) - [ -z "$dun_device" ] && continue - dun_device_path="$slot_path/$interface/mhi_uci_q/$dun_device" - [ ! -d "$dun_device_path" ] && continue - dun_device_path=$(readlink -f "$dun_device_path") - [ ! -d "$dun_device_path" ] && continue - dun_device=$(basename "$dun_device_path") - [ -z "$dun_device" ] && continue - [ -z "$dun_devices" ] && dun_devices="$dun_device" || dun_devices="$dun_devices $dun_device" +case "$action" in + add) + [ -n "$config" ] && [ -n "$slot_type" ] || exit 1 + scanc add "$config" "$slot_type" "${delay:-0}" + exit $? + ;; + remove) + [ -n "$config" ] || exit 1 + scanc remove "$config" "${delay:-0}" + exit $? + ;; + disable) + [ -n "$config" ] || exit 1 + scanc disable "$config" "${delay:-0}" + exit $? + ;; + scan) + # Old format: modem_scan.sh scan [delay] [usb|pcie] + # Also accept: modem_scan.sh scan [usb|pcie|all] + case "$config" in + usb|pcie|all) + scanc scan "$config" "${delay:-0}" + exit $? ;; esac - done - interface_mhi_path="$slot_path/mhi0" - if [ ! -z "$interface_mhi_path" ]; then - wwan0_path="$slot_path/mhi0/wwan/wwan0" - if [ -d "$wwan0_path" ];then - dun_device=$(ls "$wwan0_path" | grep wwan0at0) - [ ! -z "$dun_device" ] && dun_device_path="$wwan0_path/$dun_device" - [ ! -z "$dun_device_path" ] && dun_devices=$(basename "$dun_device_path") + if [ -n "$config" ] && [ "$config" -gt 0 ] 2>/dev/null; then + delay="$config" fi - fi - #mt_t7xx device - wwan_path="$slot_path/wwan" - if [ -d "$wwan_path" ]; then - net_devices=$(ls "$wwan_path" | grep -E "wwan[0-9]") - devices_path="$wwan_path/$net_devices" - if [ -d "$devices_path" ];then - mbim_devices=$(ls "$devices_path" | grep -E "wwan[0-9]mbim[0-9]") - dun_devices=$(ls "$devices_path" | grep -E "wwan[0-9]at[0-9]") - fi - fi - m_debug "net_devices: $net_devices dun_devices: $dun_devices" - at_ports="$dun_devices" - validate_at_port -} - -scan_associated_usb_slot_interfaces() -{ - local slot=$1 - tty_devices="" - associated_usb="" - - get_associate_usb $slot - [ -n "$associated_usb" ] || return 1 - [ -d "/sys/bus/usb/devices/$associated_usb" ] || return 1 - - m_debug "checking associated_usb: $associated_usb" - local assoc_usb_path="/sys/bus/usb/devices/$associated_usb" - local slot_interfaces=$(ls $assoc_usb_path | grep -E "$associated_usb:[0-9]\.[0-9]+") - slot_vid=$(cat "$assoc_usb_path/idVendor") - slot_pid=$(cat "$assoc_usb_path/idProduct") - modem_port_rule=$(cat /usr/share/qmodem/modem_port_rule.json) - modem_port_config=$(echo $modem_port_rule | jq --arg id "$slot_vid:$slot_pid" '.modem_port_rule.usb[$id]') - included_ports=$(echo $modem_port_config | jq -r '.include // empty') - - for interface in $slot_interfaces; do - unset device - unset ttyUSB_device - unset ttyACM_device - interface_driver_path="$assoc_usb_path/$interface/driver" - if_port=$(echo "$interface" | grep -oE "[0-9]+\.[0-9]+$" || echo "") - [ ! -d "$interface_driver_path" ] && continue - interface_driver=$(basename $(readlink $interface_driver_path)) - [ -z "$interface_driver" ] && continue - case $interface_driver in - option|\ - cdc_acm|\ - usbserial_generic|\ - qcserial|\ - usbserial) - ttyUSB_device=$(ls "$assoc_usb_path/$interface/" | grep ttyUSB) - ttyACM_device=$(ls "$assoc_usb_path/$interface/" | grep ttyACM) - [ -z "$ttyUSB_device" ] && [ -z "$ttyACM_device" ] && continue - if [ -n "$included_ports" ]; then - if [ -n "$if_port" ]; then - index=$(echo $included_ports | jq --arg port "$if_port" 'index($port)') - m_debug "included_ports: $included_ports if_port: $if_port index: $index" - [ "$index" != "null" ] || continue - fi - fi - [ -n "$ttyUSB_device" ] && device="$ttyUSB_device" - [ -n "$ttyACM_device" ] && device="$ttyACM_device" - [ -z "$tty_devices" ] && tty_devices="$device" || tty_devices="$tty_devices $device" - ;; + case "$slot_type" in + usb|pcie) + scanc scan "$slot_type" "${delay:-0}" + exit $? + ;; + *) + scanc scan all "${delay:-0}" + exit $? + ;; esac - done - - at_ports="$tty_devices" - validate_at_port - [ -n "$valid_at_ports" ] -} - -scan_usb_slot_interfaces() -{ - local slot=$1 - local slot_path="/sys/bus/usb/devices/$slot" - net_devices="" - tty_devices="" - [ ! -d "$slot_path" ] && return - slot_vid=$(cat "$slot_path/$interface/idVendor") - slot_pid=$(cat "$slot_path/$interface/idProduct") - local slot_interfaces=$(ls $slot_path | grep -E "$slot:[0-9]\.[0-9]+") - for interface in $slot_interfaces; do - unset device - unset ttyUSB_device - unset ttyACM_device - interface_driver_path="$slot_path/$interface/driver" - [ ! -d "$interface_driver_path" ] && continue - interface_driver=$(basename $(readlink "$interface_driver_path")) - modem_port_rule=$(cat /usr/share/qmodem/modem_port_rule.json) - modem_port_config=$(echo $modem_port_rule | jq --arg id "$slot_vid:$slot_pid" '.modem_port_rule.usb[$id]') - included_ports=$(echo $modem_port_config | jq -r '.include // empty') - [ -z "$interface_driver" ] && continue - - local if_port=$(echo "$interface" | grep -oE "[0-9]+\.[0-9]+$" || echo "") - - case $interface_driver in - option|\ - cdc_acm|\ - qcserial|\ - usbserial_generic|\ - usbserial) - ttyUSB_device=$(ls "$slot_path/$interface/" | grep ttyUSB) - ttyACM_device=$(ls "$slot_path/$interface/" | grep ttyACM) - [ -z "$ttyUSB_device" ] && [ -z "$ttyACM_device" ] && continue - [ -n "$ttyUSB_device" ] && device="$ttyUSB_device" - [ -n "$ttyACM_device" ] && device="$ttyACM_device" - - local if_port=$(echo "$interface" | grep -oE "[0-9]+\.[0-9]+$" || echo "") - if [ -n "$included_ports" ]; then - if [ -n "$if_port" ]; then - index=$(echo $included_ports | jq --arg port "$if_port" 'index($port)') - m_debug "included_ports: $included_ports if_port: $if_port index: $index" - [ "$index" != "null" ] || continue - fi - fi - [ -z "$tty_devices" ] && tty_devices="$device" || tty_devices="$tty_devices $device" - - ;; - qmi_wwan*|\ - cdc_mbim|\ - *cdc_ncm|\ - cdc_ether|\ - rndis_host) - net_path="$slot_path/$interface/net" - [ ! -d "$net_path" ] && continue - device=$(ls $net_path) - [ -z "$net_devices" ] && net_devices="$device" || net_devices="$net_devices $device" - ;; - esac - done - m_debug "net_devices: $net_devices tty_devices: $tty_devices" - at_ports="$tty_devices" - validate_at_port -} - -validate_at_port() -{ - valid_at_ports="" - for at_port in $at_ports; do - dev_path="/dev/$at_port" - [ ! -e "$dev_path" ] && continue - #disable at-daemon binding - ubus call at-daemon close '{ "at_port": "'$dev_path'" }' 2>/dev/null - res=$(fastat $dev_path "ATI") - [ -z "$res" ] && continue - !(echo "$res" | grep -qE 'OK|ATI') && continue - valid_at_port="$at_port" - [ -z "$valid_at_ports" ] && valid_at_ports="$valid_at_port" || valid_at_ports="$valid_at_ports $valid_at_port" - done -} - -match_config() -{ - local name=$(echo $1 | sed 's/\r//g' | tr 'A-Z' 'a-z') - [[ "$name" = *"nl668"* ]] && name="nl668" - [[ "$name" = *"nl678"* ]] && name="nl678" - - [[ "$name" = *"em120k"* ]] && name="em120k" - - #FM350-GL-00 5G Module - [[ "$name" = *"fm350-gl"* ]] && name="fm350-gl" - - #FM190W-GL 5G Module - [[ "$name" = *"fm190w-gl"* ]] && name="fm190w-gl" - - [[ "$name" = *"rm500u-ea"* ]] && name="rm500u-ea" - #t99w175 - [[ "$name" = *"mv31-w"* ]] || [[ "$name" = *"T99W175"* ]] && name="t99w175" - - [[ "$name" = *"T99W373"* ]] && name="t99w373" - - [[ "$name" = *"dp25-42843-47"* ]] && name="t99w640" - - [[ "$name" = *"SIM8380G"* ]] && name="SIM8380G-M2" - - #rg200u-cn - [[ "$name" = *"rg200u-cn"* ]] && name="rg200u-cn" - - #nu313-m2 - [[ "$name" = *"nu313-m2"* ]] && name="srm821" - - #nari-m601 - [[ "$name" = *"m601"* ]] && name="n510m" - - modem_config=$(echo $modem_support | jq '.modem_support."'$slot_type'"."'$name'"') - [ "$modem_config" == "null" ] && return - [ -z "$modem_config" ] && return - modem_name=$name - manufacturer=$(echo $modem_config | jq -r ".manufacturer") - platform=$(echo $modem_config | jq -r ".platform") - suggest_pdp_index=$(echo $modem_config | jq -r ".pdp_index") - modes=$(echo $modem_config | jq -r ".modes[]") - wcdma_available_band=$(echo $modem_config | jq -r ".wcdma_band") - lte_available_band=$(echo $modem_config | jq -r ".lte_band") - nsa_available_band=$(echo $modem_config | jq -r ".nsa_band") - sa_available_band=$(echo $modem_config | jq -r ".sa_band") -} - -get_model_name_by_id() -{ - local id=$1 - local name=$(echo $modem_support | jq -r '.modem_support."'$slot_type'" | to_entries[] | select(.value.id=="'$id'") | .key') - modem_config=$(echo $modem_support | jq '.modem_support."'$slot_type'"."'$name'"') - [ "$modem_config" == "null" ] && return - [ -z "$modem_config" ] && return - modem_name=$name - manufacturer=$(echo $modem_config | jq -r ".manufacturer") - platform=$(echo $modem_config | jq -r ".platform") - suggest_pdp_index=$(echo $modem_config | jq -r ".pdp_index") - modes=$(echo $modem_config | jq -r ".modes[]") - wcdma_available_band=$(echo $modem_config | jq -r ".wcdma_band") - lte_available_band=$(echo $modem_config | jq -r ".lte_band") - nsa_available_band=$(echo $modem_config | jq -r ".nsa_band") - sa_available_band=$(echo $modem_config | jq -r ".sa_band") -} - -get_modem_model() -{ - local at_port=$1 - sleep 1 - cgmm=$(at $at_port "AT+CGMM") - sleep 1 - cgmm_1=$(at $at_port "AT+CGMM?") - sleep 1 - gmm=$(at $at_port "AT+GMM") - name_1=$(echo -e "$cgmm" |grep "+CGMM: " | awk -F': ' '{print $2}') - name_2=$(echo -e "$cgmm_1" |grep "+CGMM: " | awk -F'"' '{print $2} '| cut -d ' ' -f 1) - name_3=$(echo -e "$cgmm" | sed -n '2p') - name_4=$(echo -e "$cgmm" | sed -n '3p') - name_5=$(echo -e "$cgmm" |grep "+CGMM: " | awk -F'"' '{print $2} '| cut -d ' ' -f 1) - name_6=$(echo -e "$gmm" | sed -n '2p') - name_7=$(echo -e "$gmm" | sed -n '3p') - modem_name="" - - [ -n "$name_1" ] && match_config "$name_1" - [ -n "$name_2" ] && [ -z "$modem_name" ] && match_config "$name_2" - [ -n "$name_3" ] && [ -z "$modem_name" ] && match_config "$name_3" - [ -n "$name_4" ] && [ -z "$modem_name" ] && match_config "$name_4" - [ -n "$name_5" ] && [ -z "$modem_name" ] && match_config "$name_5" - [ -n "$name_6" ] && [ -z "$modem_name" ] && match_config "$name_6" - [ -n "$name_7" ] && [ -z "$modem_name" ] && match_config "$name_7" - [ -z "$modem_name" ] && return 1 - return 0 -} - -match_modem_from_valid_at_ports() -{ - modem_name="" - for trys in $(seq 1 3);do - for at_port in $valid_at_ports; do - m_debug "try at port $at_port;time $trys" - get_modem_model "/dev/$at_port" - [ $? -eq 0 ] && break || modem_name="" - done - [ -n "$modem_name" ] && break - sleep 1 - done - [ -n "$modem_name" ] -} - -match_modem_by_id() -{ - [ -f "$modem_path/idProduct" ] || return 1 - [ -f "$modem_path/idVendor" ] || return 1 - - m_debug "modem $modem_name not found, try to get modem model by id" - product_id=$(cat $modem_path/idProduct) - vendor_id=$(cat $modem_path/idVendor) - id="$vendor_id:$product_id" - get_model_name_by_id $id - [ -n "$modem_name" ] -} - -modem_device_has_name_by_slot() -{ - local slot=$1 - local section_name=$(echo $slot | sed 's/[\.:-]/_/g') - local name=$(uci -q get qmodem.$section_name.name) - [ -n "$name" ] -} - -should_skip_usb_slot_for_pcie_priority() -{ - local slot=$1 - - get_pcie_slot_by_associated_usb "$slot" - [ -n "$associated_pcie_slot" ] || return 1 - - get_slot_scan_priority "$associated_pcie_slot" - [ "$scan_priority" == "pcie" ] || return 1 - - if modem_device_has_name_by_slot "$associated_pcie_slot"; then - m_debug "skip usb slot $slot: associated pcie slot $associated_pcie_slot already matched modem name" - return 0 - fi - - return 1 -} - -should_skip_pcie_slot_for_usb_priority() -{ - local slot=$1 - local orig_slot_type - - get_slot_scan_priority "$slot" - [ "$scan_priority" == "usb" ] || return 1 - - associated_usb="" - get_associate_usb "$slot" - [ -n "$associated_usb" ] || return 1 - - if modem_device_has_name_by_slot "$associated_usb"; then - m_debug "skip pcie slot $slot: associated usb slot $associated_usb already matched modem name" - return 0 - fi - - if [ -d "/sys/bus/usb/devices/$associated_usb" ]; then - orig_slot_type="$slot_type" - slot_type="usb" - add "$associated_usb" - slot_type="$orig_slot_type" - - if modem_device_has_name_by_slot "$associated_usb"; then - m_debug "skip pcie slot $slot: associated usb slot $associated_usb matched modem name first" - return 0 - fi - fi - - return 1 -} - -match_pcie_modem_by_priority() -{ - local slot=$1 - local pcie_at_ports="$at_ports" - local pcie_valid_at_ports="$valid_at_ports" - - get_slot_scan_priority "$slot" - m_debug "slot $slot scan_priority: $scan_priority" - - if [ "$scan_priority" == "usb" ]; then - if scan_associated_usb_slot_interfaces "$slot"; then - match_modem_from_valid_at_ports && return 0 - fi - - at_ports="$pcie_at_ports" - valid_at_ports="$pcie_valid_at_ports" - match_modem_from_valid_at_ports && return 0 - else - match_modem_from_valid_at_ports && return 0 - - if scan_associated_usb_slot_interfaces "$slot"; then - match_modem_from_valid_at_ports && return 0 - fi - - at_ports="$pcie_at_ports" - valid_at_ports="$pcie_valid_at_ports" - fi - - return 1 -} - -add() -{ - local slot=$1 - lock -n /tmp/lock/modem_add_$slot - [ $? -eq 0 ] || return - #slot_type is usb or pcie - #section name is replace slot .:- with _ - section_name=$(echo $slot | sed 's/[\.:-]/_/g') - is_exist=$(uci -q get qmodem.$section_name) - is_fixed_device=$(uci -q get qmodem.${section_name}.fixed_device) - if [ "$is_fixed_device" == "1" ];then - m_debug "modem $modem_name slot $slot slot_type $slot_type is fixed device, skip" - lock -u /tmp/lock/modem_add_$slot - exec_post_init $section_name - return - fi - case $slot_type in - "usb") - if should_skip_usb_slot_for_pcie_priority "$slot"; then - lock -u /tmp/lock/modem_add_$slot - return - fi - scan_usb_slot_interfaces $slot - modem_path="/sys/bus/usb/devices/$slot/" - ;; - "pcie") - if should_skip_pcie_slot_for_usb_priority "$slot"; then - lock -u /tmp/lock/modem_add_$slot - return - fi - #under test - scan_pcie_slot_interfaces $slot - modem_path="/sys/bus/pci/devices/$slot/" - ;; - esac - #if no netdev return - [ -z "$net_devices" ] && lock -u /tmp/lock/modem_add_$slot && return - - case $slot_type in - "pcie") - match_pcie_modem_by_priority "$slot" - ;; - *) - match_modem_from_valid_at_ports - ;; - esac - - [ -z "$modem_name" ] && match_modem_by_id - [ -z "$modem_name" ] && lock -u /tmp/lock/modem_add_$slot && return - m_debug "add modem $modem_name slot $slot slot_type $slot_type" - if [ -n "$is_exist" ]; then - #network at_port state name 不变,则不需要重启网络 - orig_network=$(uci -q get qmodem.$section_name.network) - orig_at_port=$(uci -q get qmodem.$section_name.at_port) - orig_state=$(uci -q get qmodem.$section_name.state) - orig_name=$(uci -q get qmodem.$section_name.name) - uci -q del qmodem.$section_name.modes - uci -q del qmodem.$section_name.valid_at_ports - uci -q del qmodem.$section_name.tty_devices - uci -q del qmodem.$section_name.net_devices - uci -q del qmodem.$section_name.ports - uci -q set qmodem.$section_name.state="enabled" - else - - #aqcuire lock - lock /tmp/lock/modem_add - unset default_alias - unset default_metric - get_default_alias $slot - get_default_metric $slot - get_led_sript_by_slot $slot - modem_count=$(uci -q get qmodem.main.modem_count) - [ -z "$modem_count" ] && modem_count=0 - modem_count=$(($modem_count+1)) - uci -q set qmodem.main.modem_count=$modem_count - uci -q set qmodem.$section_name=modem-device - [ -n "$default_alias" ] && uci -q set qmodem.${section_name}.alias="$default_alias" - [ -n "$led_script" ] && uci -q set qmodem.${section_name}.led_script="$led_script" - uci commit qmodem - lock -u /tmp/lock/modem_add - #release lock - metric=$(($modem_count+10)) - [ -n "$default_metric" ] && metric=$default_metric - uci -q batch << EOF -set qmodem.$section_name.path="$modem_path" -set qmodem.$section_name.data_interface="$slot_type" -set qmodem.$section_name.enable_dial="1" -set qmodem.$section_name.soft_reboot="1" -set qmodem.$section_name.extend_prefix="1" -set qmodem.$section_name.pdp_type="ipv4v6" -set qmodem.$section_name.state="enabled" -set qmodem.$section_name.metric=$metric -EOF - fi - uci -q batch < [delay] | remove
[delay] | disable [delay] | scan [delay] [usb|pcie]" >&2 + exit 1 ;; esac diff --git a/luci/luci-app-qmodem-next/htdocs/luci-static/resources/view/qmodem/settings.js b/luci/luci-app-qmodem-next/htdocs/luci-static/resources/view/qmodem/settings.js index ec418c00..b0a70ee8 100644 --- a/luci/luci-app-qmodem-next/htdocs/luci-static/resources/view/qmodem/settings.js +++ b/luci/luci-app-qmodem-next/htdocs/luci-static/resources/view/qmodem/settings.js @@ -118,6 +118,46 @@ return view.extend({ o.description = _('Once enabled, the USB ports will be scanned on every boot.'); o.default = '1'; + o = s.option(form.ListValue, 'scan_log_level', _('Scan Log Level')); + o.value('debug', _('Debug')); + o.value('info', _('Info')); + o.value('notice', _('Notice')); + o.value('warn', _('Warning')); + o.value('err', _('Error')); + o.default = 'info'; + + o = s.option(form.Value, 'scan_workers', _('Scan Workers')); + o.datatype = 'and(uinteger,min(1),max(16))'; + o.default = '4'; + + o = s.option(form.Value, 'at_probe_workers', _('AT Probe Workers')); + o.datatype = 'and(uinteger,min(1),max(16))'; + o.default = '4'; + + o = s.option(form.Value, 'at_timeout_fast', _('Fast AT Timeout')); + o.description = _('Units: seconds'); + o.datatype = 'and(uinteger,min(1),max(30))'; + o.default = '2'; + + o = s.option(form.Value, 'at_timeout_model', _('Model AT Timeout')); + o.description = _('Units: seconds'); + o.datatype = 'and(uinteger,min(1),max(60))'; + o.default = '8'; + + o = s.option(form.Value, 'hotplug_add_delay', _('Hotplug Add Delay')); + o.description = _('Units: seconds'); + o.datatype = 'and(uinteger,min(0),max(60))'; + o.default = '8'; + + o = s.option(form.Value, 'add_retry_delay', _('Add Retry Delay')); + o.description = _('Units: seconds'); + o.datatype = 'and(uinteger,min(0),max(60))'; + o.default = '8'; + + o = s.option(form.Value, 'add_retry_max', _('Add Retry Max')); + o.datatype = 'and(uinteger,min(0),max(20))'; + o.default = '5'; + o = s.option(form.Flag, 'try_preset_usb', _('Try Preset USB Port')); o.description = _('Attempt to use pre-configured USB settings from the CPE vendor.'); o.default = '0'; diff --git a/luci/luci-app-qmodem/luasrc/model/cbi/qmodem/settings.lua b/luci/luci-app-qmodem/luasrc/model/cbi/qmodem/settings.lua index 4e19c776..d9f47c1d 100644 --- a/luci/luci-app-qmodem/luasrc/model/cbi/qmodem/settings.lua +++ b/luci/luci-app-qmodem/luasrc/model/cbi/qmodem/settings.lua @@ -23,6 +23,46 @@ enable_pcie_scan.description = translate("Once enabled, the PCIe ports will be s enable_usb_scan = s:option(Flag, "enable_usb_scan",translate("Enable USB Scan")) enable_usb_scan.description = translate("Once enabled, the USB ports will be scanned on every boot.") +scan_log_level = s:option(ListValue, "scan_log_level", translate("Scan Log Level")) +scan_log_level:value("debug", "Debug") +scan_log_level:value("info", "Info") +scan_log_level:value("notice", "Notice") +scan_log_level:value("warn", "Warning") +scan_log_level:value("err", "Error") +scan_log_level.default = "info" + +scan_workers = s:option(Value, "scan_workers", translate("Scan Workers")) +scan_workers.datatype = "and(uinteger,min(1),max(16))" +scan_workers.default = "4" + +at_probe_workers = s:option(Value, "at_probe_workers", translate("AT Probe Workers")) +at_probe_workers.datatype = "and(uinteger,min(1),max(16))" +at_probe_workers.default = "4" + +at_timeout_fast = s:option(Value, "at_timeout_fast", translate("Fast AT Timeout")) +at_timeout_fast.description = translate("Units: seconds") +at_timeout_fast.datatype = "and(uinteger,min(1),max(30))" +at_timeout_fast.default = "2" + +at_timeout_model = s:option(Value, "at_timeout_model", translate("Model AT Timeout")) +at_timeout_model.description = translate("Units: seconds") +at_timeout_model.datatype = "and(uinteger,min(1),max(60))" +at_timeout_model.default = "8" + +hotplug_add_delay = s:option(Value, "hotplug_add_delay", translate("Hotplug Add Delay")) +hotplug_add_delay.description = translate("Units: seconds") +hotplug_add_delay.datatype = "and(uinteger,min(0),max(60))" +hotplug_add_delay.default = "8" + +add_retry_delay = s:option(Value, "add_retry_delay", translate("Add Retry Delay")) +add_retry_delay.description = translate("Units: seconds") +add_retry_delay.datatype = "and(uinteger,min(0),max(60))" +add_retry_delay.default = "8" + +add_retry_max = s:option(Value, "add_retry_max", translate("Add Retry Max")) +add_retry_max.datatype = "and(uinteger,min(0),max(20))" +add_retry_max.default = "5" + try_vendor_preset_usb = s:option(Flag,"try_preset_usb",translate("Try Preset USB Port")) try_vendor_preset_usb.description = translate("Attempt to use pre-configured USB settings from the cpe vendor.")