| /* |
| * Copyright (C) 2005-2007 Takahiro Hirofuchi |
| */ |
| |
| #include "usbip_common.h" |
| #include "vhci_driver.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 %s\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); |
| |
| /* seach 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; |
| } |
| |
| |
| /* ---------------------------------------------------------------------- */ |
| |
| 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; |
| } |