]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later |
518cd6b5 SS |
2 | * Copyright © 2020 VMware, Inc. */ |
3 | ||
b0ff6dea | 4 | #include "device-enumerator-private.h" |
96f974e4 | 5 | #include "device-util.h" |
b0ff6dea | 6 | #include "fd-util.h" |
b4fd34d8 | 7 | #include "networkd-link.h" |
518cd6b5 | 8 | #include "networkd-manager.h" |
cb8453cc | 9 | #include "networkd-queue.h" |
518cd6b5 | 10 | #include "networkd-sriov.h" |
518cd6b5 | 11 | |
cb8453cc | 12 | static int sr_iov_handler(sd_netlink *rtnl, sd_netlink_message *m, Request *req, Link *link, SRIOV *sr_iov) { |
518cd6b5 SS |
13 | int r; |
14 | ||
cb8453cc | 15 | assert(m); |
518cd6b5 | 16 | assert(link); |
518cd6b5 SS |
17 | |
18 | r = sd_netlink_message_get_errno(m); | |
19 | if (r < 0 && r != -EEXIST) { | |
20 | log_link_message_error_errno(link, m, r, "Could not set up SR-IOV"); | |
21 | link_enter_failed(link); | |
22 | return 1; | |
23 | } | |
24 | ||
25 | if (link->sr_iov_messages == 0) { | |
26 | log_link_debug(link, "SR-IOV configured"); | |
27 | link->sr_iov_configured = true; | |
28 | link_check_ready(link); | |
29 | } | |
30 | ||
31 | return 1; | |
32 | } | |
33 | ||
cb8453cc YW |
34 | static int sr_iov_configure(SRIOV *sr_iov, Link *link, Request *req) { |
35 | _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; | |
518cd6b5 SS |
36 | int r; |
37 | ||
cb8453cc | 38 | assert(sr_iov); |
518cd6b5 SS |
39 | assert(link); |
40 | assert(link->manager); | |
41 | assert(link->manager->rtnl); | |
42 | assert(link->ifindex > 0); | |
cb8453cc | 43 | assert(req); |
518cd6b5 | 44 | |
cb8453cc | 45 | log_link_debug(link, "Setting SR-IOV virtual function %"PRIu32".", sr_iov->vf); |
518cd6b5 | 46 | |
cb8453cc | 47 | r = sd_rtnl_message_new_link(link->manager->rtnl, &m, RTM_SETLINK, link->ifindex); |
518cd6b5 | 48 | if (r < 0) |
b4fd34d8 | 49 | return r; |
518cd6b5 | 50 | |
cb8453cc | 51 | r = sr_iov_set_netlink_message(sr_iov, m); |
518cd6b5 | 52 | if (r < 0) |
b4fd34d8 | 53 | return r; |
518cd6b5 | 54 | |
cb8453cc YW |
55 | return request_call_netlink_async(link->manager->rtnl, m, req); |
56 | } | |
518cd6b5 | 57 | |
cb8453cc YW |
58 | static int sr_iov_process_request(Request *req, Link *link, SRIOV *sr_iov) { |
59 | int r; | |
518cd6b5 | 60 | |
cb8453cc YW |
61 | assert(req); |
62 | assert(link); | |
63 | assert(sr_iov); | |
64 | ||
65 | if (!IN_SET(link->state, LINK_STATE_CONFIGURING, LINK_STATE_CONFIGURED)) | |
66 | return 0; | |
67 | ||
68 | r = sr_iov_configure(sr_iov, link, req); | |
69 | if (r < 0) | |
70 | return log_link_warning_errno(link, r, | |
71 | "Failed to configure SR-IOV virtual function %"PRIu32": %m", | |
72 | sr_iov->vf); | |
73 | ||
74 | return 1; | |
518cd6b5 SS |
75 | } |
76 | ||
cb8453cc | 77 | int link_request_sr_iov_vfs(Link *link) { |
f3a3ff27 YW |
78 | SRIOV *sr_iov; |
79 | int r; | |
80 | ||
bd4733da YW |
81 | assert(link); |
82 | assert(link->network); | |
83 | ||
f3a3ff27 | 84 | link->sr_iov_configured = false; |
f3a3ff27 YW |
85 | |
86 | ORDERED_HASHMAP_FOREACH(sr_iov, link->network->sr_iov_by_section) { | |
cb8453cc YW |
87 | r = link_queue_request_safe(link, REQUEST_TYPE_SRIOV, |
88 | sr_iov, NULL, | |
89 | sr_iov_hash_func, | |
90 | sr_iov_compare_func, | |
91 | sr_iov_process_request, | |
92 | &link->sr_iov_messages, | |
93 | sr_iov_handler, | |
94 | NULL); | |
f3a3ff27 | 95 | if (r < 0) |
b4fd34d8 | 96 | return log_link_warning_errno(link, r, |
cb8453cc | 97 | "Failed to request SR-IOV virtual function %"PRIu32": %m", |
b4fd34d8 | 98 | sr_iov->vf); |
f3a3ff27 YW |
99 | } |
100 | ||
cb8453cc | 101 | if (link->sr_iov_messages == 0) { |
f3a3ff27 | 102 | link->sr_iov_configured = true; |
cb8453cc YW |
103 | link_check_ready(link); |
104 | } else | |
f3a3ff27 YW |
105 | log_link_debug(link, "Configuring SR-IOV"); |
106 | ||
107 | return 0; | |
108 | } | |
b0ff6dea YW |
109 | |
110 | static int find_ifindex_from_pci_dev_port(sd_device *pci_dev, const char *dev_port) { | |
111 | _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; | |
112 | sd_device *dev; | |
113 | int ifindex, r; | |
114 | ||
115 | assert(pci_dev); | |
116 | assert(dev_port); | |
117 | ||
118 | r = sd_device_enumerator_new(&e); | |
119 | if (r < 0) | |
120 | return r; | |
121 | ||
122 | r = sd_device_enumerator_allow_uninitialized(e); | |
123 | if (r < 0) | |
124 | return r; | |
125 | ||
126 | r = sd_device_enumerator_add_match_parent(e, pci_dev); | |
127 | if (r < 0) | |
128 | return r; | |
129 | ||
130 | r = sd_device_enumerator_add_match_subsystem(e, "net", true); | |
131 | if (r < 0) | |
132 | return r; | |
133 | ||
134 | r = sd_device_enumerator_add_match_sysattr(e, "dev_port", dev_port, true); | |
135 | if (r < 0) | |
136 | return r; | |
137 | ||
138 | dev = sd_device_enumerator_get_device_first(e); | |
139 | if (!dev) | |
140 | return -ENODEV; /* no device found */ | |
141 | ||
142 | if (sd_device_enumerator_get_device_next(e)) | |
143 | return -ENXIO; /* multiple devices found */ | |
144 | ||
145 | r = sd_device_get_ifindex(dev, &ifindex); | |
146 | if (r < 0) | |
147 | return r; | |
148 | ||
149 | assert(ifindex > 0); | |
150 | return ifindex; | |
151 | } | |
152 | ||
153 | static int manager_update_sr_iov_ifindices(Manager *manager, int phys_port_ifindex, int virt_port_ifindex) { | |
154 | Link *phys_link = NULL, *virt_link = NULL; | |
155 | int r; | |
156 | ||
157 | assert(manager); | |
158 | assert(phys_port_ifindex > 0); | |
159 | assert(virt_port_ifindex > 0); | |
160 | ||
5c19169f | 161 | /* This sets ifindices only when both interfaces are already managed by us. */ |
b0ff6dea YW |
162 | |
163 | r = link_get_by_index(manager, phys_port_ifindex, &phys_link); | |
164 | if (r < 0) | |
165 | return r; | |
166 | ||
167 | r = link_get_by_index(manager, virt_port_ifindex, &virt_link); | |
168 | if (r < 0) | |
169 | return r; | |
170 | ||
171 | /* update VF ifindex in PF */ | |
172 | r = set_ensure_put(&phys_link->sr_iov_virt_port_ifindices, NULL, INT_TO_PTR(virt_port_ifindex)); | |
173 | if (r < 0) | |
174 | return r; | |
175 | ||
176 | log_link_debug(phys_link, | |
177 | "Found SR-IOV VF port %s(%i).", | |
178 | virt_link ? virt_link->ifname : "n/a", virt_port_ifindex); | |
179 | ||
180 | /* update PF ifindex in VF */ | |
181 | if (virt_link->sr_iov_phys_port_ifindex > 0 && virt_link->sr_iov_phys_port_ifindex != phys_port_ifindex) { | |
182 | Link *old_phys_link; | |
183 | ||
184 | if (link_get_by_index(manager, virt_link->sr_iov_phys_port_ifindex, &old_phys_link) >= 0) | |
185 | set_remove(old_phys_link->sr_iov_virt_port_ifindices, INT_TO_PTR(virt_port_ifindex)); | |
186 | } | |
187 | ||
188 | virt_link->sr_iov_phys_port_ifindex = phys_port_ifindex; | |
189 | ||
190 | log_link_debug(virt_link, | |
191 | "Found SR-IOV PF port %s(%i).", | |
192 | phys_link ? phys_link->ifname : "n/a", phys_port_ifindex); | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
b9962da6 | 197 | static int link_set_sr_iov_phys_port(Link *link, sd_device *pci_dev, const char *dev_port) { |
b0ff6dea | 198 | _cleanup_(sd_device_unrefp) sd_device *pci_physfn_dev = NULL; |
b0ff6dea YW |
199 | int r; |
200 | ||
201 | assert(link); | |
202 | assert(link->manager); | |
b9962da6 YW |
203 | assert(pci_dev); |
204 | assert(dev_port); | |
b0ff6dea YW |
205 | |
206 | if (link->sr_iov_phys_port_ifindex > 0) | |
207 | return 0; | |
208 | ||
b0ff6dea YW |
209 | r = sd_device_new_child(&pci_physfn_dev, pci_dev, "physfn"); |
210 | if (r < 0) | |
211 | return r; | |
212 | ||
213 | r = find_ifindex_from_pci_dev_port(pci_physfn_dev, dev_port); | |
214 | if (r < 0) | |
215 | return r; | |
216 | ||
217 | return manager_update_sr_iov_ifindices(link->manager, r, link->ifindex); | |
218 | } | |
219 | ||
b9962da6 YW |
220 | static int link_set_sr_iov_virt_ports(Link *link, sd_device *pci_dev, const char *dev_port) { |
221 | const char *name; | |
b0ff6dea YW |
222 | int r; |
223 | ||
224 | assert(link); | |
225 | assert(link->manager); | |
b9962da6 YW |
226 | assert(pci_dev); |
227 | assert(dev_port); | |
b0ff6dea YW |
228 | |
229 | set_clear(link->sr_iov_virt_port_ifindices); | |
230 | ||
96f974e4 YW |
231 | FOREACH_DEVICE_CHILD_WITH_SUFFIX(pci_dev, child, name) { |
232 | const char *n; | |
b0ff6dea YW |
233 | |
234 | /* Accept name prefixed with "virtfn", but refuse "virtfn" itself. */ | |
96f974e4 YW |
235 | n = startswith(name, "virtfn"); |
236 | if (isempty(n) || !in_charset(n, DIGITS)) | |
b0ff6dea YW |
237 | continue; |
238 | ||
96f974e4 | 239 | r = find_ifindex_from_pci_dev_port(child, dev_port); |
29c1fb3c | 240 | if (r < 0) |
b0ff6dea YW |
241 | continue; |
242 | ||
243 | if (manager_update_sr_iov_ifindices(link->manager, link->ifindex, r) < 0) | |
244 | continue; | |
245 | } | |
246 | ||
247 | return 0; | |
248 | } | |
249 | ||
250 | int link_set_sr_iov_ifindices(Link *link) { | |
b9962da6 YW |
251 | const char *dev_port; |
252 | sd_device *pci_dev; | |
b0ff6dea YW |
253 | int r; |
254 | ||
255 | assert(link); | |
256 | ||
b9962da6 YW |
257 | if (!link->dev) |
258 | return -ENODEV; | |
259 | ||
260 | r = sd_device_get_parent_with_subsystem_devtype(link->dev, "pci", NULL, &pci_dev); | |
261 | if (ERRNO_IS_NEG_DEVICE_ABSENT(r)) | |
262 | return 0; | |
263 | if (r < 0) | |
264 | return log_link_debug_errno(link, r, "Failed to get parent PCI device: %m"); | |
265 | ||
266 | /* This may return -EINVAL or -ENODEV, instead of -ENOENT, if the device has been removed or is being | |
267 | * removed. Let's ignore the error codes here. */ | |
268 | r = sd_device_get_sysattr_value(link->dev, "dev_port", &dev_port); | |
269 | if (ERRNO_IS_NEG_DEVICE_ABSENT(r) || r == -EINVAL) | |
270 | return 0; | |
271 | if (r < 0) | |
272 | return log_link_debug_errno(link, r, "Failed to get 'dev_port' sysfs attribute: %m"); | |
273 | ||
274 | r = link_set_sr_iov_phys_port(link, pci_dev, dev_port); | |
b0ff6dea | 275 | if (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)) |
b9962da6 | 276 | return log_link_debug_errno(link, r, "Failed to set SR-IOV physical port: %m"); |
b0ff6dea | 277 | |
b9962da6 | 278 | r = link_set_sr_iov_virt_ports(link, pci_dev, dev_port); |
b0ff6dea | 279 | if (r < 0 && !ERRNO_IS_DEVICE_ABSENT(r)) |
b9962da6 | 280 | return log_link_debug_errno(link, r, "Failed to set SR-IOV virtual ports: %m"); |
b0ff6dea YW |
281 | |
282 | return 0; | |
283 | } | |
284 | ||
285 | void link_clear_sr_iov_ifindices(Link *link) { | |
286 | void *v; | |
287 | ||
288 | assert(link); | |
289 | assert(link->manager); | |
290 | ||
291 | if (link->sr_iov_phys_port_ifindex > 0) { | |
292 | Link *phys_link; | |
293 | ||
294 | if (link_get_by_index(link->manager, link->sr_iov_phys_port_ifindex, &phys_link) >= 0) | |
295 | set_remove(phys_link->sr_iov_virt_port_ifindices, INT_TO_PTR(link->ifindex)); | |
296 | ||
297 | link->sr_iov_phys_port_ifindex = 0; | |
298 | } | |
299 | ||
300 | while ((v = set_steal_first(link->sr_iov_virt_port_ifindices))) { | |
301 | Link *virt_link; | |
302 | ||
303 | if (link_get_by_index(link->manager, PTR_TO_INT(v), &virt_link) >= 0) | |
304 | virt_link->sr_iov_phys_port_ifindex = 0; | |
305 | } | |
306 | } | |
e33232d4 YW |
307 | |
308 | bool check_ready_for_all_sr_iov_ports( | |
309 | Link *link, | |
310 | bool allow_unmanaged, /* for the main target */ | |
311 | bool (check_one)(Link *link, bool allow_unmanaged)) { | |
312 | ||
313 | Link *phys_link; | |
314 | void *v; | |
315 | ||
316 | assert(link); | |
317 | assert(link->manager); | |
318 | assert(check_one); | |
319 | ||
320 | /* Some drivers make VF ports become down when their PF port becomes down, and may fail to configure | |
321 | * VF ports. Also, when a VF port becomes up/down, its PF port and other VF ports may become down. | |
322 | * See issue #23315. */ | |
323 | ||
324 | /* First, check the main target. */ | |
325 | if (!check_one(link, allow_unmanaged)) | |
326 | return false; | |
327 | ||
328 | /* If this is a VF port, then also check the PF port. */ | |
329 | if (link->sr_iov_phys_port_ifindex > 0) { | |
330 | if (link_get_by_index(link->manager, link->sr_iov_phys_port_ifindex, &phys_link) < 0 || | |
331 | !check_one(phys_link, /* allow_unmanaged = */ true)) | |
332 | return false; | |
333 | } else | |
334 | phys_link = link; | |
335 | ||
336 | /* Also check all VF ports. */ | |
337 | SET_FOREACH(v, phys_link->sr_iov_virt_port_ifindices) { | |
338 | int ifindex = PTR_TO_INT(v); | |
339 | Link *virt_link; | |
340 | ||
341 | if (ifindex == link->ifindex) | |
342 | continue; /* The main target link is a VF port, and its state is already checked. */ | |
343 | ||
344 | if (link_get_by_index(link->manager, ifindex, &virt_link) < 0) | |
345 | return false; | |
346 | ||
347 | if (!check_one(virt_link, /* allow_unmanaged = */ true)) | |
348 | return false; | |
349 | } | |
350 | ||
351 | return true; | |
352 | } |