blob: 209df9b37cb43f78094af0354fa3e047d0e601c3 [file] [log] [blame]
/*
* Copyright (C) 2005-2007 Takahiro Hirofuchi
*/
#include "usbip_common.h"
#include "vhci_driver.h"
#include <limits.h>
#include <netdb.h>
#undef PROGNAME
#define PROGNAME "libusbip"
struct usbip_vhci_driver *vhci_driver;
static struct usbip_imported_device *
imported_device_init(struct usbip_imported_device *idev, char *busid)
{
struct sysfs_device *sudev;
sudev = sysfs_open_device("usb", busid);
if (!sudev) {
dbg("sysfs_open_device failed: %s", busid);
goto err;
}
read_usb_device(sudev, &idev->udev);
sysfs_close_device(sudev);
/* add class devices of this imported device */
struct usbip_class_device *cdev;
dlist_for_each_data(vhci_driver->cdev_list, cdev,
struct usbip_class_device) {
if (!strncmp(cdev->dev_path, idev->udev.path,
strlen(idev->udev.path))) {
struct usbip_class_device *new_cdev;
/*
* alloc and copy because dlist is linked
* from only one list
*/
new_cdev = calloc(1, sizeof(*new_cdev));
if (!new_cdev)
goto err;
memcpy(new_cdev, cdev, sizeof(*new_cdev));
dlist_unshift(idev->cdev_list, (void *) new_cdev);
}
}
return idev;
err:
return NULL;
}
static int parse_status(char *value)
{
int ret = 0;
char *c;
for (int i = 0; i < vhci_driver->nports; i++)
memset(&vhci_driver->idev[i], 0, sizeof(vhci_driver->idev[i]));
/* skip a header line */
c = strchr(value, '\n');
if (!c)
return -1;
c++;
while (*c != '\0') {
int port, status, speed, devid;
unsigned long socket;
char lbusid[SYSFS_BUS_ID_SIZE];
ret = sscanf(c, "%d %d %d %x %lx %31s\n",
&port, &status, &speed,
&devid, &socket, lbusid);
if (ret < 5) {
dbg("sscanf failed: %d", ret);
BUG();
}
dbg("port %d status %d speed %d devid %x",
port, status, speed, devid);
dbg("socket %lx lbusid %s", socket, lbusid);
/* if a device is connected, look at it */
{
struct usbip_imported_device *idev = &vhci_driver->idev[port];
idev->port = port;
idev->status = status;
idev->devid = devid;
idev->busnum = (devid >> 16);
idev->devnum = (devid & 0x0000ffff);
idev->cdev_list = dlist_new(sizeof(struct usbip_class_device));
if (!idev->cdev_list) {
dbg("dlist_new failed");
return -1;
}
if (idev->status != VDEV_ST_NULL
&& idev->status != VDEV_ST_NOTASSIGNED) {
idev = imported_device_init(idev, lbusid);
if (!idev) {
dbg("imported_device_init failed");
return -1;
}
}
}
/* go to the next line */
c = strchr(c, '\n');
if (!c)
break;
c++;
}
dbg("exit");
return 0;
}
static int check_usbip_device(struct sysfs_class_device *cdev)
{
/* /sys/class/video4linux/video0/device */
char class_path[SYSFS_PATH_MAX];
/* /sys/devices/platform/vhci_hcd/usb6/6-1:1.1 */
char dev_path[SYSFS_PATH_MAX];
int ret;
struct usbip_class_device *usbip_cdev;
snprintf(class_path, sizeof(class_path), "%s/device", cdev->path);
ret = sysfs_get_link(class_path, dev_path, sizeof(dev_path));
if (ret == 0) {
if (!strncmp(dev_path, vhci_driver->hc_device->path,
strlen(vhci_driver->hc_device->path))) {
/* found usbip device */
usbip_cdev = calloc(1, sizeof(*usbip_cdev));
if (!usbip_cdev) {
dbg("calloc failed");
return -1;
}
dlist_unshift(vhci_driver->cdev_list, usbip_cdev);
strncpy(usbip_cdev->class_path, class_path,
sizeof(usbip_cdev->class_path));
strncpy(usbip_cdev->dev_path, dev_path,
sizeof(usbip_cdev->dev_path));
dbg("found: %s %s", class_path, dev_path);
}
}
return 0;
}
static int search_class_for_usbip_device(char *cname)
{
struct sysfs_class *class;
struct dlist *cdev_list;
struct sysfs_class_device *cdev;
int ret = 0;
class = sysfs_open_class(cname);
if (!class) {
dbg("sysfs_open_class failed");
return -1;
}
dbg("class: %s", class->name);
cdev_list = sysfs_get_class_devices(class);
if (!cdev_list)
/* nothing */
goto out;
dlist_for_each_data(cdev_list, cdev, struct sysfs_class_device) {
dbg("cdev: %s", cdev->name);
ret = check_usbip_device(cdev);
if (ret < 0)
goto out;
}
out:
sysfs_close_class(class);
return ret;
}
static int refresh_class_device_list(void)
{
int ret;
struct dlist *cname_list;
char *cname;
char sysfs_mntpath[SYSFS_PATH_MAX];
char class_path[SYSFS_PATH_MAX];
ret = sysfs_get_mnt_path(sysfs_mntpath, SYSFS_PATH_MAX);
if (ret < 0) {
dbg("sysfs_get_mnt_path failed");
return -1;
}
snprintf(class_path, sizeof(class_path), "%s/%s", sysfs_mntpath,
SYSFS_CLASS_NAME);
/* search under /sys/class */
cname_list = sysfs_open_directory_list(class_path);
if (!cname_list) {
dbg("sysfs_open_directory failed");
return -1;
}
dlist_for_each_data(cname_list, cname, char) {
ret = search_class_for_usbip_device(cname);
if (ret < 0) {
sysfs_close_list(cname_list);
return -1;
}
}
sysfs_close_list(cname_list);
/* search under /sys/block */
ret = search_class_for_usbip_device(SYSFS_BLOCK_NAME);
if (ret < 0)
return -1;
return 0;
}
static int refresh_imported_device_list(void)
{
struct sysfs_attribute *attr_status;
attr_status = sysfs_get_device_attr(vhci_driver->hc_device, "status");
if (!attr_status) {
dbg("sysfs_get_device_attr(\"status\") failed: %s",
vhci_driver->hc_device->name);
return -1;
}
dbg("name: %s path: %s len: %d method: %d value: %s",
attr_status->name, attr_status->path, attr_status->len,
attr_status->method, attr_status->value);
return parse_status(attr_status->value);
}
static int get_nports(void)
{
char *c;
int nports = 0;
struct sysfs_attribute *attr_status;
attr_status = sysfs_get_device_attr(vhci_driver->hc_device, "status");
if (!attr_status) {
dbg("sysfs_get_device_attr(\"status\") failed: %s",
vhci_driver->hc_device->name);
return -1;
}
dbg("name: %s path: %s len: %d method: %d value: %s",
attr_status->name, attr_status->path, attr_status->len,
attr_status->method, attr_status->value);
/* skip a header line */
c = strchr(attr_status->value, '\n');
if (!c)
return 0;
c++;
while (*c != '\0') {
/* go to the next line */
c = strchr(c, '\n');
if (!c)
return nports;
c++;
nports += 1;
}
return nports;
}
static int get_hc_busid(char *sysfs_mntpath, char *hc_busid)
{
struct sysfs_driver *sdriver;
char sdriver_path[SYSFS_PATH_MAX];
struct sysfs_device *hc_dev;
struct dlist *hc_devs;
int found = 0;
snprintf(sdriver_path, SYSFS_PATH_MAX, "%s/%s/%s/%s/%s", sysfs_mntpath,
SYSFS_BUS_NAME, USBIP_VHCI_BUS_TYPE, SYSFS_DRIVERS_NAME,
USBIP_VHCI_DRV_NAME);
sdriver = sysfs_open_driver_path(sdriver_path);
if (!sdriver) {
dbg("sysfs_open_driver_path failed: %s", sdriver_path);
dbg("make sure " USBIP_CORE_MOD_NAME ".ko and "
USBIP_VHCI_DRV_NAME ".ko are loaded!");
return -1;
}
hc_devs = sysfs_get_driver_devices(sdriver);
if (!hc_devs) {
dbg("sysfs_get_driver failed");
goto err;
}
/* assume only one vhci_hcd */
dlist_for_each_data(hc_devs, hc_dev, struct sysfs_device) {
strncpy(hc_busid, hc_dev->bus_id, SYSFS_BUS_ID_SIZE);
found = 1;
}
err:
sysfs_close_driver(sdriver);
if (found)
return 0;
dbg("%s not found", hc_busid);
return -1;
}
static int read_record(int rhport, char *host, char *port, char *busid)
{
FILE *file;
char path[PATH_MAX+1];
snprintf(path, PATH_MAX, VHCI_STATE_PATH"/port%d", rhport);
file = fopen(path, "r");
if (!file) {
err("fopen");
return -1;
}
if (fscanf(file, "%s %s %s\n", host, port, busid) != 3) {
err("fscanf");
fclose(file);
return -1;
}
fclose(file);
return 0;
}
/* ---------------------------------------------------------------------- */
int usbip_vhci_driver_open(void)
{
int ret;
char hc_busid[SYSFS_BUS_ID_SIZE];
vhci_driver = (struct usbip_vhci_driver *) calloc(1, sizeof(*vhci_driver));
if (!vhci_driver) {
dbg("calloc failed");
return -1;
}
ret = sysfs_get_mnt_path(vhci_driver->sysfs_mntpath, SYSFS_PATH_MAX);
if (ret < 0) {
dbg("sysfs_get_mnt_path failed");
goto err;
}
ret = get_hc_busid(vhci_driver->sysfs_mntpath, hc_busid);
if (ret < 0)
goto err;
/* will be freed in usbip_driver_close() */
vhci_driver->hc_device = sysfs_open_device(USBIP_VHCI_BUS_TYPE,
hc_busid);
if (!vhci_driver->hc_device) {
dbg("sysfs_open_device failed");
goto err;
}
vhci_driver->nports = get_nports();
dbg("available ports: %d", vhci_driver->nports);
vhci_driver->cdev_list = dlist_new(sizeof(struct usbip_class_device));
if (!vhci_driver->cdev_list)
goto err;
if (refresh_class_device_list())
goto err;
if (refresh_imported_device_list())
goto err;
return 0;
err:
if (vhci_driver->cdev_list)
dlist_destroy(vhci_driver->cdev_list);
if (vhci_driver->hc_device)
sysfs_close_device(vhci_driver->hc_device);
if (vhci_driver)
free(vhci_driver);
vhci_driver = NULL;
return -1;
}
void usbip_vhci_driver_close()
{
if (!vhci_driver)
return;
if (vhci_driver->cdev_list)
dlist_destroy(vhci_driver->cdev_list);
for (int i = 0; i < vhci_driver->nports; i++) {
if (vhci_driver->idev[i].cdev_list)
dlist_destroy(vhci_driver->idev[i].cdev_list);
}
if (vhci_driver->hc_device)
sysfs_close_device(vhci_driver->hc_device);
free(vhci_driver);
vhci_driver = NULL;
}
int usbip_vhci_refresh_device_list(void)
{
if (vhci_driver->cdev_list)
dlist_destroy(vhci_driver->cdev_list);
for (int i = 0; i < vhci_driver->nports; i++) {
if (vhci_driver->idev[i].cdev_list)
dlist_destroy(vhci_driver->idev[i].cdev_list);
}
vhci_driver->cdev_list = dlist_new(sizeof(struct usbip_class_device));
if (!vhci_driver->cdev_list)
goto err;
if (refresh_class_device_list())
goto err;
if (refresh_imported_device_list())
goto err;
return 0;
err:
if (vhci_driver->cdev_list)
dlist_destroy(vhci_driver->cdev_list);
for (int i = 0; i < vhci_driver->nports; i++) {
if (vhci_driver->idev[i].cdev_list)
dlist_destroy(vhci_driver->idev[i].cdev_list);
}
dbg("failed to refresh device list");
return -1;
}
int usbip_vhci_get_free_port(void)
{
for (int i = 0; i < vhci_driver->nports; i++) {
if (vhci_driver->idev[i].status == VDEV_ST_NULL)
return i;
}
return -1;
}
int usbip_vhci_attach_device2(uint8_t port, int sockfd, uint32_t devid,
uint32_t speed) {
struct sysfs_attribute *attr_attach;
char buff[200]; /* what size should be ? */
int ret;
attr_attach = sysfs_get_device_attr(vhci_driver->hc_device, "attach");
if (!attr_attach) {
dbg("sysfs_get_device_attr(\"attach\") failed: %s",
vhci_driver->hc_device->name);
return -1;
}
snprintf(buff, sizeof(buff), "%u %u %u %u",
port, sockfd, devid, speed);
dbg("writing: %s", buff);
ret = sysfs_write_attribute(attr_attach, buff, strlen(buff));
if (ret < 0) {
dbg("sysfs_write_attribute failed");
return -1;
}
dbg("attached port: %d", port);
return 0;
}
static unsigned long get_devid(uint8_t busnum, uint8_t devnum)
{
return (busnum << 16) | devnum;
}
/* will be removed */
int usbip_vhci_attach_device(uint8_t port, int sockfd, uint8_t busnum,
uint8_t devnum, uint32_t speed)
{
int devid = get_devid(busnum, devnum);
return usbip_vhci_attach_device2(port, sockfd, devid, speed);
}
int usbip_vhci_detach_device(uint8_t port)
{
struct sysfs_attribute *attr_detach;
char buff[200]; /* what size should be ? */
int ret;
attr_detach = sysfs_get_device_attr(vhci_driver->hc_device, "detach");
if (!attr_detach) {
dbg("sysfs_get_device_attr(\"detach\") failed: %s",
vhci_driver->hc_device->name);
return -1;
}
snprintf(buff, sizeof(buff), "%u", port);
dbg("writing: %s", buff);
ret = sysfs_write_attribute(attr_detach, buff, strlen(buff));
if (ret < 0) {
dbg("sysfs_write_attribute failed");
return -1;
}
dbg("detached port: %d", port);
return 0;
}
int usbip_vhci_imported_device_dump(struct usbip_imported_device *idev)
{
char product_name[100];
char host[NI_MAXHOST] = "unknown host";
char serv[NI_MAXSERV] = "unknown port";
char remote_busid[SYSFS_BUS_ID_SIZE];
int ret;
int read_record_error = 0;
if (idev->status == VDEV_ST_NULL || idev->status == VDEV_ST_NOTASSIGNED)
return 0;
ret = read_record(idev->port, host, serv, remote_busid);
if (ret) {
err("read_record");
read_record_error = 1;
}
printf("Port %02d: <%s> at %s\n", idev->port,
usbip_status_string(idev->status),
usbip_speed_string(idev->udev.speed));
usbip_names_get_product(product_name, sizeof(product_name),
idev->udev.idVendor, idev->udev.idProduct);
printf(" %s\n", product_name);
if (!read_record_error) {
printf("%10s -> usbip://%s:%s/%s\n", idev->udev.busid,
host, serv, remote_busid);
printf("%10s -> remote bus/dev %03d/%03d\n", " ",
idev->busnum, idev->devnum);
} else {
printf("%10s -> unknown host, remote port and remote busid\n",
idev->udev.busid);
printf("%10s -> remote bus/dev %03d/%03d\n", " ",
idev->busnum, idev->devnum);
}
return 0;
}