linux/drivers/net/wwan/t7xx/t7xx_port_wwan.c

// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2021, MediaTek Inc.
 * Copyright (c) 2021-2022, Intel Corporation.
 * Copyright (c) 2024, Fibocom Wireless Inc.
 *
 * Authors:
 *  Amir Hanania <[email protected]>
 *  Chandrashekar Devegowda <[email protected]>
 *  Haijun Liu <[email protected]>
 *  Moises Veleta <[email protected]>
 *  Ricardo Martinez <[email protected]>
 *
 * Contributors:
 *  Andy Shevchenko <[email protected]>
 *  Chiranjeevi Rapolu <[email protected]>
 *  Eliot Lee <[email protected]>
 *  Sreehari Kancharla <[email protected]>
 *  Jinjian Song <[email protected]>
 */

#include <linux/atomic.h>
#include <linux/bitfield.h>
#include <linux/dev_printk.h>
#include <linux/err.h>
#include <linux/gfp.h>
#include <linux/minmax.h>
#include <linux/netdevice.h>
#include <linux/skbuff.h>
#include <linux/spinlock.h>
#include <linux/string.h>
#include <linux/wwan.h>

#include "t7xx_port.h"
#include "t7xx_port_proxy.h"
#include "t7xx_state_monitor.h"

static int t7xx_port_wwan_start(struct wwan_port *port)
{
	struct t7xx_port *port_mtk = wwan_port_get_drvdata(port);

	if (atomic_read(&port_mtk->usage_cnt))
		return -EBUSY;

	atomic_inc(&port_mtk->usage_cnt);
	return 0;
}

static void t7xx_port_wwan_stop(struct wwan_port *port)
{
	struct t7xx_port *port_mtk = wwan_port_get_drvdata(port);

	atomic_dec(&port_mtk->usage_cnt);
}

static int t7xx_port_fastboot_tx(struct t7xx_port *port, struct sk_buff *skb)
{
	struct sk_buff *cur = skb, *tx_skb;
	size_t actual, len, offset = 0;
	int txq_mtu;
	int ret;

	txq_mtu = t7xx_get_port_mtu(port);
	if (txq_mtu < 0)
		return -EINVAL;

	actual = cur->len;
	while (actual) {
		len = min_t(size_t, actual, txq_mtu);
		tx_skb = __dev_alloc_skb(len, GFP_KERNEL);
		if (!tx_skb)
			return -ENOMEM;

		skb_put_data(tx_skb, cur->data + offset, len);

		ret = t7xx_port_send_raw_skb(port, tx_skb);
		if (ret) {
			dev_kfree_skb(tx_skb);
			dev_err(port->dev, "Write error on fastboot port, %d\n", ret);
			break;
		}
		offset += len;
		actual -= len;
	}

	dev_kfree_skb(skb);
	return 0;
}

static int t7xx_port_ctrl_tx(struct t7xx_port *port, struct sk_buff *skb)
{
	const struct t7xx_port_conf *port_conf;
	struct sk_buff *cur = skb, *cloned;
	struct t7xx_fsm_ctl *ctl;
	enum md_state md_state;
	int cnt = 0, ret;

	port_conf = port->port_conf;
	ctl = port->t7xx_dev->md->fsm_ctl;
	md_state = t7xx_fsm_get_md_state(ctl);
	if (md_state == MD_STATE_WAITING_FOR_HS1 || md_state == MD_STATE_WAITING_FOR_HS2) {
		dev_warn(port->dev, "Cannot write to %s port when md_state=%d\n",
			 port_conf->name, md_state);
		return -ENODEV;
	}

	while (cur) {
		cloned = skb_clone(cur, GFP_KERNEL);
		cloned->len = skb_headlen(cur);
		ret = t7xx_port_send_skb(port, cloned, 0, 0);
		if (ret) {
			dev_kfree_skb(cloned);
			dev_err(port->dev, "Write error on %s port, %d\n",
				port_conf->name, ret);
			return cnt ? cnt + ret : ret;
		}
		cnt += cur->len;
		if (cur == skb)
			cur = skb_shinfo(skb)->frag_list;
		else
			cur = cur->next;
	}

	dev_kfree_skb(skb);
	return 0;
}

static int t7xx_port_wwan_tx(struct wwan_port *port, struct sk_buff *skb)
{
	struct t7xx_port *port_private = wwan_port_get_drvdata(port);
	const struct t7xx_port_conf *port_conf = port_private->port_conf;
	int ret;

	if (!port_private->chan_enable)
		return -EINVAL;

	if (port_conf->port_type != WWAN_PORT_FASTBOOT)
		ret = t7xx_port_ctrl_tx(port_private, skb);
	else
		ret = t7xx_port_fastboot_tx(port_private, skb);

	return ret;
}

static const struct wwan_port_ops wwan_ops = {
	.start = t7xx_port_wwan_start,
	.stop = t7xx_port_wwan_stop,
	.tx = t7xx_port_wwan_tx,
};

static void t7xx_port_wwan_create(struct t7xx_port *port)
{
	const struct t7xx_port_conf *port_conf = port->port_conf;
	unsigned int header_len = sizeof(struct ccci_header), mtu;
	struct wwan_port_caps caps;

	if (!port->wwan.wwan_port) {
		mtu = t7xx_get_port_mtu(port);
		caps.frag_len = mtu - header_len;
		caps.headroom_len = header_len;
		port->wwan.wwan_port = wwan_create_port(port->dev, port_conf->port_type,
							&wwan_ops, &caps, port);
		if (IS_ERR(port->wwan.wwan_port))
			dev_err(port->dev, "Unable to create WWAN port %s", port_conf->name);
	}
}

static int t7xx_port_wwan_init(struct t7xx_port *port)
{
	const struct t7xx_port_conf *port_conf = port->port_conf;

	if (port_conf->port_type == WWAN_PORT_FASTBOOT)
		t7xx_port_wwan_create(port);

	port->rx_length_th = RX_QUEUE_MAXLEN;
	return 0;
}

static void t7xx_port_wwan_uninit(struct t7xx_port *port)
{
	if (!port->wwan.wwan_port)
		return;

	port->rx_length_th = 0;
	wwan_remove_port(port->wwan.wwan_port);
	port->wwan.wwan_port = NULL;
}

static int t7xx_port_wwan_recv_skb(struct t7xx_port *port, struct sk_buff *skb)
{
	if (!atomic_read(&port->usage_cnt) || !port->chan_enable) {
		const struct t7xx_port_conf *port_conf = port->port_conf;

		dev_kfree_skb_any(skb);
		dev_err_ratelimited(port->dev, "Port %s is not opened, drop packets\n",
				    port_conf->name);
		/* Dropping skb, caller should not access skb.*/
		return 0;
	}

	wwan_port_rx(port->wwan.wwan_port, skb);
	return 0;
}

static int t7xx_port_wwan_enable_chl(struct t7xx_port *port)
{
	spin_lock(&port->port_update_lock);
	port->chan_enable = true;
	spin_unlock(&port->port_update_lock);

	return 0;
}

static int t7xx_port_wwan_disable_chl(struct t7xx_port *port)
{
	spin_lock(&port->port_update_lock);
	port->chan_enable = false;
	spin_unlock(&port->port_update_lock);

	return 0;
}

static void t7xx_port_wwan_md_state_notify(struct t7xx_port *port, unsigned int state)
{
	const struct t7xx_port_conf *port_conf = port->port_conf;

	if (port_conf->port_type == WWAN_PORT_FASTBOOT)
		return;

	if (state != MD_STATE_READY)
		return;

	t7xx_port_wwan_create(port);
}

struct port_ops wwan_sub_port_ops = {
	.init = t7xx_port_wwan_init,
	.recv_skb = t7xx_port_wwan_recv_skb,
	.uninit = t7xx_port_wwan_uninit,
	.enable_chl = t7xx_port_wwan_enable_chl,
	.disable_chl = t7xx_port_wwan_disable_chl,
	.md_state_notify = t7xx_port_wwan_md_state_notify,
};