[PATCH 4/4] drivers: net: mediatek: initial implementation of ccmni
From: Rocco Yue <hidden>
Date: 2021-06-23 11:50:14
Also in:
bpf, linux-arm-kernel, linux-doc, linux-mediatek, lkml
Subsystem:
documentation, mediatek ethernet driver, networking drivers, networking [general], the rest · Maintainers:
Jonathan Corbet, Felix Fietkau, Lorenzo Bianconi, Andrew Lunn, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
ccmni driver is used by all recent chipsets using MediaTek Inc. modems. It provides a bridge to shield hardware differences and connect kernel netdevice. Module provides virtual network devices which can be used to support different mobile networks, such as a default cellular internet,or IMS network. Signed-off-by: Rocco Yue <redacted> --- .../device_drivers/cellular/ccmni/ccmni.rst | 151 +++++++++ MAINTAINERS | 9 + drivers/net/ethernet/mediatek/Kconfig | 5 + drivers/net/ethernet/mediatek/Makefile | 4 +- drivers/net/ethernet/mediatek/ccmni/Kconfig | 15 + drivers/net/ethernet/mediatek/ccmni/Makefile | 7 + drivers/net/ethernet/mediatek/ccmni/ccmni.c | 291 ++++++++++++++++++ drivers/net/ethernet/mediatek/ccmni/ccmni.h | 53 ++++ 8 files changed, 534 insertions(+), 1 deletion(-) create mode 100644 Documentation/networking/device_drivers/cellular/ccmni/ccmni.rst create mode 100644 drivers/net/ethernet/mediatek/ccmni/Kconfig create mode 100644 drivers/net/ethernet/mediatek/ccmni/Makefile create mode 100644 drivers/net/ethernet/mediatek/ccmni/ccmni.c create mode 100644 drivers/net/ethernet/mediatek/ccmni/ccmni.h
diff --git a/Documentation/networking/device_drivers/cellular/ccmni/ccmni.rst b/Documentation/networking/device_drivers/cellular/ccmni/ccmni.rst
new file mode 100644
index 000000000000..16d547786cbd
--- /dev/null
+++ b/Documentation/networking/device_drivers/cellular/ccmni/ccmni.rst@@ -0,0 +1,151 @@ +.. SPDX-License-Identifier: GPL-2.0+ + +=================================================================== +Linux kernel driver for Cross Core Multi Network Interface (ccmni): +=================================================================== + + +1. Introduction +=============== + +ccmni driver is built on the top of AP-CCCI (Application Processor Cross +Core Communite Interface) to emulate the modem’s data networking service +as a network interface card. This driver is used by all recent chipsets +using MediaTek Inc. modems. + +This driver can support multiple tx/rx channels, each channel has its +own IP address, and ipv4 and ipv6 link-local addresses on the interface +is set through ioctl, which means that ccmni driver is a pure ip device +and it does not need the kernel to generate these ip addresses +automatically. + +Creating logical network devices (ccmni devices) can be used to handle +multiple private data networks (PDNs), such as a defaule cellular +internet, IP Multimedia Subsystem (IMS) network (VoWiFi/VoLTE), IMS +Emergency, Tethering, Multimedia Messaging Service (MMS), and so on. +In general, one ccmni device corresponds to one PDN. + +ccmni design +------------ + +- AT commands and responses between the Application Processor and Modem + Processor take place over the ccci tty serial port emulation. +- IP packets coming into the ccmni driver (from the Modem) needs to be + un-framed (via the Packet Framing Protocol) before it can be stuffed + into the Socket Buffer. +- IP Packets going out of the ccmni driver (to the Modem) needs to be + framed (via the Packet Framing Protocol) before it can be pushed into + the respective ccci tx buffer. +- The Packet Framing Protocol module contains algorithms to correctly + add or remove frames, going out to and coming into, the ccmni driver + respectively. +- Packets are passed to, and received from the Linux networking system, + via Socket Buffers. +- The Android Application Framework contains codes to setup the ccmni’s + parameters (such as netmask), and the routing table. + + +2. Architecture +=============== + + +------------------------+ +---------------------+ +user | MTK RilD | | network process | +space | (config/up/down ccmni) | | (send/recv packets) | + +------------------------+ +---------------------+ ++--------------------------------------------------------------------+ + +--------------------------------------------------+ + | socket | + +--------------------------------------------------+ + +---------------------+ + | TCP/IP stack | + +---------------------+ + +--------------------------------------------------+ + | net device layer | + +--------------------------------------------------+ + +--------+ +--------+ +--------+ +kernel | ccmni0 | | ccmni1 | ... | ccmnix | +space +--------+ +--------+ +--------+ + +-------------------------------------------------+ + | ccmni driver | + +-------------------------------------------------+ + +-------------------------------------------------+ + | AP-CCCI | + +-------------------------------------------------+ ++---------------------------------------------------------------------+ + +-------------------------------------------------+ + | +-------------------+ +-------------------+ | + | | DPMAIF hardware | | MD-CCCI | | + | +-------------------+ +-------------------+ | + | ... ... | + | Modem Processor | + +-------------------------------------------------+ + + +3. Driver information and notes +=============================== + +Data Connection Set Up +---------------------- + +The data framework will first SetupDataCall with passing ccmni index, +and then RilD will activate PDN connection and get CID (Connection ID). + +Next, RilD will creat ccmni socket to use ioctl to configure ccmni up, +and then ccmni_open() will be called. + +In addition, since ccmni is a pureip device, RilD needs to use ioctl to +configure the ipv4/ipv6-link-local address for ccmni after it is up. + +Alternatively, you can use the ip command as follows:: + ip link set up dev ccmni<x> + ip addr add a.b.c.d dev ccmni<x> + ip -6 addr add fe80::1/64 dev ccmni<x> + +Data Connection Set Down +------------------------ + +The data service implements a method to tear down the data connection, +after RilD deactivate the PDN connection, RilD will down the specific +interface of ccmnix through ioctl SIOCSIFFLAGS, and then ccmni_close() +will be called. After that, if any network process (such as browser) wants +to write data to ccmni socket, TCP/IP stack will return an error to +this socket. + +Data Transmit +------------- + +In the uplink direction, when there is data to be transmitted to the cellular +network, ccmni_start_xmit() will be called by the Linux networking system. + +main operations in ccmni_start_xmit(): +- the datagram to be transmitted is housed in the Socket Buffer. +- check if the datagram is within limits (i.e. 1500 bytes) acceptable by the + Modem. If the datagram exceeds limit, the datagram will be dropped, and free + the Socket Buffer. +- check if the AP-CCCI TX buffer is busy, or do not have enough space for this + datagram. If it is busy, or the free space is too small, ccmni_start_xmit() + return NETDEV_TX_BUSY and ask the Linux netdevice to stop the tx queue. + +To handle outcoming datagrams to the Modem, ccmni register a callback function +for AP-CCCI driver. ccmni_hif_hook() means ccci can implement specific egress +function to send these packets to the specific hardware. + +Data Receive +------------ + +In the downlink direction, DPMAIF (Data Path MD AP Interface) hardware sends +packets and messages with channel id matching these packets to AP-CCCI driver. + +To handle incoming datagrams from the Modem, ccmni register a callback +function for the AP-CCCI driver. ccmni_rx_push() responsible for extracting +the incoming packets from the ccci rx buffer, and updating skb. Once ready, +ccmni signal to the Linux networking system to take out Socket Buffer +(via netif_rx() / netif_rx_ni()). + + +Support +======= + +If an issue is identified by published source code and supported adapter on +the supported kernel, please email the specific information about the issue +to rocco.yue@mediatek.com and chao.song@mediatek.com
diff --git a/MAINTAINERS b/MAINTAINERS
index 8c5ee008301a..1e53a754c727 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS@@ -11612,6 +11612,15 @@ F: Documentation/devicetree/bindings/usb/mediatek,* F: drivers/usb/host/xhci-mtk* F: drivers/usb/mtu3/ +MEDIATEK CCMNI DRIVER +M: Rocco Yue <rocco.yue@mediatek.com> +M: Chao Song <chao.song@mediatek.com> +M: Zhuoliang Zhang <zhuoliang.zhang@mediatek.com> +L: netdev@vger.kernel.org +S: Maintained +F: Documentation/networking/device_drivers/cellular/mediatek/ccmni.rst +F: drivers/net/ethernet/mediatek/ccmni/ + MEGACHIPS STDPXXXX-GE-B850V3-FW LVDS/DP++ BRIDGES M: Peter Senna Tschudin <peter.senna@gmail.com> M: Martin Donnelly <martin.donnelly@ge.com>
diff --git a/drivers/net/ethernet/mediatek/Kconfig b/drivers/net/ethernet/mediatek/Kconfig
index c357c193378e..2f499f3bc720 100644
--- a/drivers/net/ethernet/mediatek/Kconfig
+++ b/drivers/net/ethernet/mediatek/Kconfig@@ -1,4 +1,7 @@ # SPDX-License-Identifier: GPL-2.0-only +# +# MediaTek network device configuration +# config NET_VENDOR_MEDIATEK bool "MediaTek devices" depends on ARCH_MEDIATEK || SOC_MT7621 || SOC_MT7620
@@ -24,4 +27,6 @@ config NET_MEDIATEK_STAR_EMAC This driver supports the ethernet MAC IP first used on MediaTek MT85** SoCs. +source "drivers/net/ethernet/mediatek/ccmni/Kconfig" + endif #NET_VENDOR_MEDIATEK
diff --git a/drivers/net/ethernet/mediatek/Makefile b/drivers/net/ethernet/mediatek/Makefile
index 79d4cdbbcbf5..85bd3ebf2388 100644
--- a/drivers/net/ethernet/mediatek/Makefile
+++ b/drivers/net/ethernet/mediatek/Makefile@@ -1,8 +1,10 @@ # SPDX-License-Identifier: GPL-2.0-only # -# Makefile for the Mediatek SoCs built-in ethernet macs +# Makefile for the Mediatek network device drivers. # obj-$(CONFIG_NET_MEDIATEK_SOC) += mtk_eth.o mtk_eth-y := mtk_eth_soc.o mtk_sgmii.o mtk_eth_path.o mtk_ppe.o mtk_ppe_debugfs.o mtk_ppe_offload.o obj-$(CONFIG_NET_MEDIATEK_STAR_EMAC) += mtk_star_emac.o + +obj-$(CONFIG_MTK_NET_CCMNI) += ccmni/
diff --git a/drivers/net/ethernet/mediatek/ccmni/Kconfig b/drivers/net/ethernet/mediatek/ccmni/Kconfig
new file mode 100644
index 000000000000..d97c0e48b58e
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/ccmni/Kconfig@@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# MediaTek CCMNI driver +# + +menuconfig MTK_NET_CCMNI + tristate "MediaTek CCMNI driver" + default n + help + Cross Core Multi Network Interface: + If you select this, you will enable the CCMNI module which is used + to shield hardware differences and communicate with kernel netdevice + and be used for handling outgoing/incoimg mobile data. Module provides + virtual network devices which can be used to support different mobile + networks.
diff --git a/drivers/net/ethernet/mediatek/ccmni/Makefile b/drivers/net/ethernet/mediatek/ccmni/Makefile
new file mode 100644
index 000000000000..e79c23d1b79d
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/ccmni/Makefile@@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for the ccmni module +# +ccflags-y += -I$(srctree)/drivers/net/ethernet/mediatek/ccmni/ +obj-$(CONFIG_MTK_NET_CCMNI) += ccmni.o +
diff --git a/drivers/net/ethernet/mediatek/ccmni/ccmni.c b/drivers/net/ethernet/mediatek/ccmni/ccmni.c
new file mode 100644
index 000000000000..700a0d092596
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/ccmni/ccmni.c@@ -0,0 +1,291 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021 MediaTek Inc. + * + * CCMNI Data virtual netwotrk driver + */ + +#include <linux/if_arp.h> +#include <linux/module.h> +#include <linux/preempt.h> +#include <net/sch_generic.h> + +#include "ccmni.h" + +static struct ccmni_ctl_block *s_ccmni_ctlb; +static int ccmni_hook_ready; + +/* Network Device Operations */ + +static int ccmni_open(struct net_device *ccmni_dev) +{ + struct ccmni_inst *ccmni = netdev_priv(ccmni_dev); + + netif_tx_start_all_queues(ccmni_dev); + netif_carrier_on(ccmni_dev); + + if (atomic_inc_return(&ccmni->usage) > 1) { + atomic_dec(&ccmni->usage); + netdev_err(ccmni_dev, "dev already open\n"); + return -EINVAL; + } + + return 0; +} + +static int ccmni_close(struct net_device *ccmni_dev) +{ + struct ccmni_inst *ccmni = netdev_priv(ccmni_dev); + + atomic_dec(&ccmni->usage); + netif_tx_disable(ccmni_dev); + + return 0; +} + +static netdev_tx_t +ccmni_start_xmit(struct sk_buff *skb, struct net_device *ccmni_dev) +{ + struct ccmni_inst *ccmni = NULL; + + if (unlikely(!ccmni_hook_ready)) + goto tx_ok; + + if (!skb || !ccmni_dev) + goto tx_ok; + + ccmni = netdev_priv(ccmni_dev); + + /* some process can modify ccmni_dev->mtu */ + if (skb->len > ccmni_dev->mtu) { + netdev_err(ccmni_dev, "xmit fail: len(0x%x) > MTU(0x%x, 0x%x)", + skb->len, CCMNI_MTU, ccmni_dev->mtu); + goto tx_ok; + } + + /* hardware driver send packet will return a negative value + * ask the Linux netdevice to stop the tx queue + */ + if ((s_ccmni_ctlb->xmit_pkt(ccmni->index, skb, 0)) < 0) + return NETDEV_TX_BUSY; + + return NETDEV_TX_OK; +tx_ok: + dev_kfree_skb(skb); + ccmni_dev->stats.tx_dropped++; + return NETDEV_TX_OK; +} + +static int ccmni_change_mtu(struct net_device *ccmni_dev, int new_mtu) +{ + if (new_mtu < 0 || new_mtu > CCMNI_MTU) + return -EINVAL; + + if (unlikely(!ccmni_dev)) + return -EINVAL; + + ccmni_dev->mtu = new_mtu; + return 0; +} + +static void ccmni_tx_timeout(struct net_device *ccmni_dev, unsigned int txqueue) +{ + struct ccmni_inst *ccmni = netdev_priv(ccmni_dev); + + ccmni_dev->stats.tx_errors++; + if (atomic_read(&ccmni->usage) > 0) + netif_tx_wake_all_queues(ccmni_dev); +} + +static const struct net_device_ops ccmni_netdev_ops = { + .ndo_open = ccmni_open, + .ndo_stop = ccmni_close, + .ndo_start_xmit = ccmni_start_xmit, + .ndo_tx_timeout = ccmni_tx_timeout, + .ndo_change_mtu = ccmni_change_mtu, +}; + +/* init ccmni network device */ +static inline void ccmni_dev_init(struct net_device *ccmni_dev, unsigned int idx) +{ + ccmni_dev->mtu = CCMNI_MTU; + ccmni_dev->tx_queue_len = CCMNI_TX_QUEUE; + ccmni_dev->watchdog_timeo = CCMNI_NETDEV_WDT_TO; + ccmni_dev->flags = IFF_NOARP & + (~IFF_BROADCAST & ~IFF_MULTICAST); + + /* not support VLAN */ + ccmni_dev->features = NETIF_F_VLAN_CHALLENGED; + ccmni_dev->features |= NETIF_F_SG; + ccmni_dev->hw_features |= NETIF_F_SG; + + /* pure ip mode */ + ccmni_dev->type = ARPHRD_PUREIP; + ccmni_dev->header_ops = NULL; + ccmni_dev->hard_header_len = 0; + ccmni_dev->addr_len = 0; + ccmni_dev->priv_destructor = free_netdev; + ccmni_dev->netdev_ops = &ccmni_netdev_ops; + random_ether_addr((u8 *)ccmni_dev->dev_addr); + sprintf(ccmni_dev->name, "ccmni%d", idx); +} + +/* init ccmni instance */ +static inline void ccmni_inst_init(struct net_device *netdev, unsigned int idx) +{ + struct ccmni_inst *ccmni = netdev_priv(netdev); + + ccmni->index = idx; + ccmni->dev = netdev; + atomic_set(&ccmni->usage, 0); + + s_ccmni_ctlb->ccmni_inst[idx] = ccmni; +} + +/* ccmni driver module startup/shutdown */ + +static int __init ccmni_init(void) +{ + struct net_device *dev = NULL; + unsigned int i, j; + int ret = 0; + + s_ccmni_ctlb = kzalloc(sizeof(*s_ccmni_ctlb), GFP_KERNEL); + if (!s_ccmni_ctlb) + return -ENOMEM; + + s_ccmni_ctlb->max_num = MAX_CCMNI_NUM; + for (i = 0; i < MAX_CCMNI_NUM; i++) { + /* alloc multiple tx queue, 2 txq and 1 rxq */ + dev = alloc_etherdev_mqs(sizeof(struct ccmni_inst), 2, 1); + if (unlikely(!dev)) { + ret = -ENOMEM; + goto alloc_netdev_fail; + } + ccmni_dev_init(dev, i); + ccmni_inst_init(dev, i); + ret = register_netdev(dev); + if (ret) + goto alloc_netdev_fail; + } + return ret; + +alloc_netdev_fail: + if (dev) { + free_netdev(dev); + s_ccmni_ctlb->ccmni_inst[i] = NULL; + } + for (j = i - 1; j >= 0; j--) { + unregister_netdev(s_ccmni_ctlb->ccmni_inst[j]->dev); + s_ccmni_ctlb->ccmni_inst[j] = NULL; + } + kfree(s_ccmni_ctlb); + s_ccmni_ctlb = NULL; + + return ret; +} + +static void __exit ccmni_exit(void) +{ + struct ccmni_ctl_block *ctlb = NULL; + struct ccmni_inst *ccmni = NULL; + int i; + + ctlb = s_ccmni_ctlb; + if (!s_ccmni_ctlb) + return; + for (i = 0; i < s_ccmni_ctlb->max_num; i++) { + ccmni = s_ccmni_ctlb->ccmni_inst[i]; + if (ccmni) { + unregister_netdev(ccmni->dev); + s_ccmni_ctlb->ccmni_inst[i] = NULL; + } + } + kfree(s_ccmni_ctlb); + s_ccmni_ctlb = NULL; +} + +/* exposed API + * receive incoming datagrams from the Modem and push them to the + * kernel networking system + */ +int ccmni_rx_push(unsigned int ccmni_idx, struct sk_buff *skb) +{ + struct ccmni_inst *ccmni = NULL; + struct net_device *dev = NULL; + int pkt_type, skb_len; + + if (unlikely(!ccmni_hook_ready)) + return -EINVAL; + + /* Some hardware can send us error index. Catch them */ + if (unlikely(ccmni_idx >= s_ccmni_ctlb->max_num)) + return -EINVAL; + + ccmni = s_ccmni_ctlb->ccmni_inst[ccmni_idx]; + dev = ccmni->dev; + + pkt_type = skb->data[0] & 0xF0; + skb_reset_transport_header(skb); + skb_reset_network_header(skb); + skb_set_mac_header(skb, 0); + skb_reset_mac_len(skb); + skb->dev = dev; + + if (pkt_type == IPV6_VERSION) + skb->protocol = htons(ETH_P_IPV6); + else if (pkt_type == IPV4_VERSION) + skb->protocol = htons(ETH_P_IP); + + skb_len = skb->len; + + if (!in_interrupt()) + netif_rx_ni(skb); + else + netif_rx(skb); + + dev->stats.rx_packets++; + dev->stats.rx_bytes += skb_len; + + return 0; +} +EXPORT_SYMBOL(ccmni_rx_push); + +/* exposed API + * hardware driver can init the struct ccmni_hif_ops and implement specific + * xmnit function to send UL packets to the specific hardware + */ +int ccmni_hif_hook(struct ccmni_hif_ops *hif_ops) +{ + if (unlikely(!hif_ops)) { + pr_err("ccmni: %s fail: argument is NULL\n", __func__); + return -EINVAL; + } + if (unlikely(!s_ccmni_ctlb)) { + pr_err("ccmni: %s fail: s_ccmni_ctlb is NULL\n", __func__); + return -EINVAL; + } + if (unlikely(s_ccmni_ctlb->hif_ops)) { + pr_err("ccmni: %s fail: hif_ops already hooked\n", __func__); + return -EINVAL; + } + + s_ccmni_ctlb->hif_ops = hif_ops; + if (!hif_ops->xmit_pkt) { + pr_err("ccmni: %s fail: key hook func: xmit is NULL\n", + __func__); + return -EINVAL; + } + + s_ccmni_ctlb->xmit_pkt = hif_ops->xmit_pkt; + ccmni_hook_ready = 1; + + return 0; +} +EXPORT_SYMBOL(ccmni_hif_hook); + +module_init(ccmni_init); +module_exit(ccmni_exit); +MODULE_AUTHOR("MediaTek, Inc."); +MODULE_DESCRIPTION("ccmni driver v1.0"); +MODULE_LICENSE("GPL");
diff --git a/drivers/net/ethernet/mediatek/ccmni/ccmni.h b/drivers/net/ethernet/mediatek/ccmni/ccmni.h
new file mode 100644
index 000000000000..e2799aa2c9d4
--- /dev/null
+++ b/drivers/net/ethernet/mediatek/ccmni/ccmni.h@@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2021 MediaTek Inc. + * + * CCMNI Data virtual netwotrk driver + */ + +#ifndef __CCMNI_NET_H__ +#define __CCMNI_NET_H__ + +#include <linux/device.h> +#include <linux/etherdevice.h> +#include <linux/if_ether.h> +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/skbuff.h> + +#define CCMNI_MTU 1500 +#define CCMNI_TX_QUEUE 1000 +#define CCMNI_NETDEV_WDT_TO (1 * HZ) + +#define IPV4_VERSION 0x40 +#define IPV6_VERSION 0x60 + +#define MAX_CCMNI_NUM 22 + +/* One instance of this structure is instantiated for each + * real_dev associated with ccmni + */ +struct ccmni_inst { + int index; + atomic_t usage; + struct net_device *dev; + unsigned char name[16]; +}; + +/* an export struct of ccmni hardware interface operations + */ +struct ccmni_hif_ops { + int (*xmit_pkt)(int index, void *data, int ref_flag); +}; + +struct ccmni_ctl_block { + int (*xmit_pkt)(int index, void *data, int ref_flag); + struct ccmni_hif_ops *hif_ops; + struct ccmni_inst *ccmni_inst[MAX_CCMNI_NUM]; + int max_num; +}; + +int ccmni_hif_hook(struct ccmni_hif_ops *hif_ops); +int ccmni_rx_push(unsigned int ccmni_idx, struct sk_buff *skb); + +#endif /* __CCMNI_NET_H__ */
--
2.18.0