From 95686425ce9775e983ea434ca187f3d66c083ea5 Mon Sep 17 00:00:00 2001 From: Alexander Couzens Date: Sat, 18 May 2024 14:33:40 +0100 Subject: [PATCH] uqmid: wwan: add support to handle kernel network interface The kernel network interface can have different modes - 802.3 - raw-ip - passthrough towards rmnet/qmap Currently uqmid will only support raw-ip. To support higher throughput it will use the rmnet ip interface with QMAP support. Signed-off-by: Alexander Couzens --- uqmid/CMakeLists.txt | 2 +- uqmid/modem.h | 18 +++ uqmid/modem_fsm.c | 125 +++++++++++++++++- uqmid/modem_fsm.h | 1 + uqmid/modem_tx.c | 27 ++++ uqmid/modem_tx.h | 1 + uqmid/wwan.c | 298 +++++++++++++++++++++++++++++++++++++++++++ uqmid/wwan.h | 32 +++++ 8 files changed, 500 insertions(+), 4 deletions(-) create mode 100644 uqmid/wwan.c create mode 100644 uqmid/wwan.h diff --git a/uqmid/CMakeLists.txt b/uqmid/CMakeLists.txt index d53096b..ebda892 100644 --- a/uqmid/CMakeLists.txt +++ b/uqmid/CMakeLists.txt @@ -1,7 +1,7 @@ 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}) diff --git a/uqmid/modem.h b/uqmid/modem.h index c31c966..fa4a827 100644 --- a/uqmid/modem.h +++ b/uqmid/modem.h @@ -39,12 +39,30 @@ struct modem_config { 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; diff --git a/uqmid/modem_fsm.c b/uqmid/modem_fsm.c index f86dae4..bc88311 100644 --- a/uqmid/modem_fsm.c +++ b/uqmid/modem_fsm.c @@ -16,9 +16,10 @@ #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)) @@ -26,6 +27,8 @@ /* Timeouts periods in seconds */ #define T_GET_VERSION_S 5 #define T_SYNC_S 3 +/* default timeout */ +#define T_DEFAULT_S 5 /* Timeout number */ @@ -722,12 +725,121 @@ static void modem_st_configure_modem(struct osmo_fsm_inst *fi, uint32_t event, v } 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; @@ -1178,11 +1290,18 @@ static const struct osmo_fsm_state modem_states[] = { .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) diff --git a/uqmid/modem_fsm.h b/uqmid/modem_fsm.h index c91ea4d..5eeb42a 100644 --- a/uqmid/modem_fsm.h +++ b/uqmid/modem_fsm.h @@ -10,6 +10,7 @@ enum modem_fsm_state { MODEM_ST_POWEROFF, MODEM_ST_UNLOCK_PIN, MODEM_ST_CONFIGURE_MODEM, + MODEM_ST_CONFIGURE_KERNEL, MODEM_ST_POWERON, MODEM_ST_NETSEARCH, MODEM_ST_REGISTERED, diff --git a/uqmid/modem_tx.c b/uqmid/modem_tx.c index f57a86a..edec7b9 100644 --- a/uqmid/modem_tx.c +++ b/uqmid/modem_tx.c @@ -38,6 +38,33 @@ #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); diff --git a/uqmid/modem_tx.h b/uqmid/modem_tx.h index 7b1b8fb..4f58e14 100644 --- a/uqmid/modem_tx.h +++ b/uqmid/modem_tx.h @@ -10,6 +10,7 @@ struct qmi_service; 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); diff --git a/uqmid/wwan.c b/uqmid/wwan.c new file mode 100644 index 0000000..543ef3a --- /dev/null +++ b/uqmid/wwan.c @@ -0,0 +1,298 @@ +/* + * uqmid -- implement a daemon based on the uqmi idea + * + * Copyright (C) 2023-2024 Alexander Couzens + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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; +} diff --git a/uqmid/wwan.h b/uqmid/wwan.h new file mode 100644 index 0000000..6882b51 --- /dev/null +++ b/uqmid/wwan.h @@ -0,0 +1,32 @@ +/* + * uqmid -- implement a daemon based on the uqmi idea + * + * Copyright (C) 2023-2024 Alexander Couzens + * + * 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 +#include + +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); -- 2.30.2