root/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 <amir.hanania@intel.com>
 *  Chandrashekar Devegowda <chandrashekar.devegowda@intel.com>
 *  Haijun Liu <haijun.liu@mediatek.com>
 *  Moises Veleta <moises.veleta@intel.com>
 *  Ricardo Martinez <ricardo.martinez@linux.intel.com>
 *
 * Contributors:
 *  Andy Shevchenko <andriy.shevchenko@linux.intel.com>
 *  Chiranjeevi Rapolu <chiranjeevi.rapolu@intel.com>
 *  Eliot Lee <eliot.lee@intel.com>
 *  Sreehari Kancharla <sreehari.kancharla@intel.com>
 *  Jinjian Song <jinjian.song@fibocom.com>
 */

#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 ||
            port_conf->port_type == WWAN_PORT_ADB ||
            port_conf->port_type == WWAN_PORT_MIPC)
                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 ||
            port_conf->port_type == WWAN_PORT_ADB ||
            port_conf->port_type == WWAN_PORT_MIPC)
                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,
};