/* * Copyright (c) 2016 Cisco and/or its affiliates. * 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. */ /* * pci.c: Linux user space PCI bus management. * * Copyright (c) 2008 Eliot Dresselhaus * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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"; #define pci_log_debug(vm, dev, f, ...) \ vlib_log(VLIB_LOG_LEVEL_DEBUG, pci_main.log_default, "%U: " f, \ format_vlib_pci_addr, vlib_pci_get_addr(vm, dev->handle), ## __VA_ARGS__) #define pci_log_err(vm, dev, f, ...) \ vlib_log(VLIB_LOG_LEVEL_ERR, pci_main.log_default, "%U: " f, \ format_vlib_pci_addr, vlib_pci_get_addr(vm, dev->handle), ## __VA_ARGS__) typedef struct { int fd; void *addr; size_t size; } linux_pci_region_t; typedef struct { int fd; u32 clib_file_index; union { pci_intx_handler_function_t *intx_handler; pci_msix_handler_function_t *msix_handler; }; } linux_pci_irq_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; u32 numa_node; /* Resource file descriptors. */ linux_pci_region_t *regions; /* File descriptor for config space read/write. */ int config_fd; u64 config_offset; /* Device File descriptor */ int fd; /* read/write file descriptor for io bar */ int io_fd; u64 io_offset; /* Minor device for uio device. */ u32 uio_minor; /* Interrupt handlers */ linux_pci_irq_t intx_irq; linux_pci_irq_t *msix_irqs; /* private data */ uword private_data; u8 supports_va_dma; } linux_pci_device_t; /* Pool of PCI devices. */ typedef struct { vlib_main_t *vlib_main; linux_pci_device_t *linux_pci_devices; } linux_pci_main_t; extern linux_pci_main_t linux_pci_main; static linux_pci_device_t * linux_pci_get_device (vlib_pci_dev_handle_t h) { linux_pci_main_t *lpm = &linux_pci_main; return pool_elt_at_index (lpm->linux_pci_devices, h); } uword vlib_pci_get_private_data (vlib_main_t * vm, vlib_pci_dev_handle_t h) { linux_pci_device_t *d = linux_pci_get_device (h); return d->private_data; } void vlib_pci_set_private_data (vlib_main_t * vm, vlib_pci_dev_handle_t h, uword private_data) { linux_pci_device_t *d = linux_pci_get_device (h); d->private_data = private_data; } vlib_pci_addr_t * vlib_pci_get_addr (vlib_main_t * vm, vlib_pci_dev_handle_t h) { linux_pci_device_t *d = linux_pci_get_device (h); return &d->addr; } u32 vlib_pci_get_numa_node (vlib_main_t * vm, vlib_pci_dev_handle_t h) { linux_pci_device_t *d = linux_pci_get_device (h); return d->numa_node; } u32 vlib_pci_get_num_msix_interrupts (vlib_main_t * vm, vlib_pci_dev_handle_t h) { linux_pci_device_t *d = linux_pci_get_device (h); if (d->type == LINUX_PCI_DEVICE_TYPE_VFIO) { struct vfio_irq_info ii = { 0 }; ii.argsz = sizeof (struct vfio_irq_info); ii.index = VFIO_PCI_MSIX_IRQ_INDEX; if (ioctl (d->fd, VFIO_DEVICE_GET_IRQ_INFO, &ii) < 0) return 0; return ii.count; } 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); linux_pci_main_t linux_pci_main; vlib_pci_device_info_t * vlib_pci_get_device_info (vlib_main_t * vm, vlib_pci_addr_t * addr, clib_error_t ** error) { linux_vfio_main_t *lvm = &vfio_main; clib_error_t *err; vlib_pci_device_info_t *di; u8 *f = 0; u32 tmp; int fd; 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; u8 *dev_dir_name = format (0, "%s/%U", sysfs_pci_dev_path, format_vlib_pci_addr, addr); f = format (0, "%v/config%c", dev_dir_name, 0); fd = open ((char *) f, O_RDWR); /* Try read-only access if write fails. */ if (fd < 0) fd = open ((char *) f, O_RDONLY); if (fd < 0) { err = clib_error_return_unix (0, "open `%s'", f); goto 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->config0)) { err = clib_error_re
from vpp_tunnel_interface import VppTunnelInterface
from ipaddress import ip_address
from vpp_papi import VppEnum


class VppIpIpTunInterface(VppTunnelInterface):
    """
    VPP IP-IP Tunnel interface
    """

    def __init__(self, test, parent_if, src, dst,
                 table_id=0, dscp=0x0,
                 flags=0, mode=None):
        super(VppIpIpTunInterface, self).__init__(test, parent_if)
        self.src = src
        self.dst = dst
        self.table_id = table_id
        self.dscp = dscp
        self.flags = flags
        self.mode = mode
        if not self.mode:
            self.mode = (VppEnum.vl_api_tunnel_mode_t.
                         TUNNEL_API_MODE_P2P)

    def add_vpp_config(self):
        r = self.test.vapi.ipip_add_tunnel(
            tunnel={
                'src': self.src,
                'dst': self.dst,
                'table_id': self.table_id,
                'flags': self.flags,
                'dscp': self.dscp,
                'instance': 0xffffffff,
                'mode': self.mode,
            })
        self.set_sw_if_index(r.sw_if_index)
        self.test.registry.register(self, self.test.logger)
        return self

    def remove_vpp_config(self):
        self.test.vapi.ipip_del_tunnel(sw_if_index=self._sw_if_index)

    def query_vpp_config(self):
        ts = self.test.vapi.ipip_tunnel_dump(sw_if_index=0xffffffff)
        for t in ts:
            if t.tunnel.sw_if_index == self._sw_if_index:
                return True
        return False

    def __str__(self):
        return self.object_id()

    def object_id(self):
        return "ipip-%d" % self._sw_if_index

    @property
    def remote_ip(self):
        return self.dst

    @property
    def local_ip(self):
        return self.src
DEVICE_GET_INFO, &device_info) < 0) { err = clib_error_return_unix (0, "ioctl(VFIO_DEVICE_GET_INFO) '%U'", format_vlib_pci_addr, &di->addr); goto error; } reg.argsz = sizeof (struct vfio_region_info); reg.index = VFIO_PCI_CONFIG_REGION_INDEX; if (ioctl (p->fd, VFIO_DEVICE_GET_REGION_INFO, ®) < 0) { err = clib_error_return_unix (0, "ioctl(VFIO_DEVICE_GET_INFO) '%U'", format_vlib_pci_addr, &di->addr); goto error; } pci_log_debug (vm, p, "%s region_info index:%u size:0x%lx offset:0x%lx " "flags: %s%s%s(0x%x)", __func__, reg.index, reg.size, reg.offset, reg.flags & VFIO_REGION_INFO_FLAG_READ ? "rd " : "", reg.flags & VFIO_REGION_INFO_FLAG_WRITE ? "wr " : "", reg.flags & VFIO_REGION_INFO_FLAG_MMAP ? "mmap " : "", reg.flags); p->config_offset = reg.offset; p->config_fd = p->fd; /* reset if device supports it */ if (device_info.flags & VFIO_DEVICE_FLAGS_RESET) 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); goto error; } if (r && r->interrupt_handler) { vlib_pci_register_intx_handler (vm, p->handle, r->interrupt_handler); linux_pci_vfio_unmask_intx (vm, p); } if (p->supports_va_dma) { vlib_buffer_pool_t *bp; /* *INDENT-OFF* */ vec_foreach (bp, vm->buffer_main->buffer_pools) { u32 i; vlib_physmem_map_t *pm; pm = vlib_physmem_get_map (vm, bp->physmem_map_index); for (i = 0; i < pm->n_pages; i++) vfio_map_physmem_page (vm, pm->base + (i << pm->log2_page_size)); } /* *INDENT-ON* */ } if (r && r->init_function) err = r->init_function (lpm->vlib_main, p->handle); error: vec_free (s); if (err) { if (p->fd != -1) close (p->fd); if (p->config_fd != -1 && p->config_fd != p->fd) close (p->config_fd); p->config_fd = p->fd = -1; } return err; } /* 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) { linux_pci_device_t *p = linux_pci_get_device (h); int n; if (read_or_write == VLIB_READ) n = pread (p->config_fd, data, n_bytes, p->config_offset + address); else n = pwrite (p->config_fd, data, n_bytes, p->config_offset + address); if (n != n_bytes) return clib_error_return_unix (0, "%s", read_or_write == VLIB_READ ? "read" : "write"); return 0; } static clib_error_t * vlib_pci_region (vlib_main_t * vm, vlib_pci_dev_handle_t h, u32 bar, int *fd, u64 * size, u64 * offset) { linux_pci_device_t *p = linux_pci_get_device (h); clib_error_t *error = 0; int _fd = -1; u64 _size = 0, _offset = 0; ASSERT (bar <= 5); error = 0; if (p->type == LINUX_PCI_DEVICE_TYPE_UIO) { 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); _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; } if (fstat (_fd, &stat_buf) < 0) { error = clib_error_return_unix (0, "fstat `%s'", file_name); vec_free (file_name); close (_fd); return error; } vec_free (file_name); _size = stat_buf.st_size; _offset = 0; } else if (p->type == LINUX_PCI_DEVICE_TYPE_VFIO) { 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, ®) < 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; pci_log_debug (vm, p, "%s region_info index:%u size:0x%lx offset:0x%lx " "flags: %s%s%s(0x%x)", __func__, reg.index, reg.size, reg.offset, reg.flags & VFIO_REGION_INFO_FLAG_READ ? "rd " : "", reg.flags & VFIO_REGION_INFO_FLAG_WRITE ? "wr " : "", reg.flags & VFIO_REGION_INFO_FLAG_MMAP ? "mmap " : "", reg.flags); } else ASSERT (0); *fd = _fd; *size = _size; *offset = _offset; return error; } static clib_error_t * vlib_pci_map_region_int (vlib_main_t * vm, vlib_pci_dev_handle_t h, u32 bar, u8 * addr, void **result) { linux_pci_device_t *p = linux_pci_get_device (h); int fd = -1; clib_error_t *error; int flags = MAP_SHARED; u64 size = 0, offset = 0; pci_log_debug (vm, p, "map region %u to va %p", bar, addr); if ((error = vlib_pci_region (vm, h, bar, &fd, &size, &offset))) return error; if (p->type == LINUX_PCI_DEVICE_TYPE_UIO && addr != 0) flags |= MAP_FIXED; *result = mmap (addr, size, PROT_READ | PROT_WRITE, flags, fd, offset); if (*result == (void *) -1) { error = clib_error_return_unix (0, "mmap `BAR%u'", bar); if (p->type == LINUX_PCI_DEVICE_TYPE_UIO) close (fd); 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_region (vlib_main_t * vm, vlib_pci_dev_handle_t h, u32 resource, void **result) { return (vlib_pci_map_region_int (vm, h, resource, 0 /* addr */ , result)); } 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 (vlib_pci_map_region_int (vm, h, resource, addr, result)); } clib_error_t * vlib_pci_io_region (vlib_main_t * vm, vlib_pci_dev_handle_t h, u32 resource) { linux_pci_device_t *p = linux_pci_get_device (h); clib_error_t *error = 0; int fd = -1; u64 size = 0, offset = 0; if ((error = vlib_pci_region (vm, h, resource, &fd, &size, &offset))) return error; p->io_fd = fd; p->io_offset = offset; return error; } 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) { linux_pci_device_t *p = linux_pci_get_device (h); int n = 0; if (read_or_write == VLIB_READ) n = pread (p->io_fd, data, length, p->io_offset + offset); else n = pwrite (p->io_fd, data, length, p->io_offset + offset); if (n != length) return clib_error_return_unix (0, "%s", read_or_write == VLIB_READ ? "read" : "write"); return 0; } clib_error_t * vlib_pci_map_dma (vlib_main_t * vm, vlib_pci_dev_handle_t h, void *ptr) { linux_pci_device_t *p = linux_pci_get_device (h); if (!p->supports_va_dma) return 0; return vfio_map_physmem_page (vm, ptr); } int vlib_pci_supports_virtual_addr_dma (vlib_main_t * vm, vlib_pci_dev_handle_t h) { linux_pci_device_t *p = linux_pci_get_device (h); return p->supports_va_dma != 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) { linux_pci_main_t *lpm = &linux_pci_main; vlib_pci_device_info_t *di; linux_pci_device_t *p; clib_error_t *err = 0; pci_device_id_t *i; di = vlib_pci_get_device_info (vm, addr, &err); if (err) return err; for (i = ids; i->vendor_id != 0; i++) if (i->vendor_id == di->vendor_id && i->device_id == di->device_id) break; if (i->vendor_id == 0) return clib_error_return (0, "Wrong vendor or device id"); pool_get (lpm->linux_pci_devices, p); p->handle = p - lpm->linux_pci_devices; p->addr.as_u32 = di->addr.as_u32; p->intx_irq.fd = -1; p->intx_irq.clib_file_index = -1; p->numa_node = di->numa_node; /* * pci io bar read/write fd */ p->io_fd = -1; pci_log_debug (vm, p, "open vid:0x%04x did:0x%04x driver:%s iommu_group:%d", di->vendor_id, di->device_id, di->driver_name, di->iommu_group); if (strncmp ("vfio-pci", (char *) di->driver_name, 8) == 0) err = add_device_vfio (vm, p, di, 0); else if (strncmp ("uio_pci_generic", (char *) di->driver_name, 8) == 0) err = add_device_uio (vm, p, di, 0); else err = clib_error_create ("device not bound to 'vfio-pci' or " "'uio_pci_generic' kernel module"); if (err) goto error; *handle = p->handle; error: vlib_pci_free_device_info (di); if (err) { pci_log_err (vm, p, "%U", format_clib_error, err); clib_memset (p, 0, sizeof (linux_pci_device_t)); pool_put (lpm->linux_pci_devices, p); } return err; } void vlib_pci_device_close (vlib_main_t * vm, vlib_pci_dev_handle_t h) { linux_pci_main_t *lpm = &linux_pci_main; linux_pci_device_t *p = linux_pci_get_device (h); linux_pci_irq_t *irq; linux_pci_region_t *res; clib_error_t *err = 0; if (p->type == LINUX_PCI_DEVICE_TYPE_UIO) { irq = &p->intx_irq; if (irq->clib_file_index != -1) clib_file_del_by_index (&file_main, irq->clib_file_index); close (p->config_fd); if (p->io_fd != -1) close (p->io_fd); } else if (p->type == LINUX_PCI_DEVICE_TYPE_VFIO) { irq = &p->intx_irq; /* close INTx irqs */ if (irq->fd != -1) { err = vfio_set_irqs (vm, p, VFIO_PCI_INTX_IRQ_INDEX, 0, 0, VFIO_IRQ_SET_ACTION_TRIGGER, 0); clib_error_free (err); if (irq->clib_file_index != -1) clib_file_del_by_index (&file_main, irq->clib_file_index); close (irq->fd); } /* close MSI-X irqs */ if (vec_len (p->msix_irqs)) { err = vfio_set_irqs (vm, p, VFIO_PCI_MSIX_IRQ_INDEX, 0, 0, VFIO_IRQ_SET_ACTION_TRIGGER, 0); clib_error_free (err); /* *INDENT-OFF* */ vec_foreach (irq, p->msix_irqs) { if (irq->fd == -1) continue; clib_file_del_by_index (&file_main, irq->clib_file_index); close (irq->fd); } /* *INDENT-ON* */ vec_free (p->msix_irqs); } } /* *INDENT-OFF* */ vec_foreach (res, p->regions) { if (res->size == 0) continue; munmap (res->addr, res->size); if (res->fd != -1) close (res->fd); } /* *INDENT-ON* */ vec_free (p->regions); close (p->fd); clib_memset (p, 0, sizeof (linux_pci_device_t)); pool_put (lpm->linux_pci_devices, p); } void init_device_from_registered (vlib_main_t * vm, vlib_pci_device_info_t * di) { vlib_pci_main_t *pm = &pci_main; linux_pci_main_t *lpm = &linux_pci_main; pci_device_registration_t *r; pci_device_id_t *i; clib_error_t *err = 0; linux_pci_device_t *p; pool_get (lpm->linux_pci_devices, p); p->handle = p - lpm->linux_pci_devices; p->intx_irq.fd = -1; r = pm->pci_device_registrations; while (r) { for (i = r->supported_devices; i->vendor_id != 0; i++) 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); else err = add_device_uio (vm, p, di, r); if (err) clib_error_report (err); else return; } r = r->next_registration; } /* No driver, close the PCI config-space FD */ clib_memset (p, 0, sizeof (linux_pci_device_t)); pool_put (lpm->linux_pci_devices, p); } static clib_error_t * scan_pci_addr (void *arg, u8 * dev_dir_name, u8 * ignored) { vlib_pci_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/pci/devices/%U", unformat_vlib_pci_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 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; clib_error_t *err; err = foreach_directory_file ((char *) sysfs_pci_dev_path, scan_pci_addr, &addrs, /* scan_dirs */ 0); if (err) { vec_free (addrs); return 0; } vec_sort_with_function (addrs, pci_addr_cmp); return addrs; } clib_error_t * linux_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 (); /* *INDENT-OFF* */ 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); } } /* *INDENT-ON* */ return 0; } /* *INDENT-OFF* */ VLIB_INIT_FUNCTION (linux_pci_init) = { .runs_after = VLIB_INITS("unix_input_init"), }; /* *INDENT-ON* */ /* * fd.io coding-style-patch-verification: ON * * Local Variables: * eval: (c-set-style "gnu") * End: */