acrn-hypervisor/devicemodel/hw/platform/usb_pmapper.c
Ziheng Li eb8bcb06b3 Update copyright year range in code headers
Modified the copyright year range in code, and corrected "int32_tel"
into "Intel" in two "hypervisor/include/debug/profiling.h" and
"hypervisor/include/debug/profiling_internal.h".

Tracked-On: #7559
Signed-off-by: Ziheng Li <ziheng.li@intel.com>
2022-07-15 11:48:35 +08:00

1384 lines
32 KiB
C

/*
* Copyright (C) 2018-2022 Intel Corporation.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "usb.h"
#include "usbdi.h"
#include "usb_pmapper.h"
#undef LOG_TAG
#define LOG_TAG "USBPM: "
static struct usb_dev_sys_ctx_info g_ctx;
static uint16_t usb_dev_get_ep_maxp(struct usb_dev *udev, int pid, int epnum);
static bool
usb_get_native_devinfo(struct libusb_device *ldev,
struct usb_native_devinfo *info,
struct libusb_device_descriptor *desc)
{
struct libusb_device_descriptor d;
int rc;
if (!ldev || !info)
return false;
memset(info, 0, sizeof(*info));
info->speed = libusb_get_device_speed(ldev);
info->priv_data = ldev;
info->path.bus = libusb_get_bus_number(ldev);
info->path.depth = libusb_get_port_numbers(ldev, info->path.path,
USB_MAX_TIERS);
rc = libusb_get_device_descriptor(ldev, &d);
if (rc) {
UPRINTF(LWRN, "fail to get descriptor for %d-%s\r\n",
info->path.bus, usb_dev_path(&info->path));
return false;
}
/* set device type */
if (ROOTHUB_PORT(info->path) == 0)
info->type = USB_TYPE_ROOTHUB;
else if (d.bDeviceClass == LIBUSB_CLASS_HUB)
info->type = USB_TYPE_EXTHUB;
else if (info->path.path[1] == 0)
info->type = USB_TYPE_ROOTHUB_SUBDEV;
else
info->type = USB_TYPE_EXTHUB_SUBDEV;
if (info->type == USB_TYPE_EXTHUB) {
info->maxchild = usb_get_hub_port_num(&info->path);
if (info->maxchild < 0)
UPRINTF(LDBG, "fail to get count of numbers of hub"
" %d-%s\r\n", info->path.bus,
usb_dev_path(&info->path));
}
info->pid = d.idProduct;
info->vid = d.idVendor;
info->bcd = d.bcdUSB;
if (desc != NULL)
*desc = d;
return true;
}
static void
internal_scan(struct libusb_device ***list, int list_sz, int depth,
int8_t *visit, int visit_sz)
{
int i;
struct libusb_device **devlist;
struct usb_native_devinfo di;
devlist = *list;
if (depth >= USB_MAX_TIERS) {
UPRINTF(LFTL, "max hub layers(7) reached, stop scan\r\n");
return;
}
/* The scanning must be done according to the order from depth 1 to
* USB_MAX_TIERS. The reason is if hub exist in the USB device tree,
* the ports of hub should be assigned first, and then its child
* is scanned. The reason is external hub ports are dyanmically
* assigned.
*/
/* scan devices and assign ports if hub is found */
for (i = 0; i < list_sz; i++) {
if (usb_get_native_devinfo(devlist[i], &di, NULL) == false)
continue;
if (!visit[i] && di.path.depth == depth &&
ROOTHUB_PORT(di.path)) {
visit[i] = 1;
if (g_ctx.conn_cb)
g_ctx.conn_cb(g_ctx.hci_data, &di);
}
}
/* do the scanning in deeper depth */
for (i = 0; i < list_sz; i++) {
if (usb_get_native_devinfo(devlist[i], &di, NULL) == false)
continue;
if (!visit[i] && di.path.depth > depth && ROOTHUB_PORT(di.path))
internal_scan(list, list_sz, depth + 1, visit,
visit_sz);
}
}
static int
usb_dev_scan_dev(struct libusb_device ***devlist)
{
int num_devs;
int8_t visit[USB_MAX_DEVICES];
if (!g_ctx.libusb_ctx)
return -1;
num_devs = libusb_get_device_list(g_ctx.libusb_ctx, devlist);
if (num_devs < 0) {
*devlist = NULL;
return -1;
}
memset(visit, 0, sizeof(visit));
internal_scan(devlist, num_devs, 1, visit, USB_MAX_DEVICES);
return num_devs;
}
static int
libusb_speed_to_usb_speed(int libusb_speed)
{
int speed = LIBUSB_SPEED_UNKNOWN;
switch (libusb_speed) {
case LIBUSB_SPEED_LOW:
speed = USB_SPEED_LOW;
break;
case LIBUSB_SPEED_FULL:
speed = USB_SPEED_FULL;
break;
case LIBUSB_SPEED_HIGH:
speed = USB_SPEED_HIGH;
break;
case LIBUSB_SPEED_SUPER:
speed = USB_SPEED_SUPER;
break;
default:
UPRINTF(LWRN, "%s unexpect speed %d\r\n", __func__,
libusb_speed);
}
return speed;
}
static void
usb_dev_comp_cb(struct libusb_transfer *trn)
{
struct usb_dev_req *r;
struct usb_xfer *xfer;
struct usb_block *block;
struct usb_native_devinfo *info;
int do_intr = 0;
int i, idx, buf_idx, done;
int is_stalled = 0;
int framelen = 0;
uint16_t maxp;
uint8_t *buf;
/* async request */
r = trn->user_data;
if (!r) {
UPRINTF(LFTL, "error: user context data not found on USB transfer\r\n");
goto free_transfer;
}
info = &r->udev->info;
/* async transfer */
xfer = r->xfer;
maxp = usb_dev_get_ep_maxp(r->udev, r->in, xfer->epid / 2);
if (trn->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
/* got the isoc frame length */
framelen = USB_EP_MAXP_SZ(maxp) * (1 + USB_EP_MAXP_MT(maxp));
UPRINTF(LDBG, "iso maxp %u framelen %d\r\n", maxp, framelen);
}
UPRINTF(LDBG, "%s: %d-%s: actlen %d ep%d-xfr [%d-%d %d] rq-%d "
"[%d-%d %d] st %d\r\n", __func__, info->path.bus,
usb_dev_path(&info->path), trn->actual_length,
xfer->epid, xfer->head, xfer->tail, xfer->ndata,
r->seq, r->blk_head, r->blk_tail, r->buf_size,
trn->status);
/* lock for protecting the transfer */
xfer->status = USB_ERR_NORMAL_COMPLETION;
switch (trn->status) {
case LIBUSB_TRANSFER_STALL:
xfer->status = USB_ERR_STALLED;
is_stalled = 1;
goto stall_out;
case LIBUSB_TRANSFER_NO_DEVICE:
/* avoid short packet warnings when devices are plugged out. */
xfer->status = USB_ERR_SHORT_XFER;
goto out;
case LIBUSB_TRANSFER_ERROR:
/*
* If this error happened due to device disconnecting, there is
* nothing should do and just wait usb_dev_native_sys_disconn_cb
* to do the 'unplugging process'.
*/
if (usb_native_is_device_existed(&info->path) == 0)
goto cancel_out;
is_stalled = 1;
xfer->status = USB_ERR_STALLED;
goto stall_out;
case LIBUSB_TRANSFER_CANCELLED:
xfer->status = USB_ERR_IOERROR;
goto cancel_out;
case LIBUSB_TRANSFER_TIMED_OUT:
xfer->status = USB_ERR_TIMEOUT;
goto out;
case LIBUSB_TRANSFER_OVERFLOW:
xfer->status = USB_ERR_BAD_BUFSIZE;
goto out;
case LIBUSB_TRANSFER_COMPLETED:
break;
default:
UPRINTF(LWRN, "unknown failure: %x\r\n", trn->status);
break;
}
g_ctx.lock_ep_cb(xfer->dev, &xfer->epid);
for (i = 0; i < trn->num_iso_packets; i++)
UPRINTF(LDBG, "iso_frame %d len %u act_len %u\n", i,
trn->iso_packet_desc[i].length,
trn->iso_packet_desc[i].actual_length);
/* handle the blocks belong to this request */
i = 0;
buf = r->buffer;
idx = r->blk_head;
buf_idx = 0;
done = trn->actual_length;
while (index_valid(r->blk_head, r->blk_tail, xfer->max_blk_cnt, idx)) {
if (trn->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
buf_idx = 0;
buf = libusb_get_iso_packet_buffer_simple(trn, i);
done = trn->iso_packet_desc[i].actual_length;
i++;
}
do {
int d;
block = &xfer->data[idx];
if (block->stat == USB_BLOCK_FREE &&
block->type != USB_DATA_NONE)
UPRINTF(LFTL, "error: found free block\r\n");
d = done;
if (d > block->blen)
d = block->blen;
if (block->type == USB_DATA_PART ||
block->type == USB_DATA_FULL) {
if (r->in == TOKEN_IN) {
memcpy(block->buf, buf + buf_idx, d);
buf_idx += d;
}
} else {
/* Link TRB */
i--;
}
done -= d;
block->blen -= d;
block->bdone = d;
block->stat = USB_BLOCK_HANDLED;
idx = index_inc(idx, xfer->max_blk_cnt);
} while (block->type == USB_DATA_PART);
}
stall_out:
if (is_stalled) {
idx = r->blk_head;
while (index_valid(r->blk_head, r->blk_tail,
xfer->max_blk_cnt, idx)) {
block = &xfer->data[idx];
block->stat = USB_BLOCK_HANDLED;
idx = index_inc(idx, xfer->max_blk_cnt);
}
}
out:
/* 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);
cancel_out:
/* unlock and release memory */
g_ctx.unlock_ep_cb(xfer->dev, &xfer->epid);
if (r && r->buffer)
free(r->buffer);
xfer->reqs[r->blk_head] = NULL;
free(r);
free_transfer:
libusb_free_transfer(trn);
}
static struct usb_dev_req *
usb_dev_alloc_req(struct usb_dev *udev, struct usb_xfer *xfer, int in,
size_t size, size_t count)
{
struct usb_dev_req *req;
static int seq = 1;
if (!udev || !xfer || count < 0)
return NULL;
req = calloc(1, sizeof(*req));
if (!req)
return NULL;
req->udev = udev;
req->in = in;
req->xfer = xfer;
req->seq = seq++;
req->trn = libusb_alloc_transfer(count);
if (!req->trn)
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->trn)
libusb_free_transfer(req->trn);
if (req)
free(req);
return NULL;
}
static int
usb_dev_prepare_xfer(struct usb_xfer *xfer, int *head, int *tail)
{
int i, idx, size, first;
struct usb_block *block = NULL;
idx = xfer->head;
first = -1;
size = 0;
if (idx < 0 || idx >= xfer->max_blk_cnt)
return -1;
for (i = 0; i < xfer->ndata;
i++, idx = index_inc(idx, xfer->max_blk_cnt)) {
block = &xfer->data[idx];
if (block->stat == USB_BLOCK_HANDLED ||
block->stat == USB_BLOCK_HANDLING)
continue;
first = (first < 0) ? idx : first;
switch (block->type) {
case USB_DATA_PART:
case USB_DATA_FULL:
size += block->blen;
block->stat = USB_BLOCK_HANDLING;
break;
case USB_DATA_NONE:
block->stat = USB_BLOCK_HANDLED;
continue;
default:
UPRINTF(LFTL, "%s error stat %d\r\n",
__func__, block->type);
}
}
*head = first;
*tail = xfer->tail;
return size;
}
static inline int
usb_dev_err_convert(int err)
{
switch (err) {
case LIBUSB_ERROR_TIMEOUT: return USB_ERR_TIMEOUT;
case LIBUSB_ERROR_PIPE: return USB_ERR_STALLED;
case LIBUSB_ERROR_NO_DEVICE: return USB_ERR_IOERROR;
case LIBUSB_ERROR_BUSY: return USB_ERR_IN_USE;
case LIBUSB_ERROR_OVERFLOW: return USB_ERR_BAD_BUFSIZE;
case LIBUSB_ERROR_IO: return USB_ERR_IOERROR;
default:
break; /* add more when required */
}
return USB_ERR_IOERROR;
}
static inline struct usb_dev_ep *
usb_dev_get_ep(struct usb_dev *udev, int pid, int ep)
{
if (ep < 0 || ep >= USB_NUM_ENDPOINT) {
UPRINTF(LWRN, "invalid ep %d\r\n", ep);
return NULL;
}
if (ep == 0)
return &udev->epc;
if (pid == TOKEN_IN)
return udev->epi + ep - 1;
else
return udev->epo + ep - 1;
}
static inline void
usb_dev_set_ep_type(struct usb_dev *udev, int pid, int epnum,
uint8_t type)
{
struct usb_dev_ep *ep;
ep = usb_dev_get_ep(udev, pid, epnum);
if (ep)
ep->type = type;
}
static inline uint8_t
usb_dev_get_ep_type(struct usb_dev *udev, int pid, int epnum)
{
struct usb_dev_ep *ep;
ep = usb_dev_get_ep(udev, pid, epnum);
if (!ep)
return USB_EP_ERR_TYPE;
else
return ep->type;
}
static inline void
usb_dev_set_ep_maxp(struct usb_dev *udev, int pid, int epnum, uint16_t maxp)
{
struct usb_dev_ep *ep;
ep = usb_dev_get_ep(udev, pid, epnum);
if (ep)
ep->maxp = maxp;
}
static inline uint16_t
usb_dev_get_ep_maxp(struct usb_dev *udev, int pid, int epnum)
{
struct usb_dev_ep *ep;
ep = usb_dev_get_ep(udev, pid, epnum);
if (!ep)
return 0;
else
return ep->maxp;
}
static void
usb_dev_reset_ep(struct usb_dev *udev)
{
int ep;
udev->epc.type = USB_ENDPOINT_CONTROL;
for (ep = 0; ep < USB_NUM_ENDPOINT; ep++) {
udev->epi[ep].pid = TOKEN_IN;
udev->epo[ep].pid = TOKEN_OUT;
udev->epi[ep].type = USB_ENDPOINT_INVALID;
udev->epo[ep].type = USB_ENDPOINT_INVALID;
}
}
static void
usb_dev_update_ep(struct usb_dev *udev)
{
struct libusb_config_descriptor *cfg;
const struct libusb_interface_descriptor *_if;
const struct libusb_endpoint_descriptor *desc;
int i, j;
if (libusb_get_active_config_descriptor(udev->info.priv_data, &cfg))
return;
for (i = 0; i < cfg->bNumInterfaces; i++) {
_if = &cfg->interface[i].altsetting[udev->alts[i]];
for (j = 0; j < _if->bNumEndpoints; j++) {
desc = &_if->endpoint[j];
usb_dev_set_ep_type(udev,
USB_EP_PID(desc),
USB_EP_NR(desc),
USB_EP_TYPE(desc));
usb_dev_set_ep_maxp(udev,
USB_EP_PID(desc),
USB_EP_NR(desc),
USB_EP_MAXP(desc));
}
}
libusb_free_config_descriptor(cfg);
}
static int
usb_dev_native_toggle_if(struct usb_dev *udev, int claim)
{
struct libusb_config_descriptor *config;
struct usb_devpath *path;
uint8_t c, i;
int rc = 0, r;
path = &udev->info.path;
r = libusb_get_active_config_descriptor(udev->info.priv_data, &config);
if (r) {
UPRINTF(LWRN, "%d-%s: can't get config\r\n", path->bus,
usb_dev_path(path));
return -1;
}
c = config->bConfigurationValue;
for (i = 0; i < config->bNumInterfaces; i++) {
if (claim == 1)
r = libusb_claim_interface(udev->handle, i);
else {
r = libusb_release_interface(udev->handle, i);
/* according to libusb, if libusb_release_interface
* return LIBUSB_ERROR_NOT_FOUND, it means that this
* interface is not claimed before. This case should
* not be considered as an error here.
*/
if (r == LIBUSB_ERROR_NOT_FOUND)
r = 0;
}
if (r) {
rc = -1;
UPRINTF(LWRN, "%d-%s:%d.%d can't %s if, r %d\r\n",
path->bus, usb_dev_path(path), c, i,
claim == 1 ? "claim" : "release", r);
}
}
if (rc)
UPRINTF(LWRN, "%d-%s fail to %s rc %d\r\n", path->bus,
usb_dev_path(path), claim == 1 ? "claim" :
"release", rc);
libusb_free_config_descriptor(config);
return rc;
}
static int
usb_dev_native_toggle_if_drivers(struct usb_dev *udev, int attach)
{
struct libusb_config_descriptor *config;
struct usb_devpath *path;
uint8_t c, i;
int rc = 0, r;
path = &udev->info.path;
r = libusb_get_active_config_descriptor(udev->info.priv_data, &config);
if (r) {
UPRINTF(LWRN, "%d-%s: can't get config\r\n", path->bus,
usb_dev_path(path));
return -1;
}
UPRINTF(LDBG, "%s driver\r\n", attach == 1 ? "attach" : "detach");
c = config->bConfigurationValue;
for (i = 0; i < config->bNumInterfaces; i++) {
if (attach == 1)
r = libusb_attach_kernel_driver(udev->handle, i);
else {
if (libusb_kernel_driver_active(udev->handle, i) == 1)
r = libusb_detach_kernel_driver(udev->handle,
i);
}
if (r) {
rc = -1;
UPRINTF(LWRN, "%d-%s:%d.%d can't %stach if driver, r %d"
"\r\n", path->bus, usb_dev_path(path),
c, i, attach == 1 ? "at" : "de", r);
}
}
if (rc)
UPRINTF(LWRN, "%d-%s fail to %s rc %d\r\n", path->bus,
usb_dev_path(path), attach == 1 ? "attach" :
"detach", rc);
libusb_free_config_descriptor(config);
return rc;
}
static void
usb_dev_set_config(struct usb_dev *udev, struct usb_xfer *xfer, int config)
{
int rc = 0;
struct libusb_config_descriptor *cfg;
/*
* set configuration
* according to the libusb doc, the detach and release work
* should be done before set configuration.
*/
usb_dev_native_toggle_if_drivers(udev, 0);
usb_dev_native_toggle_if(udev, 0);
rc = libusb_set_configuration(udev->handle, config);
if (rc) {
UPRINTF(LWRN, "fail to set config rc %d\r\n", rc);
goto err2;
}
/* claim all the interfaces of this configuration */
rc = libusb_get_active_config_descriptor(udev->info.priv_data, &cfg);
if (rc) {
UPRINTF(LWRN, "fail to get config rc %d\r\n", rc);
goto err2;
}
rc = usb_dev_native_toggle_if(udev, 1);
if (rc) {
UPRINTF(LWRN, "fail to claim if, rc %d\r\n", rc);
goto err1;
}
udev->if_num = cfg->bNumInterfaces;
udev->configuration = config;
usb_dev_reset_ep(udev);
usb_dev_update_ep(udev);
libusb_free_config_descriptor(cfg);
return;
err1:
usb_dev_native_toggle_if(udev, 0);
libusb_free_config_descriptor(cfg);
err2:
UPRINTF(LWRN, "%d-%s: fail to set config\r\n", udev->info.path.bus,
usb_dev_path(&udev->info.path));
xfer->status = USB_ERR_STALLED;
}
static void
usb_dev_set_if(struct usb_dev *udev, int iface, int alt, struct usb_xfer
*xfer)
{
if (iface >= USB_NUM_INTERFACE)
goto errout;
UPRINTF(LDBG, "%d-%s set if, iface %d alt %d\r\n", udev->info.path.bus,
usb_dev_path(&udev->info.path), iface, alt);
if (libusb_set_interface_alt_setting(udev->handle, iface, alt))
goto errout;
udev->alts[iface] = alt;
/*
* FIXME: Only support single interface USB device first. Need fix in
* future to support composite USB device.
*/
usb_dev_reset_ep(udev);
usb_dev_update_ep(udev);
return;
errout:
xfer->status = USB_ERR_STALLED;
UPRINTF(LDBG, "%d-%s fail to set if, iface %d alt %d\r\n",
udev->info.path.bus,
usb_dev_path(&udev->info.path),
iface,
alt);
}
static struct usb_block *
usb_dev_prepare_ctrl_xfer(struct usb_xfer *xfer)
{
int i, idx;
struct usb_block *ret = NULL;
struct usb_block *blk = NULL;
idx = xfer->head;
if (idx < 0 || idx >= xfer->max_blk_cnt)
return NULL;
for (i = 0; i < xfer->ndata; i++) {
/*
* find out the data block and set every
* block to be USB_BLOCK_HANDLED
*/
blk = &xfer->data[idx];
if (blk->blen > 0 && !ret)
ret = blk;
blk->stat = USB_BLOCK_HANDLED;
idx = index_inc(idx, xfer->max_blk_cnt);
}
return ret;
}
int
usb_dev_reset(void *pdata)
{
struct usb_dev *udev;
int rc = 0;
udev = pdata;
UPRINTF(LDBG, "reset endpoints\n");
libusb_reset_device(udev->handle);
usb_dev_reset_ep(udev);
usb_dev_update_ep(udev);
rc = libusb_reset_device(udev->handle);
if (!rc) {
usb_dev_reset_ep(udev);
usb_dev_update_ep(udev);
}
return rc;
}
int
usb_dev_data(void *pdata, struct usb_xfer *xfer, int dir, int epctx)
{
struct usb_dev *udev;
struct usb_dev_req *r;
struct usb_native_devinfo *info;
int rc = 0, epid;
uint8_t type;
int i, idx, buf_idx, head, tail, size;
struct usb_block *b;
static const char * const type_str[] = {"CTRL", "ISO", "BULK", "INT"};
static const char * const dir_str[] = {"OUT", "IN"};
int framelen = 0, framecnt = 0;
uint16_t maxp;
udev = pdata;
info = &udev->info;
xfer->status = USB_ERR_NORMAL_COMPLETION;
size = usb_dev_prepare_xfer(xfer, &head, &tail);
if (size <= 0)
goto done;
type = usb_dev_get_ep_type(udev, dir ? TOKEN_IN : TOKEN_OUT, epctx);
if (type > USB_ENDPOINT_INT) {
xfer->status = USB_ERR_IOERROR;
goto done;
}
epid = dir ? (0x80 | epctx) : epctx;
if (!(dir == USB_XFER_IN || dir == USB_XFER_OUT)) {
xfer->status = USB_ERR_IOERROR;
goto done;
}
maxp = usb_dev_get_ep_maxp(udev, dir, epctx);
if (type == USB_ENDPOINT_ISOC) {
/* need to double check it, there might be some non-spec
* compatible usb devices in the market.
*/
framelen = USB_EP_MAXP_SZ(maxp) * (1 + USB_EP_MAXP_MT(maxp));
UPRINTF(LDBG, "iso maxp %u framelen %d\r\n", maxp, framelen);
for (idx = head;
index_valid(head, tail, xfer->max_blk_cnt, idx);
idx = index_inc(idx, xfer->max_blk_cnt)) {
if (xfer->data[idx].blen > framelen)
UPRINTF(LFTL, "err framelen %d\r\n", framelen);
if (xfer->data[idx].type == USB_DATA_NONE ||
xfer->data[idx].type == USB_DATA_PART)
continue;
else if (xfer->data[idx].type == USB_DATA_FULL)
framecnt++;
else
UPRINTF(LFTL, "%s:%d error\r\n", __func__, __LINE__);
}
UPRINTF(LDBG, "iso maxp %u framelen %d, framecnt %d\r\n", maxp,
framelen, framecnt);
}
r = usb_dev_alloc_req(udev, xfer, dir, size, type ==
USB_ENDPOINT_ISOC ? framecnt : 0);
if (!r) {
xfer->status = USB_ERR_IOERROR;
goto done;
}
r->buf_size = size;
r->blk_head = head;
r->blk_tail = tail;
xfer->reqs[head] = r;
UPRINTF(LDBG, "%s: %d-%s: explen %d ep%d-xfr [%d-%d %d] rq-%d "
"[%d-%d %d] dir %s type %s\r\n", __func__,
info->path.bus, usb_dev_path(&info->path), size, epctx,
xfer->head, xfer->tail, xfer->ndata, r->seq,
r->blk_head, r->blk_tail, r->buf_size, dir_str[dir],
type_str[type]);
if (!dir) {
for (idx = head, buf_idx = 0;
index_valid(head, tail, xfer->max_blk_cnt, idx);
idx = index_inc(idx, xfer->max_blk_cnt)) {
b = &xfer->data[idx];
if (b->type == USB_DATA_PART ||
b->type == USB_DATA_FULL) {
memcpy(&r->buffer[buf_idx], b->buf, b->blen);
buf_idx += b->blen;
}
}
}
if (type == USB_ENDPOINT_ISOC) {
for (i = 0, idx = head;
index_valid(head, tail, xfer->max_blk_cnt, idx);
idx = index_inc(idx, xfer->max_blk_cnt)) {
int len = xfer->data[idx].blen;
if (xfer->data[idx].type == USB_DATA_NONE) {
continue;
} else if (xfer->data[idx].type == USB_DATA_PART) {
r->trn->iso_packet_desc[i].length += len;
continue;
} else if (xfer->data[idx].type == USB_DATA_FULL) {
r->trn->iso_packet_desc[i].length += len;
} else {
UPRINTF(LFTL, "%s:%d error\r\n",
__func__, __LINE__);
}
UPRINTF(LDBG, "desc[%d].length %d\r\n", i,
r->trn->iso_packet_desc[i].length);
i++;
}
}
if (type == USB_ENDPOINT_BULK) {
libusb_fill_bulk_transfer(r->trn, udev->handle, epid,
r->buffer, size, usb_dev_comp_cb, r, 0);
} else if (type == USB_ENDPOINT_INT) {
libusb_fill_interrupt_transfer(r->trn, udev->handle, epid,
r->buffer, size, usb_dev_comp_cb, r, 0);
} else if (type == USB_ENDPOINT_ISOC) {
libusb_fill_iso_transfer(r->trn, udev->handle, epid,
r->buffer, size, framecnt,
usb_dev_comp_cb, r, 0);
} else {
UPRINTF(LFTL, "%s: wrong endpoint type %d\r\n", __func__, type);
if (r->buffer)
free(r->buffer);
if (r->trn)
libusb_free_transfer(r->trn);
free(r);
xfer->status = USB_ERR_INVAL;
}
rc = libusb_submit_transfer(r->trn);
if (rc) {
xfer->status = USB_ERR_IOERROR;
UPRINTF(LDBG, "libusb_submit_transfer fail: %d\n", rc);
}
done:
return xfer->status;
}
static void
clear_uas_desc(struct usb_dev *udev, uint8_t *data, int len)
{
struct usb_devpath *path;
int32_t i;
/* only process configuration descriptor */
if (len < 2 || data[1] != 0x2)
return;
i = 0;
path = &udev->info.path;
while (i < len) {
/* When UAS protocol is found in interface descriptor, set it
* to invalid value.
*
* According to USB3 spec 9.6.5, Standard Interface Descriptor,
* data[i+0] => bLength
* data[i+1] => bDescriptorType
* data[i+7] => bInterfaceProtocol
*/
if (data[i] == 9 && data[i+1] == 0x4 && data[i+7] == 0x62) {
UPRINTF(LFTL, "%d-%s: clear uas protocol\r\n",
path->bus, usb_dev_path(path));
data[i+7] = 0;
}
i = i + data[i];
}
}
int
usb_dev_request(void *pdata, struct usb_xfer *xfer)
{
struct usb_dev *udev;
uint8_t request_type;
uint8_t request;
uint16_t value;
uint16_t index;
uint16_t len;
struct usb_block *blk;
uint8_t *data;
int rc;
bool need_chk_uas = false;
udev = pdata;
xfer->status = USB_ERR_NORMAL_COMPLETION;
if (!udev->info.priv_data || !xfer->ureq) {
UPRINTF(LWRN, "invalid request\r\n");
xfer->status = USB_ERR_IOERROR;
goto out;
}
request_type = xfer->ureq->bmRequestType;
request = xfer->ureq->bRequest;
value = xfer->ureq->wValue;
index = xfer->ureq->wIndex;
len = xfer->ureq->wLength;
blk = usb_dev_prepare_ctrl_xfer(xfer);
data = blk ? blk->buf : NULL;
UPRINTF(LDBG, "%d-%s: urb: type 0x%x req 0x%x val 0x%x idx %d len %d "
"data %d\r\n", udev->info.path.bus,
usb_dev_path(&udev->info.path), request_type, request,
value, index, len, blk ? blk->blen : 0);
/*
* according to usb spec, control transfer may have no
* DATA STAGE, so the valid situations are:
* a. with DATA STAGE: blk != NULL && len > 0
* b. without DATA STAGE: blk == NULL && len == 0
* any other situations, just skip process
*/
if ((!blk && len > 0) || (blk && len <= 0))
goto out;
switch (UREQ(request, request_type)) {
case UREQ(UR_SET_ADDRESS, UT_WRITE_DEVICE):
UPRINTF(LDBG, "UR_SET_ADDRESS\n");
udev->addr = value;
goto out;
case UREQ(UR_SET_CONFIG, UT_WRITE_DEVICE):
UPRINTF(LDBG, "UR_SET_CONFIG\n");
usb_dev_set_config(udev, xfer, value & 0xff);
goto out;
case UREQ(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
UPRINTF(LDBG, "UR_SET_INTERFACE\n");
usb_dev_set_if(udev, index, value, xfer);
goto out;
case UREQ(UR_GET_DESCRIPTOR, UT_READ):
if (value == 0x0200)
need_chk_uas = true;
break;
case UREQ(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
if (value) {
/* according to usb spec (ch9), this is impossible */
UPRINTF(LWRN, "Clear Feature request with non-zero "
"value %d\r\n", value);
break;
}
UPRINTF(LDBG, "UR_CLEAR_HALT\n");
rc = libusb_clear_halt(udev->handle, index);
if (rc)
UPRINTF(LWRN, "fail to clear halted ep, rc %d\r\n", rc);
goto out;
}
/* (data == NULL && len == 0) is okey to libusb_control_transfer */
if (data == NULL && len) {
xfer->status = USB_ERR_IOERROR;
UPRINTF(LFTL, "%s unexpected NULL data\r\n", __func__);
goto out;
}
/* send it to physical device */
/* FIXME: In the process of implementation of USB isochronouse transfer,
* the timeout time is not enough for Plantronics headset. So this
* issue should be investigated detailly, and at worst situation, the
* control transfer should be also changed to async operation.
*/
rc = libusb_control_transfer(udev->handle, request_type, request,
value, index, data, len, 300);
if (rc < 0) {
xfer->status = usb_dev_err_convert(rc);
goto out;
}
/* TODO: Currently, the USB Attached SCSI (UAS) protocol is not
* supported and the following code is used as a workaround now.
* UAS will be implemented in future.
*/
if (need_chk_uas && data)
clear_uas_desc(udev, data, rc);
if (blk) {
blk->blen = len - rc;
blk->bdone += rc;
xfer->status = blk->blen > 0 ? USB_ERR_SHORT_XFER :
USB_ERR_NORMAL_COMPLETION;
} else
xfer->status = USB_ERR_NORMAL_COMPLETION;
UPRINTF(LDBG, "%d-%s: usb rc %d, blk %p, blen %u bdon %u\n",
udev->info.path.bus, usb_dev_path(&udev->info.path),
rc, blk, blk ? blk->blen : 0, blk ? blk->bdone : 0);
out:
return xfer->status;
}
void *
usb_dev_init(void *pdata, char *opt)
{
struct usb_dev *udev = NULL;
struct libusb_device_descriptor desc;
struct usb_native_devinfo *di;
int ver;
di = pdata;
libusb_get_device_descriptor(di->priv_data, &desc);
UPRINTF(LINF, "Found USB device: %d-%s\r\nPID(0x%X), VID(0x%X) CLASS"
"(0x%X) SUBCLASS(0x%X) BCD(0x%X) SPEED(%d)\r\n",
di->path.bus, usb_dev_path(&di->path), di->pid,
di->vid, desc.bDeviceClass, desc.bDeviceSubClass,
di->bcd, di->speed);
/* allocate and populate udev */
udev = calloc(1, sizeof(struct usb_dev));
if (!udev)
goto errout;
/* this is a root hub */
if (ROOTHUB_PORT(di->path) == 0)
goto errout;
switch (desc.bcdUSB) {
case 0x320:
case 0x310:
case 0x300:
ver = 3; break;
case 0x200:
case 0x201:
case 0x210:
case 0x110:
/* 0x110 is a special case.
* xHCI spec v1.0 was released in 2010 and USB spec v1.1 was
* released in 1998, anything about USB 1.x could hardly be
* found in xHCI spec. So here use USB 2.x to do the emulation
* for USB 1.x device.
* And one more thing, it is almost impossible to find an USB
* 1.x device today.
*/
ver = 2; break;
default:
goto errout;
}
udev->info = *di;
udev->version = ver;
udev->handle = NULL;
/* configure physical device through libusb library */
if (libusb_open(udev->info.priv_data, &udev->handle)) {
UPRINTF(LWRN, "fail to open device.\r\n");
goto errout;
}
if (usb_dev_native_toggle_if_drivers(udev, 0) < 0) {
UPRINTF(LWRN, "fail to detach interface driver.\r\n");
goto errout;
}
return udev;
errout:
if (udev && udev->handle)
libusb_close(udev->handle);
free(udev);
return NULL;
}
void
usb_dev_free_request(void *pdata)
{
struct libusb_transfer *trn;
trn = pdata;
libusb_free_transfer(trn);
}
void
usb_dev_cancel_request(void *pdata)
{
struct libusb_transfer *trn;
trn = pdata;
libusb_cancel_transfer(trn);
}
void
usb_dev_deinit(void *pdata)
{
int rc = 0;
struct usb_dev *udev;
udev = pdata;
if (udev) {
if (udev->handle) {
rc = usb_dev_native_toggle_if_drivers(udev, 1);
if (rc)
UPRINTF(LWRN, "fail to attach if drv rc:%d\r\n",
rc);
libusb_close(udev->handle);
}
free(udev);
}
}
int
usb_dev_info(void *pdata, int type, void *value, int size)
{
struct usb_dev *udev;
int sz;
void *pv;
udev = pdata;
switch (type) {
case USB_INFO_VERSION:
sz = sizeof(udev->version);
pv = &udev->version;
break;
case USB_INFO_SPEED:
sz = sizeof(udev->info.speed);
udev->info.speed = libusb_speed_to_usb_speed(udev->info.speed);
pv = &udev->info.speed;
break;
case USB_INFO_BUS:
sz = sizeof(udev->info.path.bus);
pv = &udev->info.path.bus;
break;
case USB_INFO_PORT:
sz = sizeof(udev->info.path.path[0]);
pv = &udev->info.path.path[0];
break;
case USB_INFO_VID:
sz = sizeof(udev->info.vid);
pv = &udev->info.vid;
break;
case USB_INFO_PID:
sz = sizeof(udev->info.pid);
pv = &udev->info.pid;
break;
default:
return -1;
}
if (size == sz)
memcpy(value, pv, sz);
return sz == size ? 0 : -1;
}
static void *
usb_dev_sys_thread(void *arg)
{
struct timeval t = {1, 0};
int rc = 0;
while (g_ctx.thread_exit == 0) {
rc = libusb_handle_events_timeout(g_ctx.libusb_ctx, &t);
if (rc < 0)
/* TODO: maybe one second as interval is too long which
* may result of slower USB enumeration process.
*/
sleep(1);
}
UPRINTF(LINF, "poll thread exit\n\r");
return NULL;
}
static int
usb_dev_native_sys_conn_cb(struct libusb_context *ctx, struct libusb_device
*ldev, libusb_hotplug_event event, void *pdata)
{
struct usb_native_devinfo di;
bool ret;
UPRINTF(LDBG, "connect event\r\n");
if (!ctx || !ldev) {
UPRINTF(LFTL, "connect callback fails!\n");
return -1;
}
ret = usb_get_native_devinfo(ldev, &di, NULL);
if (ret == false)
return 0;
if (g_ctx.conn_cb)
g_ctx.conn_cb(g_ctx.hci_data, &di);
return 0;
}
static int
usb_dev_native_sys_disconn_cb(struct libusb_context *ctx, struct libusb_device
*ldev, libusb_hotplug_event event, void *pdata)
{
struct usb_native_devinfo di;
bool ret;
UPRINTF(LDBG, "disconnect event\r\n");
if (!ctx || !ldev) {
UPRINTF(LFTL, "disconnect callback fails!\n");
return -1;
}
ret = usb_get_native_devinfo(ldev, &di, NULL);
if (ret == false)
return 0;
if (g_ctx.disconn_cb)
g_ctx.disconn_cb(g_ctx.hci_data, &di);
return 0;
}
int
usb_dev_sys_init(usb_dev_sys_cb conn_cb, usb_dev_sys_cb disconn_cb,
usb_dev_sys_cb notify_cb, usb_dev_sys_cb intr_cb,
usb_dev_sys_cb lock_ep_cb, usb_dev_sys_cb unlock_ep_cb,
void *hci_data, int log_level)
{
libusb_hotplug_event native_conn_evt;
libusb_hotplug_event native_disconn_evt;
libusb_hotplug_flag flags;
libusb_hotplug_callback_handle native_conn_handle;
libusb_hotplug_callback_handle native_disconn_handle;
int native_pid, native_vid, native_cls, rc;
int num_devs;
usb_set_log_level(log_level);
if (g_ctx.libusb_ctx) {
UPRINTF(LFTL, "port mapper is already initialized.\r\n");
return -1;
}
rc = libusb_init(&g_ctx.libusb_ctx);
if (rc < 0) {
UPRINTF(LFTL, "libusb_init fails, rc:%d\r\n", rc);
return -1;
}
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;
g_ctx.lock_ep_cb = lock_ep_cb;
g_ctx.unlock_ep_cb = unlock_ep_cb;
num_devs = usb_dev_scan_dev(&g_ctx.devlist);
UPRINTF(LINF, "found %d devices before Guest OS booted\r\n", num_devs);
native_conn_evt = LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED;
native_disconn_evt = LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT;
native_pid = LIBUSB_HOTPLUG_MATCH_ANY;
native_vid = LIBUSB_HOTPLUG_MATCH_ANY;
native_cls = LIBUSB_HOTPLUG_MATCH_ANY;
flags = 0;
/* register connect callback */
rc = libusb_hotplug_register_callback(g_ctx.libusb_ctx, native_conn_evt,
flags, native_vid, native_pid, native_cls,
usb_dev_native_sys_conn_cb, NULL, &native_conn_handle);
if (rc != LIBUSB_SUCCESS)
goto errout;
/* register disconnect callback */
rc = libusb_hotplug_register_callback(g_ctx.libusb_ctx,
native_disconn_evt, flags, native_vid, native_pid,
native_cls, usb_dev_native_sys_disconn_cb, NULL,
&native_disconn_handle);
if (rc != LIBUSB_SUCCESS) {
libusb_hotplug_deregister_callback(g_ctx.libusb_ctx,
native_conn_handle);
goto errout;
}
/* this is for guest rebooting purpose */
g_ctx.conn_handle = native_conn_handle;
g_ctx.disconn_handle = native_disconn_handle;
g_ctx.thread_exit = 0;
if (pthread_create(&g_ctx.thread, NULL, usb_dev_sys_thread, NULL)) {
libusb_hotplug_deregister_callback(g_ctx.libusb_ctx,
native_conn_handle);
libusb_hotplug_deregister_callback(g_ctx.libusb_ctx,
native_disconn_handle);
goto errout;
}
pthread_setname_np(g_ctx.thread, "usb_dev_sys");
return 0;
errout:
if (g_ctx.devlist) {
libusb_free_device_list(g_ctx.devlist, 1);
g_ctx.devlist = NULL;
}
if (g_ctx.libusb_ctx) {
libusb_exit(g_ctx.libusb_ctx);
g_ctx.libusb_ctx = NULL;
}
return -1;
}
void
usb_dev_sys_deinit(void)
{
if (!g_ctx.libusb_ctx)
return;
UPRINTF(LINF, "port-mapper de-initialization\r\n");
libusb_hotplug_deregister_callback(g_ctx.libusb_ctx, g_ctx.conn_handle);
libusb_hotplug_deregister_callback(g_ctx.libusb_ctx,
g_ctx.disconn_handle);
g_ctx.thread_exit = 1;
pthread_join(g_ctx.thread, NULL);
if (g_ctx.devlist) {
libusb_free_device_list(g_ctx.devlist, 1);
g_ctx.devlist = NULL;
}
libusb_exit(g_ctx.libusb_ctx);
g_ctx.libusb_ctx = NULL;
}