SET(UQMID_LIBS ${talloc_library} ${ubus_library})
-SET(UQMID uqmid.c ddev.c ubus.c modem.c modem_fsm.c modem_tx.c services.c sim.c ctrl.c)
+SET(UQMID uqmid.c ddev.c ubus.c modem.c modem_fsm.c modem_tx.c services.c sim.c ctrl.c wwan.c)
ADD_SUBDIRECTORY(osmocom)
ADD_EXECUTABLE(uqmid ${UQMID})
uint8_t pdp_type;
};
+struct wwan_conf {
+ bool raw_ip;
+ bool pass_through;
+};
+
struct modem {
char *name;
+ /*! path to the device. /dev/cdc-wdm */
char *device;
char imei[IMEI_LEN];
char path[PATH_LEN];
+ /* Either usb or usbmisc of cdc-wdm0 */
+ char *subsystem_name;
+ struct {
+ /* network device name. e.g. wwan0 */
+ char *dev;
+ char *sysfs;
+ struct wwan_conf config;
+
+ /* uqmid won't do any sysfs/kernel configuration */
+ bool skip_configuration;
+ } wwan;
+
char *manuf;
char *model;
char *rev;
#include "utils.h"
#include "modem.h"
-#include "services.h"
#include "modem_fsm.h"
#include "modem_tx.h"
+#include "services.h"
+#include "wwan.h"
#define S(x) (1 << (x))
/* Timeouts periods in seconds */
#define T_GET_VERSION_S 5
#define T_SYNC_S 3
+/* default timeout */
+#define T_DEFAULT_S 5
/* Timeout number */
}
break;
case MODEM_EV_RX_MODIFIED_PROFILE:
- osmo_fsm_inst_state_chg(fi, MODEM_ST_POWERON, 0, 0);
+ /* configure the physical kernel interface */
+ osmo_fsm_inst_state_chg(fi, MODEM_ST_CONFIGURE_KERNEL, T_DEFAULT_S, 0);
break;
}
/* TODO: check if this is the default profile! */
}
+
+static void wda_set_data_format_cb(struct qmi_service *service, struct qmi_request *req, struct qmi_msg *msg)
+{
+ struct modem *modem = req->cb_data;
+ int ret = 0;
+ struct qmi_wda_set_data_format_response res = {};
+
+ if (req->ret) {
+ modem_log(modem, LOGL_ERROR, "Failed to set data format. Status %d/%s.", req->ret, qmi_get_error_str(req->ret));
+ osmo_fsm_inst_dispatch(modem->fi, MODEM_EV_RX_FAILED, NULL);
+ return;
+ }
+
+ ret = qmi_parse_wda_set_data_format_response(msg, &res);
+ if (ret) {
+ modem_log(modem, LOGL_ERROR, "Failed to set data format. Failed to parse message");
+ osmo_fsm_inst_dispatch(modem->fi, MODEM_EV_RX_FAILED, NULL);
+ return;
+ }
+
+ if (!res.set.link_layer_protocol) {
+ modem_log(modem, LOGL_ERROR, "Failed to set data format. Link layer protocol TLV missing");
+ osmo_fsm_inst_dispatch(modem->fi, MODEM_EV_RX_FAILED, NULL);
+ return;
+ }
+
+ /* currently uqmid only supports raw-ip */
+ if (res.data.link_layer_protocol != QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP) {
+ modem_log(modem, LOGL_ERROR, "Failed to set data format. Link layer protocol TLV missing");
+ osmo_fsm_inst_dispatch(modem->fi, MODEM_EV_RX_FAILED, NULL);
+ return;
+ }
+
+ osmo_fsm_inst_dispatch(modem->fi, MODEM_EV_RX_SUCCEED, (void *) (long) msg->svc.message);
+}
+
+/* Configure the kernel network interface */
+static void modem_st_configure_kernel_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
+{
+ struct modem *modem = fi->priv;
+ struct qmi_service *wda = uqmi_service_find(modem->qmi, QMI_SERVICE_WDA);
+
+ if (modem->wwan.skip_configuration) {
+ tx_wda_set_data_format(modem, wda, wda_set_data_format_cb);
+ return;
+ }
+
+ /* when using raw-ip later with QMAP support, ensure the mtu is at the maximum of the USB transfer */
+ int ret = wwan_refresh_device(modem);
+ if (ret) {
+ modem_log(modem, LOGL_ERROR, "Failed to get wwan device. err %d", ret);
+ return;
+ }
+
+ ret = wwan_ifupdown(modem->wwan.dev, 0);
+ if (ret) {
+ modem_log(modem, LOGL_ERROR, "Failed to bring down wwan device. err %d", ret);
+ return;
+ }
+
+ modem->wwan.config.pass_through = false;
+ modem->wwan.config.raw_ip = false;
+ ret = wwan_set_configuration(modem->wwan.sysfs, &modem->wwan.config);
+ if (ret) {
+ modem_log(modem, LOGL_ERROR, "Failed to set qmi configuration. err %d", ret);
+ return;
+ }
+
+ ret = wwan_set_mtu(modem->wwan.dev, 1500);
+ if (ret) {
+ modem_log(modem, LOGL_ERROR, "Failed to set mtu. err %d", ret);
+ return;
+ }
+
+ modem->wwan.config.pass_through = false;
+ modem->wwan.config.raw_ip = true;
+ ret = wwan_set_configuration(modem->wwan.sysfs, &modem->wwan.config);
+ if (ret) {
+ modem_log(modem, LOGL_ERROR, "Failed to set qmi configuration (final). err %d", ret);
+ return;
+ }
+
+ ret = wwan_ifupdown(modem->wwan.dev, 1);
+ if (ret) {
+ modem_log(modem, LOGL_ERROR, "Failed to bring up wwan device. err %d", ret);
+ return;
+ }
+
+ tx_wda_set_data_format(modem, wda, wda_set_data_format_cb);
+}
+
+
+static void modem_st_configure_kernel(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+ long msg = (long) data;
+
+ switch (event) {
+ case MODEM_EV_RX_SUCCEED:
+ /* Wrong message handler */
+ /* TODO: generate and use WDA Message defines */
+ if (msg != 0x0020)
+ break;
+
+ osmo_fsm_inst_state_chg(fi, MODEM_ST_POWERON, 0, 0);
+ break;
+ }
+}
+
static void modem_st_poweron_onenter(struct osmo_fsm_inst *fi, uint32_t old_state)
{
struct modem *modem = fi->priv;
.in_event_mask = S(MODEM_EV_RX_CONFIGURED)
| S(MODEM_EV_RX_GET_PROFILE_LIST)
| S(MODEM_EV_RX_MODIFIED_PROFILE),
- .out_state_mask = S(MODEM_ST_POWERON) | S(MODEM_ST_DESTROY),
+ .out_state_mask = S(MODEM_ST_CONFIGURE_KERNEL) | S(MODEM_ST_DESTROY),
.name = "CONFIGURE_MODEM",
.action = modem_st_configure_modem,
.onenter = modem_st_configure_modem_onenter,
},
+ [MODEM_ST_CONFIGURE_KERNEL] = {
+ .in_event_mask = S(MODEM_EV_RX_SUCCEED),
+ .out_state_mask = S(MODEM_ST_POWERON) | S(MODEM_ST_DESTROY),
+ .name = "CONFIGURE_KERNEL",
+ .action = modem_st_configure_kernel,
+ .onenter = modem_st_configure_kernel_onenter,
+ },
[MODEM_ST_POWERON] = {
.in_event_mask = S(MODEM_EV_RX_POWEROFF)
| S(MODEM_EV_RX_POWERON)
MODEM_ST_POWEROFF,
MODEM_ST_UNLOCK_PIN,
MODEM_ST_CONFIGURE_MODEM,
+ MODEM_ST_CONFIGURE_KERNEL,
MODEM_ST_POWERON,
MODEM_ST_NETSEARCH,
MODEM_ST_REGISTERED,
#include "modem_tx.h"
+/* TODO: add qmap */
+/* TODO: check when we have to use the endpoint number (usb) */
+int
+tx_wda_set_data_format(struct modem *modem, struct qmi_service *wda, request_cb cb)
+{
+ struct qmi_request *req = talloc_zero(wda, struct qmi_request);
+ struct qmi_msg *msg = talloc_zero_size(req, 1024);
+
+ struct qmi_wda_set_data_format_request set_data_format_req = { 0 };
+ qmi_set(&set_data_format_req, qos_format, false);
+ qmi_set(&set_data_format_req, link_layer_protocol, QMI_WDA_LINK_LAYER_PROTOCOL_RAW_IP);
+ qmi_set(&set_data_format_req, downlink_data_aggregation_protocol, QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED);
+ qmi_set(&set_data_format_req, uplink_data_aggregation_protocol, QMI_WDA_DATA_AGGREGATION_PROTOCOL_DISABLED);
+
+ int ret = qmi_set_wda_set_data_format_request(msg, &set_data_format_req);
+ if (ret) {
+ modem_log(modem, LOGL_ERROR, "Failed to encode set_data_format_req");
+ return 1;
+ }
+
+ req->msg = msg;
+ req->cb = cb;
+ req->cb_data = modem;
+ return uqmi_service_send_msg(wda, req);
+}
+
+
int tx_dms_set_operating_mode(struct modem *modem, struct qmi_service *dms, uint8_t operating_mode, request_cb cb)
{
struct qmi_request *req = talloc_zero(dms, struct qmi_request);
int tx_dms_set_operating_mode(struct modem *modem, struct qmi_service *dms, uint8_t operating_mode, request_cb cb);
int tx_nas_subscribe_nas_events(struct modem *modem, struct qmi_service *nas, bool action, request_cb cb);
+int tx_wda_set_data_format(struct modem *modem, struct qmi_service *wda, request_cb cb);
int tx_wds_get_profile_list(struct modem *modem, struct qmi_service *wds, request_cb cb);
int tx_wds_modify_profile(struct modem *modem, struct qmi_service *wds, request_cb cb, uint8_t profile, const char *apn,
uint8_t pdp_type);
--- /dev/null
+/*
+ * uqmid -- implement a daemon based on the uqmi idea
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+/** Interface with the linux kernel module */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <libgen.h>
+#include <linux/limits.h>
+#include <talloc.h>
+
+#include <sys/ioctl.h>
+#include <linux/if_ether.h>
+#include <linux/if.h>
+#include <linux/sockios.h>
+
+#include "wwan.h"
+#include "logging.h"
+#include "modem.h"
+
+static int get_real_device(const char *cdc_device_path, char *real_path, size_t real_path_size)
+{
+ ssize_t ret = readlink(cdc_device_path, real_path, real_path_size - 1);
+ if (ret == -1) {
+ strncpy(real_path, cdc_device_path, real_path_size - 1);
+ } else if (ret == real_path_size - 1) {
+ real_path[real_path_size - 1] = 0;
+ }
+
+ return 0;
+}
+
+static int get_real_device_name(const char *cdc_device_path, char *real_name, size_t real_name_size)
+{
+ char real_device[PATH_MAX];
+ char *base;
+
+ get_real_device(cdc_device_path, real_device, PATH_MAX);
+ base = basename(real_device);
+ strncpy(real_name, base, real_name_size - 1);
+
+ return 0;
+}
+
+static int _get_wwan_device(const char *sysfs_path,
+ char *wwan_device, size_t wwan_device_size)
+{
+ DIR *dirfd;
+ int found = 0;
+ struct dirent *e;
+
+ dirfd = opendir(sysfs_path);
+ if (!dirfd) {
+ return -1;
+ }
+
+ while ((e = readdir(dirfd)) != NULL)
+ {
+ if (!strncmp(e->d_name, ".", strlen(e->d_name)) ||
+ !strncmp(e->d_name, "..", strlen(e->d_name)))
+ continue;
+
+ if (found == 0)
+ strncpy(wwan_device, e->d_name, wwan_device_size - 1);
+ found++;
+ }
+
+ closedir(dirfd);
+ return found;
+}
+
+/**
+ *
+ * @param modem
+ * @param wwan_device result if found
+ * @param wwan_device_size
+ * @return 0 on success
+ */
+int wwan_refresh_device(struct modem *modem)
+{
+ char sysfs_path[PATH_MAX];
+ char cdcname[128];
+ char wwan_device[17];
+ int ret;
+
+ get_real_device_name(modem->device, cdcname, sizeof(cdcname));
+ TALLOC_FREE(modem->wwan.dev);
+
+ /*
+ * usbmisc >= 3.6
+ * usb < 3.6
+ */
+ snprintf(&sysfs_path[0], sizeof(sysfs_path)-1, "/sys/class/%s/%s/device/net/", "usbmisc", cdcname);
+ ret = _get_wwan_device(sysfs_path, wwan_device, sizeof(wwan_device));
+ if (ret >= 1) {
+ snprintf(sysfs_path, sizeof(sysfs_path)-1, "/sys/class/%s/%s/device/net/%s", "usbmisc", cdcname, wwan_device);
+ modem->wwan.dev = talloc_strdup(modem, wwan_device);
+ modem->wwan.sysfs = talloc_strdup(modem, sysfs_path);
+ modem->subsystem_name = "usbmisc";
+ return 0;
+ }
+
+ snprintf(sysfs_path, sizeof(sysfs_path)-1, "/sys/class/%s/%s/device/net/", "usb", wwan_device);
+ ret = _get_wwan_device(sysfs_path, wwan_device, sizeof(wwan_device));
+ if (ret >= 1) {
+ snprintf(sysfs_path, sizeof(sysfs_path)-1, "/sys/class/%s/%s/device/net/%s", "usbmisc", cdcname, wwan_device);
+ modem->wwan.dev = talloc_strdup(modem, wwan_device);
+ modem->wwan.sysfs = talloc_strdup(modem, sysfs_path);
+ modem->subsystem_name = "usb";
+ return 0;
+ }
+
+ return -1;
+}
+
+/* read_uint_from_file from fstools under GPLv2 */
+static int
+read_char_from_file(const char *dirname, const char *filename, char *achar)
+{
+ FILE *f;
+ char fname[PATH_MAX];
+ int ret = -1;
+
+ snprintf(fname, sizeof(fname), "%s/%s", dirname, filename);
+ f = fopen(fname, "r");
+ if (!f)
+ return ret;
+
+ if (fread(achar, 1, 1, f) == 1)
+ ret = 0;
+
+ fclose(f);
+ return ret;
+}
+
+static int
+write_char_to_file(const char *dirname, const char *filename, const char *achar)
+{
+ FILE *f;
+ char fname[PATH_MAX];
+ int ret = -1;
+
+ snprintf(fname, sizeof(fname), "%s/%s", dirname, filename);
+
+ f = fopen(fname, "w");
+ if (!f)
+ return ret;
+
+ if (fwrite(achar, 1, 1, f) == 1)
+ ret = 0;
+
+ fclose(f);
+ return ret;
+}
+
+/* use ioctl until uqmid has netlink support */
+int wwan_set_mtu(const char *netif, unsigned int mtu)
+{
+ int sock, rc;
+ struct ifreq req;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return sock;
+
+ memset(&req, 0, sizeof req);
+ strncpy(req.ifr_name, netif, sizeof(req.ifr_name));
+
+ rc = ioctl(sock, SIOCGIFMTU, &req);
+ if (rc < 0) {
+ close(sock);
+ return rc;
+ }
+
+ if (req.ifr_mtu == mtu) {
+ close(sock);
+ return 0;
+ }
+
+ req.ifr_mtu = mtu;
+
+ rc = ioctl(sock, SIOCSIFMTU, &req);
+ close(sock);
+ return rc;
+}
+
+/* use ioctl until uqmid has netlink support */
+int wwan_ifupdown(const char *netif, bool up)
+{
+ int sock, rc;
+ struct ifreq req;
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0)
+ return sock;
+
+ memset(&req, 0, sizeof req);
+ strncpy(req.ifr_name, netif, sizeof(req.ifr_name));
+
+ rc = ioctl(sock, SIOCGIFFLAGS, &req);
+ if (rc < 0) {
+ close(sock);
+ return rc;
+ }
+
+ if ((req.ifr_flags & IFF_UP) == up) {
+ close(sock);
+ return 0;
+ }
+
+ if (up)
+ req.ifr_flags |= IFF_UP;
+ else
+ req.ifr_flags &= ~IFF_UP;
+
+ rc = ioctl(sock, SIOCSIFFLAGS, &req);
+ close(sock);
+ return rc;
+}
+
+int wwan_read_configuration(const char *sysfs_path, struct wwan_conf *config)
+{
+ char tmp = 0;
+ int ret;
+ char qmi_path[PATH_MAX];
+
+ snprintf(qmi_path, sizeof(qmi_path) - 1, "%s/qmi", sysfs_path);
+ ret = read_char_from_file(qmi_path, "raw_ip", &tmp);
+ if (ret)
+ return -ENOENT;
+ config->raw_ip = (tmp == 'Y');
+
+ ret = read_char_from_file(qmi_path, "pass_through", &tmp);
+ if (ret)
+ return -ENOENT;
+
+ config->pass_through = (tmp == 'Y');
+
+ return 0;
+}
+
+/**
+ *
+ * @param sysfs_path path to the network sysfs path
+ * @param config
+ * @return 0 on success
+ */
+int
+wwan_set_configuration(const char *sysfs_path, const struct wwan_conf *config)
+{
+ struct wwan_conf old = { 0 };
+ int ret;
+ char yes = 'Y';
+ char no = 'N';
+ char qmi_path[PATH_MAX];
+
+ snprintf(qmi_path, sizeof(qmi_path) - 1, "%s/qmi", sysfs_path);
+ ret = wwan_read_configuration(sysfs_path, &old);
+ if (ret) {
+ return -ENOENT;
+ }
+
+ if (config->raw_ip != old.raw_ip) {
+ ret = write_char_to_file(qmi_path, "raw_ip", config->raw_ip ? &yes : &no);
+ if (ret) {
+ return -EINVAL;
+ }
+ }
+
+ if (config->pass_through != old.pass_through) {
+ ret = write_char_to_file(qmi_path, "pass_through", config->pass_through ? &yes : &no);
+ if (ret) {
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
--- /dev/null
+/*
+ * uqmid -- implement a daemon based on the uqmi idea
+ *
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ */
+
+#include <stdbool.h>
+#include <stddef.h>
+
+struct modem;
+struct wwan_conf;
+
+int wwan_refresh_device(struct modem *modem);
+int wwan_read_configuration(const char *sysfs_path, struct wwan_conf *config);
+int wwan_set_configuration(const char *sysfs_path, const struct wwan_conf *config);
+int wwan_ifupdown(const char *netif, bool up);
+int wwan_set_mtu(const char *netif, unsigned int mtu);