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
---
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
#include
#include
+#include
#include
#include
#include
@@ -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_de