[net-next PATCH v2] ibmvnic: map L2/L3/L4 header descriptors to firmware

Thomas Falcon tlfalcon at linux.vnet.ibm.com
Thu Feb 25 08:55:48 AEDT 2016


From: root <root at ltcalpine2-lp23.aus.stglabs.ibm.com>

Allow the VNIC driver to provide descriptors containing
L2/L3/L4 headers to firmware.  This feature is needed
for greater hardware compatibility and enablement of offloading
technologies for some backing hardware.

Signed-off-by: Thomas Falcon <tlfalcon at linux.vnet.ibm.com>
---
v2: Fixed typo error caught by kbuild test bot
---
 drivers/net/ethernet/ibm/ibmvnic.c | 238 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 235 insertions(+), 3 deletions(-)

diff --git a/drivers/net/ethernet/ibm/ibmvnic.c b/drivers/net/ethernet/ibm/ibmvnic.c
index 7d657084..43c1df6 100644
--- a/drivers/net/ethernet/ibm/ibmvnic.c
+++ b/drivers/net/ethernet/ibm/ibmvnic.c
@@ -61,6 +61,7 @@
 #include <linux/proc_fs.h>
 #include <linux/in.h>
 #include <linux/ip.h>
+#include <linux/ipv6.h>
 #include <linux/irq.h>
 #include <linux/kthread.h>
 #include <linux/seq_file.h>
@@ -94,6 +95,7 @@ static int ibmvnic_reenable_crq_queue(struct ibmvnic_adapter *);
 static int ibmvnic_send_crq(struct ibmvnic_adapter *, union ibmvnic_crq *);
 static int send_subcrq(struct ibmvnic_adapter *adapter, u64 remote_handle,
 		       union sub_crq *sub_crq);
+static int send_subcrq_indirect(struct ibmvnic_adapter *, u64, u64, u64);
 static irqreturn_t ibmvnic_interrupt_rx(int irq, void *instance);
 static int enable_scrq_irq(struct ibmvnic_adapter *,
 			   struct ibmvnic_sub_crq_queue *);
@@ -561,12 +563,177 @@ static int ibmvnic_close(struct net_device *netdev)
 	return 0;
 }
 
+/**
+ * build_hdr_data - creates L2/L3/L4 header data buffer
+ * @hdr_field - bitfield determining needed headers
+ * @skb - socket buffer
+ * @hdr_len - array of header lengths
+ * @tot_len - total length of data
+ *
+ * Reads hdr_field to determine which headers are needed by firmware.
+ * Builds a buffer containing these headers.  Saves individual header
+ * lengths and total buffer length to be used to build descriptors.
+ */
+static unsigned char *build_hdr_data(u8 hdr_field, struct sk_buff *skb,
+				     int *hdr_len, int *tot_len)
+{
+	unsigned char *hdr_data;
+	unsigned char *hdrs[3];
+	u8 proto = 0;
+	int len = 0;
+	int i;
+
+	if ((hdr_field >> 6) & 1) {
+		hdrs[0] = skb_mac_header(skb);
+		hdr_len[0] = sizeof(struct ethhdr);
+	}
+
+	if ((hdr_field >> 5) & 1) {
+		hdrs[1] = skb_network_header(skb);
+		if (skb->protocol == htons(ETH_P_IP))
+			hdr_len[1] = ip_hdr(skb)->ihl * 4;
+		else if (skb->protocol == htons(ETH_P_IPV6))
+			hdr_len[1] = sizeof(struct ipv6hdr);
+	}
+
+	if ((hdr_field >> 4) & 1) {
+		hdrs[2] = skb_transport_header(skb);
+		if (skb->protocol == htons(ETH_P_IP))
+			proto = ip_hdr(skb)->protocol;
+		else if (skb->protocol == htons(ETH_P_IPV6))
+			proto = ipv6_hdr(skb)->nexthdr;
+
+		if (proto == IPPROTO_TCP)
+			hdr_len[2] = tcp_hdrlen(skb);
+		else if (proto == IPPROTO_UDP)
+			hdr_len[2] = sizeof(struct udphdr);
+	}
+
+	*tot_len = hdr_len[0] + hdr_len[1] + hdr_len[2];
+
+	hdr_data = kmalloc(*tot_len, GFP_KERNEL);
+	if (!hdr_data)
+		return NULL;
+
+	for (i = 0; i < 3; i++) {
+		if (hdrs[i])
+			memcpy(hdr_data, hdrs[i] + len, hdr_len[i]);
+		len += hdr_len[i];
+	}
+	return hdr_data;
+}
+
+/**
+ * create_hdr_descs - create header and header extension descriptors
+ * @hdr_field - bitfield determining needed headers
+ * @data - buffer containing header data
+ * @len - length of data buffer
+ * @hdr_len - array of individual header lengths
+ * @scrq_arr - descriptor array
+ *
+ * Creates header and, if needed, header extension descriptors and
+ * places them in a descriptor array, scrq_arr
+ */
+
+void create_hdr_descs(u8 hdr_field, unsigned char *data, int len, int *hdr_len,
+		      union sub_crq *scrq_arr)
+{
+	union sub_crq hdr_desc;
+	int tmp_len = len;
+	int tmp;
+
+	while (tmp_len > 0) {
+		unsigned char *cur = data + len - tmp_len;
+
+		memset(&hdr_desc, 0, sizeof(hdr_desc));
+		if (cur != data) {
+			tmp = tmp_len > 29 ? 29 : tmp_len;
+			hdr_desc.hdr_ext.first = IBMVNIC_CRQ_CMD;
+			hdr_desc.hdr_ext.type = IBMVNIC_HDR_EXT_DESC;
+			hdr_desc.hdr_ext.len = tmp;
+		} else {
+			tmp = tmp_len > 24 ? 24 : tmp_len;
+			hdr_desc.hdr.first = IBMVNIC_CRQ_CMD;
+			hdr_desc.hdr.type = IBMVNIC_HDR_DESC;
+			hdr_desc.hdr.len = tmp;
+			hdr_desc.hdr.l2_len = (u8)hdr_len[0];
+			hdr_desc.hdr.l3_len = (u16)hdr_len[1];
+			hdr_desc.hdr.l4_len = (u8)hdr_len[2];
+			hdr_desc.hdr.flag = hdr_field << 1;
+		}
+		memcpy(hdr_desc.hdr.data, cur, tmp);
+		tmp_len -= tmp;
+		*scrq_arr = hdr_desc;
+		scrq_arr++;
+	}
+}
+
+static int calc_num_hdr_descs(int len)
+{
+	int num_descs = 1;
+
+	len -= 24;
+	if (len > 0)
+		num_descs += len % 29 ? len / 29 + 1 : len / 29;
+	return num_descs;
+}
+
+static union sub_crq *alloc_scrq_array(int num, union sub_crq subcrq)
+{
+	union sub_crq *scrq_arr;
+
+	scrq_arr = kcalloc(num, sizeof(*scrq_arr), GFP_KERNEL);
+	if (!scrq_arr)
+		return NULL;
+	scrq_arr[0] = subcrq;
+	return scrq_arr;
+}
+
+/**
+ * build_hdr_descs_arr - build a header descriptor array
+ * @skb - socket buffer
+ * @num_entries - number of descriptors to be sent
+ * @subcrq - first TX descriptor
+ * @hdr_field - bit field determining which headers will be sent
+ *
+ * This function will build a TX with descriptor array with applicable
+ * L2/L3/L4 packet header descriptors to be sent by send_subcrq_indirect.
+ */
+
+static union sub_crq *build_hdr_descs_arr(struct sk_buff *skb,
+					  int *num_entries,
+					  union sub_crq subcrq, u8 hdr_field)
+{
+	unsigned char *hdr_data;
+	union sub_crq *entries;
+	int hdr_len[3] = {0};
+	int tot_len;
+
+	hdr_data = build_hdr_data(hdr_field, skb, hdr_len, &tot_len);
+	if (!hdr_data)
+		return NULL;
+
+	*num_entries += calc_num_hdr_descs(tot_len);
+
+	entries = alloc_scrq_array(*num_entries, subcrq);
+	if (!entries) {
+		kfree(hdr_data);
+		return NULL;
+	}
+
+	create_hdr_descs(hdr_field, hdr_data, tot_len, hdr_len, entries + 1);
+	kfree(hdr_data);
+	return entries;
+}
+
 static int ibmvnic_xmit(struct sk_buff *skb, struct net_device *netdev)
 {
 	struct ibmvnic_adapter *adapter = netdev_priv(netdev);
 	int queue_num = skb_get_queue_mapping(skb);
+	u8 *hdrs = (u8 *)&adapter->tx_rx_desc_req;
 	struct device *dev = &adapter->vdev->dev;
 	struct ibmvnic_tx_buff *tx_buff = NULL;
+	union sub_crq *indir_arr = NULL;
 	struct ibmvnic_tx_pool *tx_pool;
 	unsigned int tx_send_failed = 0;
 	unsigned int tx_map_failed = 0;
@@ -576,9 +743,11 @@ static int ibmvnic_xmit(struct sk_buff *skb, struct net_device *netdev)
 	dma_addr_t data_dma_addr;
 	struct netdev_queue *txq;
 	bool used_bounce = false;
+	dma_addr_t indir_ioba;
 	unsigned long lpar_rc;
 	union sub_crq tx_crq;
 	unsigned int offset;
+	int num_entries = 1;
 	unsigned char *dst;
 	u64 *handle_array;
 	int index = 0;
@@ -644,10 +813,47 @@ static int ibmvnic_xmit(struct sk_buff *skb, struct net_device *netdev)
 			tx_crq.v1.flags1 |= IBMVNIC_TX_PROT_UDP;
 	}
 
-	if (skb->ip_summed == CHECKSUM_PARTIAL)
+	if (skb->ip_summed == CHECKSUM_PARTIAL) {
 		tx_crq.v1.flags1 |= IBMVNIC_TX_CHKSUM_OFFLOAD;
+		hdrs += 2;
+	}
+
+	/* determine if l2/3/4 headers are sent to firmware */
+	if ((*hdrs >> 7) & 1 &&
+	    (skb->protocol == htons(ETH_P_IP) ||
+	     skb->protocol == htons(ETH_P_IPV6))) {
+		indir_arr = build_hdr_descs_arr(skb,
+						&num_entries,
+						tx_crq, *hdrs);
+		if (!indir_arr) {
+			dev_err(dev, "tx: unable to create descriptor array\n");
+			tx_send_failed++;
+			tx_dropped++;
+			ret = NETDEV_TX_BUSY;
+			goto out;
+		}
+		tx_crq.v1.n_crq_elem = num_entries;
+		indir_ioba = dma_map_single(dev, indir_arr,
+					    num_entries * sizeof(*indir_arr),
+					    DMA_TO_DEVICE);
+		if (dma_mapping_error(dev, indir_ioba)) {
+			if (!firmware_has_feature(FW_FEATURE_CMO))
+				dev_err(dev, "tx: unable to map descriptor array\n");
+			tx_map_failed++;
+			tx_dropped++;
+			ret = NETDEV_TX_BUSY;
+			goto out;
+		}
 
-	lpar_rc = send_subcrq(adapter, handle_array[0], &tx_crq);
+		lpar_rc = send_subcrq_indirect(adapter, handle_array[0],
+					       (u64)indir_ioba,
+					       (u64)num_entries);
+		dma_unmap_single(dev, indir_ioba,
+				 num_entries * sizeof(*indir_arr),
+				 DMA_TO_DEVICE);
+	} else {
+		lpar_rc = send_subcrq(adapter, handle_array[0], &tx_crq);
+	}
 
 	if (lpar_rc != H_SUCCESS) {
 		dev_err(dev, "tx failed with code %ld\n", lpar_rc);
@@ -674,6 +880,7 @@ out:
 	netdev->stats.tx_packets += tx_packets;
 	adapter->tx_send_failed += tx_send_failed;
 	adapter->tx_map_failed += tx_map_failed;
+	kfree(indir_arr);
 
 	return ret;
 }
@@ -1494,6 +1701,30 @@ static int send_subcrq(struct ibmvnic_adapter *adapter, u64 remote_handle,
 	return rc;
 }
 
+static int send_subcrq_indirect(struct ibmvnic_adapter *adapter,
+				u64 remote_handle, u64 ioba, u64 num_entries)
+{
+	unsigned int ua = adapter->vdev->unit_address;
+	struct device *dev = &adapter->vdev->dev;
+	int rc;
+
+	/* Make sure the hypervisor sees the complete request */
+	mb();
+
+	rc = plpar_hcall_norets(H_SEND_SUB_CRQ_INDIRECT, ua,
+				cpu_to_be64(remote_handle),
+				cpu_to_be64(ioba),
+				cpu_to_be64(num_entries));
+
+	if (rc) {
+		if (rc == H_CLOSED)
+			dev_warn(dev, "CRQ Queue closed\n");
+		dev_err(dev, "Send (indirect) error (rc=%d)\n", rc);
+	}
+
+	return rc;
+}
+
 static int ibmvnic_send_crq(struct ibmvnic_adapter *adapter,
 			    union ibmvnic_crq *crq)
 {
@@ -2447,7 +2678,8 @@ static void handle_query_cap_rsp(union ibmvnic_crq *crq,
 			   adapter->opt_rxba_entries_per_subcrq);
 		break;
 	case TX_RX_DESC_REQ:
-		adapter->tx_rx_desc_req = crq->query_capability.number;
+		adapter->tx_rx_desc_req =
+				be64_to_cpu(crq->query_capability.number);
 		netdev_dbg(netdev, "tx_rx_desc_req = %llx\n",
 			   adapter->tx_rx_desc_req);
 		break;
-- 
1.8.3.1



More information about the Linuxppc-dev mailing list