Changeset View
Standalone View
usr.sbin/bhyve/pci_lpc.c
Show First 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | ||||||||||||||
#include "acpi.h" | #include "acpi.h" | |||||||||||||
#include "debug.h" | #include "debug.h" | |||||||||||||
#include "bootrom.h" | #include "bootrom.h" | |||||||||||||
#include "config.h" | #include "config.h" | |||||||||||||
#include "inout.h" | #include "inout.h" | |||||||||||||
#include "pci_emul.h" | #include "pci_emul.h" | |||||||||||||
#include "pci_irq.h" | #include "pci_irq.h" | |||||||||||||
#include "pci_lpc.h" | #include "pci_lpc.h" | |||||||||||||
#include "pci_passthru.h" | ||||||||||||||
#include "pctestdev.h" | #include "pctestdev.h" | |||||||||||||
#include "uart_emul.h" | #include "uart_emul.h" | |||||||||||||
#define IO_ICU1 0x20 | #define IO_ICU1 0x20 | |||||||||||||
#define IO_ICU2 0xA0 | #define IO_ICU2 0xA0 | |||||||||||||
SET_DECLARE(lpc_dsdt_set, struct lpc_dsdt); | SET_DECLARE(lpc_dsdt_set, struct lpc_dsdt); | |||||||||||||
SET_DECLARE(lpc_sysres_set, struct lpc_sysres); | SET_DECLARE(lpc_sysres_set, struct lpc_sysres); | |||||||||||||
Show All 23 Lines | ||||||||||||||
static const char *lpc_uart_acpi_names[LPC_UART_NUM] = { | static const char *lpc_uart_acpi_names[LPC_UART_NUM] = { | |||||||||||||
"COM1", "COM2", "COM3", "COM4" | "COM1", "COM2", "COM3", "COM4" | |||||||||||||
}; | }; | |||||||||||||
/* | /* | |||||||||||||
* LPC device configuration is in the following form: | * LPC device configuration is in the following form: | |||||||||||||
* <lpc_device_name>[,<options>] | * <lpc_device_name>[,<options>] | |||||||||||||
* For e.g. "com1,stdio" or "bootrom,/var/romfile" | * For e.g. "com1,stdio" or "bootrom,/var/romfile" | |||||||||||||
*/ | */ | |||||||||||||
markj: I'd prefer to pass the descriptor as a parameter rather than introducing a global variable. | ||||||||||||||
int | int | |||||||||||||
lpc_device_parse(const char *opts) | lpc_device_parse(const char *opts) | |||||||||||||
{ | { | |||||||||||||
int unit, error; | int unit, error; | |||||||||||||
char *str, *cpy, *lpcdev, *node_name; | char *str, *cpy, *lpcdev, *node_name; | |||||||||||||
const char *romfile, *varfile; | const char *romfile, *varfile; | |||||||||||||
error = -1; | error = -1; | |||||||||||||
▲ Show 20 Lines • Show All 341 Lines • ▼ Show 20 Lines | ||||||||||||||
pci_lpc_read(struct pci_devinst *pi __unused, int baridx __unused, | pci_lpc_read(struct pci_devinst *pi __unused, int baridx __unused, | |||||||||||||
uint64_t offset __unused, int size __unused) | uint64_t offset __unused, int size __unused) | |||||||||||||
{ | { | |||||||||||||
return (0); | return (0); | |||||||||||||
} | } | |||||||||||||
#define LPC_DEV 0x7000 | #define LPC_DEV 0x7000 | |||||||||||||
#define LPC_VENDOR 0x8086 | #define LPC_VENDOR 0x8086 | |||||||||||||
#define LPC_REVID 0x00 | ||||||||||||||
#define LPC_SUBVEND_0 0x0000 | ||||||||||||||
#define LPC_SUBDEV_0 0x0000 | ||||||||||||||
static int | static int | |||||||||||||
pci_lpc_init(struct pci_devinst *pi, nvlist_t *nvl __unused) | pci_lpc_get_sel(struct pcisel *const sel) | |||||||||||||
{ | { | |||||||||||||
assert(sel != NULL); | ||||||||||||||
memset(sel, 0, sizeof(*sel)); | ||||||||||||||
for (uint8_t slot = 0; slot <= PCI_SLOTMAX; ++slot) { | ||||||||||||||
sel->pc_dev = slot; | ||||||||||||||
if ((read_config(sel, PCIR_CLASS, 1) == PCIC_BRIDGE) && | ||||||||||||||
Done Inline Actions
jhb: | ||||||||||||||
(read_config(sel, PCIR_SUBCLASS, 1) == PCIS_BRIDGE_ISA)) { | ||||||||||||||
return (0); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
return (-1); | ||||||||||||||
} | ||||||||||||||
static int | ||||||||||||||
pci_lpc_init(struct pci_devinst *pi, nvlist_t *nvl) | ||||||||||||||
{ | ||||||||||||||
struct pcisel sel = { 0 }; | ||||||||||||||
uint16_t device, subdevice, subvendor, vendor; | ||||||||||||||
uint8_t revid; | ||||||||||||||
Done Inline ActionsCould maybe s/def/default/. :) jhb: Could maybe s/def/default/. :) | ||||||||||||||
Done Inline Actionserror: invalid parameter name: 'default' is a keyword corvink: `error: invalid parameter name: 'default' is a keyword` | ||||||||||||||
/* | /* | |||||||||||||
* Do not allow more than one LPC bridge to be configured. | * Do not allow more than one LPC bridge to be configured. | |||||||||||||
*/ | */ | |||||||||||||
if (lpc_bridge != NULL) { | if (lpc_bridge != NULL) { | |||||||||||||
EPRINTLN("Only one LPC bridge is allowed."); | EPRINTLN("Only one LPC bridge is allowed."); | |||||||||||||
return (-1); | return (-1); | |||||||||||||
} | } | |||||||||||||
/* | /* | |||||||||||||
* Enforce that the LPC can only be configured on bus 0. This | * Enforce that the LPC can only be configured on bus 0. This | |||||||||||||
* simplifies the ACPI DSDT because it can provide a decode for | * simplifies the ACPI DSDT because it can provide a decode for | |||||||||||||
* all legacy i/o ports behind bus 0. | * all legacy i/o ports behind bus 0. | |||||||||||||
*/ | */ | |||||||||||||
if (pi->pi_bus != 0) { | if (pi->pi_bus != 0) { | |||||||||||||
EPRINTLN("LPC bridge can be present only on bus 0."); | EPRINTLN("LPC bridge can be present only on bus 0."); | |||||||||||||
return (-1); | return (-1); | |||||||||||||
} | } | |||||||||||||
if (lpc_init(pi->pi_vmctx) != 0) | if (lpc_init(pi->pi_vmctx) != 0) | |||||||||||||
return (-1); | return (-1); | |||||||||||||
if (pci_lpc_get_sel(&sel) != 0) | ||||||||||||||
return (-1); | ||||||||||||||
Done Inline Actions
markj: | ||||||||||||||
vendor = pci_config_read_reg(&sel, nvl, PCIR_VENDOR, 2, LPC_VENDOR); | ||||||||||||||
device = pci_config_read_reg(&sel, nvl, PCIR_DEVICE, 2, LPC_DEV); | ||||||||||||||
Done Inline ActionsShould we also check the device class and subclass? markj: Should we also check the device class and subclass? | ||||||||||||||
Done Inline ActionsOn physical systems, it's unnecessary. However, it might change in future and it could be different in nested VM situations. So yeah, it makes sense to check device class and subclass too. corvink: On physical systems, it's unnecessary. However, it might change in future and it could be… | ||||||||||||||
revid = pci_config_read_reg(&sel, nvl, PCIR_REVID, 1, LPC_REVID); | ||||||||||||||
subvendor = pci_config_read_reg(&sel, nvl, PCIR_SUBVEND_0, 2, | ||||||||||||||
LPC_SUBVEND_0); | ||||||||||||||
subdevice = pci_config_read_reg(&sel, nvl, PCIR_SUBDEV_0, 2, | ||||||||||||||
Done Inline Actions
markj: | ||||||||||||||
LPC_SUBDEV_0); | ||||||||||||||
/* initialize config space */ | /* initialize config space */ | |||||||||||||
pci_set_cfgdata16(pi, PCIR_DEVICE, LPC_DEV); | pci_set_cfgdata16(pi, PCIR_VENDOR, vendor); | |||||||||||||
Done Inline Actions
or "prove" markj: or "prove" | ||||||||||||||
pci_set_cfgdata16(pi, PCIR_VENDOR, LPC_VENDOR); | pci_set_cfgdata16(pi, PCIR_DEVICE, device); | |||||||||||||
pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_BRIDGE); | pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_BRIDGE); | |||||||||||||
pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_BRIDGE_ISA); | pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_BRIDGE_ISA); | |||||||||||||
pci_set_cfgdata8(pi, PCIR_REVID, revid); | ||||||||||||||
pci_set_cfgdata16(pi, PCIR_SUBVEND_0, subvendor); | ||||||||||||||
pci_set_cfgdata16(pi, PCIR_SUBDEV_0, subdevice); | ||||||||||||||
lpc_bridge = pi; | lpc_bridge = pi; | |||||||||||||
return (0); | return (0); | |||||||||||||
Done Inline ActionsPlease limit capabilities on the descriptor, see the use of caph_rights_limit() and caph_ioctls_limit() in passthru_init(). Actually I do not see why the descriptor needs to be kept open indefinitely. markj: Please limit capabilities on the descriptor, see the use of caph_rights_limit() and… | ||||||||||||||
} | } | |||||||||||||
char * | char * | |||||||||||||
Done Inline ActionsPlease add a comment explaining why we look at the host device. markj: Please add a comment explaining why we look at the host device. | ||||||||||||||
Done Inline Actions/* * The VID, DID, REVID, SUBVID and SUBDID of lpc need to be * aligned with the physical ones. Without these physical * values, GPU passthrough of Intel integrated graphics devices * won't work properly. The Intel GOP driver checks these values * to proof that it runs on the correct platform. */ It's already there. corvink: ```
/*
* The VID, DID, REVID, SUBVID and SUBDID of lpc need to be
* aligned with the physical… | ||||||||||||||
lpc_pirq_name(int pin) | lpc_pirq_name(int pin) | |||||||||||||
{ | { | |||||||||||||
char *name; | char *name; | |||||||||||||
if (lpc_bridge == NULL) | if (lpc_bridge == NULL) | |||||||||||||
return (NULL); | return (NULL); | |||||||||||||
Done Inline ActionsThe way this is handled in pci_hostbridge.c might be a bit simpler than needing set_config_value_if_unset, etc. It does something like this: u_int vendor, device, revid, subvendor, subdevice; const char *value; vendor = LPC_DEV; device = 0; ... /* On Intel systems... */ if (....) { vendor = read_config(&sel, PCIR_VENDOR, 2); ... } /* Check for config variable overrides. */ value = get_config_value_node(nvl, "vendor"); if (value != NULL) vendor = strtol(value, NULL, 0); ... It might also be nice to use a single PCIOCGETCONF request to fetch the 'conf' structure for the LPC rather than a bunch of read_config ioctls. I'm also still a bit torn on if we need a separate 'lpc.use_host_ids' or the like that gated the if clause to override the values with host values. jhb: The way this is handled in pci_hostbridge.c might be a bit simpler than needing… | ||||||||||||||
asprintf(&name, "\\_SB.PC00.ISA.LNK%c,", 'A' + pin - 1); | asprintf(&name, "\\_SB.PC00.ISA.LNK%c,", 'A' + pin - 1); | |||||||||||||
return (name); | return (name); | |||||||||||||
} | } | |||||||||||||
void | void | |||||||||||||
Done Inline ActionsSorry, I was not clear. It's not really worthwhile to limit rights if the descriptor is not kept open. Since it's closed right after this, the caph_* calls can be dropped IMO. markj: Sorry, I was not clear. It's not really worthwhile to limit rights if the descriptor is not… | ||||||||||||||
lpc_pirq_routed(void) | lpc_pirq_routed(void) | |||||||||||||
{ | { | |||||||||||||
int pin; | int pin; | |||||||||||||
if (lpc_bridge == NULL) | if (lpc_bridge == NULL) | |||||||||||||
return; | return; | |||||||||||||
for (pin = 0; pin < 4; pin++) | for (pin = 0; pin < 4; pin++) | |||||||||||||
pci_set_cfgdata8(lpc_bridge, 0x60 + pin, pirq_read(pin + 1)); | pci_set_cfgdata8(lpc_bridge, 0x60 + pin, pirq_read(pin + 1)); | |||||||||||||
Done Inline ActionsIn general I'd like some way to not force this on always perhaps? Or at least provide a configuration knob to allow it to be turned off in case it breaks other systems that don't need GVT-d. Ideally I think what I would like is for the lpc device to honor config values to set these registers and have a hook here that sets the config variables if they aren't always set (so the user can always override them if needed). jhb: In general I'd like some way to not force this on always perhaps? Or at least provide a… | ||||||||||||||
Done Inline ActionsWhat do you think about the current solution? It allows the user to override all values if he wants. corvink: What do you think about the current solution? It allows the user to override all values if he… | ||||||||||||||
for (pin = 0; pin < 4; pin++) | for (pin = 0; pin < 4; pin++) | |||||||||||||
pci_set_cfgdata8(lpc_bridge, 0x68 + pin, pirq_read(pin + 5)); | pci_set_cfgdata8(lpc_bridge, 0x68 + pin, pirq_read(pin + 5)); | |||||||||||||
} | } | |||||||||||||
#ifdef BHYVE_SNAPSHOT | #ifdef BHYVE_SNAPSHOT | |||||||||||||
static int | static int | |||||||||||||
pci_lpc_snapshot(struct vm_snapshot_meta *meta) | pci_lpc_snapshot(struct vm_snapshot_meta *meta) | |||||||||||||
{ | { | |||||||||||||
Show All 28 Lines |
I'd prefer to pass the descriptor as a parameter rather than introducing a global variable.