diff --git a/drivers/virtualization/Kconfig b/drivers/virtualization/Kconfig index c22b9073d7e..4783d8d3c50 100644 --- a/drivers/virtualization/Kconfig +++ b/drivers/virtualization/Kconfig @@ -30,6 +30,30 @@ module = IVSHMEM module-str = ivshmem source "subsys/logging/Kconfig.template.log_config" +config IVSHMEM_DOORBELL + bool "Support interrupt based ivshmem (doorbell version)" + depends on PCIE_MSI_X && PCIE_MSI_MULTI_VECTOR + help + This will enable support of ivshmem-doorbell, i.e. the interrupt + based ivshmem. + +config IVSHMEM_MSI_X_VECTORS + int "How many notification vectors should be pre-allocated?" + default 2 + depends on IVSHMEM_DOORBELL + help + MSI-X vector holders must be pre-allocated. One can pre-allocate + more or less than necessary. Depends on how many VMs will connect + with each other. These are know to be the notification vectors in + ivshmem. + +config IVSHMEM_INT_PRIORITY + int "Interrupt priority" + default 2 + depends on IVSHMEM_DOORBELL + help + Interrupt priority used for the MSI-X generated interrupts. + endif # IVSHMEM endif # VIRTUALIZATION diff --git a/drivers/virtualization/virt_ivshmem.c b/drivers/virtualization/virt_ivshmem.c index a5ee8e50c87..fb6573b575d 100644 --- a/drivers/virtualization/virt_ivshmem.c +++ b/drivers/virtualization/virt_ivshmem.c @@ -16,11 +16,89 @@ LOG_MODULE_REGISTER(ivshmem); #include #include #include -#include #include #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)) { + 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 +#define ivshmem_configure_interrupts(...) true +#define register_signal(...) +#endif /* CONFIG_IVSHMEM_DOORBELL */ + static bool ivshmem_check_on_bdf(pcie_bdf_t bdf) { uint32_t data; @@ -90,7 +168,7 @@ static bool ivshmem_configure(const struct device *dev) LOG_DBG("- Shared memory of %lu bytes at 0x%lx (mapped to 0x%lx)", data->size, mbar_mem.phys_addr, data->shmem); - return true; + return ivshmem_configure_interrupts(dev); } static size_t ivshmem_api_get_mem(const struct device *dev, @@ -103,8 +181,70 @@ static size_t ivshmem_api_get_mem(const struct device *dev, 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) diff --git a/drivers/virtualization/virt_ivshmem.h b/drivers/virtualization/virt_ivshmem.h index 7d70ae345d7..4323cf3ac66 100644 --- a/drivers/virtualization/virt_ivshmem.h +++ b/drivers/virtualization/virt_ivshmem.h @@ -7,21 +7,44 @@ #ifndef ZEPHYR_DRIVERS_VIRTUALIZATION_VIRT_IVSHMEM_H_ #define ZEPHYR_DRIVERS_VIRTUALIZATION_VIRT_IVSHMEM_H_ +#include +#include + #define IVSHMEM_VENDOR_ID 0x1AF4 #define IVSHMEM_DEVICE_ID 0x1110 #define IVSHMEM_PCIE_REG_BAR_IDX 0 -#define IVSHMEM_PCIE_SHMEM_BAR_IDX 1 +#define IVSHMEM_PCIE_SHMEM_BAR_IDX 2 #define MAX_BUS (0xFFFFFFFF & PCIE_BDF_BUS_MASK) #define MAX_DEV (0xFFFFFFFF & PCIE_BDF_DEV_MASK) #define MAX_FUNC (0xFFFFFFFF & PCIE_BDF_FUNC_MASK) +struct ivshmem_param { + const struct device *dev; + struct k_poll_signal *signal; + uint8_t vector; +}; + struct ivshmem { DEVICE_MMIO_RAM; pcie_bdf_t bdf; uintptr_t shmem; size_t size; +#ifdef CONFIG_IVSHMEM_DOORBELL + msi_vector_t vectors[CONFIG_IVSHMEM_MSI_X_VECTORS]; + struct ivshmem_param params[CONFIG_IVSHMEM_MSI_X_VECTORS]; + uint16_t n_vectors; +#endif }; +struct ivshmem_reg { + uint32_t int_mask; + uint32_t int_status; + uint32_t iv_position; + uint32_t doorbell; +}; + +#define IVSHMEM_GEN_DOORBELL(i, v) ((i << 16) | (v & 0xFFFF)) + #endif /* ZEPHYR_DRIVERS_VIRTUALIZATION_VIRT_IVSHMEM_H_ */ diff --git a/include/drivers/virtualization/ivshmem.h b/include/drivers/virtualization/ivshmem.h index f4e6beadc3a..36299ec8433 100644 --- a/include/drivers/virtualization/ivshmem.h +++ b/include/drivers/virtualization/ivshmem.h @@ -18,8 +18,23 @@ extern "C" { typedef size_t (*ivshmem_get_mem_f)(const struct device *dev, uintptr_t *memmap); +typedef uint32_t (*ivshmem_get_id_f)(const struct device *dev); + +typedef uint16_t (*ivshmem_get_vectors_f)(const struct device *dev); + +typedef int (*ivshmem_int_peer_f)(const struct device *dev, + uint32_t peer_id, uint16_t vector); + +typedef int (*ivshmem_register_handler_f)(const struct device *dev, + struct k_poll_signal *signal, + uint16_t vector); + struct ivshmem_driver_api { ivshmem_get_mem_f get_mem; + ivshmem_get_id_f get_id; + ivshmem_get_vectors_f get_vectors; + ivshmem_int_peer_f int_peer; + ivshmem_register_handler_f register_handler; }; /** @@ -39,6 +54,79 @@ static inline size_t ivshmem_get_mem(const struct device *dev, return api->get_mem(dev, memmap); } +/** + * @brief Get our VM ID + * + * @param dev Pointer to the device structure for the driver instance + * + * @return our VM ID or 0 if we are not running on doorbell version + */ +static inline uint32_t ivshmem_get_id(const struct device *dev) +{ + const struct ivshmem_driver_api *api = + (const struct ivshmem_driver_api *)dev->api; + + return api->get_id(dev); +} + +/** + * @brief Get the number of interrupt vectors we can use + * + * @param dev Pointer to the device structure for the driver instance + * + * @return the number of available interrupt vectors + */ +static inline uint16_t ivshmem_get_vectors(const struct device *dev) +{ + const struct ivshmem_driver_api *api = + (const struct ivshmem_driver_api *)dev->api; + + return api->get_vectors(dev); +} + +/** + * @brief Interrupt another VM + * + * @param dev Pointer to the device structure for the driver instance + * @param peer_id The VM ID to interrupt + * @param vector The interrupt vector to use + * + * @return 0 on success, a negative errno otherwise + */ +static inline int ivshmem_int_peer(const struct device *dev, + uint32_t peer_id, uint16_t vector) +{ + const struct ivshmem_driver_api *api = + (const struct ivshmem_driver_api *)dev->api; + + return api->int_peer(dev, peer_id, vector); +} + +/** + * @brief Register a vector notification (interrupt) handler + * + * @param dev Pointer to the device structure for the driver instance + * @param signal A pointer to a valid and ready to be signaled + * struct k_poll_signal. Or NULL to unregister any handler + * registered for the given vector. + * @param vector The interrupt vector to get notification from + * + * Note: The returned status, if positive, to a raised signal is the vector + * that generated the signal. This lets the possibility to the user + * to have one signal for all vectors, or one per-vector. + * + * @return 0 on success, a negative errno otherwise + */ +static inline int ivshmem_register_handler(const struct device *dev, + struct k_poll_signal *signal, + uint16_t vector) +{ + const struct ivshmem_driver_api *api = + (const struct ivshmem_driver_api *)dev->api; + + return api->register_handler(dev, signal, vector); +} + #ifdef __cplusplus } #endif