| /* SPDX-License-Identifier: LGPL-2.1-or-later |
| * Copyright © 2020 VMware, Inc. */ |
| |
| #include "device-enumerator-private.h" |
| #include "device-util.h" |
| #include "fd-util.h" |
| #include "networkd-link.h" |
| #include "networkd-manager.h" |
| #include "networkd-queue.h" |
| #include "networkd-sriov.h" |
| |
| static int sr_iov_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, SRIOV *sr_iov) { |
| int r; |
| |
| assert(m); |
| assert(link); |
| |
| r = sd_netlink_message_get_errno(m); |
| if (r < 0 && r != -EEXIST) { |
| log_link_message_error_errno(link, m, r, "Could not set up SR-IOV"); |
| link_enter_failed(link); |
| return 1; |
| } |
| |
| if (link->sr_iov_messages == 0) { |
| log_link_debug(link, "SR-IOV configured"); |
| link->sr_iov_configured = true; |
| link_check_ready(link); |
| } |
| |
| return 1; |
| } |
| |
| static int sr_iov_configure(SRIOV *sr_iov, Link *link, Request *req) { |
| _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; |
| int r; |
| |
| assert(sr_iov); |
| assert(link); |
| assert(link->manager); |
| assert(link->manager->rtnl); |
| assert(link->ifindex > 0); |
| assert(req); |
| |
| log_link_debug(link, "Setting SR-IOV virtual function %"PRIu32".", sr_iov->vf); |
| |
| r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_SETLINK, link->ifindex); |
| if (r < 0) |
| return r; |
| |
| r = sr_iov_set_netlink_message(sr_iov, m); |
| if (r < 0) |
| return r; |
| |
| return request_call_netlink_async(link->manager->rtnl, m, req); |
| } |
| |
| static int sr_iov_process_request(Request *req, Link *link, SRIOV *sr_iov) { |
| int r; |
| |
| assert(req); |
| assert(link); |
| assert(sr_iov); |
| |
| if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) |
| return 0; |
| |
| r = sr_iov_configure(sr_iov, link, req); |
| if (r < 0) |
| return log_link_warning_errno(link, r, |
| "Failed to configure SR-IOV virtual function %"PRIu32": %m", |
| sr_iov->vf); |
| |
| return 1; |
| } |
| |
| int link_request_sr_iov_vfs(Link *link) { |
| SRIOV *sr_iov; |
| int r; |
| |
| assert(link); |
| assert(link->network); |
| |
| link->sr_iov_configured = false; |
| |
| ORDERED_HASHMAP_FOREACH(sr_iov, link->network->sr_iov_by_section) { |
| r = link_queue_request_safe(link, REQUEST_TYPE_SRIOV, |
| sr_iov, NULL, |
| sr_iov_hash_func, |
| sr_iov_compare_func, |
| sr_iov_process_request, |
| &link->sr_iov_messages, |
| sr_iov_handler, |
| NULL); |
| if (r < 0) |
| return log_link_warning_errno(link, r, |
| "Failed to request SR-IOV virtual function %"PRIu32": %m", |
| sr_iov->vf); |
| } |
| |
| if (link->sr_iov_messages == 0) { |
| link->sr_iov_configured = true; |
| link_check_ready(link); |
| } else |
| log_link_debug(link, "Configuring SR-IOV"); |
| |
| return 0; |
| } |
| |
| static int find_ifindex_from_pci_dev_port(sd_device *pci_dev, const char *dev_port) { |
| _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; |
| sd_device *dev; |
| int ifindex, r; |
| |
| assert(pci_dev); |
| assert(dev_port); |
| |
| r = sd_device_enumerator_new(&e); |
| if (r < 0) |
| return r; |
| |
| r = sd_device_enumerator_allow_uninitialized(e); |
| if (r < 0) |
| return r; |
| |
| r = sd_device_enumerator_add_match_parent(e, pci_dev); |
| if (r < 0) |
| return r; |
| |
| r = sd_device_enumerator_add_match_subsystem(e, "net", true); |
| if (r < 0) |
| return r; |
| |
| r = sd_device_enumerator_add_match_sysattr(e, "dev_port", dev_port, true); |
| if (r < 0) |
| return r; |
| |
| dev = sd_device_enumerator_get_device_first(e); |
| if (!dev) |
| return -ENODEV; /* no device found */ |
| |
| if (sd_device_enumerator_get_device_next(e)) |
| return -ENXIO; /* multiple devices found */ |
| |
| r = sd_device_get_ifindex(dev, &ifindex); |
| if (r < 0) |
| return r; |
| |
| assert(ifindex > 0); |
| return ifindex; |
| } |
| |
| static int manager_update_sr_iov_ifindices(Manager *manager, int phys_port_ifindex, int virt_port_ifindex) { |
| Link *phys_link = NULL, *virt_link = NULL; |
| int r; |
| |
| assert(manager); |
| assert(phys_port_ifindex > 0); |
| assert(virt_port_ifindex > 0); |
| |
| /* This sets ifindices only when both interfaces are already managed by us. */ |
| |
| r = link_get_by_index(manager, phys_port_ifindex, &phys_link); |
| if (r < 0) |
| return r; |
| |
| r = link_get_by_index(manager, virt_port_ifindex, &virt_link); |
| if (r < 0) |
| return r; |
| |
| /* update VF ifindex in PF */ |
| r = set_ensure_put(&phys_link->sr_iov_virt_port_ifindices, NULL, INT_TO_PTR(virt_port_ifindex)); |
| if (r < 0) |
| return r; |
| |
| log_link_debug(phys_link, |
| "Found SR-IOV VF port %s(%i).", |
| virt_link ? virt_link->ifname : "n/a", virt_port_ifindex); |
| |
| /* update PF ifindex in VF */ |
| if (virt_link->sr_iov_phys_port_ifindex > 0 && virt_link->sr_iov_phys_port_ifindex != phys_port_ifindex) { |
| Link *old_phys_link; |
| |
| if (link_get_by_index(manager, virt_link->sr_iov_phys_port_ifindex, &old_phys_link) >= 0) |
| set_remove(old_phys_link->sr_iov_virt_port_ifindices, INT_TO_PTR(virt_port_ifindex)); |
| } |
| |
| virt_link->sr_iov_phys_port_ifindex = phys_port_ifindex; |
| |
| log_link_debug(virt_link, |
| "Found SR-IOV PF port %s(%i).", |
| phys_link ? phys_link->ifname : "n/a", phys_port_ifindex); |
| |
| return 0; |
| } |
| |
| static int link_set_sr_iov_phys_port(Link *link) { |
| _cleanup_(sd_device_unrefp) sd_device *pci_physfn_dev = NULL; |
| const char *dev_port; |
| sd_device *pci_dev; |
| int r; |
| |
| assert(link); |
| assert(link->manager); |
| |
| if (link->sr_iov_phys_port_ifindex > 0) |
| return 0; |
| |
| if (!link->dev) |
| return -ENODEV; |
| |
| r = sd_device_get_sysattr_value(link->dev, "dev_port", &dev_port); |
| if (r < 0) |
| return r; |
| |
| r = sd_device_get_parent_with_subsystem_devtype(link->dev, "pci", NULL, &pci_dev); |
| if (r < 0) |
| return r; |
| |
| r = sd_device_new_child(&pci_physfn_dev, pci_dev, "physfn"); |
| if (r < 0) |
| return r; |
| |
| r = find_ifindex_from_pci_dev_port(pci_physfn_dev, dev_port); |
| if (r < 0) |
| return r; |
| |
| return manager_update_sr_iov_ifindices(link->manager, r, link->ifindex); |
| } |
| |
| static int link_set_sr_iov_virt_ports(Link *link) { |
| const char *dev_port, *name; |
| sd_device *pci_dev, *child; |
| int r; |
| |
| assert(link); |
| assert(link->manager); |
| |
| set_clear(link->sr_iov_virt_port_ifindices); |
| |
| if (!link->dev) |
| return -ENODEV; |
| |
| r = sd_device_get_sysattr_value(link->dev, "dev_port", &dev_port); |
| if (r < 0) |
| return r; |
| |
| r = sd_device_get_parent_with_subsystem_devtype(link->dev, "pci", NULL, &pci_dev); |
| if (r < 0) |
| return r; |
| |
| FOREACH_DEVICE_CHILD_WITH_SUFFIX(pci_dev, child, name) { |
| const char *n; |
| |
| /* Accept name prefixed with "virtfn", but refuse "virtfn" itself. */ |
| n = startswith(name, "virtfn"); |
| if (isempty(n) || !in_charset(n, DIGITS)) |
| continue; |
| |
| r = find_ifindex_from_pci_dev_port(child, dev_port); |
| if (r < 0) |
| continue; |
| |
| if (manager_update_sr_iov_ifindices(link->manager, link->ifindex, r) < 0) |
| continue; |
| } |
| |
| return 0; |
| } |
| |
| int link_set_sr_iov_ifindices(Link *link) { |
| int r; |
| |
| assert(link); |
| |
| r = link_set_sr_iov_phys_port(link); |
| if (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)) |
| return r; |
| |
| r = link_set_sr_iov_virt_ports(link); |
| if (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)) |
| return r; |
| |
| return 0; |
| } |
| |
| void link_clear_sr_iov_ifindices(Link *link) { |
| void *v; |
| |
| assert(link); |
| assert(link->manager); |
| |
| if (link->sr_iov_phys_port_ifindex > 0) { |
| Link *phys_link; |
| |
| if (link_get_by_index(link->manager, link->sr_iov_phys_port_ifindex, &phys_link) >= 0) |
| set_remove(phys_link->sr_iov_virt_port_ifindices, INT_TO_PTR(link->ifindex)); |
| |
| link->sr_iov_phys_port_ifindex = 0; |
| } |
| |
| while ((v = set_steal_first(link->sr_iov_virt_port_ifindices))) { |
| Link *virt_link; |
| |
| if (link_get_by_index(link->manager, PTR_TO_INT(v), &virt_link) >= 0) |
| virt_link->sr_iov_phys_port_ifindex = 0; |
| } |
| } |
| |
| bool check_ready_for_all_sr_iov_ports( |
| Link *link, |
| bool allow_unmanaged, /* for the main target */ |
| bool (check_one)(Link *link, bool allow_unmanaged)) { |
| |
| Link *phys_link; |
| void *v; |
| |
| assert(link); |
| assert(link->manager); |
| assert(check_one); |
| |
| /* Some drivers make VF ports become down when their PF port becomes down, and may fail to configure |
| * VF ports. Also, when a VF port becomes up/down, its PF port and other VF ports may become down. |
| * See issue #23315. */ |
| |
| /* First, check the main target. */ |
| if (!check_one(link, allow_unmanaged)) |
| return false; |
| |
| /* If this is a VF port, then also check the PF port. */ |
| if (link->sr_iov_phys_port_ifindex > 0) { |
| if (link_get_by_index(link->manager, link->sr_iov_phys_port_ifindex, &phys_link) < 0 || |
| !check_one(phys_link, /* allow_unmanaged = */ true)) |
| return false; |
| } else |
| phys_link = link; |
| |
| /* Also check all VF ports. */ |
| SET_FOREACH(v, phys_link->sr_iov_virt_port_ifindices) { |
| int ifindex = PTR_TO_INT(v); |
| Link *virt_link; |
| |
| if (ifindex == link->ifindex) |
| continue; /* The main target link is a VF port, and its state is already checked. */ |
| |
| if (link_get_by_index(link->manager, ifindex, &virt_link) < 0) |
| return false; |
| |
| if (!check_one(virt_link, /* allow_unmanaged = */ true)) |
| return false; |
| } |
| |
| return true; |
| } |