aae11a1fefb0f49736c180a0d84943133e981584
[vpp.git] / src / vlib / linux / vmbus.c
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 linux_vmbus_get_device (vlib_vmbus_dev_handle_t h)
81 {
82   linux_vmbus_main_t *lpm = &linux_vmbus_main;
83   return pool_elt_at_index (lpm->linux_vmbus_devices, h);
84 }
85
86 uword
87 vlib_vmbus_get_private_data (vlib_vmbus_dev_handle_t h)
88 {
89   linux_vmbus_device_t *d = linux_vmbus_get_device (h);
90   return d->private_data;
91 }
92
93 void
94 vlib_vmbus_set_private_data (vlib_vmbus_dev_handle_t h, uword private_data)
95 {
96   linux_vmbus_device_t *d = linux_vmbus_get_device (h);
97   d->private_data = private_data;
98 }
99
100 vlib_vmbus_addr_t *
101 vlib_vmbus_get_addr (vlib_vmbus_dev_handle_t h)
102 {
103   linux_vmbus_device_t *d = linux_vmbus_get_device (h);
104   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 unformat_vlib_vmbus_addr (unformat_input_t *input, va_list *args)
121 {
122   vlib_vmbus_addr_t *addr = va_arg (*args, vlib_vmbus_addr_t *);
123   uword ret = 0;
124   u8 *s = 0;
125
126   if (!unformat (input, "%U", unformat_token, "a-zA-Z0-9-", &s))
127     return 0;
128
129   if (vec_len (s) != 36)
130     goto fail;
131
132   if (s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-')
133     goto fail;
134
135   clib_memmove (s + 8, s + 9, 4);
136   clib_memmove (s + 12, s + 14, 4);
137   clib_memmove (s + 16, s + 19, 4);
138   clib_memmove (s + 20, s + 24, 12);
139
140   for (int i = 0; i < 32; i++)
141     if (s[i] >= '0' && s[i] <= '9')
142       s[i] -= '0';
143     else if (s[i] >= 'A' && s[i] <= 'F')
144       s[i] -= 'A' - 10;
145     else if (s[i] >= 'a' && s[i] <= 'f')
146       s[i] -= 'a' - 10;
147     else
148       goto fail;
149
150   for (int i = 0; i < 16; i++)
151     addr->guid[i] = s[2 * i] * 16 + s[2 * i + 1];
152
153   ret = 1;
154
155 fail:
156   vec_free (s);
157   return ret;
158 }
159
160 /* Convert bus address to standard UUID string */
161 u8 *
162 format_vlib_vmbus_addr (u8 *s, va_list *va)
163 {
164   vlib_vmbus_addr_t *addr = va_arg (*va, vlib_vmbus_addr_t *);
165   u8 *bytes = addr->guid;
166
167   for (int i = 0; i < 4; i++)
168     s = format (s, "%02X", bytes++[0]);
169   vec_add1 (s, '-');
170   for (int i = 0; i < 2; i++)
171     s = format (s, "%02X", bytes++[0]);
172   vec_add1 (s, '-');
173   for (int i = 0; i < 2; i++)
174     s = format (s, "%02X", bytes++[0]);
175   vec_add1 (s, '-');
176   for (int i = 0; i < 2; i++)
177     s = format (s, "%02X", bytes++[0]);
178   vec_add1 (s, '-');
179   for (int i = 0; i < 6; i++)
180     s = format (s, "%02X", bytes++[0]);
181
182   return s;
183 }
184
185 /* workaround for mlx bug, bring lower device up before unbind */
186 static clib_error_t *
187 vlib_vmbus_raise_lower (int fd, const char *upper_name)
188 {
189   clib_error_t *error = 0;
190   struct dirent *e;
191   struct ifreq ifr;
192   u8 *dev_net_dir;
193   DIR *dir;
194
195   clib_memset (&ifr, 0, sizeof (ifr));
196
197   dev_net_dir = format (0, "%s/%s%c", sysfs_class_net_path, upper_name, 0);
198
199   dir = opendir ((char *) dev_net_dir);
200
201   if (!dir)
202     {
203       error = clib_error_return (0, "VMBUS failed to open %s", dev_net_dir);
204       goto done;
205     }
206
207   while ((e = readdir (dir)))
208     {
209       /* look for lower_enXXXX */
210       if (strncmp (e->d_name, "lower_", 6))
211         continue;
212
213       strncpy (ifr.ifr_name, e->d_name + 6, IFNAMSIZ - 1);
214       break;
215     }
216   closedir (dir);
217
218   if (!e)
219     goto done;                  /* no lower device */
220
221   if (ioctl (fd, SIOCGIFFLAGS, &ifr) < 0)
222     error = clib_error_return_unix (0, "ioctl fetch intf %s flags",
223                                     ifr.ifr_name);
224   else if (!(ifr.ifr_flags & IFF_UP))
225     {
226       ifr.ifr_flags |= IFF_UP;
227
228       if (ioctl (fd, SIOCSIFFLAGS, &ifr) < 0)
229         error = clib_error_return_unix (0, "ioctl set intf %s flags",
230                                         ifr.ifr_name);
231     }
232 done:
233   vec_free (dev_net_dir);
234   return error;
235 }
236
237 static int
238 directory_exists (char *path)
239 {
240   struct stat s = { 0 };
241   if (stat (path, &s) == -1)
242     return 0;
243
244   return S_ISDIR (s.st_mode);
245 }
246
247 clib_error_t *
248 vlib_vmbus_bind_to_uio (vlib_vmbus_addr_t * addr)
249 {
250   clib_error_t *error = 0;
251   u8 *dev_dir_name;
252   char *ifname = 0;
253   static int uio_new_id_needed = 1;
254   struct dirent *e;
255   struct ifreq ifr;
256   u8 *s, *driver_name;
257   DIR *dir;
258   int fd;
259
260   dev_dir_name = format (0, "%s/%U", sysfs_vmbus_dev_path,
261                          format_vlib_vmbus_addr, addr);
262   s = format (0, "%v/driver%c", dev_dir_name, 0);
263
264   driver_name = clib_sysfs_link_to_name ((char *) s);
265   vec_reset_length (s);
266
267   /* skip if not using the Linux kernel netvsc driver */
268   if (!driver_name || strcmp ("hv_netvsc", (char *) driver_name) != 0)
269     goto done;
270
271   /* if uio_hv_generic is not loaded, then can't use native DPDK driver. */
272   if (!directory_exists ("/sys/module/uio_hv_generic"))
273     goto done;
274
275   s = format (s, "%v/net%c", dev_dir_name, 0);
276   dir = opendir ((char *) s);
277   vec_reset_length (s);
278
279   if (!dir)
280     return clib_error_return (0, "VMBUS failed to open %s", s);
281
282   while ((e = readdir (dir)))
283     {
284       if (e->d_name[0] == '.')  /* skip . and .. */
285         continue;
286
287       ifname = strdup (e->d_name);
288       break;
289     }
290   closedir (dir);
291
292   if (!ifname)
293     {
294       error = clib_error_return (0,
295                                  "VMBUS device %U eth not found",
296                                  format_vlib_vmbus_addr, addr);
297       goto done;
298     }
299
300
301   clib_memset (&ifr, 0, sizeof (ifr));
302   strncpy (ifr.ifr_name, ifname, IFNAMSIZ - 1);
303
304   /* read up/down flags */
305   fd = socket (PF_INET, SOCK_DGRAM, 0);
306   if (fd < 0)
307     {
308       error = clib_error_return_unix (0, "socket");
309       goto done;
310     }
311
312   if (ioctl (fd, SIOCGIFFLAGS, &ifr) < 0)
313     {
314       error = clib_error_return_unix (0, "ioctl fetch intf %s flags",
315                                       ifr.ifr_name);
316       close (fd);
317       goto done;
318     }
319
320   if (ifr.ifr_flags & IFF_UP)
321     {
322       error = clib_error_return (
323         0, "Skipping VMBUS device %U as host interface %s is up",
324         format_vlib_vmbus_addr, addr, ifname);
325       close (fd);
326       goto done;
327     }
328
329   /* tell uio_hv_generic about netvsc device type */
330   if (uio_new_id_needed)
331     {
332       vec_reset_length (s);
333       s = format (s, "%s/%s/new_id%c", sysfs_vmbus_drv_path, uio_drv_name, 0);
334       error = clib_sysfs_write ((char *) s, "%s", netvsc_uuid);
335       /* If device already exists, we can bind/unbind/override driver */
336       if (error)
337         {
338           if (error->code == EEXIST)
339             {
340               clib_error_free (error);
341             }
342           else
343             {
344               close (fd);
345               goto done;
346             }
347         }
348
349       uio_new_id_needed = 0;
350     }
351
352   error = vlib_vmbus_raise_lower (fd, ifname);
353   close (fd);
354
355   if (error)
356     goto done;
357
358   /* prefer the simplier driver_override model */
359   vec_reset_length (s);
360   s = format (s, "%/driver_override%c", dev_dir_name, 0);
361   if (access ((char *) s, F_OK) == 0)
362     {
363       clib_sysfs_write ((char *) s, "%s", uio_drv_name);
364     }
365   else
366     {
367       vec_reset_length (s);
368
369       s = format (s, "%v/driver/unbind%c", dev_dir_name, 0);
370       error =
371         clib_sysfs_write ((char *) s, "%U", format_vlib_vmbus_addr, addr);
372
373       if (error)
374         goto done;
375
376       vec_reset_length (s);
377
378       s = format (s, "%s/%s/bind%c", sysfs_vmbus_drv_path, uio_drv_name, 0);
379       error =
380         clib_sysfs_write ((char *) s, "%U", format_vlib_vmbus_addr, addr);
381     }
382   vec_reset_length (s);
383
384 done:
385   free (ifname);
386   vec_free (s);
387   vec_free (dev_dir_name);
388   vec_free (driver_name);
389   return error;
390 }
391
392 static clib_error_t *
393 scan_vmbus_addr (void *arg, u8 * dev_dir_name, u8 * ignored)
394 {
395   vlib_vmbus_addr_t addr, **addrv = arg;
396   unformat_input_t input;
397   clib_error_t *err = 0;
398
399   unformat_init_string (&input, (char *) dev_dir_name,
400                         vec_len (dev_dir_name));
401
402   if (!unformat (&input, "/sys/bus/vmbus/devices/%U",
403                  unformat_vlib_vmbus_addr, &addr))
404     err = clib_error_return (0, "unformat error `%v`", dev_dir_name);
405
406   unformat_free (&input);
407
408   if (err)
409     return err;
410
411   vec_add1 (*addrv, addr);
412   return 0;
413 }
414
415 static int
416 vmbus_addr_cmp (void *v1, void *v2)
417 {
418   vlib_vmbus_addr_t *a1 = v1;
419   vlib_vmbus_addr_t *a2 = v2;
420
421   for (int i = 0; i < ARRAY_LEN (a1->guid); i++)
422     if (a1->guid[i] > a2->guid[i])
423       return 1;
424     else if (a1->guid[i] < a2->guid[i])
425       return -1;
426
427   return 0;
428 }
429
430 vlib_vmbus_addr_t *
431 vlib_vmbus_get_all_dev_addrs ()
432 {
433   vlib_vmbus_addr_t *addrs = 0;
434   clib_error_t *err;
435
436   err =
437     foreach_directory_file ((char *) sysfs_vmbus_dev_path, scan_vmbus_addr,
438                             &addrs, /* scan_dirs */ 0);
439   if (err)
440     {
441       vec_free (addrs);
442       return 0;
443     }
444
445   vec_sort_with_function (addrs, vmbus_addr_cmp);
446
447   return addrs;
448 }
449
450 clib_error_t *
451 linux_vmbus_init (vlib_main_t * vm)
452 {
453   linux_vmbus_main_t *pm = &linux_vmbus_main;
454
455   pm->vlib_main = vm;
456
457   return 0;
458 }
459
460 /* *INDENT-OFF* */
461 VLIB_INIT_FUNCTION (linux_vmbus_init) =
462 {
463   .runs_before = VLIB_INITS("unix_input_init"),
464 };
465 /* *INDENT-ON* */
466
467 /*
468  * fd.io coding-style-patch-verification: ON
469  *
470  * Local Variables:
471  * eval: (c-set-style "gnu")
472  * End:
473  */