blob: 2af62241d4dc875b24c93e000bfce57b20932f58 [file] [log] [blame]
Stephen Hemminger6fbef232018-10-15 12:52:30 -07001/*
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
36static const char sysfs_vmbus_dev_path[] = "/sys/bus/vmbus/devices";
37static const char sysfs_vmbus_drv_path[] = "/sys/bus/vmbus/drivers";
38static const char sysfs_class_net_path[] = "/sys/class/net";
39static const char uio_drv_name[] = "uio_hv_generic";
40static const char netvsc_uuid[] = "f8615163-df3e-46c5-913f-f2d2f965ed0e";
41
42typedef struct
43{
44 int fd;
45 void *addr;
46 size_t size;
47} linux_vmbus_region_t;
48
49typedef struct
50{
51 int fd;
52 u32 clib_file_index;
53} linux_vmbus_irq_t;
54
55typedef 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. */
72typedef struct
73{
74 vlib_main_t *vlib_main;
75 linux_vmbus_device_t *linux_vmbus_devices;
76
77} linux_vmbus_main_t;
78
79linux_vmbus_main_t linux_vmbus_main;
80
81static linux_vmbus_device_t *
82linux_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
88uword
89vlib_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
95void
96vlib_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
102vlib_vmbus_addr_t *
103vlib_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. */
112clib_error_t *vmbus_bus_init (vlib_main_t * vm);
113
114linux_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 */
121static uword
122unformat_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 */
140static u8 *
141format_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 */
151static clib_error_t *
152vlib_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 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);
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 }
197done:
198 vec_free (dev_net_dir);
199 return error;
200}
201
202clib_error_t *
203vlib_vmbus_bind_to_uio (vlib_vmbus_addr_t * addr)
204{
205 clib_error_t *error = 0;
206 u8 *dev_dir_name;
207 char *ifname = 0;
208 static int uio_new_id_needed = 1;
209 struct dirent *e;
210 struct ifreq ifr;
211 u8 *s, *driver_name;
212 DIR *dir;
213 int fd;
214
215 dev_dir_name = format (0, "%s/%U", sysfs_vmbus_dev_path,
216 format_vlib_vmbus_addr, addr);
217 s = format (0, "%v/driver%c", dev_dir_name, 0);
218
219 driver_name = clib_sysfs_link_to_name ((char *) s);
220 vec_reset_length (s);
221
222 /* skip if not using the Linux kernel netvsc driver */
223 if (!driver_name || strcmp ("hv_netvsc", (char *) driver_name) != 0)
224 goto done;
225
226 s = format (s, "%v/net%c", dev_dir_name, 0);
227 dir = opendir ((char *) s);
228 vec_reset_length (s);
229
230 if (!dir)
231 return clib_error_return (0, "VMBUS failed to open %s", s);
232
233 while ((e = readdir (dir)))
234 {
235 if (e->d_name[0] == '.') /* skip . and .. */
236 continue;
237
238 ifname = strdup (e->d_name);
239 break;
240 }
241 closedir (dir);
242
243 if (!ifname)
244 {
245 error = clib_error_return (0,
246 "VMBUS device %U eth not found",
247 format_vlib_vmbus_addr, addr);
248 goto done;
249 }
250
251
252 memset (&ifr, 0, sizeof (ifr));
253 strncpy (ifr.ifr_name, ifname, IFNAMSIZ);
254
255 /* read up/down flags */
256 fd = socket (PF_INET, SOCK_DGRAM, 0);
257 if (fd < 0)
258 {
259 error = clib_error_return_unix (0, "socket");
260 goto done;
261 }
262
263 if (ioctl (fd, SIOCGIFFLAGS, &ifr) < 0)
264 {
265 error = clib_error_return_unix (0, "ioctl fetch intf %s flags",
266 ifr.ifr_name);
267 close (fd);
268 goto done;
269 }
270
271 if (ifr.ifr_flags & IFF_UP)
272 {
273 error = clib_error_return (0,
274 "Skipping VMBUS device %U as host interface %s is up",
275 format_vlib_vmbus_addr, addr, e->d_name);
276 close (fd);
277 goto done;
278 }
279
280 error = vlib_vmbus_raise_lower (fd, ifname);
281 close (fd);
282
283 if (error)
284 goto done;
285
286
287 /* tell uio_hv_generic about netvsc device type */
288 if (uio_new_id_needed)
289 {
290 uio_new_id_needed = 0;
291
292 vec_reset_length (s);
293 s = format (s, "%s/%s/new_id%c", sysfs_vmbus_drv_path, uio_drv_name, 0);
294 error = clib_sysfs_write ((char *) s, "%s", netvsc_uuid);
295
296 if (error)
297 goto done;
298
299 }
300
301 /* prefer the simplier driver_override model */
302 vec_reset_length (s);
303 s = format (s, "%/driver_override%c", dev_dir_name, 0);
304 if (access ((char *) s, F_OK) == 0)
305 {
306 clib_sysfs_write ((char *) s, "%s", uio_drv_name);
307 }
308 else
309 {
310 vec_reset_length (s);
311
312 s = format (s, "%v/driver/unbind%c", dev_dir_name, 0);
313 error =
314 clib_sysfs_write ((char *) s, "%U", format_vlib_vmbus_addr, addr);
315
316 if (error)
317 goto done;
318
319 vec_reset_length (s);
320
321 s = format (s, "%s/%s/bind%c", sysfs_vmbus_drv_path, uio_drv_name, 0);
322 error =
323 clib_sysfs_write ((char *) s, "%U", format_vlib_vmbus_addr, addr);
324 }
325 vec_reset_length (s);
326
327done:
328 free (ifname);
329 vec_free (s);
330 vec_free (dev_dir_name);
331 vec_free (driver_name);
332 return error;
333}
334
335static clib_error_t *
336scan_vmbus_addr (void *arg, u8 * dev_dir_name, u8 * ignored)
337{
338 vlib_vmbus_addr_t addr, **addrv = arg;
339 unformat_input_t input;
340 clib_error_t *err = 0;
341
342 unformat_init_string (&input, (char *) dev_dir_name,
343 vec_len (dev_dir_name));
344
345 if (!unformat (&input, "/sys/bus/vmbus/devices/%U",
346 unformat_vlib_vmbus_addr, &addr))
347 err = clib_error_return (0, "unformat error `%v`", dev_dir_name);
348
349 unformat_free (&input);
350
351 if (err)
352 return err;
353
354 vec_add1 (*addrv, addr);
355 return 0;
356}
357
358static int
359vmbus_addr_cmp (void *v1, void *v2)
360{
361 vlib_vmbus_addr_t *a1 = v1;
362 vlib_vmbus_addr_t *a2 = v2;
363
364 return uuid_compare (a1->guid, a2->guid);
365}
366
367vlib_vmbus_addr_t *
368vlib_vmbus_get_all_dev_addrs ()
369{
370 vlib_vmbus_addr_t *addrs = 0;
371 clib_error_t *err;
372
373 err =
374 foreach_directory_file ((char *) sysfs_vmbus_dev_path, scan_vmbus_addr,
375 &addrs, /* scan_dirs */ 0);
376 if (err)
377 {
378 vec_free (addrs);
379 return 0;
380 }
381
382 vec_sort_with_function (addrs, vmbus_addr_cmp);
383
384 return addrs;
385}
386
387clib_error_t *
388linux_vmbus_init (vlib_main_t * vm)
389{
390 linux_vmbus_main_t *pm = &linux_vmbus_main;
391
392 pm->vlib_main = vm;
393
394 return vlib_call_init_function (vm, unix_input_init);
395}
396
397VLIB_INIT_FUNCTION (linux_vmbus_init);
398
399/*
400 * fd.io coding-style-patch-verification: ON
401 *
402 * Local Variables:
403 * eval: (c-set-style "gnu")
404 * End:
405 */