blob: 1eec1294ef9f8123fe9165eebf6d7050ce6ff91d [file] [log] [blame]
/*
* teamd_phys_port_check.c - Physical port checking support for teamd
* Copyright (C) 2013-2015 Jiri Pirko <jiri@resnulli.us>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "teamd.h"
struct pcie_addr {
uint16_t domain;
uint8_t bus;
uint8_t slot;
uint8_t function;
};
static int teamd_sriov_physfn_addr(struct pcie_addr *addr, const char *ifname)
{
char link[256];
char *path;
char *start;
int ret;
ret = asprintf(&path, "/sys/class/net/%s/device/physfn", ifname);
if (ret == -1)
return -ENOMEM;
ret = readlink(path, link, sizeof(link) - 1);
free(path);
if (ret == -1)
return -errno;
link[ret] = '\0';
start = strrchr(link, '/');
if (!start)
return -EINVAL;
start++;
memset(addr, 0, sizeof(*addr));
ret = sscanf(start, "%04x:%02x:%02x.%x", (unsigned int *) &addr->domain,
(unsigned int *) &addr->bus,
(unsigned int *) &addr->slot,
(unsigned int *) &addr->function);
if (ret != 4)
return -EINVAL;
return 0;
}
static bool teamd_phys_port_sriovsysfs_cmp(struct teamd_port *tdport1,
struct teamd_port *tdport2)
{
struct pcie_addr physfnaddr1;
struct pcie_addr physfnaddr2;
int err;
err = teamd_sriov_physfn_addr(&physfnaddr1, tdport1->ifname);
if (err)
return false;
err = teamd_sriov_physfn_addr(&physfnaddr2, tdport2->ifname);
if (err)
return false;
if (!memcmp(&physfnaddr1, &physfnaddr2, sizeof(physfnaddr1)))
return true;
return false;
}
static bool teamd_phys_port_ifinfo_cmp(struct teamd_port *tdport1,
struct teamd_port *tdport2)
{
const char *id1 = team_get_ifinfo_phys_port_id(tdport1->team_ifinfo);
const char *id2 = team_get_ifinfo_phys_port_id(tdport2->team_ifinfo);
size_t id1_len = team_get_ifinfo_phys_port_id_len(tdport1->team_ifinfo);
size_t id2_len = team_get_ifinfo_phys_port_id_len(tdport2->team_ifinfo);
if (!id1_len || !id2_len || id1_len != id2_len)
return false;
if (!memcmp(id1, id2, id1_len))
return true;
return false;
}
static int teamd_phys_port_check_event_watch_port_added(struct teamd_context *ctx,
struct teamd_port *tdport,
void *priv)
{
struct teamd_port *cur_tdport;
teamd_for_each_tdport(cur_tdport, ctx) {
if (cur_tdport == tdport)
continue;
/* Once all drivers implement ndo_get_phys_port_id() function,
* teamd_phys_port_sriovsysfs_cmp() would not be needed to be
* called here.
*/
if (teamd_phys_port_ifinfo_cmp(tdport, cur_tdport) ||
teamd_phys_port_sriovsysfs_cmp(tdport, cur_tdport))
teamd_log_warn("%s: device is using the same physical port as device %s. Note that teaming multiple devices which use the same physical port makes no sense.",
tdport->ifname, cur_tdport->ifname);
}
return 0;
}
static const struct teamd_event_watch_ops teamd_phys_port_check_event_watch_ops = {
.port_added = teamd_phys_port_check_event_watch_port_added,
};
int teamd_phys_port_check_init(struct teamd_context *ctx)
{
int err;
err = teamd_event_watch_register(ctx, &teamd_phys_port_check_event_watch_ops,
NULL);
if (err) {
teamd_log_err("Failed to register event watch.");
return err;
}
return 0;
}
void teamd_phys_port_check_fini(struct teamd_context *ctx)
{
teamd_event_watch_unregister(ctx, &teamd_phys_port_check_event_watch_ops,
NULL);
}