libcxgbi: Add ipv6 api to driver
authorAnish Bhatt <anish@chelsio.com>
Thu, 17 Jul 2014 07:18:17 +0000 (00:18 -0700)
committerDavid S. Miller <davem@davemloft.net>
Thu, 17 Jul 2014 23:06:03 +0000 (16:06 -0700)
Signed-off-by: Anish Bhatt <anish@chelsio.com>
Signed-off-by: Karen Xie <kxie@chelsio.com>
Signed-off-by: Manoj Malviya <manojmalviya@chelsio.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/scsi/cxgbi/libcxgbi.c
drivers/scsi/cxgbi/libcxgbi.h

index b44c1cf..d2fe507 100644 (file)
 #include <linux/inet.h>
 #include <net/dst.h>
 #include <net/route.h>
+#include <net/ipv6.h>
+#include <net/ip6_route.h>
+#include <net/addrconf.h>
+
 #include <linux/inetdevice.h>  /* ip_dev_find */
 #include <linux/module.h>
 #include <net/tcp.h>
@@ -193,8 +197,8 @@ struct cxgbi_device *cxgbi_device_find_by_lldev(void *lldev)
 }
 EXPORT_SYMBOL_GPL(cxgbi_device_find_by_lldev);
 
-static struct cxgbi_device *cxgbi_device_find_by_netdev(struct net_device *ndev,
-                                                       int *port)
+struct cxgbi_device *cxgbi_device_find_by_netdev(struct net_device *ndev,
+                                                int *port)
 {
        struct net_device *vdev = NULL;
        struct cxgbi_device *cdev, *tmp;
@@ -224,6 +228,40 @@ static struct cxgbi_device *cxgbi_device_find_by_netdev(struct net_device *ndev,
                "ndev 0x%p, %s, NO match found.\n", ndev, ndev->name);
        return NULL;
 }
+EXPORT_SYMBOL_GPL(cxgbi_device_find_by_netdev);
+
+static struct cxgbi_device *cxgbi_device_find_by_mac(struct net_device *ndev,
+                                                    int *port)
+{
+       struct net_device *vdev = NULL;
+       struct cxgbi_device *cdev, *tmp;
+       int i;
+
+       if (ndev->priv_flags & IFF_802_1Q_VLAN) {
+               vdev = ndev;
+               ndev = vlan_dev_real_dev(ndev);
+               pr_info("vlan dev %s -> %s.\n", vdev->name, ndev->name);
+       }
+
+       mutex_lock(&cdev_mutex);
+       list_for_each_entry_safe(cdev, tmp, &cdev_list, list_head) {
+               for (i = 0; i < cdev->nports; i++) {
+                       if (!memcmp(ndev->dev_addr, cdev->ports[i]->dev_addr,
+                                   MAX_ADDR_LEN)) {
+                               cdev->hbas[i]->vdev = vdev;
+                               mutex_unlock(&cdev_mutex);
+                               if (port)
+                                       *port = i;
+                               return cdev;
+                       }
+               }
+       }
+       mutex_unlock(&cdev_mutex);
+       log_debug(1 << CXGBI_DBG_DEV,
+                 "ndev 0x%p, %s, NO match mac found.\n",
+                 ndev, ndev->name);
+       return NULL;
+}
 
 void cxgbi_hbas_remove(struct cxgbi_device *cdev)
 {
@@ -320,6 +358,7 @@ static int sock_get_port(struct cxgbi_sock *csk)
        struct cxgbi_ports_map *pmap = &cdev->pmap;
        unsigned int start;
        int idx;
+       __be16 *port;
 
        if (!pmap->max_connect) {
                pr_err("cdev 0x%p, p#%u %s, NO port map.\n",
@@ -327,9 +366,14 @@ static int sock_get_port(struct cxgbi_sock *csk)
                return -EADDRNOTAVAIL;
        }
 
-       if (csk->saddr.sin_port) {
+       if (csk->csk_family == AF_INET)
+               port = &csk->saddr.sin_port;
+       else /* ipv6 */
+               port = &csk->saddr6.sin6_port;
+
+       if (*port) {
                pr_err("source port NON-ZERO %u.\n",
-                       ntohs(csk->saddr.sin_port));
+                       ntohs(*port));
                return -EADDRINUSE;
        }
 
@@ -347,8 +391,7 @@ static int sock_get_port(struct cxgbi_sock *csk)
                        idx = 0;
                if (!pmap->port_csk[idx]) {
                        pmap->used++;
-                       csk->saddr.sin_port =
-                               htons(pmap->sport_base + idx);
+                       *port = htons(pmap->sport_base + idx);
                        pmap->next = idx;
                        pmap->port_csk[idx] = csk;
                        spin_unlock_bh(&pmap->lock);
@@ -374,16 +417,22 @@ static void sock_put_port(struct cxgbi_sock *csk)
 {
        struct cxgbi_device *cdev = csk->cdev;
        struct cxgbi_ports_map *pmap = &cdev->pmap;
+       __be16 *port;
 
-       if (csk->saddr.sin_port) {
-               int idx = ntohs(csk->saddr.sin_port) - pmap->sport_base;
+       if (csk->csk_family == AF_INET)
+               port = &csk->saddr.sin_port;
+       else /* ipv6 */
+               port = &csk->saddr6.sin6_port;
 
-               csk->saddr.sin_port = 0;
+       if (*port) {
+               int idx = ntohs(*port) - pmap->sport_base;
+
+               *port = 0;
                if (idx < 0 || idx >= pmap->max_connect) {
                        pr_err("cdev 0x%p, p#%u %s, port %u OOR.\n",
                                cdev, csk->port_id,
                                cdev->ports[csk->port_id]->name,
-                               ntohs(csk->saddr.sin_port));
+                               ntohs(*port));
                        return;
                }
 
@@ -479,17 +528,11 @@ static struct cxgbi_sock *cxgbi_check_route(struct sockaddr *dst_addr)
        int port = 0xFFFF;
        int err = 0;
 
-       if (daddr->sin_family != AF_INET) {
-               pr_info("address family 0x%x NOT supported.\n",
-                       daddr->sin_family);
-               err = -EAFNOSUPPORT;
-               goto err_out;
-       }
-
        rt = find_route_ipv4(&fl4, 0, daddr->sin_addr.s_addr, 0, daddr->sin_port, 0);
        if (!rt) {
                pr_info("no route to ipv4 0x%x, port %u.\n",
-                       daddr->sin_addr.s_addr, daddr->sin_port);
+                       be32_to_cpu(daddr->sin_addr.s_addr),
+                       be16_to_cpu(daddr->sin_port));
                err = -ENETUNREACH;
                goto err_out;
        }
@@ -537,9 +580,12 @@ static struct cxgbi_sock *cxgbi_check_route(struct sockaddr *dst_addr)
        csk->port_id = port;
        csk->mtu = mtu;
        csk->dst = dst;
+
+       csk->csk_family = AF_INET;
        csk->daddr.sin_addr.s_addr = daddr->sin_addr.s_addr;
        csk->daddr.sin_port = daddr->sin_port;
        csk->daddr.sin_family = daddr->sin_family;
+       csk->saddr.sin_family = daddr->sin_family;
        csk->saddr.sin_addr.s_addr = fl4.saddr;
        neigh_release(n);
 
@@ -556,6 +602,121 @@ err_out:
        return ERR_PTR(err);
 }
 
+static struct rt6_info *find_route_ipv6(const struct in6_addr *saddr,
+                                       const struct in6_addr *daddr)
+{
+       struct flowi6 fl;
+
+       if (saddr)
+               memcpy(&fl.saddr, saddr, sizeof(struct in6_addr));
+       if (daddr)
+               memcpy(&fl.daddr, daddr, sizeof(struct in6_addr));
+       return (struct rt6_info *)ip6_route_output(&init_net, NULL, &fl);
+}
+
+static struct cxgbi_sock *cxgbi_check_route6(struct sockaddr *dst_addr)
+{
+       struct sockaddr_in6 *daddr6 = (struct sockaddr_in6 *)dst_addr;
+       struct dst_entry *dst;
+       struct net_device *ndev;
+       struct cxgbi_device *cdev;
+       struct rt6_info *rt = NULL;
+       struct neighbour *n;
+       struct in6_addr pref_saddr;
+       struct cxgbi_sock *csk = NULL;
+       unsigned int mtu = 0;
+       int port = 0xFFFF;
+       int err = 0;
+
+       rt = find_route_ipv6(NULL, &daddr6->sin6_addr);
+
+       if (!rt) {
+               pr_info("no route to ipv6 %pI6 port %u\n",
+                       daddr6->sin6_addr.s6_addr,
+                       be16_to_cpu(daddr6->sin6_port));
+               err = -ENETUNREACH;
+               goto err_out;
+       }
+
+       dst = &rt->dst;
+
+       n = dst_neigh_lookup(dst, &daddr6->sin6_addr);
+
+       if (!n) {
+               pr_info("%pI6, port %u, dst no neighbour.\n",
+                       daddr6->sin6_addr.s6_addr,
+                       be16_to_cpu(daddr6->sin6_port));
+               err = -ENETUNREACH;
+               goto rel_rt;
+       }
+       ndev = n->dev;
+
+       if (ipv6_addr_is_multicast(&rt->rt6i_dst.addr)) {
+               pr_info("multi-cast route %pI6 port %u, dev %s.\n",
+                       daddr6->sin6_addr.s6_addr,
+                       ntohs(daddr6->sin6_port), ndev->name);
+               err = -ENETUNREACH;
+               goto rel_rt;
+       }
+
+       cdev = cxgbi_device_find_by_netdev(ndev, &port);
+       if (!cdev)
+               cdev = cxgbi_device_find_by_mac(ndev, &port);
+       if (!cdev) {
+               pr_info("dst %pI6 %s, NOT cxgbi device.\n",
+                       daddr6->sin6_addr.s6_addr, ndev->name);
+               err = -ENETUNREACH;
+               goto rel_rt;
+       }
+       log_debug(1 << CXGBI_DBG_SOCK,
+                 "route to %pI6 :%u, ndev p#%d,%s, cdev 0x%p.\n",
+                 daddr6->sin6_addr.s6_addr, ntohs(daddr6->sin6_port), port,
+                 ndev->name, cdev);
+
+       csk = cxgbi_sock_create(cdev);
+       if (!csk) {
+               err = -ENOMEM;
+               goto rel_rt;
+       }
+       csk->cdev = cdev;
+       csk->port_id = port;
+       csk->mtu = mtu;
+       csk->dst = dst;
+
+       if (ipv6_addr_any(&rt->rt6i_prefsrc.addr)) {
+               struct inet6_dev *idev = ip6_dst_idev((struct dst_entry *)rt);
+
+               err = ipv6_dev_get_saddr(&init_net, idev ? idev->dev : NULL,
+                                        &daddr6->sin6_addr, 0, &pref_saddr);
+               if (err) {
+                       pr_info("failed to get source address to reach %pI6\n",
+                               &daddr6->sin6_addr);
+                       goto rel_rt;
+               }
+       } else {
+               pref_saddr = rt->rt6i_prefsrc.addr;
+       }
+
+       csk->csk_family = AF_INET6;
+       csk->daddr6.sin6_addr = daddr6->sin6_addr;
+       csk->daddr6.sin6_port = daddr6->sin6_port;
+       csk->daddr6.sin6_family = daddr6->sin6_family;
+       csk->saddr6.sin6_addr = pref_saddr;
+
+       neigh_release(n);
+       return csk;
+
+rel_rt:
+       if (n)
+               neigh_release(n);
+
+       ip6_rt_put(rt);
+       if (csk)
+               cxgbi_sock_closed(csk);
+err_out:
+       return ERR_PTR(err);
+}
+
 void cxgbi_sock_established(struct cxgbi_sock *csk, unsigned int snd_isn,
                        unsigned int opt)
 {
@@ -2194,6 +2355,34 @@ int cxgbi_set_conn_param(struct iscsi_cls_conn *cls_conn,
 }
 EXPORT_SYMBOL_GPL(cxgbi_set_conn_param);
 
+static inline int csk_print_port(struct cxgbi_sock *csk, char *buf)
+{
+       int len;
+
+       cxgbi_sock_get(csk);
+       len = sprintf(buf, "%hu\n", ntohs(csk->daddr.sin_port));
+       cxgbi_sock_put(csk);
+
+       return len;
+}
+
+static inline int csk_print_ip(struct cxgbi_sock *csk, char *buf)
+{
+       int len;
+
+       cxgbi_sock_get(csk);
+       if (csk->csk_family == AF_INET)
+               len = sprintf(buf, "%pI4",
+                             &csk->daddr.sin_addr.s_addr);
+       else
+               len = sprintf(buf, "%pI6",
+                             &csk->daddr6.sin6_addr);
+
+       cxgbi_sock_put(csk);
+
+       return len;
+}
+
 int cxgbi_get_ep_param(struct iscsi_endpoint *ep, enum iscsi_param param,
                       char *buf)
 {
@@ -2447,7 +2636,17 @@ struct iscsi_endpoint *cxgbi_ep_connect(struct Scsi_Host *shost,
                }
        }
 
-       csk = cxgbi_check_route(dst_addr);
+       if (dst_addr->sa_family == AF_INET) {
+               csk = cxgbi_check_route(dst_addr);
+       } else if (dst_addr->sa_family == AF_INET6) {
+               csk = cxgbi_check_route6(dst_addr);
+       } else {
+               pr_info("address family 0x%x NOT supported.\n",
+                       dst_addr->sa_family);
+               err = -EAFNOSUPPORT;
+               return (struct iscsi_endpoint *)ERR_PTR(err);
+       }
+
        if (IS_ERR(csk))
                return (struct iscsi_endpoint *)csk;
        cxgbi_sock_get(csk);
index 8135f04..8ad73d9 100644 (file)
@@ -44,6 +44,15 @@ enum cxgbi_dbg_flag {
                        pr_info(fmt, ##__VA_ARGS__); \
        } while (0)
 
+#define pr_info_ipaddr(fmt_trail,                                      \
+                       addr1, addr2, args_trail...)                    \
+do {                                                                   \
+       if (!((1 << CXGBI_DBG_SOCK) & dbg_level))                       \
+               break;                                                  \
+       pr_info("%pISpc - %pISpc, " fmt_trail,                          \
+               addr1, addr2, args_trail);                              \
+} while (0)
+
 /* max. connections per adapter */
 #define CXGBI_MAX_CONN         16384
 
@@ -202,8 +211,15 @@ struct cxgbi_sock {
        spinlock_t lock;
        struct kref refcnt;
        unsigned int state;
-       struct sockaddr_in saddr;
-       struct sockaddr_in daddr;
+       unsigned int csk_family;
+       union {
+               struct sockaddr_in saddr;
+               struct sockaddr_in6 saddr6;
+       };
+       union {
+               struct sockaddr_in daddr;
+               struct sockaddr_in6 daddr6;
+       };
        struct dst_entry *dst;
        struct sk_buff_head receive_queue;
        struct sk_buff_head write_queue;
@@ -692,6 +708,7 @@ struct cxgbi_device *cxgbi_device_register(unsigned int, unsigned int);
 void cxgbi_device_unregister(struct cxgbi_device *);
 void cxgbi_device_unregister_all(unsigned int flag);
 struct cxgbi_device *cxgbi_device_find_by_lldev(void *);
+struct cxgbi_device *cxgbi_device_find_by_netdev(struct net_device *, int *);
 int cxgbi_hbas_add(struct cxgbi_device *, unsigned int, unsigned int,
                        struct scsi_host_template *,
                        struct scsi_transport_template *);