vlib: Add a skeleton pci interface for FreeBSD

Implement a subset of the pci interface with unimplemented methods
marked with a comment. This is enough functionality to allow dpdk to
attach to pci devices and forward traffic on FreeBSD.

Type: improvement
Change-Id: I0d87778cd92ae332a6802c11083d3d99988678a1
Signed-off-by: Tom Jones <thj@freebsd.org>
diff --git a/src/vlib/freebsd/pci.c b/src/vlib/freebsd/pci.c
new file mode 100644
index 0000000..a4e9eb2
--- /dev/null
+++ b/src/vlib/freebsd/pci.c
@@ -0,0 +1,380 @@
+/*
+ * SPDX-License-Identifier: Apache-2.0
+ * Copyright (c) 2024 Tom Jones <thj@freebsd.org>
+ *
+ * This software was developed by Tom Jones <thj@freebsd.org> under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ */
+
+#include <vlib/vlib.h>
+#include <vlib/pci/pci.h>
+#include <vlib/unix/unix.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/eventfd.h>
+
+#include <sys/pciio.h>
+
+#include <fcntl.h>
+#include <dirent.h>
+#include <net/if.h>
+
+extern vlib_pci_main_t freebsd_pci_main;
+
+uword
+vlib_pci_get_private_data (vlib_main_t *vm, vlib_pci_dev_handle_t h)
+{
+  return 0;
+}
+
+void
+vlib_pci_set_private_data (vlib_main_t *vm, vlib_pci_dev_handle_t h,
+			   uword private_data)
+{
+}
+
+vlib_pci_addr_t *
+vlib_pci_get_addr (vlib_main_t *vm, vlib_pci_dev_handle_t h)
+{
+  return NULL;
+}
+
+u32
+vlib_pci_get_numa_node (vlib_main_t *vm, vlib_pci_dev_handle_t h)
+{
+  return 0;
+}
+
+u32
+vlib_pci_get_num_msix_interrupts (vlib_main_t *vm, vlib_pci_dev_handle_t h)
+{
+  return 0;
+}
+
+/* Call to allocate/initialize the pci subsystem.
+   This is not an init function so that users can explicitly enable
+   pci only when it's needed. */
+clib_error_t *pci_bus_init (vlib_main_t *vm);
+
+vlib_pci_device_info_t *
+vlib_pci_get_device_info (vlib_main_t *vm, vlib_pci_addr_t *addr,
+			  clib_error_t **error)
+{
+  /* Populate a vlib_pci_device_info_t from the given address */
+  clib_error_t *err = NULL;
+  vlib_pci_device_info_t *di = NULL;
+
+  int fd = -1;
+  struct pci_conf_io pci;
+  struct pci_conf match;
+  struct pci_match_conf pattern;
+  bzero (&match, sizeof (match));
+  bzero (&pattern, sizeof (pattern));
+
+  pattern.pc_sel.pc_domain = addr->domain;
+  pattern.pc_sel.pc_bus = addr->bus;
+  pattern.pc_sel.pc_dev = addr->slot;
+  pattern.pc_sel.pc_func = addr->function;
+  pattern.flags = PCI_GETCONF_MATCH_DOMAIN | PCI_GETCONF_MATCH_BUS |
+		  PCI_GETCONF_MATCH_DEV | PCI_GETCONF_MATCH_FUNC;
+
+  pci.pat_buf_len = sizeof (pattern);
+  pci.num_patterns = 1;
+  pci.patterns = &pattern;
+  pci.match_buf_len = sizeof (match);
+  pci.num_matches = 1;
+  pci.matches = &match;
+  pci.offset = 0;
+  pci.generation = 0;
+  pci.status = 0;
+
+  fd = open ("/dev/pci", 0);
+  if (fd == -1)
+    {
+      err = clib_error_return_unix (0, "open '/dev/pci'");
+      goto error;
+    }
+
+  if (ioctl (fd, PCIOCGETCONF, &pci) == -1)
+    {
+      err = clib_error_return_unix (0, "reading PCIOCGETCONF");
+      goto error;
+    }
+
+  di = clib_mem_alloc (sizeof (vlib_pci_device_info_t));
+  clib_memset (di, 0, sizeof (vlib_pci_device_info_t));
+
+  di->addr.as_u32 = addr->as_u32;
+  di->numa_node = 0; /* TODO: Place holder until we have NUMA on FreeBSD */
+
+  di->device_class = match.pc_class;
+  di->vendor_id = match.pc_vendor;
+  di->device_id = match.pc_device;
+  di->revision = match.pc_revid;
+
+  di->product_name = NULL;
+  di->vpd_r = 0;
+  di->vpd_w = 0;
+  di->driver_name = format (0, "%s", &match.pd_name);
+  di->iommu_group = -1;
+
+  goto done;
+
+error:
+  vlib_pci_free_device_info (di);
+  di = NULL;
+done:
+  if (error)
+    *error = err;
+  close (fd);
+  return di;
+}
+
+clib_error_t *__attribute__ ((weak))
+vlib_pci_get_device_root_bus (vlib_pci_addr_t *addr, vlib_pci_addr_t *root_bus)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_bind_to_uio (vlib_main_t *vm, vlib_pci_addr_t *addr,
+		      char *uio_drv_name, int force)
+{
+  clib_error_t *error = 0;
+
+  if (error)
+    {
+      return error;
+    }
+
+  if (strncmp ("auto", uio_drv_name, 5) == 0)
+    {
+      /* TODO: We should confirm that nic_uio is loaded and return an error. */
+      uio_drv_name = "nic_uio";
+    }
+  return error;
+}
+
+clib_error_t *
+vlib_pci_register_intx_handler (vlib_main_t *vm, vlib_pci_dev_handle_t h,
+				pci_intx_handler_function_t *intx_handler)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_unregister_intx_handler (vlib_main_t *vm, vlib_pci_dev_handle_t h)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_register_msix_handler (vlib_main_t *vm, vlib_pci_dev_handle_t h,
+				u32 start, u32 count,
+				pci_msix_handler_function_t *msix_handler)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_unregister_msix_handler (vlib_main_t *vm, vlib_pci_dev_handle_t h,
+				  u32 start, u32 count)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_enable_msix_irq (vlib_main_t *vm, vlib_pci_dev_handle_t h, u16 start,
+			  u16 count)
+{
+  return NULL;
+}
+
+uword
+vlib_pci_get_msix_file_index (vlib_main_t *vm, vlib_pci_dev_handle_t h,
+			      u16 index)
+{
+  return 0;
+}
+
+clib_error_t *
+vlib_pci_disable_msix_irq (vlib_main_t *vm, vlib_pci_dev_handle_t h, u16 start,
+			   u16 count)
+{
+  return NULL;
+}
+
+/* Configuration space read/write. */
+clib_error_t *
+vlib_pci_read_write_config (vlib_main_t *vm, vlib_pci_dev_handle_t h,
+			    vlib_read_or_write_t read_or_write, uword address,
+			    void *data, u32 n_bytes)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_map_region (vlib_main_t *vm, vlib_pci_dev_handle_t h, u32 resource,
+		     void **result)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_map_region_fixed (vlib_main_t *vm, vlib_pci_dev_handle_t h,
+			   u32 resource, u8 *addr, void **result)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_io_region (vlib_main_t *vm, vlib_pci_dev_handle_t h, u32 resource)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_read_write_io (vlib_main_t *vm, vlib_pci_dev_handle_t h,
+			vlib_read_or_write_t read_or_write, uword offset,
+			void *data, u32 length)
+{
+  return NULL;
+}
+
+clib_error_t *
+vlib_pci_map_dma (vlib_main_t *vm, vlib_pci_dev_handle_t h, void *ptr)
+{
+  return NULL;
+}
+
+int
+vlib_pci_supports_virtual_addr_dma (vlib_main_t *vm, vlib_pci_dev_handle_t h)
+{
+  return 0;
+}
+
+clib_error_t *
+vlib_pci_device_open (vlib_main_t *vm, vlib_pci_addr_t *addr,
+		      pci_device_id_t ids[], vlib_pci_dev_handle_t *handle)
+{
+  return NULL;
+}
+
+void
+vlib_pci_device_close (vlib_main_t *vm, vlib_pci_dev_handle_t h)
+{
+}
+
+void
+init_device_from_registered (vlib_main_t *vm, vlib_pci_device_info_t *di)
+{
+}
+
+static int
+pci_addr_cmp (void *v1, void *v2)
+{
+  vlib_pci_addr_t *a1 = v1;
+  vlib_pci_addr_t *a2 = v2;
+
+  if (a1->domain > a2->domain)
+    return 1;
+  if (a1->domain < a2->domain)
+    return -1;
+  if (a1->bus > a2->bus)
+    return 1;
+  if (a1->bus < a2->bus)
+    return -1;
+  if (a1->slot > a2->slot)
+    return 1;
+  if (a1->slot < a2->slot)
+    return -1;
+  if (a1->function > a2->function)
+    return 1;
+  if (a1->function < a2->function)
+    return -1;
+  return 0;
+}
+
+vlib_pci_addr_t *
+vlib_pci_get_all_dev_addrs ()
+{
+  vlib_pci_addr_t *addrs = 0;
+
+  int fd = -1;
+  struct pci_conf_io pci;
+  struct pci_conf matches[32];
+  bzero (matches, sizeof (matches));
+
+  pci.pat_buf_len = 0;
+  pci.num_patterns = 0;
+  pci.patterns = NULL;
+  pci.match_buf_len = sizeof (matches);
+  pci.num_matches = 32;
+  pci.matches = (struct pci_conf *) &matches;
+  pci.offset = 0;
+  pci.generation = 0;
+  pci.status = 0;
+
+  fd = open ("/dev/pci", 0);
+  if (fd == -1)
+    {
+      clib_error_return_unix (0, "opening /dev/pci");
+      return (NULL);
+    }
+
+  if (ioctl (fd, PCIOCGETCONF, &pci) == -1)
+    {
+      clib_error_return_unix (0, "reading pci config");
+      close (fd);
+      return (NULL);
+    }
+
+  for (int i = 0; i < pci.num_matches; i++)
+    {
+      struct pci_conf *m = &pci.matches[i];
+      vlib_pci_addr_t addr;
+
+      addr.domain = m->pc_sel.pc_domain;
+      addr.bus = m->pc_sel.pc_bus;
+      addr.slot = m->pc_sel.pc_dev;
+      addr.function = m->pc_sel.pc_func;
+
+      vec_add1 (addrs, addr);
+    }
+
+  vec_sort_with_function (addrs, pci_addr_cmp);
+  close (fd);
+
+  return addrs;
+}
+
+clib_error_t *
+freebsd_pci_init (vlib_main_t *vm)
+{
+  vlib_pci_main_t *pm = &pci_main;
+  vlib_pci_addr_t *addr = 0, *addrs;
+
+  pm->vlib_main = vm;
+
+  ASSERT (sizeof (vlib_pci_addr_t) == sizeof (u32));
+
+  addrs = vlib_pci_get_all_dev_addrs ();
+  vec_foreach (addr, addrs)
+    {
+      vlib_pci_device_info_t *d;
+      if ((d = vlib_pci_get_device_info (vm, addr, 0)))
+	{
+	  init_device_from_registered (vm, d);
+	  vlib_pci_free_device_info (d);
+	}
+    }
+
+  return 0;
+}
+
+VLIB_INIT_FUNCTION (freebsd_pci_init) = {
+  .runs_after = VLIB_INITS ("unix_input_init"),
+};