libvirt/src/util/hostusb.c
Jiri Denemark 059aff6b98 qemu: Add option to treat missing USB devices as success
All USB device lookup functions emit an error when they cannot find the
requested device. With this patch, their caller can choose if a missing
device is an error or normal condition.
2012-10-11 15:11:41 +02:00

507 lines
12 KiB
C

/*
* Copyright (C) 2009-2012 Red Hat, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <http://www.gnu.org/licenses/>.
*
* Authors:
* Daniel P. Berrange <berrange@redhat.com>
*/
#include <config.h>
#include <dirent.h>
#include <fcntl.h>
#include <inttypes.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "hostusb.h"
#include "logging.h"
#include "memory.h"
#include "util.h"
#include "virterror_internal.h"
#define USB_SYSFS "/sys/bus/usb"
#define USB_DEVFS "/dev/bus/usb/"
#define USB_ID_LEN 10 /* "1234 5678" */
#define USB_ADDR_LEN 8 /* "123:456" */
/* For virReportOOMError() and virReportSystemError() */
#define VIR_FROM_THIS VIR_FROM_NONE
struct _usbDevice {
unsigned int bus;
unsigned int dev;
char name[USB_ADDR_LEN]; /* domain:bus:slot.function */
char id[USB_ID_LEN]; /* product vendor */
char *path;
const char *used_by; /* name of the domain using this dev */
};
struct _usbDeviceList {
unsigned int count;
usbDevice **devs;
};
typedef enum {
USB_DEVICE_ALL = 0,
USB_DEVICE_FIND_BY_VENDOR = 1 << 0,
USB_DEVICE_FIND_BY_BUS = 1 << 1,
} usbDeviceFindFlags;
static int usbSysReadFile(const char *f_name, const char *d_name,
int base, unsigned int *value)
{
int ret = -1, tmp;
char *buf = NULL;
char *filename = NULL;
char *ignore = NULL;
tmp = virAsprintf(&filename, USB_SYSFS "/devices/%s/%s", d_name, f_name);
if (tmp < 0) {
virReportOOMError();
goto cleanup;
}
if (virFileReadAll(filename, 1024, &buf) < 0)
goto cleanup;
if (virStrToLong_ui(buf, &ignore, base, value) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Could not parse usb file %s"), filename);
goto cleanup;
}
ret = 0;
cleanup:
VIR_FREE(filename);
VIR_FREE(buf);
return ret;
}
static usbDeviceList *
usbDeviceSearch(unsigned int vendor,
unsigned int product,
unsigned int bus,
unsigned int devno,
unsigned int flags)
{
DIR *dir = NULL;
bool found = false;
char *ignore = NULL;
struct dirent *de;
usbDeviceList *list = NULL, *ret = NULL;
usbDevice *usb;
if (!(list = usbDeviceListNew()))
goto cleanup;
dir = opendir(USB_SYSFS "/devices");
if (!dir) {
virReportSystemError(errno,
_("Could not open directory %s"),
USB_SYSFS "/devices");
goto cleanup;
}
while ((de = readdir(dir))) {
unsigned int found_prod, found_vend, found_bus, found_devno;
char *tmpstr = de->d_name;
if (de->d_name[0] == '.' || strchr(de->d_name, ':'))
continue;
if (usbSysReadFile("idVendor", de->d_name,
16, &found_vend) < 0)
goto cleanup;
if (usbSysReadFile("idProduct", de->d_name,
16, &found_prod) < 0)
goto cleanup;
if (STRPREFIX(de->d_name, "usb"))
tmpstr += 3;
if (virStrToLong_ui(tmpstr, &ignore, 10, &found_bus) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Failed to parse dir name '%s'"),
de->d_name);
goto cleanup;
}
if (usbSysReadFile("devnum", de->d_name,
10, &found_devno) < 0)
goto cleanup;
if ((flags & USB_DEVICE_FIND_BY_VENDOR) &&
(found_prod != product || found_vend != vendor))
continue;
if (flags & USB_DEVICE_FIND_BY_BUS) {
if (found_bus != bus || found_devno != devno)
continue;
found = true;
}
usb = usbGetDevice(found_bus, found_devno);
if (!usb)
goto cleanup;
if (usbDeviceListAdd(list, usb) < 0) {
usbFreeDevice(usb);
goto cleanup;
}
if (found)
break;
}
ret = list;
cleanup:
if (dir) {
int saved_errno = errno;
closedir(dir);
errno = saved_errno;
}
if (!ret)
usbDeviceListFree(list);
return ret;
}
int
usbFindDeviceByVendor(unsigned int vendor,
unsigned product,
bool mandatory,
usbDeviceList **devices)
{
usbDeviceList *list;
int count;
if (!(list = usbDeviceSearch(vendor, product, 0 , 0,
USB_DEVICE_FIND_BY_VENDOR)))
return -1;
if (list->count == 0) {
usbDeviceListFree(list);
if (!mandatory) {
VIR_DEBUG("Did not find USB device %x:%x",
vendor, product);
if (devices)
*devices = NULL;
return 0;
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Did not find USB device %x:%x"), vendor, product);
return -1;
}
count = list->count;
if (devices)
*devices = list;
else
usbDeviceListFree(list);
return count;
}
int
usbFindDeviceByBus(unsigned int bus,
unsigned devno,
bool mandatory,
usbDevice **usb)
{
usbDeviceList *list;
if (!(list = usbDeviceSearch(0, 0, bus, devno,
USB_DEVICE_FIND_BY_BUS)))
return -1;
if (list->count == 0) {
usbDeviceListFree(list);
if (!mandatory) {
VIR_DEBUG("Did not find USB device bus:%u device:%u",
bus, devno);
if (usb)
*usb = NULL;
return 0;
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Did not find USB device bus:%u device:%u"),
bus, devno);
return -1;
}
if (usb) {
*usb = usbDeviceListGet(list, 0);
usbDeviceListSteal(list, *usb);
}
usbDeviceListFree(list);
return 0;
}
int
usbFindDevice(unsigned int vendor,
unsigned int product,
unsigned int bus,
unsigned int devno,
bool mandatory,
usbDevice **usb)
{
usbDeviceList *list;
unsigned int flags = USB_DEVICE_FIND_BY_VENDOR|USB_DEVICE_FIND_BY_BUS;
if (!(list = usbDeviceSearch(vendor, product, bus, devno, flags)))
return -1;
if (list->count == 0) {
usbDeviceListFree(list);
if (!mandatory) {
VIR_DEBUG("Did not find USB device %x:%x bus:%u device:%u",
vendor, product, bus, devno);
if (usb)
*usb = NULL;
return 0;
}
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Did not find USB device %x:%x bus:%u device:%u"),
vendor, product, bus, devno);
return -1;
}
if (usb) {
*usb = usbDeviceListGet(list, 0);
usbDeviceListSteal(list, *usb);
}
usbDeviceListFree(list);
return 0;
}
usbDevice *
usbGetDevice(unsigned int bus,
unsigned int devno)
{
usbDevice *dev;
if (VIR_ALLOC(dev) < 0) {
virReportOOMError();
return NULL;
}
dev->bus = bus;
dev->dev = devno;
if (snprintf(dev->name, sizeof(dev->name), "%.3o:%.3o",
dev->bus, dev->dev) >= sizeof(dev->name)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("dev->name buffer overflow: %.3o:%.3o"),
dev->bus, dev->dev);
usbFreeDevice(dev);
return NULL;
}
if (virAsprintf(&dev->path, USB_DEVFS "%03d/%03d",
dev->bus, dev->dev) < 0) {
virReportOOMError();
usbFreeDevice(dev);
return NULL;
}
/* XXX fixme. this should be product/vendor */
if (snprintf(dev->id, sizeof(dev->id), "%d %d", dev->bus,
dev->dev) >= sizeof(dev->id)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("dev->id buffer overflow: %d %d"),
dev->bus, dev->dev);
usbFreeDevice(dev);
return NULL;
}
VIR_DEBUG("%s %s: initialized", dev->id, dev->name);
return dev;
}
void
usbFreeDevice(usbDevice *dev)
{
VIR_DEBUG("%s %s: freeing", dev->id, dev->name);
VIR_FREE(dev->path);
VIR_FREE(dev);
}
void usbDeviceSetUsedBy(usbDevice *dev,
const char *name)
{
dev->used_by = name;
}
const char * usbDeviceGetUsedBy(usbDevice *dev)
{
return dev->used_by;
}
const char *usbDeviceGetName(usbDevice *dev)
{
return dev->name;
}
unsigned int usbDeviceGetBus(usbDevice *dev)
{
return dev->bus;
}
unsigned int usbDeviceGetDevno(usbDevice *dev)
{
return dev->dev;
}
int usbDeviceFileIterate(usbDevice *dev,
usbDeviceFileActor actor,
void *opaque)
{
return (actor)(dev, dev->path, opaque);
}
usbDeviceList *
usbDeviceListNew(void)
{
usbDeviceList *list;
if (VIR_ALLOC(list) < 0) {
virReportOOMError();
return NULL;
}
return list;
}
void
usbDeviceListFree(usbDeviceList *list)
{
int i;
if (!list)
return;
for (i = 0; i < list->count; i++)
usbFreeDevice(list->devs[i]);
VIR_FREE(list->devs);
VIR_FREE(list);
}
int
usbDeviceListAdd(usbDeviceList *list,
usbDevice *dev)
{
if (usbDeviceListFind(list, dev)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Device %s is already in use"),
dev->name);
return -1;
}
if (VIR_REALLOC_N(list->devs, list->count+1) < 0) {
virReportOOMError();
return -1;
}
list->devs[list->count++] = dev;
return 0;
}
usbDevice *
usbDeviceListGet(usbDeviceList *list,
int idx)
{
if (idx >= list->count ||
idx < 0)
return NULL;
return list->devs[idx];
}
int
usbDeviceListCount(usbDeviceList *list)
{
return list->count;
}
usbDevice *
usbDeviceListSteal(usbDeviceList *list,
usbDevice *dev)
{
usbDevice *ret = NULL;
int i;
for (i = 0; i < list->count; i++) {
if (list->devs[i]->bus != dev->bus ||
list->devs[i]->dev != dev->dev)
continue;
ret = list->devs[i];
if (i != list->count--)
memmove(&list->devs[i],
&list->devs[i+1],
sizeof(*list->devs) * (list->count - i));
if (VIR_REALLOC_N(list->devs, list->count) < 0) {
; /* not fatal */
}
break;
}
return ret;
}
void
usbDeviceListDel(usbDeviceList *list,
usbDevice *dev)
{
usbDevice *ret = usbDeviceListSteal(list, dev);
if (ret)
usbFreeDevice(ret);
}
usbDevice *
usbDeviceListFind(usbDeviceList *list,
usbDevice *dev)
{
int i;
for (i = 0; i < list->count; i++) {
if (list->devs[i]->bus == dev->bus &&
list->devs[i]->dev == dev->dev)
return list->devs[i];
}
return NULL;
}