| // 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); |