Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow adding MSI capability via vfu_pci_add_capability #758

Merged
merged 6 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/libvfio-user.h
Original file line number Diff line number Diff line change
Expand Up @@ -981,6 +981,7 @@ vfu_pci_get_config_space(vfu_ctx_t *vfu_ctx);
* Certain standard capabilities are handled entirely within the library:
*
* PCI_CAP_ID_EXP (pxcap)
* PCI_CAP_ID_MSI (msicap)
* PCI_CAP_ID_MSIX (msixcap)
* PCI_CAP_ID_PM (pmcap)
*
Expand Down
33 changes: 19 additions & 14 deletions include/pci_caps/msi.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,38 @@
extern "C" {
#endif

/* Message Control for MSI */
struct mc {
unsigned int msie:1;
unsigned int mmc:3;
unsigned int mme:3;
unsigned int c64:1;
unsigned int pvm:1;
unsigned int res1:7;
unsigned int msie:1; /* RW */
unsigned int mmc:3; /* RO */
unsigned int mme:3; /* RW */
unsigned int c64:1; /* RO */
unsigned int pvm:1; /* RO */
unsigned int res1:7; /* not implemented, extended message data control */
} __attribute__ ((packed));
_Static_assert(sizeof(struct mc) == 0x2, "bad MC size");

/* Message Address for MSI */
struct ma {
unsigned int res1:2;
unsigned int addr:30;
unsigned int res1:2; /* read must return 0, write has no effect */
unsigned int addr:30; /* RW */
} __attribute__ ((packed));
_Static_assert(sizeof(struct ma) == 0x4, "bad MA size");

#define VFIO_USER_PCI_CAP_MSI_SIZEOF (0x18)

struct msicap {
struct cap_hdr hdr;
struct mc mc;
struct ma ma;
uint32_t mua;
uint16_t md;
uint16_t padding;
uint32_t mmask;
uint32_t mpend;
uint32_t mua; /* RW */
uint16_t md; /* RW */
uint16_t padding; /* not implemented, extended message data */
uint32_t mmask; /* RW */
uint32_t mpend; /* RO */
} __attribute__ ((packed));
_Static_assert(sizeof(struct msicap) == 0x18, "bad MSICAP size");
_Static_assert(sizeof(struct msicap) == VFIO_USER_PCI_CAP_MSI_SIZEOF,
"bad MSICAP size");
_Static_assert(offsetof(struct msicap, hdr) == 0, "bad offset");

#ifdef __cplusplus
Expand Down
67 changes: 67 additions & 0 deletions lib/pci_caps.c
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ cap_size(vfu_ctx_t *vfu_ctx, void *data, bool extended)
return PCI_PM_SIZEOF;
case PCI_CAP_ID_EXP:
return VFIO_USER_PCI_CAP_EXP_SIZEOF;
case PCI_CAP_ID_MSI:
return VFIO_USER_PCI_CAP_MSI_SIZEOF;
case PCI_CAP_ID_MSIX:
return PCI_CAP_MSIX_SIZEOF;
case PCI_CAP_ID_VNDR:
Expand Down Expand Up @@ -166,6 +168,67 @@ cap_write_pm(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char * buf,
return ERROR_INT(EINVAL);
}

static ssize_t
cap_write_msi(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char *buf,
size_t count, loff_t offset)
{
struct msicap *msi = cap_data(vfu_ctx, cap);
struct msicap new_msi = *msi;

memcpy((char *)&new_msi + offset - cap->off, buf, count);

if (msi->mc.msie != new_msi.mc.msie) {
msi->mc.msie = new_msi.mc.msie;
vfu_log(vfu_ctx, LOG_DEBUG, "%s MSI",
msi->mc.msie ? "enable" : "disable");
}

if (msi->mc.mme != new_msi.mc.mme) {
if (new_msi.mc.mme > 5) {
vfu_log(vfu_ctx, LOG_ERR,
"MSI cannot have more than 32 interrupt vectors");
return ERROR_INT(EINVAL);
}

if (new_msi.mc.mme > msi->mc.mmc) {
vfu_log(vfu_ctx, LOG_ERR,
"MSI cannot have more interrupt vectors"
" in MME than defined in MMC");
return ERROR_INT(EINVAL);
}
msi->mc.mme = new_msi.mc.mme;

vfu_log(vfu_ctx, LOG_DEBUG,
"MSI Updated Multiple Message Enable count");
}

if (msi->ma.addr != new_msi.ma.addr) {
msi->ma.addr = new_msi.ma.addr;
vfu_log(vfu_ctx, LOG_DEBUG,
"MSI Message Address set to %x", msi->ma.addr << 2);
}

if (msi->mua != new_msi.mua) {
msi->mua = new_msi.mua;
vfu_log(vfu_ctx, LOG_DEBUG,
"MSI Message Upper Address set to %x", msi->mua);
}

if (msi->md != new_msi.md) {
msi->md = new_msi.md;
vfu_log(vfu_ctx, LOG_DEBUG,
"MSI Message Data set to %x", msi->md);
}

if (msi->mmask != new_msi.mmask) {
msi->mmask = new_msi.mmask;
vfu_log(vfu_ctx, LOG_DEBUG,
"MSI Mask Bits set to %x", msi->mmask);
}

return count;
}

static ssize_t
cap_write_msix(vfu_ctx_t *vfu_ctx, struct pci_cap *cap, char *buf,
size_t count, loff_t offset)
Expand Down Expand Up @@ -682,6 +745,10 @@ vfu_pci_add_capability(vfu_ctx_t *vfu_ctx, size_t pos, int flags, void *data)
cap.name = "PCI Express";
cap.cb = cap_write_px;
break;
case PCI_CAP_ID_MSI:
cap.name = "MSI";
cap.cb = cap_write_msi;
break;
case PCI_CAP_ID_MSIX:
cap.name = "MSI-X";
cap.cb = cap_write_msix;
Expand Down
7 changes: 7 additions & 0 deletions test/py/libvfio_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@

PCI_CAP_ID_PM = 0x1
PCI_CAP_ID_VNDR = 0x9
PCI_CAP_ID_MSI = 0x5
PCI_CAP_ID_MSIX = 0x11
PCI_CAP_ID_EXP = 0x10

Expand All @@ -83,6 +84,12 @@

PCI_EXT_CAP_VNDR_HDR_SIZEOF = 8

# MSI registers
PCI_MSI_FLAGS = 2 # Message Control offset
PCI_MSI_ADDRESS_LO = 4 # Message Address offset
PCI_MSI_FLAGS_ENABLE = 0x0001 # MSI enable
PCI_CAP_MSI_SIZEOF = 24 # size of MSI registers

# MSI-X registers
PCI_MSIX_FLAGS = 2 # Message Control
PCI_MSIX_TABLE = 4 # Table offset
Expand Down
58 changes: 58 additions & 0 deletions test/py/test_pci_caps.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,64 @@ def test_pci_cap_write_px(mock_quiesce, mock_reset):
expect=errno.EINVAL)


def test_pci_cap_write_msi():
setup_pci_dev(realize=True)
sock = connect_client(ctx)

# Set MMC to 100b (16 interrupt vectors)
mmc = 0b00001000

# Bad MME with 101b (32 interrupt vectors), over MMC
mme_bad = 0b01010000
# Test MME with 100b (16 interrupt vectors)
mme_good = 0b01000000

# Test if capability is placed at right offset
pos = vfu_pci_add_capability(ctx, pos=0, flags=0,
data=struct.pack("ccHIIIII",
to_byte(PCI_CAP_ID_MSI),
b'\0', mmc, 0, 0, 0, 0, 0))
assert pos == cap_offsets[0]

offset = vfu_pci_find_capability(ctx, False, PCI_CAP_ID_MSI)

# Test if write fails as expected
# as MME is out of bounds, 111b is over the max of 101b (32 vectors)
data = b'\xff\xff'
write_region(ctx, sock, VFU_PCI_DEV_CFG_REGION_IDX,
offset=offset + PCI_MSI_FLAGS,
count=len(data), data=data, expect=errno.EINVAL)

# Test if write fails as expected
# as MME is over MMC, 101b (32 vectors) > 100b (16 vectors)
data = to_bytes_le(mme_bad, 2)
write_region(ctx, sock, VFU_PCI_DEV_CFG_REGION_IDX,
offset=offset + PCI_MSI_FLAGS,
count=len(data), data=data, expect=errno.EINVAL)

# Test good write, MSI Enable + good MME
data = to_bytes_le(PCI_MSI_FLAGS_ENABLE | mme_good, 2)
write_region(ctx, sock, VFU_PCI_DEV_CFG_REGION_IDX,
offset=offset + PCI_MSI_FLAGS,
count=len(data), data=data)

size_before_flags = PCI_CAP_MSI_SIZEOF - PCI_MSI_FLAGS
size_after_flags = PCI_CAP_MSI_SIZEOF - PCI_MSI_ADDRESS_LO

# reset
data = size_before_flags * b'\x00'
write_region(ctx, sock, VFU_PCI_DEV_CFG_REGION_IDX,
offset=offset + PCI_MSI_FLAGS,
count=len(data), data=data)

# Check if MMC is still present after reset (since it is RO)
expected = (to_bytes_le(PCI_CAP_ID_MSI) + b'\x00' +
to_bytes_le(mmc, 2) + (size_after_flags * b'\x00'))
payload = read_region(ctx, sock, VFU_PCI_DEV_CFG_REGION_IDX, offset=offset,
count=len(expected))
assert expected == payload


def test_pci_cap_write_msix():
setup_pci_dev(realize=True)
sock = connect_client(ctx)
Expand Down
Loading