Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/bhyve/pci_xhci.c
Show First 20 Lines • Show All 42 Lines • ▼ Show 20 Lines | |||||
#include <stdio.h> | #include <stdio.h> | ||||
#include <stdlib.h> | #include <stdlib.h> | ||||
#include <stdint.h> | #include <stdint.h> | ||||
#include <string.h> | #include <string.h> | ||||
#include <errno.h> | #include <errno.h> | ||||
#include <pthread.h> | #include <pthread.h> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
#include <machine/vmm_snapshot.h> | |||||
#include <dev/usb/usbdi.h> | #include <dev/usb/usbdi.h> | ||||
#include <dev/usb/usb.h> | #include <dev/usb/usb.h> | ||||
#include <dev/usb/usb_freebsd.h> | #include <dev/usb/usb_freebsd.h> | ||||
#include <xhcireg.h> | #include <xhcireg.h> | ||||
#include "bhyverun.h" | #include "bhyverun.h" | ||||
#include "debug.h" | #include "debug.h" | ||||
#include "pci_emul.h" | #include "pci_emul.h" | ||||
▲ Show 20 Lines • Show All 87 Lines • ▼ Show 20 Lines | |||||
#define MASK_64_HI(x) ((x) & ~0xFFFFFFFFULL) | #define MASK_64_HI(x) ((x) & ~0xFFFFFFFFULL) | ||||
#define MASK_64_LO(x) ((x) & 0xFFFFFFFFULL) | #define MASK_64_LO(x) ((x) & 0xFFFFFFFFULL) | ||||
#define FIELD_REPLACE(a,b,m,s) (((a) & ~((m) << (s))) | \ | #define FIELD_REPLACE(a,b,m,s) (((a) & ~((m) << (s))) | \ | ||||
(((b) & (m)) << (s))) | (((b) & (m)) << (s))) | ||||
#define FIELD_COPY(a,b,m,s) (((a) & ~((m) << (s))) | \ | #define FIELD_COPY(a,b,m,s) (((a) & ~((m) << (s))) | \ | ||||
(((b) & ((m) << (s))))) | (((b) & ((m) << (s))))) | ||||
#define SNAP_DEV_NAME_LEN 128 | |||||
struct pci_xhci_trb_ring { | struct pci_xhci_trb_ring { | ||||
uint64_t ringaddr; /* current dequeue guest address */ | uint64_t ringaddr; /* current dequeue guest address */ | ||||
uint32_t ccs; /* consumer cycle state */ | uint32_t ccs; /* consumer cycle state */ | ||||
}; | }; | ||||
/* device endpoint transfer/stream rings */ | /* device endpoint transfer/stream rings */ | ||||
struct pci_xhci_dev_ep { | struct pci_xhci_dev_ep { | ||||
union { | union { | ||||
▲ Show 20 Lines • Show All 119 Lines • ▼ Show 20 Lines | |||||
/* portregs and devices arrays are set up to start from idx=1 */ | /* portregs and devices arrays are set up to start from idx=1 */ | ||||
#define XHCI_PORTREG_PTR(x,n) &(x)->portregs[(n)] | #define XHCI_PORTREG_PTR(x,n) &(x)->portregs[(n)] | ||||
#define XHCI_DEVINST_PTR(x,n) (x)->devices[(n)] | #define XHCI_DEVINST_PTR(x,n) (x)->devices[(n)] | ||||
#define XHCI_SLOTDEV_PTR(x,n) (x)->slots[(n)] | #define XHCI_SLOTDEV_PTR(x,n) (x)->slots[(n)] | ||||
#define XHCI_HALTED(sc) ((sc)->opregs.usbsts & XHCI_STS_HCH) | #define XHCI_HALTED(sc) ((sc)->opregs.usbsts & XHCI_STS_HCH) | ||||
#define XHCI_GADDR_SIZE(a) (XHCI_PADDR_SZ - \ | |||||
(((uint64_t) (a)) & (XHCI_PADDR_SZ - 1))) | |||||
#define XHCI_GADDR(sc,a) paddr_guest2host((sc)->xsc_pi->pi_vmctx, \ | #define XHCI_GADDR(sc,a) paddr_guest2host((sc)->xsc_pi->pi_vmctx, \ | ||||
(a), \ | (a), XHCI_GADDR_SIZE(a)) | ||||
XHCI_PADDR_SZ - ((a) & (XHCI_PADDR_SZ-1))) | |||||
static int xhci_in_use; | static int xhci_in_use; | ||||
/* map USB errors to XHCI */ | /* map USB errors to XHCI */ | ||||
static const int xhci_usb_errors[USB_ERR_MAX] = { | static const int xhci_usb_errors[USB_ERR_MAX] = { | ||||
[USB_ERR_NORMAL_COMPLETION] = XHCI_TRB_ERROR_SUCCESS, | [USB_ERR_NORMAL_COMPLETION] = XHCI_TRB_ERROR_SUCCESS, | ||||
[USB_ERR_PENDING_REQUESTS] = XHCI_TRB_ERROR_RESOURCE, | [USB_ERR_PENDING_REQUESTS] = XHCI_TRB_ERROR_RESOURCE, | ||||
[USB_ERR_NOT_STARTED] = XHCI_TRB_ERROR_ENDP_NOT_ON, | [USB_ERR_NOT_STARTED] = XHCI_TRB_ERROR_ENDP_NOT_ON, | ||||
▲ Show 20 Lines • Show All 2,550 Lines • ▼ Show 20 Lines | |||||
done: | done: | ||||
if (error) { | if (error) { | ||||
free(sc); | free(sc); | ||||
} | } | ||||
return (error); | return (error); | ||||
} | } | ||||
#ifdef BHYVE_SNAPSHOT | |||||
static void | |||||
pci_xhci_map_devs_slots(struct pci_xhci_softc *sc, int maps[]) | |||||
{ | |||||
int i, j; | |||||
struct pci_xhci_dev_emu *dev, *slot; | |||||
memset(maps, 0, sizeof(maps[0]) * XHCI_MAX_SLOTS); | |||||
for (i = 1; i <= XHCI_MAX_SLOTS; i++) { | |||||
for (j = 1; j <= XHCI_MAX_DEVS; j++) { | |||||
slot = XHCI_SLOTDEV_PTR(sc, i); | |||||
dev = XHCI_DEVINST_PTR(sc, j); | |||||
if (slot == dev) | |||||
maps[i] = j; | |||||
} | |||||
} | |||||
} | |||||
static int | |||||
pci_xhci_snapshot_ep(struct pci_xhci_softc *sc, struct pci_xhci_dev_emu *dev, | |||||
int idx, struct vm_snapshot_meta *meta) | |||||
{ | |||||
int k; | |||||
int ret; | |||||
struct usb_data_xfer *xfer; | |||||
struct usb_data_xfer_block *xfer_block; | |||||
/* some sanity checks */ | |||||
if (meta->op == VM_SNAPSHOT_SAVE) | |||||
xfer = dev->eps[idx].ep_xfer; | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer, meta, ret, done); | |||||
if (xfer == NULL) { | |||||
ret = 0; | |||||
goto done; | |||||
} | |||||
if (meta->op == VM_SNAPSHOT_RESTORE) { | |||||
pci_xhci_init_ep(dev, idx); | |||||
xfer = dev->eps[idx].ep_xfer; | |||||
} | |||||
/* save / restore proper */ | |||||
for (k = 0; k < USB_MAX_XFER_BLOCKS; k++) { | |||||
xfer_block = &xfer->data[k]; | |||||
SNAPSHOT_GUEST2HOST_ADDR_OR_LEAVE(xfer_block->buf, | |||||
XHCI_GADDR_SIZE(xfer_block->buf), true, meta, ret, | |||||
done); | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer_block->blen, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer_block->bdone, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer_block->processed, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer_block->hci_data, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer_block->ccs, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer_block->streamid, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer_block->trbnext, meta, ret, done); | |||||
} | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer->ureq, meta, ret, done); | |||||
if (xfer->ureq) { | |||||
/* xfer->ureq is not allocated at restore time */ | |||||
if (meta->op == VM_SNAPSHOT_RESTORE) | |||||
xfer->ureq = malloc(sizeof(struct usb_device_request)); | |||||
SNAPSHOT_BUF_OR_LEAVE(xfer->ureq, | |||||
sizeof(struct usb_device_request), | |||||
meta, ret, done); | |||||
} | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer->ndata, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer->head, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(xfer->tail, meta, ret, done); | |||||
done: | |||||
return (ret); | |||||
} | |||||
static int | |||||
pci_xhci_snapshot(struct vm_snapshot_meta *meta) | |||||
{ | |||||
int i, j; | |||||
int ret; | |||||
int restore_idx; | |||||
struct pci_devinst *pi; | |||||
struct pci_xhci_softc *sc; | |||||
struct pci_xhci_portregs *port; | |||||
struct pci_xhci_dev_emu *dev; | |||||
char dname[SNAP_DEV_NAME_LEN]; | |||||
int maps[XHCI_MAX_SLOTS + 1]; | |||||
pi = meta->dev_data; | |||||
sc = pi->pi_arg; | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->caplength, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->hcsparams1, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->hcsparams2, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->hcsparams3, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->hccparams1, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->dboff, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsoff, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->hccparams2, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->regsend, meta, ret, done); | |||||
/* opregs */ | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->opregs.usbcmd, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->opregs.usbsts, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->opregs.pgsz, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->opregs.dnctrl, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->opregs.crcr, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->opregs.dcbaap, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->opregs.config, meta, ret, done); | |||||
/* opregs.cr_p */ | |||||
SNAPSHOT_GUEST2HOST_ADDR_OR_LEAVE(sc->opregs.cr_p, | |||||
XHCI_GADDR_SIZE(sc->opregs.cr_p), false, meta, ret, done); | |||||
/* opregs.dcbaa_p */ | |||||
SNAPSHOT_GUEST2HOST_ADDR_OR_LEAVE(sc->opregs.dcbaa_p, | |||||
XHCI_GADDR_SIZE(sc->opregs.dcbaa_p), false, meta, ret, done); | |||||
/* rtsregs */ | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.mfindex, meta, ret, done); | |||||
/* rtsregs.intrreg */ | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.intrreg.iman, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.intrreg.imod, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.intrreg.erstsz, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.intrreg.rsvd, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.intrreg.erstba, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.intrreg.erdp, meta, ret, done); | |||||
/* rtsregs.erstba_p */ | |||||
SNAPSHOT_GUEST2HOST_ADDR_OR_LEAVE(sc->rtsregs.erstba_p, | |||||
XHCI_GADDR_SIZE(sc->rtsregs.erstba_p), false, meta, ret, done); | |||||
/* rtsregs.erst_p */ | |||||
SNAPSHOT_GUEST2HOST_ADDR_OR_LEAVE(sc->rtsregs.erst_p, | |||||
XHCI_GADDR_SIZE(sc->rtsregs.erst_p), false, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.er_deq_seg, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.er_enq_idx, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.er_enq_seg, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.er_events_cnt, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->rtsregs.event_pcs, meta, ret, done); | |||||
/* sanity checking */ | |||||
for (i = 1; i <= XHCI_MAX_DEVS; i++) { | |||||
dev = XHCI_DEVINST_PTR(sc, i); | |||||
if (dev == NULL) | |||||
continue; | |||||
if (meta->op == VM_SNAPSHOT_SAVE) | |||||
restore_idx = i; | |||||
SNAPSHOT_VAR_OR_LEAVE(restore_idx, meta, ret, done); | |||||
/* check if the restored device (when restoring) is sane */ | |||||
if (restore_idx != i) { | |||||
fprintf(stderr, "%s: idx not matching: actual: %d, " | |||||
"expected: %d\r\n", __func__, restore_idx, i); | |||||
ret = EINVAL; | |||||
goto done; | |||||
} | |||||
if (meta->op == VM_SNAPSHOT_SAVE) { | |||||
memset(dname, 0, sizeof(dname)); | |||||
strncpy(dname, dev->dev_ue->ue_emu, sizeof(dname) - 1); | |||||
} | |||||
SNAPSHOT_BUF_OR_LEAVE(dname, sizeof(dname), meta, ret, done); | |||||
if (meta->op == VM_SNAPSHOT_RESTORE) { | |||||
dname[sizeof(dname) - 1] = '\0'; | |||||
if (strcmp(dev->dev_ue->ue_emu, dname)) { | |||||
fprintf(stderr, "%s: device names mismatch: " | |||||
"actual: %s, expected: %s\r\n", | |||||
__func__, dname, dev->dev_ue->ue_emu); | |||||
ret = EINVAL; | |||||
goto done; | |||||
} | |||||
} | |||||
} | |||||
/* portregs */ | |||||
for (i = 1; i <= XHCI_MAX_DEVS; i++) { | |||||
port = XHCI_PORTREG_PTR(sc, i); | |||||
dev = XHCI_DEVINST_PTR(sc, i); | |||||
if (dev == NULL) | |||||
continue; | |||||
SNAPSHOT_VAR_OR_LEAVE(port->portsc, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(port->portpmsc, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(port->portli, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(port->porthlpmc, meta, ret, done); | |||||
} | |||||
/* slots */ | |||||
if (meta->op == VM_SNAPSHOT_SAVE) | |||||
pci_xhci_map_devs_slots(sc, maps); | |||||
for (i = 1; i <= XHCI_MAX_SLOTS; i++) { | |||||
SNAPSHOT_VAR_OR_LEAVE(maps[i], meta, ret, done); | |||||
if (meta->op == VM_SNAPSHOT_SAVE) { | |||||
dev = XHCI_SLOTDEV_PTR(sc, i); | |||||
} else if (meta->op == VM_SNAPSHOT_RESTORE) { | |||||
if (maps[i] != 0) | |||||
dev = XHCI_DEVINST_PTR(sc, maps[i]); | |||||
else | |||||
dev = NULL; | |||||
XHCI_SLOTDEV_PTR(sc, i) = dev; | |||||
} else { | |||||
/* error */ | |||||
ret = EINVAL; | |||||
goto done; | |||||
} | |||||
if (dev == NULL) | |||||
continue; | |||||
SNAPSHOT_GUEST2HOST_ADDR_OR_LEAVE(dev->dev_ctx, | |||||
XHCI_GADDR_SIZE(dev->dev_ctx), false, meta, ret, done); | |||||
for (j = 1; j < XHCI_MAX_ENDPOINTS; j++) { | |||||
ret = pci_xhci_snapshot_ep(sc, dev, j, meta); | |||||
if (ret != 0) | |||||
goto done; | |||||
} | |||||
SNAPSHOT_VAR_OR_LEAVE(dev->dev_slotstate, meta, ret, done); | |||||
/* devices[i]->dev_sc */ | |||||
dev->dev_ue->ue_snapshot(dev->dev_sc, meta); | |||||
/* devices[i]->hci */ | |||||
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_address, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(dev->hci.hci_port, meta, ret, done); | |||||
} | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->ndevices, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->usb2_port_start, meta, ret, done); | |||||
SNAPSHOT_VAR_OR_LEAVE(sc->usb3_port_start, meta, ret, done); | |||||
done: | |||||
return (ret); | |||||
} | |||||
#endif | |||||
struct pci_devemu pci_de_xhci = { | struct pci_devemu pci_de_xhci = { | ||||
.pe_emu = "xhci", | .pe_emu = "xhci", | ||||
.pe_init = pci_xhci_init, | .pe_init = pci_xhci_init, | ||||
.pe_barwrite = pci_xhci_write, | .pe_barwrite = pci_xhci_write, | ||||
.pe_barread = pci_xhci_read | .pe_barread = pci_xhci_read, | ||||
#ifdef BHYVE_SNAPSHOT | |||||
.pe_snapshot = pci_xhci_snapshot, | |||||
#endif | |||||
}; | }; | ||||
PCI_EMUL_SET(pci_de_xhci); | PCI_EMUL_SET(pci_de_xhci); |