qed: Add IP services APIs support
authorNikolay Assa <nassa@marvell.com>
Wed, 2 Jun 2021 17:16:55 +0000 (20:16 +0300)
committerDavid S. Miller <davem@davemloft.net>
Thu, 3 Jun 2021 21:04:18 +0000 (14:04 -0700)
This patch introduces APIs which the NVMeTCP Offload device (qedn)
will use through the paired net-device (qede).
It includes APIs for:
- ipv4/ipv6 routing
- get VLAN from net-device
- TCP ports reservation

Acked-by: Igor Russkikh <irusskikh@marvell.com>
Signed-off-by: Nikolay Assa <nassa@marvell.com>
Signed-off-by: Prabhakar Kushwaha <pkushwaha@marvell.com>
Signed-off-by: Omkar Kulkarni <okulkarni@marvell.com>
Signed-off-by: Michal Kalderon <mkalderon@marvell.com>
Signed-off-by: Ariel Elior <aelior@marvell.com>
Signed-off-by: Shai Malin <smalin@marvell.com>
Reviewed-by: Hannes Reinecke <hare@suse.de>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/ethernet/qlogic/qed/qed_nvmetcp_ip_services.c [new file with mode: 0644]
include/linux/qed/qed_nvmetcp_ip_services_if.h [new file with mode: 0644]

diff --git a/drivers/net/ethernet/qlogic/qed/qed_nvmetcp_ip_services.c b/drivers/net/ethernet/qlogic/qed/qed_nvmetcp_ip_services.c
new file mode 100644 (file)
index 0000000..96a2077
--- /dev/null
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
+/*
+ * Copyright 2021 Marvell. All rights reserved.
+ */
+
+#include <linux/types.h>
+#include <asm/byteorder.h>
+#include <asm/param.h>
+#include <linux/delay.h>
+#include <linux/pci.h>
+#include <linux/dma-mapping.h>
+#include <linux/etherdevice.h>
+#include <linux/kernel.h>
+#include <linux/stddef.h>
+#include <linux/errno.h>
+
+#include <net/tcp.h>
+
+#include <linux/qed/qed_nvmetcp_ip_services_if.h>
+
+#define QED_IP_RESOL_TIMEOUT  4
+
+int qed_route_ipv4(struct sockaddr_storage *local_addr,
+                  struct sockaddr_storage *remote_addr,
+                  struct sockaddr *hardware_address,
+                  struct net_device **ndev)
+{
+       struct neighbour *neigh = NULL;
+       __be32 *loc_ip, *rem_ip;
+       struct rtable *rt;
+       int rc = -ENXIO;
+       int retry;
+
+       loc_ip = &((struct sockaddr_in *)local_addr)->sin_addr.s_addr;
+       rem_ip = &((struct sockaddr_in *)remote_addr)->sin_addr.s_addr;
+       *ndev = NULL;
+       rt = ip_route_output(&init_net, *rem_ip, *loc_ip, 0/*tos*/, 0/*oif*/);
+       if (IS_ERR(rt)) {
+               pr_err("lookup route failed\n");
+               rc = PTR_ERR(rt);
+               goto return_err;
+       }
+
+       neigh = dst_neigh_lookup(&rt->dst, rem_ip);
+       if (!neigh) {
+               rc = -ENOMEM;
+               ip_rt_put(rt);
+               goto return_err;
+       }
+
+       *ndev = rt->dst.dev;
+       ip_rt_put(rt);
+
+       /* If not resolved, kick-off state machine towards resolution */
+       if (!(neigh->nud_state & NUD_VALID))
+               neigh_event_send(neigh, NULL);
+
+       /* query neighbor until resolved or timeout */
+       retry = QED_IP_RESOL_TIMEOUT;
+       while (!(neigh->nud_state & NUD_VALID) && retry > 0) {
+               msleep(1000);
+               retry--;
+       }
+
+       if (neigh->nud_state & NUD_VALID) {
+               /* copy resolved MAC address */
+               neigh_ha_snapshot(hardware_address->sa_data, neigh, *ndev);
+               hardware_address->sa_family = (*ndev)->type;
+               rc = 0;
+       }
+
+       neigh_release(neigh);
+       if (!(*loc_ip)) {
+               *loc_ip = inet_select_addr(*ndev, *rem_ip, RT_SCOPE_UNIVERSE);
+               local_addr->ss_family = AF_INET;
+       }
+
+return_err:
+
+       return rc;
+}
+EXPORT_SYMBOL(qed_route_ipv4);
+
+int qed_route_ipv6(struct sockaddr_storage *local_addr,
+                  struct sockaddr_storage *remote_addr,
+                  struct sockaddr *hardware_address,
+                  struct net_device **ndev)
+{
+       struct neighbour *neigh = NULL;
+       struct dst_entry *dst;
+       struct flowi6 fl6;
+       int rc = -ENXIO;
+       int retry;
+
+       memset(&fl6, 0, sizeof(fl6));
+       fl6.saddr = ((struct sockaddr_in6 *)local_addr)->sin6_addr;
+       fl6.daddr = ((struct sockaddr_in6 *)remote_addr)->sin6_addr;
+       dst = ip6_route_output(&init_net, NULL, &fl6);
+       if (!dst || dst->error) {
+               if (dst) {
+                       dst_release(dst);
+                       pr_err("lookup route failed %d\n", dst->error);
+               }
+
+               goto out;
+       }
+
+       neigh = dst_neigh_lookup(dst, &fl6.daddr);
+       if (neigh) {
+               *ndev = ip6_dst_idev(dst)->dev;
+
+               /* If not resolved, kick-off state machine towards resolution */
+               if (!(neigh->nud_state & NUD_VALID))
+                       neigh_event_send(neigh, NULL);
+
+               /* query neighbor until resolved or timeout */
+               retry = QED_IP_RESOL_TIMEOUT;
+               while (!(neigh->nud_state & NUD_VALID) && retry > 0) {
+                       msleep(1000);
+                       retry--;
+               }
+
+               if (neigh->nud_state & NUD_VALID) {
+                       neigh_ha_snapshot((u8 *)hardware_address->sa_data,
+                                         neigh, *ndev);
+                       hardware_address->sa_family = (*ndev)->type;
+                       rc = 0;
+               }
+
+               neigh_release(neigh);
+
+               if (ipv6_addr_any(&fl6.saddr)) {
+                       if (ipv6_dev_get_saddr(dev_net(*ndev), *ndev,
+                                              &fl6.daddr, 0, &fl6.saddr)) {
+                               pr_err("Unable to find source IP address\n");
+                               goto out;
+                       }
+
+                       local_addr->ss_family = AF_INET6;
+                       ((struct sockaddr_in6 *)local_addr)->sin6_addr =
+                                                               fl6.saddr;
+               }
+       }
+
+       dst_release(dst);
+
+out:
+
+       return rc;
+}
+EXPORT_SYMBOL(qed_route_ipv6);
+
+void qed_vlan_get_ndev(struct net_device **ndev, u16 *vlan_id)
+{
+       if (is_vlan_dev(*ndev)) {
+               *vlan_id = vlan_dev_vlan_id(*ndev);
+               *ndev = vlan_dev_real_dev(*ndev);
+       }
+}
+EXPORT_SYMBOL(qed_vlan_get_ndev);
+
+struct pci_dev *qed_validate_ndev(struct net_device *ndev)
+{
+       struct pci_dev *pdev = NULL;
+       struct net_device *upper;
+
+       for_each_pci_dev(pdev) {
+               if (pdev && pdev->driver &&
+                   !strcmp(pdev->driver->name, "qede")) {
+                       upper = pci_get_drvdata(pdev);
+                       if (upper->ifindex == ndev->ifindex)
+                               return pdev;
+               }
+       }
+
+       return NULL;
+}
+EXPORT_SYMBOL(qed_validate_ndev);
+
+__be16 qed_get_in_port(struct sockaddr_storage *sa)
+{
+       return sa->ss_family == AF_INET
+               ? ((struct sockaddr_in *)sa)->sin_port
+               : ((struct sockaddr_in6 *)sa)->sin6_port;
+}
+EXPORT_SYMBOL(qed_get_in_port);
+
+int qed_fetch_tcp_port(struct sockaddr_storage local_ip_addr,
+                      struct socket **sock, u16 *port)
+{
+       struct sockaddr_storage sa;
+       int rc = 0;
+
+       rc = sock_create(local_ip_addr.ss_family, SOCK_STREAM, IPPROTO_TCP,
+                        sock);
+       if (rc) {
+               pr_warn("failed to create socket: %d\n", rc);
+               goto err;
+       }
+
+       (*sock)->sk->sk_allocation = GFP_KERNEL;
+       sk_set_memalloc((*sock)->sk);
+
+       rc = kernel_bind(*sock, (struct sockaddr *)&local_ip_addr,
+                        sizeof(local_ip_addr));
+
+       if (rc) {
+               pr_warn("failed to bind socket: %d\n", rc);
+               goto err_sock;
+       }
+
+       rc = kernel_getsockname(*sock, (struct sockaddr *)&sa);
+       if (rc < 0) {
+               pr_warn("getsockname() failed: %d\n", rc);
+               goto err_sock;
+       }
+
+       *port = ntohs(qed_get_in_port(&sa));
+
+       return 0;
+
+err_sock:
+       sock_release(*sock);
+       sock = NULL;
+err:
+
+       return rc;
+}
+EXPORT_SYMBOL(qed_fetch_tcp_port);
+
+void qed_return_tcp_port(struct socket *sock)
+{
+       if (sock && sock->sk) {
+               tcp_set_state(sock->sk, TCP_CLOSE);
+               sock_release(sock);
+       }
+}
+EXPORT_SYMBOL(qed_return_tcp_port);
diff --git a/include/linux/qed/qed_nvmetcp_ip_services_if.h b/include/linux/qed/qed_nvmetcp_ip_services_if.h
new file mode 100644 (file)
index 0000000..3604aee
--- /dev/null
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * Copyright 2021 Marvell. All rights reserved.
+ */
+
+#ifndef _QED_IP_SERVICES_IF_H
+#define _QED_IP_SERVICES_IF_H
+
+#include <linux/types.h>
+#include <net/route.h>
+#include <net/ip6_route.h>
+#include <linux/inetdevice.h>
+
+int qed_route_ipv4(struct sockaddr_storage *local_addr,
+                  struct sockaddr_storage *remote_addr,
+                  struct sockaddr *hardware_address,
+                  struct net_device **ndev);
+int qed_route_ipv6(struct sockaddr_storage *local_addr,
+                  struct sockaddr_storage *remote_addr,
+                  struct sockaddr *hardware_address,
+                  struct net_device **ndev);
+void qed_vlan_get_ndev(struct net_device **ndev, u16 *vlan_id);
+struct pci_dev *qed_validate_ndev(struct net_device *ndev);
+void qed_return_tcp_port(struct socket *sock);
+int qed_fetch_tcp_port(struct sockaddr_storage local_ip_addr,
+                      struct socket **sock, u16 *port);
+__be16 qed_get_in_port(struct sockaddr_storage *sa);
+
+#endif /* _QED_IP_SERVICES_IF_H */