pci: Add an ioctl to perform I/O to BARs

This is useful for bhyve, which otherwise has to use /dev/io to handle
accesses to I/O port BARs when PCI passthrough is in use.

Reviewed by:	imp, kib
Discussed with:	jhb
MFC after:	2 weeks
Sponsored by:	The FreeBSD Foundation
Differential Revision:	https://reviews.freebsd.org/D31307
This commit is contained in:
Mark Johnston 2021-08-14 10:41:43 -04:00
parent a3d932dfef
commit 7e14be0b07
3 changed files with 147 additions and 1 deletions

View File

@ -24,7 +24,7 @@
.\"
.\" $FreeBSD$
.\"
.Dd July 27, 2021
.Dd August 13, 2021
.Dt PCI 4
.Os
.Sh NAME
@ -430,6 +430,40 @@ even on reads.
of mapping.
Currently attempt to mmap an inactive BAR results in error.
.El
.It PCIOCBARIO
This
.Xr ioctl 2
command allows users to read from and write to BARs.
The I/O request parameters are passed in a
.Va struct pci_bar_ioreq
structure, which has the following fields:
.Bl -tag
.It Vt struct pcisel pbi_sel
Describes the device to operate on.
.It Vt int pbi_op
The operation to perform.
Currently supported values are
.Dv PCIBARIO_READ
and
.Dv PCIBARIO_WRITE .
.It Vt uint32_t pbi_bar
The index of the BAR on which to operate.
.It Vt uint32_t pbi_offset
The offset into the BAR at which to operate.
.It Vt uint32_t pbi_width
The size, in bytes, of the I/O operation.
1-byte, 2-byte, 4-byte and 8-byte perations are supported.
.It Vt uint32_t pbi_value
For reads, the value is returned in this field.
For writes, the caller specifies the value to be written in this field.
.Pp
Note that this operation maps and unmaps the corresponding resource and
so is relatively expensive for memory BARs.
The
.Va PCIOCBARMMAP
.Xr ioctl 2
can be used to create a persistent userspace mapping for such BARs instead.
.El
.El
.Sh LOADER TUNABLES
Tunables can be set at the

View File

@ -923,6 +923,92 @@ pci_bar_mmap(device_t pcidev, struct pci_bar_mmap *pbm)
return (error);
}
static int
pci_bar_io(device_t pcidev, struct pci_bar_ioreq *pbi)
{
struct pci_map *pm;
struct resource *res;
uint32_t offset, width;
int bar, error, type;
if (pbi->pbi_op != PCIBARIO_READ &&
pbi->pbi_op != PCIBARIO_WRITE)
return (EINVAL);
bar = PCIR_BAR(pbi->pbi_bar);
pm = pci_find_bar(pcidev, bar);
if (pm == NULL)
return (EINVAL);
offset = pbi->pbi_offset;
width = pbi->pbi_width;
if (offset + width < offset ||
((pci_addr_t)1 << pm->pm_size) < offset + width)
return (EINVAL);
type = PCI_BAR_MEM(pm->pm_value) ? SYS_RES_MEMORY : SYS_RES_IOPORT;
/*
* This will fail if a driver has allocated the resource. This could be
* worked around by detecting that case and using bus_map_resource() to
* populate the handle, but so far this is not needed.
*/
res = bus_alloc_resource_any(pcidev, type, &bar, RF_ACTIVE);
if (res == NULL)
return (ENOENT);
error = 0;
switch (pbi->pbi_op) {
case PCIBARIO_READ:
switch (pbi->pbi_width) {
case 1:
pbi->pbi_value = bus_read_1(res, offset);
break;
case 2:
pbi->pbi_value = bus_read_2(res, offset);
break;
case 4:
pbi->pbi_value = bus_read_4(res, offset);
break;
#ifndef __i386__
case 8:
pbi->pbi_value = bus_read_8(res, offset);
break;
#endif
default:
error = EINVAL;
break;
}
break;
case PCIBARIO_WRITE:
switch (pbi->pbi_width) {
case 1:
bus_write_1(res, offset, pbi->pbi_value);
break;
case 2:
bus_write_2(res, offset, pbi->pbi_value);
break;
case 4:
bus_write_4(res, offset, pbi->pbi_value);
break;
#ifndef __i386__
case 8:
bus_write_8(res, offset, pbi->pbi_value);
break;
#endif
default:
error = EINVAL;
break;
}
break;
}
bus_release_resource(pcidev, type, bar, res);
return (error);
}
static int
pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
{
@ -932,6 +1018,7 @@ pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *t
struct pci_conf_io *cio = NULL;
struct pci_devinfo *dinfo;
struct pci_io *io;
struct pci_bar_ioreq *pbi;
struct pci_bar_io *bio;
struct pci_list_vpd_io *lvio;
struct pci_match_conf *pattern_buf;
@ -1307,6 +1394,19 @@ pci_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *t
error = pcidev == NULL ? ENODEV : pci_bar_mmap(pcidev, pbm);
break;
case PCIOCBARIO:
pbi = (struct pci_bar_ioreq *)data;
pcidev = pci_find_dbsf(pbi->pbi_sel.pc_domain,
pbi->pbi_sel.pc_bus, pbi->pbi_sel.pc_dev,
pbi->pbi_sel.pc_func);
if (pcidev == NULL) {
error = ENODEV;
break;
}
error = pci_bar_io(pcidev, pbi);
break;
default:
error = ENOTTY;
break;

View File

@ -151,6 +151,17 @@ struct pci_bar_mmap {
int pbm_memattr;
};
struct pci_bar_ioreq {
struct pcisel pbi_sel; /* device to operate on */
#define PCIBARIO_READ 0x1
#define PCIBARIO_WRITE 0x2
int pbi_op;
uint32_t pbi_bar;
uint32_t pbi_offset;
uint32_t pbi_width;
uint32_t pbi_value;
};
#define PCIIO_BAR_MMAP_FIXED 0x01
#define PCIIO_BAR_MMAP_EXCL 0x02
#define PCIIO_BAR_MMAP_RW 0x04
@ -163,5 +174,6 @@ struct pci_bar_mmap {
#define PCIOCGETBAR _IOWR('p', 6, struct pci_bar_io)
#define PCIOCLISTVPD _IOWR('p', 7, struct pci_list_vpd_io)
#define PCIOCBARMMAP _IOWR('p', 8, struct pci_bar_mmap)
#define PCIOCBARIO _IOWR('p', 9, struct pci_bar_ioreq)
#endif /* !_SYS_PCIIO_H_ */