New upstream version 18.08
[deb_dpdk.git] / drivers / bus / vmbus / linux / vmbus_uio.c
diff --git a/drivers/bus/vmbus/linux/vmbus_uio.c b/drivers/bus/vmbus/linux/vmbus_uio.c
new file mode 100644 (file)
index 0000000..856c6d6
--- /dev/null
@@ -0,0 +1,398 @@
+/* SPDX-License-Identifier: BSD-3-Clause
+ * Copyright (c) 2018, Microsoft Corporation.
+ * All Rights Reserved.
+ */
+
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <inttypes.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+
+#include <rte_log.h>
+#include <rte_bus.h>
+#include <rte_memory.h>
+#include <rte_eal_memconfig.h>
+#include <rte_common.h>
+#include <rte_malloc.h>
+#include <rte_bus_vmbus.h>
+#include <rte_string_fns.h>
+
+#include "private.h"
+
+/** Pathname of VMBUS devices directory. */
+#define SYSFS_VMBUS_DEVICES "/sys/bus/vmbus/devices"
+
+static void *vmbus_map_addr;
+
+/* Control interrupts */
+void vmbus_uio_irq_control(struct rte_vmbus_device *dev, int32_t onoff)
+{
+       if (write(dev->intr_handle.fd, &onoff, sizeof(onoff)) < 0) {
+               VMBUS_LOG(ERR, "cannot write to %d:%s",
+                       dev->intr_handle.fd, strerror(errno));
+       }
+}
+
+int vmbus_uio_irq_read(struct rte_vmbus_device *dev)
+{
+       int32_t count;
+       int cc;
+
+       cc = read(dev->intr_handle.fd, &count, sizeof(count));
+       if (cc < (int)sizeof(count)) {
+               if (cc < 0) {
+                       VMBUS_LOG(ERR, "IRQ read failed %s",
+                                 strerror(errno));
+                       return -errno;
+               }
+               VMBUS_LOG(ERR, "can't read IRQ count");
+               return -EINVAL;
+       }
+
+       return count;
+}
+
+void
+vmbus_uio_free_resource(struct rte_vmbus_device *dev,
+               struct mapped_vmbus_resource *uio_res)
+{
+       rte_free(uio_res);
+
+       if (dev->intr_handle.uio_cfg_fd >= 0) {
+               close(dev->intr_handle.uio_cfg_fd);
+               dev->intr_handle.uio_cfg_fd = -1;
+       }
+
+       if (dev->intr_handle.fd >= 0) {
+               close(dev->intr_handle.fd);
+               dev->intr_handle.fd = -1;
+               dev->intr_handle.type = RTE_INTR_HANDLE_UNKNOWN;
+       }
+}
+
+int
+vmbus_uio_alloc_resource(struct rte_vmbus_device *dev,
+                        struct mapped_vmbus_resource **uio_res)
+{
+       char devname[PATH_MAX]; /* contains the /dev/uioX */
+
+       /* save fd if in primary process */
+       snprintf(devname, sizeof(devname), "/dev/uio%u", dev->uio_num);
+       dev->intr_handle.fd = open(devname, O_RDWR);
+       if (dev->intr_handle.fd < 0) {
+               VMBUS_LOG(ERR, "Cannot open %s: %s",
+                       devname, strerror(errno));
+               goto error;
+       }
+       dev->intr_handle.type = RTE_INTR_HANDLE_UIO_INTX;
+
+       /* allocate the mapping details for secondary processes*/
+       *uio_res = rte_zmalloc("UIO_RES", sizeof(**uio_res), 0);
+       if (*uio_res == NULL) {
+               VMBUS_LOG(ERR, "cannot store uio mmap details");
+               goto error;
+       }
+
+       strlcpy((*uio_res)->path, devname, PATH_MAX);
+       rte_uuid_copy((*uio_res)->id, dev->device_id);
+
+       return 0;
+
+error:
+       vmbus_uio_free_resource(dev, *uio_res);
+       return -1;
+}
+
+static int
+find_max_end_va(const struct rte_memseg_list *msl, void *arg)
+{
+       size_t sz = msl->memseg_arr.len * msl->page_sz;
+       void *end_va = RTE_PTR_ADD(msl->base_va, sz);
+       void **max_va = arg;
+
+       if (*max_va < end_va)
+               *max_va = end_va;
+       return 0;
+}
+
+/*
+ * TODO: this should be part of memseg api.
+ *       code is duplicated from PCI.
+ */
+static void *
+vmbus_find_max_end_va(void)
+{
+       void *va = NULL;
+
+       rte_memseg_list_walk(find_max_end_va, &va);
+       return va;
+}
+
+int
+vmbus_uio_map_resource_by_index(struct rte_vmbus_device *dev, int idx,
+                               struct mapped_vmbus_resource *uio_res,
+                               int flags)
+{
+       size_t size = dev->resource[idx].len;
+       struct vmbus_map *maps = uio_res->maps;
+       void *mapaddr;
+       off_t offset;
+       int fd;
+
+       /* devname for mmap  */
+       fd = open(uio_res->path, O_RDWR);
+       if (fd < 0) {
+               VMBUS_LOG(ERR, "Cannot open %s: %s",
+                         uio_res->path, strerror(errno));
+               return -1;
+       }
+
+       /* try mapping somewhere close to the end of hugepages */
+       if (vmbus_map_addr == NULL)
+               vmbus_map_addr = vmbus_find_max_end_va();
+
+       /* offset is special in uio it indicates which resource */
+       offset = idx * PAGE_SIZE;
+
+       mapaddr = vmbus_map_resource(vmbus_map_addr, fd, offset, size, flags);
+       close(fd);
+
+       if (mapaddr == MAP_FAILED)
+               return -1;
+
+       dev->resource[idx].addr = mapaddr;
+       vmbus_map_addr = RTE_PTR_ADD(mapaddr, size);
+
+       /* Record result of sucessful mapping for use by secondary */
+       maps[idx].addr = mapaddr;
+       maps[idx].size = size;
+
+       return 0;
+}
+
+static int vmbus_uio_map_primary(struct vmbus_channel *chan,
+                                void **ring_buf, uint32_t *ring_size)
+{
+       struct mapped_vmbus_resource *uio_res;
+
+       uio_res = vmbus_uio_find_resource(chan->device);
+       if (!uio_res) {
+               VMBUS_LOG(ERR, "can not find resources!");
+               return -ENOMEM;
+       }
+
+       if (uio_res->nb_maps < VMBUS_MAX_RESOURCE) {
+               VMBUS_LOG(ERR, "VMBUS: only %u resources found!",
+                         uio_res->nb_maps);
+               return -EINVAL;
+       }
+
+       *ring_size = uio_res->maps[HV_TXRX_RING_MAP].size / 2;
+       *ring_buf  = uio_res->maps[HV_TXRX_RING_MAP].addr;
+       return 0;
+}
+
+static int vmbus_uio_map_subchan(const struct rte_vmbus_device *dev,
+                                const struct vmbus_channel *chan,
+                                void **ring_buf, uint32_t *ring_size)
+{
+       char ring_path[PATH_MAX];
+       size_t file_size;
+       struct stat sb;
+       int fd;
+
+       snprintf(ring_path, sizeof(ring_path),
+                "%s/%s/channels/%u/ring",
+                SYSFS_VMBUS_DEVICES, dev->device.name,
+                chan->relid);
+
+       fd = open(ring_path, O_RDWR);
+       if (fd < 0) {
+               VMBUS_LOG(ERR, "Cannot open %s: %s",
+                         ring_path, strerror(errno));
+               return -errno;
+       }
+
+       if (fstat(fd, &sb) < 0) {
+               VMBUS_LOG(ERR, "Cannot state %s: %s",
+                         ring_path, strerror(errno));
+               close(fd);
+               return -errno;
+       }
+       file_size = sb.st_size;
+
+       if (file_size == 0 || (file_size & (PAGE_SIZE - 1))) {
+               VMBUS_LOG(ERR, "incorrect size %s: %zu",
+                         ring_path, file_size);
+
+               close(fd);
+               return -EINVAL;
+       }
+
+       *ring_size = file_size / 2;
+       *ring_buf = vmbus_map_resource(vmbus_map_addr, fd,
+                                      0, sb.st_size, 0);
+       close(fd);
+
+       if (ring_buf == MAP_FAILED)
+               return -EIO;
+
+       vmbus_map_addr = RTE_PTR_ADD(ring_buf, file_size);
+       return 0;
+}
+
+int vmbus_uio_map_rings(struct vmbus_channel *chan)
+{
+       const struct rte_vmbus_device *dev = chan->device;
+       uint32_t ring_size;
+       void *ring_buf;
+       int ret;
+
+       /* Primary channel */
+       if (chan->subchannel_id == 0)
+               ret = vmbus_uio_map_primary(chan, &ring_buf, &ring_size);
+       else
+               ret = vmbus_uio_map_subchan(dev, chan, &ring_buf, &ring_size);
+
+       if (ret)
+               return ret;
+
+       vmbus_br_setup(&chan->txbr, ring_buf, ring_size);
+       vmbus_br_setup(&chan->rxbr, (char *)ring_buf + ring_size, ring_size);
+       return 0;
+}
+
+static int vmbus_uio_sysfs_read(const char *dir, const char *name,
+                               unsigned long *val, unsigned long max_range)
+{
+       char path[PATH_MAX];
+       FILE *f;
+       int ret;
+
+       snprintf(path, sizeof(path), "%s/%s", dir, name);
+       f = fopen(path, "r");
+       if (!f) {
+               VMBUS_LOG(ERR, "can't open %s:%s",
+                         path, strerror(errno));
+               return -errno;
+       }
+
+       if (fscanf(f, "%lu", val) != 1)
+               ret = -EIO;
+       else if (*val > max_range)
+               ret = -ERANGE;
+       else
+               ret = 0;
+       fclose(f);
+
+       return ret;
+}
+
+static bool vmbus_uio_ring_present(const struct rte_vmbus_device *dev,
+                                  uint32_t relid)
+{
+       char ring_path[PATH_MAX];
+
+       /* Check if kernel has subchannel sysfs files */
+       snprintf(ring_path, sizeof(ring_path),
+                "%s/%s/channels/%u/ring",
+                SYSFS_VMBUS_DEVICES, dev->device.name, relid);
+
+       return access(ring_path, R_OK|W_OK) == 0;
+}
+
+bool vmbus_uio_subchannels_supported(const struct rte_vmbus_device *dev,
+                                    const struct vmbus_channel *chan)
+{
+       return vmbus_uio_ring_present(dev, chan->relid);
+}
+
+static bool vmbus_isnew_subchannel(struct vmbus_channel *primary,
+                                  unsigned long id)
+{
+       const struct vmbus_channel *c;
+
+       STAILQ_FOREACH(c, &primary->subchannel_list, next) {
+               if (c->relid == id)
+                       return false;
+       }
+       return true;
+}
+
+int vmbus_uio_get_subchan(struct vmbus_channel *primary,
+                         struct vmbus_channel **subchan)
+{
+       const struct rte_vmbus_device *dev = primary->device;
+       char chan_path[PATH_MAX], subchan_path[PATH_MAX];
+       struct dirent *ent;
+       DIR *chan_dir;
+
+       snprintf(chan_path, sizeof(chan_path),
+                "%s/%s/channels",
+                SYSFS_VMBUS_DEVICES, dev->device.name);
+
+       chan_dir = opendir(chan_path);
+       if (!chan_dir) {
+               VMBUS_LOG(ERR, "cannot open %s: %s",
+                         chan_path, strerror(errno));
+               return -errno;
+       }
+
+       while ((ent = readdir(chan_dir))) {
+               unsigned long relid, subid, monid;
+               char *endp;
+               int err;
+
+               if (ent->d_name[0] == '.')
+                       continue;
+
+               errno = 0;
+               relid = strtoul(ent->d_name, &endp, 0);
+               if (*endp || errno != 0 || relid > UINT16_MAX) {
+                       VMBUS_LOG(NOTICE, "not a valid channel relid: %s",
+                                 ent->d_name);
+                       continue;
+               }
+
+               snprintf(subchan_path, sizeof(subchan_path), "%s/%lu",
+                        chan_path, relid);
+               err = vmbus_uio_sysfs_read(subchan_path, "subchannel_id",
+                                          &subid, UINT16_MAX);
+               if (err) {
+                       VMBUS_LOG(NOTICE, "invalid subchannel id %lu",
+                                 subid);
+                       closedir(chan_dir);
+                       return err;
+               }
+
+               if (subid == 0)
+                       continue;       /* skip primary channel */
+
+               if (!vmbus_isnew_subchannel(primary, relid))
+                       continue;
+
+               if (!vmbus_uio_ring_present(dev, relid))
+                       continue;       /* Ring may not be ready yet */
+
+               err = vmbus_uio_sysfs_read(subchan_path, "monitor_id",
+                                          &monid, UINT8_MAX);
+               if (err) {
+                       VMBUS_LOG(NOTICE, "invalid monitor id %lu",
+                                 monid);
+                       return err;
+               }
+
+               err = vmbus_chan_create(dev, relid, subid, monid, subchan);
+               if (err) {
+                       VMBUS_LOG(NOTICE, "subchannel setup failed");
+                       return err;
+               }
+               break;
+       }
+       closedir(chan_dir);
+
+       return (ent == NULL) ? -ENOENT : 0;
+}