diff --git a/devicemodel/hw/pci/xhci.c b/devicemodel/hw/pci/xhci.c index be686dda7..4d6f3528f 100644 --- a/devicemodel/hw/pci/xhci.c +++ b/devicemodel/hw/pci/xhci.c @@ -322,6 +322,9 @@ static void pci_xhci_dev_destroy(struct pci_xhci_dev_emu *de); static int pci_xhci_port_chg(struct pci_xhci_vdev *xdev, int port, int conn); static void pci_xhci_set_evtrb(struct xhci_trb *evtrb, uint64_t port, uint32_t errcode, uint32_t evtype); +static int pci_xhci_xfer_complete(struct pci_xhci_vdev *xdev, + struct usb_data_xfer *xfer, uint32_t slot, uint32_t epid, + int *do_intr); static int pci_xhci_native_usb_dev_conn_cb(void *hci_data, void *dev_data) @@ -417,6 +420,56 @@ pci_xhci_native_usb_dev_disconn_cb(void *hci_data, void *dev_data) return 0; } +/* + * return value: + * = 0: succeed without interrupt + * > 0: succeed with interrupt + * < 0: failure + */ +static int +pci_xhci_usb_dev_notify_cb(void *hci_data, void *udev_data) +{ + int slot, epid, intr, rc; + struct usb_data_xfer *xfer; + struct pci_xhci_dev_emu *edev; + struct pci_xhci_vdev *xdev; + + xfer = udev_data; + if (!xfer) + return -1; + + epid = xfer->epid; + edev = xfer->dev; + if (!edev) + return -1; + + xdev = edev->xdev; + if (!xdev) + return -1; + + slot = edev->hci.hci_address; + rc = pci_xhci_xfer_complete(xdev, xfer, slot, epid, &intr); + + if (rc) + return -1; + else if (intr) + return 1; + else + return 0; +} + +static int +pci_xhci_usb_dev_intr_cb(void *hci_data, void *udev_data) +{ + struct pci_xhci_dev_emu *edev; + + edev = hci_data; + if (edev && edev->xdev) + pci_xhci_assert_interrupt(edev->xdev); + + return 0; +} + static struct pci_xhci_dev_emu* pci_xhci_dev_create(struct pci_xhci_vdev *xdev, void *dev_data) { @@ -432,15 +485,22 @@ pci_xhci_dev_create(struct pci_xhci_vdev *xdev, void *dev_data) if (!ue) return NULL; - /* TODO: following function pointers will be populated in future */ + /* + * TODO: at present, the following functions are + * enough. But for the purpose to be compatible with + * usb_mouse.c, the high level design including the + * function interface should be changed and refined + * in future. + */ ue->ue_init = usb_dev_init; ue->ue_request = usb_dev_request; - ue->ue_data = NULL; + ue->ue_data = usb_dev_data; ue->ue_info = usb_dev_info; ue->ue_reset = usb_dev_reset; ue->ue_remove = NULL; ue->ue_stop = NULL; ue->ue_deinit = usb_dev_deinit; + ue->ue_devtype = USB_DEV_PORT_MAPPER; ud = ue->ue_init(dev_data, NULL); if (!ud) @@ -489,10 +549,16 @@ pci_xhci_dev_destroy(struct pci_xhci_dev_emu *de) ue = de->dev_ue; ud = de->dev_instance; if (ue) { - assert(ue->ue_deinit); - ue->ue_deinit(ud); + if (ue->ue_devtype == USB_DEV_PORT_MAPPER) { + assert(ue->ue_deinit); + if (ue->ue_deinit) + ue->ue_deinit(ud); + } } - free(ue); + + if (ue->ue_devtype == USB_DEV_PORT_MAPPER) + free(ue); + free(de); } } @@ -879,9 +945,11 @@ pci_xhci_init_ep(struct pci_xhci_dev_emu *dev, int epid) if (devep->ep_xfer == NULL) { devep->ep_xfer = malloc(sizeof(struct usb_data_xfer)); - if (devep->ep_xfer) + if (devep->ep_xfer) { USB_DATA_XFER_INIT(devep->ep_xfer); - else + devep->ep_xfer->dev = (void *)dev; + devep->ep_xfer->epid = epid; + } else return -1; } return 0; @@ -1821,6 +1889,7 @@ pci_xhci_xfer_complete(struct pci_xhci_vdev *xdev, } xfer->ndata--; + xfer->head = (xfer->head + 1) % USB_MAX_XFER_BLOCKS; edtla += xfer->data[i].bdone; trb->dwTrb3 = (trb->dwTrb3 & ~0x1) | (xfer->data[i].ccs); @@ -1929,7 +1998,12 @@ pci_xhci_try_usb_xfer(struct pci_xhci_vdev *xdev, if (USB_DATA_GET_ERRCODE(&xfer->data[xfer->head]) == USB_NAK) err = XHCI_TRB_ERROR_SUCCESS; - } else { + } + /* + * Only for usb_mouse.c, emulation with port mapping will do it + * by the libusb callback function. + */ + else if (dev->dev_ue->ue_devtype == USB_DEV_STATIC) { err = pci_xhci_xfer_complete(xdev, xfer, slot, epid, &do_intr); if (err == XHCI_TRB_ERROR_SUCCESS && do_intr) @@ -2050,13 +2124,12 @@ retry: /* fall through */ case XHCI_TRB_TYPE_DATA_STAGE: - xfer_block = - usb_data_xfer_append(xfer, (void *)(trbflags & - XHCI_TRB_3_IDT_BIT ? - &trb->qwTrb0 : - XHCI_GADDR(xdev, - trb->qwTrb0)), - trb->dwTrb2 & 0x1FFFF, (void *)addr, ccs); + xfer_block = usb_data_xfer_append(xfer, + (void *)(trbflags & XHCI_TRB_3_IDT_BIT ? + &trb->qwTrb0 : + XHCI_GADDR(xdev, trb->qwTrb0)), + trb->dwTrb2 & 0x1FFFF, (void *)addr, + ccs); break; case XHCI_TRB_TYPE_STATUS_STAGE: @@ -2200,8 +2273,14 @@ pci_xhci_device_doorbell(struct pci_xhci_vdev *xdev, if (ep_ctx->qwEpCtx2 == 0) return; + /* + * In USB emulation with port mapping, the following transfer should + * NOT be called, or else the interrupt transfer will result + * of invalid and infinite loop. It is used by usb_mouse.c only. + */ /* handle pending transfers */ - if (devep->ep_xfer->ndata > 0) { + if (dev->dev_ue && dev->dev_ue->ue_devtype == USB_DEV_STATIC && + devep->ep_xfer->ndata > 0) { pci_xhci_try_usb_xfer(xdev, dev, devep, ep_ctx, slot, epid); return; } @@ -3090,10 +3169,10 @@ pci_xhci_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts) if (xdev->ndevices == 0) if (usb_dev_sys_init(pci_xhci_native_usb_dev_conn_cb, pci_xhci_native_usb_dev_disconn_cb, - NULL, NULL, xdev, - usb_get_log_level()) < 0) { + pci_xhci_usb_dev_notify_cb, + pci_xhci_usb_dev_intr_cb, + xdev, usb_get_log_level()) < 0) goto done; - } xdev->caplength = XHCI_SET_CAPLEN(XHCI_CAPLEN) | XHCI_SET_HCIVERSION(0x0100); diff --git a/devicemodel/hw/platform/usb_mouse.c b/devicemodel/hw/platform/usb_mouse.c index 1081b5c7d..bd79d8043 100644 --- a/devicemodel/hw/platform/usb_mouse.c +++ b/devicemodel/hw/platform/usb_mouse.c @@ -809,6 +809,7 @@ struct usb_devemu ue_mouse = { .ue_emu = "tablet", .ue_usbver = 3, .ue_usbspeed = USB_SPEED_HIGH, + .ue_devtype = USB_DEV_STATIC, .ue_init = umouse_init, .ue_request = umouse_request, .ue_data = umouse_data_handler, diff --git a/devicemodel/hw/platform/usb_pmapper.c b/devicemodel/hw/platform/usb_pmapper.c index 2628c9ecb..499840656 100644 --- a/devicemodel/hw/platform/usb_pmapper.c +++ b/devicemodel/hw/platform/usb_pmapper.c @@ -44,6 +44,167 @@ static struct usb_dev_sys_ctx_info g_ctx; +static void +usb_dev_comp_req(struct libusb_transfer *libusb_xfer) +{ + struct usb_dev_req *req; + struct usb_data_xfer *xfer; + struct usb_data_xfer_block *block; + int len, do_intr = 0, short_data = 0; + int i, idx, buf_idx, done; + + assert(libusb_xfer); + assert(libusb_xfer->user_data); + + req = libusb_xfer->user_data; + len = libusb_xfer->actual_length; + xfer = req->xfer; + + assert(xfer); + assert(xfer->dev); + UPRINTF(LDBG, "xfer_comp: ep %d with %d bytes. status %d,%d\n", + req->xfer->epid, len, libusb_xfer->status, + xfer->ndata); + + /* lock for protecting the transfer */ + USB_DATA_XFER_LOCK(xfer); + xfer->status = USB_ERR_NORMAL_COMPLETION; + + /* in case the xfer is reset by the USB_DATA_XFER_RESET */ + if (xfer->reset == 1 || + libusb_xfer->status != LIBUSB_TRANSFER_COMPLETED) { + UPRINTF(LDBG, "ep%d reset detected\r\n", xfer->epid); + xfer->reset = 0; + USB_DATA_XFER_UNLOCK(xfer); + goto reset_out; + } + + /* post process the usb transfer data */ + buf_idx = 0; + idx = req->blk_start; + for (i = 0; i < req->blk_count; i++) { + done = 0; + block = &xfer->data[idx % USB_MAX_XFER_BLOCKS]; + if (len > buf_idx) { + done = block->blen; + if (done > len - buf_idx) { + done = len - buf_idx; + short_data = 1; + } + if (req->in) + memcpy(block->buf, &req->buffer[buf_idx], done); + } + + assert(block->processed); + buf_idx += done; + block->bdone = done; + block->blen -= done; + idx = (idx + 1) % USB_MAX_XFER_BLOCKS; + } + +reset_out: + if (short_data) + xfer->status = USB_ERR_SHORT_XFER; + + /* notify the USB core this transfer is over */ + if (g_ctx.notify_cb) + do_intr = g_ctx.notify_cb(xfer->dev, xfer); + + /* if a interrupt is needed, send it to guest */ + if (do_intr && g_ctx.intr_cb) + g_ctx.intr_cb(xfer->dev, NULL); + + /* unlock and release memory */ + USB_DATA_XFER_UNLOCK(xfer); + libusb_free_transfer(libusb_xfer); + if (req && req->buffer) + free(req->buffer); + + free(req); +} + +static struct usb_dev_req * +usb_dev_alloc_req(struct usb_dev *udev, struct usb_data_xfer *xfer, int in, + size_t size) +{ + struct usb_dev_req *req; + static int seq = 1; + + if (!udev || !xfer) + return NULL; + + req = calloc(1, sizeof(*req)); + if (!req) + return NULL; + + req->udev = udev; + req->in = in; + req->xfer = xfer; + req->seq = seq++; + req->libusb_xfer = libusb_alloc_transfer(0); + if (!req->libusb_xfer) + goto errout; + + if (size) + req->buffer = malloc(size); + + if (!req->buffer) + goto errout; + + return req; + +errout: + if (req && req->buffer) + free(req->buffer); + if (req && req->libusb_xfer) + libusb_free_transfer(req->libusb_xfer); + if (req) + free(req); + return NULL; +} + +static int +usb_dev_prepare_xfer(struct usb_data_xfer *xfer, int *count, int *size) +{ + int found, i, idx, c, s, first; + struct usb_data_xfer_block *block = NULL; + + assert(xfer); + idx = xfer->head; + found = 0; + first = -1; + c = s = 0; + if (!count || !size) + return -1; + + for (i = 0; i < xfer->ndata; i++) { + block = &xfer->data[idx]; + + if (block->processed) { + idx = (idx + 1) % USB_MAX_XFER_BLOCKS; + continue; + } + if (block->buf && block->blen > 0) { + if (!found) { + found = 1; + first = idx; + } + c++; + s += block->blen; + + } else if (found) { + UPRINTF(LWRN, "find a NULL data. %d total %d\n", + i, xfer->ndata); + } + block->processed = 1; + idx = (idx + 1) % USB_MAX_XFER_BLOCKS; + } + + *count = c; + *size = s; + return first; +} + static inline int usb_dev_err_convert(int err) { @@ -346,6 +507,104 @@ usb_dev_reset(void *pdata) return 0; } +int +usb_dev_data(void *pdata, struct usb_data_xfer *xfer, int dir, int epctx) +{ + struct usb_dev *udev; + struct usb_dev_req *req; + int rc = 0, epid; + uint8_t type; + int blk_start, data_size, blk_count; + int retries = 3, i, buf_idx; + struct usb_data_xfer_block *b; + + udev = pdata; + assert(udev); + xfer->status = USB_ERR_NORMAL_COMPLETION; + + blk_start = usb_dev_prepare_xfer(xfer, &blk_count, &data_size); + if (blk_start < 0) + goto done; + + type = usb_dev_get_ep_type(udev, dir ? TOKEN_IN : TOKEN_OUT, epctx); + epid = dir ? (0x80 | epctx) : epctx; + + if (data_size <= 0) + goto done; + + UPRINTF(LDBG, "%s: DIR=%s|EP=%x|*%s*, data %d %d-%d\n", __func__, + dir ? "IN" : "OUT", epid, type == USB_ENDPOINT_BULK ? + "BULK" : "INT", data_size, + blk_start, blk_start + blk_count - 1); + + req = usb_dev_alloc_req(udev, xfer, dir, data_size); + if (!req) { + xfer->status = USB_ERR_IOERROR; + goto done; + } + + req->buf_length = data_size; + req->blk_start = blk_start; + req->blk_count = blk_count; + + if (!dir) { + for (i = 0, buf_idx = 0; i < blk_count; i++) { + b = &xfer->data[(blk_start + i) % USB_MAX_XFER_BLOCKS]; + if (b->buf) { + memcpy(&req->buffer[buf_idx], b->buf, b->blen); + buf_idx += b->blen; + } + } + } + + if (type == USB_ENDPOINT_BULK) { + /* + * give data to physical device through libusb. + * This is an asynchronous process, data is sent to libusb.so, + * and it may be not sent to physical device instantly, but + * just return here. After the data is really received by the + * physical device, the callback function usb_dev_comp_req + * will be triggered. + */ + /* + * TODO: Is there any risk of data missing? + */ + libusb_fill_bulk_transfer(req->libusb_xfer, + udev->handle, epid, + req->buffer, + data_size, + usb_dev_comp_req, + req, + 0); + do { + rc = libusb_submit_transfer(req->libusb_xfer); + } while (rc && retries--); + + } else if (type == USB_ENDPOINT_INT) { + /* give data to physical device through libusb */ + libusb_fill_interrupt_transfer(req->libusb_xfer, + udev->handle, + epid, + req->buffer, + data_size, + usb_dev_comp_req, + req, + 0); + rc = libusb_submit_transfer(req->libusb_xfer); + } else { + /* TODO isoch transfer is not implemented */ + UPRINTF(LWRN, "ISOCH transfer still not supported.\n"); + xfer->status = USB_ERR_INVAL; + } + + if (rc) { + xfer->status = USB_ERR_IOERROR; + UPRINTF(LDBG, "libusb_submit_transfer fail: %d\n", rc); + } +done: + return xfer->status; +} + int usb_dev_request(void *pdata, struct usb_data_xfer *xfer) { @@ -648,6 +907,8 @@ usb_dev_sys_init(usb_dev_sys_cb conn_cb, usb_dev_sys_cb disconn_cb, g_ctx.hci_data = hci_data; g_ctx.conn_cb = conn_cb; g_ctx.disconn_cb = disconn_cb; + g_ctx.notify_cb = notify_cb; + g_ctx.intr_cb = intr_cb; native_conn_evt = LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED; native_disconn_evt = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT; native_pid = LIBUSB_HOTPLUG_MATCH_ANY; diff --git a/devicemodel/include/usb_core.h b/devicemodel/include/usb_core.h index d4bf44460..ed5c65519 100644 --- a/devicemodel/include/usb_core.h +++ b/devicemodel/include/usb_core.h @@ -34,7 +34,8 @@ #include #include "types.h" -#define USB_MAX_XFER_BLOCKS 8 +#define USB_MAX_XFER_BLOCKS 256 + #define USB_XFER_OUT 0 #define USB_XFER_IN 1 @@ -64,6 +65,11 @@ enum token_type { TOKEN_SETUP }; +enum usb_dev_type { + USB_DEV_STATIC = 0, + USB_DEV_PORT_MAPPER +}; + struct usb_hci; struct usb_device_request; struct usb_data_xfer; @@ -73,6 +79,7 @@ struct usb_devemu { char *ue_emu; /* name of device emulation */ int ue_usbver; /* usb version: 2 or 3 */ int ue_usbspeed; /* usb device speed */ + int ue_devtype; /* instance creation */ void *(*ue_init)(void *pdata, char *opt); @@ -134,6 +141,10 @@ struct usb_data_xfer { int ndata; /* # of data items */ int head; int tail; + void *dev; /* struct pci_xhci_dev_emu *dev */ + int epid; /* related endpoint id */ + int pid; /* token id */ + int reset; /* detect ep reset */ int status; pthread_mutex_t mtx; }; @@ -153,15 +164,21 @@ enum USB_ERRCODE { #define USB_DATA_OK(x, i) ((x)->data[(i)].buf != NULL) -#define USB_DATA_XFER_INIT(x) do { \ - memset((x), 0, sizeof(*(x))); \ - pthread_mutex_init(&((x)->mtx), NULL); \ - } while (0) +#define USB_DATA_XFER_INIT(x) do { \ + pthread_mutexattr_t attr; \ + pthread_mutexattr_init(&attr); \ + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); \ + memset((x), 0, sizeof(*(x))); \ + pthread_mutex_init(&((x)->mtx), &attr); \ + } while (0) #define USB_DATA_XFER_RESET(x) do { \ + pthread_mutex_lock(&((x)->mtx)); \ memset((x)->data, 0, sizeof((x)->data)); \ (x)->ndata = 0; \ (x)->head = (x)->tail = 0; \ + (x)->reset = 1; \ + pthread_mutex_unlock((&(x)->mtx)); \ } while (0) #define USB_DATA_XFER_LOCK(x) \ diff --git a/devicemodel/include/usb_pmapper.h b/devicemodel/include/usb_pmapper.h index 211252377..4df8035c3 100644 --- a/devicemodel/include/usb_pmapper.h +++ b/devicemodel/include/usb_pmapper.h @@ -83,6 +83,30 @@ struct usb_dev { libusb_device_handle *handle; }; +/* + * The purpose to implement struct usb_dev_req is to adapt + * struct usb_data_xfer to make a proper data format to talk + * with libusb. + */ +struct usb_dev_req { + struct usb_dev *udev; + int in; + int seq; + /* + * buffer could include data from multiple + * usb_data_xfer_block, so here need some + * data to record it. + */ + uint8_t *buffer; + int buf_length; + int blk_start; + int blk_count; + + struct usb_data_xfer *xfer; + struct libusb_transfer *libusb_xfer; + struct usb_data_xfer_block *setup_blk; +}; + /* callback type used by code from HCD layer */ typedef int (*usb_dev_sys_cb)(void *hci_data, void *dev_data); @@ -100,6 +124,8 @@ struct usb_dev_sys_ctx_info { */ usb_dev_sys_cb conn_cb; usb_dev_sys_cb disconn_cb; + usb_dev_sys_cb notify_cb; + usb_dev_sys_cb intr_cb; /* * private data from HCD layer @@ -116,4 +142,5 @@ void usb_dev_deinit(void *pdata); int usb_dev_info(void *pdata, int type, void *value, int size); int usb_dev_request(void *pdata, struct usb_data_xfer *xfer); int usb_dev_reset(void *pdata); +int usb_dev_data(void *pdata, struct usb_data_xfer *xfer, int dir, int epctx); #endif