Changeset View
Standalone View
usr.sbin/bhyve/pci_passthru.c
Show First 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | ||||||||||
#include <string.h> | #include <string.h> | |||||||||
#include <err.h> | #include <err.h> | |||||||||
#include <errno.h> | #include <errno.h> | |||||||||
#include <fcntl.h> | #include <fcntl.h> | |||||||||
#include <sysexits.h> | #include <sysexits.h> | |||||||||
#include <unistd.h> | #include <unistd.h> | |||||||||
#include <machine/vmm.h> | #include <machine/vmm.h> | |||||||||
#include <vmmapi.h> | ||||||||||
#include "config.h" | #include "config.h" | |||||||||
#include "debug.h" | #include "debug.h" | |||||||||
#include "pci_emul.h" | ||||||||||
#include "mem.h" | #include "mem.h" | |||||||||
#include "pci_passthru.h" | ||||||||||
#ifndef _PATH_DEVPCI | #ifndef _PATH_DEVPCI | |||||||||
#define _PATH_DEVPCI "/dev/pci" | #define _PATH_DEVPCI "/dev/pci" | |||||||||
#endif | #endif | |||||||||
#define LEGACY_SUPPORT 1 | #define LEGACY_SUPPORT 1 | |||||||||
#define MSIX_TABLE_COUNT(ctrl) (((ctrl) & PCIM_MSIXCTRL_TABLE_SIZE) + 1) | #define MSIX_TABLE_COUNT(ctrl) (((ctrl) & PCIM_MSIXCTRL_TABLE_SIZE) + 1) | |||||||||
#define MSIX_CAPLEN 12 | #define MSIX_CAPLEN 12 | |||||||||
#define PCI_CAP_START_OFFSET 0x40 | ||||||||||
static int pcifd = -1; | static int pcifd = -1; | |||||||||
struct passthru_softc { | ||||||||||
struct pci_devinst *psc_pi; | ||||||||||
struct pcibar psc_bar[PCI_BARMAX + 1]; | ||||||||||
struct { | ||||||||||
int capoff; | ||||||||||
int msgctrl; | ||||||||||
int emulated; | ||||||||||
} psc_msi; | ||||||||||
struct { | ||||||||||
int capoff; | ||||||||||
} psc_msix; | ||||||||||
struct pcisel psc_sel; | ||||||||||
}; | ||||||||||
static int | static int | |||||||||
msi_caplen(int msgctrl) | msi_caplen(int msgctrl) | |||||||||
{ | { | |||||||||
int len; | int len; | |||||||||
len = 10; /* minimum length of msi capability */ | len = 10; /* minimum length of msi capability */ | |||||||||
if (msgctrl & PCIM_MSICTRL_64BIT) | if (msgctrl & PCIM_MSICTRL_64BIT) | |||||||||
len += 4; | len += 4; | |||||||||
#if 0 | #if 0 | |||||||||
/* | /* | |||||||||
* Ignore the 'mask' and 'pending' bits in the MSI capability. | * Ignore the 'mask' and 'pending' bits in the MSI capability. | |||||||||
* We'll let the guest manipulate them directly. | * We'll let the guest manipulate them directly. | |||||||||
*/ | */ | |||||||||
if (msgctrl & PCIM_MSICTRL_VECTOR) | if (msgctrl & PCIM_MSICTRL_VECTOR) | |||||||||
len += 10; | len += 10; | |||||||||
#endif | #endif | |||||||||
return (len); | return (len); | |||||||||
} | } | |||||||||
static uint32_t | uint32_t | |||||||||
read_config(const struct pcisel *sel, long reg, int width) | read_config(const struct pcisel *sel, long reg, int width) | |||||||||
{ | { | |||||||||
struct pci_io pi; | struct pci_io pi; | |||||||||
bzero(&pi, sizeof(pi)); | bzero(&pi, sizeof(pi)); | |||||||||
pi.pi_sel = *sel; | pi.pi_sel = *sel; | |||||||||
pi.pi_reg = reg; | pi.pi_reg = reg; | |||||||||
pi.pi_width = width; | pi.pi_width = width; | |||||||||
if (ioctl(pcifd, PCIOCREAD, &pi) < 0) | if (ioctl(pcifd, PCIOCREAD, &pi) < 0) | |||||||||
return (0); /* XXX */ | return (0); /* XXX */ | |||||||||
else | else | |||||||||
return (pi.pi_data); | return (pi.pi_data); | |||||||||
} | } | |||||||||
static void | void | |||||||||
write_config(const struct pcisel *sel, long reg, int width, uint32_t data) | write_config(const struct pcisel *sel, long reg, int width, uint32_t data) | |||||||||
{ | { | |||||||||
struct pci_io pi; | struct pci_io pi; | |||||||||
bzero(&pi, sizeof(pi)); | bzero(&pi, sizeof(pi)); | |||||||||
pi.pi_sel = *sel; | pi.pi_sel = *sel; | |||||||||
pi.pi_reg = reg; | pi.pi_reg = reg; | |||||||||
pi.pi_width = width; | pi.pi_width = width; | |||||||||
▲ Show 20 Lines • Show All 422 Lines • ▼ Show 20 Lines | cfginit(struct vmctx *ctx, struct pci_devinst *pi, int bus, int slot, int func) | |||||||||
error = 1; | error = 1; | |||||||||
sc = pi->pi_arg; | sc = pi->pi_arg; | |||||||||
bzero(&sc->psc_sel, sizeof(struct pcisel)); | bzero(&sc->psc_sel, sizeof(struct pcisel)); | |||||||||
sc->psc_sel.pc_bus = bus; | sc->psc_sel.pc_bus = bus; | |||||||||
sc->psc_sel.pc_dev = slot; | sc->psc_sel.pc_dev = slot; | |||||||||
sc->psc_sel.pc_func = func; | sc->psc_sel.pc_func = func; | |||||||||
/* copy physical PCI header to virtual cfgspace */ | ||||||||||
for (uint32_t i = 0; i < PCI_CAP_START_OFFSET; ++i) { | ||||||||||
jhb: Humm, I would maybe just use `int` or `u_int` instead of `uint32_t` for config register… | ||||||||||
Done Inline ActionsI would maybe use int or u_int instead of uint32_t for PCI config space addresses? If you really wanted a fixed-width type, uint16_t would be the most accurate, but a simple int is what the new function hooks use already. Also, I would maybe use i <= PCIR_MAXLAT instead of adding a new constant. jhb: I would maybe use `int` or `u_int` instead of `uint32_t` for PCI config space addresses? If… | ||||||||||
/* | ||||||||||
Not Done Inline Actions
markj: | ||||||||||
* INTLINE and INTPIN shouldn't be aligned with it's physical | ||||||||||
Not Done Inline Actions
markj: | ||||||||||
* value and they are already set by pci_emul_init | ||||||||||
*/ | ||||||||||
if (i == PCIR_INTLINE || i == PCIR_INTPIN) | ||||||||||
continue; | ||||||||||
pci_set_cfgdata8(pi, i, read_config(&sc->psc_sel, i, 1)); | ||||||||||
Done Inline ActionsHumm, is it really safe to read all these as individual bytes? I would be tempted to read things like BARs as 32-bit values as I'm not sure if all hardware is going to tolerate byte reads? jhb: Humm, is it really safe to read all these as individual bytes? I would be tempted to read… | ||||||||||
} | ||||||||||
if (cfginitmsi(sc) != 0) { | if (cfginitmsi(sc) != 0) { | |||||||||
warnx("failed to initialize MSI for PCI %d/%d/%d", | warnx("failed to initialize MSI for PCI %d/%d/%d", | |||||||||
bus, slot, func); | bus, slot, func); | |||||||||
goto done; | goto done; | |||||||||
} | } | |||||||||
if (cfginitbar(ctx, sc) != 0) { | if (cfginitbar(ctx, sc) != 0) { | |||||||||
warnx("failed to initialize BARs for PCI %d/%d/%d", | warnx("failed to initialize BARs for PCI %d/%d/%d", | |||||||||
bus, slot, func); | bus, slot, func); | |||||||||
goto done; | goto done; | |||||||||
} | } | |||||||||
pci_set_cfgdata16(pi, PCIR_COMMAND, read_config(&sc->psc_sel, | pci_set_cfgdata16(pi, PCIR_COMMAND, read_config(&sc->psc_sel, | |||||||||
PCIR_COMMAND, 2)); | PCIR_COMMAND, 2)); | |||||||||
error = 0; /* success */ | error = 0; /* success */ | |||||||||
done: | done: | |||||||||
return (error); | return (error); | |||||||||
} | } | |||||||||
int | ||||||||||
set_pcir_handler(struct passthru_softc *sc, uint32_t reg, uint32_t len, cfgread_handler rhandler, cfgwrite_handler whandler) | ||||||||||
Done Inline Actions80 cols? jhb: 80 cols? | ||||||||||
{ | ||||||||||
if (reg > PCI_REGMAX || reg + len > PCI_REGMAX + 1) | ||||||||||
return (-1); | ||||||||||
for (uint32_t i = reg; i < reg + len; ++i) { | ||||||||||
sc->psc_pcir_rhandler[i] = rhandler; | ||||||||||
sc->psc_pcir_whandler[i] = whandler; | ||||||||||
} | ||||||||||
return 0; | ||||||||||
} | ||||||||||
static int | static int | |||||||||
passthru_legacy_config(nvlist_t *nvl, const char *opts) | passthru_legacy_config(nvlist_t *nvl, const char *opts) | |||||||||
{ | { | |||||||||
char value[16]; | char value[16]; | |||||||||
int bus, slot, func; | int bus, slot, func; | |||||||||
if (opts == NULL) | if (opts == NULL) | |||||||||
return (0); | return (0); | |||||||||
▲ Show 20 Lines • Show All 72 Lines • ▼ Show 20 Lines | #define GET_INT_CONFIG(var, name) do { \ | |||||||||
} | } | |||||||||
sc = calloc(1, sizeof(struct passthru_softc)); | sc = calloc(1, sizeof(struct passthru_softc)); | |||||||||
pi->pi_arg = sc; | pi->pi_arg = sc; | |||||||||
sc->psc_pi = pi; | sc->psc_pi = pi; | |||||||||
/* initialize config space */ | /* initialize config space */ | |||||||||
error = cfginit(ctx, pi, bus, slot, func); | if ((error = cfginit(ctx, pi, bus, slot, func)) != 0) | |||||||||
Done Inline ActionsMaybe "Default PCI config registers to accessing the config register of the physical device." jhb: Maybe "Default PCI config registers to accessing the config register of the physical device." | ||||||||||
goto done; | ||||||||||
Not Done Inline ActionsBut now we don't emulate accesses to fields below PCIR_BAR(0)? markj: But now we don't emulate accesses to fields below PCIR_BAR(0)? | ||||||||||
Not Done Inline ActionsI should improve my commit message. Except the command and status register, the PCI header contains fixed values or values which require emulation anyways. So, IMHO, we should always emulate the whole PCI header. It makes the code simpler and avoids hardware accesses. corvink: I should improve my commit message.
Except the command and status register, the PCI header… | ||||||||||
/* set default handler for all PCI registers */ | ||||||||||
Done Inline ActionsThe whitespace seems inconsistent here and in the clauses below? jhb: The whitespace seems inconsistent here and in the clauses below? | ||||||||||
if ((error = set_pcir_handler(sc, 0, PCI_REGMAX + 1, | ||||||||||
Done Inline ActionsNewlines before comments. Also, I would maybe reword this somewhat to be more like "Emulate most PCI header registers", jhb: Newlines before comments.
Also, I would maybe reword this somewhat to be more like "Emulate… | ||||||||||
passthru_cfgread_default, passthru_cfgwrite_default)) != 0) | ||||||||||
goto done; | ||||||||||
/* protect PCI header */ | ||||||||||
if ((error = set_pcir_handler(sc, 0, PCI_CAP_START_OFFSET, | ||||||||||
passthru_cfgread_emulate, passthru_cfgwrite_emulate)) != 0) | ||||||||||
goto done; | ||||||||||
/* allow access to command and status register */ | ||||||||||
if ((error = set_pcir_handler(sc, PCIR_COMMAND, 0x04, | ||||||||||
passthru_cfgread_default, passthru_cfgwrite_default)) != 0) | ||||||||||
goto done; | ||||||||||
error = 0; /* success */ | ||||||||||
done: | done: | |||||||||
if (error) { | if (error) { | |||||||||
free(sc); | free(sc); | |||||||||
vm_unassign_pptdev(ctx, bus, slot, func); | vm_unassign_pptdev(ctx, bus, slot, func); | |||||||||
} | } | |||||||||
return (error); | return (error); | |||||||||
} | } | |||||||||
Show All 28 Lines | msixcap_access(struct passthru_softc *sc, int coff) | |||||||||
if (sc->psc_msix.capoff == 0) | if (sc->psc_msix.capoff == 0) | |||||||||
return (0); | return (0); | |||||||||
return (coff >= sc->psc_msix.capoff && | return (coff >= sc->psc_msix.capoff && | |||||||||
coff < sc->psc_msix.capoff + MSIX_CAPLEN); | coff < sc->psc_msix.capoff + MSIX_CAPLEN); | |||||||||
} | } | |||||||||
static int | static int | |||||||||
passthru_cfgread(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | passthru_cfgread(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int coff, | |||||||||
int bytes, uint32_t *rv) | ||||||||||
{ | ||||||||||
struct passthru_softc *sc; | ||||||||||
sc = pi->pi_arg; | ||||||||||
return sc->psc_pcir_rhandler[coff](ctx, vcpu, pi, coff, bytes, rv); | ||||||||||
} | ||||||||||
int | ||||||||||
passthru_cfgread_default(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | ||||||||||
int coff, int bytes, uint32_t *rv) | int coff, int bytes, uint32_t *rv) | |||||||||
{ | { | |||||||||
struct passthru_softc *sc; | struct passthru_softc *sc; | |||||||||
sc = pi->pi_arg; | sc = pi->pi_arg; | |||||||||
Done Inline Actions()'s around the "argument" to return jhb: ()'s around the "argument" to return | ||||||||||
/* | /* | |||||||||
* PCI BARs and MSI capability is emulated. | * MSI capability is emulated. | |||||||||
*/ | */ | |||||||||
if (bar_access(coff) || msicap_access(sc, coff)) | if (msicap_access(sc, coff)) | |||||||||
return (-1); | return (-1); | |||||||||
Done Inline ActionsHummm, shouldn't these be handled by setting the ranges for these config registers to the emulated hooks earlier? That is, I guess you need custom handlers for write, but having passthru_cfgwrite_msicap seems more consistent with the change you are making here? jhb: Hummm, shouldn't these be handled by setting the ranges for these config registers to the… | ||||||||||
#ifdef LEGACY_SUPPORT | ||||||||||
/* | /* | |||||||||
* Emulate PCIR_CAP_PTR if this device does not support MSI capability | ||||||||||
* natively. | ||||||||||
*/ | ||||||||||
if (sc->psc_msi.emulated) { | ||||||||||
Done Inline ActionsHow is this handled now? Seems like you need to add a new call to set emulated hooks for this range when this value is true? jhb: How is this handled now? Seems like you need to add a new call to set emulated hooks for this… | ||||||||||
if (coff >= PCIR_CAP_PTR && coff < PCIR_CAP_PTR + 4) | ||||||||||
return (-1); | ||||||||||
} | ||||||||||
#endif | ||||||||||
/* | ||||||||||
* Emulate the command register. If a single read reads both the | * Emulate the command register. If a single read reads both the | |||||||||
Done Inline ActionsI think it would be beneficial for this patch to move this into a separate function. jhb: I think it would be beneficial for this patch to move this into a separate function. | ||||||||||
* command and status registers, read the status register from the | * command and status registers, read the status register from the | |||||||||
* device's config space. | * device's config space. | |||||||||
*/ | */ | |||||||||
if (coff == PCIR_COMMAND) { | if (coff == PCIR_COMMAND) { | |||||||||
if (bytes <= 2) | if (bytes <= 2) | |||||||||
return (-1); | return (-1); | |||||||||
*rv = read_config(&sc->psc_sel, PCIR_STATUS, 2) << 16 | | *rv = read_config(&sc->psc_sel, PCIR_STATUS, 2) << 16 | | |||||||||
pci_get_cfgdata16(pi, PCIR_COMMAND); | pci_get_cfgdata16(pi, PCIR_COMMAND); | |||||||||
return (0); | return (0); | |||||||||
} | } | |||||||||
/* Everything else just read from the device's config space */ | /* Everything else just read from the device's config space */ | |||||||||
*rv = read_config(&sc->psc_sel, coff, bytes); | *rv = read_config(&sc->psc_sel, coff, bytes); | |||||||||
return (0); | return (0); | |||||||||
} | } | |||||||||
int | ||||||||||
passthru_cfgread_emulate(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | ||||||||||
int coff, int bytes, uint32_t *rv) | ||||||||||
{ | ||||||||||
return (-1); | ||||||||||
} | ||||||||||
static int | static int | |||||||||
passthru_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | passthru_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int coff, | |||||||||
int bytes, uint32_t val) | ||||||||||
{ | ||||||||||
struct passthru_softc *sc; | ||||||||||
sc = pi->pi_arg; | ||||||||||
return sc->psc_pcir_whandler[coff](ctx, vcpu, pi, coff, bytes, val); | ||||||||||
} | ||||||||||
int | ||||||||||
passthru_cfgwrite_default(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | ||||||||||
int coff, int bytes, uint32_t val) | int coff, int bytes, uint32_t val) | |||||||||
{ | { | |||||||||
int error, msix_table_entries, i; | int error, msix_table_entries, i; | |||||||||
struct passthru_softc *sc; | struct passthru_softc *sc; | |||||||||
uint16_t cmd_old; | uint16_t cmd_old; | |||||||||
sc = pi->pi_arg; | sc = pi->pi_arg; | |||||||||
/* | /* | |||||||||
* PCI BARs are emulated | ||||||||||
*/ | ||||||||||
if (bar_access(coff)) | ||||||||||
return (-1); | ||||||||||
/* | ||||||||||
* MSI capability is emulated | * MSI capability is emulated | |||||||||
*/ | */ | |||||||||
if (msicap_access(sc, coff)) { | if (msicap_access(sc, coff)) { | |||||||||
pci_emul_capwrite(pi, coff, bytes, val, sc->psc_msi.capoff, | pci_emul_capwrite(pi, coff, bytes, val, sc->psc_msi.capoff, | |||||||||
PCIY_MSI); | PCIY_MSI); | |||||||||
error = vm_setup_pptdev_msi(ctx, vcpu, sc->psc_sel.pc_bus, | error = vm_setup_pptdev_msi(ctx, vcpu, sc->psc_sel.pc_bus, | |||||||||
sc->psc_sel.pc_dev, sc->psc_sel.pc_func, | sc->psc_sel.pc_dev, sc->psc_sel.pc_func, | |||||||||
pi->pi_msi.addr, pi->pi_msi.msg_data, | pi->pi_msi.addr, pi->pi_msi.msg_data, | |||||||||
▲ Show 20 Lines • Show All 46 Lines • ▼ Show 20 Lines | if (coff == PCIR_COMMAND) { | |||||||||
if (bytes == 1) | if (bytes == 1) | |||||||||
pci_set_cfgdata8(pi, PCIR_COMMAND, val); | pci_set_cfgdata8(pi, PCIR_COMMAND, val); | |||||||||
else if (bytes == 2) | else if (bytes == 2) | |||||||||
pci_set_cfgdata16(pi, PCIR_COMMAND, val); | pci_set_cfgdata16(pi, PCIR_COMMAND, val); | |||||||||
pci_emul_cmd_changed(pi, cmd_old); | pci_emul_cmd_changed(pi, cmd_old); | |||||||||
} | } | |||||||||
return (0); | return (0); | |||||||||
} | ||||||||||
int | ||||||||||
passthru_cfgwrite_emulate(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, | ||||||||||
int coff, int bytes, uint32_t val) | ||||||||||
{ | ||||||||||
return (-1); | ||||||||||
} | } | |||||||||
static void | static void | |||||||||
passthru_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, | passthru_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx, | |||||||||
uint64_t offset, int size, uint64_t value) | uint64_t offset, int size, uint64_t value) | |||||||||
{ | { | |||||||||
struct passthru_softc *sc; | struct passthru_softc *sc; | |||||||||
struct pci_bar_ioreq pio; | struct pci_bar_ioreq pio; | |||||||||
▲ Show 20 Lines • Show All 151 Lines • Show Last 20 Lines |
Humm, I would maybe just use int or u_int instead of uint32_t for config register addresses throughout? The new function hooks all use int.