Platform bus is a software bus under Linux that manages devices which
generally do not have built-in discovery mechanisms. Linux normally
learns about platform devices directly from device-tree during
boot-up phase.

Up to this point, whenever some userspace app needed control over
platform device or a range of thereof some sort of driver being
a mixture of vdev/rawdev was required.

In order to simplify this task, provide an auto-probe
experience and separate bus logic from the driver itself,
add platform bus support.

Signed-off-by: Tomasz Duszynski <tduszyn...@marvell.com>
---
 drivers/bus/meson.build                 |   1 +
 drivers/bus/platform/meson.build        |  17 +
 drivers/bus/platform/platform.c         | 394 ++++++++++++++++++++++++
 drivers/bus/platform/platform_params.c  |  70 +++++
 drivers/bus/platform/private.h          |  47 +++
 drivers/bus/platform/rte_bus_platform.h | 165 ++++++++++
 drivers/bus/platform/version.map        |  10 +
 7 files changed, 704 insertions(+)
 create mode 100644 drivers/bus/platform/meson.build
 create mode 100644 drivers/bus/platform/platform.c
 create mode 100644 drivers/bus/platform/platform_params.c
 create mode 100644 drivers/bus/platform/private.h
 create mode 100644 drivers/bus/platform/rte_bus_platform.h
 create mode 100644 drivers/bus/platform/version.map

diff --git a/drivers/bus/meson.build b/drivers/bus/meson.build
index 45eab5233d..6d2520c543 100644
--- a/drivers/bus/meson.build
+++ b/drivers/bus/meson.build
@@ -7,6 +7,7 @@ drivers = [
         'fslmc',
         'ifpga',
         'pci',
+        'platform',
         'vdev',
         'vmbus',
 ]
diff --git a/drivers/bus/platform/meson.build b/drivers/bus/platform/meson.build
new file mode 100644
index 0000000000..b51d757a07
--- /dev/null
+++ b/drivers/bus/platform/meson.build
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(C) 2023 Marvell.
+#
+
+if not is_linux
+  build = false
+  reason = 'only supported on Linux'
+endif
+
+deps += ['kvargs']
+sources = files(
+        'platform_params.c',
+        'platform.c',
+)
+headers = files(
+        'rte_bus_platform.h',
+)
diff --git a/drivers/bus/platform/platform.c b/drivers/bus/platform/platform.c
new file mode 100644
index 0000000000..d489dde7ed
--- /dev/null
+++ b/drivers/bus/platform/platform.c
@@ -0,0 +1,394 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell.
+ */
+
+#include <dirent.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/queue.h>
+#include <unistd.h>
+
+#include <rte_bus.h>
+#include <rte_bus_platform.h>
+#include <rte_devargs.h>
+#include <rte_errno.h>
+#include <rte_log.h>
+#include <rte_memory.h>
+#include <rte_string_fns.h>
+
+#include "eal_filesystem.h"
+#include "private.h"
+
+#define PLATFORM_BUS_DEVICES_PATH "/sys/bus/platform/devices"
+
+void
+rte_platform_register(struct rte_platform_driver *pdrv)
+{
+       TAILQ_INSERT_TAIL(&platform_bus.driver_list, pdrv, next);
+}
+
+void
+rte_platform_unregister(struct rte_platform_driver *pdrv)
+{
+       TAILQ_REMOVE(&platform_bus.driver_list, pdrv, next);
+}
+
+static int
+parse_sysfs_value(const char *path, unsigned long *val)
+{
+       if (access(path, F_OK))
+               return -1;
+
+       return eal_parse_sysfs_value(path, val);
+}
+
+static struct rte_devargs *
+device_devargs(const char *dev_name)
+{
+       struct rte_devargs *devargs;
+
+       RTE_EAL_DEVARGS_FOREACH("platform", devargs) {
+               if (!strcmp(devargs->name, dev_name))
+                       return devargs;
+       }
+
+       return NULL;
+}
+
+static bool
+device_allowed(const char *dev_name)
+{
+       struct rte_devargs *devargs;
+
+       devargs = device_devargs(dev_name);
+       if (!devargs)
+               return true;
+
+       switch (platform_bus.bus.conf.scan_mode) {
+       case RTE_BUS_SCAN_UNDEFINED:
+       case RTE_BUS_SCAN_ALLOWLIST:
+               if (devargs->policy == RTE_DEV_ALLOWED)
+                       return true;
+               break;
+       case RTE_BUS_SCAN_BLOCKLIST:
+               if (devargs->policy == RTE_DEV_BLOCKED)
+                       return false;
+               break;
+       }
+
+       return true;
+}
+
+static struct rte_platform_driver *
+device_lookup_driver(const char *dev_name)
+{
+       struct rte_platform_driver *pdrv = NULL;
+       char buf[BUFSIZ] = { };
+       char path[PATH_MAX];
+       char *p;
+       int ret;
+
+       snprintf(path, sizeof(path), PLATFORM_BUS_DEVICES_PATH "/%s/driver", 
dev_name);
+       /* save space for terminating null byte */
+       ret = readlink(path, buf, sizeof(buf) - 1);
+       if (ret <= 0)
+               return NULL;
+
+       /* last token is kernel driver name */
+       p = strrchr(buf, '/');
+       if (!p++)
+               return NULL;
+
+       FOREACH_DRIVER_ON_PLATFORM_BUS(pdrv) {
+               /* match by driver name */
+               if (!strcmp(p, pdrv->driver.name))
+                       break;
+
+               /* match by driver alias */
+               if (pdrv->driver.alias) {
+                       if (!strcmp(p, pdrv->driver.alias))
+                               break;
+               }
+
+               /* match by device name */
+               if (!strcmp(dev_name, pdrv->driver.name))
+                       break;
+       }
+
+       return pdrv;
+}
+
+static int
+device_add(const char *dev_name)
+{
+       struct rte_platform_device *pdev;
+       char path[PATH_MAX];
+       unsigned long val;
+
+       pdev = calloc(1, sizeof(*pdev));
+       if (!pdev)
+               return -ENOMEM;
+
+       strncpy(pdev->name, dev_name, RTE_DEV_NAME_MAX_LEN);
+       pdev->device.name = pdev->name;
+       pdev->device.bus = &platform_bus.bus;
+
+       snprintf(path, sizeof(path), PLATFORM_BUS_DEVICES_PATH "/%s/numa_node", 
dev_name);
+       pdev->device.numa_node = parse_sysfs_value(path, &val) ? 
rte_socket_id() : val;
+
+       TAILQ_INSERT_HEAD(&platform_bus.device_list, pdev, next);
+
+       PLATFORM_LOG(INFO, "adding %s to the platform devices list\n", 
dev_name);
+
+       return 0;
+}
+
+static int
+platform_bus_scan(void)
+{
+       const struct dirent *ent;
+       const char *name;
+       int ret = 0;
+       DIR *dp;
+
+       if (!(dp = opendir(PLATFORM_BUS_DEVICES_PATH))) {
+               PLATFORM_LOG(INFO, "failed to open %s\n", 
PLATFORM_BUS_DEVICES_PATH);
+
+               return -errno;
+       }
+
+       while ((ent = readdir(dp))) {
+               name = ent->d_name;
+
+               if (name[0] == '.')
+                       continue;
+
+               if (!device_allowed(name))
+                       continue;
+
+               if (!device_lookup_driver(name))
+                       continue;
+
+               ret = device_add(name);
+               if (ret)
+                       break;
+       }
+
+       closedir(dp);
+
+       return ret;
+}
+
+static int
+platform_probe(struct rte_platform_device *pdev)
+{
+       struct rte_platform_driver *pdrv;
+       enum rte_iova_mode iova_mode;
+       int ret;
+
+       if (!pdev)
+               return -EINVAL;
+
+       pdrv = device_lookup_driver(pdev->name);
+       if (!pdrv)
+               return -ENODEV;
+
+       iova_mode = rte_eal_iova_mode();
+       if (pdrv->drv_flags & RTE_PLATFORM_DRV_NEED_IOVA_AS_VA && iova_mode != 
RTE_IOVA_VA) {
+               PLATFORM_LOG(ERR, "Driver %s expects VA IOVA mode but current 
mode is PA\n",
+                            pdrv->driver.name);
+
+               return -EINVAL;
+       }
+
+       if (!pdrv->probe)
+               return -ENOTSUP;
+
+       pdev->driver = pdrv;
+
+       ret = pdrv->probe(pdev);
+       if (ret)
+               return ret;
+
+       pdev->device.driver = &pdrv->driver;
+
+       return 0;
+}
+
+static int
+platform_remove(struct rte_platform_device *pdev)
+{
+       struct rte_platform_driver *pdrv;
+       int ret;
+
+       if (!pdev)
+               return -EINVAL;
+
+       pdrv = pdev->driver;
+       if (pdrv->remove) {
+               ret = pdrv->remove(pdev);
+               if (ret)
+                       return ret;
+       }
+
+       pdev->driver = NULL;
+       pdev->device.driver = NULL;
+
+       TAILQ_REMOVE(&platform_bus.device_list, pdev, next);
+       rte_devargs_remove(pdev->device.devargs);
+       free(pdrv);
+
+       return 0;
+}
+
+static int
+platform_bus_probe(void)
+{
+       struct rte_platform_device *pdev;
+       int ret;
+
+       FOREACH_DEVICE_ON_PLATFORM_BUS(pdev) {
+               ret = platform_probe(pdev);
+               if (ret)
+                       PLATFORM_LOG(ERR, "failed to probe %s\n", pdev->name);
+       }
+
+       return 0;
+}
+
+static struct rte_device *
+platform_bus_find_device(const struct rte_device *start, rte_dev_cmp_t cmp, 
const void *data)
+{
+       struct rte_platform_device *pdev;
+
+       pdev = start ? RTE_TAILQ_NEXT(RTE_DEV_TO_PLATFORM_DEV_CONST(start), 
next) :
+                      RTE_TAILQ_FIRST(&platform_bus.device_list);
+       while (pdev) {
+               if (cmp(&pdev->device, data) == 0)
+                       return &pdev->device;
+
+               pdev = RTE_TAILQ_NEXT(pdev, next);
+       }
+
+       return NULL;
+}
+
+static bool
+device_exists(const char *dev_name)
+{
+       char path[PATH_MAX];
+
+       snprintf(path, sizeof(path), PLATFORM_BUS_DEVICES_PATH "/%s", dev_name);
+
+       return access(path, R_OK) == 0;
+}
+
+static int
+platform_bus_plug(struct rte_device *dev)
+{
+       if (!device_exists(dev->name))
+               return -ENODEV;
+
+       if (device_allowed(dev->name)) {
+               PLATFORM_LOG(WARNING, "Device not allowed\n");
+
+               return -ENODEV;
+       }
+
+       return platform_probe(RTE_DEV_TO_PLATFORM_DEV(dev));
+}
+
+static int
+platform_bus_unplug(struct rte_device *dev)
+{
+       struct rte_platform_device *pdev = RTE_DEV_TO_PLATFORM_DEV(dev);
+
+       return platform_remove(pdev);
+}
+
+static int
+platform_bus_parse(const char *name, void *addr)
+{
+       struct rte_platform_driver *pdrv;
+       const char **out = addr;
+
+       pdrv = device_lookup_driver(name);
+       if (pdrv && addr)
+               *out = name;
+
+       return pdrv ? 0 : -1;
+}
+
+static int
+platform_bus_dma_map(struct rte_device *dev, void *addr, uint64_t iova, size_t 
len)
+{
+       struct rte_platform_device *pdev = RTE_DEV_TO_PLATFORM_DEV(dev);
+
+       if (!pdev || !pdev->driver) {
+               rte_errno = EINVAL;
+
+               return -1;
+       }
+
+       if (!pdev->driver->dma_map) {
+               rte_errno = ENOTSUP;
+
+               return -1;
+       }
+
+       return pdev->driver->dma_map(pdev, addr, iova, len);
+}
+
+static int
+platform_bus_dma_unmap(struct rte_device *dev, void *addr, uint64_t iova, 
size_t len)
+{
+
+       struct rte_platform_device *pdev = RTE_DEV_TO_PLATFORM_DEV(dev);
+
+       if (!pdev || !pdev->driver) {
+               rte_errno = EINVAL;
+
+               return -1;
+       }
+
+       if (!pdev->driver->dma_unmap) {
+               rte_errno = ENOTSUP;
+
+               return -1;
+       }
+
+       return pdev->driver->dma_unmap(pdev, addr, iova, len);
+}
+
+static enum rte_iova_mode
+platform_bus_get_iommu_class(void)
+{
+       struct rte_platform_driver *pdrv;
+
+       FOREACH_DRIVER_ON_PLATFORM_BUS(pdrv) {
+               if (pdrv->drv_flags & RTE_PLATFORM_DRV_NEED_IOVA_AS_VA)
+                       return RTE_IOVA_VA;
+       }
+
+       return RTE_IOVA_DC;
+}
+
+struct rte_platform_bus platform_bus = {
+       .bus = {
+               .scan = platform_bus_scan,
+               .probe = platform_bus_probe,
+               .find_device = platform_bus_find_device,
+               .plug = platform_bus_plug,
+               .unplug = platform_bus_unplug,
+               .parse = platform_bus_parse,
+               .dma_map = platform_bus_dma_map,
+               .dma_unmap = platform_bus_dma_unmap,
+               .get_iommu_class = platform_bus_get_iommu_class,
+               .dev_iterate = platform_bus_dev_iterate,
+       },
+       .device_list = TAILQ_HEAD_INITIALIZER(platform_bus.device_list),
+       .driver_list = TAILQ_HEAD_INITIALIZER(platform_bus.driver_list),
+};
+
+RTE_REGISTER_BUS(platform_bus, platform_bus.bus);
+RTE_LOG_REGISTER_DEFAULT(platform_bus_logtype, NOTICE);
diff --git a/drivers/bus/platform/platform_params.c 
b/drivers/bus/platform/platform_params.c
new file mode 100644
index 0000000000..86e8587b82
--- /dev/null
+++ b/drivers/bus/platform/platform_params.c
@@ -0,0 +1,70 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell.
+ */
+
+#include <string.h>
+
+#include <rte_bus.h>
+#include <rte_common.h>
+#include <rte_dev.h>
+#include <rte_errno.h>
+#include <rte_kvargs.h>
+
+#include "private.h"
+#include "rte_bus_platform.h"
+
+enum platform_params {
+       RTE_PLATFORM_PARAM_NAME,
+};
+
+static const char * const platform_params_keys[] = {
+       [RTE_PLATFORM_PARAM_NAME] = "name",
+       NULL
+};
+
+static int
+platform_dev_match(const struct rte_device *dev, const void *_kvlist)
+{
+       const char *key = platform_params_keys[RTE_PLATFORM_PARAM_NAME];
+       const struct rte_kvargs *kvlist = _kvlist;
+       const char *name;
+
+       /* no kvlist arg, all devices match */
+       if (!kvlist)
+               return 0;
+
+       /* if key is present in kvlist and does not match, filter device */
+       name = rte_kvargs_get(kvlist, key);
+       if (name && strcmp(name, dev->name))
+               return -1;
+
+       return 0;
+}
+
+void *
+platform_bus_dev_iterate(const void *start, const char *str,
+                        const struct rte_dev_iterator *it __rte_unused)
+{
+       rte_bus_find_device_t find_device;
+       struct rte_kvargs *kvargs = NULL;
+       struct rte_device *dev;
+
+       if (str) {
+               kvargs = rte_kvargs_parse(str, platform_params_keys);
+               if (!kvargs) {
+                       PLATFORM_LOG(ERR, "cannot parse argument list %s", str);
+                       rte_errno = EINVAL;
+
+                       return NULL;
+               }
+       }
+
+       find_device = platform_bus.bus.find_device;
+       if (!find_device)
+               return NULL;
+
+       dev = platform_bus.bus.find_device(start, platform_dev_match, kvargs);
+       rte_kvargs_free(kvargs);
+
+       return dev;
+}
diff --git a/drivers/bus/platform/private.h b/drivers/bus/platform/private.h
new file mode 100644
index 0000000000..988803080f
--- /dev/null
+++ b/drivers/bus/platform/private.h
@@ -0,0 +1,47 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell.
+ */
+
+#ifndef _RTE_BUS_PLATFORM_PRIVATE_H_
+#define _RTE_BUS_PLATFORM_PRIVATE_H_
+
+#include <rte_bus.h>
+#include <rte_common.h>
+#include <rte_dev.h>
+#include <rte_log.h>
+#include <rte_os.h>
+
+#include "rte_bus_platform.h"
+
+extern struct rte_platform_bus platform_bus;
+extern int platform_bus_logtype;
+
+/* Platform bus iterators. */
+#define FOREACH_DEVICE_ON_PLATFORM_BUS(p) \
+       RTE_TAILQ_FOREACH(p, &(platform_bus.device_list), next)
+
+#define FOREACH_DRIVER_ON_PLATFORM_BUS(p) \
+       RTE_TAILQ_FOREACH(p, &(platform_bus.driver_list), next)
+
+/*
+ * Structure describing platform bus.
+ */
+struct rte_platform_bus {
+       struct rte_bus bus; /* Core bus */
+       RTE_TAILQ_HEAD(, rte_platform_device) device_list; /* List of bus 
devices */
+       RTE_TAILQ_HEAD(, rte_platform_driver) driver_list; /* List of bus 
drivers */
+};
+
+#define PLATFORM_LOG(level, ...) \
+       rte_log(RTE_LOG_ ## level, platform_bus_logtype, \
+               RTE_FMT("platform bus: " RTE_FMT_HEAD(__VA_ARGS__,) , \
+                       RTE_FMT_TAIL(__VA_ARGS__,)))
+
+/*
+ * Iterate registered platform devices and find one that matches provided 
string.
+ */
+void *
+platform_bus_dev_iterate(const void *start, const char *str,
+                        const struct rte_dev_iterator *it __rte_unused);
+
+#endif /* _RTE_BUS_PLATFORM_PRIVATE_H_ */
diff --git a/drivers/bus/platform/rte_bus_platform.h 
b/drivers/bus/platform/rte_bus_platform.h
new file mode 100644
index 0000000000..5e5cdb166b
--- /dev/null
+++ b/drivers/bus/platform/rte_bus_platform.h
@@ -0,0 +1,165 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(C) 2023 Marvell.
+ */
+
+#ifndef _RTE_BUS_PLATFORM_H_
+#define _RTE_BUS_PLATFORM_H_
+
+/**
+ * @file
+ * Platform bus interface.
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#include <rte_common.h>
+#include <rte_dev.h>
+#include <rte_os.h>
+
+/* Forward declarations */
+struct rte_platform_bus;
+struct rte_platform_device;
+struct rte_platform_driver;
+
+/**
+ * Initialization function for the driver called during platform device 
probing.
+ *
+ * @param pdev
+ *   Pointer to the platform device.
+ * @return
+ *   0 on success, negative value otherwise.
+ */
+typedef int (rte_platform_probe_t)(struct rte_platform_device *pdev);
+
+/**
+ * Removal function for the driver called during platform device removal.
+ *
+ * @param pdev
+ *   Pointer to the platform device.
+ * @return
+ *   0 on success, negative value otherwise.
+ */
+typedef int (rte_platform_remove_t)(struct rte_platform_device *pdev);
+
+/**
+ * Driver specific DMA mapping.
+ *
+ * @param pdev
+ *   Pointer to the platform device.
+ * @param addr
+ *   Starting virtual address of memory to be mapped.
+ * @param iova
+ *   Starting IOVA address of memory to be mapped.
+ * @param len
+ *   Length of memory segment being mapped.
+ * @return
+ *   - 0 on success.
+ *   - Negative value and rte_errno is set otherwise.
+ */
+typedef int (rte_platform_dma_map_t)(struct rte_platform_device *pdev, void 
*addr, uint64_t iova,
+                                    size_t len);
+
+/**
+ * Driver specific DMA unmapping.
+ *
+ * @param pdev
+ *   Pointer to the platform device.
+ * @param addr
+ *   Starting virtual address of memory to be mapped.
+ * @param iova
+ *   Starting IOVA address of memory to be mapped.
+ * @param len
+ *   Length of memory segment being mapped.
+ * @return
+ *   - 0 on success.
+ *   - Negative value and rte_errno is set otherwise.
+ */
+typedef int (rte_platform_dma_unmap_t)(struct rte_platform_device *pdev, void 
*addr, uint64_t iova,
+                                      size_t len);
+
+/**
+ * A structure describing a platform device.
+ */
+struct rte_platform_device {
+       RTE_TAILQ_ENTRY(rte_platform_device) next; /**< Next attached platform 
device */
+       struct rte_device device; /**< Core device */
+       struct rte_platform_driver *driver; /**< Matching device driver */
+       char name[RTE_DEV_NAME_MAX_LEN + 1]; /**< Device name */
+       void *driver_data; /**< Driver data */
+};
+
+/**
+ * A structure describing a platform device driver.
+ */
+struct rte_platform_driver {
+       RTE_TAILQ_ENTRY(rte_platform_driver) next; /**< Next available platform 
driver */
+       struct rte_driver driver; /**< Core driver */
+       rte_platform_probe_t *probe;  /**< Device probe function */
+       rte_platform_remove_t *remove; /**< Device remove function */
+       rte_platform_dma_map_t *dma_map; /**< Device DMA map function */
+       rte_platform_dma_unmap_t *dma_unmap; /**< Device DMA unmap function */
+       /** Device driver needs IOVA as VA and cannot work with IOVA as VA */
+#define RTE_PLATFORM_DRV_NEED_IOVA_AS_VA 0x0001
+       uint32_t drv_flags; /**< Driver flags */
+};
+
+/**
+ * @internal
+ * Helper macros used to convert core device to platform device.
+ */
+#define RTE_DEV_TO_PLATFORM_DEV(ptr) \
+       container_of(ptr, struct rte_platform_device, device)
+
+#define RTE_DEV_TO_PLATFORM_DEV_CONST(ptr) \
+       container_of(ptr, const struct rte_platform_device, device)
+
+/**
+ * Register a platform device driver.
+ *
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * @param pdrv
+ *   A pointer to a rte_platform_driver structure describing driver to be 
registered.
+ */
+__rte_experimental
+void rte_platform_register(struct rte_platform_driver *pdrv);
+
+/** Helper for platform driver registration. */
+#define RTE_PMD_REGISTER_PLATFORM(nm, platform_drv) \
+static const char *pdrvinit_ ## nm ## _alias; \
+RTE_INIT(pdrvinitfn_ ##nm) \
+{ \
+       (platform_drv).driver.name = RTE_STR(nm); \
+       (platform_drv).driver.alias = pdrvinit_ ## nm ## _alias; \
+       rte_platform_register(&(platform_drv)); \
+} \
+RTE_PMD_EXPORT_NAME(nm, __COUNTER__)
+
+/** Helper for setting platform driver alias. */
+#define RTE_PMD_REGISTER_ALIAS(nm, alias) \
+static const char *pdrvinit_ ## nm ## _alias = RTE_STR(alias)
+
+/**
+ * Unregister a platform device driver.
+ *
+ * @warning
+ * @b EXPERIMENTAL: this API may change without prior notice.
+ *
+ * @param pdrv
+ *   A pointer to a rte_platform_driver structure describing driver to be 
unregistered.
+ */
+__rte_experimental
+void rte_platform_unregister(struct rte_platform_driver *pdrv);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RTE_BUS_PLATFORM_H_ */
diff --git a/drivers/bus/platform/version.map b/drivers/bus/platform/version.map
new file mode 100644
index 0000000000..ee77a6beff
--- /dev/null
+++ b/drivers/bus/platform/version.map
@@ -0,0 +1,10 @@
+DPDK_23 {
+       local: *;
+};
+
+EXPERIMENTAL {
+       global:
+
+       rte_platform_register;
+       rte_platform_unregister;
+};
--
2.25.1

Reply via email to