mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-08-12 05:22:33 +00:00
1) region ID shall be configured by user via config tool. 2) region ID is programmed to "Subsystem ID" of PCI config space. 2) "Subsystem Vendor ID" is harded coded as 0x8086 Parameters to configure dm-land IVSHMEM device example generated by config tool as below: `add_virtual_device 8 ivshmem hv:/shm_region_0,256,2` Tracked-On: #8566 Signed-off-by: Yonghua Huang <yonghua.huang@intel.com> Reviewed-by: Jian Jun Chen <jian.jun.chen@intel.com>
410 lines
11 KiB
C
410 lines
11 KiB
C
/*
|
|
* Copyright (C) 2019-2022 Intel Corporation.
|
|
*
|
|
* SPDX-License-Identifier: BSD-3-Clause
|
|
*/
|
|
/*
|
|
* ACRN Inter-VM Virtualizaiton based on ivshmem-v1 device
|
|
*
|
|
* +----------+ +-----------------------------------------+ +----------+
|
|
* |Postlaunch| | Service OS | |Postlaunch|
|
|
* | VM | | | | VM |
|
|
* | | | Interrupt | | |
|
|
* |+--------+| |+----------+ Foward +----------+| |+--------+|
|
|
* || App || || acrn-dm | +-------+ | acrn-dm || || App ||
|
|
* || || ||+--------+| |ivshmem| |+--------+|| || ||
|
|
* |+---+----+| |||ivshmem ||<---+server +--->||ivshmem ||| |+---+----+|
|
|
* | | | +-+++ dm || +-------+ || dm +++-+ | | |
|
|
* | | | | ||+---+----+| |+----+---+|| | | | |
|
|
* |+---+----+| | |+----^-----+ +-----^----+| | |+---+----+|
|
|
* ||UIO || | | +---------------+-------------+ | | ||UIO ||
|
|
* ||driver || | | v | | ||driver ||
|
|
* |+---+----+| | | +--------+-------+ | | |+---+----+|
|
|
* | | | | | | /dev/shm | | | | | |
|
|
* | | | | | +--------+-------+ | | | | |
|
|
* |+---+----+| | | | | | |+---+----+|
|
|
* ||ivshmem || | | +--------+-------+ | | ||ivshmem ||
|
|
* ||device || | | | Shared Memory | | | ||device ||
|
|
* |+---+----+| | | +----------------+ | | |+---+----+|
|
|
* +----+-----+ | +-----------------------------------------+ | +----+-----+
|
|
* +--------+ +-------+
|
|
*
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "pci_core.h"
|
|
#include "vmmapi.h"
|
|
#include "dm_string.h"
|
|
#include "log.h"
|
|
|
|
#define IVSHMEM_MMIO_BAR 0
|
|
#define IVSHMEM_MSIX_BAR 1
|
|
#define IVSHMEM_MEM_BAR 2
|
|
|
|
#define IVSHMEM_VENDOR_ID 0x1af4
|
|
#define IVSHMEM_DEVICE_ID 0x1110
|
|
#define IVSHMEM_CLASS 0x05
|
|
#define IVSHMEM_REV 0x01
|
|
#define IVSHMEM_INTEL_SUBVENDOR_ID 0x8086U
|
|
|
|
|
|
/* IVSHMEM MMIO Registers */
|
|
#define IVSHMEM_REG_SIZE 0x100
|
|
#define IVSHMEM_IRQ_MASK_REG 0x00
|
|
#define IVSHMEM_IRQ_STA_REG 0x04
|
|
#define IVSHMEM_IV_POS_REG 0x08
|
|
#define IVSHMEM_DOORBELL_REG 0x0c
|
|
#define IVSHMEM_RESERVED_REG 0x0f
|
|
|
|
/*Size of MSIX BAR of ivshmem device should be 4KB-aligned.*/
|
|
#define IVSHMEM_MSIX_PBA_SIZE 0x1000
|
|
|
|
#define hv_land_prefix "hv:/"
|
|
#define dm_land_prefix "dm:/"
|
|
|
|
struct pci_ivshmem_vdev {
|
|
struct pci_vdev *dev;
|
|
char *name;
|
|
int fd;
|
|
void *addr;
|
|
uint32_t size;
|
|
bool is_hv_land;
|
|
};
|
|
|
|
static int
|
|
create_ivshmem_from_dm(struct vmctx *ctx, struct pci_vdev *vdev,
|
|
const char *name, uint32_t size)
|
|
{
|
|
struct stat st;
|
|
int fd = -1;
|
|
void *addr = NULL;
|
|
bool is_shm_creator = false;
|
|
struct pci_ivshmem_vdev *ivshmem_vdev = (struct pci_ivshmem_vdev *) vdev->arg;
|
|
uint64_t bar_addr;
|
|
|
|
fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600);
|
|
if (fd > 0)
|
|
is_shm_creator = true;
|
|
else if (fd < 0 && errno == EEXIST)
|
|
fd = shm_open(name, O_RDWR, 0600);
|
|
|
|
if (fd < 0) {
|
|
pr_warn("failed to get %s status, error %s\n",
|
|
name, strerror(errno));
|
|
goto err;
|
|
}
|
|
if (is_shm_creator) {
|
|
if (ftruncate(fd, size) < 0) {
|
|
pr_warn("can't resize the shm size %u\n", size);
|
|
goto err;
|
|
}
|
|
} else {
|
|
if ((fstat(fd, &st) < 0) || st.st_size != size) {
|
|
pr_warn("shm size is different, cur %u, creator %ld\n",
|
|
size, st.st_size);
|
|
goto err;
|
|
}
|
|
}
|
|
|
|
addr = (void *)mmap(NULL, size, PROT_READ | PROT_WRITE,
|
|
MAP_SHARED, fd, 0);
|
|
bar_addr = pci_get_cfgdata32(vdev, PCIR_BAR(IVSHMEM_MEM_BAR));
|
|
bar_addr |= ((uint64_t)pci_get_cfgdata32(vdev, PCIR_BAR(IVSHMEM_MEM_BAR + 1)) << 32);
|
|
bar_addr &= PCIM_BAR_MEM_BASE;
|
|
pr_dbg("shm configuration, vma 0x%lx, ivshmem bar 0x%lx, size 0x%x\n",
|
|
(uint64_t)addr, bar_addr, size);
|
|
if (!addr || vm_map_memseg_vma(ctx, size, bar_addr,
|
|
(uint64_t)addr, PROT_RW) < 0) {
|
|
pr_warn("failed to map shared memory\n");
|
|
goto err;
|
|
}
|
|
|
|
ivshmem_vdev->name = strdup(name);
|
|
if (!ivshmem_vdev->name) {
|
|
pr_warn("No memory for shm_name\n");
|
|
goto err;
|
|
}
|
|
ivshmem_vdev->fd = fd;
|
|
ivshmem_vdev->addr = addr;
|
|
ivshmem_vdev->size = size;
|
|
return 0;
|
|
err:
|
|
if (addr)
|
|
munmap(addr, size);
|
|
if (fd > 0)
|
|
close(fd);
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
create_ivshmem_from_hv(struct vmctx *ctx, struct pci_vdev *vdev,
|
|
const char *shm_name, uint32_t shm_size)
|
|
{
|
|
struct acrn_vdev dev = {};
|
|
uint64_t addr = 0;
|
|
|
|
dev.id.fields.vendor = IVSHMEM_VENDOR_ID;
|
|
dev.id.fields.device = IVSHMEM_DEVICE_ID;
|
|
dev.slot = PCI_BDF(vdev->bus, vdev->slot, vdev->func);
|
|
dev.io_addr[IVSHMEM_MMIO_BAR] = pci_get_cfgdata32(vdev,
|
|
PCIR_BAR(IVSHMEM_MMIO_BAR));
|
|
|
|
/*MSI-x entry table BAR(BAR1)*/
|
|
addr = pci_get_cfgdata32(vdev, PCIR_BAR(IVSHMEM_MSIX_BAR));
|
|
dev.io_addr[IVSHMEM_MSIX_BAR] = addr;
|
|
|
|
addr = pci_get_cfgdata32(vdev, PCIR_BAR(IVSHMEM_MEM_BAR));
|
|
addr |= 0x0c; /* 64bit, prefetchable */
|
|
dev.io_addr[IVSHMEM_MEM_BAR] = addr;
|
|
addr = pci_get_cfgdata32(vdev, PCIR_BAR(IVSHMEM_MEM_BAR + 1));
|
|
dev.io_addr[IVSHMEM_MEM_BAR + 1] = addr;
|
|
dev.io_size[IVSHMEM_MEM_BAR] = shm_size;
|
|
strncpy((char*)dev.args, shm_name, 32);
|
|
return vm_add_hv_vdev(ctx, &dev);
|
|
}
|
|
|
|
static void
|
|
pci_ivshmem_write(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
|
int baridx, uint64_t offset, int size, uint64_t value)
|
|
{
|
|
pr_dbg("%s: baridx %d, offset = %lx, value = 0x%lx\n",
|
|
__func__, baridx, offset, value);
|
|
|
|
if (baridx == IVSHMEM_MMIO_BAR) {
|
|
switch (offset) {
|
|
/*
|
|
* Following registers are used to support
|
|
* notification/interrupt in future.
|
|
*/
|
|
case IVSHMEM_IRQ_MASK_REG:
|
|
case IVSHMEM_IRQ_STA_REG:
|
|
break;
|
|
case IVSHMEM_DOORBELL_REG:
|
|
pr_warn("Doorbell capability doesn't support for now, ignore vectors 0x%lx, peer id %lu\n",
|
|
value & 0xff, ((value >> 16) & 0xff));
|
|
break;
|
|
default:
|
|
pr_dbg("%s: invalid device register 0x%lx\n",
|
|
__func__, offset);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint64_t
|
|
pci_ivshmem_read(struct vmctx *ctx, int vcpu, struct pci_vdev *dev,
|
|
int baridx, uint64_t offset, int size)
|
|
{
|
|
uint64_t val = ~0;
|
|
|
|
pr_dbg("%s: baridx %d, offset = 0x%lx, size = 0x%x\n",
|
|
__func__, baridx, offset, size);
|
|
|
|
if (baridx == IVSHMEM_MMIO_BAR) {
|
|
switch (offset) {
|
|
/*
|
|
* Following registers are used to support
|
|
* notification/interrupt in future.
|
|
*/
|
|
case IVSHMEM_IRQ_MASK_REG:
|
|
case IVSHMEM_IRQ_STA_REG:
|
|
val = 0;
|
|
break;
|
|
/*
|
|
* If ivshmem device doesn't support interrupt,
|
|
* The IVPosition is zero. otherwise, it is Peer ID.
|
|
*/
|
|
case IVSHMEM_IV_POS_REG:
|
|
val = 0;
|
|
break;
|
|
default:
|
|
pr_dbg("%s: invalid device register 0x%lx\n",
|
|
__func__, offset);
|
|
break;
|
|
}
|
|
}
|
|
|
|
switch (size) {
|
|
case 1:
|
|
val &= 0xFF;
|
|
break;
|
|
case 2:
|
|
val &= 0xFFFF;
|
|
break;
|
|
case 4:
|
|
val &= 0xFFFFFFFF;
|
|
break;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
static int
|
|
pci_ivshmem_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
|
{
|
|
uint32_t size, region_id = 0;
|
|
char *tmp, *name, *size_str, *orig;
|
|
struct pci_ivshmem_vdev *ivshmem_vdev = NULL;
|
|
bool is_hv_land;
|
|
int rc;
|
|
|
|
/* ivshmem device usage: "-s N,ivshmem,shm_name,shm_size,region_id" */
|
|
tmp = orig = strdup(opts);
|
|
if (!orig) {
|
|
pr_warn("No memory for strdup\n");
|
|
goto err;
|
|
}
|
|
name = strsep(&tmp, ",");
|
|
if (!name) {
|
|
pr_warn("the shared memory size is not set\n");
|
|
goto err;
|
|
}
|
|
|
|
if (!strncmp(name, hv_land_prefix, strlen(hv_land_prefix))) {
|
|
is_hv_land = true;
|
|
} else if (!strncmp(name, dm_land_prefix, strlen(dm_land_prefix))) {
|
|
is_hv_land = false;
|
|
name += strlen(dm_land_prefix);
|
|
} else {
|
|
pr_warn("the ivshmem memory prefix name is incorrect\n");
|
|
goto err;
|
|
}
|
|
|
|
size_str = strsep(&tmp, ",");
|
|
if (dm_strtoui(size_str, &size_str, 10, &size) != 0) {
|
|
pr_warn("the shared memory size is incorrect, %s\n", size_str);
|
|
goto err;
|
|
}
|
|
size *= 0x100000; /* convert to megabytes */
|
|
if (size < 0x200000 || size >= 0x40000000 ||
|
|
(size & (size - 1)) != 0) {
|
|
pr_warn("Invalid shared memory size %u, the size range is [2MB,512MB], the unit is megabyte and the value must be a power of 2\n",
|
|
size/0x100000);
|
|
goto err;
|
|
}
|
|
|
|
if (tmp) {
|
|
if (dm_strtoui(tmp, &tmp, 10, ®ion_id) != 0) {
|
|
pr_warn("shared memory region ID is incorrect, %s, 0 will used.\n", tmp);
|
|
region_id = 0;
|
|
}
|
|
}
|
|
|
|
ivshmem_vdev = calloc(1, sizeof(struct pci_ivshmem_vdev));
|
|
if (!ivshmem_vdev) {
|
|
pr_warn("failed to allocate ivshmem device\n");
|
|
goto err;
|
|
}
|
|
|
|
ivshmem_vdev->dev = dev;
|
|
ivshmem_vdev->is_hv_land = is_hv_land;
|
|
dev->arg = ivshmem_vdev;
|
|
|
|
/* initialize config space */
|
|
pci_set_cfgdata16(dev, PCIR_VENDOR, IVSHMEM_VENDOR_ID);
|
|
pci_set_cfgdata16(dev, PCIR_DEVICE, IVSHMEM_DEVICE_ID);
|
|
pci_set_cfgdata16(dev, PCIR_REVID, IVSHMEM_REV);
|
|
pci_set_cfgdata8(dev, PCIR_CLASS, IVSHMEM_CLASS);
|
|
pci_set_cfgdata16(dev, PCIR_SUBDEV_0, (uint16_t)region_id);
|
|
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, IVSHMEM_INTEL_SUBVENDOR_ID);
|
|
|
|
pci_emul_alloc_bar(dev, IVSHMEM_MMIO_BAR, PCIBAR_MEM32, IVSHMEM_REG_SIZE);
|
|
pci_emul_alloc_bar(dev, IVSHMEM_MSIX_BAR, PCIBAR_MEM32, IVSHMEM_MSIX_PBA_SIZE);
|
|
pci_emul_alloc_bar(dev, IVSHMEM_MEM_BAR, PCIBAR_MEM64, size);
|
|
|
|
if (is_hv_land) {
|
|
rc = create_ivshmem_from_hv(ctx, dev, name, size);
|
|
} else {
|
|
/*
|
|
* TODO: If User VM reprograms ivshmem BAR2, the shared memory will be
|
|
* unavailable for User VM, so we need to remap GPA and HPA of shared
|
|
* memory in this case.
|
|
*/
|
|
rc = create_ivshmem_from_dm(ctx, dev, name, size);
|
|
}
|
|
if (rc < 0)
|
|
goto err;
|
|
|
|
free(orig);
|
|
return 0;
|
|
err:
|
|
if (orig)
|
|
free(orig);
|
|
if (ivshmem_vdev) {
|
|
free(ivshmem_vdev);
|
|
dev->arg = NULL;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
destroy_ivshmem_from_dm(struct pci_ivshmem_vdev *vdev)
|
|
{
|
|
if (vdev->addr && vdev->size)
|
|
munmap(vdev->addr, vdev->size);
|
|
if (vdev->fd > 0)
|
|
close(vdev->fd);
|
|
}
|
|
|
|
static void
|
|
destroy_ivshmem_from_hv(struct vmctx *ctx, struct pci_vdev *vdev)
|
|
{
|
|
|
|
struct acrn_vdev emul_dev = {};
|
|
|
|
emul_dev.id.fields.vendor = IVSHMEM_VENDOR_ID;
|
|
emul_dev.id.fields.device = IVSHMEM_DEVICE_ID;
|
|
emul_dev.slot = PCI_BDF(vdev->bus, vdev->slot, vdev->func);
|
|
vm_remove_hv_vdev(ctx, &emul_dev);
|
|
}
|
|
|
|
static void
|
|
pci_ivshmem_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
|
|
{
|
|
struct pci_ivshmem_vdev *vdev;
|
|
|
|
vdev = (struct pci_ivshmem_vdev *)dev->arg;
|
|
if (!vdev) {
|
|
pr_warn("%s, invalid ivshmem instance\n", __func__);
|
|
return;
|
|
}
|
|
if (vdev->is_hv_land)
|
|
destroy_ivshmem_from_hv(ctx, dev);
|
|
else
|
|
destroy_ivshmem_from_dm(vdev);
|
|
|
|
if (vdev->name) {
|
|
/*
|
|
* shm_unlink will only remove the shared memory file object,
|
|
* the shared memory will be released until all processes
|
|
* which opened the shared memory close the file.
|
|
*
|
|
* Don't invoke shm_unlink(vdev->name) to remove file object now,
|
|
* so that the acrn-dm can communicate with the peer again after
|
|
* rebooting/shutdown, the side effect is that the shared memory
|
|
* will not be released even if all peers exit.
|
|
*/
|
|
free(vdev->name);
|
|
}
|
|
|
|
free(vdev);
|
|
dev->arg = NULL;
|
|
}
|
|
|
|
struct pci_vdev_ops pci_ops_ivshmem = {
|
|
.class_name = "ivshmem",
|
|
.vdev_init = pci_ivshmem_init,
|
|
.vdev_deinit = pci_ivshmem_deinit,
|
|
.vdev_barwrite = pci_ivshmem_write,
|
|
.vdev_barread = pci_ivshmem_read
|
|
};
|
|
DEFINE_PCI_DEVTYPE(pci_ops_ivshmem);
|