mirror of
https://github.com/projectacrn/acrn-hypervisor.git
synced 2025-08-14 14:25:14 +00:00
The USB mediator doesn't support USB Attached SCSI (UAS) protocol due to no proper implementation for USB Stream. This patch will use USB Bulk transfer to workaround until formal implentation for UAS is ready. Tracked-On: #3486 Signed-off-by: Xiaoguang Wu <xiaoguang.wu@intel.com> Acked-by: Yu Wang <yu1.wang@intel.com>
1363 lines
32 KiB
C
1363 lines
32 KiB
C
/*
|
|
* Copyright (C) 2018 Intel Corporation. All rights reserved.
|
|
*
|
|
* 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 uint8_t usb_dev_get_ep_type(struct usb_dev *udev, int pid, int epnum);
|
|
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_data_xfer *xfer;
|
|
struct usb_data_xfer_block *block;
|
|
struct usb_native_devinfo *info;
|
|
int do_intr = 0;
|
|
int i, j, idx, buf_idx, done;
|
|
int bstart, bcount;
|
|
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);
|
|
}
|
|
bstart = r->blk_start;
|
|
bcount = r->blk_count;
|
|
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 - 1) % USB_MAX_XFER_BLOCKS,
|
|
xfer->ndata, r->seq, bstart,
|
|
(bstart + bcount - 1) % USB_MAX_XFER_BLOCKS,
|
|
r->buf_length, 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:
|
|
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 = j = 0;
|
|
buf_idx = 0;
|
|
idx = r->blk_start;
|
|
buf = r->buffer;
|
|
done = trn->actual_length;
|
|
|
|
while (i < r->blk_count) {
|
|
|
|
if (trn->type == LIBUSB_TRANSFER_TYPE_ISOCHRONOUS) {
|
|
buf_idx = 0;
|
|
buf = libusb_get_iso_packet_buffer_simple(trn, j);
|
|
done = trn->iso_packet_desc[j].actual_length;
|
|
j++;
|
|
}
|
|
do {
|
|
int d;
|
|
|
|
if (i >= r->blk_count)
|
|
break;
|
|
|
|
block = &xfer->data[idx % USB_MAX_XFER_BLOCKS];
|
|
if (block->processed == USB_XFER_BLK_FREE)
|
|
UPRINTF(LFTL, "error: found free block\r\n");
|
|
|
|
d = done;
|
|
if (d > block->blen)
|
|
d = block->blen;
|
|
|
|
if (block->buf) {
|
|
if (r->in == TOKEN_IN) {
|
|
memcpy(block->buf, buf + buf_idx, d);
|
|
buf_idx += d;
|
|
}
|
|
} else {
|
|
/* Link TRB */
|
|
i--;
|
|
j--;
|
|
}
|
|
|
|
done -= d;
|
|
block->blen -= d;
|
|
block->bdone = d;
|
|
block->processed = USB_XFER_BLK_HANDLED;
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
i++;
|
|
|
|
} while (block->chained == 1);
|
|
}
|
|
|
|
stall_out:
|
|
if (is_stalled) {
|
|
for (i = 0, idx = r->blk_start; i < r->blk_count; ++i) {
|
|
block = &xfer->data[idx % USB_MAX_XFER_BLOCKS];
|
|
block->processed = USB_XFER_BLK_HANDLED;
|
|
}
|
|
}
|
|
|
|
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->requests[r->blk_start] = NULL;
|
|
free(r);
|
|
free_transfer:
|
|
libusb_free_transfer(trn);
|
|
}
|
|
|
|
static struct usb_dev_req *
|
|
usb_dev_alloc_req(struct usb_dev *udev, struct usb_data_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_data_xfer *xfer, int *count, int *size)
|
|
{
|
|
int found, i, idx, c, s, first;
|
|
struct usb_data_xfer_block *block = NULL;
|
|
|
|
idx = xfer->head;
|
|
found = 0;
|
|
first = -1;
|
|
c = s = 0;
|
|
if (!count || !size || idx < 0 || idx >= USB_MAX_XFER_BLOCKS)
|
|
return -1;
|
|
|
|
for (i = 0; i < xfer->ndata; i++) {
|
|
block = &xfer->data[idx];
|
|
|
|
if (block->processed == USB_XFER_BLK_HANDLED ||
|
|
block->processed == USB_XFER_BLK_HANDLING) {
|
|
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 (!block->buf || !block->blen) {
|
|
/* there are two cases:
|
|
* 1. LINK trb is in the middle of trbs.
|
|
* 2. LINK trb is a single trb.
|
|
*/
|
|
block->processed = USB_XFER_BLK_HANDLED;
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
continue;
|
|
} else if (found) {
|
|
UPRINTF(LWRN, "find a NULL data. %d total %d\n",
|
|
i, xfer->ndata);
|
|
}
|
|
block->processed = USB_XFER_BLK_HANDLING;
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
}
|
|
|
|
*count = c;
|
|
*size = s;
|
|
return first;
|
|
}
|
|
|
|
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_data_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_data_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_data_xfer_block *
|
|
usb_dev_prepare_ctrl_xfer(struct usb_data_xfer *xfer)
|
|
{
|
|
int i, idx;
|
|
struct usb_data_xfer_block *ret = NULL;
|
|
struct usb_data_xfer_block *blk = NULL;
|
|
|
|
idx = xfer->head;
|
|
|
|
if (idx < 0 || idx >= USB_MAX_XFER_BLOCKS)
|
|
return NULL;
|
|
|
|
for (i = 0; i < xfer->ndata; i++) {
|
|
/*
|
|
* find out the data block and set every
|
|
* block to be processed
|
|
*/
|
|
blk = &xfer->data[idx];
|
|
if (blk->blen > 0 && !ret)
|
|
ret = blk;
|
|
|
|
blk->processed = USB_XFER_BLK_HANDLED;
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
int
|
|
usb_dev_reset(void *pdata)
|
|
{
|
|
struct usb_dev *udev;
|
|
|
|
udev = pdata;
|
|
|
|
UPRINTF(LDBG, "reset endpoints\n");
|
|
libusb_reset_device(udev->handle);
|
|
usb_dev_reset_ep(udev);
|
|
usb_dev_update_ep(udev);
|
|
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 *r;
|
|
struct usb_native_devinfo *info;
|
|
int rc = 0, epid;
|
|
uint8_t type;
|
|
int blk_start, data_size, blk_count;
|
|
int i, j, idx, buf_idx;
|
|
struct usb_data_xfer_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;
|
|
|
|
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);
|
|
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 (i = 0, idx = blk_start; i < blk_count; i++) {
|
|
if (xfer->data[idx].blen > framelen)
|
|
UPRINTF(LFTL, "err framelen %d\r\n", framelen);
|
|
|
|
if (xfer->data[idx].blen <= 0) {
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
if (xfer->data[idx].chained == 1) {
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
continue;
|
|
}
|
|
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
framecnt++;
|
|
}
|
|
UPRINTF(LDBG, "iso maxp %u framelen %d, framecnt %d\r\n", maxp,
|
|
framelen, framecnt);
|
|
}
|
|
|
|
if (data_size <= 0)
|
|
goto done;
|
|
|
|
r = usb_dev_alloc_req(udev, xfer, dir, data_size, type ==
|
|
USB_ENDPOINT_ISOC ? framecnt : 0);
|
|
if (!r) {
|
|
xfer->status = USB_ERR_IOERROR;
|
|
goto done;
|
|
}
|
|
|
|
r->buf_length = data_size;
|
|
r->blk_start = blk_start;
|
|
r->blk_count = blk_count;
|
|
xfer->requests[blk_start] = 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),
|
|
data_size, epctx, xfer->head, (xfer->tail - 1) %
|
|
USB_MAX_XFER_BLOCKS, xfer->ndata, r->seq, blk_start,
|
|
(blk_start + blk_count - 1) % USB_MAX_XFER_BLOCKS,
|
|
data_size, dir_str[dir], type_str[type]);
|
|
|
|
if (!dir) {
|
|
for (i = 0, j = 0, buf_idx = 0; j < blk_count; ++i) {
|
|
b = &xfer->data[(blk_start + i) % USB_MAX_XFER_BLOCKS];
|
|
if (b->buf) {
|
|
memcpy(&r->buffer[buf_idx], b->buf, b->blen);
|
|
buf_idx += b->blen;
|
|
j++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (type == USB_ENDPOINT_ISOC) {
|
|
for (i = 0, j = 0, idx = blk_start; i < blk_count; ++i) {
|
|
int len = xfer->data[idx].blen;
|
|
|
|
if (len <= 0) {
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
i--;
|
|
continue;
|
|
}
|
|
|
|
if (xfer->data[idx].chained == 1) {
|
|
r->trn->iso_packet_desc[j].length += len;
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
continue;
|
|
}
|
|
|
|
r->trn->iso_packet_desc[j].length += len;
|
|
idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
|
|
UPRINTF(LDBG, "desc[%d].length %d\r\n", j,
|
|
r->trn->iso_packet_desc[j].length);
|
|
j++;
|
|
}
|
|
}
|
|
|
|
if (type == USB_ENDPOINT_BULK) {
|
|
libusb_fill_bulk_transfer(r->trn, udev->handle, epid,
|
|
r->buffer, data_size, usb_dev_comp_cb, r, 0);
|
|
|
|
} else if (type == USB_ENDPOINT_INT) {
|
|
libusb_fill_interrupt_transfer(r->trn, udev->handle, epid,
|
|
r->buffer, data_size, usb_dev_comp_cb, r, 0);
|
|
|
|
} else if (type == USB_ENDPOINT_ISOC) {
|
|
libusb_fill_iso_transfer(r->trn, udev->handle, epid,
|
|
r->buffer, data_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, uint32_t 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_data_xfer *xfer)
|
|
{
|
|
struct usb_dev *udev;
|
|
uint8_t request_type;
|
|
uint8_t request;
|
|
uint16_t value;
|
|
uint16_t index;
|
|
uint16_t len;
|
|
struct usb_data_xfer_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;
|
|
|
|
}
|
|
|
|
/* 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);
|
|
|
|
/* 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)
|
|
clear_uas_desc(udev, data, rc);
|
|
|
|
if (rc >= 0 && blk) {
|
|
blk->blen = len - rc;
|
|
blk->bdone += rc;
|
|
xfer->status = blk->blen > 0 ? USB_ERR_SHORT_XFER :
|
|
USB_ERR_NORMAL_COMPLETION;
|
|
} else if (rc >= 0)
|
|
xfer->status = USB_ERR_NORMAL_COMPLETION;
|
|
else
|
|
xfer->status = usb_dev_err_convert(rc);
|
|
|
|
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 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_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;
|
|
}
|