Line data Source code
1 : /*
2 : * Copyright (c) 2018, Microsoft Corporation.
3 : * Licensed under the Apache License, Version 2.0 (the "License");
4 : * you may not use this file except in compliance with the License.
5 : * You may obtain a copy of the License at:
6 : *
7 : * http://www.apache.org/licenses/LICENSE-2.0
8 : *
9 : * Unless required by applicable law or agreed to in writing, software
10 : * distributed under the License is distributed on an "AS IS" BASIS,
11 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : * See the License for the specific language governing permissions and
13 : * limitations under the License.
14 : */
15 : /*
16 : * vmbus.c: Linux user space VMBus bus management.
17 : */
18 :
19 : #include <vppinfra/linux/sysfs.h>
20 :
21 : #include <vlib/vlib.h>
22 : #include <vlib/vmbus/vmbus.h>
23 : #include <vlib/unix/unix.h>
24 :
25 : #include <sys/types.h>
26 : #include <sys/stat.h>
27 : #include <fcntl.h>
28 : #include <dirent.h>
29 : #include <sys/ioctl.h>
30 : #include <net/if.h>
31 : #include <linux/ethtool.h>
32 : #include <linux/sockios.h>
33 :
34 : static const char sysfs_vmbus_dev_path[] = "/sys/bus/vmbus/devices";
35 : static const char sysfs_vmbus_drv_path[] = "/sys/bus/vmbus/drivers";
36 : static const char sysfs_class_net_path[] = "/sys/class/net";
37 : static const char uio_drv_name[] = "uio_hv_generic";
38 : static const char netvsc_uuid[] = "f8615163-df3e-46c5-913f-f2d2f965ed0e";
39 :
40 : typedef struct
41 : {
42 : int fd;
43 : void *addr;
44 : size_t size;
45 : } linux_vmbus_region_t;
46 :
47 : typedef struct
48 : {
49 : int fd;
50 : u32 clib_file_index;
51 : } linux_vmbus_irq_t;
52 :
53 : typedef struct
54 : {
55 : vlib_vmbus_dev_handle_t handle;
56 : vlib_vmbus_addr_t addr;
57 :
58 : /* Device File descriptor */
59 : int fd;
60 :
61 : /* Minor device for uio device. */
62 : u32 uio_minor;
63 :
64 : /* private data */
65 : uword private_data;
66 :
67 : } linux_vmbus_device_t;
68 :
69 : /* Pool of VMBUS devices. */
70 : typedef struct
71 : {
72 : vlib_main_t *vlib_main;
73 : linux_vmbus_device_t *linux_vmbus_devices;
74 :
75 : } linux_vmbus_main_t;
76 :
77 : linux_vmbus_main_t linux_vmbus_main;
78 :
79 : static linux_vmbus_device_t *
80 0 : linux_vmbus_get_device (vlib_vmbus_dev_handle_t h)
81 : {
82 0 : linux_vmbus_main_t *lpm = &linux_vmbus_main;
83 0 : return pool_elt_at_index (lpm->linux_vmbus_devices, h);
84 : }
85 :
86 : uword
87 0 : vlib_vmbus_get_private_data (vlib_vmbus_dev_handle_t h)
88 : {
89 0 : linux_vmbus_device_t *d = linux_vmbus_get_device (h);
90 0 : return d->private_data;
91 : }
92 :
93 : void
94 0 : vlib_vmbus_set_private_data (vlib_vmbus_dev_handle_t h, uword private_data)
95 : {
96 0 : linux_vmbus_device_t *d = linux_vmbus_get_device (h);
97 0 : d->private_data = private_data;
98 0 : }
99 :
100 : vlib_vmbus_addr_t *
101 0 : vlib_vmbus_get_addr (vlib_vmbus_dev_handle_t h)
102 : {
103 0 : linux_vmbus_device_t *d = linux_vmbus_get_device (h);
104 0 : return &d->addr;
105 : }
106 :
107 : /* Call to allocate/initialize the vmbus subsystem.
108 : This is not an init function so that users can explicitly enable
109 : vmbus only when it's needed. */
110 : clib_error_t *vmbus_bus_init (vlib_main_t * vm);
111 :
112 : linux_vmbus_main_t linux_vmbus_main;
113 :
114 : /*
115 : * Take VMBus address represented in standard form like:
116 : * "f2c086b2-ff2e-11e8-88de-7bad0a57de05" and convert
117 : * it to u8[16]
118 : */
119 : uword
120 0 : unformat_vlib_vmbus_addr (unformat_input_t *input, va_list *args)
121 : {
122 0 : vlib_vmbus_addr_t *addr = va_arg (*args, vlib_vmbus_addr_t *);
123 0 : uword ret = 0;
124 0 : u8 *s = 0;
125 :
126 0 : if (!unformat (input, "%U", unformat_token, "a-zA-Z0-9-", &s))
127 0 : return 0;
128 :
129 0 : if (vec_len (s) != 36)
130 0 : goto fail;
131 :
132 0 : if (s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-')
133 0 : goto fail;
134 :
135 0 : clib_memmove (s + 8, s + 9, 4);
136 0 : clib_memmove (s + 12, s + 14, 4);
137 0 : clib_memmove (s + 16, s + 19, 4);
138 0 : clib_memmove (s + 20, s + 24, 12);
139 :
140 0 : for (int i = 0; i < 32; i++)
141 0 : if (s[i] >= '0' && s[i] <= '9')
142 0 : s[i] -= '0';
143 0 : else if (s[i] >= 'A' && s[i] <= 'F')
144 0 : s[i] -= 'A' - 10;
145 0 : else if (s[i] >= 'a' && s[i] <= 'f')
146 0 : s[i] -= 'a' - 10;
147 : else
148 0 : goto fail;
149 :
150 0 : for (int i = 0; i < 16; i++)
151 0 : addr->guid[i] = s[2 * i] * 16 + s[2 * i + 1];
152 :
153 0 : ret = 1;
154 :
155 0 : fail:
156 0 : vec_free (s);
157 0 : return ret;
158 : }
159 :
160 : /* Convert bus address to standard UUID string */
161 : u8 *
162 0 : format_vlib_vmbus_addr (u8 *s, va_list *va)
163 : {
164 0 : vlib_vmbus_addr_t *addr = va_arg (*va, vlib_vmbus_addr_t *);
165 0 : u8 *bytes = addr->guid;
166 :
167 0 : for (int i = 0; i < 4; i++)
168 0 : s = format (s, "%02X", bytes++[0]);
169 0 : vec_add1 (s, '-');
170 0 : for (int i = 0; i < 2; i++)
171 0 : s = format (s, "%02X", bytes++[0]);
172 0 : vec_add1 (s, '-');
173 0 : for (int i = 0; i < 2; i++)
174 0 : s = format (s, "%02X", bytes++[0]);
175 0 : vec_add1 (s, '-');
176 0 : for (int i = 0; i < 2; i++)
177 0 : s = format (s, "%02X", bytes++[0]);
178 0 : vec_add1 (s, '-');
179 0 : for (int i = 0; i < 6; i++)
180 0 : s = format (s, "%02X", bytes++[0]);
181 :
182 0 : return s;
183 : }
184 :
185 : /* workaround for mlx bug, bring lower device up before unbind */
186 : static clib_error_t *
187 0 : vlib_vmbus_raise_lower (int fd, const char *upper_name)
188 : {
189 0 : clib_error_t *error = 0;
190 : struct dirent *e;
191 : struct ifreq ifr;
192 : u8 *dev_net_dir;
193 : DIR *dir;
194 :
195 0 : clib_memset (&ifr, 0, sizeof (ifr));
196 :
197 0 : dev_net_dir = format (0, "%s/%s%c", sysfs_class_net_path, upper_name, 0);
198 :
199 0 : dir = opendir ((char *) dev_net_dir);
200 :
201 0 : if (!dir)
202 : {
203 0 : error = clib_error_return (0, "VMBUS failed to open %s", dev_net_dir);
204 0 : goto done;
205 : }
206 :
207 0 : while ((e = readdir (dir)))
208 : {
209 : /* look for lower_enXXXX */
210 0 : if (strncmp (e->d_name, "lower_", 6))
211 0 : continue;
212 :
213 0 : strncpy (ifr.ifr_name, e->d_name + 6, IFNAMSIZ - 1);
214 0 : break;
215 : }
216 0 : closedir (dir);
217 :
218 0 : if (!e)
219 0 : goto done; /* no lower device */
220 :
221 0 : if (ioctl (fd, SIOCGIFFLAGS, &ifr) < 0)
222 0 : error = clib_error_return_unix (0, "ioctl fetch intf %s flags",
223 : ifr.ifr_name);
224 0 : else if (!(ifr.ifr_flags & IFF_UP))
225 : {
226 0 : ifr.ifr_flags |= IFF_UP;
227 :
228 0 : if (ioctl (fd, SIOCSIFFLAGS, &ifr) < 0)
229 0 : error = clib_error_return_unix (0, "ioctl set intf %s flags",
230 : ifr.ifr_name);
231 : }
232 0 : done:
233 0 : vec_free (dev_net_dir);
234 0 : return error;
235 : }
236 :
237 : static int
238 0 : directory_exists (char *path)
239 : {
240 0 : struct stat s = { 0 };
241 0 : if (stat (path, &s) == -1)
242 0 : return 0;
243 :
244 0 : return S_ISDIR (s.st_mode);
245 : }
246 :
247 : clib_error_t *
248 0 : vlib_vmbus_bind_to_uio (vlib_vmbus_addr_t * addr)
249 : {
250 0 : clib_error_t *error = 0;
251 : u8 *dev_dir_name;
252 0 : char *ifname = 0;
253 : static int uio_new_id_needed = 1;
254 : struct dirent *e;
255 : struct ifreq ifr;
256 0 : u8 *s = 0, *driver_name;
257 : DIR *dir;
258 : int fd;
259 :
260 0 : dev_dir_name = format (0, "%s/%U", sysfs_vmbus_dev_path,
261 : format_vlib_vmbus_addr, addr);
262 :
263 0 : driver_name = clib_file_get_resolved_basename ("%v/driver", dev_dir_name);
264 :
265 : /* skip if not using the Linux kernel netvsc driver */
266 0 : if (!driver_name || strcmp ("hv_netvsc", (char *) driver_name) != 0)
267 0 : goto done;
268 :
269 : /* if uio_hv_generic is not loaded, then can't use native DPDK driver. */
270 0 : if (!directory_exists ("/sys/module/uio_hv_generic"))
271 0 : goto done;
272 :
273 0 : s = format (s, "%v/net%c", dev_dir_name, 0);
274 0 : dir = opendir ((char *) s);
275 0 : vec_reset_length (s);
276 :
277 0 : if (!dir)
278 0 : return clib_error_return (0, "VMBUS failed to open %s", s);
279 :
280 0 : while ((e = readdir (dir)))
281 : {
282 0 : if (e->d_name[0] == '.') /* skip . and .. */
283 0 : continue;
284 :
285 0 : ifname = strdup (e->d_name);
286 0 : break;
287 : }
288 0 : closedir (dir);
289 :
290 0 : if (!ifname)
291 : {
292 0 : error = clib_error_return (0,
293 : "VMBUS device %U eth not found",
294 : format_vlib_vmbus_addr, addr);
295 0 : goto done;
296 : }
297 :
298 :
299 0 : clib_memset (&ifr, 0, sizeof (ifr));
300 0 : strncpy (ifr.ifr_name, ifname, IFNAMSIZ - 1);
301 :
302 : /* read up/down flags */
303 0 : fd = socket (PF_INET, SOCK_DGRAM, 0);
304 0 : if (fd < 0)
305 : {
306 0 : error = clib_error_return_unix (0, "socket");
307 0 : goto done;
308 : }
309 :
310 0 : if (ioctl (fd, SIOCGIFFLAGS, &ifr) < 0)
311 : {
312 0 : error = clib_error_return_unix (0, "ioctl fetch intf %s flags",
313 : ifr.ifr_name);
314 0 : close (fd);
315 0 : goto done;
316 : }
317 :
318 0 : if (ifr.ifr_flags & IFF_UP)
319 : {
320 0 : error = clib_error_return (
321 : 0, "Skipping VMBUS device %U as host interface %s is up",
322 : format_vlib_vmbus_addr, addr, ifname);
323 0 : close (fd);
324 0 : goto done;
325 : }
326 :
327 : /* tell uio_hv_generic about netvsc device type */
328 0 : if (uio_new_id_needed)
329 : {
330 0 : vec_reset_length (s);
331 0 : s = format (s, "%s/%s/new_id%c", sysfs_vmbus_drv_path, uio_drv_name, 0);
332 0 : error = clib_sysfs_write ((char *) s, "%s", netvsc_uuid);
333 : /* If device already exists, we can bind/unbind/override driver */
334 0 : if (error)
335 : {
336 0 : if (error->code == EEXIST)
337 : {
338 0 : clib_error_free (error);
339 : }
340 : else
341 : {
342 0 : close (fd);
343 0 : goto done;
344 : }
345 : }
346 :
347 0 : uio_new_id_needed = 0;
348 : }
349 :
350 0 : error = vlib_vmbus_raise_lower (fd, ifname);
351 0 : close (fd);
352 :
353 0 : if (error)
354 0 : goto done;
355 :
356 : /* prefer the simplier driver_override model */
357 0 : vec_reset_length (s);
358 0 : s = format (s, "%/driver_override%c", dev_dir_name, 0);
359 0 : if (access ((char *) s, F_OK) == 0)
360 : {
361 0 : clib_sysfs_write ((char *) s, "%s", uio_drv_name);
362 : }
363 : else
364 : {
365 0 : vec_reset_length (s);
366 :
367 0 : s = format (s, "%v/driver/unbind%c", dev_dir_name, 0);
368 : error =
369 0 : clib_sysfs_write ((char *) s, "%U", format_vlib_vmbus_addr, addr);
370 :
371 0 : if (error)
372 0 : goto done;
373 :
374 0 : vec_reset_length (s);
375 :
376 0 : s = format (s, "%s/%s/bind%c", sysfs_vmbus_drv_path, uio_drv_name, 0);
377 : error =
378 0 : clib_sysfs_write ((char *) s, "%U", format_vlib_vmbus_addr, addr);
379 : }
380 0 : vec_reset_length (s);
381 :
382 0 : done:
383 0 : free (ifname);
384 0 : vec_free (s);
385 0 : vec_free (dev_dir_name);
386 0 : vec_free (driver_name);
387 0 : return error;
388 : }
389 :
390 : static clib_error_t *
391 0 : scan_vmbus_addr (void *arg, u8 * dev_dir_name, u8 * ignored)
392 : {
393 0 : vlib_vmbus_addr_t addr, **addrv = arg;
394 : unformat_input_t input;
395 0 : clib_error_t *err = 0;
396 :
397 0 : unformat_init_string (&input, (char *) dev_dir_name,
398 0 : vec_len (dev_dir_name));
399 :
400 0 : if (!unformat (&input, "/sys/bus/vmbus/devices/%U",
401 : unformat_vlib_vmbus_addr, &addr))
402 0 : err = clib_error_return (0, "unformat error `%v`", dev_dir_name);
403 :
404 0 : unformat_free (&input);
405 :
406 0 : if (err)
407 0 : return err;
408 :
409 0 : vec_add1 (*addrv, addr);
410 0 : return 0;
411 : }
412 :
413 : static int
414 0 : vmbus_addr_cmp (void *v1, void *v2)
415 : {
416 0 : vlib_vmbus_addr_t *a1 = v1;
417 0 : vlib_vmbus_addr_t *a2 = v2;
418 :
419 0 : for (int i = 0; i < ARRAY_LEN (a1->guid); i++)
420 0 : if (a1->guid[i] > a2->guid[i])
421 0 : return 1;
422 0 : else if (a1->guid[i] < a2->guid[i])
423 0 : return -1;
424 :
425 0 : return 0;
426 : }
427 :
428 : vlib_vmbus_addr_t *
429 0 : vlib_vmbus_get_all_dev_addrs ()
430 : {
431 0 : vlib_vmbus_addr_t *addrs = 0;
432 : clib_error_t *err;
433 :
434 : err =
435 0 : foreach_directory_file ((char *) sysfs_vmbus_dev_path, scan_vmbus_addr,
436 : &addrs, /* scan_dirs */ 0);
437 0 : if (err)
438 : {
439 0 : vec_free (addrs);
440 0 : return 0;
441 : }
442 :
443 0 : vec_sort_with_function (addrs, vmbus_addr_cmp);
444 :
445 0 : return addrs;
446 : }
447 :
448 : clib_error_t *
449 575 : linux_vmbus_init (vlib_main_t * vm)
450 : {
451 575 : linux_vmbus_main_t *pm = &linux_vmbus_main;
452 :
453 575 : pm->vlib_main = vm;
454 :
455 575 : return 0;
456 : }
457 :
458 : /* *INDENT-OFF* */
459 1727 : VLIB_INIT_FUNCTION (linux_vmbus_init) =
460 : {
461 : .runs_before = VLIB_INITS("unix_input_init"),
462 : };
463 : /* *INDENT-ON* */
464 :
465 : /*
466 : * fd.io coding-style-patch-verification: ON
467 : *
468 : * Local Variables:
469 : * eval: (c-set-style "gnu")
470 : * End:
471 : */
|