| /* |
| * Copyright (c) 2018, Microsoft Corporation. |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| /* |
| * vmbus.c: Linux user space VMBus bus management. |
| */ |
| |
| #include <vppinfra/linux/sysfs.h> |
| |
| #include <vlib/vlib.h> |
| #include <vlib/vmbus/vmbus.h> |
| #include <vlib/unix/unix.h> |
| |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <fcntl.h> |
| #include <dirent.h> |
| #include <sys/ioctl.h> |
| #include <net/if.h> |
| #include <linux/ethtool.h> |
| #include <linux/sockios.h> |
| |
| #include <uuid/uuid.h> |
| |
| static const char sysfs_vmbus_dev_path[] = "/sys/bus/vmbus/devices"; |
| static const char sysfs_vmbus_drv_path[] = "/sys/bus/vmbus/drivers"; |
| static const char sysfs_class_net_path[] = "/sys/class/net"; |
| static const char uio_drv_name[] = "uio_hv_generic"; |
| static const char netvsc_uuid[] = "f8615163-df3e-46c5-913f-f2d2f965ed0e"; |
| |
| typedef struct |
| { |
| int fd; |
| void *addr; |
| size_t size; |
| } linux_vmbus_region_t; |
| |
| typedef struct |
| { |
| int fd; |
| u32 clib_file_index; |
| } linux_vmbus_irq_t; |
| |
| typedef struct |
| { |
| vlib_vmbus_dev_handle_t handle; |
| vlib_vmbus_addr_t addr; |
| |
| /* Device File descriptor */ |
| int fd; |
| |
| /* Minor device for uio device. */ |
| u32 uio_minor; |
| |
| /* private data */ |
| uword private_data; |
| |
| } linux_vmbus_device_t; |
| |
| /* Pool of VMBUS devices. */ |
| typedef struct |
| { |
| vlib_main_t *vlib_main; |
| linux_vmbus_device_t *linux_vmbus_devices; |
| |
| } linux_vmbus_main_t; |
| |
| linux_vmbus_main_t linux_vmbus_main; |
| |
| static linux_vmbus_device_t * |
| linux_vmbus_get_device (vlib_vmbus_dev_handle_t h) |
| { |
| linux_vmbus_main_t *lpm = &linux_vmbus_main; |
| return pool_elt_at_index (lpm->linux_vmbus_devices, h); |
| } |
| |
| uword |
| vlib_vmbus_get_private_data (vlib_vmbus_dev_handle_t h) |
| { |
| linux_vmbus_device_t *d = linux_vmbus_get_device (h); |
| return d->private_data; |
| } |
| |
| void |
| vlib_vmbus_set_private_data (vlib_vmbus_dev_handle_t h, uword private_data) |
| { |
| linux_vmbus_device_t *d = linux_vmbus_get_device (h); |
| d->private_data = private_data; |
| } |
| |
| vlib_vmbus_addr_t * |
| vlib_vmbus_get_addr (vlib_vmbus_dev_handle_t h) |
| { |
| linux_vmbus_device_t *d = linux_vmbus_get_device (h); |
| return &d->addr; |
| } |
| |
| /* Call to allocate/initialize the vmbus subsystem. |
| This is not an init function so that users can explicitly enable |
| vmbus only when it's needed. */ |
| clib_error_t *vmbus_bus_init (vlib_main_t * vm); |
| |
| linux_vmbus_main_t linux_vmbus_main; |
| |
| /* |
| * Take VMBus address represented in standard form like: |
| * "f2c086b2-ff2e-11e8-88de-7bad0a57de05" and convert |
| * it to u8[16] |
| */ |
| static uword |
| unformat_vlib_vmbus_addr (unformat_input_t * input, va_list * args) |
| { |
| vlib_vmbus_addr_t *addr = va_arg (*args, vlib_vmbus_addr_t *); |
| uword ret = 0; |
| u8 *s; |
| |
| if (!unformat (input, "%s", &s)) |
| return 0; |
| |
| if (uuid_parse ((char *) s, addr->guid) == 0) |
| ret = 1; |
| |
| vec_free (s); |
| |
| return ret; |
| } |
| |
| /* Convert bus address to standard UUID string */ |
| static u8 * |
| format_vlib_vmbus_addr (u8 * s, va_list * va) |
| { |
| vlib_vmbus_addr_t *addr = va_arg (*va, vlib_vmbus_addr_t *); |
| char tmp[40]; |
| |
| uuid_unparse (addr->guid, tmp); |
| return format (s, "%s", tmp); |
| } |
| |
| /* workaround for mlx bug, bring lower device up before unbind */ |
| static clib_error_t * |
| vlib_vmbus_raise_lower (int fd, const char *upper_name) |
| { |
| clib_error_t *error = 0; |
| struct dirent *e; |
| struct ifreq ifr; |
| u8 *dev_net_dir; |
| DIR *dir; |
| |
| memset (&ifr, 0, sizeof (ifr)); |
| |
| dev_net_dir = format (0, "%s/%s%c", sysfs_class_net_path, upper_name, 0); |
| |
| dir = opendir ((char *) dev_net_dir); |
| |
| if (!dir) |
| { |
| error = clib_error_return (0, "VMBUS failed to open %s", dev_net_dir); |
| goto done; |
| } |
| |
| while ((e = readdir (dir))) |
| { |
| /* look for lower_enXXXX */ |
| if (strncmp (e->d_name, "lower_", 6)) |
| continue; |
| |
| strncpy (ifr.ifr_name, e->d_name + 6, IFNAMSIZ); |
| break; |
| } |
| closedir (dir); |
| |
| if (!e) |
| goto done; /* no lower device */ |
| |
| if (ioctl (fd, SIOCGIFFLAGS, &ifr) < 0) |
| error = clib_error_return_unix (0, "ioctl fetch intf %s flags", |
| ifr.ifr_name); |
| else if (!(ifr.ifr_flags & IFF_UP)) |
| { |
| ifr.ifr_flags |= IFF_UP; |
| |
| if (ioctl (fd, SIOCSIFFLAGS, &ifr) < 0) |
| error = clib_error_return_unix (0, "ioctl set intf %s flags", |
| ifr.ifr_name); |
| } |
| done: |
| vec_free (dev_net_dir); |
| return error; |
| } |
| |
| clib_error_t * |
| vlib_vmbus_bind_to_uio (vlib_vmbus_addr_t * addr) |
| { |
| clib_error_t *error = 0; |
| u8 *dev_dir_name; |
| char *ifname = 0; |
| static int uio_new_id_needed = 1; |
| struct dirent *e; |
| struct ifreq ifr; |
| u8 *s, *driver_name; |
| DIR *dir; |
| int fd; |
| |
| dev_dir_name = format (0, "%s/%U", sysfs_vmbus_dev_path, |
| format_vlib_vmbus_addr, addr); |
| s = format (0, "%v/driver%c", dev_dir_name, 0); |
| |
| driver_name = clib_sysfs_link_to_name ((char *) s); |
| vec_reset_length (s); |
| |
| /* skip if not using the Linux kernel netvsc driver */ |
| if (!driver_name || strcmp ("hv_netvsc", (char *) driver_name) != 0) |
| goto done; |
| |
| s = format (s, "%v/net%c", dev_dir_name, 0); |
| dir = opendir ((char *) s); |
| vec_reset_length (s); |
| |
| if (!dir) |
| return clib_error_return (0, "VMBUS failed to open %s", s); |
| |
| while ((e = readdir (dir))) |
| { |
| if (e->d_name[0] == '.') /* skip . and .. */ |
| continue; |
| |
| ifname = strdup (e->d_name); |
| break; |
| } |
| closedir (dir); |
| |
| if (!ifname) |
| { |
| error = clib_error_return (0, |
| "VMBUS device %U eth not found", |
| format_vlib_vmbus_addr, addr); |
| goto done; |
| } |
| |
| |
| memset (&ifr, 0, sizeof (ifr)); |
| strncpy (ifr.ifr_name, ifname, IFNAMSIZ); |
| |
| /* read up/down flags */ |
| fd = socket (PF_INET, SOCK_DGRAM, 0); |
| if (fd < 0) |
| { |
| error = clib_error_return_unix (0, "socket"); |
| goto done; |
| } |
| |
| if (ioctl (fd, SIOCGIFFLAGS, &ifr) < 0) |
| { |
| error = clib_error_return_unix (0, "ioctl fetch intf %s flags", |
| ifr.ifr_name); |
| close (fd); |
| goto done; |
| } |
| |
| if (ifr.ifr_flags & IFF_UP) |
| { |
| error = clib_error_return (0, |
| "Skipping VMBUS device %U as host interface %s is up", |
| format_vlib_vmbus_addr, addr, e->d_name); |
| close (fd); |
| goto done; |
| } |
| |
| error = vlib_vmbus_raise_lower (fd, ifname); |
| close (fd); |
| |
| if (error) |
| goto done; |
| |
| |
| /* tell uio_hv_generic about netvsc device type */ |
| if (uio_new_id_needed) |
| { |
| uio_new_id_needed = 0; |
| |
| vec_reset_length (s); |
| s = format (s, "%s/%s/new_id%c", sysfs_vmbus_drv_path, uio_drv_name, 0); |
| error = clib_sysfs_write ((char *) s, "%s", netvsc_uuid); |
| |
| if (error) |
| goto done; |
| |
| } |
| |
| /* prefer the simplier driver_override model */ |
| vec_reset_length (s); |
| s = format (s, "%/driver_override%c", dev_dir_name, 0); |
| if (access ((char *) s, F_OK) == 0) |
| { |
| clib_sysfs_write ((char *) s, "%s", uio_drv_name); |
| } |
| else |
| { |
| vec_reset_length (s); |
| |
| s = format (s, "%v/driver/unbind%c", dev_dir_name, 0); |
| error = |
| clib_sysfs_write ((char *) s, "%U", format_vlib_vmbus_addr, addr); |
| |
| if (error) |
| goto done; |
| |
| vec_reset_length (s); |
| |
| s = format (s, "%s/%s/bind%c", sysfs_vmbus_drv_path, uio_drv_name, 0); |
| error = |
| clib_sysfs_write ((char *) s, "%U", format_vlib_vmbus_addr, addr); |
| } |
| vec_reset_length (s); |
| |
| done: |
| free (ifname); |
| vec_free (s); |
| vec_free (dev_dir_name); |
| vec_free (driver_name); |
| return error; |
| } |
| |
| static clib_error_t * |
| scan_vmbus_addr (void *arg, u8 * dev_dir_name, u8 * ignored) |
| { |
| vlib_vmbus_addr_t addr, **addrv = arg; |
| unformat_input_t input; |
| clib_error_t *err = 0; |
| |
| unformat_init_string (&input, (char *) dev_dir_name, |
| vec_len (dev_dir_name)); |
| |
| if (!unformat (&input, "/sys/bus/vmbus/devices/%U", |
| unformat_vlib_vmbus_addr, &addr)) |
| err = clib_error_return (0, "unformat error `%v`", dev_dir_name); |
| |
| unformat_free (&input); |
| |
| if (err) |
| return err; |
| |
| vec_add1 (*addrv, addr); |
| return 0; |
| } |
| |
| static int |
| vmbus_addr_cmp (void *v1, void *v2) |
| { |
| vlib_vmbus_addr_t *a1 = v1; |
| vlib_vmbus_addr_t *a2 = v2; |
| |
| return uuid_compare (a1->guid, a2->guid); |
| } |
| |
| vlib_vmbus_addr_t * |
| vlib_vmbus_get_all_dev_addrs () |
| { |
| vlib_vmbus_addr_t *addrs = 0; |
| clib_error_t *err; |
| |
| err = |
| foreach_directory_file ((char *) sysfs_vmbus_dev_path, scan_vmbus_addr, |
| &addrs, /* scan_dirs */ 0); |
| if (err) |
| { |
| vec_free (addrs); |
| return 0; |
| } |
| |
| vec_sort_with_function (addrs, vmbus_addr_cmp); |
| |
| return addrs; |
| } |
| |
| clib_error_t * |
| linux_vmbus_init (vlib_main_t * vm) |
| { |
| linux_vmbus_main_t *pm = &linux_vmbus_main; |
| |
| pm->vlib_main = vm; |
| |
| return vlib_call_init_function (vm, unix_input_init); |
| } |
| |
| VLIB_INIT_FUNCTION (linux_vmbus_init); |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |