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 <rocco.yue@xxxxxxxxxxxx> --- .../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@xxxxxxxxxxxx and chao.song@xxxxxxxxxxxx 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@xxxxxxxxxxxx> +M: Chao Song <chao.song@xxxxxxxxxxxx> +M: Zhuoliang Zhang <zhuoliang.zhang@xxxxxxxxxxxx> +L: netdev@xxxxxxxxxxxxxxx +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@xxxxxxxxx> M: Martin Donnelly <martin.donnelly@xxxxxx> 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