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