| /* |
| * Copyright (c) 2020 Intel Corporation. |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #define DT_DRV_COMPAT qemu_ivshmem |
| |
| #define LOG_LEVEL CONFIG_IVSHMEM_LOG_LEVEL |
| #include <logging/log.h> |
| LOG_MODULE_REGISTER(ivshmem); |
| |
| #include <errno.h> |
| |
| #include <kernel.h> |
| #include <arch/cpu.h> |
| |
| #include <soc.h> |
| #include <device.h> |
| #include <init.h> |
| |
| #include <drivers/virtualization/ivshmem.h> |
| #include "virt_ivshmem.h" |
| |
| #ifdef CONFIG_IVSHMEM_DOORBELL |
| |
| static void ivshmem_doorbell(const void *arg) |
| { |
| const struct ivshmem_param *param = arg; |
| |
| LOG_DBG("Interrupt received on vector %u", param->vector); |
| |
| if (param->signal != NULL) { |
| k_poll_signal_raise(param->signal, param->vector); |
| } |
| } |
| |
| static bool ivshmem_configure_interrupts(const struct device *dev) |
| { |
| struct ivshmem *data = dev->data; |
| bool ret = false; |
| uint8_t n_vectors; |
| uint32_t key; |
| int i; |
| |
| key = irq_lock(); |
| |
| n_vectors = pcie_msi_vectors_allocate(data->bdf, |
| CONFIG_IVSHMEM_INT_PRIORITY, |
| data->vectors, |
| CONFIG_IVSHMEM_MSI_X_VECTORS); |
| if (n_vectors == 0) { |
| LOG_ERR("Could not allocate %u MSI-X vectors", |
| CONFIG_IVSHMEM_MSI_X_VECTORS); |
| goto out; |
| } |
| |
| LOG_DBG("Allocated %u vectors", n_vectors); |
| |
| for (i = 0; i < n_vectors; i++) { |
| data->params[i].dev = dev; |
| data->params[i].vector = i; |
| |
| if (!pcie_msi_vector_connect(data->bdf, |
| &data->vectors[i], |
| ivshmem_doorbell, |
| &data->params[i], 0)) { |
| LOG_ERR("Failed to connect MSI-X vector %u", i); |
| goto out; |
| } |
| } |
| |
| LOG_DBG("%u MSI-X Vectors connected", n_vectors); |
| |
| if (!pcie_msi_enable(data->bdf, data->vectors, n_vectors, 0)) { |
| LOG_ERR("Could not enable MSI-X"); |
| goto out; |
| } |
| |
| data->n_vectors = n_vectors; |
| ret = true; |
| |
| LOG_DBG("MSI-X configured"); |
| out: |
| irq_unlock(key); |
| |
| return ret; |
| } |
| |
| static void register_signal(const struct device *dev, |
| struct k_poll_signal *signal, |
| uint16_t vector) |
| { |
| struct ivshmem *data = dev->data; |
| |
| data->params[vector].signal = signal; |
| } |
| |
| #else |
| |
| static const struct ivshmem_reg no_reg; |
| |
| #define ivshmem_configure_interrupts(...) true |
| #define register_signal(...) |
| |
| #endif /* CONFIG_IVSHMEM_DOORBELL */ |
| |
| static bool ivshmem_configure(const struct device *dev) |
| { |
| struct ivshmem *data = dev->data; |
| struct pcie_mbar mbar_regs, mbar_mem; |
| |
| if (!pcie_get_mbar(data->bdf, IVSHMEM_PCIE_REG_BAR_IDX, &mbar_regs)) { |
| #ifdef CONFIG_IVSHMEM_DOORBELL |
| LOG_ERR("ivshmem regs bar not found"); |
| return false; |
| #else |
| LOG_DBG("ivshmem regs bar not found"); |
| device_map(DEVICE_MMIO_RAM_PTR(dev), (uintptr_t)&no_reg, |
| sizeof(struct ivshmem_reg), K_MEM_CACHE_NONE); |
| #endif /* CONFIG_IVSHMEM_DOORBELL */ |
| } else { |
| pcie_set_cmd(data->bdf, PCIE_CONF_CMDSTAT_MEM, true); |
| |
| device_map(DEVICE_MMIO_RAM_PTR(dev), mbar_regs.phys_addr, |
| mbar_regs.size, K_MEM_CACHE_NONE); |
| } |
| |
| if (!pcie_get_mbar(data->bdf, IVSHMEM_PCIE_SHMEM_BAR_IDX, &mbar_mem)) { |
| LOG_ERR("ivshmem mem bar not found"); |
| return false; |
| } |
| |
| data->size = mbar_mem.size; |
| |
| z_phys_map((uint8_t **)&data->shmem, |
| mbar_mem.phys_addr, data->size, |
| K_MEM_CACHE_WB | K_MEM_PERM_RW | K_MEM_PERM_USER); |
| |
| LOG_DBG("ivshmem configured:"); |
| LOG_DBG("- Registers at 0x%lx (mapped to 0x%lx)", |
| mbar_regs.phys_addr, DEVICE_MMIO_GET(dev)); |
| LOG_DBG("- Shared memory of %lu bytes at 0x%lx (mapped to 0x%lx)", |
| data->size, mbar_mem.phys_addr, data->shmem); |
| |
| return ivshmem_configure_interrupts(dev); |
| } |
| |
| static size_t ivshmem_api_get_mem(const struct device *dev, |
| uintptr_t *memmap) |
| { |
| struct ivshmem *data = dev->data; |
| |
| *memmap = data->shmem; |
| |
| return data->size; |
| } |
| |
| static uint32_t ivshmem_api_get_id(const struct device *dev) |
| { |
| struct ivshmem_reg *regs = (struct ivshmem_reg *)DEVICE_MMIO_GET(dev); |
| |
| return regs->iv_position; |
| } |
| |
| static uint16_t ivshmem_api_get_vectors(const struct device *dev) |
| { |
| #if CONFIG_IVSHMEM_DOORBELL |
| struct ivshmem *data = dev->data; |
| |
| return data->n_vectors; |
| #else |
| return 0; |
| #endif |
| } |
| |
| static int ivshmem_api_int_peer(const struct device *dev, |
| uint32_t peer_id, uint16_t vector) |
| { |
| #if CONFIG_IVSHMEM_DOORBELL |
| struct ivshmem_reg *regs = (struct ivshmem_reg *)DEVICE_MMIO_GET(dev); |
| struct ivshmem *data = dev->data; |
| uint32_t doorbell; |
| |
| if (vector >= data->n_vectors) { |
| return -EINVAL; |
| } |
| |
| doorbell = IVSHMEM_GEN_DOORBELL(peer_id, vector); |
| regs->doorbell = doorbell; |
| |
| return 0; |
| #else |
| return -ENOSYS; |
| #endif |
| } |
| |
| static int ivshmem_api_register_handler(const struct device *dev, |
| struct k_poll_signal *signal, |
| uint16_t vector) |
| { |
| #if CONFIG_IVSHMEM_DOORBELL |
| struct ivshmem *data = dev->data; |
| |
| if (vector >= data->n_vectors) { |
| return -EINVAL; |
| } |
| |
| register_signal(dev, signal, vector); |
| |
| return 0; |
| #else |
| return -ENOSYS; |
| #endif |
| } |
| |
| static const struct ivshmem_driver_api ivshmem_api = { |
| .get_mem = ivshmem_api_get_mem, |
| .get_id = ivshmem_api_get_id, |
| .get_vectors = ivshmem_api_get_vectors, |
| .int_peer = ivshmem_api_int_peer, |
| .register_handler = ivshmem_api_register_handler |
| }; |
| |
| static int ivshmem_init(const struct device *dev) |
| { |
| struct ivshmem *data = dev->data; |
| static bool bdf_lookup_done; |
| |
| if ((data->bdf == PCIE_BDF_NONE) && bdf_lookup_done) { |
| LOG_ERR("One instance of ivshmem with pcie_bdf_lookup() already initialized.\n" |
| "Using more than one with PCIE_BDF_NONE parameter might conflict\n" |
| "with already initialized instances."); |
| return -ENOTSUP; |
| } |
| if ((data->bdf == PCIE_BDF_NONE) && !bdf_lookup_done) { |
| if (data->dev_ven_id) { |
| data->bdf = pcie_bdf_lookup(data->dev_ven_id); |
| } else { |
| data->bdf = pcie_bdf_lookup(PCIE_ID(IVSHMEM_VENDOR_ID, IVSHMEM_DEVICE_ID)); |
| } |
| if (data->bdf == PCIE_BDF_NONE) { |
| LOG_WRN("ivshmem device not found"); |
| return -ENOTSUP; |
| } |
| } |
| LOG_DBG("ivshmem found at bdf 0x%x", data->bdf); |
| bdf_lookup_done = true; |
| |
| if (!ivshmem_configure(dev)) { |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| #define IVSHMEM_DEVICE_INIT(n) \ |
| static struct ivshmem ivshmem_data_##n = { \ |
| .bdf = DT_INST_REG_ADDR_BY_IDX(n, 0), \ |
| .dev_ven_id = DT_INST_REG_SIZE_BY_IDX(n, 0) \ |
| }; \ |
| DEVICE_DT_INST_DEFINE(n, &ivshmem_init, NULL, \ |
| &ivshmem_data_##n, NULL, \ |
| POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, \ |
| &ivshmem_api); |
| |
| DT_INST_FOREACH_STATUS_OKAY(IVSHMEM_DEVICE_INIT) |