Contributors: 1
Author Tokens Token Proportion Commits Commit Proportion
Nikolay Assa 1102 100.00% 1 100.00%
Total 1102 1


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