|
@@ -0,0 +1,471 @@
|
|
|
+/*
|
|
|
+ * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
|
|
|
+ *
|
|
|
+ * This program is free software; you can redistribute it and/or modify
|
|
|
+ * it under the terms of the GNU General Public License version 2 and
|
|
|
+ * only version 2 as published by the Free Software Foundation.
|
|
|
+ *
|
|
|
+ * This program 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 General Public License for more details.
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/netlink.h>
|
|
|
+#include <uapi/linux/rtnetlink.h>
|
|
|
+#include <linux/net.h>
|
|
|
+#include <net/sock.h>
|
|
|
+#include "dfc.h"
|
|
|
+#include "rmnet_qmi.h"
|
|
|
+#include "rmnet_qmap.h"
|
|
|
+#include "qmi_rmnet_i.h"
|
|
|
+
|
|
|
+#define QMAP_LL_VER 1
|
|
|
+#define QMAP_LL_MAX_BEARER 15
|
|
|
+
|
|
|
+#define QMAP_SWITCH_TO_LL 1
|
|
|
+#define QMAP_SWITCH_TO_DEFAULT 2
|
|
|
+#define QMAP_SWITCH_QUERY 3
|
|
|
+
|
|
|
+/* Switch status from modem */
|
|
|
+#define SWITCH_STATUS_ERROR 0
|
|
|
+#define SWITCH_STATUS_SUCCESS 1
|
|
|
+#define SWITCH_STATUS_DEFAULT 2
|
|
|
+#define SWITCH_STATUS_LL 3
|
|
|
+#define SWITCH_STATUS_FAIL_TEMP 4
|
|
|
+#define SWITCH_STATUS_FAIL_PERM 5
|
|
|
+
|
|
|
+/* Internal switch status */
|
|
|
+#define SWITCH_STATUS_NONE 0xFF
|
|
|
+#define SWITCH_STATUS_TIMEOUT 0xFE
|
|
|
+#define SWITCH_STATUS_NO_EFFECT 0xFD
|
|
|
+
|
|
|
+#define LL_MASK_NL_ACK 1
|
|
|
+#define LL_MASK_AUTO_RETRY 2
|
|
|
+
|
|
|
+#define LL_TIMEOUT (5 * HZ)
|
|
|
+#define LL_RETRY_TIME (10 * HZ)
|
|
|
+#define LL_MAX_RETRY (3)
|
|
|
+
|
|
|
+struct qmap_ll_bearer {
|
|
|
+ u8 bearer_id;
|
|
|
+ u8 status;
|
|
|
+ u8 reserved[2];
|
|
|
+} __aligned(1);
|
|
|
+
|
|
|
+struct qmap_ll_switch {
|
|
|
+ struct qmap_cmd_hdr hdr;
|
|
|
+ u8 cmd_ver;
|
|
|
+ u8 reserved;
|
|
|
+ u8 request_type;
|
|
|
+ u8 num_bearers;
|
|
|
+ struct qmap_ll_bearer bearer[0];
|
|
|
+} __aligned(1);
|
|
|
+
|
|
|
+struct qmap_ll_switch_resp {
|
|
|
+ struct qmap_cmd_hdr hdr;
|
|
|
+ u8 cmd_ver;
|
|
|
+ u8 reserved[2];
|
|
|
+ u8 num_bearers;
|
|
|
+ struct qmap_ll_bearer bearer[0];
|
|
|
+} __aligned(1);
|
|
|
+
|
|
|
+struct qmap_ll_switch_status {
|
|
|
+ struct qmap_cmd_hdr hdr;
|
|
|
+ u8 cmd_ver;
|
|
|
+ u8 reserved[2];
|
|
|
+ u8 num_bearers;
|
|
|
+ struct qmap_ll_bearer bearer[0];
|
|
|
+} __aligned(1);
|
|
|
+
|
|
|
+static void ll_send_nl_ack(struct rmnet_bearer_map *bearer)
|
|
|
+{
|
|
|
+ struct sk_buff *skb;
|
|
|
+ struct nlmsghdr *nlh;
|
|
|
+ struct nlmsgerr *errmsg;
|
|
|
+ unsigned int flags = NLM_F_CAPPED;
|
|
|
+
|
|
|
+ if (!(bearer->ch_switch.flags & LL_MASK_NL_ACK))
|
|
|
+ return;
|
|
|
+
|
|
|
+ skb = nlmsg_new(sizeof(*errmsg), GFP_ATOMIC);
|
|
|
+ if (!skb)
|
|
|
+ return;
|
|
|
+
|
|
|
+ nlh = __nlmsg_put(skb, bearer->ch_switch.nl_pid,
|
|
|
+ bearer->ch_switch.nl_seq, NLMSG_ERROR,
|
|
|
+ sizeof(*errmsg), flags);
|
|
|
+ errmsg = nlmsg_data(nlh);
|
|
|
+ errmsg->error = 0;
|
|
|
+ errmsg->msg.nlmsg_type = bearer->bearer_id;
|
|
|
+ errmsg->msg.nlmsg_flags = bearer->ch_switch.status_code;
|
|
|
+ errmsg->msg.nlmsg_seq = bearer->ch_switch.current_ch;
|
|
|
+ nlmsg_end(skb, nlh);
|
|
|
+
|
|
|
+ rtnl_unicast(skb, &init_net, bearer->ch_switch.nl_pid);
|
|
|
+}
|
|
|
+
|
|
|
+static void ll_qmap_maybe_set_ch(struct qos_info *qos,
|
|
|
+ struct rmnet_bearer_map *bearer, u8 status)
|
|
|
+{
|
|
|
+ u8 ch;
|
|
|
+
|
|
|
+ if (status == SWITCH_STATUS_DEFAULT)
|
|
|
+ ch = RMNET_CH_DEFAULT;
|
|
|
+ else if (status == SWITCH_STATUS_LL)
|
|
|
+ ch = RMNET_CH_LL;
|
|
|
+ else
|
|
|
+ return;
|
|
|
+
|
|
|
+ bearer->ch_switch.current_ch = ch;
|
|
|
+ if (bearer->mq_idx < MAX_MQ_NUM)
|
|
|
+ qos->mq[bearer->mq_idx].is_ll_ch = ch;
|
|
|
+}
|
|
|
+
|
|
|
+static void ll_switch_complete(struct rmnet_bearer_map *bearer, u8 status)
|
|
|
+{
|
|
|
+ bearer->ch_switch.status_code = status;
|
|
|
+
|
|
|
+ if (status == SWITCH_STATUS_FAIL_TEMP &&
|
|
|
+ bearer->ch_switch.retry_left) {
|
|
|
+ /* Temp failure retry */
|
|
|
+ bearer->ch_switch.state = CH_SWITCH_FAILED_RETRY;
|
|
|
+ mod_timer(&bearer->ch_switch.guard_timer,
|
|
|
+ jiffies + LL_RETRY_TIME);
|
|
|
+ bearer->ch_switch.retry_left--;
|
|
|
+ } else {
|
|
|
+ /* Success or permanent failure */
|
|
|
+ bearer->ch_switch.timer_quit = true;
|
|
|
+ del_timer(&bearer->ch_switch.guard_timer);
|
|
|
+ bearer->ch_switch.state = CH_SWITCH_NONE;
|
|
|
+ bearer->ch_switch.retry_left = 0;
|
|
|
+ ll_send_nl_ack(bearer);
|
|
|
+ bearer->ch_switch.flags = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static int ll_qmap_handle_switch_resp(struct sk_buff *skb)
|
|
|
+{
|
|
|
+ struct qmap_ll_switch_resp *cmd;
|
|
|
+ struct rmnet_bearer_map *bearer;
|
|
|
+ struct qos_info *qos;
|
|
|
+ struct net_device *dev;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (skb->len < sizeof(struct qmap_ll_switch_resp))
|
|
|
+ return QMAP_CMD_DONE;
|
|
|
+
|
|
|
+ cmd = (struct qmap_ll_switch_resp *)skb->data;
|
|
|
+ if (!cmd->num_bearers)
|
|
|
+ return QMAP_CMD_DONE;
|
|
|
+
|
|
|
+ if (skb->len < sizeof(*cmd) +
|
|
|
+ cmd->num_bearers * sizeof(struct qmap_ll_bearer))
|
|
|
+ return QMAP_CMD_DONE;
|
|
|
+
|
|
|
+ dev = rmnet_qmap_get_dev(cmd->hdr.mux_id);
|
|
|
+ if (!dev)
|
|
|
+ return QMAP_CMD_DONE;
|
|
|
+
|
|
|
+ qos = rmnet_get_qos_pt(dev);
|
|
|
+ if (!qos)
|
|
|
+ return QMAP_CMD_DONE;
|
|
|
+
|
|
|
+ trace_dfc_ll_switch("ACK", 0, cmd->num_bearers, cmd->bearer);
|
|
|
+
|
|
|
+ spin_lock_bh(&qos->qos_lock);
|
|
|
+
|
|
|
+ for (i = 0; i < cmd->num_bearers; i++) {
|
|
|
+ bearer = qmi_rmnet_get_bearer_map(qos,
|
|
|
+ cmd->bearer[i].bearer_id);
|
|
|
+ if (!bearer)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ ll_qmap_maybe_set_ch(qos, bearer, cmd->bearer[i].status);
|
|
|
+
|
|
|
+ if (bearer->ch_switch.state == CH_SWITCH_STARTED &&
|
|
|
+ bearer->ch_switch.switch_txid == cmd->hdr.tx_id) {
|
|
|
+ /* This is an ACK to the switch request */
|
|
|
+ if (cmd->bearer[i].status == SWITCH_STATUS_SUCCESS)
|
|
|
+ bearer->ch_switch.state = CH_SWITCH_ACKED;
|
|
|
+ else
|
|
|
+ ll_switch_complete(bearer,
|
|
|
+ cmd->bearer[i].status);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_unlock_bh(&qos->qos_lock);
|
|
|
+
|
|
|
+ return QMAP_CMD_DONE;
|
|
|
+}
|
|
|
+
|
|
|
+static int ll_qmap_handle_switch_status(struct sk_buff *skb)
|
|
|
+{
|
|
|
+ struct qmap_ll_switch_status *cmd;
|
|
|
+ struct rmnet_bearer_map *bearer;
|
|
|
+ struct qos_info *qos;
|
|
|
+ struct net_device *dev;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (skb->len < sizeof(struct qmap_ll_switch_status))
|
|
|
+ return QMAP_CMD_INVALID;
|
|
|
+
|
|
|
+ cmd = (struct qmap_ll_switch_status *)skb->data;
|
|
|
+ if (!cmd->num_bearers)
|
|
|
+ return QMAP_CMD_ACK;
|
|
|
+
|
|
|
+ if (skb->len < sizeof(*cmd) +
|
|
|
+ cmd->num_bearers * sizeof(struct qmap_ll_bearer))
|
|
|
+ return QMAP_CMD_INVALID;
|
|
|
+
|
|
|
+ dev = rmnet_qmap_get_dev(cmd->hdr.mux_id);
|
|
|
+ if (!dev)
|
|
|
+ return QMAP_CMD_ACK;
|
|
|
+
|
|
|
+ qos = rmnet_get_qos_pt(dev);
|
|
|
+ if (!qos)
|
|
|
+ return QMAP_CMD_ACK;
|
|
|
+
|
|
|
+ trace_dfc_ll_switch("STS", 0, cmd->num_bearers, cmd->bearer);
|
|
|
+
|
|
|
+ spin_lock_bh(&qos->qos_lock);
|
|
|
+
|
|
|
+ for (i = 0; i < cmd->num_bearers; i++) {
|
|
|
+ bearer = qmi_rmnet_get_bearer_map(qos,
|
|
|
+ cmd->bearer[i].bearer_id);
|
|
|
+ if (!bearer)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ ll_qmap_maybe_set_ch(qos, bearer, cmd->bearer[i].status);
|
|
|
+
|
|
|
+ if (bearer->ch_switch.state == CH_SWITCH_ACKED)
|
|
|
+ ll_switch_complete(bearer, cmd->bearer[i].status);
|
|
|
+ }
|
|
|
+
|
|
|
+ spin_unlock_bh(&qos->qos_lock);
|
|
|
+
|
|
|
+ return QMAP_CMD_ACK;
|
|
|
+}
|
|
|
+
|
|
|
+int ll_qmap_cmd_handler(struct sk_buff *skb)
|
|
|
+{
|
|
|
+ struct qmap_cmd_hdr *cmd;
|
|
|
+ int rc = QMAP_CMD_DONE;
|
|
|
+
|
|
|
+ cmd = (struct qmap_cmd_hdr *)skb->data;
|
|
|
+
|
|
|
+ if (cmd->cmd_name == QMAP_LL_SWITCH) {
|
|
|
+ if (cmd->cmd_type != QMAP_CMD_ACK)
|
|
|
+ return rc;
|
|
|
+ } else if (cmd->cmd_type != QMAP_CMD_REQUEST) {
|
|
|
+ return rc;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (cmd->cmd_name) {
|
|
|
+ case QMAP_LL_SWITCH:
|
|
|
+ rc = ll_qmap_handle_switch_resp(skb);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case QMAP_LL_SWITCH_STATUS:
|
|
|
+ rc = ll_qmap_handle_switch_status(skb);
|
|
|
+ break;
|
|
|
+
|
|
|
+ default:
|
|
|
+ if (cmd->cmd_type == QMAP_CMD_REQUEST)
|
|
|
+ rc = QMAP_CMD_UNSUPPORTED;
|
|
|
+ }
|
|
|
+
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+static int ll_qmap_send_switch(u8 mux_id, u8 channel, u8 num_bearers,
|
|
|
+ u8 *bearer_list, __be32 *txid)
|
|
|
+{
|
|
|
+ struct sk_buff *skb;
|
|
|
+ struct qmap_ll_switch *ll_switch;
|
|
|
+ unsigned int len;
|
|
|
+ int i;
|
|
|
+
|
|
|
+ if (!num_bearers || num_bearers > QMAP_LL_MAX_BEARER || !bearer_list)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ len = sizeof(struct qmap_ll_switch) +
|
|
|
+ num_bearers * sizeof(struct qmap_ll_bearer);
|
|
|
+
|
|
|
+ skb = alloc_skb(len, GFP_ATOMIC);
|
|
|
+ if (!skb)
|
|
|
+ return -ENOMEM;
|
|
|
+
|
|
|
+ skb->protocol = htons(ETH_P_MAP);
|
|
|
+ ll_switch = skb_put(skb, len);
|
|
|
+ memset(ll_switch, 0, len);
|
|
|
+
|
|
|
+ ll_switch->hdr.cd_bit = 1;
|
|
|
+ ll_switch->hdr.mux_id = mux_id;
|
|
|
+ ll_switch->hdr.pkt_len = htons(len - QMAP_HDR_LEN);
|
|
|
+ ll_switch->hdr.cmd_name = QMAP_LL_SWITCH;
|
|
|
+ ll_switch->hdr.cmd_type = QMAP_CMD_REQUEST;
|
|
|
+ ll_switch->hdr.tx_id = htonl(rmnet_qmap_next_txid());
|
|
|
+
|
|
|
+ ll_switch->cmd_ver = QMAP_LL_VER;
|
|
|
+ if (channel == RMNET_CH_CTL)
|
|
|
+ ll_switch->request_type = QMAP_SWITCH_QUERY;
|
|
|
+ else if (channel == RMNET_CH_LL)
|
|
|
+ ll_switch->request_type = QMAP_SWITCH_TO_LL;
|
|
|
+ else
|
|
|
+ ll_switch->request_type = QMAP_SWITCH_TO_DEFAULT;
|
|
|
+ ll_switch->num_bearers = num_bearers;
|
|
|
+ for (i = 0; i < num_bearers; i++)
|
|
|
+ ll_switch->bearer[i].bearer_id = bearer_list[i];
|
|
|
+
|
|
|
+ if (txid)
|
|
|
+ *txid = ll_switch->hdr.tx_id;
|
|
|
+
|
|
|
+ trace_dfc_ll_switch("REQ", ll_switch->request_type,
|
|
|
+ ll_switch->num_bearers, ll_switch->bearer);
|
|
|
+
|
|
|
+ return rmnet_qmap_send(skb, RMNET_CH_CTL, false);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Start channel switch. The switch request is sent only if all bearers
|
|
|
+ * are eligible to switch. Return 0 if switch request is sent.
|
|
|
+ */
|
|
|
+int rmnet_ll_switch(struct net_device *dev, struct tcmsg *tcm, int attrlen)
|
|
|
+{
|
|
|
+ u8 switch_to_ch;
|
|
|
+ u8 num_bearers;
|
|
|
+ u8 *bearer_list;
|
|
|
+ u32 flags;
|
|
|
+ struct qos_info *qos;
|
|
|
+ struct rmnet_bearer_map *bearer;
|
|
|
+ __be32 txid;
|
|
|
+ int i;
|
|
|
+ int j;
|
|
|
+ int rc = -EINVAL;
|
|
|
+
|
|
|
+ if (!dev || !tcm)
|
|
|
+ return -EINVAL;
|
|
|
+ /*
|
|
|
+ * tcm__pad1: switch type (ch #, 0xFF query)
|
|
|
+ * tcm__pad2: num bearers
|
|
|
+ * tcm_info: flags
|
|
|
+ * tcm_ifindex: netlink fd
|
|
|
+ * tcm_handle: pid
|
|
|
+ * tcm_parent: seq
|
|
|
+ */
|
|
|
+
|
|
|
+ switch_to_ch = tcm->tcm__pad1;
|
|
|
+ num_bearers = tcm->tcm__pad2;
|
|
|
+ flags = tcm->tcm_info;
|
|
|
+
|
|
|
+ if (switch_to_ch != RMNET_CH_CTL && switch_to_ch >= RMNET_CH_MAX)
|
|
|
+ return -EOPNOTSUPP;
|
|
|
+ if (!num_bearers || num_bearers > QMAP_LL_MAX_BEARER)
|
|
|
+ return -EINVAL;
|
|
|
+ if (attrlen - sizeof(*tcm) < num_bearers)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ bearer_list = (u8 *)tcm + sizeof(*tcm);
|
|
|
+
|
|
|
+ for (i = 0; i < num_bearers; i++)
|
|
|
+ for (j = 0; j < num_bearers; j++)
|
|
|
+ if (j != i && bearer_list[i] == bearer_list[j])
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ qos = rmnet_get_qos_pt(dev);
|
|
|
+ if (!qos)
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ spin_lock_bh(&qos->qos_lock);
|
|
|
+
|
|
|
+ /* Validate the bearer list */
|
|
|
+ for (i = 0; i < num_bearers; i++) {
|
|
|
+ bearer = qmi_rmnet_get_bearer_map(qos, bearer_list[i]);
|
|
|
+ if (!bearer) {
|
|
|
+ rc = -EFAULT;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ if (bearer->ch_switch.state != CH_SWITCH_NONE) {
|
|
|
+ rc = -EBUSY;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Send QMAP switch command */
|
|
|
+ rc = ll_qmap_send_switch(qos->mux_id, switch_to_ch,
|
|
|
+ num_bearers, bearer_list, &txid);
|
|
|
+ if (rc)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ /* Update state */
|
|
|
+ for (i = 0; i < num_bearers; i++) {
|
|
|
+ bearer = qmi_rmnet_get_bearer_map(qos, bearer_list[i]);
|
|
|
+ if (!bearer)
|
|
|
+ continue;
|
|
|
+ bearer->ch_switch.switch_to_ch = switch_to_ch;
|
|
|
+ bearer->ch_switch.switch_txid = txid;
|
|
|
+ bearer->ch_switch.state = CH_SWITCH_STARTED;
|
|
|
+ bearer->ch_switch.status_code = SWITCH_STATUS_NONE;
|
|
|
+ bearer->ch_switch.retry_left =
|
|
|
+ (flags & LL_MASK_AUTO_RETRY) ? LL_MAX_RETRY : 0;
|
|
|
+ bearer->ch_switch.flags = flags;
|
|
|
+ bearer->ch_switch.timer_quit = false;
|
|
|
+ mod_timer(&bearer->ch_switch.guard_timer,
|
|
|
+ jiffies + LL_TIMEOUT);
|
|
|
+
|
|
|
+ bearer->ch_switch.nl_pid = tcm->tcm_handle;
|
|
|
+ bearer->ch_switch.nl_seq = tcm->tcm_parent;
|
|
|
+ }
|
|
|
+out:
|
|
|
+ spin_unlock_bh(&qos->qos_lock);
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+void rmnet_ll_guard_fn(struct timer_list *t)
|
|
|
+{
|
|
|
+ struct rmnet_ch_switch *ch_switch;
|
|
|
+ struct rmnet_bearer_map *bearer;
|
|
|
+ int switch_status = SWITCH_STATUS_TIMEOUT;
|
|
|
+ __be32 txid;
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ ch_switch = container_of(t, struct rmnet_ch_switch, guard_timer);
|
|
|
+ bearer = container_of(ch_switch, struct rmnet_bearer_map, ch_switch);
|
|
|
+
|
|
|
+ spin_lock_bh(&bearer->qos->qos_lock);
|
|
|
+
|
|
|
+ if (bearer->ch_switch.timer_quit ||
|
|
|
+ bearer->ch_switch.state == CH_SWITCH_NONE)
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ if (bearer->ch_switch.state == CH_SWITCH_FAILED_RETRY) {
|
|
|
+ if (bearer->ch_switch.current_ch ==
|
|
|
+ bearer->ch_switch.switch_to_ch) {
|
|
|
+ switch_status = SWITCH_STATUS_NO_EFFECT;
|
|
|
+ goto send_err;
|
|
|
+ }
|
|
|
+
|
|
|
+ rc = ll_qmap_send_switch(bearer->qos->mux_id,
|
|
|
+ bearer->ch_switch.switch_to_ch,
|
|
|
+ 1,
|
|
|
+ &bearer->bearer_id,
|
|
|
+ &txid);
|
|
|
+ if (!rc) {
|
|
|
+ bearer->ch_switch.switch_txid = txid;
|
|
|
+ bearer->ch_switch.state = CH_SWITCH_STARTED;
|
|
|
+ bearer->ch_switch.status_code = SWITCH_STATUS_NONE;
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+send_err:
|
|
|
+ bearer->ch_switch.state = CH_SWITCH_NONE;
|
|
|
+ bearer->ch_switch.status_code = switch_status;
|
|
|
+ bearer->ch_switch.retry_left = 0;
|
|
|
+ ll_send_nl_ack(bearer);
|
|
|
+ bearer->ch_switch.flags = 0;
|
|
|
+
|
|
|
+out:
|
|
|
+ spin_unlock_bh(&bearer->qos->qos_lock);
|
|
|
+}
|