Hello,
This series introduces the CPSW Proxy Client driver to interface with
Ethernet Switch Firmware (EthFw) running on a remote core on TI's K3
SoCs. Further details are in patch 01/28 which adds documentation for
the driver and describes the intended use-case, design and execution
of the driver.
Please DO NOT MERGE this series. I will appreciate feedback on this
series in the form of both documentation enhancements and code review.
This series does not depend on other series and applies cleanly on the
latest net-next commit:
4b377b4868ef kprobe/ftrace: fix build error due to bad function definition
However, for purposes of functionality, device-tree changes are required
in the form of a device-tree overlay in order to mark device-tree nodes
as reserved for EthFw's use.
I have tested this series on J721E EVM verifying:
1. Ping/Iperf
2. Interface Up-Down
3. Module Removal
Linux Logs:
https://gist.github.com/Siddharth-Vadapalli-at-TI/0972e74383cd1ec16e2f82c0d447f90b
EthFw Logs corresponding to the Linux Logs shared above:
https://gist.github.com/Siddharth-Vadapalli-at-TI/28743298daf113f90be9ceb26c46b16b
Regards,
Siddharth.
Siddharth Vadapalli (28):
docs: networking: ti: add driver doc for CPSW Proxy Client
net: ethernet: ti: add RPMsg structures for Ethernet Switch Firmware
net: ethernet: ti: introduce the CPSW Proxy Client
net: ethernet: ti: cpsw-proxy-client: add support for creating
requests
net: ethernet: ti: cpsw-proxy-client: enable message exchange with
EthFw
net: ethernet: ti: cpsw-proxy-client: add helper to get virtual port
info
net: ethernet: ti: cpsw-proxy-client: add helper to attach virtual
ports
net: ethernet: ti: cpsw-proxy-client: add helpers to alloc/free
resources
net: ethernet: ti: cpsw-proxy-client: add helper to init TX DMA
Channels
net: ethernet: ti: cpsw-proxy-client: add helper to init RX DMA
Channels
net: ethernet: ti: cpsw-proxy-client: add NAPI TX polling function
net: ethernet: ti: cpsw-proxy-client: add NAPI RX polling function
net: ethernet: ti: cpsw-proxy-client: add helper to create netdevs
net: ethernet: ti: cpsw-proxy-client: add and register dma irq
handlers
net: ethernet: ti: cpsw-proxy-client: add helpers to (de)register MAC
net: ethernet: ti: cpsw-proxy-client: implement and register ndo_open
net: ethernet: ti: cpsw-proxy-client: implement and register ndo_stop
net: ethernet: ti: cpsw-proxy-client: implement and register
ndo_start_xmit
net: ethernet: ti: cpsw-proxy-client: implement and register
ndo_get_stats64
net: ethernet: ti: cpsw-proxy-client: implement and register
ndo_tx_timeout
net: ethernet: ti: cpsw-proxy-client: register
ndo_validate/ndo_set_mac_addr
net: ethernet: ti: cpsw-proxy-client: implement .get_link ethtool op
net: ethernet: ti: cpsw-proxy-client: add sw tx/rx irq coalescing
net: ethernet: ti: cpsw-proxy-client: export coalescing support
net: ethernet: ti: cpsw-proxy-client: add helpers to (de)register IPv4
net: ethernet: ti: cpsw-proxy-client: add ndo_set_rx_mode member
net: ethernet: ti: cpsw-proxy-client: add helper to detach virtual
ports
net: ethernet: ti: cpsw-proxy-client: enable client driver
functionality
.../ethernet/ti/cpsw_proxy_client.rst | 182 ++
drivers/net/ethernet/ti/Kconfig | 14 +
drivers/net/ethernet/ti/Makefile | 3 +
drivers/net/ethernet/ti/cpsw-proxy-client.c | 2354 +++++++++++++++++
drivers/net/ethernet/ti/ethfw_abi.h | 370 +++
5 files changed, 2923 insertions(+)
create mode 100644 Documentation/networking/device_drivers/ethernet/ti/cpsw_proxy_client.rst
create mode 100644 drivers/net/ethernet/ti/cpsw-proxy-client.c
create mode 100644 drivers/net/ethernet/ti/ethfw_abi.h
--
2.40.1
The CPSW Proxy Client driver in Linux communicates with Ethernet
Switch Firmware (EthFw) running on a remote core via RPMsg.
EthFw announces its RPMsg Endpoint over the RPMsg-Bus notifying
its presence to all Clients.
Register the CPSW Proxy Client driver with the RPMsg framework.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/Kconfig | 14 +++++
drivers/net/ethernet/ti/Makefile | 3 +
drivers/net/ethernet/ti/cpsw-proxy-client.c | 70 +++++++++++++++++++++
3 files changed, 87 insertions(+)
create mode 100644 drivers/net/ethernet/ti/cpsw-proxy-client.c
diff --git a/drivers/net/ethernet/ti/Kconfig b/drivers/net/ethernet/ti/Kconfig
index 1729eb0e0b41..ffbfd625625d 100644
--- a/drivers/net/ethernet/ti/Kconfig
+++ b/drivers/net/ethernet/ti/Kconfig
@@ -79,6 +79,20 @@ config TI_CPSW_SWITCHDEV
To compile this driver as a module, choose M here: the module
will be called cpsw_new.
+config TI_CPSW_PROXY_CLIENT
+ tristate "TI CPSW Proxy Client"
+ depends on ARCH_K3 && OF && TI_K3_UDMA_GLUE_LAYER
+ help
+ This driver supports Ethernet functionality for CPSWnG
+ Ethernet Subsystem which is configured by Ethernet Switch
+ Firmware (EthFw).
+
+ The Ethernet Switch Firmware acts as a proxy to the Linux
+ Client driver by performing all the necessary configuration
+ of the CPSW Peripheral while enabling network data transfer
+ to/from the Linux Client to CPSW over the allocated TX DMA
+ Channels and RX DMA Flows.
+
config TI_CPTS
tristate "TI Common Platform Time Sync (CPTS) Support"
depends on ARCH_OMAP2PLUS || ARCH_KEYSTONE || COMPILE_TEST
diff --git a/drivers/net/ethernet/ti/Makefile b/drivers/net/ethernet/ti/Makefile
index 6e086b4c0384..229b828f099e 100644
--- a/drivers/net/ethernet/ti/Makefile
+++ b/drivers/net/ethernet/ti/Makefile
@@ -7,6 +7,9 @@ obj-$(CONFIG_TI_CPSW) += cpsw-common.o
obj-$(CONFIG_TI_DAVINCI_EMAC) += cpsw-common.o
obj-$(CONFIG_TI_CPSW_SWITCHDEV) += cpsw-common.o
+obj-$(CONFIG_TI_CPSW_PROXY_CLIENT) += ti-cpsw-proxy-client.o
+ti-cpsw-proxy-client-y := cpsw-proxy-client.o
+
obj-$(CONFIG_TLAN) += tlan.o
obj-$(CONFIG_TI_DAVINCI_EMAC) += ti_davinci_emac.o
ti_davinci_emac-y := davinci_emac.o davinci_cpdma.o
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
new file mode 100644
index 000000000000..91d3338b3788
--- /dev/null
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -0,0 +1,70 @@
+// SPDX-License-Identifier: GPL-2.0-only or MIT
+/* Texas Instruments CPSW Proxy Client Driver
+ *
+ * Copyright (C) 2024 Texas Instruments Incorporated - https://www.ti.com/
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/rpmsg.h>
+
+#include "ethfw_abi.h"
+
+struct cpsw_proxy_priv {
+ struct rpmsg_device *rpdev;
+ struct device *dev;
+};
+
+static int cpsw_proxy_client_cb(struct rpmsg_device *rpdev, void *data,
+ int len, void *priv, u32 src)
+{
+ struct device *dev = &rpdev->dev;
+
+ dev_dbg(dev, "callback invoked\n");
+
+ return 0;
+}
+
+static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
+{
+ struct cpsw_proxy_priv *proxy_priv;
+
+ proxy_priv = devm_kzalloc(&rpdev->dev, sizeof(struct cpsw_proxy_priv), GFP_KERNEL);
+ if (!proxy_priv)
+ return -ENOMEM;
+
+ proxy_priv->rpdev = rpdev;
+ proxy_priv->dev = &rpdev->dev;
+ dev_dbg(proxy_priv->dev, "driver probed\n");
+
+ return 0;
+}
+
+static void cpsw_proxy_client_remove(struct rpmsg_device *rpdev)
+{
+ struct device *dev = &rpdev->dev;
+
+ dev_dbg(dev, "driver removed\n");
+}
+
+static struct rpmsg_device_id cpsw_proxy_client_id_table[] = {
+ {
+ .name = ETHFW_SERVICE_EP_NAME,
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(rpmsg, cpsw_proxy_client_id_table);
+
+static struct rpmsg_driver cpsw_proxy_client_driver = {
+ .drv.name = KBUILD_MODNAME,
+ .id_table = cpsw_proxy_client_id_table,
+ .probe = cpsw_proxy_client_probe,
+ .callback = cpsw_proxy_client_cb,
+ .remove = cpsw_proxy_client_remove,
+};
+module_rpmsg_driver(cpsw_proxy_client_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("CPSW Proxy Client Driver");
+MODULE_AUTHOR("Siddharth Vadapalli <[email protected]>");
--
2.40.1
The Ethernet Switch Firmware (EthFw) running on the remote MAIN R5 core is
capable of configuring the CPSWnG instance of TI's CPSW Ethernet Switch
on various TI SoCs such as J721E, J7200 and J784S4. EthFw is in complete
control of CPSWnG and acts as a server which allocates and provides the
resources to clients on different cores. All communication between EthFw
and the clients occurs via RPMsg.
Define the message structures used to communicate with EthFw. This shall
enable the Linux Client to avail the services provided by EthFw.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/ethfw_abi.h | 370 ++++++++++++++++++++++++++++
1 file changed, 370 insertions(+)
create mode 100644 drivers/net/ethernet/ti/ethfw_abi.h
diff --git a/drivers/net/ethernet/ti/ethfw_abi.h b/drivers/net/ethernet/ti/ethfw_abi.h
new file mode 100644
index 000000000000..a857e920445b
--- /dev/null
+++ b/drivers/net/ethernet/ti/ethfw_abi.h
@@ -0,0 +1,370 @@
+/* SPDX-License-Identifier: GPL-2.0-only or MIT */
+/* Texas Instruments Ethernet Switch Firmware (EthFw) ABIs
+ *
+ * Copyright (C) 2024 Texas Instruments Incorporated - https://www.ti.com/
+ *
+ */
+
+#ifndef __ETHFW_ABI_H_
+#define __ETHFW_ABI_H_
+
+/* Name of the RPMsg Endpoint announced by EthFw on the RPMsg-Bus */
+#define ETHFW_SERVICE_EP_NAME "ti.ethfw.ethdevice"
+
+/* Response status set by EthFw on the Success of a Request */
+#define ETHFW_RES_OK (0)
+
+/* Response status set by EthFw notifying the Client to retry a Request */
+#define ETHFW_RES_TRY_AGAIN (-1)
+
+/* Default VLAN ID for a Virtual Port */
+#define ETHFW_DFLT_VLAN 0xFFFF
+
+/* EthFw TX Checksum Offload Capability */
+#define ETHFW_TX_CSUM_OFFLOAD BIT(0)
+
+/* EthFw Multicast Filtering Capability */
+#define ETHFW_MCAST_FILTERING BIT(3)
+
+/* Token corresponding to the Linux CPSW Proxy Client assigned by EthFw */
+#define ETHFW_LINUX_CLIENT_TOKEN 3
+
+/* Default token used by Virtual Port to communicate with EthFw */
+#define ETHFW_TOKEN_NONE 0xFFFFFFFF
+
+/* MAC Address length in octets */
+#define ETHFW_MACADDRLEN 6
+
+/* IPV4 Address length in octets */
+#define ETHFW_IPV4ADDRLEN 4
+
+/* Types of request messages sent to EthFw from CPSW Proxy Client */
+enum request_msg_type {
+ /* Request details of Virtual Ports allocated to the Client.
+ * Two types of Virtual Ports exist:
+ * 1. MAC Only Port:
+ * The Physical MAC Port corresponding to this type of Virtual
+ * Port does not belong to the group of MAC Ports which Switch
+ * traffic among themselves. The Physical MAC Port is dedicated
+ * solely to the Client which has been allocated this type of
+ * Virtual Port.
+ *
+ * 2. Switch Port:
+ * The Physical MAC Port corresponding to this type of Virtual
+ * Port belongs to the group of MAC Ports which Switch traffic
+ * among themselves. The Physical MAC Port is shared with other
+ * Clients in terms of the traffic that is sent out of or received
+ * on this port.
+ *
+ * EthFw responds to this request by providing a bitmask of the
+ * Virtual Port IDs for each type of Virtual Port allocated to
+ * the Client.
+ */
+ ETHFW_VIRT_PORT_INFO,
+
+ /* Request usage of a Virtual Port that has been allocated to the
+ * Client.
+ *
+ * EthFw responds with details of the supported MTU size, the number
+ * of TX DMA Channels and the number of RX DMA Flows for the specified
+ * Virtual Port.
+ */
+ ETHFW_VIRT_PORT_ATTACH,
+
+ /* Request disuse of a Virtual Port that was in use prior to the
+ * generation of this request.
+ */
+ ETHFW_VIRT_PORT_DETACH,
+
+ /* Request for allocation of a TX DMA Channel for a Virtual Port.
+ * Client can request as many TX DMA Channels as have been allocated
+ * by EthFw for the specified Virtual Port.
+ *
+ * EthFw responds with the TX PSI-L Thread ID corresponding to
+ * the TX DMA Channel for the Virtual Port to transmit traffic
+ * to CPSW.
+ */
+ ETHFW_ALLOC_TX,
+
+ /* Request for allocation of an RX DMA Flow for a Virtual Port.
+ * Client can request as many RX DMA Flows as have been allocated
+ * by EthFw for the specified Virtual Port.
+ *
+ * EthFw responds with the RX PSI-L Thread ID, the base of the RX
+ * Flow index and the offset from the base of the allocated RX Flow
+ * index. The RX Flow/Channel is used to receive traffic from CPSW.
+ */
+ ETHFW_ALLOC_RX,
+
+ /* Request for allocation of the MAC Address for a Virtual Port.
+ *
+ * EthFw responds with the MAC Address corresponding to the
+ * specified Virtual Port.
+ */
+ ETHFW_ALLOC_MAC,
+
+ /* Request for release of a TX DMA Channel that had been allocated
+ * to the specified Virtual Port.
+ */
+ ETHFW_FREE_TX,
+
+ /* Request for release of an RX DMA Flow that had been allocated to
+ * the specified Virtual Port.
+ */
+ ETHFW_FREE_RX,
+
+ /* Request for release of the MAC Address that had been allocated to
+ * the specified Virtual Port.
+ */
+ ETHFW_FREE_MAC,
+
+ /* Request for usage of the specified MAC Address for the traffic
+ * sent or received on the Virtual Port for which the MAC Address
+ * has been allocated.
+ */
+ ETHFW_MAC_REGISTER,
+
+ /* Request for disuse of the specified MAC Address for the traffic
+ * sent or received on the Virtual Port for which the MAC Address
+ * had been allocated.
+ */
+ ETHFW_MAC_DEREGISTER,
+
+ /* Request for setting the default RX DMA Flow for a Virtual Port. */
+ ETHFW_SET_DEFAULT_RX_FLOW,
+
+ /* Request for deleting the default RX DMA Flow for a Virtual Port. */
+ ETHFW_DEL_DEFAULT_RX_FLOW,
+
+ /* Request for registering the IPv4 Address of the Network Interface
+ * in Linux corresponding to the specified Virtual Port.
+ */
+ ETHFW_IPv4_REGISTER,
+
+ /* Request for deregistering the IPv4 Address of the Network Interface
+ * in Linux corresponding to the specified Virtual Port that had been
+ * registered prior to this request.
+ */
+ ETHFW_IPv4_DEREGISTER,
+
+ /* Request for joining a Multicast Address group */
+ ETHFW_MCAST_FILTER_ADD,
+
+ /* Request for leaving a Multicast Address group */
+ ETHFW_MCAST_FILTER_DEL,
+
+ /* Request to get link status */
+ ETHFW_VIRT_PORT_LINK_STATUS,
+};
+
+enum notify_msg_type {
+ ETHFW_NOTIFYCLIENT_FWINFO,
+ ETHFW_NOTIFYCLIENT_HWPUSH,
+ ETHFW_NOTIFYCLIENT_HWERROR,
+ ETHFW_NOTIFYCLIENT_RECOVERED,
+ ETHFW_NOTIFYCLIENT_CUSTOM,
+ ETHFW_NOTIFYCLIENT_LAST,
+};
+
+enum ethfw_status {
+ ETHFW_INIT,
+ ETHFW_RECOVERY,
+ ETHFW_DEINIT,
+};
+
+enum message_type {
+ ETHFW_MSG_REQUEST,
+ ETHFW_MSG_NOTIFY,
+ ETHFW_MSG_RESPONSE,
+};
+
+struct message_header {
+ u32 token;
+ u32 client_id;
+ u32 msg_type;
+} __packed;
+
+struct message {
+ struct message_header msg_hdr;
+ u32 message_data[120];
+} __packed;
+
+struct request_message_header {
+ struct message_header msg_hdr;
+ u32 request_type;
+ u32 request_id;
+} __packed;
+
+struct response_message_header {
+ struct message_header msg_hdr;
+ u32 response_type; /* Same as request_type */
+ u32 response_id;
+ int response_status;
+} __packed;
+
+struct notify_message_header {
+ struct message_header msg_hdr;
+ u32 notify_type;
+} __packed;
+
+struct common_response_message {
+ struct response_message_header response_msg_hdr;
+} __packed;
+
+struct common_request_message {
+ struct request_message_header request_msg_hdr;
+} __packed;
+
+struct common_notify_message {
+ struct notify_message_header notify_msg_hdr;
+} __packed;
+
+struct virt_port_info_response {
+ struct response_message_header response_msg_hdr;
+ /* Port mask denoting absolute virtual switch ports allocated */
+ u32 switch_port_mask;
+ /* Port mask denoting absolute virtual MAC ports allocated */
+ u32 mac_port_mask;
+} __packed;
+
+struct attach_request {
+ struct request_message_header request_msg_hdr;
+ /* Virtual port which needs core attach */
+ u32 virt_port;
+} __packed;
+
+struct attach_response {
+ struct response_message_header response_msg_hdr;
+ /* MTU of RX packet */
+ u32 rx_mtu;
+ /* MTU of TX packet */
+ u32 tx_mtu;
+ /* Feature bitmask */
+ u32 features;
+ /* Number of TX DMA Channels available for the virtual port */
+ u32 num_tx_chan;
+ /* Number of RX DMA Flows available for the virtual port */
+ u32 num_rx_flow;
+} __packed;
+
+struct rx_flow_alloc_request {
+ struct request_message_header request_msg_hdr;
+ /* Relative index of RX flow among available num_rx_flow flows */
+ u32 rx_flow_idx;
+} __packed;
+
+struct rx_flow_alloc_response {
+ struct response_message_header response_msg_hdr;
+ /* Allocated RX flow index base */
+ u32 rx_flow_idx_base;
+ /* Allocated flow index offset */
+ u32 rx_flow_idx_offset;
+ /* RX PSIL Peer source thread id */
+ u32 rx_psil_src_id;
+} __packed;
+
+struct tx_thread_alloc_request {
+ struct request_message_header request_msg_hdr;
+ /* Relative index of TX channel among available num_tx_chan channels */
+ u32 tx_chan_idx;
+} __packed;
+
+struct tx_thread_alloc_response {
+ struct response_message_header response_msg_hdr;
+ /* TX PSIL peer destination thread id which should be paired with the TX UDMA channel */
+ u32 tx_psil_dest_id;
+} __packed;
+
+struct mac_alloc_response {
+ struct response_message_header response_msg_hdr;
+ /* Allocated MAC address */
+ u8 mac_addr[ETHFW_MACADDRLEN];
+} __packed;
+
+struct rx_flow_release_request {
+ struct request_message_header request_msg_hdr;
+ /* RX flow index base */
+ u32 rx_flow_idx_base;
+ /* RX flow index offset */
+ u32 rx_flow_idx_offset;
+} __packed;
+
+struct tx_thread_release_request {
+ struct request_message_header request_msg_hdr;
+ /* TX PSIL Peer destination thread id to be freed */
+ u32 tx_psil_dest_id;
+} __packed;
+
+struct mac_release_request {
+ struct request_message_header request_msg_hdr;
+ /* MAC address to be freed */
+ u8 mac_addr[ETHFW_MACADDRLEN];
+} __packed;
+
+struct mac_register_deregister_request {
+ struct request_message_header request_msg_hdr;
+ /* MAC address which needs to be registered/deregistered */
+ u8 mac_addr[ETHFW_MACADDRLEN];
+ /* RX flow index Base */
+ u32 rx_flow_idx_base;
+ /* RX flow index offset */
+ u32 rx_flow_idx_offset;
+} __packed;
+
+struct ipv4_register_request {
+ struct request_message_header request_msg_hdr;
+ /* IPv4 Address */
+ u8 ipv4_addr[ETHFW_IPV4ADDRLEN];
+ /* MAC address associated with the IP address which should be added to
+ * the ARP table
+ */
+ u8 mac_addr[ETHFW_MACADDRLEN];
+} __packed;
+
+struct ipv4_deregister_request {
+ struct request_message_header request_msg_hdr;
+ /* IPv4 Address */
+ u8 ipv4_addr[ETHFW_IPV4ADDRLEN];
+} __packed;
+
+struct default_rx_flow_register_request {
+ struct request_message_header request_msg_hdr;
+ /* RX flow index Base */
+ u32 rx_flow_idx_base;
+ /* RX flow index offset */
+ u32 rx_flow_idx_offset;
+} __packed;
+
+struct port_link_status_response {
+ struct response_message_header response_msg_hdr;
+ /* Link status of the port */
+ bool link_up;
+ /* Link speed */
+ u32 speed;
+ /* Duplex mode */
+ u32 duplex;
+} __packed;
+
+struct add_multicast_request {
+ struct request_message_header request_msg_hdr;
+ /* Multicast MAC address to be added */
+ u8 mac_addr[ETHFW_MACADDRLEN];
+ /* VLAN id */
+ u16 vlan_id;
+ /* RX flow index from which the MAC_address association will be added.
+ * It's applicable only for _exclusive multicast traffic_
+ */
+ u32 rx_flow_idx_base;
+ /* RX flow index offset */
+ u32 rx_flow_idx_offset;
+} __packed;
+
+struct del_multicast_request {
+ struct request_message_header request_msg_hdr;
+ /* Multicast MAC address to be added */
+ u8 mac_addr[ETHFW_MACADDRLEN];
+ /* VLAN id */
+ u16 vlan_id;
+} __packed;
+
+#endif /* __ETHFW_ABI_H_ */
--
2.40.1
The CPSW Proxy Client driver interfaces with Ethernet Switch Firmware on
a remote core to enable Ethernet functionality for applications running
on Linux. The Ethernet Switch Firmware (EthFw) is in control of the CPSW
Ethernet Switch on the SoC and acts as the Server, offering services to
Clients running on various cores.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
.../ethernet/ti/cpsw_proxy_client.rst | 182 ++++++++++++++++++
1 file changed, 182 insertions(+)
create mode 100644 Documentation/networking/device_drivers/ethernet/ti/cpsw_proxy_client.rst
diff --git a/Documentation/networking/device_drivers/ethernet/ti/cpsw_proxy_client.rst b/Documentation/networking/device_drivers/ethernet/ti/cpsw_proxy_client.rst
new file mode 100644
index 000000000000..46bff67b3446
--- /dev/null
+++ b/Documentation/networking/device_drivers/ethernet/ti/cpsw_proxy_client.rst
@@ -0,0 +1,182 @@
+.. SPDX-License-Identifier: GPL-2.0-only or MIT
+
+==========================================
+Texas Instruments CPSW Proxy Client driver
+==========================================
+
+Introduction
+============
+
+The CPSW (Common Platform Switch) Ethernet Switch on TI's K3 SoCs provides
+Ethernet functionality. There may be multiple instances of CPSW on a single
+SoC. The term "CPSWnG" is used to indicate the number of MAC Ports supported
+by a specific instance of CPSW. CPSWnG indicates that the peripheral has
+(n-1) MAC Ports and 1 Host Port. Examples of existing instances are:
+CPSW2G => 1 MAC Port and 1 Host Port
+CPSW3G => 2 MAC Ports and 1 Host Port
+CPSW5G => 4 MAC Ports and 1 Host Port
+CPSW9G => 8 MAC Ports and 1 Host Port
+
+The presence of 2 or more MAC Ports implies that Hardware Switching can
+be enabled between the MAC Ports if required.
+
+The "am65-cpsw-nuss.c" driver in Linux at:
+drivers/net/ethernet/ti/am65-cpsw-nuss.c
+provides Ethernet functionality for applications on Linux.
+It also handles both the control-path and data-path, namely:
+Control => Configuration of the CPSW Peripheral
+Data => Configuration of the DMA Channels to transmit/receive data
+
+The aforementioned configuration supports use-cases where all applications
+which require Ethernet functionality are only running on Linux.
+
+However, there are use-cases where applications running on different
+Operating Systems across multiple cores on the SoC require Ethernet
+functionality. Such use-cases can be supported by implementing a
+Client-Server model to share the data-path among Clients while the Server
+owns the control-path.
+
+On TI's K3 SoCs (J721E, J7200 and J784S4 in particular), the Ethernet Switch
+Firmware (EthFw) running on the MAIN R5F core acts as the Server and
+configures the CPSWnG instance (CPSW5G on J7200 and CPSW9G on J721E, J784S4)
+of the CPSW Ethernet Switch on the SoC. The Clients running on various cores
+communicate with EthFw via RPMsg (Remote Processor Messaging) to request
+resource allocation details during initialization, followed by requesting
+EthFw to enable features supported by CPSW based on the features required
+by the applications running on the respective cores.
+
+EthFw handles requests from the Clients and evaluates them before configuring
+CPSW based on the request. Since no Client is actually in control of CPSW and
+only requests EthFw for configuring CPSW, EthFw acts as the proxy for the
+Clients. Thus, the Linux Client which interfaces with EthFw is named:
+CPSW Proxy Client
+
+The data-path for the CPSW Proxy Client driver remains identical to the
+"am65-cpsw-nuss.c" driver which happens to be DMA. It is only the control-path
+that is different.
+
+Client-Server discovery occurs over the RPMsg-Bus. EthFw announces its
+RPMsg Endpoint name over the RPMsg-Bus. The CPSW Proxy Client driver
+registers itself with the Linux RPMsg framework to be probed for the same
+Endpoint name. Following probe, the Linux Client driver begins communicating
+with EthFw and queries details of the resources available for the Linux Client.
+
+Terminology
+===========
+
+Virtual Port
+ A Virtual Port refers to the Software View of an Ethernet MAC Port.
+ There are two types of Virtual Ports:
+ 1. Virtual MAC Only Port
+ 2. Virtual Switch Port
+
+Virtual MAC Only Port
+ A Virtual MAC only Port refers to a dedicated physical MAC Port for
+ a Client. This corresponds to MAC Mode of operation in Ethernet
+ Terminology. All traffic sent to or received from the Physical
+ MAC Port is that of the Client to which the Virtual MAC Only Port
+ has been allocated.
+
+Virtual Switch Port
+ A Virtual Switch Port refers to a group of physical MAC ports with
+ Switching enabled across them. This implies that any traffic sent
+ to the Port from a Client could potentially exit a Physical MAC
+ Port along with the traffic from other Clients. Similarly, the traffic
+ received on the Port by a Client could have potentially ingressed
+ on a Physical MAC Port along with the traffic meant for other Clients.
+ While the ALE (Address Lookup Engine) handles segregating the traffic,
+ and the CPSW Ethernet Switch places traffic on dedicated RX DMA Flows
+ meant for a single Client, it is worth noting that the bandwidths
+ of the Physical MAC Port are shared by Clients when traffic is sent to
+ or received from a Virtual Switch Port.
+
+Network Interface
+ The user-visible interface in Linux userspace exposed to applications
+ that serves as the entry/exit point for traffic to/from the Virtual
+ Ports. A single network interface (ethX) maps to either a Virtual
+ MAC Only Port or a Virtual Switch Port.
+
+C2S
+ RPMsg source is Client and destination is Server.
+
+S2C
+ RPMsg source is Server and destination is Client.
+
+Initialization Sequence
+=======================
+
+The sequence of message exchanges between the Client driver and EthFw starting
+from the driver probe and ending with the interfaces being brought up is as
+follows:
+1. C2S ETHFW_VIRT_PORT_INFO requesting details of Virtual Ports available
+ for the Linux Client.
+2. S2C response containing requested details
+3. C2S ETHFW_VIRT_PORT_ATTACH request for each Virtual Port allocated during
+ step 2.
+4. S2C response containing details of the MTU Size, number of Tx DMA Channels
+ and RX DMA Flows for the specified Virtual Port. The *Features* associated
+ with the Virtual Port are also shared such as Multicast Filtering capability.
+5. C2S ETHFW_ALLOC_RX request for each RX DMA Flow for a Virtual Port.
+6. S2C response containing details of the RX PSI-L Thread ID, Flow base and
+ Flow offset.
+7. C2S ETHFW_ALLOC_TX request for each TX DMA Channel for a Virtual Port.
+8. S2C response containing details of the TX PSI-L Thread ID.
+9. C2S ETHFW_ALLOC_MAC request for each Virtual Port.
+10. S2C response containing the MAC Address corresponding to the Virtual Port.
+11. C2S ETHFW_MAC_REGISTER request for each Virtual Port with the MAC Address
+ allocated in step 10. This is necessary to steer packets that ingress on
+ the MAC Ports of CPSW onto the RX DMA Flow for the Virtual Port in order
+ to allow the Client to receive the packets.
+12. S2C response indicating status of request.
+13. C2S ETHFW_IPv4_REGISTER request *only* for Virtual Switch Port interface.
+ The IPv4 address assigned to the "ethX" network interface in Linux
+ corresponding to the Virtual Switch Port interface has to be registered
+ with EthFw. This is due to the reason that all Broadcast requests including
+ ARP requests received by the MAC Ports corresponding to the Virtual Switch
+ Port are consumed solely be EthFw. Such traffic is sent to Clients by
+ alternate methods. Therefore EthFw needs to know the IPv4 address for the
+ "ethX" network interface in Linux in order to automatically respond to
+ ARP requests, thereby enabling Unicast communication.
+14. S2C response indicating status of request.
+15. C2S ETHFW_MCAST_FILTER_ADD request to register the Multicast Addresses
+ associated with the network interface corresponding to the Virtual Port
+ which has the Multicast Filtering capability.
+16. S2C response indicating status of request.
+17. C2S ETHFW_MCAST_FILTER_DEL request to deregister the Multicast Addresses
+ associated with the network interface corresponding to the Virtual Port
+ which has the Multicast Filtering capability.
+18. S2C response indicating status of request.
+
+Shutdown Sequence
+=================
+
+The sequence of message exchanges between the Client driver and EthFw on module
+removal are as follows:
+1. C2S ETHFW_MAC_DEREGISTER request to deregister the MAC Address for each
+ Virtual Port.
+2. S2C response indicating status of request.
+3. C2S ETHFW_MCAST_FILTER_DEL request to deregister the Multicast Addresses
+ associated with the network interface corresponding to the Virtual Port
+ which has the Multicast Filtering capability.
+4. S2C response indicating status of request.
+5. C2S ETHFW_FREE_MAC request to release the MAC Address allocated to each
+ Virtual Port.
+6. S2C response indicating status of request.
+7. C2S ETHFW_FREE_TX request to release the TX DMA Channel for each TX Channel
+ for every Virtual Port.
+8. S2C response indicating status of request.
+9. C2S ETHFW_FREE_RX request to release the RX DMA Flow for each RX Channel
+ for every Virtual Port.
+10. S2C response indicating status of request.
+11. C2S ETHFW_VIRT_PORT_DETACH request to release each Virtual Port.
+12. S2C response indicating status of request.
+
+Features Supported
+==================
+
+The set of features supported in addition to providing basic Ethernet
+Functionality are:
+1. Multicast Filtering
+2. Determining Link Status of the network interface corresponding to the
+ Virtual MAC Only port via ethtool.
+3. Interrupt Pacing/Coalescing
--
2.40.1
Add the helper function "get_virtual_port_info()" to send the
ETHFW_VIRT_PORT_INFO request and store details of virtual port
allocation. The details include type of virtual port, the virtual
port ID and the token identifying the virtual port.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 75 +++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 70b8cfe67921..262fbf59ac72 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -14,6 +14,11 @@
#define ETHFW_RESPONSE_TIMEOUT_MS 500
+enum virtual_port_type {
+ VIRT_SWITCH_PORT,
+ VIRT_MAC_ONLY_PORT,
+};
+
struct cpsw_proxy_req_params {
struct message req_msg; /* Request message to be filled */
u32 token;
@@ -30,13 +35,26 @@ struct cpsw_proxy_req_params {
u8 ipv4_addr[ETHFW_IPV4ADDRLEN];
};
+struct virtual_port {
+ struct cpsw_proxy_priv *proxy_priv;
+ enum virtual_port_type port_type;
+ u32 port_id;
+};
+
struct cpsw_proxy_priv {
struct rpmsg_device *rpdev;
struct device *dev;
+ struct virtual_port *virt_ports;
struct cpsw_proxy_req_params req_params;
+ struct mutex req_params_mutex; /* Request params mutex */
struct message resp_msg;
struct completion wait_for_response;
int resp_msg_len;
+ u32 vswitch_ports; /* Bitmask of Virtual Switch Port IDs */
+ u32 vmac_ports /* Bitmask of Virtual MAC Only Port IDs */;
+ u32 num_switch_ports;
+ u32 num_mac_ports;
+ u32 num_virt_ports;
};
static int cpsw_proxy_client_cb(struct rpmsg_device *rpdev, void *data,
@@ -275,6 +293,63 @@ static int send_request_get_response(struct cpsw_proxy_priv *proxy_priv,
return ret;
}
+static int get_virtual_port_info(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct virt_port_info_response *vpi_resp;
+ struct cpsw_proxy_req_params *req_p;
+ struct virtual_port *vport;
+ struct message resp_msg;
+ unsigned int vp_id, i;
+ int ret;
+
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_VIRT_PORT_INFO;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+
+ if (ret) {
+ dev_err(proxy_priv->dev, "failed to get virtual port info\n");
+ return ret;
+ }
+
+ vpi_resp = (struct virt_port_info_response *)&resp_msg;
+ proxy_priv->vswitch_ports = vpi_resp->switch_port_mask;
+ proxy_priv->vmac_ports = vpi_resp->mac_port_mask;
+ /* Number of 1s set in vswitch_ports is the count of switch ports */
+ proxy_priv->num_switch_ports = hweight32(proxy_priv->vswitch_ports);
+ proxy_priv->num_virt_ports = proxy_priv->num_switch_ports;
+ /* Number of 1s set in vmac_ports is the count of mac ports */
+ proxy_priv->num_mac_ports = hweight32(proxy_priv->vmac_ports);
+ proxy_priv->num_virt_ports += proxy_priv->num_mac_ports;
+
+ proxy_priv->virt_ports = devm_kcalloc(proxy_priv->dev,
+ proxy_priv->num_virt_ports,
+ sizeof(*proxy_priv->virt_ports),
+ GFP_KERNEL);
+
+ vp_id = 0;
+ for (i = 0; i < proxy_priv->num_switch_ports; i++) {
+ vport = &proxy_priv->virt_ports[vp_id];
+ vport->proxy_priv = proxy_priv;
+ vport->port_type = VIRT_SWITCH_PORT;
+ /* Port ID is derived from the bit set in the bitmask */
+ vport->port_id = fns(proxy_priv->vswitch_ports, i);
+ vp_id++;
+ }
+
+ for (i = 0; i < proxy_priv->num_mac_ports; i++) {
+ vport = &proxy_priv->virt_ports[vp_id];
+ vport->proxy_priv = proxy_priv;
+ vport->port_type = VIRT_MAC_ONLY_PORT;
+ /* Port ID is derived from the bit set in the bitmask */
+ vport->port_id = fns(proxy_priv->vmac_ports, i);
+ vp_id++;
+ }
+
+ return 0;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add a new function which sends RPMsg requests to EthFw and shares the
response for the request. The RPMsg callback function copies the response
it receives from EthFw to the driver's private member, thereby allowing
the response to be shared with the newly added function which sends the
request.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 112 +++++++++++++++++++-
1 file changed, 111 insertions(+), 1 deletion(-)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 3533f4ce1e3f..70b8cfe67921 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -12,6 +12,8 @@
#include "ethfw_abi.h"
+#define ETHFW_RESPONSE_TIMEOUT_MS 500
+
struct cpsw_proxy_req_params {
struct message req_msg; /* Request message to be filled */
u32 token;
@@ -31,16 +33,50 @@ struct cpsw_proxy_req_params {
struct cpsw_proxy_priv {
struct rpmsg_device *rpdev;
struct device *dev;
+ struct cpsw_proxy_req_params req_params;
+ struct message resp_msg;
+ struct completion wait_for_response;
+ int resp_msg_len;
};
static int cpsw_proxy_client_cb(struct rpmsg_device *rpdev, void *data,
int len, void *priv, u32 src)
{
+ struct cpsw_proxy_priv *proxy_priv = dev_get_drvdata(&rpdev->dev);
+ struct response_message_header *resp_msg_hdr;
+ struct message *msg = (struct message *)data;
+ struct cpsw_proxy_req_params *req_params;
struct device *dev = &rpdev->dev;
+ u32 msg_type, resp_id;
dev_dbg(dev, "callback invoked\n");
+ msg_type = msg->msg_hdr.msg_type;
+ switch (msg_type) {
+ case ETHFW_MSG_RESPONSE:
+ resp_msg_hdr = (struct response_message_header *)msg;
+ resp_id = resp_msg_hdr->response_id;
+ req_params = &proxy_priv->req_params;
- return 0;
+ if (unlikely(resp_id == req_params->request_id - 1)) {
+ dev_info(dev, "ignoring late response for request: %u\n",
+ resp_id);
+ return 0;
+ } else if (unlikely(resp_id != req_params->request_id)) {
+ dev_err(dev, "expected response id: %u but received %u\n",
+ req_params->request_id, resp_id);
+ return -EINVAL;
+ }
+
+ /* Share response */
+ memcpy(&proxy_priv->resp_msg, msg, len);
+ proxy_priv->resp_msg_len = len;
+ complete(&proxy_priv->wait_for_response);
+ return 0;
+
+ default:
+ dev_err(dev, "unsupported message received from EthFw\n");
+ return -EOPNOTSUPP;
+ }
}
static int create_request_message(struct cpsw_proxy_req_params *req_params)
@@ -166,6 +202,79 @@ static int create_request_message(struct cpsw_proxy_req_params *req_params)
return 0;
}
+/* Send a request to EthFw and receive the response for request.
+ * Since the response is received by the callback function, it is
+ * copied to "resp_msg" member of "struct cpsw_proxy_priv" to
+ * allow sharing it with the following function.
+ *
+ * The request parameters within proxy_priv are expected to be set
+ * correctly by the caller. The caller is also expected to acquire
+ * lock before invoking this function, since requests and responses
+ * to/from EthFw are serialized.
+ */
+static int send_request_get_response(struct cpsw_proxy_priv *proxy_priv,
+ struct message *response)
+{
+ struct cpsw_proxy_req_params *req_params = &proxy_priv->req_params;
+ struct message *send_msg = &req_params->req_msg;
+ struct rpmsg_device *rpdev = proxy_priv->rpdev;
+ struct response_message_header *resp_msg_hdr;
+ struct device *dev = proxy_priv->dev;
+ unsigned long timeout;
+ u32 resp_status;
+ bool retry = 0;
+ int ret;
+
+ ret = create_request_message(req_params);
+ if (ret) {
+ dev_err(dev, "failed to create request %d\n", ret);
+ goto err;
+ }
+
+ /* Send request and wait for callback function to acknowledge
+ * receiving the response.
+ */
+ reinit_completion(&proxy_priv->wait_for_response);
+ ret = rpmsg_send(rpdev->ept, (void *)(send_msg),
+ sizeof(struct message));
+ if (ret) {
+ dev_err(dev, "failed to send rpmsg\n");
+ goto err;
+ }
+ timeout = msecs_to_jiffies(ETHFW_RESPONSE_TIMEOUT_MS);
+ ret = wait_for_completion_timeout(&proxy_priv->wait_for_response,
+ timeout);
+ if (!ret) {
+ dev_err(dev, "response timedout\n");
+ ret = -ETIMEDOUT;
+ goto err;
+ }
+ ret = 0;
+
+ /* Store response shared by callback function */
+ memcpy(response, &proxy_priv->resp_msg, proxy_priv->resp_msg_len);
+ resp_msg_hdr = (struct response_message_header *)response;
+ resp_status = resp_msg_hdr->response_status;
+
+ if (unlikely(resp_status != ETHFW_RES_OK)) {
+ if (resp_status == ETHFW_RES_TRY_AGAIN) {
+ dev_info(dev, "resending request\n");
+ ret = -EAGAIN;
+ retry = 1;
+ } else {
+ dev_err(dev, "bad response status: %d\n", resp_status);
+ ret = -EIO;
+ }
+ }
+
+err:
+ req_params->request_id++;
+ if (retry)
+ ret = send_request_get_response(proxy_priv, response);
+
+ return ret;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
@@ -176,6 +285,7 @@ static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
proxy_priv->rpdev = rpdev;
proxy_priv->dev = &rpdev->dev;
+ dev_set_drvdata(proxy_priv->dev, proxy_priv);
dev_dbg(proxy_priv->dev, "driver probed\n");
return 0;
--
2.40.1
The CPSW Proxy Client driver exchanges various types of requests with EthFw
to achieve desired functionality. Add a function to create request messages
that can be sent to EthFw over RPMsg-Bus, given the type of the request
message and additional parameters required to form the message.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 140 ++++++++++++++++++++
1 file changed, 140 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 91d3338b3788..3533f4ce1e3f 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -5,12 +5,29 @@
*
*/
+#include <linux/etherdevice.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
#include "ethfw_abi.h"
+struct cpsw_proxy_req_params {
+ struct message req_msg; /* Request message to be filled */
+ u32 token;
+ u32 client_id;
+ u32 request_id;
+ u32 request_type;
+ u32 rx_tx_idx; /* RX or TX Channel index */
+ u32 rx_flow_base; /* RX DMA Flow base */
+ u32 rx_flow_offset; /* RX DMA Flow offset */
+ u32 tx_thread_id; /* PSI-L Thread ID of TX Channel */
+ u32 port_id; /* Virtual Port ID */
+ u16 vlan_id;
+ u8 mac_addr[ETH_ALEN];
+ u8 ipv4_addr[ETHFW_IPV4ADDRLEN];
+};
+
struct cpsw_proxy_priv {
struct rpmsg_device *rpdev;
struct device *dev;
@@ -26,6 +43,129 @@ static int cpsw_proxy_client_cb(struct rpmsg_device *rpdev, void *data,
return 0;
}
+static int create_request_message(struct cpsw_proxy_req_params *req_params)
+{
+ struct mac_register_deregister_request *mac_reg_dereg_req;
+ struct ipv4_deregister_request *ipv4_dereg_req;
+ struct common_request_message *common_req_msg;
+ struct tx_thread_release_request *tx_free_req;
+ struct tx_thread_alloc_request *tx_alloc_req;
+ struct add_multicast_request *mcast_add_req;
+ struct del_multicast_request *mcast_del_req;
+ struct rx_flow_release_request *rx_free_req;
+ struct ipv4_register_request *ipv4_reg_req;
+ struct request_message_header *req_msg_hdr;
+ struct rx_flow_alloc_request *rx_alloc_req;
+ struct message *msg = &req_params->req_msg;
+ struct mac_release_request *mac_free_req;
+ struct attach_request *attach_req;
+ u32 req_type;
+
+ /* Set message header fields */
+ msg->msg_hdr.token = req_params->token;
+ msg->msg_hdr.client_id = req_params->client_id;
+ msg->msg_hdr.msg_type = ETHFW_MSG_REQUEST;
+
+ req_type = req_params->request_type;
+
+ switch (req_type) {
+ case ETHFW_ALLOC_RX:
+ rx_alloc_req = (struct rx_flow_alloc_request *)msg;
+ req_msg_hdr = &rx_alloc_req->request_msg_hdr;
+ rx_alloc_req->rx_flow_idx = req_params->rx_tx_idx;
+ break;
+
+ case ETHFW_ALLOC_TX:
+ tx_alloc_req = (struct tx_thread_alloc_request *)msg;
+ req_msg_hdr = &tx_alloc_req->request_msg_hdr;
+ tx_alloc_req->tx_chan_idx = req_params->rx_tx_idx;
+ break;
+
+ case ETHFW_VIRT_PORT_ATTACH:
+ attach_req = (struct attach_request *)msg;
+ req_msg_hdr = &attach_req->request_msg_hdr;
+ attach_req->virt_port = req_params->port_id;
+ break;
+
+ case ETHFW_FREE_MAC:
+ mac_free_req = (struct mac_release_request *)msg;
+ req_msg_hdr = &mac_free_req->request_msg_hdr;
+ ether_addr_copy(mac_free_req->mac_addr, req_params->mac_addr);
+ break;
+
+ case ETHFW_FREE_RX:
+ rx_free_req = (struct rx_flow_release_request *)msg;
+ req_msg_hdr = &rx_free_req->request_msg_hdr;
+ rx_free_req->rx_flow_idx_base = req_params->rx_flow_base;
+ rx_free_req->rx_flow_idx_offset = req_params->rx_flow_offset;
+ break;
+
+ case ETHFW_FREE_TX:
+ tx_free_req = (struct tx_thread_release_request *)msg;
+ req_msg_hdr = &tx_free_req->request_msg_hdr;
+ tx_free_req->tx_psil_dest_id = req_params->tx_thread_id;
+ break;
+
+ case ETHFW_IPv4_DEREGISTER:
+ ipv4_dereg_req = (struct ipv4_deregister_request *)msg;
+ req_msg_hdr = &ipv4_dereg_req->request_msg_hdr;
+ memcpy(&ipv4_dereg_req->ipv4_addr, req_params->ipv4_addr,
+ ETHFW_IPV4ADDRLEN);
+ break;
+
+ case ETHFW_IPv4_REGISTER:
+ ipv4_reg_req = (struct ipv4_register_request *)msg;
+ req_msg_hdr = &ipv4_reg_req->request_msg_hdr;
+ memcpy(&ipv4_reg_req->ipv4_addr, req_params->ipv4_addr,
+ ETHFW_IPV4ADDRLEN);
+ ether_addr_copy(ipv4_reg_req->mac_addr,
+ req_params->mac_addr);
+ break;
+
+ case ETHFW_MAC_DEREGISTER:
+ case ETHFW_MAC_REGISTER:
+ mac_reg_dereg_req = (struct mac_register_deregister_request *)msg;
+ req_msg_hdr = &mac_reg_dereg_req->request_msg_hdr;
+ ether_addr_copy(mac_reg_dereg_req->mac_addr,
+ req_params->mac_addr);
+ mac_reg_dereg_req->rx_flow_idx_base = req_params->rx_flow_base;
+ mac_reg_dereg_req->rx_flow_idx_offset = req_params->rx_flow_offset;
+ break;
+
+ case ETHFW_MCAST_FILTER_ADD:
+ mcast_add_req = (struct add_multicast_request *)msg;
+ req_msg_hdr = &mcast_add_req->request_msg_hdr;
+ ether_addr_copy(mcast_add_req->mac_addr, req_params->mac_addr);
+ mcast_add_req->vlan_id = req_params->vlan_id;
+ mcast_add_req->rx_flow_idx_base = req_params->rx_flow_base;
+ mcast_add_req->rx_flow_idx_offset = req_params->rx_flow_offset;
+ break;
+
+ case ETHFW_MCAST_FILTER_DEL:
+ mcast_del_req = (struct del_multicast_request *)msg;
+ req_msg_hdr = &mcast_del_req->request_msg_hdr;
+ ether_addr_copy(mcast_del_req->mac_addr, req_params->mac_addr);
+ mcast_del_req->vlan_id = req_params->vlan_id;
+ break;
+
+ case ETHFW_ALLOC_MAC:
+ case ETHFW_VIRT_PORT_DETACH:
+ case ETHFW_VIRT_PORT_INFO:
+ common_req_msg = (struct common_request_message *)msg;
+ req_msg_hdr = &common_req_msg->request_msg_hdr;
+ break;
+
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ /* Set request message header fields */
+ req_msg_hdr->request_id = req_params->request_id;
+ req_msg_hdr->request_type = req_params->request_type;
+
+ return 0;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add the helper function "attach_virtual_ports()" to send the
ETHFW_ATTACH_VIRT_PORT request for each virtual port and store details
of features corresponding to the virtual port, the number of TX DMA
Channels allocated to the virtual port and the number of RX DMA Channels
allocated to the virtual port. If attaching any of the virtual ports
fails, detach all previously attached virtual ports.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 90 +++++++++++++++++++++
1 file changed, 90 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 262fbf59ac72..691b36bc3715 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -35,10 +35,26 @@ struct cpsw_proxy_req_params {
u8 ipv4_addr[ETHFW_IPV4ADDRLEN];
};
+struct rx_dma_chan {
+ struct virtual_port *vport;
+ u32 rel_chan_idx;
+};
+
+struct tx_dma_chan {
+ struct virtual_port *vport;
+ u32 rel_chan_idx;
+};
+
struct virtual_port {
struct cpsw_proxy_priv *proxy_priv;
+ struct rx_dma_chan *rx_chans;
+ struct tx_dma_chan *tx_chans;
enum virtual_port_type port_type;
u32 port_id;
+ u32 port_token;
+ u32 port_features;
+ u32 num_rx_chan;
+ u32 num_tx_chan;
};
struct cpsw_proxy_priv {
@@ -350,6 +366,80 @@ static int get_virtual_port_info(struct cpsw_proxy_priv *proxy_priv)
return 0;
}
+static int attach_virtual_ports(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct cpsw_proxy_req_params *req_p;
+ struct attach_response *att_resp;
+ struct rx_dma_chan *rx_chn;
+ struct tx_dma_chan *tx_chn;
+ struct virtual_port *vport;
+ struct message resp_msg;
+ unsigned int i, j;
+ u32 port_id;
+ int ret;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ port_id = vport->port_id;
+
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->port_id = port_id;
+ req_p->request_type = ETHFW_VIRT_PORT_ATTACH;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+
+ if (ret) {
+ dev_err(proxy_priv->dev, "attaching virtual port failed\n");
+ goto err;
+ }
+
+ att_resp = (struct attach_response *)&resp_msg;
+ vport->port_token = att_resp->response_msg_hdr.msg_hdr.token;
+ vport->port_features = att_resp->features;
+ vport->num_tx_chan = att_resp->num_tx_chan;
+ vport->num_rx_chan = att_resp->num_rx_flow;
+
+ vport->rx_chans = devm_kcalloc(proxy_priv->dev,
+ vport->num_rx_chan,
+ sizeof(*vport->rx_chans),
+ GFP_KERNEL);
+ for (j = 0; j < vport->num_rx_chan; j++) {
+ rx_chn = &vport->rx_chans[j];
+ rx_chn->vport = vport;
+ rx_chn->rel_chan_idx = j;
+ }
+
+ vport->tx_chans = devm_kcalloc(proxy_priv->dev,
+ vport->num_tx_chan,
+ sizeof(*vport->tx_chans),
+ GFP_KERNEL);
+ for (j = 0; j < vport->num_tx_chan; j++) {
+ tx_chn = &vport->tx_chans[j];
+ tx_chn->vport = vport;
+ tx_chn->rel_chan_idx = j;
+ }
+ }
+
+ return 0;
+
+err:
+ /* Detach virtual ports which were successfully attached */
+ while (i--) {
+ vport = &proxy_priv->virt_ports[i];
+ port_id = vport->port_id;
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_VIRT_PORT_DETACH;
+ req_p->token = vport->port_token;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret)
+ dev_err(proxy_priv->dev, "detaching virtual port %u failed\n", port_id);
+ }
+ return -EIO;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add the function "allocate_port_resources()" to request EthFw to
allocate TX and RX DMA Channels and MAC Address for each Virtual Port
which has been allocated to the Client. If allocating any of the
resources fails, release all resources which were allocated earlier
through the "free_port_resources()" function. During the process of
freeing resources, if any request fails, avoid attempting to release
other resources. This is due to the assumption that EthFw is
non-functional and all further requests to free resources will most
likely fail.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 165 ++++++++++++++++++++
1 file changed, 165 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 691b36bc3715..b057cf4b7bea 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -38,11 +38,17 @@ struct cpsw_proxy_req_params {
struct rx_dma_chan {
struct virtual_port *vport;
u32 rel_chan_idx;
+ u32 flow_base;
+ u32 flow_offset;
+ u32 thread_id;
+ bool in_use;
};
struct tx_dma_chan {
struct virtual_port *vport;
u32 rel_chan_idx;
+ u32 thread_id;
+ bool in_use;
};
struct virtual_port {
@@ -55,6 +61,8 @@ struct virtual_port {
u32 port_features;
u32 num_rx_chan;
u32 num_tx_chan;
+ u8 mac_addr[ETH_ALEN];
+ bool mac_in_use;
};
struct cpsw_proxy_priv {
@@ -440,6 +448,163 @@ static int attach_virtual_ports(struct cpsw_proxy_priv *proxy_priv)
return -EIO;
}
+static void free_port_resources(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct cpsw_proxy_req_params *req_p;
+ struct rx_dma_chan *rx_chn;
+ struct tx_dma_chan *tx_chn;
+ struct virtual_port *vport;
+ struct message resp_msg;
+ u32 port_id, i, j;
+ int ret;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ port_id = vport->port_id;
+
+ /* Free allocated MAC */
+ if (vport->mac_in_use) {
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_FREE_MAC;
+ req_p->token = vport->port_token;
+ ether_addr_copy(req_p->mac_addr, vport->mac_addr);
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret) {
+ dev_err(proxy_priv->dev,
+ "failed to free MAC Address for port %u err: %d\n",
+ port_id, ret);
+ return;
+ }
+ }
+
+ /* Free TX DMA Channels */
+ for (j = 0; j < vport->num_tx_chan; j++) {
+ tx_chn = &vport->tx_chans[j];
+ if (!tx_chn->in_use)
+ continue;
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_FREE_TX;
+ req_p->token = vport->port_token;
+ req_p->tx_thread_id = tx_chn->thread_id;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret) {
+ dev_err(proxy_priv->dev,
+ "failed to free TX Channel for port %u err: %d\n",
+ port_id, ret);
+ return;
+ }
+ }
+
+ /* Free RX DMA Channels */
+ for (j = 0; j < vport->num_rx_chan; j++) {
+ rx_chn = &vport->rx_chans[j];
+ if (!rx_chn->in_use)
+ continue;
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_FREE_RX;
+ req_p->token = vport->port_token;
+ req_p->rx_flow_base = rx_chn->flow_base;
+ req_p->rx_flow_offset = rx_chn->flow_offset;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret) {
+ dev_err(proxy_priv->dev,
+ "failed to free RX Channel for port %u err: %d\n",
+ port_id, ret);
+ return;
+ }
+ }
+ }
+}
+
+static int allocate_port_resources(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct tx_thread_alloc_response *tta_resp;
+ struct rx_flow_alloc_response *rfa_resp;
+ struct cpsw_proxy_req_params *req_p;
+ struct mac_alloc_response *ma_resp;
+ struct rx_dma_chan *rx_chn;
+ struct tx_dma_chan *tx_chn;
+ struct virtual_port *vport;
+ struct message resp_msg;
+ u32 port_id, i, j;
+ int ret;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ port_id = vport->port_id;
+
+ /* Request RX DMA Flow allocation */
+ for (j = 0; j < vport->num_rx_chan; j++) {
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_ALLOC_RX;
+ req_p->token = vport->port_token;
+ req_p->rx_tx_idx = j;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret) {
+ dev_err(proxy_priv->dev, "RX Alloc for port %u failed\n", port_id);
+ goto err;
+ }
+
+ rfa_resp = (struct rx_flow_alloc_response *)&resp_msg;
+ rx_chn = &vport->rx_chans[j];
+ rx_chn->flow_base = rfa_resp->rx_flow_idx_base;
+ rx_chn->flow_offset = rfa_resp->rx_flow_idx_offset;
+ rx_chn->thread_id = rfa_resp->rx_psil_src_id;
+ rx_chn->in_use = 1;
+ }
+
+ /* Request TX DMA Channel allocation */
+ for (j = 0; j < vport->num_tx_chan; j++) {
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_ALLOC_TX;
+ req_p->token = vport->port_token;
+ req_p->rx_tx_idx = j;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret) {
+ dev_err(proxy_priv->dev, "TX Alloc for port %u failed\n", port_id);
+ goto err;
+ }
+
+ tta_resp = (struct tx_thread_alloc_response *)&resp_msg;
+ tx_chn = &vport->tx_chans[j];
+ tx_chn->thread_id = tta_resp->tx_psil_dest_id;
+ tx_chn->in_use = 1;
+ }
+
+ /* Request MAC allocation */
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_ALLOC_MAC;
+ req_p->token = vport->port_token;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret) {
+ dev_err(proxy_priv->dev, "MAC Alloc for port %u failed\n", port_id);
+ goto err;
+ }
+
+ ma_resp = (struct mac_alloc_response *)&resp_msg;
+ ether_addr_copy(vport->mac_addr, ma_resp->mac_addr);
+ vport->mac_in_use = 1;
+ }
+
+ return 0;
+
+err:
+ free_port_resources(proxy_priv);
+ return -EIO;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add the "init_tx_chans()" function to initialize the TX DMA Channels.
With the knowledge of the PSI-L Thread IDs allocated to the Client for
each Virtual Port, the TX DMA Channels can be setup using the DMA APIs.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 115 ++++++++++++++++++++
1 file changed, 115 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index b057cf4b7bea..efb44ff04b6a 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -9,11 +9,20 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
+#include <linux/dma/k3-udma-glue.h>
#include "ethfw_abi.h"
+#include "k3-cppi-desc-pool.h"
#define ETHFW_RESPONSE_TIMEOUT_MS 500
+#define PS_DATA_SIZE 16
+#define SW_DATA_SIZE 16
+
+#define MAX_TX_DESC 500
+
+#define CHAN_NAME_LEN 128
+
enum virtual_port_type {
VIRT_SWITCH_PORT,
VIRT_MAC_ONLY_PORT,
@@ -46,8 +55,14 @@ struct rx_dma_chan {
struct tx_dma_chan {
struct virtual_port *vport;
+ struct device *dev;
+ struct k3_cppi_desc_pool *desc_pool;
+ struct k3_udma_glue_tx_channel *tx_chan;
u32 rel_chan_idx;
u32 thread_id;
+ u32 num_descs;
+ unsigned int irq;
+ char tx_chan_name[CHAN_NAME_LEN];
bool in_use;
};
@@ -68,6 +83,7 @@ struct virtual_port {
struct cpsw_proxy_priv {
struct rpmsg_device *rpdev;
struct device *dev;
+ struct device_node *dma_node;
struct virtual_port *virt_ports;
struct cpsw_proxy_req_params req_params;
struct mutex req_params_mutex; /* Request params mutex */
@@ -79,6 +95,7 @@ struct cpsw_proxy_priv {
u32 num_switch_ports;
u32 num_mac_ports;
u32 num_virt_ports;
+ u32 num_active_tx_chans;
};
static int cpsw_proxy_client_cb(struct rpmsg_device *rpdev, void *data,
@@ -605,6 +622,104 @@ static int allocate_port_resources(struct cpsw_proxy_priv *proxy_priv)
return -EIO;
}
+static void free_tx_chns(void *data)
+{
+ struct cpsw_proxy_priv *proxy_priv = data;
+ struct tx_dma_chan *tx_chn;
+ struct virtual_port *vport;
+ u32 i, j;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ for (j = 0; j < vport->num_tx_chan; j++) {
+ tx_chn = &vport->tx_chans[j];
+
+ if (!IS_ERR_OR_NULL(tx_chn->desc_pool))
+ k3_cppi_desc_pool_destroy(tx_chn->desc_pool);
+
+ if (!IS_ERR_OR_NULL(tx_chn->tx_chan))
+ k3_udma_glue_release_tx_chn(tx_chn->tx_chan);
+
+ memset(tx_chn, 0, sizeof(*tx_chn));
+ }
+ }
+}
+
+static int init_tx_chans(struct cpsw_proxy_priv *proxy_priv)
+{
+ u32 max_desc_num = ALIGN(MAX_TX_DESC, MAX_SKB_FRAGS);
+ struct k3_udma_glue_tx_channel_cfg tx_cfg = { 0 };
+ struct device *dev = proxy_priv->dev;
+ u32 hdesc_size, tx_chn_num, i, j;
+ char tx_chn_name[CHAN_NAME_LEN];
+ struct k3_ring_cfg ring_cfg = {
+ .elm_size = K3_RINGACC_RING_ELSIZE_8,
+ .mode = K3_RINGACC_RING_MODE_RING,
+ .flags = 0
+ };
+ struct tx_dma_chan *tx_chn;
+ struct virtual_port *vport;
+ int ret = 0, ret1;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+
+ for (j = 0; j < vport->num_tx_chan; j++) {
+ tx_chn = &vport->tx_chans[j];
+
+ tx_chn_num = proxy_priv->num_active_tx_chans++;
+ snprintf(tx_chn_name, sizeof(tx_chn_name), "tx%u-virt-port-%u",
+ tx_chn_num, vport->port_id);
+ strscpy(tx_chn->tx_chan_name, tx_chn_name, sizeof(tx_chn->tx_chan_name));
+
+ hdesc_size = cppi5_hdesc_calc_size(true, PS_DATA_SIZE, SW_DATA_SIZE);
+
+ tx_cfg.swdata_size = SW_DATA_SIZE;
+ tx_cfg.tx_cfg = ring_cfg;
+ tx_cfg.txcq_cfg = ring_cfg;
+ tx_cfg.tx_cfg.size = max_desc_num;
+ tx_cfg.txcq_cfg.size = max_desc_num;
+
+ tx_chn->dev = dev;
+ tx_chn->num_descs = max_desc_num;
+ tx_chn->desc_pool = k3_cppi_desc_pool_create_name(dev,
+ tx_chn->num_descs,
+ hdesc_size,
+ tx_chn_name);
+ if (IS_ERR(tx_chn->desc_pool)) {
+ ret = PTR_ERR(tx_chn->desc_pool);
+ dev_err(dev, "failed to create tx pool %d\n", ret);
+ goto err;
+ }
+
+ tx_chn->tx_chan =
+ k3_udma_glue_request_tx_chn_for_thread_id(dev, &tx_cfg,
+ proxy_priv->dma_node,
+ tx_chn->thread_id);
+ if (IS_ERR(tx_chn->tx_chan)) {
+ ret = PTR_ERR(tx_chn->tx_chan);
+ dev_err(dev, "Failed to request tx dma channel %d\n", ret);
+ goto err;
+ }
+
+ tx_chn->irq = k3_udma_glue_tx_get_irq(tx_chn->tx_chan);
+ if (tx_chn->irq <= 0) {
+ dev_err(dev, "Failed to get tx dma irq %d\n", tx_chn->irq);
+ ret = -ENXIO;
+ }
+ }
+ }
+
+err:
+ ret1 = devm_add_action(dev, free_tx_chns, proxy_priv);
+ if (ret1) {
+ dev_err(dev, "failed to add free_tx_chns action %d", ret1);
+ return ret1;
+ }
+
+ return ret;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add the "init_rx_chans()" function to initialize the RX DMA Channels.
With the knowledge of the PSI-L Thread ID for the RX Channel along with
the details of the RX Flow Base and RX Flow Offset, the RX DMA Flow on
the RX Channel can be setup using the DMA APIs.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 128 ++++++++++++++++++++
1 file changed, 128 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index efb44ff04b6a..16e8e585adce 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -20,6 +20,8 @@
#define SW_DATA_SIZE 16
#define MAX_TX_DESC 500
+#define MAX_RX_DESC 500
+#define MAX_RX_FLOWS 1
#define CHAN_NAME_LEN 128
@@ -46,10 +48,16 @@ struct cpsw_proxy_req_params {
struct rx_dma_chan {
struct virtual_port *vport;
+ struct device *dev;
+ struct k3_cppi_desc_pool *desc_pool;
+ struct k3_udma_glue_rx_channel *rx_chan;
u32 rel_chan_idx;
u32 flow_base;
u32 flow_offset;
u32 thread_id;
+ u32 num_descs;
+ unsigned int irq;
+ char rx_chan_name[CHAN_NAME_LEN];
bool in_use;
};
@@ -96,6 +104,7 @@ struct cpsw_proxy_priv {
u32 num_mac_ports;
u32 num_virt_ports;
u32 num_active_tx_chans;
+ u32 num_active_rx_chans;
};
static int cpsw_proxy_client_cb(struct rpmsg_device *rpdev, void *data,
@@ -720,6 +729,125 @@ static int init_tx_chans(struct cpsw_proxy_priv *proxy_priv)
return ret;
}
+static void free_rx_chns(void *data)
+{
+ struct cpsw_proxy_priv *proxy_priv = data;
+ struct rx_dma_chan *rx_chn;
+ struct virtual_port *vport;
+ u32 i, j;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+
+ for (j = 0; j < vport->num_rx_chan; j++) {
+ rx_chn = &vport->rx_chans[j];
+
+ if (!IS_ERR_OR_NULL(rx_chn->desc_pool))
+ k3_cppi_desc_pool_destroy(rx_chn->desc_pool);
+
+ if (!IS_ERR_OR_NULL(rx_chn->rx_chan))
+ k3_udma_glue_release_rx_chn(rx_chn->rx_chan);
+ }
+ }
+}
+
+static int init_rx_chans(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct k3_udma_glue_rx_channel_cfg rx_cfg = {0};
+ struct device *dev = proxy_priv->dev;
+ u32 hdesc_size, rx_chn_num, i, j;
+ u32 max_desc_num = MAX_RX_DESC;
+ char rx_chn_name[CHAN_NAME_LEN];
+ struct rx_dma_chan *rx_chn;
+ struct virtual_port *vport;
+ struct k3_ring_cfg rxring_cfg = {
+ .elm_size = K3_RINGACC_RING_ELSIZE_8,
+ .mode = K3_RINGACC_RING_MODE_MESSAGE,
+ .flags = 0,
+ };
+ struct k3_ring_cfg fdqring_cfg = {
+ .elm_size = K3_RINGACC_RING_ELSIZE_8,
+ .mode = K3_RINGACC_RING_MODE_MESSAGE,
+ .flags = 0,
+ };
+ struct k3_udma_glue_rx_flow_cfg rx_flow_cfg = {
+ .rx_cfg = rxring_cfg,
+ .rxfdq_cfg = fdqring_cfg,
+ .ring_rxq_id = K3_RINGACC_RING_ID_ANY,
+ .ring_rxfdq0_id = K3_RINGACC_RING_ID_ANY,
+ .src_tag_lo_sel = K3_UDMA_GLUE_SRC_TAG_LO_USE_REMOTE_SRC_TAG,
+ };
+ int ret = 0, ret1;
+
+ hdesc_size = cppi5_hdesc_calc_size(true, PS_DATA_SIZE, SW_DATA_SIZE);
+
+ rx_cfg.swdata_size = SW_DATA_SIZE;
+ rx_cfg.flow_id_num = MAX_RX_FLOWS;
+ rx_cfg.remote = true;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+
+ for (j = 0; j < vport->num_rx_chan; j++) {
+ rx_chn = &vport->rx_chans[j];
+
+ rx_chn_num = proxy_priv->num_active_rx_chans++;
+ snprintf(rx_chn_name, sizeof(rx_chn_name), "rx%u-virt-port-%u", rx_chn_num,
+ vport->port_id);
+ strscpy(rx_chn->rx_chan_name, rx_chn_name, sizeof(rx_chn->rx_chan_name));
+
+ rx_cfg.flow_id_base = rx_chn->flow_base + rx_chn->flow_offset;
+
+ /* init all flows */
+ rx_chn->dev = dev;
+ rx_chn->num_descs = max_desc_num;
+ rx_chn->desc_pool = k3_cppi_desc_pool_create_name(dev,
+ rx_chn->num_descs,
+ hdesc_size,
+ rx_chn_name);
+ if (IS_ERR(rx_chn->desc_pool)) {
+ ret = PTR_ERR(rx_chn->desc_pool);
+ dev_err(dev, "Failed to create rx pool %d\n", ret);
+ goto err;
+ }
+
+ rx_chn->rx_chan =
+ k3_udma_glue_request_remote_rx_chn_for_thread_id(dev, &rx_cfg,
+ proxy_priv->dma_node,
+ rx_chn->thread_id);
+ if (IS_ERR(rx_chn->rx_chan)) {
+ ret = PTR_ERR(rx_chn->rx_chan);
+ dev_err(dev, "Failed to request rx dma channel %d\n", ret);
+ goto err;
+ }
+
+ rx_flow_cfg.rx_cfg.size = max_desc_num;
+ rx_flow_cfg.rxfdq_cfg.size = max_desc_num;
+ ret = k3_udma_glue_rx_flow_init(rx_chn->rx_chan,
+ 0, &rx_flow_cfg);
+ if (ret) {
+ dev_err(dev, "Failed to init rx flow %d\n", ret);
+ goto err;
+ }
+
+ rx_chn->irq = k3_udma_glue_rx_get_irq(rx_chn->rx_chan, 0);
+ if (rx_chn->irq <= 0) {
+ ret = -ENXIO;
+ dev_err(dev, "Failed to get rx dma irq %d\n", rx_chn->irq);
+ }
+ }
+ }
+
+err:
+ ret1 = devm_add_action(dev, free_rx_chns, proxy_priv);
+ if (ret1) {
+ dev_err(dev, "failed to add free_rx_chns action %d", ret1);
+ return ret1;
+ }
+
+ return ret;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add the "vport_tx_poll()" function to be registered as the NAPI TX
polling function via "netif_napi_add_tx()".
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 140 ++++++++++++++++++++
1 file changed, 140 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 16e8e585adce..cf99d8b6c1ec 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -66,6 +66,7 @@ struct tx_dma_chan {
struct device *dev;
struct k3_cppi_desc_pool *desc_pool;
struct k3_udma_glue_tx_channel *tx_chan;
+ struct napi_struct napi_tx;
u32 rel_chan_idx;
u32 thread_id;
u32 num_descs;
@@ -74,11 +75,26 @@ struct tx_dma_chan {
bool in_use;
};
+struct vport_netdev_stats {
+ u64 tx_packets;
+ u64 tx_bytes;
+ u64 rx_packets;
+ u64 rx_bytes;
+ struct u64_stats_sync syncp;
+};
+
+struct vport_netdev_priv {
+ struct vport_netdev_stats __percpu *stats;
+ struct virtual_port *vport;
+};
+
struct virtual_port {
struct cpsw_proxy_priv *proxy_priv;
struct rx_dma_chan *rx_chans;
struct tx_dma_chan *tx_chans;
+ struct completion tdown_complete;
enum virtual_port_type port_type;
+ atomic_t tdown_cnt;
u32 port_id;
u32 port_token;
u32 port_features;
@@ -672,6 +688,7 @@ static int init_tx_chans(struct cpsw_proxy_priv *proxy_priv)
for (i = 0; i < proxy_priv->num_virt_ports; i++) {
vport = &proxy_priv->virt_ports[i];
+ init_completion(&vport->tdown_complete);
for (j = 0; j < vport->num_tx_chan; j++) {
tx_chn = &vport->tx_chans[j];
@@ -848,6 +865,129 @@ static int init_rx_chans(struct cpsw_proxy_priv *proxy_priv)
return ret;
}
+static void vport_xmit_free(struct tx_dma_chan *tx_chn, struct device *dev,
+ struct cppi5_host_desc_t *desc)
+{
+ struct cppi5_host_desc_t *first_desc, *next_desc;
+ dma_addr_t buf_dma, next_desc_dma;
+ u32 buf_dma_len;
+
+ first_desc = desc;
+ next_desc = first_desc;
+
+ cppi5_hdesc_get_obuf(first_desc, &buf_dma, &buf_dma_len);
+
+ dma_unmap_single(dev, buf_dma, buf_dma_len,
+ DMA_TO_DEVICE);
+
+ next_desc_dma = cppi5_hdesc_get_next_hbdesc(first_desc);
+ while (next_desc_dma) {
+ next_desc = k3_cppi_desc_pool_dma2virt(tx_chn->desc_pool,
+ next_desc_dma);
+ cppi5_hdesc_get_obuf(next_desc, &buf_dma, &buf_dma_len);
+
+ dma_unmap_page(dev, buf_dma, buf_dma_len,
+ DMA_TO_DEVICE);
+
+ next_desc_dma = cppi5_hdesc_get_next_hbdesc(next_desc);
+
+ k3_cppi_desc_pool_free(tx_chn->desc_pool, next_desc);
+ }
+
+ k3_cppi_desc_pool_free(tx_chn->desc_pool, first_desc);
+}
+
+static int tx_compl_packets(struct virtual_port *vport, unsigned int tx_chan_idx,
+ unsigned int budget, bool *tdown)
+{
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct device *dev = proxy_priv->dev;
+ struct cppi5_host_desc_t *desc_tx;
+ struct netdev_queue *netif_txq;
+ unsigned int total_bytes = 0;
+ struct tx_dma_chan *tx_chn;
+ struct net_device *ndev;
+ struct sk_buff *skb;
+ dma_addr_t desc_dma;
+ int res, num_tx = 0;
+ void **swdata;
+
+ tx_chn = &vport->tx_chans[tx_chan_idx];
+
+ while (budget--) {
+ struct vport_netdev_priv *ndev_priv;
+ struct vport_netdev_stats *stats;
+
+ res = k3_udma_glue_pop_tx_chn(tx_chn->tx_chan, &desc_dma);
+ if (res == -ENODATA)
+ break;
+
+ if (desc_dma & 0x1) {
+ if (atomic_dec_and_test(&vport->tdown_cnt))
+ complete(&vport->tdown_complete);
+ *tdown = true;
+ break;
+ }
+
+ desc_tx = k3_cppi_desc_pool_dma2virt(tx_chn->desc_pool,
+ desc_dma);
+ swdata = cppi5_hdesc_get_swdata(desc_tx);
+ skb = *(swdata);
+ vport_xmit_free(tx_chn, dev, desc_tx);
+
+ ndev = skb->dev;
+
+ ndev_priv = netdev_priv(ndev);
+ stats = this_cpu_ptr(ndev_priv->stats);
+ u64_stats_update_begin(&stats->syncp);
+ stats->tx_packets++;
+ stats->tx_bytes += skb->len;
+ u64_stats_update_end(&stats->syncp);
+
+ total_bytes += skb->len;
+ napi_consume_skb(skb, budget);
+ num_tx++;
+ }
+
+ if (!num_tx)
+ return 0;
+
+ netif_txq = netdev_get_tx_queue(ndev, tx_chan_idx);
+ netdev_tx_completed_queue(netif_txq, num_tx, total_bytes);
+
+ if (netif_tx_queue_stopped(netif_txq)) {
+ __netif_tx_lock(netif_txq, smp_processor_id());
+ if (netif_running(ndev) &&
+ (k3_cppi_desc_pool_avail(tx_chn->desc_pool) >=
+ MAX_SKB_FRAGS))
+ netif_tx_wake_queue(netif_txq);
+
+ __netif_tx_unlock(netif_txq);
+ }
+
+ return num_tx;
+}
+
+static int vport_tx_poll(struct napi_struct *napi_tx, int budget)
+{
+ struct tx_dma_chan *tx_chn = container_of(napi_tx, struct tx_dma_chan,
+ napi_tx);
+ struct virtual_port *vport = tx_chn->vport;
+ bool tdown = false;
+ int num_tx;
+
+ /* process every unprocessed channel */
+ num_tx = tx_compl_packets(vport, tx_chn->rel_chan_idx, budget, &tdown);
+
+ if (num_tx >= budget)
+ return budget;
+
+ if (napi_complete_done(napi_tx, num_tx))
+ enable_irq(tx_chn->irq);
+
+ return 0;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add the "vport_rx_poll()" function to be registered as the NAPI RX
polling function via "netif_napi_add()".
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 189 ++++++++++++++++++++
1 file changed, 189 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index cf99d8b6c1ec..6926f65a4613 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -6,7 +6,9 @@
*/
#include <linux/etherdevice.h>
+#include <linux/if_vlan.h>
#include <linux/kernel.h>
+#include <linux/kmemleak.h>
#include <linux/module.h>
#include <linux/rpmsg.h>
#include <linux/dma/k3-udma-glue.h>
@@ -23,6 +25,8 @@
#define MAX_RX_DESC 500
#define MAX_RX_FLOWS 1
+#define MAX_PACKET_SIZE (VLAN_ETH_FRAME_LEN + ETH_FCS_LEN)
+
#define CHAN_NAME_LEN 128
enum virtual_port_type {
@@ -51,6 +55,7 @@ struct rx_dma_chan {
struct device *dev;
struct k3_cppi_desc_pool *desc_pool;
struct k3_udma_glue_rx_channel *rx_chan;
+ struct napi_struct napi_rx;
u32 rel_chan_idx;
u32 flow_base;
u32 flow_offset;
@@ -90,6 +95,7 @@ struct vport_netdev_priv {
struct virtual_port {
struct cpsw_proxy_priv *proxy_priv;
+ struct net_device *ndev;
struct rx_dma_chan *rx_chans;
struct tx_dma_chan *tx_chans;
struct completion tdown_complete;
@@ -988,6 +994,189 @@ static int vport_tx_poll(struct napi_struct *napi_tx, int budget)
return 0;
}
+/* RX psdata[2] word format - checksum information */
+#define RX_PSD_CSUM_ERR BIT(16)
+#define RX_PSD_IS_FRAGMENT BIT(17)
+#define RX_PSD_IPV6_VALID BIT(19)
+#define RX_PSD_IPV4_VALID BIT(20)
+
+static void vport_rx_csum(struct sk_buff *skb, u32 csum_info)
+{
+ /* HW can verify IPv4/IPv6 TCP/UDP packets checksum
+ * csum information provides in psdata[2] word:
+ * RX_PSD_CSUM_ERR bit - indicates csum error
+ * RX_PSD_IPV6_VALID and CPSW_RX_PSD_IPV4_VALID
+ * bits - indicates IPv4/IPv6 packet
+ * RX_PSD_IS_FRAGMENT bit - indicates fragmented packet
+ * RX_PSD_CSUM_ADD has value 0xFFFF for non fragmented packets
+ * or csum value for fragmented packets if !RX_PSD_CSUM_ERR
+ */
+ skb_checksum_none_assert(skb);
+
+ if (unlikely(!(skb->dev->features & NETIF_F_RXCSUM)))
+ return;
+
+ if ((csum_info & (RX_PSD_IPV6_VALID |
+ RX_PSD_IPV4_VALID)) &&
+ !(csum_info & RX_PSD_CSUM_ERR)) {
+ /* csum for fragmented packets is unsupported */
+ if (!(csum_info & RX_PSD_IS_FRAGMENT))
+ skb->ip_summed = CHECKSUM_UNNECESSARY;
+ }
+}
+
+static int vport_rx_push(struct virtual_port *vport, struct sk_buff *skb,
+ u32 rx_chan_idx)
+{
+ struct rx_dma_chan *rx_chn = &vport->rx_chans[rx_chan_idx];
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct device *dev = proxy_priv->dev;
+ struct cppi5_host_desc_t *desc_rx;
+ u32 pkt_len = skb_tailroom(skb);
+ dma_addr_t desc_dma;
+ dma_addr_t buf_dma;
+ void *swdata;
+
+ desc_rx = k3_cppi_desc_pool_alloc(rx_chn->desc_pool);
+ if (!desc_rx) {
+ dev_err(dev, "Failed to allocate RXFDQ descriptor\n");
+ return -ENOMEM;
+ }
+ desc_dma = k3_cppi_desc_pool_virt2dma(rx_chn->desc_pool, desc_rx);
+
+ buf_dma = dma_map_single(dev, skb->data, pkt_len, DMA_FROM_DEVICE);
+ if (unlikely(dma_mapping_error(dev, buf_dma))) {
+ k3_cppi_desc_pool_free(rx_chn->desc_pool, desc_rx);
+ dev_err(dev, "Failed to map rx skb buffer\n");
+ return -EINVAL;
+ }
+
+ cppi5_hdesc_init(desc_rx, CPPI5_INFO0_HDESC_EPIB_PRESENT,
+ PS_DATA_SIZE);
+ cppi5_hdesc_attach_buf(desc_rx, 0, 0, buf_dma, skb_tailroom(skb));
+ swdata = cppi5_hdesc_get_swdata(desc_rx);
+ *((void **)swdata) = skb;
+
+ return k3_udma_glue_push_rx_chn(rx_chn->rx_chan, 0, desc_rx, desc_dma);
+}
+
+static int vport_rx_packets(struct virtual_port *vport, u32 rx_chan_idx)
+{
+ struct rx_dma_chan *rx_chn = &vport->rx_chans[rx_chan_idx];
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ u32 buf_dma_len, pkt_len, port_id = 0, csum_info;
+ struct device *dev = proxy_priv->dev;
+ struct vport_netdev_priv *ndev_priv;
+ struct cppi5_host_desc_t *desc_rx;
+ struct vport_netdev_stats *stats;
+ struct sk_buff *skb, *new_skb;
+ dma_addr_t desc_dma, buf_dma;
+ struct net_device *ndev;
+ u32 flow_idx = 0;
+ void **swdata;
+ int ret = 0;
+ u32 *psdata;
+
+ ret = k3_udma_glue_pop_rx_chn(rx_chn->rx_chan, flow_idx, &desc_dma);
+ if (ret) {
+ if (ret != -ENODATA)
+ dev_err(dev, "RX: pop chn fail %d\n", ret);
+ return ret;
+ }
+
+ if (desc_dma & 0x1) {
+ dev_dbg(dev, "%s RX tdown flow: %u\n", __func__, flow_idx);
+ return 0;
+ }
+
+ desc_rx = k3_cppi_desc_pool_dma2virt(rx_chn->desc_pool, desc_dma);
+ dev_dbg(dev, "%s flow_idx: %u desc %pad\n",
+ __func__, flow_idx, &desc_dma);
+
+ swdata = cppi5_hdesc_get_swdata(desc_rx);
+ skb = *swdata;
+ cppi5_hdesc_get_obuf(desc_rx, &buf_dma, &buf_dma_len);
+ pkt_len = cppi5_hdesc_get_pktlen(desc_rx);
+ cppi5_desc_get_tags_ids(&desc_rx->hdr, &port_id, NULL);
+ /* read port for dbg */
+ dev_dbg(dev, "%s rx port_id:%d\n", __func__, port_id);
+ ndev = vport->ndev;
+ skb->dev = ndev;
+
+ psdata = cppi5_hdesc_get_psdata(desc_rx);
+ csum_info = psdata[2];
+ dev_dbg(dev, "%s rx csum_info:%#x\n", __func__, csum_info);
+
+ dma_unmap_single(dev, buf_dma, buf_dma_len, DMA_FROM_DEVICE);
+
+ k3_cppi_desc_pool_free(rx_chn->desc_pool, desc_rx);
+
+ if (unlikely(!netif_running(skb->dev))) {
+ dev_kfree_skb_any(skb);
+ return -ENODEV;
+ }
+
+ new_skb = netdev_alloc_skb_ip_align(ndev, MAX_PACKET_SIZE);
+ if (new_skb) {
+ skb_put(skb, pkt_len);
+ skb->protocol = eth_type_trans(skb, ndev);
+ vport_rx_csum(skb, csum_info);
+ napi_gro_receive(&rx_chn->napi_rx, skb);
+
+ ndev_priv = netdev_priv(ndev);
+ stats = this_cpu_ptr(ndev_priv->stats);
+
+ u64_stats_update_begin(&stats->syncp);
+ stats->rx_packets++;
+ stats->rx_bytes += pkt_len;
+ u64_stats_update_end(&stats->syncp);
+ kmemleak_not_leak(new_skb);
+ } else {
+ ndev->stats.rx_dropped++;
+ new_skb = skb;
+ }
+
+ if (netif_dormant(ndev)) {
+ dev_kfree_skb_any(new_skb);
+ ndev->stats.rx_dropped++;
+ return -ENODEV;
+ }
+
+ ret = vport_rx_push(vport, new_skb, rx_chn->rel_chan_idx);
+ if (WARN_ON(ret < 0)) {
+ dev_kfree_skb_any(new_skb);
+ ndev->stats.rx_errors++;
+ ndev->stats.rx_dropped++;
+ }
+
+ return ret;
+}
+
+static int vport_rx_poll(struct napi_struct *napi_rx, int budget)
+{
+ struct rx_dma_chan *rx_chn = container_of(napi_rx, struct rx_dma_chan,
+ napi_rx);
+ struct virtual_port *vport = rx_chn->vport;
+ int num_rx = 0;
+ int cur_budget;
+ int ret;
+
+ /* process every flow */
+ cur_budget = budget;
+
+ while (cur_budget--) {
+ ret = vport_rx_packets(vport, rx_chn->rel_chan_idx);
+ if (ret)
+ break;
+ num_rx++;
+ }
+
+ if (num_rx < budget && napi_complete_done(napi_rx, num_rx))
+ enable_irq(rx_chn->irq);
+
+ return num_rx;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add the function "init_netdevs()" to initialize and register net-device
for each Virtual Port.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 102 ++++++++++++++++++++
1 file changed, 102 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 6926f65a4613..30d53a8e174e 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -6,6 +6,7 @@
*/
#include <linux/etherdevice.h>
+#include <linux/ethtool.h>
#include <linux/if_vlan.h>
#include <linux/kernel.h>
#include <linux/kmemleak.h>
@@ -25,6 +26,7 @@
#define MAX_RX_DESC 500
#define MAX_RX_FLOWS 1
+#define MIN_PACKET_SIZE ETH_ZLEN
#define MAX_PACKET_SIZE (VLAN_ETH_FRAME_LEN + ETH_FCS_LEN)
#define CHAN_NAME_LEN 128
@@ -1177,6 +1179,106 @@ static int vport_rx_poll(struct napi_struct *napi_rx, int budget)
return num_rx;
}
+const struct ethtool_ops cpsw_proxy_client_ethtool_ops = {
+};
+
+static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
+};
+
+static int init_netdev(struct cpsw_proxy_priv *proxy_priv, struct virtual_port *vport)
+{
+ struct device *dev = proxy_priv->dev;
+ struct vport_netdev_priv *ndev_priv;
+ struct rx_dma_chan *rx_chn;
+ struct tx_dma_chan *tx_chn;
+ int ret = 0;
+ u32 i;
+
+ vport->ndev = devm_alloc_etherdev_mqs(dev, sizeof(struct vport_netdev_priv),
+ vport->num_tx_chan, vport->num_rx_chan);
+
+ if (!vport->ndev) {
+ dev_err(dev, "error allocating netdev for port %u\n", vport->port_id);
+ return -ENOMEM;
+ }
+
+ ndev_priv = netdev_priv(vport->ndev);
+ ndev_priv->vport = vport;
+ SET_NETDEV_DEV(vport->ndev, dev);
+
+ if (is_valid_ether_addr(vport->mac_addr))
+ eth_hw_addr_set(vport->ndev, vport->mac_addr);
+
+ vport->ndev->min_mtu = MIN_PACKET_SIZE;
+ vport->ndev->max_mtu = MAX_PACKET_SIZE;
+ vport->ndev->hw_features = NETIF_F_SG | NETIF_F_RXCSUM;
+ vport->ndev->features = vport->ndev->hw_features;
+ vport->ndev->vlan_features |= NETIF_F_SG;
+ vport->ndev->netdev_ops = &cpsw_proxy_client_netdev_ops;
+ vport->ndev->ethtool_ops = &cpsw_proxy_client_ethtool_ops;
+
+ ndev_priv->stats = netdev_alloc_pcpu_stats(struct vport_netdev_stats);
+ if (!ndev_priv->stats)
+ return -ENOMEM;
+
+ ret = devm_add_action_or_reset(dev, (void(*)(void *))free_percpu, ndev_priv->stats);
+ if (ret) {
+ dev_err(dev, "failed to add free_percpu action, err: %d\n", ret);
+ return ret;
+ }
+
+ for (i = 0; i < vport->num_tx_chan; i++) {
+ tx_chn = &vport->tx_chans[i];
+ netif_napi_add_tx(vport->ndev, &tx_chn->napi_tx, vport_tx_poll);
+ }
+
+ for (i = 0; i < vport->num_rx_chan; i++) {
+ rx_chn = &vport->rx_chans[i];
+ netif_napi_add(vport->ndev, &rx_chn->napi_rx, vport_rx_poll);
+ }
+
+ ret = register_netdev(vport->ndev);
+ if (ret)
+ dev_err(dev, "error registering net device, err: %d\n", ret);
+
+ return ret;
+}
+
+static void unreg_netdevs(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct virtual_port *vport;
+ u32 i;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ if (vport->ndev)
+ unregister_netdev(vport->ndev);
+ }
+}
+
+static int init_netdevs(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct virtual_port *vport;
+ int ret;
+ u32 i;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ ret = init_netdev(proxy_priv, vport);
+ if (ret) {
+ dev_err(proxy_priv->dev, "failed to initialize ndev for port %u\n",
+ vport->port_id);
+ goto err;
+ }
+ }
+
+ return 0;
+
+err:
+ unreg_netdevs(proxy_priv);
+ return ret;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add the function "register_dma_irq_handlers()" to register the TX and RX
DMA Interrupt handlers for all the TX and RX DMA Channels for every Virtual
Port.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 60 +++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 30d53a8e174e..b0f0e5db3a74 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -1279,6 +1279,66 @@ static int init_netdevs(struct cpsw_proxy_priv *proxy_priv)
return ret;
}
+static irqreturn_t tx_irq_handler(int irq, void *dev_id)
+{
+ struct tx_dma_chan *tx_chn = dev_id;
+
+ disable_irq_nosync(irq);
+ napi_schedule(&tx_chn->napi_tx);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t rx_irq_handler(int irq, void *dev_id)
+{
+ struct rx_dma_chan *rx_chn = dev_id;
+
+ disable_irq_nosync(irq);
+ napi_schedule(&rx_chn->napi_rx);
+
+ return IRQ_HANDLED;
+}
+
+static int register_dma_irq_handlers(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct device *dev = proxy_priv->dev;
+ struct rx_dma_chan *rx_chn;
+ struct tx_dma_chan *tx_chn;
+ struct virtual_port *vport;
+ u32 i, j;
+ int ret;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+
+ for (j = 0; j < vport->num_tx_chan; j++) {
+ tx_chn = &vport->tx_chans[j];
+
+ ret = devm_request_irq(dev, tx_chn->irq, tx_irq_handler,
+ IRQF_TRIGGER_HIGH, tx_chn->tx_chan_name, tx_chn);
+ if (ret) {
+ dev_err(dev, "failed to request tx irq: %u, err: %d\n",
+ tx_chn->irq, ret);
+ return ret;
+ }
+ }
+
+ for (j = 0; j < vport->num_rx_chan; j++) {
+ rx_chn = &vport->rx_chans[j];
+
+ ret = devm_request_irq(dev, rx_chn->irq, rx_irq_handler,
+ IRQF_TRIGGER_HIGH, rx_chn->rx_chan_name, rx_chn);
+ if (ret) {
+ dev_err(dev, "failed to request rx irq: %u, err: %d\n",
+ rx_chn->irq, ret);
+ return ret;
+ }
+ }
+ }
+
+ return 0;
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add functions "register_mac()" and "deregister_mac()" to register and
deregister MAC Address of the network interface corresponding to the
Virtual Port with EthFw. Registering the MAC Address with EthFw is
necessary to receive unicast traffic directed to the MAC Address.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 47 +++++++++++++++++++++
1 file changed, 47 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index b0f0e5db3a74..7af4a89a1847 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -1182,6 +1182,53 @@ static int vport_rx_poll(struct napi_struct *napi_rx, int budget)
const struct ethtool_ops cpsw_proxy_client_ethtool_ops = {
};
+static int register_mac(struct virtual_port *vport)
+{
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct rx_dma_chan *rx_chn = &vport->rx_chans[0];
+ struct cpsw_proxy_req_params *req_p;
+ struct message resp_msg;
+ int ret;
+
+ /* Register MAC Address only for RX DMA Channel 0 */
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_MAC_REGISTER;
+ req_p->token = vport->port_token;
+ req_p->rx_flow_base = rx_chn->flow_base;
+ req_p->rx_flow_offset = rx_chn->flow_offset;
+ ether_addr_copy(req_p->mac_addr, vport->mac_addr);
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret)
+ dev_err(proxy_priv->dev, "failed to register MAC Address\n");
+
+ return ret;
+}
+
+static int deregister_mac(struct virtual_port *vport)
+{
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct rx_dma_chan *rx_chn = &vport->rx_chans[0];
+ struct cpsw_proxy_req_params *req_p;
+ struct message resp_msg;
+ int ret;
+
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_MAC_DEREGISTER;
+ req_p->token = vport->port_token;
+ req_p->rx_flow_base = rx_chn->flow_base;
+ req_p->rx_flow_offset = rx_chn->flow_offset;
+ ether_addr_copy(req_p->mac_addr, vport->mac_addr);
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret)
+ dev_err(proxy_priv->dev, "failed to deregister MAC Address\n");
+
+ return ret;
+}
+
static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
};
--
2.40.1
Add the function "vport_ndo_open()" and register it as the driver's
ndo_open callback.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 161 ++++++++++++++++++++
1 file changed, 161 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 7af4a89a1847..e643ffb9455a 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -131,6 +131,11 @@ struct cpsw_proxy_priv {
u32 num_active_rx_chans;
};
+#define vport_netdev_to_priv(ndev) \
+ ((struct vport_netdev_priv *)netdev_priv(ndev))
+#define vport_ndev_to_vport(ndev) \
+ (vport_netdev_to_priv(ndev)->vport)
+
static int cpsw_proxy_client_cb(struct rpmsg_device *rpdev, void *data,
int len, void *priv, u32 src)
{
@@ -1229,7 +1234,163 @@ static int deregister_mac(struct virtual_port *vport)
return ret;
}
+static void vport_tx_cleanup(void *data, dma_addr_t desc_dma)
+{
+ struct tx_dma_chan *tx_chn = data;
+ struct cppi5_host_desc_t *desc_tx;
+ struct sk_buff *skb;
+ void **swdata;
+
+ desc_tx = k3_cppi_desc_pool_dma2virt(tx_chn->desc_pool, desc_dma);
+ swdata = cppi5_hdesc_get_swdata(desc_tx);
+ skb = *(swdata);
+ vport_xmit_free(tx_chn, tx_chn->dev, desc_tx);
+
+ dev_kfree_skb_any(skb);
+}
+
+static void vport_rx_cleanup(void *data, dma_addr_t desc_dma)
+{
+ struct rx_dma_chan *rx_chn = data;
+ struct cppi5_host_desc_t *desc_rx;
+ struct sk_buff *skb;
+ dma_addr_t buf_dma;
+ u32 buf_dma_len;
+ void **swdata;
+
+ desc_rx = k3_cppi_desc_pool_dma2virt(rx_chn->desc_pool, desc_dma);
+ swdata = cppi5_hdesc_get_swdata(desc_rx);
+ skb = *swdata;
+ cppi5_hdesc_get_obuf(desc_rx, &buf_dma, &buf_dma_len);
+
+ dma_unmap_single(rx_chn->dev, buf_dma, buf_dma_len, DMA_FROM_DEVICE);
+ k3_cppi_desc_pool_free(rx_chn->desc_pool, desc_rx);
+
+ dev_kfree_skb_any(skb);
+}
+
+static void vport_stop(struct virtual_port *vport)
+{
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct rx_dma_chan *rx_chn;
+ struct tx_dma_chan *tx_chn;
+ int i;
+
+ /* shutdown tx channels */
+ atomic_set(&vport->tdown_cnt, vport->num_tx_chan);
+ /* ensure new tdown_cnt value is visible */
+ smp_mb__after_atomic();
+ reinit_completion(&vport->tdown_complete);
+
+ for (i = 0; i < vport->num_tx_chan; i++)
+ k3_udma_glue_tdown_tx_chn(vport->tx_chans[i].tx_chan, false);
+
+ i = wait_for_completion_timeout(&vport->tdown_complete, msecs_to_jiffies(1000));
+ if (!i)
+ dev_err(proxy_priv->dev, "tx teardown timeout\n");
+
+ for (i = 0; i < vport->num_tx_chan; i++) {
+ tx_chn = &vport->tx_chans[i];
+ k3_udma_glue_reset_tx_chn(tx_chn->tx_chan, tx_chn, vport_tx_cleanup);
+ k3_udma_glue_disable_tx_chn(tx_chn->tx_chan);
+ napi_disable(&tx_chn->napi_tx);
+ }
+
+ for (i = 0; i < vport->num_rx_chan; i++) {
+ rx_chn = &vport->rx_chans[i];
+ k3_udma_glue_rx_flow_disable(rx_chn->rx_chan, 0);
+ /* Need some delay to process RX ring before reset */
+ msleep(100);
+ k3_udma_glue_reset_rx_chn(rx_chn->rx_chan, 0, rx_chn, vport_rx_cleanup,
+ false);
+ napi_disable(&rx_chn->napi_rx);
+ }
+}
+
+static int vport_open(struct virtual_port *vport, netdev_features_t features)
+{
+ struct rx_dma_chan *rx_chn;
+ struct tx_dma_chan *tx_chn;
+ struct sk_buff *skb;
+ u32 i, j;
+ int ret;
+
+ for (i = 0; i < vport->num_rx_chan; i++) {
+ rx_chn = &vport->rx_chans[i];
+
+ for (j = 0; j < rx_chn->num_descs; j++) {
+ skb = __netdev_alloc_skb_ip_align(NULL, MAX_PACKET_SIZE, GFP_KERNEL);
+ if (!skb)
+ return -ENOMEM;
+
+ ret = vport_rx_push(vport, skb, i);
+ if (ret < 0) {
+ netdev_err(vport->ndev,
+ "cannot submit skb to rx channel\n");
+ kfree_skb(skb);
+ return ret;
+ }
+ kmemleak_not_leak(skb);
+ }
+
+ ret = k3_udma_glue_rx_flow_enable(rx_chn->rx_chan, 0);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < vport->num_tx_chan; i++) {
+ tx_chn = &vport->tx_chans[i];
+ ret = k3_udma_glue_enable_tx_chn(tx_chn->tx_chan);
+ if (ret)
+ return ret;
+ napi_enable(&tx_chn->napi_tx);
+ }
+
+ for (i = 0; i < vport->num_rx_chan; i++) {
+ rx_chn = &vport->rx_chans[i];
+ napi_enable(&rx_chn->napi_rx);
+ }
+
+ return 0;
+}
+
+static int vport_ndo_open(struct net_device *ndev)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ int ret;
+ u32 i;
+
+ ret = netif_set_real_num_tx_queues(ndev, vport->num_tx_chan);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < vport->num_tx_chan; i++)
+ netdev_tx_reset_queue(netdev_get_tx_queue(ndev, i));
+
+ ret = vport_open(vport, ndev->features);
+ if (ret)
+ return ret;
+
+ ret = register_mac(vport);
+ if (ret) {
+ netdev_err(ndev, "failed to register MAC for port: %u\n",
+ vport->port_id);
+ vport_stop(vport);
+ return -EIO;
+ }
+
+ netif_tx_wake_all_queues(ndev);
+ netif_carrier_on(ndev);
+
+ dev_info(proxy_priv->dev, "started port %u on interface %s\n",
+ vport->port_id, ndev->name);
+
+ return 0;
+}
+
static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
+ .ndo_open = vport_ndo_open,
};
static int init_netdev(struct cpsw_proxy_priv *proxy_priv, struct virtual_port *vport)
--
2.40.1
Add the function "vport_ndo_xmit()" and register it as the driver's
ndo_start_xmit callback.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 152 ++++++++++++++++++++
1 file changed, 152 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 646eab90832c..7cbe1d4b5112 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -1411,9 +1411,161 @@ static int vport_ndo_open(struct net_device *ndev)
return 0;
}
+static netdev_tx_t vport_ndo_xmit(struct sk_buff *skb, struct net_device *ndev)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+ struct cppi5_host_desc_t *first_desc, *next_desc, *cur_desc;
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct device *dev = proxy_priv->dev;
+ struct netdev_queue *netif_txq;
+ dma_addr_t desc_dma, buf_dma;
+ struct tx_dma_chan *tx_chn;
+ void **swdata;
+ int ret, i, q;
+ u32 pkt_len;
+ u32 *psdata;
+
+ /* padding enabled in hw */
+ pkt_len = skb_headlen(skb);
+
+ /* Get Queue / TX DMA Channel for the SKB */
+ q = skb_get_queue_mapping(skb);
+ tx_chn = &vport->tx_chans[q];
+ netif_txq = netdev_get_tx_queue(ndev, q);
+
+ /* Map the linear buffer */
+ buf_dma = dma_map_single(dev, skb->data, pkt_len,
+ DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dev, buf_dma))) {
+ dev_err(dev, "Failed to map tx skb buffer\n");
+ ndev->stats.tx_errors++;
+ goto drop_free_skb;
+ }
+
+ first_desc = k3_cppi_desc_pool_alloc(tx_chn->desc_pool);
+ if (!first_desc) {
+ dev_dbg(dev, "Failed to allocate descriptor\n");
+ dma_unmap_single(dev, buf_dma, pkt_len, DMA_TO_DEVICE);
+ goto busy_stop_q;
+ }
+
+ cppi5_hdesc_init(first_desc, CPPI5_INFO0_HDESC_EPIB_PRESENT,
+ PS_DATA_SIZE);
+ cppi5_desc_set_pktids(&first_desc->hdr, 0, 0x3FFF);
+ cppi5_hdesc_set_pkttype(first_desc, 0x7);
+ /* target port has to be 0 */
+ cppi5_desc_set_tags_ids(&first_desc->hdr, 0, vport->port_type);
+
+ cppi5_hdesc_attach_buf(first_desc, buf_dma, pkt_len, buf_dma, pkt_len);
+ swdata = cppi5_hdesc_get_swdata(first_desc);
+ *(swdata) = skb;
+ psdata = cppi5_hdesc_get_psdata(first_desc);
+
+ /* HW csum offload if enabled */
+ psdata[2] = 0;
+ if (likely(skb->ip_summed == CHECKSUM_PARTIAL)) {
+ unsigned int cs_start, cs_offset;
+
+ cs_start = skb_transport_offset(skb);
+ cs_offset = cs_start + skb->csum_offset;
+ /* HW numerates bytes starting from 1 */
+ psdata[2] = ((cs_offset + 1) << 24) |
+ ((cs_start + 1) << 16) | (skb->len - cs_start);
+ dev_dbg(dev, "%s tx psdata:%#x\n", __func__, psdata[2]);
+ }
+
+ if (!skb_is_nonlinear(skb))
+ goto done_tx;
+
+ dev_dbg(dev, "fragmented SKB\n");
+
+ /* Handle the case where skb is fragmented in pages */
+ cur_desc = first_desc;
+ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
+ skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
+ u32 frag_size = skb_frag_size(frag);
+
+ next_desc = k3_cppi_desc_pool_alloc(tx_chn->desc_pool);
+ if (!next_desc) {
+ dev_err(dev, "Failed to allocate descriptor\n");
+ goto busy_free_descs;
+ }
+
+ buf_dma = skb_frag_dma_map(dev, frag, 0, frag_size,
+ DMA_TO_DEVICE);
+ if (unlikely(dma_mapping_error(dev, buf_dma))) {
+ dev_err(dev, "Failed to map tx skb page\n");
+ k3_cppi_desc_pool_free(tx_chn->desc_pool, next_desc);
+ ndev->stats.tx_errors++;
+ goto drop_free_descs;
+ }
+
+ cppi5_hdesc_reset_hbdesc(next_desc);
+ cppi5_hdesc_attach_buf(next_desc,
+ buf_dma, frag_size, buf_dma, frag_size);
+
+ desc_dma = k3_cppi_desc_pool_virt2dma(tx_chn->desc_pool,
+ next_desc);
+ cppi5_hdesc_link_hbdesc(cur_desc, desc_dma);
+
+ pkt_len += frag_size;
+ cur_desc = next_desc;
+ }
+ WARN_ON(pkt_len != skb->len);
+
+done_tx:
+ skb_tx_timestamp(skb);
+
+ /* report bql before sending packet */
+ dev_dbg(dev, "push 0 %d Bytes\n", pkt_len);
+
+ netdev_tx_sent_queue(netif_txq, pkt_len);
+
+ cppi5_hdesc_set_pktlen(first_desc, pkt_len);
+ desc_dma = k3_cppi_desc_pool_virt2dma(tx_chn->desc_pool, first_desc);
+ ret = k3_udma_glue_push_tx_chn(tx_chn->tx_chan, first_desc, desc_dma);
+ if (ret) {
+ dev_err(dev, "can't push desc %d\n", ret);
+ /* inform bql */
+ netdev_tx_completed_queue(netif_txq, 1, pkt_len);
+ ndev->stats.tx_errors++;
+ goto drop_free_descs;
+ }
+
+ if (k3_cppi_desc_pool_avail(tx_chn->desc_pool) < MAX_SKB_FRAGS) {
+ netif_tx_stop_queue(netif_txq);
+ /* Barrier, so that stop_queue visible to other cpus */
+ smp_mb__after_atomic();
+ dev_dbg(dev, "netif_tx_stop_queue %d\n", q);
+
+ /* re-check for smp */
+ if (k3_cppi_desc_pool_avail(tx_chn->desc_pool) >=
+ MAX_SKB_FRAGS) {
+ netif_tx_wake_queue(netif_txq);
+ dev_dbg(dev, "netif_tx_wake_queue %d\n", q);
+ }
+ }
+
+ return NETDEV_TX_OK;
+
+drop_free_descs:
+ vport_xmit_free(tx_chn, dev, first_desc);
+drop_free_skb:
+ ndev->stats.tx_dropped++;
+ dev_kfree_skb_any(skb);
+ return NETDEV_TX_OK;
+
+busy_free_descs:
+ vport_xmit_free(tx_chn, dev, first_desc);
+busy_stop_q:
+ netif_tx_stop_queue(netif_txq);
+ return NETDEV_TX_BUSY;
+}
+
static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
.ndo_open = vport_ndo_open,
.ndo_stop = vport_ndo_stop,
+ .ndo_start_xmit = vport_ndo_xmit,
};
static int init_netdev(struct cpsw_proxy_priv *proxy_priv, struct virtual_port *vport)
--
2.40.1
Add the function "vport_ndo_get_stats()" and register it as the driver's
ndo_get_stats64 callback.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 35 +++++++++++++++++++++
1 file changed, 35 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 7cbe1d4b5112..6886557aa2a1 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -1562,10 +1562,45 @@ static netdev_tx_t vport_ndo_xmit(struct sk_buff *skb, struct net_device *ndev)
return NETDEV_TX_BUSY;
}
+static void vport_ndo_get_stats(struct net_device *ndev,
+ struct rtnl_link_stats64 *stats)
+{
+ struct vport_netdev_priv *ndev_priv = netdev_priv(ndev);
+ unsigned int start;
+ int cpu;
+
+ for_each_possible_cpu(cpu) {
+ struct vport_netdev_stats *cpu_stats;
+ u64 rx_packets;
+ u64 rx_bytes;
+ u64 tx_packets;
+ u64 tx_bytes;
+
+ cpu_stats = per_cpu_ptr(ndev_priv->stats, cpu);
+ do {
+ start = u64_stats_fetch_begin(&cpu_stats->syncp);
+ rx_packets = cpu_stats->rx_packets;
+ rx_bytes = cpu_stats->rx_bytes;
+ tx_packets = cpu_stats->tx_packets;
+ tx_bytes = cpu_stats->tx_bytes;
+ } while (u64_stats_fetch_retry(&cpu_stats->syncp, start));
+
+ stats->rx_packets += rx_packets;
+ stats->rx_bytes += rx_bytes;
+ stats->tx_packets += tx_packets;
+ stats->tx_bytes += tx_bytes;
+ }
+
+ stats->rx_errors = ndev->stats.rx_errors;
+ stats->rx_dropped = ndev->stats.rx_dropped;
+ stats->tx_dropped = ndev->stats.tx_dropped;
+}
+
static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
.ndo_open = vport_ndo_open,
.ndo_stop = vport_ndo_stop,
.ndo_start_xmit = vport_ndo_xmit,
+ .ndo_get_stats64 = vport_ndo_get_stats,
};
static int init_netdev(struct cpsw_proxy_priv *proxy_priv, struct virtual_port *vport)
--
2.40.1
Add the function "vport_ndo_tx_timeout()" and register it as the driver's
ndo_tx_timeout callback.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 26 +++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 6886557aa2a1..92a014e83c6c 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -1596,11 +1596,37 @@ static void vport_ndo_get_stats(struct net_device *ndev,
stats->tx_dropped = ndev->stats.tx_dropped;
}
+static void vport_ndo_tx_timeout(struct net_device *ndev, unsigned int txqueue)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+ struct netdev_queue *netif_txq;
+ struct tx_dma_chan *tx_chn;
+ unsigned long trans_start;
+
+ /* process every txq */
+ netif_txq = netdev_get_tx_queue(ndev, txqueue);
+ tx_chn = &vport->tx_chans[txqueue];
+ trans_start = READ_ONCE(netif_txq->trans_start);
+
+ netdev_err(ndev, "txq:%d DRV_XOFF: %d tmo: %u dql_avail:%d free_desc:%zu\n",
+ txqueue, netif_tx_queue_stopped(netif_txq),
+ jiffies_to_msecs(jiffies - trans_start),
+ dql_avail(&netif_txq->dql),
+ k3_cppi_desc_pool_avail(tx_chn->desc_pool));
+
+ if (netif_tx_queue_stopped(netif_txq)) {
+ /* try to recover if it was stopped by driver */
+ txq_trans_update(netif_txq);
+ netif_tx_wake_queue(netif_txq);
+ }
+}
+
static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
.ndo_open = vport_ndo_open,
.ndo_stop = vport_ndo_stop,
.ndo_start_xmit = vport_ndo_xmit,
.ndo_get_stats64 = vport_ndo_get_stats,
+ .ndo_tx_timeout = vport_ndo_tx_timeout,
};
static int init_netdev(struct cpsw_proxy_priv *proxy_priv, struct virtual_port *vport)
--
2.40.1
Register "eth_validate_addr()" as the .ndo_validate_addr callback.
Register "eth_mac_addr()" as the .ndo_set_mac_address callback.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 92a014e83c6c..be42b02c3894 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -1627,6 +1627,8 @@ static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
.ndo_start_xmit = vport_ndo_xmit,
.ndo_get_stats64 = vport_ndo_get_stats,
.ndo_tx_timeout = vport_ndo_tx_timeout,
+ .ndo_validate_addr = eth_validate_addr,
+ .ndo_set_mac_address = eth_mac_addr,
};
static int init_netdev(struct cpsw_proxy_priv *proxy_priv, struct virtual_port *vport)
--
2.40.1
Add the "vport_get_link()" function and register it as the driver's
get_link ethtool_ops callback.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 32 +++++++++++++++++++++
1 file changed, 32 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index be42b02c3894..450fc183eaac 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -284,6 +284,7 @@ static int create_request_message(struct cpsw_proxy_req_params *req_params)
case ETHFW_ALLOC_MAC:
case ETHFW_VIRT_PORT_DETACH:
case ETHFW_VIRT_PORT_INFO:
+ case ETHFW_VIRT_PORT_LINK_STATUS:
common_req_msg = (struct common_request_message *)msg;
req_msg_hdr = &common_req_msg->request_msg_hdr;
break;
@@ -1184,7 +1185,38 @@ static int vport_rx_poll(struct napi_struct *napi_rx, int budget)
return num_rx;
}
+static u32 vport_get_link(struct net_device *ndev)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct port_link_status_response *pls_resp;
+ struct cpsw_proxy_req_params *req_p;
+ struct message resp_msg;
+ bool link_up;
+ int ret;
+
+ if (vport->port_type != VIRT_MAC_ONLY_PORT)
+ return ethtool_op_get_link(ndev);
+
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_VIRT_PORT_LINK_STATUS;
+ req_p->token = vport->port_token;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret) {
+ netdev_err(ndev, "failed to get link status\n");
+ /* Assume that link is down if status is unknown */
+ return 0;
+ }
+ pls_resp = (struct port_link_status_response *)&resp_msg;
+ link_up = pls_resp->link_up;
+
+ return link_up;
+}
+
const struct ethtool_ops cpsw_proxy_client_ethtool_ops = {
+ .get_link = vport_get_link,
};
static int register_mac(struct virtual_port *vport)
--
2.40.1
Add the function "vport_ndo_stop()" and register it as the driver's
ndo_stop callback.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 23 +++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index e643ffb9455a..646eab90832c 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -1354,6 +1354,28 @@ static int vport_open(struct virtual_port *vport, netdev_features_t features)
return 0;
}
+static int vport_ndo_stop(struct net_device *ndev)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ int ret;
+
+ netif_tx_stop_all_queues(ndev);
+ netif_carrier_off(ndev);
+
+ ret = deregister_mac(vport);
+ if (ret)
+ netdev_err(ndev, "failed to deregister MAC for port %u\n",
+ vport->port_id);
+
+ vport_stop(vport);
+
+ dev_info(proxy_priv->dev, "stopped port %u on interface %s\n",
+ vport->port_id, ndev->name);
+
+ return 0;
+}
+
static int vport_ndo_open(struct net_device *ndev)
{
struct virtual_port *vport = vport_ndev_to_vport(ndev);
@@ -1391,6 +1413,7 @@ static int vport_ndo_open(struct net_device *ndev)
static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
.ndo_open = vport_ndo_open,
+ .ndo_stop = vport_ndo_stop,
};
static int init_netdev(struct cpsw_proxy_priv *proxy_priv, struct virtual_port *vport)
--
2.40.1
Add coalescing support for the interrupts corresponding to the TX and RX
DMA Channels using hrtimer.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 57 +++++++++++++++++++--
1 file changed, 53 insertions(+), 4 deletions(-)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 450fc183eaac..408c9f78c059 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -58,13 +58,16 @@ struct rx_dma_chan {
struct k3_cppi_desc_pool *desc_pool;
struct k3_udma_glue_rx_channel *rx_chan;
struct napi_struct napi_rx;
+ struct hrtimer rx_hrtimer;
u32 rel_chan_idx;
u32 flow_base;
u32 flow_offset;
u32 thread_id;
u32 num_descs;
unsigned int irq;
+ unsigned long rx_pace_timeout;
char rx_chan_name[CHAN_NAME_LEN];
+ bool rx_irq_disabled;
bool in_use;
};
@@ -74,10 +77,12 @@ struct tx_dma_chan {
struct k3_cppi_desc_pool *desc_pool;
struct k3_udma_glue_tx_channel *tx_chan;
struct napi_struct napi_tx;
+ struct hrtimer tx_hrtimer;
u32 rel_chan_idx;
u32 thread_id;
u32 num_descs;
unsigned int irq;
+ unsigned long tx_pace_timeout;
char tx_chan_name[CHAN_NAME_LEN];
bool in_use;
};
@@ -996,8 +1001,15 @@ static int vport_tx_poll(struct napi_struct *napi_tx, int budget)
if (num_tx >= budget)
return budget;
- if (napi_complete_done(napi_tx, num_tx))
- enable_irq(tx_chn->irq);
+ if (napi_complete_done(napi_tx, num_tx)) {
+ if (unlikely(tx_chn->tx_pace_timeout && !tdown)) {
+ hrtimer_start(&tx_chn->tx_hrtimer,
+ ns_to_ktime(tx_chn->tx_pace_timeout),
+ HRTIMER_MODE_REL_PINNED);
+ } else {
+ enable_irq(tx_chn->irq);
+ }
+ }
return 0;
}
@@ -1179,12 +1191,38 @@ static int vport_rx_poll(struct napi_struct *napi_rx, int budget)
num_rx++;
}
- if (num_rx < budget && napi_complete_done(napi_rx, num_rx))
- enable_irq(rx_chn->irq);
+ if (num_rx < budget && napi_complete_done(napi_rx, num_rx)) {
+ if (rx_chn->rx_irq_disabled) {
+ rx_chn->rx_irq_disabled = false;
+ if (unlikely(rx_chn->rx_pace_timeout)) {
+ hrtimer_start(&rx_chn->rx_hrtimer,
+ ns_to_ktime(rx_chn->rx_pace_timeout),
+ HRTIMER_MODE_REL_PINNED);
+ } else {
+ enable_irq(rx_chn->irq);
+ }
+ }
+ }
return num_rx;
}
+static enum hrtimer_restart vport_tx_timer_cb(struct hrtimer *timer)
+{
+ struct tx_dma_chan *tx_chn = container_of(timer, struct tx_dma_chan, tx_hrtimer);
+
+ enable_irq(tx_chn->irq);
+ return HRTIMER_NORESTART;
+}
+
+static enum hrtimer_restart vport_rx_timer_cb(struct hrtimer *timer)
+{
+ struct rx_dma_chan *rx_chn = container_of(timer, struct rx_dma_chan, rx_hrtimer);
+
+ enable_irq(rx_chn->irq);
+ return HRTIMER_NORESTART;
+}
+
static u32 vport_get_link(struct net_device *ndev)
{
struct virtual_port *vport = vport_ndev_to_vport(ndev);
@@ -1326,6 +1364,7 @@ static void vport_stop(struct virtual_port *vport)
k3_udma_glue_reset_tx_chn(tx_chn->tx_chan, tx_chn, vport_tx_cleanup);
k3_udma_glue_disable_tx_chn(tx_chn->tx_chan);
napi_disable(&tx_chn->napi_tx);
+ hrtimer_cancel(&tx_chn->tx_hrtimer);
}
for (i = 0; i < vport->num_rx_chan; i++) {
@@ -1336,6 +1375,7 @@ static void vport_stop(struct virtual_port *vport)
k3_udma_glue_reset_rx_chn(rx_chn->rx_chan, 0, rx_chn, vport_rx_cleanup,
false);
napi_disable(&rx_chn->napi_rx);
+ hrtimer_cancel(&rx_chn->rx_hrtimer);
}
}
@@ -1381,6 +1421,10 @@ static int vport_open(struct virtual_port *vport, netdev_features_t features)
for (i = 0; i < vport->num_rx_chan; i++) {
rx_chn = &vport->rx_chans[i];
napi_enable(&rx_chn->napi_rx);
+ if (rx_chn->rx_irq_disabled) {
+ rx_chn->rx_irq_disabled = false;
+ enable_irq(rx_chn->irq);
+ }
}
return 0;
@@ -1708,11 +1752,15 @@ static int init_netdev(struct cpsw_proxy_priv *proxy_priv, struct virtual_port *
for (i = 0; i < vport->num_tx_chan; i++) {
tx_chn = &vport->tx_chans[i];
netif_napi_add_tx(vport->ndev, &tx_chn->napi_tx, vport_tx_poll);
+ hrtimer_init(&tx_chn->tx_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED);
+ tx_chn->tx_hrtimer.function = &vport_tx_timer_cb;
}
for (i = 0; i < vport->num_rx_chan; i++) {
rx_chn = &vport->rx_chans[i];
netif_napi_add(vport->ndev, &rx_chn->napi_rx, vport_rx_poll);
+ hrtimer_init(&rx_chn->rx_hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL_PINNED);
+ rx_chn->rx_hrtimer.function = &vport_rx_timer_cb;
}
ret = register_netdev(vport->ndev);
@@ -1771,6 +1819,7 @@ static irqreturn_t rx_irq_handler(int irq, void *dev_id)
{
struct rx_dma_chan *rx_chn = dev_id;
+ rx_chn->rx_irq_disabled = true;
disable_irq_nosync(irq);
napi_schedule(&rx_chn->napi_rx);
--
2.40.1
Export coalescing support via ethtool ops.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 86 +++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 408c9f78c059..b42be0d389b8 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -1253,8 +1253,94 @@ static u32 vport_get_link(struct net_device *ndev)
return link_up;
}
+static int vport_get_coal(struct net_device *ndev, struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+
+ coal->tx_coalesce_usecs = vport->tx_chans[0].tx_pace_timeout / 1000;
+ coal->rx_coalesce_usecs = vport->rx_chans[0].rx_pace_timeout / 1000;
+ return 0;
+}
+
+static int vport_set_coal(struct net_device *ndev, struct ethtool_coalesce *coal,
+ struct kernel_ethtool_coalesce *kernel_coal,
+ struct netlink_ext_ack *extack)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct device *dev = proxy_priv->dev;
+ u32 i;
+
+ if (coal->tx_coalesce_usecs && coal->tx_coalesce_usecs < 20) {
+ dev_err(dev, "TX coalesce must be at least 20 usecs. Defaulting to 20 usecs\n");
+ coal->tx_coalesce_usecs = 20;
+ }
+
+ if (coal->rx_coalesce_usecs && coal->rx_coalesce_usecs < 20) {
+ dev_err(dev, "RX coalesce must be at least 20 usecs. Defaulting to 20 usecs\n");
+ coal->rx_coalesce_usecs = 20;
+ }
+
+ /* Since it is possible to set pacing values per TX and RX queue, if per queue value is
+ * not specified, apply it to all available TX and RX queues.
+ */
+
+ for (i = 0; i < vport->num_tx_chan; i++)
+ vport->tx_chans[i].tx_pace_timeout = coal->tx_coalesce_usecs * 1000;
+
+ for (i = 0; i < vport->num_rx_chan; i++)
+ vport->rx_chans[i].rx_pace_timeout = coal->rx_coalesce_usecs * 1000;
+
+ return 0;
+}
+
+static int vport_get_per_q_coal(struct net_device *ndev, u32 q,
+ struct ethtool_coalesce *coal)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+
+ if (q >= vport->num_tx_chan || q >= vport->num_rx_chan)
+ return -EINVAL;
+
+ coal->tx_coalesce_usecs = vport->tx_chans[q].tx_pace_timeout / 1000;
+ coal->rx_coalesce_usecs = vport->rx_chans[q].rx_pace_timeout / 1000;
+
+ return 0;
+}
+
+static int vport_set_per_q_coal(struct net_device *ndev, u32 q,
+ struct ethtool_coalesce *coal)
+{ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+ struct device *dev = vport->proxy_priv->dev;
+
+ if (q >= vport->num_tx_chan || q >= vport->num_rx_chan)
+ return -EINVAL;
+
+ if (coal->tx_coalesce_usecs && coal->tx_coalesce_usecs < 20) {
+ dev_err(dev, "TX coalesce must be at least 20 usecs. Defaulting to 20 usecs\n");
+ coal->tx_coalesce_usecs = 20;
+ }
+
+ if (coal->rx_coalesce_usecs && coal->rx_coalesce_usecs < 20) {
+ dev_err(dev, "RX coalesce must be at least 20 usecs. Defaulting to 20 usecs\n");
+ coal->rx_coalesce_usecs = 20;
+ }
+
+ vport->tx_chans[q].tx_pace_timeout = coal->tx_coalesce_usecs * 1000;
+ vport->rx_chans[q].rx_pace_timeout = coal->rx_coalesce_usecs * 1000;
+
+ return 0;
+}
+
const struct ethtool_ops cpsw_proxy_client_ethtool_ops = {
.get_link = vport_get_link,
+ .supported_coalesce_params = ETHTOOL_COALESCE_USECS,
+ .get_coalesce = vport_get_coal,
+ .set_coalesce = vport_set_coal,
+ .get_per_queue_coalesce = vport_get_per_q_coal,
+ .set_per_queue_coalesce = vport_set_per_q_coal,
};
static int register_mac(struct virtual_port *vport)
--
2.40.1
Add functions "register_ipv4()" and "deregister_ipv4()" to register and
deregister IPv4 Address of the network interface corresponding to the
Virtual Switch Port with EthFw. Registering the IPv4 Address with EthFw
is necessary in the case of the Virtual Switch Port. This is because all
Broadcast packets received on any of the Switch Ports are consumed by
EthFw. This includes the ARP request for the IPv4 Address of the network
interface corresponding to the Virtual Switch Port as well. Thus,
registering the IPv4 Address with EthFw results in EthFw responding to
the ARP request thereby enabling subsequent Unicast communication with
the network interface corresponding to the Virtual Switch Port.
Add a notifier block to register/deregister the IPv4 address with EthFw
corresponding to interface state changes as well as IPv4 Address
changes.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 121 ++++++++++++++++++++
1 file changed, 121 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index b42be0d389b8..9ede3e584a06 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -8,6 +8,7 @@
#include <linux/etherdevice.h>
#include <linux/ethtool.h>
#include <linux/if_vlan.h>
+#include <linux/inetdevice.h>
#include <linux/kernel.h>
#include <linux/kmemleak.h>
#include <linux/module.h>
@@ -106,6 +107,7 @@ struct virtual_port {
struct rx_dma_chan *rx_chans;
struct tx_dma_chan *tx_chans;
struct completion tdown_complete;
+ struct notifier_block inetaddr_nb;
enum virtual_port_type port_type;
atomic_t tdown_cnt;
u32 port_id;
@@ -113,6 +115,7 @@ struct virtual_port {
u32 port_features;
u32 num_rx_chan;
u32 num_tx_chan;
+ u8 ipv4_addr[ETHFW_IPV4ADDRLEN];
u8 mac_addr[ETH_ALEN];
bool mac_in_use;
};
@@ -1952,6 +1955,124 @@ static int register_dma_irq_handlers(struct cpsw_proxy_priv *proxy_priv)
return 0;
}
+static int register_ipv4(struct virtual_port *vport)
+{
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct device *dev = proxy_priv->dev;
+ struct cpsw_proxy_req_params *req_p;
+ struct message resp_msg;
+ int ret;
+
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_IPv4_REGISTER;
+ memcpy(req_p->ipv4_addr, vport->ipv4_addr, ETHFW_IPV4ADDRLEN);
+ ether_addr_copy(req_p->mac_addr, vport->mac_addr);
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+
+ if (ret) {
+ dev_err(dev, "failed to register IPv4 Address err: %d\n", ret);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int deregister_ipv4(struct virtual_port *vport)
+{
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct device *dev = proxy_priv->dev;
+ struct cpsw_proxy_req_params *req_p;
+ struct message resp_msg;
+ int ret;
+
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_IPv4_DEREGISTER;
+ memcpy(req_p->ipv4_addr, vport->ipv4_addr, ETHFW_IPV4ADDRLEN);
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+
+ if (ret) {
+ dev_err(dev, "failed to deregister IPv4 Address err: %d\n", ret);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static bool cpsw_proxy_client_check(const struct net_device *ndev)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+
+ return ndev->netdev_ops == &cpsw_proxy_client_netdev_ops &&
+ vport->port_type == VIRT_SWITCH_PORT;
+}
+
+static int cpsw_proxy_client_inetaddr(struct notifier_block *unused,
+ unsigned long event, void *ptr)
+{
+ struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
+ struct virtual_port *vport;
+ struct net_device *ndev;
+ int ret = 0;
+
+ ndev = ifa->ifa_dev ? ifa->ifa_dev->dev : NULL;
+ if (!ndev)
+ return NOTIFY_DONE;
+
+ if (!cpsw_proxy_client_check(ndev))
+ return NOTIFY_DONE;
+
+ vport = vport_ndev_to_vport(ndev);
+ memcpy(vport->ipv4_addr, &ifa->ifa_address, ETHFW_IPV4ADDRLEN);
+
+ switch (event) {
+ case NETDEV_UP:
+ case NETDEV_CHANGEADDR:
+ ret = register_ipv4(vport);
+ if (ret)
+ netdev_err(ndev, "IPv4 register failed: %d\n", ret);
+ break;
+
+ case NETDEV_DOWN:
+ case NETDEV_PRE_CHANGEADDR:
+ ret = deregister_ipv4(vport);
+ if (ret)
+ netdev_err(ndev, "IPv4 deregister failed: %d\n", ret);
+ break;
+ }
+
+ return notifier_from_errno(ret);
+}
+
+static void unregister_notifiers(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct virtual_port *vport;
+ u32 i;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ if (vport->port_type == VIRT_SWITCH_PORT)
+ unregister_inetaddr_notifier(&vport->inetaddr_nb);
+ }
+}
+
+static void register_notifiers(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct virtual_port *vport;
+ u32 i;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ if (vport->port_type == VIRT_SWITCH_PORT) {
+ vport->inetaddr_nb.notifier_call = cpsw_proxy_client_inetaddr;
+ register_inetaddr_notifier(&vport->inetaddr_nb);
+ }
+ }
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
--
2.40.1
Add the .ndo_set_rx_mode callback named "vport_set_rx_mode()". Syncing
the Multicast Address list requires adding/deleting Multicast Addresses
registered with EthFw.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 131 ++++++++++++++++++++
1 file changed, 131 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 9ede3e584a06..56311b019376 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -106,6 +106,9 @@ struct virtual_port {
struct net_device *ndev;
struct rx_dma_chan *rx_chans;
struct tx_dma_chan *tx_chans;
+ struct netdev_hw_addr_list mcast_list;
+ struct workqueue_struct *vport_wq;
+ struct work_struct rx_mode_work;
struct completion tdown_complete;
struct notifier_block inetaddr_nb;
enum virtual_port_type port_type;
@@ -1428,6 +1431,59 @@ static void vport_rx_cleanup(void *data, dma_addr_t desc_dma)
dev_kfree_skb_any(skb);
}
+static int vport_add_mcast(struct net_device *ndev, const u8 *addr)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct rx_dma_chan *rx_chn = &vport->rx_chans[0];
+ struct cpsw_proxy_req_params *req_p;
+ struct message resp_msg;
+ int ret;
+
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_MCAST_FILTER_ADD;
+ req_p->token = vport->port_token;
+ req_p->vlan_id = ETHFW_DFLT_VLAN;
+ req_p->rx_flow_base = rx_chn->flow_base;
+ req_p->rx_flow_offset = rx_chn->flow_offset;
+ ether_addr_copy(req_p->mac_addr, addr);
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+
+ if (ret) {
+ dev_err(proxy_priv->dev, "failed to add mcast filter, err: %d\n", ret);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static int vport_del_mcast(struct net_device *ndev, const u8 *addr)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+ struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
+ struct cpsw_proxy_req_params *req_p;
+ struct message resp_msg;
+ int ret;
+
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_MCAST_FILTER_DEL;
+ req_p->token = vport->port_token;
+ req_p->vlan_id = ETHFW_DFLT_VLAN;
+ ether_addr_copy(req_p->mac_addr, addr);
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+
+ if (ret) {
+ dev_err(proxy_priv->dev, "failed to delete mcast filter, err: %d\n", ret);
+ return -EIO;
+ }
+
+ return 0;
+}
+
static void vport_stop(struct virtual_port *vport)
{
struct cpsw_proxy_priv *proxy_priv = vport->proxy_priv;
@@ -1466,6 +1522,9 @@ static void vport_stop(struct virtual_port *vport)
napi_disable(&rx_chn->napi_rx);
hrtimer_cancel(&rx_chn->rx_hrtimer);
}
+
+ if (vport->port_features & ETHFW_MCAST_FILTERING)
+ cancel_work_sync(&vport->rx_mode_work);
}
static int vport_open(struct virtual_port *vport, netdev_features_t features)
@@ -1533,6 +1592,8 @@ static int vport_ndo_stop(struct net_device *ndev)
netdev_err(ndev, "failed to deregister MAC for port %u\n",
vport->port_id);
+ __dev_mc_unsync(ndev, vport_del_mcast);
+ __hw_addr_init(&vport->mcast_list);
vport_stop(vport);
dev_info(proxy_priv->dev, "stopped port %u on interface %s\n",
@@ -1786,6 +1847,31 @@ static void vport_ndo_tx_timeout(struct net_device *ndev, unsigned int txqueue)
}
}
+static void vport_set_rx_mode_work(struct work_struct *work)
+{
+ struct virtual_port *vport = container_of(work, struct virtual_port, rx_mode_work);
+ struct net_device *ndev;
+
+ if (likely(vport->port_features & ETHFW_MCAST_FILTERING)) {
+ ndev = vport->ndev;
+
+ netif_addr_lock_bh(ndev);
+ __hw_addr_sync(&vport->mcast_list, &ndev->mc, ndev->addr_len);
+ netif_addr_unlock_bh(ndev);
+
+ __hw_addr_sync_dev(&vport->mcast_list, ndev,
+ vport_add_mcast, vport_del_mcast);
+ }
+}
+
+static void vport_set_rx_mode(struct net_device *ndev)
+{
+ struct virtual_port *vport = vport_ndev_to_vport(ndev);
+
+ if (vport->port_features & ETHFW_MCAST_FILTERING)
+ queue_work(vport->vport_wq, &vport->rx_mode_work);
+}
+
static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
.ndo_open = vport_ndo_open,
.ndo_stop = vport_ndo_stop,
@@ -1794,6 +1880,7 @@ static const struct net_device_ops cpsw_proxy_client_netdev_ops = {
.ndo_tx_timeout = vport_ndo_tx_timeout,
.ndo_validate_addr = eth_validate_addr,
.ndo_set_mac_address = eth_mac_addr,
+ .ndo_set_rx_mode = vport_set_rx_mode,
};
static int init_netdev(struct cpsw_proxy_priv *proxy_priv, struct virtual_port *vport)
@@ -1871,12 +1958,56 @@ static void unreg_netdevs(struct cpsw_proxy_priv *proxy_priv)
}
}
+static void destroy_vport_wqs(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct virtual_port *vport;
+ u32 i;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ if (vport->vport_wq)
+ destroy_workqueue(vport->vport_wq);
+ }
+}
+
+static int create_vport_wqs(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct virtual_port *vport;
+ char wq_name[IFNAMSIZ];
+ u32 i;
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ if (!(vport->port_features & ETHFW_MCAST_FILTERING))
+ continue;
+
+ snprintf(wq_name, sizeof(wq_name), "vport_%d", vport->port_id);
+ __hw_addr_init(&vport->mcast_list);
+ INIT_WORK(&vport->rx_mode_work, vport_set_rx_mode_work);
+ vport->vport_wq = create_singlethread_workqueue(wq_name);
+ if (!vport->vport_wq) {
+ dev_err(proxy_priv->dev, "failed to create wq %s\n", wq_name);
+ goto err;
+ }
+ }
+
+ return 0;
+
+err:
+ destroy_vport_wqs(proxy_priv);
+ return -ENOMEM;
+}
+
static int init_netdevs(struct cpsw_proxy_priv *proxy_priv)
{
struct virtual_port *vport;
int ret;
u32 i;
+ ret = create_vport_wqs(proxy_priv);
+ if (ret)
+ return ret;
+
for (i = 0; i < proxy_priv->num_virt_ports; i++) {
vport = &proxy_priv->virt_ports[i];
ret = init_netdev(proxy_priv, vport);
--
2.40.1
Add the helper function "detach_virtual_ports()" to release all resources
held by the virtual ports and send the ETHFW_VIRT_PORT_DETACH request for
each virtual port. This notifies EthFw that the virtual ports are unused.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 23 +++++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 56311b019376..90be8bb0e37d 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -672,6 +672,29 @@ static int allocate_port_resources(struct cpsw_proxy_priv *proxy_priv)
return -EIO;
}
+static void detach_virtual_ports(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct cpsw_proxy_req_params *req_p;
+ struct virtual_port *vport;
+ struct message resp_msg;
+ u32 port_id, i;
+ int ret;
+
+ free_port_resources(proxy_priv);
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+ port_id = vport->port_id;
+ mutex_lock(&proxy_priv->req_params_mutex);
+ req_p = &proxy_priv->req_params;
+ req_p->request_type = ETHFW_VIRT_PORT_DETACH;
+ req_p->token = vport->port_token;
+ ret = send_request_get_response(proxy_priv, &resp_msg);
+ mutex_unlock(&proxy_priv->req_params_mutex);
+ if (ret)
+ dev_err(proxy_priv->dev, "detaching virtual port %u failed\n", port_id);
+ }
+}
+
static void free_tx_chns(void *data)
{
struct cpsw_proxy_priv *proxy_priv = data;
--
2.40.1
Use the helpers added so far to enable the client driver functionality.
Signed-off-by: Siddharth Vadapalli <[email protected]>
---
drivers/net/ethernet/ti/cpsw-proxy-client.c | 82 +++++++++++++++++++++
1 file changed, 82 insertions(+)
diff --git a/drivers/net/ethernet/ti/cpsw-proxy-client.c b/drivers/net/ethernet/ti/cpsw-proxy-client.c
index 90be8bb0e37d..3eccde764c17 100644
--- a/drivers/net/ethernet/ti/cpsw-proxy-client.c
+++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
@@ -12,6 +12,7 @@
#include <linux/kernel.h>
#include <linux/kmemleak.h>
#include <linux/module.h>
+#include <linux/of.h>
#include <linux/rpmsg.h>
#include <linux/dma/k3-udma-glue.h>
@@ -2227,9 +2228,33 @@ static void register_notifiers(struct cpsw_proxy_priv *proxy_priv)
}
}
+static void show_info(struct cpsw_proxy_priv *proxy_priv)
+{
+ struct device *dev = proxy_priv->dev;
+ struct virtual_port *vport;
+ u32 i;
+
+ dev_info(dev, "%u Virtual Switch Port(s), %u Virtual MAC Only Port(s)\n",
+ proxy_priv->num_switch_ports, proxy_priv->num_mac_ports);
+
+ for (i = 0; i < proxy_priv->num_virt_ports; i++) {
+ vport = &proxy_priv->virt_ports[i];
+
+ if (vport->port_type == VIRT_SWITCH_PORT)
+ dev_info(dev, "Virt Port: %u, Type: Switch Port, Iface: %s, Num TX: %u, Num RX: %u, Token: %u\n",
+ vport->port_id, vport->ndev->name, vport->num_tx_chan,
+ vport->num_rx_chan, vport->port_token);
+ else
+ dev_info(dev, "Virt Port: %u, Type: MAC Port, Iface: %s, Num TX: %u, Num RX: %u, Token: %u\n",
+ vport->port_id, vport->ndev->name, vport->num_tx_chan,
+ vport->num_rx_chan, vport->port_token);
+ }
+}
+
static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
{
struct cpsw_proxy_priv *proxy_priv;
+ int ret;
proxy_priv = devm_kzalloc(&rpdev->dev, sizeof(struct cpsw_proxy_priv), GFP_KERNEL);
if (!proxy_priv)
@@ -2237,22 +2262,79 @@ static int cpsw_proxy_client_probe(struct rpmsg_device *rpdev)
proxy_priv->rpdev = rpdev;
proxy_priv->dev = &rpdev->dev;
+ proxy_priv->dma_node = of_find_compatible_node(NULL, NULL,
+ (const char *)rpdev->id.driver_data);
dev_set_drvdata(proxy_priv->dev, proxy_priv);
dev_dbg(proxy_priv->dev, "driver probed\n");
+ proxy_priv->req_params.token = ETHFW_TOKEN_NONE;
+ proxy_priv->req_params.client_id = ETHFW_LINUX_CLIENT_TOKEN;
+ mutex_init(&proxy_priv->req_params_mutex);
+ init_completion(&proxy_priv->wait_for_response);
+
+ ret = get_virtual_port_info(proxy_priv);
+ if (ret)
+ return -EIO;
+
+ ret = attach_virtual_ports(proxy_priv);
+ if (ret)
+ return -EIO;
+
+ ret = allocate_port_resources(proxy_priv);
+ if (ret)
+ goto err_attach;
+
+ ret = dma_coerce_mask_and_coherent(proxy_priv->dev, DMA_BIT_MASK(48));
+ if (ret) {
+ dev_err(proxy_priv->dev, "error setting dma mask: %d\n", ret);
+ goto err_attach;
+ }
+
+ ret = init_tx_chans(proxy_priv);
+ if (ret)
+ goto err_attach;
+
+ ret = init_rx_chans(proxy_priv);
+ if (ret)
+ goto err_attach;
+
+ ret = init_netdevs(proxy_priv);
+ if (ret)
+ goto err_attach;
+
+ ret = register_dma_irq_handlers(proxy_priv);
+ if (ret)
+ goto err_netdevs;
+
+ register_notifiers(proxy_priv);
+ show_info(proxy_priv);
+
return 0;
+
+err_netdevs:
+ unreg_netdevs(proxy_priv);
+err_attach:
+ detach_virtual_ports(proxy_priv);
+ return ret;
}
static void cpsw_proxy_client_remove(struct rpmsg_device *rpdev)
{
+ struct cpsw_proxy_priv *proxy_priv;
struct device *dev = &rpdev->dev;
dev_dbg(dev, "driver removed\n");
+ proxy_priv = dev_get_drvdata(&rpdev->dev);
+ unregister_notifiers(proxy_priv);
+ unreg_netdevs(proxy_priv);
+ destroy_vport_wqs(proxy_priv);
+ detach_virtual_ports(proxy_priv);
}
static struct rpmsg_device_id cpsw_proxy_client_id_table[] = {
{
.name = ETHFW_SERVICE_EP_NAME,
+ .driver_data = (kernel_ulong_t)"ti,j721e-navss-main-udmap",
},
{},
};
--
2.40.1
…
> +++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
…
> @@ -988,6 +994,189 @@ static int vport_tx_poll(struct napi_struct *napi_tx, int budget)
…
> +static int vport_rx_packets(struct virtual_port *vport, u32 rx_chan_idx)
> +{
…
> + if (unlikely(!netif_running(skb->dev))) {
> + dev_kfree_skb_any(skb);
> + return -ENODEV;
> + }
I suggest to move such exception handling to the end of this function implementation
so that it can be better reused also by another if branch.
https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C.+Consider+using+a+goto+chain+when+leaving+a+function+on+error+when+using+and+releasing+resources
How do you think about to increase the application of scope-based resource management
also for such a software component?
https://elixir.bootlin.com/linux/v6.9.1/source/include/linux/cleanup.h
Regards,
Markus
…
> +++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
…
> @@ -996,8 +1001,15 @@ static int vport_tx_poll(struct napi_struct *napi_tx, int budget)
…
> + if (unlikely(tx_chn->tx_pace_timeout && !tdown)) {
> + hrtimer_start(&tx_chn->tx_hrtimer,
> + ns_to_ktime(tx_chn->tx_pace_timeout),
> + HRTIMER_MODE_REL_PINNED);
> + } else {
> + enable_irq(tx_chn->irq);
> + }
…
> @@ -1179,12 +1191,38 @@ static int vport_rx_poll(struct napi_struct *napi_rx, int budget)
…
> + if (unlikely(rx_chn->rx_pace_timeout)) {
> + hrtimer_start(&rx_chn->rx_hrtimer,
> + ns_to_ktime(rx_chn->rx_pace_timeout),
> + HRTIMER_MODE_REL_PINNED);
> + } else {
> + enable_irq(rx_chn->irq);
> + }
…
Would you like to omit curly brackets at a few source code places?
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/Documentation/process/coding-style.rst?h=v6.9#n197
Regards,
Markus
On Sat, May 18, 2024 at 06:12:07PM +0530, Siddharth Vadapalli wrote:
> The CPSW Proxy Client driver interfaces with Ethernet Switch Firmware on
> a remote core to enable Ethernet functionality for applications running
> on Linux. The Ethernet Switch Firmware (EthFw) is in control of the CPSW
> Ethernet Switch on the SoC and acts as the Server, offering services to
> Clients running on various cores.
I'm not sure we as a community what this architecture. We want Linux
to be driving the hardware, not firmware. So expect linux to be
running the server.
> +The "am65-cpsw-nuss.c" driver in Linux at:
> +drivers/net/ethernet/ti/am65-cpsw-nuss.c
> +provides Ethernet functionality for applications on Linux.
> +It also handles both the control-path and data-path, namely:
> +Control => Configuration of the CPSW Peripheral
> +Data => Configuration of the DMA Channels to transmit/receive data
So nuss is capable of controlling the hardware. nuss has an upper
interface which is switchdev, and a lower interface which somehow acts
on the hardware, maybe invoking RPCs into the firmware?
So it is not too big a step to put the server functionality in Linux,
on top of the nuss driver.
Now take a step backwards. This concept of a switch with different
CPUs attached to it is nothing special.
Some Marvell switches have a Z80 connected to a dedicated port of the
switch. You can run applications on that Z80. Those applications might
be as simple as spanning tree, so you can have a white box 8 port
ethernet switch without needing an external CPU. But there is an SDK,
you could run any sort of application on the Z80.
The microchip LAN996x switch has a Cortex A7 CPU, but also a PCIe
interface. Linux can control the switch via the PCIe interface, but
there could also be applications running on the Cortex.
Look at the Broadcom BCM89586M:
https://docs.broadcom.com/doc/89586M-PB
It is similar to the microchip device, an M7 CPU, and a PCIe
interface. It seems like a Linux host could be controlling the switch
via PCIe, and applications running on the M7.
I expect there are more examples, but you get the idea.
A completely different angle to look at is VF/PF and eswitches. A
server style CPU running virtual machines, a VM getting a VF passed
through to it. This is not something i know much about, so we might
need to pull in some data centre specialists. But we have a different
CPU, a virtual CPU, making use of part of the switch. Its the same
concept really.
My main point is, please start with an abstract view of the problem. A
lot of the solution should be generic, it can be applied to all these
devices. And then there probably needs to be a small part which is
specific to TI devices. It could be parts of the solutions already
exist, e.g. VF/PF have port represents, which might be a useful
concept here as well. Switchdev exists and provides a generic
interface for configuring switches...
Andrew
On Sun, May 19, 2024 at 03:00:42PM +0100, Matthew Wilcox wrote:
>
> FYI, Markus can be safely ignored. His opinions are well-established as
> being irrelevant.
I personally would suggest ignoring the nit-gritty details for the
moment, and concentrate on the big picture architecture. I suspect the
basic architecture is wrong, and the code is going to change in big
ways. So it is pointless reviewing the code at the moment, other than
to understand the architecture.
Andrew
FYI, Markus can be safely ignored. His opinions are well-established as
being irrelevant.
On Sun, May 19, 2024 at 03:18:52PM +0200, Markus Elfring wrote:
> …
> > +++ b/drivers/net/ethernet/ti/cpsw-proxy-client.c
> …
> > @@ -988,6 +994,189 @@ static int vport_tx_poll(struct napi_struct *napi_tx, int budget)
> …
> > +static int vport_rx_packets(struct virtual_port *vport, u32 rx_chan_idx)
> > +{
> …
> > + if (unlikely(!netif_running(skb->dev))) {
> > + dev_kfree_skb_any(skb);
> > + return -ENODEV;
> > + }
>
> I suggest to move such exception handling to the end of this function implementation
> so that it can be better reused also by another if branch.
> https://wiki.sei.cmu.edu/confluence/display/c/MEM12-C.+Consider+using+a+goto+chain+when+leaving+a+function+on+error+when+using+and+releasing+resources
>
> How do you think about to increase the application of scope-based resource management
> also for such a software component?
> https://elixir.bootlin.com/linux/v6.9.1/source/include/linux/cleanup.h
>
> Regards,
> Markus
>
On Sun, May 19, 2024 at 05:31:16PM +0200, Andrew Lunn wrote:
> On Sat, May 18, 2024 at 06:12:07PM +0530, Siddharth Vadapalli wrote:
> > The CPSW Proxy Client driver interfaces with Ethernet Switch Firmware on
> > a remote core to enable Ethernet functionality for applications running
> > on Linux. The Ethernet Switch Firmware (EthFw) is in control of the CPSW
> > Ethernet Switch on the SoC and acts as the Server, offering services to
> > Clients running on various cores.
>
> I'm not sure we as a community what this architecture. We want Linux
> to be driving the hardware, not firmware. So expect linux to be
> running the server.
>
> > +The "am65-cpsw-nuss.c" driver in Linux at:
> > +drivers/net/ethernet/ti/am65-cpsw-nuss.c
> > +provides Ethernet functionality for applications on Linux.
> > +It also handles both the control-path and data-path, namely:
> > +Control => Configuration of the CPSW Peripheral
> > +Data => Configuration of the DMA Channels to transmit/receive data
>
> So nuss is capable of controlling the hardware. nuss has an upper
> interface which is switchdev, and a lower interface which somehow acts
> on the hardware, maybe invoking RPCs into the firmware?
>
> So it is not too big a step to put the server functionality in Linux,
> on top of the nuss driver.
Andrew,
Thank you for reviewing the patch and sharing your feedback. While I
have come across other Switch Designs / Architecture, I am yet to go
through the one you have mentioned below. I will go through it in detail
and will follow up with my understanding in a future reply. This reply
is intended to be an acknowledgment that I have read your feedback.
I also wanted to clarify the use-case which this series targets. The
requirements of the use-case are:
1. Independent Ethernet Switch functionality: Switch operation and
configuration when Linux is not functional (Fast startup, Low Power
Mode, Safety use-cases).
2. Dynamic Ethernet Switch configuration changes performed based on the
applications which run on various cores.
[...]
Regards,
Siddharth.
> Andrew,
>
> Thank you for reviewing the patch and sharing your feedback. While I
> have come across other Switch Designs / Architecture, I am yet to go
> through the one you have mentioned below. I will go through it in detail
> and will follow up with my understanding in a future reply. This reply
> is intended to be an acknowledgment that I have read your feedback.
> I also wanted to clarify the use-case which this series targets. The
> requirements of the use-case are:
> 1. Independent Ethernet Switch functionality: Switch operation and
> configuration when Linux is not functional (Fast startup, Low Power
> Mode, Safety use-cases).
> 2. Dynamic Ethernet Switch configuration changes performed based on the
> applications which run on various cores.
Please make sure these requirements are clearly stated in the design.
The support for switches in Linux has initially come from big data
centre switches, and smaller SOHO switches you found in OpenWRT class
devices. The switchdev model has worked well so far for these use
cases. However, i do understand you have additional requirements.
Ideally we want to extend the existing model to support additional use
cases, not create a second parallel model. And we want a vendor
agnostic extensions of the switchdev model, something which all
automotive vendors can use, and non-automotive systems which have a
similar architecture.
Andrew
On Sun, May 19, 2024 at 05:31:16PM +0200, Andrew Lunn wrote:
Andrew,
I have spent time going through your feedback, trying to understand your
suggestions. This email is the complete reply corresponding to my earlier
reply at:
https://lore.kernel.org/r/[email protected]/
which was simply meant to serve as an acknowledgement that I have seen
your email.
> On Sat, May 18, 2024 at 06:12:07PM +0530, Siddharth Vadapalli wrote:
> > The CPSW Proxy Client driver interfaces with Ethernet Switch Firmware on
> > a remote core to enable Ethernet functionality for applications running
> > on Linux. The Ethernet Switch Firmware (EthFw) is in control of the CPSW
> > Ethernet Switch on the SoC and acts as the Server, offering services to
> > Clients running on various cores.
>
> I'm not sure we as a community what this architecture. We want Linux
> to be driving the hardware, not firmware. So expect linux to be
> running the server.
Due to the use-case requirements, Linux cannot be the server. Some of
the requirements are:
1. Fast startup and configuration of CPSW independent of Linux and Other
OS running on any of the cores on the SoC. The configuration of CPSW has
to be performed in parallel while the Bootloader starts Linux.
2. CPSW has to be functional and configurable even when Linux has been
suspended. One of the non-Linux Clients happens to be the AUTOSAR Client
which continues exchanging network data via CPSW even when Linux has
been suspended. So the server has to be functional even then, in order
to cater to the AUTOSAR Client's requests to configure CPSW. CPSW's
configuration is not static in the sense that it gets programmed and
will no longer be modified. Therefore the server has to be functional at
all times to update CPSW's configuration based on the demands of any of
the Clients.
For more details about the Ethernet Switch Firmware (EthFw) and the set
of Clients running on remote cores, please refer:
https://software-dl.ti.com/jacinto7/esd/processor-sdk-rtos-jacinto7/09_02_00_05/exports/docs/ethfw/docs/user_guide/ethfw_c_ug_top.html#ethfw_remote_clients
>
> > +The "am65-cpsw-nuss.c" driver in Linux at:
> > +drivers/net/ethernet/ti/am65-cpsw-nuss.c
> > +provides Ethernet functionality for applications on Linux.
> > +It also handles both the control-path and data-path, namely:
> > +Control => Configuration of the CPSW Peripheral
> > +Data => Configuration of the DMA Channels to transmit/receive data
>
> So nuss is capable of controlling the hardware. nuss has an upper
> interface which is switchdev, and a lower interface which somehow acts
> on the hardware, maybe invoking RPCs into the firmware?
There are no RPCs used by the "am65-cpsw-nuss.c" driver. It assumes that
it is the only user of CPSW Ethernet Switch. It doesn't interface with
any firmware. Based on the switchdev framework, it receives commands
from userspace which it then uses to directly write to CPSW's registers.
>
> So it is not too big a step to put the server functionality in Linux,
> on top of the nuss driver.
Maybe it isn't a big step but it doesn't help with the use-case that I
have described above. For that reason, while it might be a "good to have"
feature, it is not solving the problem.
>
> Now take a step backwards. This concept of a switch with different
> CPUs attached to it is nothing special.
>
> Some Marvell switches have a Z80 connected to a dedicated port of the
> switch. You can run applications on that Z80. Those applications might
> be as simple as spanning tree, so you can have a white box 8 port
> ethernet switch without needing an external CPU. But there is an SDK,
> you could run any sort of application on the Z80.
>
> The microchip LAN996x switch has a Cortex A7 CPU, but also a PCIe
> interface. Linux can control the switch via the PCIe interface, but
> there could also be applications running on the Cortex.
>
> Look at the Broadcom BCM89586M:
> https://docs.broadcom.com/doc/89586M-PB
>
> It is similar to the microchip device, an M7 CPU, and a PCIe
> interface. It seems like a Linux host could be controlling the switch
> via PCIe, and applications running on the M7.
>
> I expect there are more examples, but you get the idea.
I have gone through the examples above. All of them are referring to the
Hardware Capabilities of the Ethernet Switch, which aren't applicable to
the CPSW Ethernet Switch. I am listing why each of them isn't applicable:
1. Marvel Z80 Switch:
I assume that you are referring to:
https://wiki.espressobin.net/tiki-index.php?page=Topaz+Switch
with the "Integrated 200MHz Z80 microprocessor". CPSW doesn't have an
embedded microprocessor dedicated to programming it. The closest it
could get to the Z80 is the external R5 Core running EthFw as far as
configuring the Switch is concerned. But how does it handle the use-case
where there are applications running simultaneously on different cores,
all of which require Ethernet Functionality with the same Ethernet
Switch, in a dynamic manner?
2. Microchip LAN996x:
CPSW doesn't have a PCIe interface.
3. Broadcom BCM89586M:
Again, CPSW doesn't have a PCIe interface.
An important point to note is that all applications you have mentioned
are running on a single core. The current framework being proposed to
solve the problem is for the use-case where there are applications
running across various cores with different criticality (not all
applications may be running all the time, Linux for example will be
suspended as well).
>
> A completely different angle to look at is VF/PF and eswitches. A
> server style CPU running virtual machines, a VM getting a VF passed
> through to it. This is not something i know much about, so we might
> need to pull in some data centre specialists. But we have a different
> CPU, a virtual CPU, making use of part of the switch. Its the same
> concept really.
CPSW doesn't support SR-IOV. However, if you are referring to modelling
CPSW as an SR-IOV capable Ethernet Switch by having EthFw pose as the
Driver for the "Virtual" Physical Function of CPSW, with each Client
Driver mapping to one of the modelled "Virtual" Virtual Functions
exposed by EthFw, then yes, I will spend time looking at how that could
be implemented. The term "Virtual" has been added in the previous
sentence to clarify that CPSW isn't truly SR-IOV capable and we are
simply making it look that way via EthFw. Even in SR-IOV, the
communicatoin between PF and VF drivers happens via Hardware Mailbox
which means RPMsg is coming back into the picture. The current
implementation also is using RPMsg to exchange control information
between EthFw and all the Clients.
>
> My main point is, please start with an abstract view of the problem. A
> lot of the solution should be generic, it can be applied to all these
> devices. And then there probably needs to be a small part which is
> specific to TI devices. It could be parts of the solutions already
> exist, e.g. VF/PF have port represents, which might be a useful
> concept here as well. Switchdev exists and provides a generic
> interface for configuring switches...
The summary of the problem statement is:
We require a framework in which the Ethernet Switch (CPSW) has to be
shared across the applications running on different cores of the SoC.
Since CPSW doesn't have an Integrated Processor, some core on the SoC
has to act as the Central Entity which is responsible for arbitrating
configuration requests from various cores and performing the appropriate
configuration of CPSW. Additionally, apart from performing configuration
of CPSW, the Central Entity is also responsible for allocating resources
to different cores, including DMA Channels/Flows (There are 8 TX DMA
Channels which have to be split across different cores to allow each
core to send traffic to CPSW for example). CPSW should be functional
even when some of the Clients (including Linux) might be suspended for
Low Power use-case. The forwarding path of CPSW should be functional
within 100s of milliseconds after the Bootloader stage.
Kindly share your feedback on possible implementations to address the
problem summarized above. Thank you for sharing your valuable feedback
so far on this series.
Regards,
Siddharth.
On Sun, Jun 02, 2024 at 09:36:05AM +0530, Siddharth Vadapalli wrote:
> On Sun, May 19, 2024 at 05:31:16PM +0200, Andrew Lunn wrote:
>
> Andrew,
>
> I have spent time going through your feedback, trying to understand your
> suggestions. This email is the complete reply corresponding to my earlier
> reply at:
> https://lore.kernel.org/r/[email protected]/
> which was simply meant to serve as an acknowledgement that I have seen
> your email.
>
> > On Sat, May 18, 2024 at 06:12:07PM +0530, Siddharth Vadapalli wrote:
> > > The CPSW Proxy Client driver interfaces with Ethernet Switch Firmware on
> > > a remote core to enable Ethernet functionality for applications running
> > > on Linux. The Ethernet Switch Firmware (EthFw) is in control of the CPSW
> > > Ethernet Switch on the SoC and acts as the Server, offering services to
> > > Clients running on various cores.
> >
> > I'm not sure we as a community what this architecture. We want Linux
> > to be driving the hardware, not firmware. So expect linux to be
> > running the server.
>
> Due to the use-case requirements, Linux cannot be the server. Some of
> the requirements are:
> 1. Fast startup and configuration of CPSW independent of Linux and Other
> OS running on any of the cores on the SoC. The configuration of CPSW has
> to be performed in parallel while the Bootloader starts Linux.
> 2. CPSW has to be functional and configurable even when Linux has been
> suspended. One of the non-Linux Clients happens to be the AUTOSAR Client
> which continues exchanging network data via CPSW even when Linux has
> been suspended. So the server has to be functional even then, in order
> to cater to the AUTOSAR Client's requests to configure CPSW. CPSW's
> configuration is not static in the sense that it gets programmed and
> will no longer be modified. Therefore the server has to be functional at
> all times to update CPSW's configuration based on the demands of any of
> the Clients.
>
> For more details about the Ethernet Switch Firmware (EthFw) and the set
> of Clients running on remote cores, please refer:
> https://software-dl.ti.com/jacinto7/esd/processor-sdk-rtos-jacinto7/09_02_00_05/exports/docs/ethfw/docs/user_guide/ethfw_c_ug_top.html#ethfw_remote_clients
Thanks for the links etc.
I also admit, i did replied too soon, and should of read more of the
patches.
In Linux, we have two models with respect to switches.
1) They are external. Linux does not interact with them, other than
sending them packets, and receiving packets from them. The switch
might have some management interface, SNMP, HTTP, etc, but it is not
linuxs job to manage the switch. Linux just has its NIC connected to
the port of switch using a cable. This is the model used for a very
long time.
2) The Linux kernel is controlling the switch, configuration is
performed from userspace using iproute2. Switchdev is used internally
to interface between the linux network stack and the switch
driver. Depending on implementation, linux can either directly write
switch registers, or it can perform an RPC to firmware running on the
switch. But this is an implementation detail, Linux is in control of
all the ports, all the routing/switching, IGMP snooping, STP, PTP,
etc.
Could what Linux sees of this hardware fit into the first model? Linux
just sees a bunch of NICs connected to a switch? The switch is remote,
linux has no control over it. Linux acts purely as a client for low
level protocols like PTP, IGMP snooping, etc. It has no knowledge of
other ports of the switch, there up/down state, what STP is doing in
the switch, how PTP is forwarding packets from the upstream port to
the downstream ports. Linux has no idea and no access to the address
lookup engines in the switch. The switch is colocated in the same
silicon, but all linux has is some ports connected to the switch,
nothing more?
What is interesting is Realteks current driver work for there
automotive system. There CPU has one MAC which is connected to the
internal switch. But they have a similar setup, Linux is not
controlling the switch, some other firmware is. They have PTP, IGMP
snooping, STP etc running in firmware. Linux just has a NIC connected
to the switch as an end system.
If you do want to add a third model, where Linux has some insight into
the switch, you need to coordinate with other vendors in the
automotive world, and come up with a model which everybody can
use. What i don't want is a TI model, followed by a Realtek model,
followed by a vendor XYZ model. So if you need more than what the
first model above provides, you will need to get a consortium of
vendors together to design a new model a few vendors agree on.
Andrew
On Tue, Jun 04, 2024 at 04:20:16PM +0200, Andrew Lunn wrote:
> On Sun, Jun 02, 2024 at 09:36:05AM +0530, Siddharth Vadapalli wrote:
[...]
>
> If you do want to add a third model, where Linux has some insight into
> the switch, you need to coordinate with other vendors in the
> automotive world, and come up with a model which everybody can
> use. What i don't want is a TI model, followed by a Realtek model,
> followed by a vendor XYZ model. So if you need more than what the
> first model above provides, you will need to get a consortium of
> vendors together to design a new model a few vendors agree on.
I believe that a third model is required given the System Architecture
and the use-case that it must cater to. I agree completely that having a
vendor specific implementation should always be the last step when it is
just not possible to generalize any portion of the implementation. I will
describe the existing Architecture on the TI SoC and will also attempt to
generalize the implementation below. I hope that you could review it and
guide me towards the generic, vendor-agnostic implementation which will
also address the use-case that this series corresponds to. I am willing
to work on the generic implementation since I assume that this series
does keep it generic enough that it could be extended to be vendor
independent. So there might be minor changes required when switching to
the generic model. On the other hand, based on the description that I
provide below, if you think that the existing models can also be slightly
modified to accomodate the use-case, I will surely take that into
consideration and work on the corresponding implementation.
System Architecture and Implementation Details
==============================================
The CPSW Ethernet Switch has a single Host Port (CPU facing port) through
which it can receive data from the Host(s) and transmit data to the
Host(s). The exchange of data occurs via TX/RX DMA Channels (Hardware
Queues). These Hardware Queues are a limited resource (8 TX Channels and
up to 64 RX Flows). If the Operating System on any of the cores is the
sole user of CPSW then all of these Hardware Queues can be claimed by that
OS. However, when CPSW has to be shared across the Operating Systems on
various cores with the aim of enabling Ethernet Functionality for the
Applications running on different cores, it is necessary to share these
Hardware Queues in a manner that prevents conflicts. On the control path
which corresponds to the configuration of CPSW to get it up and running,
since there is no Integrated Processor within CPSW that can be programmed
with a startup configuration, either the Operating System or Firmware
running on one of the cores has to take the responsibility of setting it.
One option in this case happens to be the Ethernet Switch Firmware (EthFw)
which is loaded by the Bootloader on a remote core at the same time that
Linux and other Operating Systems begin booting. EthFw quickly powers on
and configures CPSW getting the Forwarding Path functional. Once Linux and
other Operating Systems on various cores are ready, they can communicate
with EthFw to obtain details of the Hardware Queues allocated to them to
exchange data with CPSW. With the knowledge of the Hardware Queues that
have been allocated, Linux can use the DMA APIs to setup these queues
to exchange data with CPSW.
Setting up the Hardware Queues alone isn't sufficient to exchange data
with the external network. Consider the following example:
The ethX interface in userspace which has been created to transmit/receive
data to/from CPSW has the user-assigned MAC Address of "M". The ping
command is run with the destination IP of "D". This results in an ARP
request sent from ethX which is transmitted out of all MAC Ports of CPSW
since it is a Broadcast request. Assuming that "D" is a valid
destination IP, the ARP reply is received on one of the MAC Ports which
is now a Unicast reply with the destination MAC Address of "M". The ALE
(Address Lookup Engine) in CPSW has learnt that the MAC Address "M"
corresponds to the Host Port when the ARP request was sent out. So the
Unicast reply isn't dropped. The challenge however is determining which
RX DMA Channel (Flow) to send the Unicast reply on. In the case of a
single Operating System owning all Hardware Queues, sending it on any of
the RX DMA Channels would have worked. In the current case where the RX
DMA Channels map to different Hosts (Operating Systems and Applications),
the mapping between the MAC Address "M" and the RX DMA Channel has to be
setup to ensure that the correct Host receives the ARP reply. This
necessitates a method to inform the MAC Address "M" associated with the
interface ethX to EthFw so that EthFw can setup the MAC Address "M" to
RX DMA Channel map accordingly.
At this point, Linux can exchange data with the external network via CPSW,
but no device on the external network can initiate the communication by
itself unless it already has the ARP entry for the IP Address of ethX.
That's because CPSW doesn't support packet replication implying that any
Broadcast/Multicast packets received on the MAC Ports can only be sent
on one of the RX DMA Channels. So the Broadcast/Multicast packets can
only be received by one Host. Consider the following example:
A PC on the network tries to ping the IP Address of ethX. In both of the
following cases:
1. Linux hasn't yet exchanged data with the PC via ethX.
2. The MAC Address of ethX has changed.
the PC sends an ARP request to one of the MAC Ports on CPSW to figure
out the MAC Address of ethX. Since the ARP request is a Broadcast
request, it is not possible for CPSW to determine the correct Host,
since the Broadcast MAC isn't unique to any Host. So CPSW is forced
to send the Broadcast request to a preconfigured RX DMA Channel which
in this case happens to be the one mapped to EthFw. Thus, if EthFw
is aware of the IP Address of ethX, it can generate and send the ARP
reply containing the MAC Address "M" of ethX that it was informed of.
With this, the PC can initiate communication with Linux as well.
Similarly, in the case of Multicast packets, if Linux wishes to receive
certain Multicast packets, it needs to inform the same to EthFw which
shall then replicate the Multicast packets it received from CPSW and
transmit them via alternate means (Shared Memory for example) to Linux.
The following is a summary of the steps so far required to enable
Ethernet Functionality for applications running on Linux:
1. Determine and setup the Hardware Queues allocated to Linux
2. Inform the MAC Address of ethX to EthFw
3. Inform the IP Address of ethX to EthFw
4. Inform any of the Multicast Addresses associated with ethX to EthFw
All data between Linux (Or any Operating System) and EthFw is exchanged
via the Hardware Mailboxes with the help of the RPMsg framework. Since
all the resource allocation information comes from EthFw, the
vendor-specific implementation in the Linux Client is limited to the DMA
APIs used to setup the Hardware Queues and to transmit/receive data with
the Ethernet Switch. Therefore, it might be possible to move most of the
vendor specific implementation to the Switch Configuration Firmware
(similar to EthFw), to make the Linux Client implementation as generic
and vendor agnostic as possible. I believe that this series more or less
does the same, just using custom terminology which can be made generic.
I can update this series to a generic implementation along with proper
documentation and naming convention to enable any vendor to reuse the
same without having to modify the implementation. The RPMsg ABIs can
be given generic names with extensive documentation and also designed
to be extensible enough to cater to functional enhancements over time.
Kindly let me know your thoughts on this.
Regards,
Siddharth.
> System Architecture and Implementation Details
> ==============================================
>
> The CPSW Ethernet Switch has a single Host Port (CPU facing port) through
> which it can receive data from the Host(s) and transmit data to the
> Host(s).
So there is a single host port, but it can support multiple hosts,
each having a subset of the available DMA channels. Maybe it is
explain later, but why call it a _single_ host port? Apart from the
DMA channels, are there other things the hosts are sharing?
> The exchange of data occurs via TX/RX DMA Channels (Hardware
> Queues). These Hardware Queues are a limited resource (8 TX Channels and
> up to 64 RX Flows). If the Operating System on any of the cores is the
> sole user of CPSW then all of these Hardware Queues can be claimed by that
> OS. However, when CPSW has to be shared across the Operating Systems on
> various cores with the aim of enabling Ethernet Functionality for the
> Applications running on different cores, it is necessary to share these
> Hardware Queues in a manner that prevents conflicts. On the control path
> which corresponds to the configuration of CPSW to get it up and running,
> since there is no Integrated Processor within CPSW that can be programmed
> with a startup configuration, either the Operating System or Firmware
> running on one of the cores has to take the responsibility of setting it.
> One option in this case happens to be the Ethernet Switch Firmware (EthFw)
> which is loaded by the Bootloader on a remote core at the same time that
> Linux and other Operating Systems begin booting. EthFw quickly powers on
> and configures CPSW getting the Forwarding Path functional.
At some point, a definition of functional will be needed. How does the
EthFw know what is required? Should Linux care? Can Linux change it?
> Once Linux and
> other Operating Systems on various cores are ready, they can communicate
> with EthFw to obtain details of the Hardware Queues allocated to them to
> exchange data with CPSW.
> With the knowledge of the Hardware Queues that
> have been allocated, Linux can use the DMA APIs to setup these queues
> to exchange data with CPSW.
This might be an important point. You communicate with the CPSW. You
don't communicate transparently through the CPSW to external ports?
There is no mechanism for a host to say, send this packet out port X?
It is the CPSW which decides, based on its address tables? The
destination MAC address decides where a packet goes.
> Setting up the Hardware Queues alone isn't sufficient to exchange data
> with the external network. Consider the following example:
> The ethX interface in userspace which has been created to transmit/receive
> data to/from CPSW has the user-assigned MAC Address of "M". The ping
> command is run with the destination IP of "D". This results in an ARP
> request sent from ethX which is transmitted out of all MAC Ports of CPSW
> since it is a Broadcast request. Assuming that "D" is a valid
> destination IP, the ARP reply is received on one of the MAC Ports which
> is now a Unicast reply with the destination MAC Address of "M". The ALE
> (Address Lookup Engine) in CPSW has learnt that the MAC Address "M"
> corresponds to the Host Port when the ARP request was sent out. So the
> Unicast reply isn't dropped. The challenge however is determining which
> RX DMA Channel (Flow) to send the Unicast reply on. In the case of a
> single Operating System owning all Hardware Queues, sending it on any of
> the RX DMA Channels would have worked. In the current case where the RX
> DMA Channels map to different Hosts (Operating Systems and Applications),
> the mapping between the MAC Address "M" and the RX DMA Channel has to be
> setup to ensure that the correct Host receives the ARP reply. This
> necessitates a method to inform the MAC Address "M" associated with the
> interface ethX to EthFw so that EthFw can setup the MAC Address "M" to
> RX DMA Channel map accordingly.
Why not have EthFW also do learning? The broadcast ARP request tells
you that MAC address M is associated to a TX DMA channel. EthFW should
know the Rx DMA channel which pairs with it, and can program ALE.
That is how a switch works, it learns what MAC address is where, it is
not told.
> At this point, Linux can exchange data with the external network via CPSW,
> but no device on the external network can initiate the communication by
> itself unless it already has the ARP entry for the IP Address of ethX.
> That's because CPSW doesn't support packet replication implying that any
> Broadcast/Multicast packets received on the MAC Ports can only be sent
> on one of the RX DMA Channels.
That sounds broken.
And this is where we need to be very careful. It is hard to build a
generic model when the first device using it is broken. Ethernet
switches have always been able to replicate. Dumb hubs did nothing but
replicate. Address learning, and forwarding out specific ports came
later, but multicast and broadcast was always replicated. IGMP
snooping came later still, which reduced multicast replication.
And your switch cannot do replication....
> So the Broadcast/Multicast packets can
> only be received by one Host. Consider the following example:
> A PC on the network tries to ping the IP Address of ethX. In both of the
> following cases:
> 1. Linux hasn't yet exchanged data with the PC via ethX.
> 2. The MAC Address of ethX has changed.
> the PC sends an ARP request to one of the MAC Ports on CPSW to figure
> out the MAC Address of ethX. Since the ARP request is a Broadcast
> request, it is not possible for CPSW to determine the correct Host,
> since the Broadcast MAC isn't unique to any Host. So CPSW is forced
> to send the Broadcast request to a preconfigured RX DMA Channel which
> in this case happens to be the one mapped to EthFw. Thus, if EthFw
> is aware of the IP Address of ethX, it can generate and send the ARP
> reply containing the MAC Address "M" of ethX that it was informed of.
> With this, the PC can initiate communication with Linux as well.
>
> Similarly, in the case of Multicast packets, if Linux wishes to receive
> certain Multicast packets, it needs to inform the same to EthFw which
> shall then replicate the Multicast packets it received from CPSW and
> transmit them via alternate means (Shared Memory for example) to Linux.
This all sounds like you are working around broken behaviour, not
something generic.
What i actually think you need to do is hide all the broken
behaviour. Trap all multicast/broadcast to EthFw. It can run a
software bridge, and do learning. It will see the outgoing ARP request
from a host and learn the host MAC address. It can then flood the
packet out the external ports, working around the CSPW brokeness. It
can also program the ALE, so the reply goes straight to the
host. Incoming broadcast and multicast is also trapped to the EthFW
and it can use its software bridge to flood the packet to all the
hosts. It can also perform IGMP snooping, and learn which hosts are
interested in Multicast.
Your switch then functions as a switch.
And you are then the same as the RealTek and Samsung device. Linux is
just a plain boring host connect to a switch, which somebody else is
managing. No new model needed.
> All data between Linux (Or any Operating System) and EthFw is exchanged
> via the Hardware Mailboxes with the help of the RPMsg framework. Since
> all the resource allocation information comes from EthFw, the
> vendor-specific implementation in the Linux Client is limited to the DMA
> APIs used to setup the Hardware Queues and to transmit/receive data with
> the Ethernet Switch. Therefore, it might be possible to move most of the
> vendor specific implementation to the Switch Configuration Firmware
> (similar to EthFw), to make the Linux Client implementation as generic
> and vendor agnostic as possible. I believe that this series more or less
> does the same, just using custom terminology which can be made generic.
This is actually very similar to what your college is doing:
https://lore.kernel.org/netdev/[email protected]/
The only real difference is shared memory vs DMA.
Andrew
On Wed, Jun 12, 2024 at 12:03:00AM +0200, Andrew Lunn wrote:
> > System Architecture and Implementation Details
> > ==============================================
> >
> > The CPSW Ethernet Switch has a single Host Port (CPU facing port) through
> > which it can receive data from the Host(s) and transmit data to the
> > Host(s).
>
> So there is a single host port, but it can support multiple hosts,
> each having a subset of the available DMA channels. Maybe it is
> explain later, but why call it a _single_ host port? Apart from the
> DMA channels, are there other things the hosts are sharing?
The term __single__ is important here in the context of the questions
you have asked below. Please consider the analogy of an external network
switch to which our Laptop/PC is connected to via a LAN Cable. Such
external network switches have multiple ports, all of which are treated
identically. Devices connected to one port can communicate with devices
connected on other ports of that network switch. So there technically
isn't a "special port" or Host Port, since each Port connects to a
different device.
In the case of CPSW, a CPSW5G instance for example, has 4 MAC Ports
(which are the equivalent of the ports present on the external network
switch and accessible via the external world) and 1 Host Port. The
single Host Port has multiple TX/RX DMA Channels to transmit/receive
data. If these channels are shared across multiple cores, then yes, we
do have multiple hosts exchanging data with CPSW via its single host
port. All rules that apply to the MAC Ports or any Ethernet Switch Port
for that matter, also apply to the CPSW's Host Port. One such rule which
is significant in the current context happens to be that "duplicate"
packets cannot be sent out of a single port. I shall refer to this again
in my reply below.
>
> > The exchange of data occurs via TX/RX DMA Channels (Hardware
> > Queues). These Hardware Queues are a limited resource (8 TX Channels and
> > up to 64 RX Flows). If the Operating System on any of the cores is the
> > sole user of CPSW then all of these Hardware Queues can be claimed by that
> > OS. However, when CPSW has to be shared across the Operating Systems on
> > various cores with the aim of enabling Ethernet Functionality for the
> > Applications running on different cores, it is necessary to share these
> > Hardware Queues in a manner that prevents conflicts. On the control path
> > which corresponds to the configuration of CPSW to get it up and running,
> > since there is no Integrated Processor within CPSW that can be programmed
> > with a startup configuration, either the Operating System or Firmware
> > running on one of the cores has to take the responsibility of setting it.
> > One option in this case happens to be the Ethernet Switch Firmware (EthFw)
> > which is loaded by the Bootloader on a remote core at the same time that
> > Linux and other Operating Systems begin booting. EthFw quickly powers on
> > and configures CPSW getting the Forwarding Path functional.
>
> At some point, a definition of functional will be needed. How does the
> EthFw know what is required? Should Linux care? Can Linux change it?
The term functional can be considered to be the equivalent of a "start-up"
configuration that is present in the external network switches. The code
that is flashed into the external network switch's non-volatile memory
to setup and configure the switch on device power-on is responsible to
get the switch functional. From a user's perspective, functional will
imply that the devices connected to the ports on the external network
switch are able to exchange data with one another (Forwarding Path).
Therefore, Linux doesn't need to know about this and also cannot change
the startup configuration performed by EthFw.
>
> > Once Linux and
> > other Operating Systems on various cores are ready, they can communicate
> > with EthFw to obtain details of the Hardware Queues allocated to them to
> > exchange data with CPSW.
>
> > With the knowledge of the Hardware Queues that
> > have been allocated, Linux can use the DMA APIs to setup these queues
> > to exchange data with CPSW.
>
> This might be an important point. You communicate with the CPSW. You
> don't communicate transparently through the CPSW to external ports?
> There is no mechanism for a host to say, send this packet out port X?
> It is the CPSW which decides, based on its address tables? The
> destination MAC address decides where a packet goes.
Yes, the host cannot/doesn't decide which MAC Port the packet goes out
of. The ALE in CPSW is responsible for deciding that. This is identical
to an external network switch. A PC which sends traffic to the port on
the network switch cannot/doesn't tell the switch which port to send it
out of. The network switch is supposed to learn and determine this.
The DMA Channels provide a path to/from CPSW's Host Port for each Host.
Please refer to the following illustration corresponding to the data
movement from each of the Hosts to the CPSW's Host Port via the ALE and
then out of the MAC Ports:
------- --------- --------- CONTROL-PATH
|Linux| |AUTOSAR| | EthFW | -------------
------- --------- --------- |
| | | | | | |
DATA TX RX TX RX TX RX |
PATH => | | | | | | |
(DMA) | | | | | | |
| | | | | | |
\ \ \ \ / / |
\ \ \ \ / / |
\ \ \ \ / / |
\ \ \ \ / / |
\ \ \ \ / / |
=============================== |
|| CPSW HOST PORT || V
=============================== -----------
| |CPSW |
TX + RX |CONTROL |
| |REGISTERS|
| -----------
|
===================
||ALE & SWITCH CORE||
===================
/ | | \
/ | | \
/ | | \
TX+RX | \ -------
/ | \ \
/ TX+RX TX+RX TX+RX
/ | \ \
========== ========== ========== ==========
|MAC Port 1| |MAC Port 2| |MAC Port 3| |MAC Port 4|
========== ========== ========== ==========
>
> > Setting up the Hardware Queues alone isn't sufficient to exchange data
> > with the external network. Consider the following example:
> > The ethX interface in userspace which has been created to transmit/receive
> > data to/from CPSW has the user-assigned MAC Address of "M". The ping
> > command is run with the destination IP of "D". This results in an ARP
> > request sent from ethX which is transmitted out of all MAC Ports of CPSW
> > since it is a Broadcast request. Assuming that "D" is a valid
> > destination IP, the ARP reply is received on one of the MAC Ports which
> > is now a Unicast reply with the destination MAC Address of "M". The ALE
> > (Address Lookup Engine) in CPSW has learnt that the MAC Address "M"
> > corresponds to the Host Port when the ARP request was sent out. So the
> > Unicast reply isn't dropped. The challenge however is determining which
> > RX DMA Channel (Flow) to send the Unicast reply on. In the case of a
> > single Operating System owning all Hardware Queues, sending it on any of
> > the RX DMA Channels would have worked. In the current case where the RX
> > DMA Channels map to different Hosts (Operating Systems and Applications),
> > the mapping between the MAC Address "M" and the RX DMA Channel has to be
> > setup to ensure that the correct Host receives the ARP reply. This
> > necessitates a method to inform the MAC Address "M" associated with the
> > interface ethX to EthFw so that EthFw can setup the MAC Address "M" to
> > RX DMA Channel map accordingly.
>
> Why not have EthFW also do learning? The broadcast ARP request tells
> you that MAC address M is associated to a TX DMA channel. EthFW should
> know the Rx DMA channel which pairs with it, and can program ALE.
>
> That is how a switch works, it learns what MAC address is where, it is
> not told.
The ALE in CPSW learns just like any other switch and doesn't need to be
programmed. When the ARP broadcast is sent out via the ALE, it learns
that the MAC Address "M" is from the Host Port. The problem however is
that knowing this alone isn't sufficient for the return path (ARP reply).
The ARP reply contains the destination MAC Address "M" which tells the
ALE that the Host Port is the destination. So the ALE will send the ARP
reply to the Host Port. But, as the illustration shows, from the Host
Port, there are multiple RX DMA Channels for each Host. So the ALE did
its job as expected from any standard ALE. The missing part here is
determining which RX DMA Channel (Flow) to place that packet on. That
requires programming the Classifiers in CPSW to map that packets
containing the MAC Address "M" to the RX DMA Flow corresponding to the
Host which has registered with that MAC Address "M". This is handled by
EthFw. EthFw doesn't/cannot snoop on all traffic on the Host Port, since
it doesn't lie in between the Host Port and the other Hosts. Rather, it
is quite similar to a Host itself since it also has dedicated TX/RX DMA
Channels to exchange traffic with CPSW.
>
> > At this point, Linux can exchange data with the external network via CPSW,
> > but no device on the external network can initiate the communication by
> > itself unless it already has the ARP entry for the IP Address of ethX.
> > That's because CPSW doesn't support packet replication implying that any
> > Broadcast/Multicast packets received on the MAC Ports can only be sent
> > on one of the RX DMA Channels.
>
> That sounds broken.
>
> And this is where we need to be very careful. It is hard to build a
> generic model when the first device using it is broken. Ethernet
> switches have always been able to replicate. Dumb hubs did nothing but
> replicate. Address learning, and forwarding out specific ports came
> later, but multicast and broadcast was always replicated. IGMP
> snooping came later still, which reduced multicast replication.
>
> And your switch cannot do replication....
I will respectfully disagree with your statement here. Packet replication
is supported in CPSW just like any other Ethernet Switch which can create
copies of Broadcast/Multicast traffic it receives on one "Port" and send
them out of the other "Ports". That is exactly how the ARP Broadcast
request sent from Linux to the CPSW Host Port is duplicated in the Switch
Core and is sent out via all of the MAC Ports in the illustration above.
The "packet replication" that I was referring to, is that of creating
duplicates of a packet and sending them out on the same "Port" (Host Port
in this case). This is not expected of any Ethernet Switch and can only
be considered an optional feature. In the current case, due to the presence
of Multiple Hosts connected to a __single__ Host Port, copies of Broadcast
or Multicast traffic are expected on a single Port so that one copy each of
the Broadcast/Multicast traffic directed to the Host Port can be sent on
each of the RX DMA Flows for every Host. Since that is not supported, all
Broadcast/Multicast traffic directed to the Host Port from the Switch Core
is by default placed on the RX DMA Flow corresponding to EthFw. EthFw then
creates copies of these in software and shares them with each Host via
Shared Memory for example.
>
> > So the Broadcast/Multicast packets can
> > only be received by one Host. Consider the following example:
> > A PC on the network tries to ping the IP Address of ethX. In both of the
> > following cases:
> > 1. Linux hasn't yet exchanged data with the PC via ethX.
> > 2. The MAC Address of ethX has changed.
> > the PC sends an ARP request to one of the MAC Ports on CPSW to figure
> > out the MAC Address of ethX. Since the ARP request is a Broadcast
> > request, it is not possible for CPSW to determine the correct Host,
> > since the Broadcast MAC isn't unique to any Host. So CPSW is forced
> > to send the Broadcast request to a preconfigured RX DMA Channel which
> > in this case happens to be the one mapped to EthFw. Thus, if EthFw
> > is aware of the IP Address of ethX, it can generate and send the ARP
> > reply containing the MAC Address "M" of ethX that it was informed of.
> > With this, the PC can initiate communication with Linux as well.
> >
> > Similarly, in the case of Multicast packets, if Linux wishes to receive
> > certain Multicast packets, it needs to inform the same to EthFw which
> > shall then replicate the Multicast packets it received from CPSW and
> > transmit them via alternate means (Shared Memory for example) to Linux.
>
> This all sounds like you are working around broken behaviour, not
> something generic.
>
> What i actually think you need to do is hide all the broken
> behaviour. Trap all multicast/broadcast to EthFw. It can run a
All Multicast/Broadcast traffic received by the Host Port is trapped and
sent to EthFw like I have mentioned in my reply above.
> software bridge, and do learning. It will see the outgoing ARP request
> from a host and learn the host MAC address. It can then flood the
As I have mentioned earlier, EthFw is not in the path between the CPSW
Host Port and the Hosts. So your suggestion is not applicable.
> packet out the external ports, working around the CSPW brokeness. It
> can also program the ALE, so the reply goes straight to the
> host. Incoming broadcast and multicast is also trapped to the EthFW
> and it can use its software bridge to flood the packet to all the
> hosts. It can also perform IGMP snooping, and learn which hosts are
> interested in Multicast.
>
> Your switch then functions as a switch.
>
> And you are then the same as the RealTek and Samsung device. Linux is
> just a plain boring host connect to a switch, which somebody else is
> managing. No new model needed.
I hope that the illustration above along with my replies would have
clarified that CPSW is *not* broken and works just like any Ethernet
Switch is expected to work. It is only because there is a __single__
Host Port from CPSW that is shared across Hosts, that the EthFw based
model is required. If there were dedicated Host Ports for each Host,
then just like the Broadcast/Multicast traffic is already replicated for
each Port (whether it is a MAC Port or the Host Port), the traffic would
also be replicated in CPSW for each Host Port.
>
> > All data between Linux (Or any Operating System) and EthFw is exchanged
> > via the Hardware Mailboxes with the help of the RPMsg framework. Since
> > all the resource allocation information comes from EthFw, the
> > vendor-specific implementation in the Linux Client is limited to the DMA
> > APIs used to setup the Hardware Queues and to transmit/receive data with
> > the Ethernet Switch. Therefore, it might be possible to move most of the
> > vendor specific implementation to the Switch Configuration Firmware
> > (similar to EthFw), to make the Linux Client implementation as generic
> > and vendor agnostic as possible. I believe that this series more or less
> > does the same, just using custom terminology which can be made generic.
>
> This is actually very similar to what your college is doing:
>
> https://lore.kernel.org/netdev/[email protected]/
>
> The only real difference is shared memory vs DMA.
Yes, the Shared Memory path is intended for the low-bandwidth
Broadcast/Multicast traffic from EthFw while the DMA path is dedicated
for high-bandwidth Unicast traffic. The current series implements the
DMA path while the other series you have referred to implements the
Shared Memory path. Both of them together enable the desired functionality.
Regards,
Siddharth.
> The DMA Channels provide a path to/from CPSW's Host Port for each Host.
>
> Please refer to the following illustration corresponding to the data
> movement from each of the Hosts to the CPSW's Host Port via the ALE and
> then out of the MAC Ports:
>
> ------- --------- --------- CONTROL-PATH
> |Linux| |AUTOSAR| | EthFW | -------------
> ------- --------- --------- |
> | | | | | | |
> DATA TX RX TX RX TX RX |
> PATH => | | | | | | |
> (DMA) | | | | | | |
> | | | | | | |
> \ \ \ \ / / |
> \ \ \ \ / / |
> \ \ \ \ / / |
> \ \ \ \ / / |
> \ \ \ \ / / |
> =============================== |
> || CPSW HOST PORT || V
> =============================== -----------
> | |CPSW |
> TX + RX |CONTROL |
> | |REGISTERS|
> | -----------
> |
> ===================
> ||ALE & SWITCH CORE||
> ===================
> / | | \
> / | | \
> / | | \
> TX+RX | \ -------
> / | \ \
> / TX+RX TX+RX TX+RX
> / | \ \
> ========== ========== ========== ==========
> |MAC Port 1| |MAC Port 2| |MAC Port 3| |MAC Port 4|
> ========== ========== ========== ==========
So, in summary, you have one host port, and on top of that a number of
virtual ports. Because of limitations in the ALE, those virtual ports
don't work in the same way as real ports, replication is not possible,
nor learning for individual virtual ports. The typical 1990 solution
to that would be to flood packets to all hosts, and let them filter
out the packets they are not interested in. 1990 Ethernet was a shared
medium, you expect to see packets for other hosts. But the hardware
also cannot do that.
So you have to program the classify to augment the ALE, and the
classifier is the one that decides which virtual port a packet goes
out. But the classifier does not perform learning. You need additional
mechanisms to program that classifier.
> Host which has registered with that MAC Address "M". This is handled by
> EthFw. EthFw doesn't/cannot snoop on all traffic on the Host Port, since
> it doesn't lie in between the Host Port and the other Hosts. Rather, it
> is quite similar to a Host itself since it also has dedicated TX/RX DMA
> Channels to exchange traffic with CPSW.
I did not say snoop. I said trap. There is a difference. Snoop would
be it sees the packet, as it going by. Trap means it actually gets
passed the packet, and it needs to deal with it, decide the outgoing
port.
So i would trap all broadcast and multicast from the virtual ports to
the EthFW. Let the EthFw deal with that traffic, perform learning, and
programming the classifier, and flood it out user ports for broadcast,
or unicast out specific ports for multicast where IGMP snooping
indicates it should go.
> Since that is not supported, all
> Broadcast/Multicast traffic directed to the Host Port from the Switch Core
> is by default placed on the RX DMA Flow corresponding to EthFw. EthFw then
> creates copies of these in software and shares them with each Host via
> Shared Memory for example.
Why shared memory? EthFw needs to be able to direct packets out
specific virtual ports otherwise it cannot do {R}STP, PTP, IGMP
snooping etc. So it should just pass the packet back to the CPSW,
which will hairpin the packet, hit the classifier, and then send it
out the correct virtual port to the correct host.
> Yes, the Shared Memory path is intended for the low-bandwidth
> Broadcast/Multicast traffic from EthFw while the DMA path is dedicated
> for high-bandwidth Unicast traffic. The current series implements the
> DMA path while the other series you have referred to implements the
> Shared Memory path. Both of them together enable the desired functionality.
So i think we are agreed a new model is not needed. Linux is just a
host connected to a managed switch. Linux has no roll in managing that
switch, and has no idea about the ports of that switch. It is just an
end system, running end system software.
You need a 'MAC' driver in Linux, so Linux sees just a normal network
interface. And it must see a single MAC driver, so if you really do
need to use shared memory in parallel to DMA, you will need to combine
that into one driver.
Andrew