[PATCH 06/11] net: wwan: t9xx: Add AT & MBIM WWAN ports

Jagielski, Jedrzej jedrzej.jagielski at intel.com
Mon Jun 1 05:09:05 PDT 2026


From: Jack Wu via B4 Relay <devnull+jackbb_wu.compal.com at kernel.org> 
Sent: Friday, May 29, 2026 12:32 PM

>From: Jack Wu <jackbb_wu at compal.com>
>
>Adds AT & MBIM ports to the port infrastructure.

please use imperative mode in commit msg

>The WWAN initialization method is responsible for creating the
>corresponding ports using the WWAN framework infrastructure. The
>implemented WWAN port operations are start, stop, tx, tx_blocking
>and tx_poll.
>
>Signed-off-by: Jack Wu <jackbb_wu at compal.com>
>---
> drivers/net/wwan/t9xx/mtk_port.c               |  27 ++
> drivers/net/wwan/t9xx/mtk_port.h               |  15 ++
> drivers/net/wwan/t9xx/mtk_port_io.c            | 332 ++++++++++++++++++++++++-
> drivers/net/wwan/t9xx/mtk_port_io.h            |   5 +
> drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c |   8 +
> 5 files changed, 386 insertions(+), 1 deletion(-)
>
>diff --git a/drivers/net/wwan/t9xx/mtk_port.c b/drivers/net/wwan/t9xx/mtk_port.c
>index dbd279cf2a14..4032df99b5b0 100644
>--- a/drivers/net/wwan/t9xx/mtk_port.c
>+++ b/drivers/net/wwan/t9xx/mtk_port.c
>@@ -630,6 +630,7 @@ static int mtk_port_rx_dispatch(struct sk_buff *skb, void *priv, bool force_recv
> 	/* Support scatter gather transmission */
> 	if (port->rx_mtu > port->rx_frag_size) {
> 		ret = mtk_port_rx_dispatch_frag_skb(port, skb);
>+		/* -EIO means partial data dispatch complete, does not goto drop flow */

unclear how adding this comment is related to the patch

> 		if (ret < 0 && ret != -EIO)
> 			goto drop_frag_skb;
> 	} else {
>@@ -818,6 +819,29 @@ int mtk_port_ch_disable(struct mtk_port *port)
> 	return ret;
> }
> 
>+static int mtk_port_enable_by_type(struct mtk_port_mngr *port_mngr, int tbl_type)
>+{
>+	struct mtk_port **ports;
>+	int ret, idx;
>+
>+	if (tbl_type < 0 || tbl_type >= PORT_TBL_MAX)
>+		return -EINVAL;
>+
>+	ports = kcalloc(port_mngr->port_cnt, sizeof(struct mtk_port *), GFP_KERNEL);
>+	if (!ports)
>+		return -ENOMEM;
>+
>+	ret = radix_tree_gang_lookup(&port_mngr->port_tbl[tbl_type],
>+				     (void **)ports, 0, port_mngr->port_cnt);
>+	for (idx = 0; idx < ret; idx++) {
>+		if (ports[idx]->enable)
>+			ports_ops[ports[idx]->info.type]->enable(ports[idx]);
>+	}
>+
>+	kfree(ports);
>+	return 0;
>+}
>+
> static void mtk_port_disable(struct mtk_port_mngr *port_mngr)
> {
> 	struct mtk_port **ports;
>@@ -851,6 +875,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 a201c0007878..cf561add6318 100644
>--- a/drivers/net/wwan/t9xx/mtk_port.h
>+++ b/drivers/net/wwan/t9xx/mtk_port.h
>@@ -56,6 +56,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_port_flag {
>@@ -73,6 +77,7 @@ enum mtk_port_tbl {
> 
> enum mtk_port_type {
> 	PORT_TYPE_INTERNAL,
>+	PORT_TYPE_WWAN,
> 	PORT_TYPE_MAX
> };
> 
>@@ -81,6 +86,13 @@ struct mtk_internal_port {
> 	int (*recv_cb)(void *arg, struct sk_buff *skb);
> };
> 
>+struct mtk_wwan_port {
>+	/* w_lock protects wwan_port when recv data and disable port at the same time */
>+	struct mutex w_lock;
>+	int w_type;
>+	void *w_port;
>+};
>+
> struct mtk_port_cfg {
> 	enum mtk_ccci_ch tx_ch;
> 	enum mtk_ccci_ch rx_ch;
>@@ -108,8 +120,11 @@ struct mtk_port {
> 	wait_queue_head_t rx_wq;
> 	struct list_head stale_entry;
> 	char dev_str[MTK_DEV_STR_LEN];
>+	/* Serializes port write operations */
>+	struct mutex write_lock;
> 	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 9e7a1207cc03..ab8b1c5157ec 100644
>--- a/drivers/net/wwan/t9xx/mtk_port_io.c
>+++ b/drivers/net/wwan/t9xx/mtk_port_io.c
>@@ -3,6 +3,10 @@
>  * Copyright (c) 2022, MediaTek Inc.
>  */
> #include <linux/netdevice.h>
>+#include <linux/poll.h>
>+#include <linux/slab.h>
>+#include <linux/wait.h>
>+#include <linux/wwan.h>
> 
> #include "mtk_port_io.h"
> 
>@@ -39,6 +43,149 @@ static void mtk_port_struct_init(struct mtk_port *port)
> 	port->rx_buf_size = MTK_RX_BUF_SIZE;
> 	init_waitqueue_head(&port->trb_wq);
> 	init_waitqueue_head(&port->rx_wq);
>+	mutex_init(&port->write_lock);
>+}
>+
>+static int mtk_port_copy_data_from(void *to, union user_buf from, unsigned int len,
>+				   unsigned int offset, bool from_user_space)
>+{
>+	int ret = 0;

like for the previous commits - please do not zeroinit when don't required
returbning 0 at the end is completely fine here

>+
>+	if (from_user_space) {
>+		ret = copy_from_user(to, from.ubuf + offset, len);
>+		if (ret)
>+			ret = -EFAULT;

#define	EFAULT		14	/* Bad address */
i believe there are better suiting codes

>+	} else {
>+		memcpy(to, from.kbuf + offset, len);
>+	}
>+
>+	return ret;
>+}
>+
>+static int mtk_port_common_write_frag_skb(struct mtk_port *port, struct sk_buff *skb,
>+					  union user_buf buf, u32 packet_size,
>+					  u32 cur_pos, bool from_user_space)
>+{
>+	struct sk_buff *frag_skb, *tmp = NULL;
>+	u32 frag_size;
>+	int ret;
>+
>+	frag_size = min(packet_size, port->tx_frag_size);
>+	ret = mtk_port_copy_data_from(skb_put(skb, frag_size),
>+				      buf, frag_size,
>+				      cur_pos, from_user_space);
>+	if (ret) {
>+		dev_err(port->port_mngr->ctrl_blk->mdev->dev,
>+			"Failed to copy skb for port(%s)\n", port->info.name);
>+		goto err_reset_skb;
>+	}
>+	cur_pos += frag_size;
>+	packet_size -= frag_size;
>+	if (!packet_size)
>+		return cur_pos;
>+
>+	while (packet_size > 0) {
>+		frag_skb = __dev_alloc_skb(port->tx_mtu, GFP_KERNEL);
>+		if (!frag_skb) {
>+			ret = -ENOMEM;
>+			goto err_free_frag_list;
>+		}
>+
>+		frag_size = min(packet_size, port->tx_frag_size);
>+		ret = mtk_port_copy_data_from(skb_put(frag_skb, frag_size),
>+					      buf, frag_size,
>+					      cur_pos, from_user_space);
>+		if (ret) {
>+			dev_err(port->port_mngr->ctrl_blk->mdev->dev,
>+				"Failed to copy frag_skb for port(%s)\n", port->info.name);
>+			dev_kfree_skb_any(frag_skb);
>+			goto err_free_frag_list;
>+		}
>+		skb->data_len += frag_size;
>+		skb->len += frag_size;
>+		cur_pos += frag_size;
>+		packet_size -= frag_size;
>+		if (!tmp)
>+			skb_shinfo(skb)->frag_list = frag_skb;
>+		else
>+			tmp->next = frag_skb;
>+		tmp = frag_skb;
>+	}
>+	return cur_pos;
>+
>+err_free_frag_list:
>+	frag_skb = skb_shinfo(skb)->frag_list;
>+	while (frag_skb) {
>+		tmp = frag_skb->next;
>+		frag_skb->next = NULL;
>+		dev_kfree_skb_any(frag_skb);
>+		frag_skb = tmp;
>+	}
>+	skb_shinfo(skb)->frag_list = NULL;
>+err_reset_skb:
>+	skb->data_len = 0;
>+	return ret;
>+}
>+
>+static int mtk_port_common_write(struct mtk_port *port, union user_buf buf, unsigned int len,
>+				 bool from_user_space)
>+{
>+	u32 packet_size, left_cnt = len, cur_pos;
>+	struct sk_buff *skb;
>+	int ret;
>+
>+	if (len == 0)

that's really successful path?

>+		return 0;
>+
>+start_write:
>+	ret = mtk_port_status_check(port);
>+	if (ret)
>+		goto end_write;
>+
>+	skb = __dev_alloc_skb(port->tx_mtu, GFP_KERNEL);
>+	if (!skb) {
>+		ret = -ENOMEM;
>+		goto end_write;
>+	}
>+
>+	skb_reserve(skb, sizeof(struct mtk_ccci_header));
>+
>+	packet_size = min(left_cnt, port->tx_mtu);
>+	cur_pos = len - left_cnt;
>+	/* Support scatter gather transmission */
>+	if (port->tx_mtu > port->tx_frag_size) {
>+		ret = mtk_port_common_write_frag_skb(port, skb, buf, packet_size,
>+						     cur_pos, from_user_space);
>+		if (ret < 0)
>+			goto err_free_skb;
>+	} else {
>+		ret = mtk_port_copy_data_from(skb_put(skb, packet_size),
>+					      buf, packet_size,
>+					      cur_pos, from_user_space);
>+		if (ret) {
>+			dev_err(port->port_mngr->ctrl_blk->mdev->dev,
>+				"Failed to copy data for port(%s)\n", port->info.name);
>+			goto err_free_skb;
>+		}
>+	}
>+
>+	ret = mtk_port_send_data(port, skb);
>+	if (ret < 0) {
>+		if (ret == -EINTR)
>+			left_cnt -= packet_size;
>+		goto end_write;
>+	}
>+
>+	left_cnt -= ret;
>+	if (left_cnt)
>+		goto start_write;
>+	else
>+		goto end_write;
>+
>+err_free_skb:
>+	dev_kfree_skb_any(skb);
>+end_write:
>+	return (len > left_cnt) ? (len - left_cnt) : ret;
> }
> 
> static int mtk_port_internal_init(struct mtk_port *port)
>@@ -109,7 +256,6 @@ static int mtk_port_internal_recv(struct mtk_port *port, struct sk_buff *skb)
> 	return ret;
> 
> drop_data:
>-	dev_kfree_skb_any(skb);
> 	return ret;
> }
> 
>@@ -241,6 +387,190 @@ static const struct port_ops port_internal_ops = {
> 	.recv = mtk_port_internal_recv,
> };
> 
>+static int mtk_port_wwan_open(struct wwan_port *w_port)
>+{
>+	struct mtk_port *port;
>+	int ret;
>+
>+	port = wwan_port_get_drvdata(w_port);
>+	ret = mtk_port_get_locked(port);
>+	if (ret)
>+		return ret;
>+
>+	ret = mtk_port_common_open(port);
>+	if (ret)
>+		mtk_port_put_locked(port);
>+
>+	return ret;
>+}
>+
>+static void mtk_port_wwan_close(struct wwan_port *w_port)
>+{
>+	struct mtk_port *port = wwan_port_get_drvdata(w_port);
>+
>+	mtk_port_common_close(port);
>+	mtk_port_put_locked(port);
>+}
>+
>+static int mtk_port_wwan_write(struct wwan_port *w_port, struct sk_buff *skb)
>+{
>+	struct mtk_port *port = wwan_port_get_drvdata(w_port);
>+	union user_buf user_buf;
>+
>+	if (unlikely(!skb->len)) {
>+		kfree_skb(skb);
>+		return 0;
>+	}
>+
>+	port->info.flags &= ~PORT_F_BLOCKING;
>+	user_buf.kbuf = (void *)skb->data;
>+	return mtk_port_common_write(port, user_buf, skb->len, false);
>+}
>+
>+static int mtk_port_wwan_write_blocking(struct wwan_port *w_port, struct sk_buff *skb)
>+{
>+	struct mtk_port *port = wwan_port_get_drvdata(w_port);
>+	union user_buf user_buf;
>+
>+	if (unlikely(!skb->len)) {
>+		kfree_skb(skb);
>+		return 0;
>+	}
>+
>+	port->info.flags |= PORT_F_BLOCKING;
>+	user_buf.kbuf = (void *)skb->data;
>+	return mtk_port_common_write(port, user_buf, skb->len, false);
>+}
>+
>+static __poll_t mtk_port_wwan_poll(struct wwan_port *w_port, struct file *file,
>+				   struct poll_table_struct *poll)
>+{
>+	struct mtk_port *port = wwan_port_get_drvdata(w_port);
>+	union ctrl_hif_cmd_data hif_cmd;
>+	struct mtk_ctrl_blk *ctrl_blk;
>+	__poll_t mask = 0;
>+
>+	poll_wait(file, &port->trb_wq, poll);
>+	if (mtk_port_status_check(port))
>+		return EPOLLERR | EPOLLHUP;
>+
>+	ctrl_blk = port->port_mngr->ctrl_blk;
>+	hif_cmd.rx_ch = port->info.rx_ch;
>+	if (!ctrl_blk->ops->send_cmd(ctrl_blk->mdev, HIF_CTRL_CMD_CHECK_TX_FULL, &hif_cmd))
>+		mask |= EPOLLOUT | EPOLLWRNORM;
>+
>+	return mask;
>+}
>+
>+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,
>+	.tx_poll = mtk_port_wwan_poll,
>+};
>+
>+static int mtk_port_wwan_init(struct mtk_port *port)

for the whole series - please assess where int over void
is really required

>+{
>+	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 int mtk_port_wwan_exit(struct mtk_port *port)
>+{
>+	if (test_bit(PORT_S_ENABLE, &port->status))
>+		ports_ops[port->info.type]->disable(port);
>+
>+	return 0;
>+}
>+
>+static int mtk_port_wwan_enable(struct mtk_port *port)
>+{
>+	struct mtk_port_mngr *port_mngr;
>+	int ret = 0;
>+
>+	port_mngr = port->port_mngr;
>+
>+	if (test_bit(PORT_S_ENABLE, &port->status))
>+		return 0;
>+
>+	ret = mtk_port_ch_enable(port);
>+	if (ret && ret != -EBUSY)
>+		return ret;
>+
>+	port->w_priv.w_port = wwan_create_port(port_mngr->ctrl_blk->mdev->dev,
>+					       port->w_priv.w_type,
>+					       &wwan_ops, NULL, port);
>+	if (IS_ERR(port->w_priv.w_port)) {
>+		dev_warn(port_mngr->ctrl_blk->mdev->dev,
>+			 "Failed to create wwan port for (%s)\n", port->info.name);
>+		return PTR_ERR(port->w_priv.w_port);
>+	}
>+
>+	set_bit(PORT_S_WR, &port->status);
>+	set_bit(PORT_S_ENABLE, &port->status);
>+
>+	return 0;
>+}
>+
>+static int mtk_port_wwan_disable(struct mtk_port *port)
>+{
>+	struct wwan_port *w_port;
>+
>+	if (!test_and_clear_bit(PORT_S_ENABLE, &port->status))
>+		return 0;
>+
>+	clear_bit(PORT_S_WR, &port->status);
>+	w_port = port->w_priv.w_port;
>+	mutex_lock(&port->w_priv.w_lock);
>+	port->w_priv.w_port = NULL;
>+	mutex_unlock(&port->w_priv.w_lock);
>+
>+	mtk_port_ch_disable(port);
>+	wwan_remove_port(w_port);
>+
>+	return 0;
>+}
>+
>+static int mtk_port_wwan_recv(struct mtk_port *port, struct sk_buff *skb)
>+{
>+	mutex_lock(&port->w_priv.w_lock);
>+	if (!port->w_priv.w_port) {
>+		mutex_unlock(&port->w_priv.w_lock);
>+		return -ENXIO;
>+	}
>+
>+	wwan_port_rx(port->w_priv.w_port, skb);
>+	mutex_unlock(&port->w_priv.w_lock);
>+	return 0;
>+}
>+
>+static const struct port_ops port_wwan_ops = {
>+	.init = mtk_port_wwan_init,
>+	.exit = mtk_port_wwan_exit,
>+	.reset = mtk_port_reset,
>+	.enable = mtk_port_wwan_enable,
>+	.disable = mtk_port_wwan_disable,
>+	.recv = mtk_port_wwan_recv,
>+};
>+
> const struct port_ops *ports_ops[PORT_TYPE_MAX] = {
> 	&port_internal_ops,
>+	&port_wwan_ops,
> };
>diff --git a/drivers/net/wwan/t9xx/mtk_port_io.h b/drivers/net/wwan/t9xx/mtk_port_io.h
>index 7d2cfe90334c..12f26d244f1f 100644
>--- a/drivers/net/wwan/t9xx/mtk_port_io.h
>+++ b/drivers/net/wwan/t9xx/mtk_port_io.h
>@@ -23,6 +23,11 @@ struct port_ops {
> 	int (*recv)(struct mtk_port *port, struct sk_buff *skb);
> };
> 
>+union user_buf {
>+	void __user *ubuf;
>+	void *kbuf;
>+};
>+
> void *mtk_port_internal_open(struct mtk_md_dev *mdev, char *name, int flag);
> int mtk_port_internal_close(void *i_port);
> int mtk_port_internal_write(void *i_port, struct sk_buff *skb);
>diff --git a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
>index 8611561dd67c..aab09cab360c 100644
>--- a/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
>+++ b/drivers/net/wwan/t9xx/pcie/mtk_ctrl_cfg_m9xx.c
>@@ -16,6 +16,10 @@ static const int mtk_srv_cfg_m9xx[NR_CLDMA][HW_QUE_NUM] = {
> 
> /* the number of RX GPDs should be at last two */
> static const struct queue_info mtk_queue_info_m9xx[] = {
>+	{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, Q_FRAG_3_5K, Q_FRAG_3_5K, 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, Q_FRAG_3_5K, Q_FRAG_3_5K, 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, Q_FRAG_3_5K, Q_FRAG_3_5K, 0},
> 	{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, CLDMA0, TXQ(0), RXQ(0),
>@@ -23,6 +27,10 @@ static const struct queue_info mtk_queue_info_m9xx[] = {
> };
> 
> static const struct mtk_port_cfg port_cfg_m9xx[] = {
>+	{CCCI_UART2_TX, CCCI_UART2_RX, PORT_TYPE_WWAN, "AT",
>+		PORT_F_ALLOW_DROP},
>+	{CCCI_MBIM_TX, CCCI_MBIM_RX, PORT_TYPE_WWAN, "MBIM",
>+		PORT_F_ALLOW_DROP},
> 	{CCCI_CONTROL_TX, CCCI_CONTROL_RX, PORT_TYPE_INTERNAL, "MDCTRL",
> 		PORT_F_ALLOW_DROP},
> 	{CCCI_SAP_CONTROL_TX, CCCI_SAP_CONTROL_RX, PORT_TYPE_INTERNAL, "SAPCTRL",
>
>-- 
>2.34.1





More information about the linux-arm-kernel mailing list