acrn-hypervisor/devicemodel/hw/pci/gsi_sharing.c

237 lines
5.5 KiB
C

/*
* Copyright (C) 2018 Intel Corporation. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <errno.h>
#include <sys/queue.h>
#include <pciaccess.h>
#include "pci_core.h"
#include "mevent.h"
#define MAX_DEV_PER_GSI 4
/* physical info of the GSI sharing group */
struct gsi_sharing_group {
uint8_t gsi;
/* the number of devices that are sharing the same GSI */
int shared_dev_num;
struct {
char *dev_name;
int assigned_to_this_vm;
} dev[MAX_DEV_PER_GSI];
LIST_ENTRY(gsi_sharing_group) gsg_list;
};
static LIST_HEAD(gsg_struct, gsi_sharing_group) gsg_head;
static int
update_gsi_sharing_info(char *dev_name, uint8_t gsi)
{
int gsi_shared = 0;
struct gsi_sharing_group *group = NULL;
LIST_FOREACH(group, &gsg_head, gsg_list) {
if (gsi != group->gsi)
continue;
if (group->shared_dev_num >= MAX_DEV_PER_GSI) {
pr_err("max %d devices share one GSI", MAX_DEV_PER_GSI);
return -EINVAL;
}
gsi_shared = 1;
break;
}
if (gsi_shared == 0) {
group = calloc(1, sizeof(struct gsi_sharing_group));
if (!group) {
pr_err("%s: calloc FAIL!", __func__);
return -ENOMEM;
}
group->gsi = gsi;
group->shared_dev_num = 0;
LIST_INSERT_HEAD(&gsg_head, group, gsg_list);
}
if (group != NULL) {
group->dev[group->shared_dev_num].dev_name = dev_name;
group->dev[group->shared_dev_num].assigned_to_this_vm = 0;
group->shared_dev_num++;
}
return 0;
}
/*
* return 1 if MSI/MSI-x is supported
* return 0 if MSI/MSI-x is NOT supported
*/
static int
check_msi_capability(char *dev_name)
{
int bus, slot, func;
uint16_t status;
uint8_t cap_ptr, cap_id;
struct pci_device *phys_dev;
/* only check the MSI/MSI-x capability for PCI device */
if (parse_bdf(dev_name, &bus, &slot, &func, 16) != 0)
return 0;
phys_dev = pci_device_find_by_slot(0, bus, slot, func);
if (!phys_dev)
return 0;
pci_device_cfg_read_u16(phys_dev, &status, PCIR_STATUS);
if (status & PCIM_STATUS_CAPPRESENT) {
pci_device_cfg_read_u8(phys_dev, &cap_ptr, PCIR_CAP_PTR);
while (cap_ptr != 0 && cap_ptr != 0xff) {
pci_device_cfg_read_u8(phys_dev, &cap_id,
cap_ptr + PCICAP_ID);
if (cap_id == PCIY_MSI)
return 1;
else if (cap_id == PCIY_MSIX)
return 1;
pci_device_cfg_read_u8(phys_dev, &cap_ptr,
cap_ptr + PCICAP_NEXTPTR);
}
}
return 0;
}
int
create_gsi_sharing_groups(void)
{
uint8_t gsi;
char *dev_name;
int i, error, msi_support;
struct gsi_sharing_group *group = NULL, *temp = NULL;
error = pciaccess_init();
if (error < 0)
return error;
for (i = 0; i < num_gsi_dev_mapping_tables; i++) {
dev_name = gsi_dev_mapping_tables[i].dev_name;
gsi = gsi_dev_mapping_tables[i].gsi;
/* skip the devices that support MSI/MSI-x */
msi_support = check_msi_capability(dev_name);
if (msi_support == 1)
continue;
/* insert the device into gsi_sharing_group */
error = update_gsi_sharing_info(dev_name, gsi);
if (error < 0)
return error;
}
pciaccess_cleanup();
/*
* clean up gsg_head - the list for gsi_sharing_group
* delete the element without gsi sharing condition (shared_dev_num < 2)
*/
list_foreach_safe(group, &gsg_head, gsg_list, temp) {
if (group->shared_dev_num < 2) {
LIST_REMOVE(group, gsg_list);
free(group);
}
}
return 0;
}
/*
* update passthrough info in gsi_sharing_group
* set assigned_to_this_vm as 1 if the PCI device is assigned to current VM
*/
void
update_pt_info(uint16_t phys_bdf)
{
char *name;
int i, bus, slot, func;
struct gsi_sharing_group *group;
LIST_FOREACH(group, &gsg_head, gsg_list) {
for (i = 0; i < (group->shared_dev_num); i++) {
name = group->dev[i].dev_name;
if (parse_bdf(name, &bus, &slot, &func, 16) != 0)
continue;
if (phys_bdf == (PCI_BDF(bus, slot, func)))
group->dev[i].assigned_to_this_vm = 1;
}
}
}
/* check if all devices in one GSI sharing group are assigned to same VM */
int
check_gsi_sharing_violation(void)
{
struct gsi_sharing_group *group, *temp;
int i, error, violation;
error = 0;
LIST_FOREACH(group, &gsg_head, gsg_list) {
/*
* All the PCI devices that are sharing the same GSI should be
* assigned to same VM to avoid physical GSI sharing between
* multiple VMs.
*
* If the value of 'assigned_to_this_vm' for each device in one
* gsi_sharing_group are all the same, either all 0 or all 1, it
* indicates that there is no GSI sharing between multiple VMs.
* All 0 means that all devices are NOT assigned to current VM.
* All 1 means that all devices are assigned to current VM.
*
* Otherwise, the passthrough will be rejected due to violation.
*/
violation = 0;
for (i = 1; i < (group->shared_dev_num); i++) {
if (group->dev[i].assigned_to_this_vm !=
group->dev[i - 1].assigned_to_this_vm) {
violation = 1;
break;
}
}
if (violation == 0)
continue;
/* reject the passthrough since gsi sharing violation occurs */
pr_err("GSI SHARING VIOLATION!");
pr_err("following physical devices are sharing same GSI, please "
"assign them to same VM to avoid physical GSI sharing "
"between multiple VMs");
for (i = 0; i < (group->shared_dev_num); i++) {
pr_info("device %s \t assigned_to_this_vm %d",
group->dev[i].dev_name,
group->dev[i].assigned_to_this_vm);
}
error = -EINVAL;
break;
}
/* destroy the gsg_head after all the checks have been done */
list_foreach_safe(group, &gsg_head, gsg_list, temp) {
LIST_REMOVE(group, gsg_list);
free(group);
}
return error;
}