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