diff mbox series

[SRU,Focal:linux-intel-iotg-5.15,1/3] net: Add support for Intel vnic driver

Message ID 20220509090346.38740-2-jianhui.lee@canonical.com
State New
Headers show
Series EHL Intel ishtp VNIC driver support | expand

Commit Message

Jian Hui Lee May 9, 2022, 9:03 a.m. UTC
From: Nachiketa Kumar <nachiketa.kumar@intel.com>

BugLink: https://bugs.launchpad.net/bugs/1943524

This driver enables a virtual network interface
for communicating with a dedicated low power
chip over ISHTP

v1:
   - Initial version
	---

Signed-off-by: Nachiketa Kumar <nachiketa.kumar@intel.com>
Signed-off-by: Anthony Wong <anthony.wong@canonical.com>
Signed-off-by: Wen-chien Jesse Sung <jesse.sung@canonical.com>
(cherry picked from commit 11d471cbed79cb98bf9108deddacff74435b2c1c linux-intel-5.13)
Signed-off-by: Jian Hui Lee <jianhui.lee@canonical.com>
---
 drivers/net/Kconfig          |   1 +
 drivers/net/Makefile         |   1 +
 drivers/net/vnic/Kconfig     |   7 +
 drivers/net/vnic/Makefile    |   1 +
 drivers/net/vnic/eth-ishtp.c | 499 +++++++++++++++++++++++++++++++++++
 5 files changed, 509 insertions(+)
 create mode 100644 drivers/net/vnic/Kconfig
 create mode 100644 drivers/net/vnic/Makefile
 create mode 100644 drivers/net/vnic/eth-ishtp.c
diff mbox series

Patch

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index dd335ae1122b..50b9c11c9fff 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -613,4 +613,5 @@  config NETDEV_LEGACY_INIT
 	  Drivers that call netdev_boot_setup_check() should select this
 	  symbol, everything else no longer needs it.
 
+source "drivers/net/vnic/Kconfig"
 endif # NETDEVICES
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 739838623cf6..3ff439e99883 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -72,6 +72,7 @@  obj-$(CONFIG_IEEE802154) += ieee802154/
 obj-$(CONFIG_WWAN) += wwan/
 obj-$(CONFIG_MCTP) += mctp/
 
+obj-$(CONFIG_INTEL_ISHTP_VNIC) += vnic/
 obj-$(CONFIG_VMXNET3) += vmxnet3/
 obj-$(CONFIG_XEN_NETDEV_FRONTEND) += xen-netfront.o
 obj-$(CONFIG_XEN_NETDEV_BACKEND) += xen-netback/
diff --git a/drivers/net/vnic/Kconfig b/drivers/net/vnic/Kconfig
new file mode 100644
index 000000000000..55ca8dad28c0
--- /dev/null
+++ b/drivers/net/vnic/Kconfig
@@ -0,0 +1,7 @@ 
+config INTEL_ISHTP_VNIC
+	tristate "Intel ISHTP VNIC controller"
+	depends on INTEL_ISH_HID
+	depends on NET
+	help
+	  If you say Y here, you get support for a virtual newtwork device
+	  on a dedicated low power chip over ISH Transport Protocol (ISHTP).
diff --git a/drivers/net/vnic/Makefile b/drivers/net/vnic/Makefile
new file mode 100644
index 000000000000..a5ad3b3fe7af
--- /dev/null
+++ b/drivers/net/vnic/Makefile
@@ -0,0 +1 @@ 
+obj-$(CONFIG_INTEL_ISHTP_VNIC) += eth-ishtp.o
diff --git a/drivers/net/vnic/eth-ishtp.c b/drivers/net/vnic/eth-ishtp.c
new file mode 100644
index 000000000000..9b4df45f423e
--- /dev/null
+++ b/drivers/net/vnic/eth-ishtp.c
@@ -0,0 +1,499 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/* Intel virtual Network Interface Card driver (vNIC driver) for
+ * ethernet communication over  Intel Integrated Sensor Hub
+ * (ISH) using ISH Trasport protocol (ISHTP)
+ *
+ * Copyright (c) 2019, Intel Corporation.
+ */
+
+#include <linux/etherdevice.h>
+#include <linux/if_arp.h>
+#include <linux/intel-ish-client-if.h>
+#include <linux/module.h>
+#include <linux/uuid.h>
+
+#define ETH_ISHTP_DRV_NAME		"eth_ishtp"
+#define ETH_ISHTP_DRV_DESC		"Intel Ethernet ISHTP Driver"
+#define ETH_ISHTP_TX_QUE_LEN		100
+#define WAIT_FOR_SEND_SLICE_MS		100
+#define WAIT_FOR_SEND_COUNT		10
+#define ETH_ISHTP_CL_RX_RING_SIZE	4
+#define ETH_ISHTP_CL_TX_RING_SIZE	4
+#define ETH_ISHTP_DATA_BUFLEN		984
+#define ETH_ISHTP_RX_MAX_LEN		984
+#define ETH_ISHTP_TX_MAX_LEN		984
+#define ETH_ISHTP_TX_MIN_LEN            14
+
+/* Default timeout period */
+#define ETH_ISHTP_TIMEOUT	500 /* In jiffies */
+#define ETH_ISHTP_MIN_MTU (ETH_ZLEN - ETH_HLEN)
+#define ETH_ISHTP_MAX_MTU  ETH_ISHTP_DATA_BUFLEN
+
+static const guid_t eth_ishtp_uuid =
+	GUID_INIT(0xeb83e1fb, 0x4c61, 0x4829,
+		  0x98, 0x4c, 0x3, 0x23, 0xab, 0x4b, 0x41, 0x65);
+
+struct eth_ishtp_prv {
+	struct device *dev;
+	struct net_device *net_dev;
+	struct net_device_stats stats;
+	unsigned char *tx_buff;
+	int tx_len;
+	struct ishtp_cl *eth_ishtp_cl;
+	struct ishtp_cl_device *cl_device;
+	struct ishtp_fw_client *fw_client;
+	struct ishtp_cl_rb *rb;
+	struct work_struct reset_work;
+};
+
+static int eth_ishtp_cl_write(struct eth_ishtp_prv *prv, int *written)
+{
+	struct ishtp_cl *cl = prv->eth_ishtp_cl;
+	unsigned char *buf = prv->tx_buff;
+	int len = prv->tx_len;
+	int ret;
+
+	ret = ishtp_cl_send(cl, buf, len);
+	if (ret)
+		goto err_exit;
+
+	*written = len;
+	return 0;
+
+err_exit:
+	*written = 0;
+	dev_err(prv->dev, "[WRITE] Data send error: %d\n", ret);
+	return ret;
+}
+
+static void eth_ishtp_rx(struct net_device *dev)
+{
+	struct eth_ishtp_prv *prv = netdev_priv(dev);
+	struct ishtp_cl_rb *rb = prv->rb;
+	struct sk_buff *skb;
+
+	/* The packet has been retrieved from the transmission
+	 * medium. Build an skb around it, so upper layers can handle it
+	 */
+	skb = netdev_alloc_skb_ip_align(dev, rb->buffer.size);
+	if (!skb) {
+		dev_err(prv->dev, "eth_ishtp rx: packet dropped\n");
+		dev->stats.rx_dropped++;
+		return;
+	}
+
+	skb_put(skb, rb->buffer.size);
+	skb_copy_to_linear_data(skb, rb->buffer.data, rb->buffer.size);
+
+	/* Write metadata, and then pass to the receive level */
+	skb->protocol = eth_type_trans(skb, dev);
+	skb->ip_summed = CHECKSUM_NONE;
+	dev->stats.rx_packets++;
+	dev->stats.rx_bytes += rb->buffer.size;
+	netif_rx(skb);
+}
+
+static void eth_ishtp_cl_event_cb(struct ishtp_cl_device *cl_device)
+{
+	struct eth_ishtp_prv *prv = ishtp_get_drvdata(cl_device);
+	struct net_device *net_dev = prv->net_dev;
+	struct ishtp_cl_rb *rb;
+
+	while ((rb = ishtp_cl_rx_get_rb(prv->eth_ishtp_cl)) != NULL) {
+		prv->rb = rb;
+		eth_ishtp_rx(net_dev);
+		prv->rb = NULL;
+
+		ishtp_cl_io_rb_recycle(rb);
+	}
+
+	if (ishtp_cl_tx_empty(prv->eth_ishtp_cl) && netif_queue_stopped(prv->net_dev))
+		netif_wake_queue(prv->net_dev);
+}
+
+static int eth_ishtp_open(struct net_device *dev)
+{
+	netif_carrier_on(dev);
+	netif_start_queue(dev);
+	return 0;
+}
+
+static int eth_ishtp_stop(struct net_device *dev)
+{
+	netif_carrier_off(dev);
+	netif_stop_queue(dev);
+	return 0;
+}
+
+ /* Configuration changes by ifconfig */
+static int eth_ishtp_config(struct net_device *dev, struct ifmap *map)
+{
+	if (dev->flags & IFF_UP) /* can't act on a running interface */
+		return -EBUSY;
+
+	return 0;
+}
+
+/* Transmit a packet (called by the kernel) */
+static int eth_ishtp_xmit(struct sk_buff *skb, struct net_device *dev)
+{
+	struct eth_ishtp_prv *prv = netdev_priv(dev);
+	int written;
+	int status;
+
+	if (skb->len < ETH_ISHTP_TX_MIN_LEN || skb->len > ETH_ISHTP_TX_MAX_LEN) {
+		dev_kfree_skb_any(skb);
+		return NETDEV_TX_OK;
+	}
+
+	if (!netif_running(dev))  {
+		dev_info(prv->dev, "xmit: iface is down\n");
+		dev_kfree_skb_any(skb);
+		netif_stop_queue(dev);
+		return NETDEV_TX_OK;
+	}
+
+	prv->tx_len = skb->len;
+	prv->tx_buff = skb->data;
+	status = eth_ishtp_cl_write(prv, &written);
+
+	/* Tx failed */
+	if (!status) {
+		dev_err(prv->dev, "xmit: ishtp_cl_write failed\n");
+		netif_stop_queue(dev);
+		return NETDEV_TX_BUSY;
+	}
+	dev_kfree_skb_any(skb);
+	return NETDEV_TX_OK;
+}
+
+static void eth_ishtp_tx_timeout(struct net_device *dev, unsigned int tx_queue)
+{
+	struct eth_ishtp_prv *prv = netdev_priv(dev);
+
+	dev_err(prv->dev, "eth_ishtp tx timeout\n");
+	dev_err(prv->dev, "Transmit timeout at %ld, latency %ld\n",
+		jiffies, dev_trans_start(dev));
+
+	dev->stats.tx_errors++;
+	dev->stats.tx_dropped++;
+
+	netif_wake_queue(dev);
+}
+
+static int eth_ishtp_dev_init(struct net_device *dev)
+{
+	struct eth_ishtp_prv *prv = netdev_priv(dev);
+	struct ishtp_fw_client *fw_client;
+	struct ishtp_device *ishtp_dev;
+	struct ishtp_cl *eth_ishtp_cl;
+	int ret, ishtp_fw_client_id;
+
+	dev->mtu = ETH_ISHTP_MAX_MTU;
+	dev->type = ARPHRD_ETHER;
+
+	eth_ishtp_cl = ishtp_cl_allocate(prv->cl_device);
+	if (!eth_ishtp_cl) {
+		dev_err(prv->dev, "ishtp cl allocate failed\n");
+		ret = -ENOMEM;
+		return ret;
+	}
+
+	prv->eth_ishtp_cl = eth_ishtp_cl;
+
+	ret = ishtp_cl_link(eth_ishtp_cl);
+	if (ret) {
+		dev_err(prv->dev, "ishtp_cl_link failed\n");
+		ret = -ENOENT;
+		goto err_free_cl;
+	}
+
+	if (!ishtp_get_ishtp_device(eth_ishtp_cl)) {
+		dev_err(prv->dev, "ishtp_get_ishtp_device failed");
+		ret = -ENODEV;
+		goto err_cl_unlink;
+	}
+
+	/* Connect to FW client */
+	ishtp_set_tx_ring_size(eth_ishtp_cl, ETH_ISHTP_CL_TX_RING_SIZE);
+	ishtp_set_rx_ring_size(eth_ishtp_cl, ETH_ISHTP_CL_RX_RING_SIZE);
+
+	ishtp_dev = ishtp_get_ishtp_device(eth_ishtp_cl);
+	fw_client = ishtp_fw_cl_get_client(ishtp_dev, &eth_ishtp_uuid);
+	if (!fw_client) {
+		dev_err(prv->dev, "FW client not found\n");
+		ret = -ENOENT;
+		goto err_cl_unlink;
+	}
+
+	ishtp_fw_client_id = ishtp_get_fw_client_id(fw_client);
+	ishtp_cl_set_fw_client_id(eth_ishtp_cl, ishtp_fw_client_id);
+	ishtp_set_connection_state(eth_ishtp_cl, ISHTP_CL_CONNECTING);
+
+	ret = ishtp_register_event_cb(prv->cl_device, eth_ishtp_cl_event_cb);
+	if (ret) {
+		dev_err(ishtp_device(prv->cl_device),
+			"ishtp register callback failed\n");
+		free_netdev(prv->net_dev);
+		goto err_cl_unlink;
+	}
+
+	ret = ishtp_cl_connect(eth_ishtp_cl);
+	if (ret) {
+		dev_err(prv->dev, "Client connect failed\n");
+		goto err_cl_unlink;
+	}
+
+	dev_info(prv->dev, "Connected to fw client = %d\n", ishtp_fw_client_id);
+
+	return 0;
+
+err_cl_unlink:
+	ishtp_cl_unlink(eth_ishtp_cl);
+
+err_free_cl:
+	ishtp_put_device(prv->cl_device);
+	ishtp_cl_free(eth_ishtp_cl);
+	prv->eth_ishtp_cl = NULL;
+	return ret;
+}
+
+static void eth_ishtp_dev_uninit(struct net_device *dev)
+{
+	struct eth_ishtp_prv *prv = netdev_priv(dev);
+	int try = WAIT_FOR_SEND_COUNT;
+	struct ishtp_cl_rb *rb;
+	struct ishtp_cl *cl;
+	int ret;
+
+	cancel_work_sync(&prv->reset_work);
+
+	cl = prv->eth_ishtp_cl;
+	if (cl) {
+		do {
+			if (ishtp_cl_tx_empty(cl))
+				break;
+
+			msleep_interruptible(WAIT_FOR_SEND_SLICE_MS);
+		} while (--try);
+
+		ishtp_set_connection_state(cl, ISHTP_CL_DISCONNECTING);
+		ret = ishtp_cl_disconnect(cl);
+		ishtp_cl_unlink(cl);
+		ishtp_cl_flush_queues(cl);
+		ishtp_cl_free(cl);
+
+		prv->eth_ishtp_cl = NULL;
+	}
+
+	rb = prv->rb;
+	if (rb) {
+		ishtp_cl_io_rb_recycle(rb);
+		prv->rb = NULL;
+	}
+}
+
+static const struct net_device_ops eth_ishtp_netdev_ops = {
+	.ndo_init            = eth_ishtp_dev_init,
+	.ndo_uninit          = eth_ishtp_dev_uninit,
+	.ndo_open            = eth_ishtp_open,
+	.ndo_stop            = eth_ishtp_stop,
+	.ndo_start_xmit      = eth_ishtp_xmit,
+	.ndo_set_mac_address = eth_mac_addr,
+	.ndo_set_config      = eth_ishtp_config,
+	.ndo_tx_timeout      = eth_ishtp_tx_timeout,
+	.ndo_validate_addr   = eth_validate_addr
+};
+
+static void eth_ishtp_setup(struct net_device *dev)
+{
+	struct eth_ishtp_prv *prv;
+
+	ether_setup(dev);
+	dev->watchdog_timeo = ETH_ISHTP_TIMEOUT;
+	dev->netdev_ops = &eth_ishtp_netdev_ops;
+	dev->flags           |= IFF_NOARP;
+	dev->flags           &= ~(IFF_MULTICAST);
+	dev->priv_flags &= ~IFF_TX_SKB_SHARING;
+	dev->tx_queue_len = ETH_ISHTP_TX_QUE_LEN;
+	dev->min_mtu = ETH_ISHTP_MIN_MTU;
+	dev->max_mtu = ETH_ISHTP_MAX_MTU;
+
+	prv = netdev_priv(dev);
+}
+
+static void eth_ishtp_reset(struct work_struct *work)
+{
+	struct ishtp_cl_device *cl_device;
+	struct ishtp_fw_client *fw_client;
+	struct ishtp_device *ishtp_dev;
+	struct ishtp_cl *eth_ishtp_cl;
+	struct eth_ishtp_prv *prv;
+	int ret, fw_client_id;
+
+	prv = container_of(work, struct eth_ishtp_prv, reset_work);
+
+	cl_device = prv->cl_device;
+	eth_ishtp_cl = prv->eth_ishtp_cl;
+
+	if (!ishtp_get_ishtp_device(eth_ishtp_cl)) {
+		dev_err(ishtp_device(prv->cl_device),
+			"Allocate ishtp_cl failed\n");
+		return;
+	}
+
+	if (eth_ishtp_cl) {
+		ishtp_cl_unlink(eth_ishtp_cl);
+		ishtp_cl_flush_queues(eth_ishtp_cl);
+		ishtp_cl_free(eth_ishtp_cl);
+		eth_ishtp_cl = NULL;
+		eth_ishtp_cl = ishtp_cl_allocate(cl_device);
+		if (!eth_ishtp_cl) {
+			dev_err(ishtp_device(prv->cl_device),
+				"Allocate ishtp_cl failed\n");
+			return;
+		}
+
+		prv->eth_ishtp_cl = eth_ishtp_cl;
+		ret = ishtp_cl_link(eth_ishtp_cl);
+		if (ret) {
+			dev_err(ishtp_device(prv->cl_device),
+				"Can not link to ishtp\n");
+			goto out_free;
+		}
+
+		ishtp_dev = ishtp_get_ishtp_device(prv->eth_ishtp_cl);
+
+		/* Connect to FW client */
+		ishtp_set_tx_ring_size(eth_ishtp_cl, ETH_ISHTP_CL_TX_RING_SIZE);
+		ishtp_set_rx_ring_size(eth_ishtp_cl, ETH_ISHTP_CL_RX_RING_SIZE);
+
+		fw_client = ishtp_fw_cl_get_client(ishtp_dev, &eth_ishtp_uuid);
+		if (!fw_client) {
+			dev_err(ishtp_device(prv->cl_device),
+				"Don't find related fw client\n");
+			ret = -ENOENT;
+			goto out_unlink_free;
+		}
+
+		fw_client_id = ishtp_get_fw_client_id(fw_client);
+		ishtp_cl_set_fw_client_id(eth_ishtp_cl, fw_client_id);
+		ishtp_set_connection_state(eth_ishtp_cl, ISHTP_CL_CONNECTING);
+
+		ret = ishtp_cl_connect(eth_ishtp_cl);
+		if (ret) {
+			dev_err(ishtp_device(prv->cl_device),
+				"Connect to fw failed %d\n", ret);
+			goto out_unlink_free;
+		}
+
+		prv->eth_ishtp_cl = eth_ishtp_cl;
+	}
+
+	/* After reset, must register event callback again */
+	ishtp_register_event_cb(cl_device, eth_ishtp_cl_event_cb);
+
+	return;
+
+out_unlink_free:
+	ishtp_cl_unlink(eth_ishtp_cl);
+
+out_free:
+	ishtp_cl_free(eth_ishtp_cl);
+	prv->eth_ishtp_cl = NULL;
+	dev_err(ishtp_device(prv->cl_device), "Reset failed\n");
+}
+
+static int eth_ishtp_cl_reset(struct ishtp_cl_device *cl_device)
+{
+	struct eth_ishtp_prv *prv;
+
+	prv = (struct eth_ishtp_prv *)ishtp_get_drvdata(cl_device);
+	if (!prv) {
+		dev_err(ishtp_device(prv->cl_device),
+			"Client driver not ready yet\n");
+		return -ENODEV;
+	}
+	schedule_work(&prv->reset_work);
+	return 0;
+}
+
+static int eth_ishtp_cl_remove(struct ishtp_cl_device *cl_device)
+{
+	struct eth_ishtp_prv *prv;
+
+	prv = ishtp_get_drvdata(cl_device);
+	if (!prv)
+		return -ENODEV;
+
+	unregister_netdev(prv->net_dev);
+	free_netdev(prv->net_dev);
+	ishtp_put_device(cl_device);
+	return 0;
+}
+
+static int eth_ishtp_cl_probe(struct ishtp_cl_device *cl_device)
+{
+	struct eth_ishtp_prv *eth_ishtp_prv;
+	struct net_device *net_dev;
+	int ret;
+
+	/* Allocate the devices */
+	net_dev = alloc_netdev(sizeof(struct eth_ishtp_prv), "ehi%d",
+			       NET_NAME_UNKNOWN, eth_ishtp_setup);
+	if (!net_dev) {
+		ret = -ENOMEM;
+		return ret;
+	}
+
+	eth_ishtp_prv = netdev_priv(net_dev);
+	eth_ishtp_prv->net_dev = net_dev;
+	eth_ishtp_prv->dev = &net_dev->dev;
+	eth_ishtp_prv->cl_device = cl_device;
+
+	ishtp_set_drvdata(cl_device, eth_ishtp_prv);
+	ishtp_get_device(cl_device);
+
+	eth_hw_addr_random(eth_ishtp_prv->net_dev);
+	netdev_info(net_dev, "Using random MAC address: %p\n",
+		    eth_ishtp_prv->net_dev->dev_addr);
+
+	INIT_WORK(&eth_ishtp_prv->reset_work, eth_ishtp_reset);
+
+	ret = register_netdev(net_dev);
+	if (ret) {
+		netdev_err(net_dev, "Unable to register net device");
+		free_netdev(net_dev);
+		return ret;
+	}
+
+	return ret;
+}
+
+static struct ishtp_cl_driver eth_ishtp_cl_driver = {
+	.name = ETH_ISHTP_DRV_NAME,
+	.guid = &eth_ishtp_uuid,
+	.probe = eth_ishtp_cl_probe,
+	.remove = eth_ishtp_cl_remove,
+	.reset = eth_ishtp_cl_reset,
+};
+
+static int __init eth_ishtp_init(void)
+{
+	return ishtp_cl_driver_register(&eth_ishtp_cl_driver, THIS_MODULE);
+}
+
+static void __exit eth_ishtp_exit(void)
+{
+	return ishtp_cl_driver_unregister(&eth_ishtp_cl_driver);
+}
+
+module_init(eth_ishtp_init);
+module_exit(eth_ishtp_exit);
+
+MODULE_DESCRIPTION(ETH_ISHTP_DRV_DESC);
+
+MODULE_AUTHOR("Nachiketa Kumar <nachiketa.kumar@intel.com>");
+MODULE_AUTHOR("Sing Nallasellan <singaravelan.nallasellan@intel.com>");
+
+MODULE_LICENSE("GPL");