vlib: map pci region by using vfio FD when vfio is used
[vpp.git] / src / vlib / linux / pci.c
index 69dcdd0..e127cf4 100644 (file)
 
 static const char *sysfs_pci_dev_path = "/sys/bus/pci/devices";
 static const char *sysfs_pci_drv_path = "/sys/bus/pci/drivers";
+static char *sysfs_mod_vfio_noiommu =
+  "/sys/module/vfio/parameters/enable_unsafe_noiommu_mode";
 
 typedef struct
 {
+  int fd;
+  void *addr;
+  size_t size;
+} linux_pci_region_t;
+
+
+typedef enum
+{
+  LINUX_PCI_DEVICE_TYPE_UNKNOWN,
+  LINUX_PCI_DEVICE_TYPE_UIO,
+  LINUX_PCI_DEVICE_TYPE_VFIO,
+} linux_pci_device_type_t;
+
+typedef struct
+{
+  linux_pci_device_type_t type;
   vlib_pci_dev_handle_t handle;
   vlib_pci_addr_t addr;
 
   /* Resource file descriptors. */
-  int *resource_fds;
+  linux_pci_region_t *regions;
 
   /* File descriptor for config space read/write. */
   int config_fd;
+  u64 config_offset;
 
-  /* File descriptor for /dev/uio%d */
-  int uio_fd;
+  /* Device File descriptor */
+  int fd;
 
   /* Minor device for uio device. */
   u32 uio_minor;
 
-  /* vfio */
-  int vfio_device_fd;
-
   /* Index given by clib_file_add. */
   u32 clib_file_index;
 
@@ -182,10 +198,8 @@ vlib_pci_get_device_info (vlib_pci_addr_t * addr, clib_error_t ** error)
 
   /* You can only read more that 64 bytes of config space as root; so we try to
      read the full space but fall back to just the first 64 bytes. */
-  if (read (fd, &di->config_data, sizeof (di->config_data)) !=
-      sizeof (di->config_data)
-      && read (fd, &di->config0,
-              sizeof (di->config0)) != sizeof (di->config0))
+  if (read (fd, &di->config_data, sizeof (di->config_data)) <
+      sizeof (di->config0))
     {
       err = clib_error_return_unix (0, "read `%s'", f);
       close (fd);
@@ -314,6 +328,16 @@ done:
   return di;
 }
 
+static int
+directory_exists (char *path)
+{
+  struct stat s = { 0 };
+  if (stat (path, &s) == -1)
+    return 0;
+
+  return S_ISDIR (s.st_mode);
+}
+
 clib_error_t *
 vlib_pci_bind_to_uio (vlib_pci_addr_t * addr, char *uio_drv_name)
 {
@@ -331,6 +355,49 @@ vlib_pci_bind_to_uio (vlib_pci_addr_t * addr, char *uio_drv_name)
   if (error)
     return error;
 
+  if (strncmp ("auto", uio_drv_name, 5) == 0)
+    {
+      int vfio_pci_loaded = 0;
+
+      if (directory_exists ("/sys/module/vfio_pci"))
+       vfio_pci_loaded = 1;
+
+      if (di->iommu_group != -1)
+       {
+         /* device is bound to IOMMU group */
+         if (!vfio_pci_loaded)
+           {
+             error = clib_error_return (0, "Skipping PCI device %U: device "
+                                        "is bound to IOMMU group and "
+                                        "vfio-pci driver is not loaded",
+                                        format_vlib_pci_addr, addr);
+             goto done;
+           }
+         else
+           uio_drv_name = "vfio-pci";
+       }
+      else
+       {
+         /* device is not bound to IOMMU group so we have multiple options */
+         if (vfio_pci_loaded &&
+             (error = clib_sysfs_write (sysfs_mod_vfio_noiommu, "Y")) == 0)
+           uio_drv_name = "vfio-pci";
+         else if (directory_exists ("/sys/module/uio_pci_generic"))
+           uio_drv_name = "uio_pci_generic";
+         else if (directory_exists ("/sys/module/igb_uio"))
+           uio_drv_name = "igb_uio";
+         else
+           {
+             clib_error_free (error);
+             error = clib_error_return (0, "Skipping PCI device %U: missing "
+                                        "kernel VFIO or UIO driver",
+                                        format_vlib_pci_addr, addr);
+             goto done;
+           }
+         clib_error_free (error);
+       }
+    }
+
   s = format (s, "%v/driver%c", dev_dir_name, 0);
   driver_name = clib_sysfs_link_to_name ((char *) s);
   vec_reset_length (s);
@@ -493,7 +560,7 @@ linux_pci_vfio_unmask_intx (linux_pci_device_t * d)
     .flags = VFIO_IRQ_SET_DATA_NONE | VFIO_IRQ_SET_ACTION_UNMASK,
   };
 
-  if (ioctl (d->vfio_device_fd, VFIO_DEVICE_SET_IRQS, &i) < 0)
+  if (ioctl (d->fd, VFIO_DEVICE_SET_IRQS, &i) < 0)
     {
       err = clib_error_return_unix (0, "ioctl(VFIO_DEVICE_SET_IRQS) '%U'",
                                    format_vlib_pci_addr, &d->addr);
@@ -544,12 +611,14 @@ add_device_uio (vlib_main_t * vm, linux_pci_device_t * p,
   u8 *s = 0;
 
   p->addr.as_u32 = di->addr.as_u32;
-  p->uio_fd = -1;
+  p->fd = -1;
+  p->type = LINUX_PCI_DEVICE_TYPE_UIO;
 
   s = format (s, "%s/%U/config%c", sysfs_pci_dev_path,
              format_vlib_pci_addr, &di->addr, 0);
 
   p->config_fd = open ((char *) s, O_RDWR);
+  p->config_offset = 0;
   vec_reset_length (s);
 
   if (p->config_fd == -1)
@@ -565,15 +634,15 @@ add_device_uio (vlib_main_t * vm, linux_pci_device_t * p,
   vec_reset_length (s);
 
   s = format (s, "/dev/uio%d%c", p->uio_minor, 0);
-  p->uio_fd = open ((char *) s, O_RDWR);
-  if (p->uio_fd < 0)
+  p->fd = open ((char *) s, O_RDWR);
+  if (p->fd < 0)
     {
       err = clib_error_return_unix (0, "open '%s'", s);
       goto error;
     }
 
   t.read_function = linux_pci_uio_read_ready;
-  t.file_descriptor = p->uio_fd;
+  t.file_descriptor = p->fd;
   t.error_function = linux_pci_uio_error_ready;
   t.private_data = p->handle;
 
@@ -587,8 +656,8 @@ error:
     {
       if (p->config_fd != -1)
        close (p->config_fd);
-      if (p->uio_fd != -1)
-       close (p->uio_fd);
+      if (p->fd != -1)
+       close (p->fd);
     }
   return err;
 }
@@ -677,13 +746,14 @@ add_device_vfio (vlib_main_t * vm, linux_pci_device_t * p,
                 vlib_pci_device_info_t * di, pci_device_registration_t * r)
 {
   linux_pci_vfio_iommu_group_t *g;
-  struct vfio_device_info device_info;
+  struct vfio_device_info device_info = { 0 };
+  struct vfio_region_info reg = { 0 };
   struct vfio_irq_info irq_info = { 0 };
   clib_error_t *err = 0;
   u8 *s = 0;
-  int dfd;
 
   p->addr.as_u32 = di->addr.as_u32;
+  p->type = LINUX_PCI_DEVICE_TYPE_VFIO;
 
   if (di->driver_name == 0 ||
       (strcmp ("vfio-pci", (char *) di->driver_name) != 0))
@@ -697,17 +767,16 @@ add_device_vfio (vlib_main_t * vm, linux_pci_device_t * p,
   g = get_vfio_iommu_group (di->iommu_group);
 
   s = format (s, "%U%c", format_vlib_pci_addr, &di->addr, 0);
-  if ((dfd = ioctl (g->fd, VFIO_GROUP_GET_DEVICE_FD, (char *) s)) < 0)
+  if ((p->fd = ioctl (g->fd, VFIO_GROUP_GET_DEVICE_FD, (char *) s)) < 0)
     {
       err = clib_error_return_unix (0, "ioctl(VFIO_GROUP_GET_DEVICE_FD) '%U'",
                                    format_vlib_pci_addr, &di->addr);
       goto error;
     }
   vec_reset_length (s);
-  p->vfio_device_fd = dfd;
 
   device_info.argsz = sizeof (device_info);
-  if (ioctl (dfd, VFIO_DEVICE_GET_INFO, &device_info) < 0)
+  if (ioctl (p->fd, VFIO_DEVICE_GET_INFO, &device_info) < 0)
     {
       err = clib_error_return_unix (0, "ioctl(VFIO_DEVICE_GET_INFO) '%U'",
                                    format_vlib_pci_addr, &di->addr);
@@ -716,7 +785,7 @@ add_device_vfio (vlib_main_t * vm, linux_pci_device_t * p,
 
   irq_info.argsz = sizeof (struct vfio_irq_info);
   irq_info.index = VFIO_PCI_INTX_IRQ_INDEX;
-  if (ioctl (dfd, VFIO_DEVICE_GET_IRQ_INFO, &irq_info) < 0)
+  if (ioctl (p->fd, VFIO_DEVICE_GET_IRQ_INFO, &irq_info) < 0)
     {
       err = clib_error_return_unix (0, "ioctl(VFIO_DEVICE_GET_IRQ_INFO) "
                                    "'%U'", format_vlib_pci_addr, &di->addr);
@@ -725,7 +794,7 @@ add_device_vfio (vlib_main_t * vm, linux_pci_device_t * p,
 
   /* reset if device supports it */
   if (device_info.flags & VFIO_DEVICE_FLAGS_RESET)
-    if (ioctl (dfd, VFIO_DEVICE_RESET) < 0)
+    if (ioctl (p->fd, VFIO_DEVICE_RESET) < 0)
       {
        err = clib_error_return_unix (0, "ioctl(VFIO_DEVICE_RESET) '%U'",
                                      format_vlib_pci_addr, &di->addr);
@@ -746,7 +815,7 @@ add_device_vfio (vlib_main_t * vm, linux_pci_device_t * p,
        VFIO_IRQ_SET_DATA_EVENTFD | VFIO_IRQ_SET_ACTION_TRIGGER;
       clib_memcpy (&irq_set->data, &efd, sizeof (int));
 
-      if (ioctl (dfd, VFIO_DEVICE_SET_IRQS, irq_set) < 0)
+      if (ioctl (p->fd, VFIO_DEVICE_SET_IRQS, irq_set) < 0)
        {
          err = clib_error_return_unix (0, "ioctl(VFIO_DEVICE_SET_IRQS) '%U'",
                                        format_vlib_pci_addr, &di->addr);
@@ -765,17 +834,16 @@ add_device_vfio (vlib_main_t * vm, linux_pci_device_t * p,
 
   p->interrupt_handler = r->interrupt_handler;
 
-  s = format (s, "%s/%U/config%c", sysfs_pci_dev_path,
-             format_vlib_pci_addr, &di->addr, 0);
-
-  p->config_fd = open ((char *) s, O_RDWR);
-  vec_reset_length (s);
-
-  if (p->config_fd == -1)
+  reg.argsz = sizeof (struct vfio_region_info);
+  reg.index = VFIO_PCI_CONFIG_REGION_INDEX;
+  if (ioctl (p->fd, VFIO_DEVICE_GET_REGION_INFO, &reg) < 0)
     {
-      err = clib_error_return_unix (0, "open '%s'", s);
+      err = clib_error_return_unix (0, "ioctl(VFIO_DEVICE_GET_REGION_INFO) "
+                                   "'%U'", format_vlib_pci_addr, &di->addr);
       goto error;
     }
+  p->config_offset = reg.offset;
+  p->config_fd = p->fd;
 
   err = r->init_function (vm, p->handle);
 
@@ -783,8 +851,8 @@ error:
   vec_free (s);
   if (err)
     {
-      if (dfd != -1)
-       close (dfd);
+      if (p->fd != -1)
+       close (p->fd);
       if (p->config_fd != -1)
        close (p->config_fd);
     }
@@ -801,9 +869,9 @@ vlib_pci_read_write_config (vlib_pci_dev_handle_t h,
   int n;
 
   if (read_or_write == VLIB_READ)
-    n = pread (p->config_fd, data, n_bytes, address);
+    n = pread (p->config_fd, data, n_bytes, p->config_offset + address);
   else
-    n = pwrite (p->config_fd, data, n_bytes, address);
+    n = pwrite (p->config_fd, data, n_bytes, p->config_offset + address);
 
   if (n != n_bytes)
     return clib_error_return_unix (0, "%s",
@@ -814,71 +882,95 @@ vlib_pci_read_write_config (vlib_pci_dev_handle_t h,
 }
 
 static clib_error_t *
-vlib_pci_map_resource_int (vlib_pci_dev_handle_t h,
-                          u32 resource, u8 * addr, void **result)
+vlib_pci_map_region_int (vlib_pci_dev_handle_t h,
+                        u32 bar, u8 * addr, void **result)
 {
   linux_pci_device_t *p = linux_pci_get_device (h);
-  struct stat stat_buf;
-  u8 *file_name;
-  int fd;
+  int fd = -1;
   clib_error_t *error;
   int flags = MAP_SHARED;
+  u64 size = 0, offset = 0;
 
-  error = 0;
+  ASSERT (bar <= 5);
 
-  file_name = format (0, "%s/%U/resource%d%c", sysfs_pci_dev_path,
-                     format_vlib_pci_addr, &p->addr, resource, 0);
+  error = 0;
 
-  fd = open ((char *) file_name, O_RDWR);
-  if (fd < 0)
+  if (p->type == LINUX_PCI_DEVICE_TYPE_UIO)
     {
-      error = clib_error_return_unix (0, "open `%s'", file_name);
-      goto done;
-    }
+      u8 *file_name;
+      struct stat stat_buf;
+      file_name = format (0, "%s/%U/resource%d%c", sysfs_pci_dev_path,
+                         format_vlib_pci_addr, &p->addr, bar, 0);
 
-  if (fstat (fd, &stat_buf) < 0)
-    {
-      error = clib_error_return_unix (0, "fstat `%s'", file_name);
-      goto done;
-    }
+      fd = open ((char *) file_name, O_RDWR);
+      if (fd < 0)
+       {
+         error = clib_error_return_unix (0, "open `%s'", file_name);
+         vec_free (file_name);
+         return error;
+       }
 
-  vec_validate (p->resource_fds, resource);
-  p->resource_fds[resource] = fd;
-  if (addr != 0)
-    flags |= MAP_FIXED;
+      if (fstat (fd, &stat_buf) < 0)
+       {
+         error = clib_error_return_unix (0, "fstat `%s'", file_name);
+         vec_free (file_name);
+         close (fd);
+         return error;
+       }
 
-  *result = mmap (addr,
-                 /* size */ stat_buf.st_size,
-                 PROT_READ | PROT_WRITE, flags,
-                 /* file */ fd,
-                 /* offset */ 0);
-  if (*result == (void *) -1)
+      vec_free (file_name);
+      if (addr != 0)
+       flags |= MAP_FIXED;
+      size = stat_buf.st_size;
+      offset = 0;
+    }
+  else if (p->type == LINUX_PCI_DEVICE_TYPE_VFIO)
     {
-      error = clib_error_return_unix (0, "mmap `%s'", file_name);
-      goto done;
+      struct vfio_region_info reg = { 0 };
+      reg.argsz = sizeof (struct vfio_region_info);
+      reg.index = bar;
+      if (ioctl (p->fd, VFIO_DEVICE_GET_REGION_INFO, &reg) < 0)
+       return clib_error_return_unix (0, "ioctl(VFIO_DEVICE_GET_INFO) "
+                                      "'%U'", format_vlib_pci_addr,
+                                      &p->addr);
+      fd = p->fd;
+      size = reg.size;
+      offset = reg.offset;
     }
+  else
+    ASSERT (0);
 
-done:
-  if (error)
+  *result = mmap (addr, size, PROT_READ | PROT_WRITE, flags, fd, offset);
+  if (*result == (void *) -1)
     {
-      if (fd >= 0)
+      error = clib_error_return_unix (0, "mmap `BAR%u'", bar);
+      if (p->type == LINUX_PCI_DEVICE_TYPE_UIO)
        close (fd);
+      return error;
     }
-  vec_free (file_name);
-  return error;
+
+  /* *INDENT-OFF* */
+  vec_validate_init_empty (p->regions, bar,
+                          (linux_pci_region_t) { .fd = -1});
+  /* *INDENT-ON* */
+  if (p->type == LINUX_PCI_DEVICE_TYPE_UIO)
+    p->regions[bar].fd = fd;
+  p->regions[bar].addr = *result;
+  p->regions[bar].size = size;
+  return 0;
 }
 
 clib_error_t *
-vlib_pci_map_resource (vlib_pci_dev_handle_t h, u32 resource, void **result)
+vlib_pci_map_region (vlib_pci_dev_handle_t h, u32 resource, void **result)
 {
-  return (vlib_pci_map_resource_int (h, resource, 0 /* addr */ , result));
+  return (vlib_pci_map_region_int (h, resource, 0 /* addr */ , result));
 }
 
 clib_error_t *
-vlib_pci_map_resource_fixed (vlib_pci_dev_handle_t h,
-                            u32 resource, u8 * addr, void **result)
+vlib_pci_map_region_fixed (vlib_pci_dev_handle_t h, u32 resource, u8 * addr,
+                          void **result)
 {
-  return (vlib_pci_map_resource_int (h, resource, addr, result));
+  return (vlib_pci_map_region_int (h, resource, addr, result));
 }
 
 void
@@ -899,8 +991,7 @@ init_device_from_registered (vlib_main_t * vm, vlib_pci_device_info_t * di)
   while (r)
     {
       for (i = r->supported_devices; i->vendor_id != 0; i++)
-       if (i->vendor_id == di->config0.header.vendor_id &&
-           i->device_id == di->config0.header.device_id)
+       if (i->vendor_id == di->vendor_id && i->device_id == di->device_id)
          {
            if (di->iommu_group != -1)
              err = add_device_vfio (vm, p, di, r);