bhyve: Support other schemes for naming pass-through devices.

Permit naming pass through devices using the syntax accepted by
pciconf (pci[<domain>:]<bus>:<slot>:<func>) as well as by device name
(e.g. "ppt0").

While here, fix an error in the manpage that had the bus and slot
arguments for the original /-delimited scheme swapped.

Reviewed by:	imp, markj
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D36147
This commit is contained in:
John Baldwin 2022-08-19 14:55:29 -07:00
parent 7647626d33
commit baf753cc19
3 changed files with 131 additions and 20 deletions

View File

@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd November 28, 2021
.Dd August 19, 2022
.Dt BHYVE 8
.Os
.Sh NAME
@ -390,6 +390,7 @@ Network device backends:
.Xc
.El
.Sm on
.Pp
If
.Cm mac
is not specified, the MAC address is derived from a fixed OUI and the
@ -520,14 +521,32 @@ to that file.
.El
.Pp
Pass-through device backends:
.Bl -tag -width 10n
.It Ns Ar slot Ns Cm \&/ Ns Ar bus Ns Cm \&/ Ns Ar function
Connect to a PCI device on the host at the selector described by
.Sm off
.Bl -bullet
.It
.Cm ppt Ar N Oo , Ar passthru-device-options Oc
.It
.Ns Ar bus Cm \&/ Ar slot Cm \&/ Ar function
.Op , Ar passthru-device-options
.It
.Cm pci Ar bus Cm : Ar slot Cm : Ns Ar function
.Op , Ar passthru-device-options
.El
.Sm on
.Pp
Connect to a PCI device on the host either named ppt
.Ns Ar N
or at the selector described by
.Ar slot ,
.Ar bus ,
and
.Ar function
numbers.
.Pp
The
.Ar passthru-device-options
are:
.Bl -tag -width 10n
.It Cm rom= Ns Ar romfile
Add
.Ar romfile

View File

@ -23,7 +23,7 @@
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.Dd June 30, 2022
.Dd August 19, 2022
.Dt BHYVE_CONFIG 5
.Os
.Sh NAME
@ -575,6 +575,12 @@ If set, allocate a memory disk as the backing store.
The value of this variable is the size of the memory disk in megabytes.
.El
.Ss PCI Passthrough Settings
The
.Xr ppt 4
device driver must be attached to the
PCI device being passed through.
The device to pass through can be identified either by name or its
host PCI bus location.
.Bl -column "Name" "integer" "Default"
.It Sy Name Ta Sy Format Ta Sy Default Ta Sy Description
.It Va bus Ta integer Ta Ta
@ -583,6 +589,10 @@ Host PCI bus address of device to pass through.
Host PCI slot address of device to pass through.
.It Va func Ta integer Ta Ta
Host PCI function address of device to pass through.
.It Va pptdev Ta string Ta Ta
Name of a
.Xr ppt 4
device to pass through.
.It Va rom Ta path Ta Ta
ROM file of the device which will be executed by OVMF to init the device.
.El

View File

@ -52,6 +52,7 @@ __FBSDID("$FreeBSD$");
#ifndef WITHOUT_CAPSICUM
#include <capsicum_helpers.h>
#endif
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -132,7 +133,7 @@ pcifd_init(void)
errx(EX_OSERR, "Unable to apply rights for sandbox");
const cap_ioctl_t pcifd_ioctls[] = { PCIOCREAD, PCIOCWRITE, PCIOCGETBAR,
PCIOCBARIO, PCIOCBARMMAP };
PCIOCBARIO, PCIOCBARMMAP, PCIOCGETCONF };
if (caph_ioctls_limit(pcifd, pcifd_ioctls, nitems(pcifd_ioctls)) == -1)
errx(EX_OSERR, "Unable to apply rights for sandbox");
#endif
@ -645,30 +646,39 @@ cfginit(struct vmctx *ctx, struct pci_devinst *pi, int bus, int slot, int func)
static int
passthru_legacy_config(nvlist_t *nvl, const char *opts)
{
const char *cp;
char *tofree;
char value[16];
int bus, slot, func;
if (opts == NULL)
return (0);
if (sscanf(opts, "%d/%d/%d", &bus, &slot, &func) != 3) {
cp = strchr(opts, ',');
if (strncmp(opts, "ppt", strlen("ppt")) == 0) {
tofree = strndup(opts, cp - opts);
set_config_value_node(nvl, "pptdev", tofree);
free(tofree);
} else if (sscanf(opts, "pci0:%d:%d:%d", &bus, &slot, &func) == 3 ||
sscanf(opts, "pci%d:%d:%d", &bus, &slot, &func) == 3 ||
sscanf(opts, "%d/%d/%d", &bus, &slot, &func) == 3) {
snprintf(value, sizeof(value), "%d", bus);
set_config_value_node(nvl, "bus", value);
snprintf(value, sizeof(value), "%d", slot);
set_config_value_node(nvl, "slot", value);
snprintf(value, sizeof(value), "%d", func);
set_config_value_node(nvl, "func", value);
} else {
EPRINTLN("passthru: invalid options \"%s\"", opts);
return (-1);
}
snprintf(value, sizeof(value), "%d", bus);
set_config_value_node(nvl, "bus", value);
snprintf(value, sizeof(value), "%d", slot);
set_config_value_node(nvl, "slot", value);
snprintf(value, sizeof(value), "%d", func);
set_config_value_node(nvl, "func", value);
opts = strchr(opts, ',');
if (opts == NULL) {
if (cp == NULL) {
return (0);
}
return pci_parse_legacy_config(nvl, opts + 1);
return (pci_parse_legacy_config(nvl, cp + 1));
}
static int
@ -722,6 +732,72 @@ passthru_init_rom(struct vmctx *const ctx, struct passthru_softc *const sc,
return (0);
}
static bool
passthru_lookup_pptdev(const char *name, int *bus, int *slot, int *func)
{
struct pci_conf_io pc;
struct pci_conf conf[1];
struct pci_match_conf patterns[1];
char *cp;
bzero(&pc, sizeof(struct pci_conf_io));
pc.match_buf_len = sizeof(conf);
pc.matches = conf;
bzero(&patterns, sizeof(patterns));
/*
* The pattern structure requires the unit to be split out from
* the driver name. Walk backwards from the end of the name to
* find the start of the unit.
*/
cp = strchr(name, '\0');
assert(cp != NULL);
while (cp != name && isdigit(cp[-1]))
cp--;
if (cp == name || !isdigit(*cp)) {
EPRINTLN("Invalid passthru device name %s", name);
return (false);
}
if ((size_t)(cp - name) + 1 > sizeof(patterns[0].pd_name)) {
EPRINTLN("Passthru device name %s is too long", name);
return (false);
}
memcpy(patterns[0].pd_name, name, cp - name);
patterns[0].pd_unit = strtol(cp, &cp, 10);
if (*cp != '\0') {
EPRINTLN("Invalid passthru device name %s", name);
return (false);
}
patterns[0].flags = PCI_GETCONF_MATCH_NAME | PCI_GETCONF_MATCH_UNIT;
pc.num_patterns = 1;
pc.pat_buf_len = sizeof(patterns);
pc.patterns = patterns;
if (ioctl(pcifd, PCIOCGETCONF, &pc) == -1) {
EPRINTLN("ioctl(PCIOCGETCONF): %s", strerror(errno));
return (false);
}
if (pc.status != PCI_GETCONF_LAST_DEVICE &&
pc.status != PCI_GETCONF_MORE_DEVS) {
EPRINTLN("error returned from PCIOCGETCONF ioctl");
return (false);
}
if (pc.num_matches == 0) {
EPRINTLN("Passthru device %s not found", name);
return (false);
}
if (conf[0].pc_sel.pc_domain != 0) {
EPRINTLN("Passthru device %s on unsupported domain", name);
return (false);
}
*bus = conf[0].pc_sel.pc_bus;
*slot = conf[0].pc_sel.pc_dev;
*func = conf[0].pc_sel.pc_func;
return (true);
}
static int
passthru_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl)
{
@ -751,9 +827,15 @@ passthru_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl)
var = atoi(value); \
} while (0)
GET_INT_CONFIG(bus, "bus");
GET_INT_CONFIG(slot, "slot");
GET_INT_CONFIG(func, "func");
value = get_config_value_node(nvl, "pptdev");
if (value != NULL) {
if (!passthru_lookup_pptdev(value, &bus, &slot, &func))
return (error);
} else {
GET_INT_CONFIG(bus, "bus");
GET_INT_CONFIG(slot, "slot");
GET_INT_CONFIG(func, "func");
}
if (vm_assign_pptdev(ctx, bus, slot, func) != 0) {
warnx("PCI device at %d/%d/%d is not using the ppt(4) driver",