diff --git a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
index 617c022a24c3..295c3c532c93 100644
--- a/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
+++ b/drivers/net/wwan/t9xx/mtk_ctrl_plane.h
@@ -21,6 +21,10 @@ enum mtk_ccci_ch {
/* to MD */
CCCI_CONTROL_RX = 0x2000,
CCCI_CONTROL_TX = 0x2001,
+ CCCI_UART2_RX = 0x200A,
+ CCCI_UART2_TX = 0x200C,
+ CCCI_MBIM_RX = 0x20D0,
+ CCCI_MBIM_TX = 0x20D1,
};
enum mtk_trb_cmd_type {diff --git a/drivers/net/wwan/t9xx/mtk_port.c b/drivers/net/wwan/t9xx/mtk_port.c
index 101ec14fc993..a0defeec9abf 100644
--- a/drivers/net/wwan/t9xx/mtk_port.c
+++ b/drivers/net/wwan/t9xx/mtk_port.c
@@ -285,6 +285,13 @@ static int mtk_port_tx_complete(struct sk_buff *skb)
"Failed to send data: status:%d, port:%s\n",
trb->status, port->info.name);
+ /* The hook may sleep, but it also runs from HIF teardown with
+ * trans->submit_lock held, so it must not take a lock that is ever
+ * held across a call into the HIF.
+ */
+ if (mtk_port_get_ops(port->info.type)->tx_complete)
+ mtk_port_get_ops(port->info.type)->tx_complete(port);
+
/* Pairs with the smp_load_acquire() of trb->done in the waiters. */
smp_store_release(&trb->done, true);
wake_up_all(&port->trb_wq);
@@ -626,6 +633,27 @@ int mtk_port_ch_disable(struct mtk_port *port)
return ret;
}
+static void mtk_port_enable_by_type(struct mtk_port_mngr *port_mngr, int tbl_type)
+{
+ struct radix_tree_iter iter;
+ struct mtk_port *port;
+ void __rcu **slot;
+
+ radix_tree_for_each_slot(slot, &port_mngr->port_tbl[tbl_type],
+ &iter, 0) {
+ port = mtk_port_tbl_slot(&slot, &iter);
+ if (!port)
+ continue;
+ /* Consumed, so a later boot acts only on what its own port
+ * enumeration advertised.
+ */
+ if (port->enable) {
+ port->enable = false;
+ mtk_port_get_ops(port->info.type)->enable(port);
+ }
+ }
+}
+
static void mtk_port_disable(struct mtk_port_mngr *port_mngr)
{
struct radix_tree_iter iter;@@ -658,6 +686,9 @@ void mtk_port_mngr_fsm_state_handler(struct mtk_fsm_param *fsm_param, void *arg)
case FSM_STATE_OFF:
mtk_port_disable(port_mngr);
break;
+ case FSM_STATE_READY:
+ mtk_port_enable_by_type(port_mngr, PORT_TBL_MD);
+ break;
default:
break;
}
diff --git a/drivers/net/wwan/t9xx/mtk_port.h b/drivers/net/wwan/t9xx/mtk_port.h
index d160f73c1834..0951c38359e1 100644
--- a/drivers/net/wwan/t9xx/mtk_port.h
+++ b/drivers/net/wwan/t9xx/mtk_port.h
@@ -11,6 +11,7 @@
#include <linux/radix-tree.h>
#include <linux/skbuff.h>
#include <linux/types.h>
+#include <linux/wwan.h>
#include "mtk_ctrl_plane.h"
#include "mtk_dev.h"
@@ -58,6 +59,7 @@ enum mtk_port_tbl {
enum mtk_port_type {
PORT_TYPE_INTERNAL,
+ PORT_TYPE_WWAN,
PORT_TYPE_MAX
};
@@ -66,6 +68,17 @@ struct mtk_internal_port {
int (*recv_cb)(void *arg, struct sk_buff *skb);
};
+struct mtk_wwan_port {
+ /* w_lock serialises w_port and PORT_S_OPEN against the receive path,
+ * and txoff/txon against TX completion. It is a leaf: TX completion
+ * can take it with trans->submit_lock held, so it is never held
+ * across a call into the HIF.
+ */
+ struct mutex w_lock;
+ enum wwan_port_type w_type;
+ struct wwan_port *w_port;
+};
+
struct mtk_port_cfg {
enum mtk_ccci_ch tx_ch;
enum mtk_ccci_ch rx_ch;@@ -91,6 +104,7 @@ struct mtk_port {
wait_queue_head_t trb_wq;
struct mtk_port_mngr *port_mngr;
struct mtk_internal_port i_priv;
+ struct mtk_wwan_port w_priv;
};
struct mtk_port_mngr {diff --git a/drivers/net/wwan/t9xx/mtk_port_io.c b/drivers/net/wwan/t9xx/mtk_port_io.c
index e92ce1cb7c00..d5232cb150bf 100644
--- a/drivers/net/wwan/t9xx/mtk_port_io.c
+++ b/drivers/net/wwan/t9xx/mtk_port_io.c
@@ -3,8 +3,13 @@
* Copyright (c) 2022, MediaTek Inc.
*/
#include <linux/netdevice.h>
+#include <linux/sched/signal.h>
+#include <linux/slab.h>
+#include <linux/wait.h>
+#include <linux/wwan.h>
#include "mtk_port_io.h"
+#include "mtk_trans_ctrl.h"
static void mtk_port_put_locked(struct mtk_port *port)
{@@ -20,6 +25,122 @@ static void mtk_port_struct_init(struct mtk_port *port)
init_waitqueue_head(&port->trb_wq);
}
+/* Packets after the first carry force_send, which bypasses the HIF depth
+ * check, so one write() must not exceed one queue depth.
+ */
+#define MTK_PORT_MAX_TX_PKT SKB_LIST_MAX_LEN
+
+/* A blocking write waits here rather than have its first packet refused by
+ * the depth check: a refused packet is freed with its trb and cannot be
+ * submitted again. TX completions and port state changes wake trb_wq.
+ */
+static int mtk_port_wait_tx_space(struct mtk_port *port)
+{
+ union ctrl_hif_cmd_data hif_cmd = { .rx_ch = port->info.rx_ch };
+ struct mtk_md_dev *mdev = port->port_mngr->ctrl_blk->mdev;
+ DEFINE_WAIT_FUNC(wait, woken_wake_function);
+ int ret = 0;
+
+ add_wait_queue(&port->trb_wq, &wait);
+ /* The check takes submit_lock, so it cannot be a wait_event()
+ * condition. An error from it is left to the submit to report.
+ */
+ while (mtk_pcie_hif_cmd_func(mdev, HIF_CTRL_CMD_CHECK_TX_FULL, &hif_cmd) > 0 &&
+ !test_bit(PORT_S_FLUSH, &port->status) &&
+ test_bit(PORT_S_WR, &port->status)) {
+ if (signal_pending(current)) {
+ ret = -ERESTARTSYS;
+ break;
+ }
+ wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
+ }
+ remove_wait_queue(&port->trb_wq, &wait);
+
+ return ret;
+}
+
+/* Every packet is built before any is submitted: the WWAN core turns a
+ * partial write into a whole-write failure and userspace re-sends the whole
+ * message, so a submit that fails midway leaves a prefix on the wire. It
+ * cannot be retried here - the skb is freed with its trb - so it is logged.
+ *
+ * -EINTR means the skb was already submitted, so that packet counts as
+ * accepted and the remaining ones are still sent.
+ */
+static int mtk_port_common_write(struct mtk_port *port, struct sk_buff *src, bool blocking)
+{
+ u32 packet_size, left_cnt = src->len, cur_pos;
+ u32 payload_mtu, sent_len = 0;
+ struct sk_buff_head list;
+ bool force_send = false;
+ struct sk_buff *skb;
+ int ret;
+
+ ret = mtk_port_status_check(port);
+ if (ret)
+ return ret;
+
+ payload_mtu = port->tx_mtu - sizeof(struct mtk_ccci_header);
+ if (src->len > MTK_PORT_MAX_TX_PKT * payload_mtu)
+ return -EMSGSIZE;
+
+ if (blocking) {
+ ret = mtk_port_wait_tx_space(port);
+ if (ret)
+ return ret;
+ }
+
+ __skb_queue_head_init(&list);
+
+ while (left_cnt) {
+ skb = __dev_alloc_skb(port->tx_mtu, GFP_KERNEL);
+ if (!skb) {
+ ret = -ENOMEM;
+ goto err_purge;
+ }
+
+ skb_reserve(skb, sizeof(struct mtk_ccci_header));
+
+ packet_size = min_t(u32, left_cnt, payload_mtu);
+ cur_pos = src->len - left_cnt;
+ ret = skb_copy_bits(src, cur_pos, skb_put(skb, packet_size), packet_size);
+ if (ret) {
+ dev_err(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to copy data for port(%s)\n", port->info.name);
+ dev_kfree_skb_any(skb);
+ goto err_purge;
+ }
+
+ __skb_queue_tail(&list, skb);
+ left_cnt -= packet_size;
+ }
+
+ while ((skb = __skb_dequeue(&list))) {
+ /* mtk_port_send_data() may free the skb, so sample its length
+ * while it is still valid.
+ */
+ packet_size = skb->len;
+
+ ret = mtk_port_send_data(port, skb, blocking, force_send);
+ if (ret < 0 && ret != -EINTR)
+ goto err_submit;
+
+ sent_len += packet_size;
+ force_send = true;
+ }
+
+ return 0;
+
+err_submit:
+ if (sent_len)
+ dev_err_ratelimited(port->port_mngr->ctrl_blk->mdev->dev,
+ "Partial write on port(%s): %u of %u bytes sent, error:%d\n",
+ port->info.name, sent_len, src->len, ret);
+err_purge:
+ __skb_queue_purge(&list);
+ return ret;
+}
+
static int mtk_port_internal_init(struct mtk_port *port)
{
mtk_port_struct_init(port);@@ -219,8 +340,300 @@ static const struct mtk_port_ops port_internal_ops = {
.recv = mtk_port_internal_recv,
};
+static void mtk_port_get_locked(struct mtk_port *port)
+{
+ mutex_lock(&mtk_port_mngr_grp_mtx);
+ kref_get(&port->kref);
+ mutex_unlock(&mtk_port_mngr_grp_mtx);
+}
+
+static int mtk_port_wwan_open(struct wwan_port *w_port)
+{
+ struct mtk_port *port;
+ int ret;
+
+ port = wwan_port_get_drvdata(w_port);
+ mtk_port_get_locked(port);
+
+ /* mtk_port_common_open() is what sets PORT_S_OPEN, and the RX path
+ * tests it together with w_port, so both are published under w_lock.
+ */
+ mutex_lock(&port->w_priv.w_lock);
+ ret = mtk_port_common_open(port);
+ if (!ret)
+ port->w_priv.w_port = w_port;
+ mutex_unlock(&port->w_priv.w_lock);
+ if (ret) {
+ mtk_port_put_locked(port);
+ return ret;
+ }
+
+ /* WWAN_PORT_TX_OFF persists on the wwan_port across close/open
+ * cycles; start every session writable.
+ */
+ wwan_port_txon(w_port);
+
+ return 0;
+}
+
+static void mtk_port_wwan_close(struct wwan_port *w_port)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+
+ /* Clear PORT_S_OPEN under the same lock mtk_port_wwan_recv() holds, so
+ * a receive that saw the port open has finished its wwan_port_rx()
+ * before stop() returns and the core purges its rx queue.
+ */
+ mutex_lock(&port->w_priv.w_lock);
+ mtk_port_common_close(port);
+ mutex_unlock(&port->w_priv.w_lock);
+
+ mtk_port_put_locked(port);
+}
+
+/* w_lock closes the txoff-after-drain window described in
+ * mtk_port_wwan_tx_pause().
+ */
+static void mtk_port_wwan_tx_complete(struct mtk_port *port)
+{
+ mutex_lock(&port->w_priv.w_lock);
+ if (port->w_priv.w_port)
+ wwan_port_txon(port->w_priv.w_port);
+ mutex_unlock(&port->w_priv.w_lock);
+}
+
+/* Pause TX after a queue-full submit failure. The queue may have drained
+ * between that failure and the txoff below, and that tx_complete is lost, so
+ * recheck and undo the txoff if the queue is no longer full.
+ *
+ * w_lock is dropped for the recheck to keep it a leaf with respect to
+ * trans->submit_lock. A tx_complete landing in the gap can only add a txon,
+ * so TX is never left off with nothing to wake it.
+ */
+static void mtk_port_wwan_tx_pause(struct mtk_port *port)
+{
+ union ctrl_hif_cmd_data hif_cmd;
+ struct mtk_ctrl_blk *ctrl_blk;
+ int ret;
+
+ ctrl_blk = port->port_mngr->ctrl_blk;
+
+ mutex_lock(&port->w_priv.w_lock);
+ if (!port->w_priv.w_port) {
+ mutex_unlock(&port->w_priv.w_lock);
+ return;
+ }
+
+ wwan_port_txoff(port->w_priv.w_port);
+ mutex_unlock(&port->w_priv.w_lock);
+
+ hif_cmd.rx_ch = port->info.rx_ch;
+ ret = mtk_pcie_hif_cmd_func(ctrl_blk->mdev, HIF_CTRL_CMD_CHECK_TX_FULL,
+ &hif_cmd);
+ if (ret <= 0)
+ mtk_port_wwan_tx_complete(port);
+}
+
+static int mtk_port_wwan_tx(struct wwan_port *w_port, struct sk_buff *skb, bool blocking)
+{
+ struct mtk_port *port = wwan_port_get_drvdata(w_port);
+ int ret;
+
+ if (unlikely(!skb->len)) {
+ consume_skb(skb);
+ return 0;
+ }
+
+ ret = mtk_port_common_write(port, skb, blocking);
+ if (ret < 0) {
+ if (ret == -EAGAIN)
+ mtk_port_wwan_tx_pause(port);
+ return ret;
+ }
+
+ consume_skb(skb);
+ return 0;
+}
+
+static int mtk_port_wwan_write(struct wwan_port *w_port, struct sk_buff *skb)
+{
+ return mtk_port_wwan_tx(w_port, skb, false);
+}
+
+static int mtk_port_wwan_write_blocking(struct wwan_port *w_port, struct sk_buff *skb)
+{
+ return mtk_port_wwan_tx(w_port, skb, true);
+}
+
+/* No .tx_poll: it would poll_wait() on port->trb_wq, which lives in a
+ * mtk_port that wwan_remove_port() lets go of while the file stays open,
+ * and wake_up_pollfree() is not available to modules. TX back-pressure
+ * reaches poll() through wwan_port_txon()/txoff() on the core's own
+ * waitqueue instead, whose lifetime is pinned by the open file.
+ */
+static const struct wwan_port_ops wwan_ops = {
+ .start = mtk_port_wwan_open,
+ .stop = mtk_port_wwan_close,
+ .tx = mtk_port_wwan_write,
+ .tx_blocking = mtk_port_wwan_write_blocking,
+};
+
+static int mtk_port_wwan_init(struct mtk_port *port)
+{
+ mtk_port_struct_init(port);
+ port->enable = false;
+
+ mutex_init(&port->w_priv.w_lock);
+
+ switch (port->info.rx_ch) {
+ case CCCI_MBIM_RX:
+ port->w_priv.w_type = WWAN_PORT_MBIM;
+ break;
+ case CCCI_UART2_RX:
+ port->w_priv.w_type = WWAN_PORT_AT;
+ break;
+ default:
+ port->w_priv.w_type = WWAN_PORT_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static void mtk_port_wwan_exit(struct mtk_port *port)
+{
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ mtk_port_get_ops(port->info.type)->disable(port);
+ mutex_destroy(&port->w_priv.w_lock);
+}
+
+static void mtk_port_wwan_enable(struct mtk_port *port)
+{
+ struct mtk_port_mngr *port_mngr;
+ struct wwan_port_caps caps;
+ struct wwan_port *wp;
+ int ret;
+
+ port_mngr = port->port_mngr;
+
+ if (test_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ ret = mtk_port_ch_enable(port);
+ if (ret && ret != -EBUSY) {
+ /* On -ETIMEDOUT the enable's outcome is not yet known: the
+ * ENABLE trb may still be queued. The DISABLE is queued
+ * behind it, so the channel cannot stay armed unowned.
+ */
+ mtk_port_ch_disable(port);
+ return;
+ }
+
+ /* A zero frag_len would make wwan_port_fops_write() loop forever. */
+ if (port->tx_mtu <= sizeof(struct mtk_ccci_header)) {
+ dev_err(port_mngr->ctrl_blk->mdev->dev,
+ "Invalid tx_mtu for port(%s)\n", port->info.name);
+ mtk_port_ch_disable(port);
+ return;
+ }
+
+ /* The core allocates frag_len + headroom_len and skb_put()s frag_len,
+ * so frag_len is the payload budget: subtract the CCCI header to make
+ * one core fragment exactly one CCCI packet.
+ */
+ caps.frag_len = port->tx_mtu - sizeof(struct mtk_ccci_header);
+ caps.headroom_len = sizeof(struct mtk_ccci_header);
+
+ /* These bits must be set before wwan_create_port(): the device node
+ * becomes openable inside it and mtk_port_common_open() rejects a
+ * port without PORT_S_ENABLE.
+ */
+ set_bit(PORT_S_WR, &port->status);
+ set_bit(PORT_S_ENABLE, &port->status);
+
+ wp = wwan_create_port(port_mngr->ctrl_blk->mdev->dev,
+ port->w_priv.w_type,
+ &wwan_ops, &caps, port);
+ if (IS_ERR(wp)) {
+ dev_warn(port_mngr->ctrl_blk->mdev->dev,
+ "Failed to create wwan port for (%s)\n", port->info.name);
+ clear_bit(PORT_S_ENABLE, &port->status);
+ clear_bit(PORT_S_WR, &port->status);
+ mtk_port_ch_disable(port);
+ return;
+ }
+
+ /* .start publishes w_port for an opened node; this covers the node
+ * nobody opened, which still needs w_port to be removed.
+ */
+ mutex_lock(&port->w_priv.w_lock);
+ port->w_priv.w_port = wp;
+ mutex_unlock(&port->w_priv.w_lock);
+}
+
+static void mtk_port_wwan_disable(struct mtk_port *port)
+{
+ struct wwan_port *w_port;
+ int ret;
+
+ if (!test_and_clear_bit(PORT_S_ENABLE, &port->status))
+ return;
+
+ /* PORT_S_WR is in the blocking TX wait condition: wake the waiters. */
+ clear_bit(PORT_S_WR, &port->status);
+ wake_up_all(&port->trb_wq);
+
+ /* w_lock must be dropped before wwan_remove_port(): that takes the
+ * core's ops_lock and forces stop(), which is close() taking w_lock.
+ */
+ mutex_lock(&port->w_priv.w_lock);
+ w_port = port->w_priv.w_port;
+ port->w_priv.w_port = NULL;
+ mutex_unlock(&port->w_priv.w_lock);
+
+ ret = mtk_port_ch_disable(port);
+ if (ret)
+ dev_warn(port->port_mngr->ctrl_blk->mdev->dev,
+ "Failed to disable channel for port(%s): %d\n",
+ port->info.name, ret);
+
+ wwan_remove_port(w_port);
+}
+
+static int mtk_port_wwan_recv(struct mtk_port *port, struct sk_buff *skb)
+{
+ /* Drop frames when nobody has the device open: wwan_port_rx() queues
+ * without bound and only a reader drains the queue, so accepting
+ * unsolicited traffic here would grow the rxq indefinitely. Both
+ * conditions are read under w_lock, which mtk_port_wwan_close() also
+ * takes, so a frame accepted here cannot land on a queue the core is
+ * about to purge.
+ */
+ mutex_lock(&port->w_priv.w_lock);
+ if (!test_bit(PORT_S_OPEN, &port->status) || !port->w_priv.w_port) {
+ mutex_unlock(&port->w_priv.w_lock);
+ dev_dbg_ratelimited(port->port_mngr->ctrl_blk->mdev->dev,
+ "Drop RX for unopened port(%s)\n", port->info.name);
+ return -ENXIO;
+ }
+
+ wwan_port_rx(port->w_priv.w_port, skb);
+ mutex_unlock(&port->w_priv.w_lock);
+ return 0;
+}
+
+static const struct mtk_port_ops port_wwan_ops = {
+ .init = mtk_port_wwan_init,
+ .exit = mtk_port_wwan_exit,
+ .enable = mtk_port_wwan_enable,
+ .disable = mtk_port_wwan_disable,
+ .recv = mtk_port_wwan_recv,
+ .tx_complete = mtk_port_wwan_tx_complete,
+};
+
static const struct mtk_port_ops *mtk_port_ops_tbl[PORT_TYPE_MAX] = {
&port_internal_ops,
+ &port_wwan_ops,
};
const struct mtk_port_ops *mtk_port_get_ops(enum mtk_port_type type)diff --git a/drivers/net/wwan/t9xx/mtk_port_io.h b/drivers/net/wwan/t9xx/mtk_port_io.h
index db1d9ae52d9a..e1e695100ab1 100644
--- a/drivers/net/wwan/t9xx/mtk_port_io.h
+++ b/drivers/net/wwan/t9xx/mtk_port_io.h
@@ -18,6 +18,7 @@ struct mtk_port_ops {
void (*enable)(struct mtk_port *port);
void (*disable)(struct mtk_port *port);
int (*recv)(struct mtk_port *port, struct sk_buff *skb);
+ void (*tx_complete)(struct mtk_port *port);
};
const struct mtk_port_ops *mtk_port_get_ops(enum mtk_port_type type);diff --git a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
index d87cb61d5326..7492a797b682 100644
--- a/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
+++ b/drivers/net/wwan/t9xx/pcie/mtk_trans_ctrl.c
@@ -26,6 +26,10 @@ static const int mtk_srv_cfg[NR_CLDMA][HW_QUE_NUM] = {
/* the number of RX GPDs should be at least two */
static const struct queue_info mtk_queue_info[] = {
+ {CCCI_UART2_TX, CCCI_UART2_RX, CLDMA1, TXQ(5), RXQ(5),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, 0},
+ {CCCI_MBIM_TX, CCCI_MBIM_RX, CLDMA1, TXQ(2), RXQ(2),
+ Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, 0},
{CCCI_CONTROL_TX, CCCI_CONTROL_RX, CLDMA1, TXQ(0), RXQ(0),
Q_MTU_3_5K, Q_MTU_3_5K, TX_GPD_NUM, RX_GPD_NUM, 0},
{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, CLDMA0, TXQ(0), RXQ(0),@@ -33,6 +37,10 @@ static const struct queue_info mtk_queue_info[] = {
};
static const struct mtk_port_cfg mtk_port_cfg_tbl[] = {
+ {CCCI_UART2_TX, CCCI_UART2_RX, PORT_TYPE_WWAN, "AT",
+ PORT_F_DFLT},
+ {CCCI_MBIM_TX, CCCI_MBIM_RX, PORT_TYPE_WWAN, "MBIM",
+ PORT_F_DFLT},
{CCCI_CONTROL_TX, CCCI_CONTROL_RX, PORT_TYPE_INTERNAL, "MDCTRL",
PORT_F_DFLT},
{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, PORT_TYPE_INTERNAL, "SAPCTRL",
--
2.34.1