| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> | |
| * 2005-2007 Takahiro Hirofuchi | |
| * Copyright (C) 2015-2016 Samsung Electronics | |
| * Igor Kotrasinski <i.kotrasinsk@samsung.com> | |
| * Krzysztof Opasiak <k.opasiak@samsung.com> | |
| */ | |
| #include <sys/types.h> | |
| #include <libudev.h> | |
| #include <errno.h> | |
| #include <stdbool.h> | |
| #include <stdint.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <getopt.h> | |
| #include <netdb.h> | |
| #include <unistd.h> | |
| #include <dirent.h> | |
| #include <linux/usb/ch9.h> | |
| #include "usbip_common.h" | |
| #include "usbip_network.h" | |
| #include "usbip.h" | |
| static const char usbip_list_usage_string[] = | |
| "usbip list [-p|--parsable] <args>\n" | |
| " -p, --parsable Parsable list format\n" | |
| " -r, --remote=<host> List the exportable USB devices on <host>\n" | |
| " -l, --local List the local USB devices\n"; | |
| void usbip_list_usage(void) | |
| { | |
| printf("usage: %s", usbip_list_usage_string); | |
| } | |
| static int get_exported_devices(char *host, int sockfd) | |
| { | |
| char product_name[100]; | |
| char class_name[100]; | |
| struct op_devlist_reply reply; | |
| uint16_t code = OP_REP_DEVLIST; | |
| struct usbip_usb_device udev; | |
| struct usbip_usb_interface uintf; | |
| unsigned int i; | |
| int rc, j; | |
| int status; | |
| rc = usbip_net_send_op_common(sockfd, OP_REQ_DEVLIST, 0); | |
| if (rc < 0) { | |
| dbg("usbip_net_send_op_common failed"); | |
| return -1; | |
| } | |
| rc = usbip_net_recv_op_common(sockfd, &code, &status); | |
| if (rc < 0) { | |
| err("Exported Device List Request failed - %s\n", | |
| usbip_op_common_status_string(status)); | |
| return -1; | |
| } | |
| memset(&reply, 0, sizeof(reply)); | |
| rc = usbip_net_recv(sockfd, &reply, sizeof(reply)); | |
| if (rc < 0) { | |
| dbg("usbip_net_recv_op_devlist failed"); | |
| return -1; | |
| } | |
| PACK_OP_DEVLIST_REPLY(0, &reply); | |
| dbg("exportable devices: %d\n", reply.ndev); | |
| if (reply.ndev == 0) { | |
| info("no exportable devices found on %s", host); | |
| return 0; | |
| } | |
| printf("Exportable USB devices\n"); | |
| printf("======================\n"); | |
| printf(" - %s\n", host); | |
| for (i = 0; i < reply.ndev; i++) { | |
| memset(&udev, 0, sizeof(udev)); | |
| rc = usbip_net_recv(sockfd, &udev, sizeof(udev)); | |
| if (rc < 0) { | |
| dbg("usbip_net_recv failed: usbip_usb_device[%d]", i); | |
| return -1; | |
| } | |
| usbip_net_pack_usb_device(0, &udev); | |
| usbip_names_get_product(product_name, sizeof(product_name), | |
| udev.idVendor, udev.idProduct); | |
| usbip_names_get_class(class_name, sizeof(class_name), | |
| udev.bDeviceClass, udev.bDeviceSubClass, | |
| udev.bDeviceProtocol); | |
| printf("%11s: %s\n", udev.busid, product_name); | |
| printf("%11s: %s\n", "", udev.path); | |
| printf("%11s: %s\n", "", class_name); | |
| for (j = 0; j < udev.bNumInterfaces; j++) { | |
| rc = usbip_net_recv(sockfd, &uintf, sizeof(uintf)); | |
| if (rc < 0) { | |
| err("usbip_net_recv failed: usbip_usb_intf[%d]", | |
| j); | |
| return -1; | |
| } | |
| usbip_net_pack_usb_interface(0, &uintf); | |
| usbip_names_get_class(class_name, sizeof(class_name), | |
| uintf.bInterfaceClass, | |
| uintf.bInterfaceSubClass, | |
| uintf.bInterfaceProtocol); | |
| printf("%11s: %2d - %s\n", "", j, class_name); | |
| } | |
| printf("\n"); | |
| } | |
| return 0; | |
| } | |
| static int list_exported_devices(char *host) | |
| { | |
| int rc; | |
| int sockfd; | |
| sockfd = usbip_net_tcp_connect(host, usbip_port_string); | |
| if (sockfd < 0) { | |
| err("could not connect to %s:%s: %s", host, | |
| usbip_port_string, gai_strerror(sockfd)); | |
| return -1; | |
| } | |
| dbg("connected to %s:%s", host, usbip_port_string); | |
| rc = get_exported_devices(host, sockfd); | |
| if (rc < 0) { | |
| err("failed to get device list from %s", host); | |
| return -1; | |
| } | |
| close(sockfd); | |
| return 0; | |
| } | |
| static void print_device(const char *busid, const char *vendor, | |
| const char *product, bool parsable) | |
| { | |
| if (parsable) | |
| printf("busid=%s#usbid=%.4s:%.4s#", busid, vendor, product); | |
| else | |
| printf(" - busid %s (%.4s:%.4s)\n", busid, vendor, product); | |
| } | |
| static void print_product_name(char *product_name, bool parsable) | |
| { | |
| if (!parsable) | |
| printf(" %s\n", product_name); | |
| } | |
| static int list_devices(bool parsable) | |
| { | |
| struct udev *udev; | |
| struct udev_enumerate *enumerate; | |
| struct udev_list_entry *devices, *dev_list_entry; | |
| struct udev_device *dev; | |
| const char *path; | |
| const char *idVendor; | |
| const char *idProduct; | |
| const char *bConfValue; | |
| const char *bNumIntfs; | |
| const char *busid; | |
| char product_name[128]; | |
| int ret = -1; | |
| const char *devpath; | |
| /* Create libudev context. */ | |
| udev = udev_new(); | |
| /* Create libudev device enumeration. */ | |
| enumerate = udev_enumerate_new(udev); | |
| /* Take only USB devices that are not hubs and do not have | |
| * the bInterfaceNumber attribute, i.e. are not interfaces. | |
| */ | |
| udev_enumerate_add_match_subsystem(enumerate, "usb"); | |
| udev_enumerate_add_nomatch_sysattr(enumerate, "bDeviceClass", "09"); | |
| udev_enumerate_add_nomatch_sysattr(enumerate, "bInterfaceNumber", NULL); | |
| udev_enumerate_scan_devices(enumerate); | |
| devices = udev_enumerate_get_list_entry(enumerate); | |
| /* Show information about each device. */ | |
| udev_list_entry_foreach(dev_list_entry, devices) { | |
| path = udev_list_entry_get_name(dev_list_entry); | |
| dev = udev_device_new_from_syspath(udev, path); | |
| /* Ignore devices attached to vhci_hcd */ | |
| devpath = udev_device_get_devpath(dev); | |
| if (strstr(devpath, USBIP_VHCI_DRV_NAME)) { | |
| dbg("Skip the device %s already attached to %s\n", | |
| devpath, USBIP_VHCI_DRV_NAME); | |
| continue; | |
| } | |
| /* Get device information. */ | |
| idVendor = udev_device_get_sysattr_value(dev, "idVendor"); | |
| idProduct = udev_device_get_sysattr_value(dev, "idProduct"); | |
| bConfValue = udev_device_get_sysattr_value(dev, | |
| "bConfigurationValue"); | |
| bNumIntfs = udev_device_get_sysattr_value(dev, | |
| "bNumInterfaces"); | |
| busid = udev_device_get_sysname(dev); | |
| if (!idVendor || !idProduct || !bConfValue || !bNumIntfs) { | |
| err("problem getting device attributes: %s", | |
| strerror(errno)); | |
| goto err_out; | |
| } | |
| /* Get product name. */ | |
| usbip_names_get_product(product_name, sizeof(product_name), | |
| strtol(idVendor, NULL, 16), | |
| strtol(idProduct, NULL, 16)); | |
| /* Print information. */ | |
| print_device(busid, idVendor, idProduct, parsable); | |
| print_product_name(product_name, parsable); | |
| printf("\n"); | |
| udev_device_unref(dev); | |
| } | |
| ret = 0; | |
| err_out: | |
| udev_enumerate_unref(enumerate); | |
| udev_unref(udev); | |
| return ret; | |
| } | |
| static int list_gadget_devices(bool parsable) | |
| { | |
| int ret = -1; | |
| struct udev *udev; | |
| struct udev_enumerate *enumerate; | |
| struct udev_list_entry *devices, *dev_list_entry; | |
| struct udev_device *dev; | |
| const char *path; | |
| const char *driver; | |
| const struct usb_device_descriptor *d_desc; | |
| const char *descriptors; | |
| char product_name[128]; | |
| uint16_t idVendor; | |
| char idVendor_buf[8]; | |
| uint16_t idProduct; | |
| char idProduct_buf[8]; | |
| const char *busid; | |
| udev = udev_new(); | |
| enumerate = udev_enumerate_new(udev); | |
| udev_enumerate_add_match_subsystem(enumerate, "platform"); | |
| udev_enumerate_scan_devices(enumerate); | |
| devices = udev_enumerate_get_list_entry(enumerate); | |
| udev_list_entry_foreach(dev_list_entry, devices) { | |
| path = udev_list_entry_get_name(dev_list_entry); | |
| dev = udev_device_new_from_syspath(udev, path); | |
| driver = udev_device_get_driver(dev); | |
| /* We only have mechanism to enumerate gadgets bound to vudc */ | |
| if (driver == NULL || strcmp(driver, USBIP_DEVICE_DRV_NAME)) | |
| continue; | |
| /* Get device information. */ | |
| descriptors = udev_device_get_sysattr_value(dev, | |
| VUDC_DEVICE_DESCR_FILE); | |
| if (!descriptors) { | |
| err("problem getting device attributes: %s", | |
| strerror(errno)); | |
| goto err_out; | |
| } | |
| d_desc = (const struct usb_device_descriptor *) descriptors; | |
| idVendor = le16toh(d_desc->idVendor); | |
| sprintf(idVendor_buf, "0x%4x", idVendor); | |
| idProduct = le16toh(d_desc->idProduct); | |
| sprintf(idProduct_buf, "0x%4x", idVendor); | |
| busid = udev_device_get_sysname(dev); | |
| /* Get product name. */ | |
| usbip_names_get_product(product_name, sizeof(product_name), | |
| le16toh(idVendor), | |
| le16toh(idProduct)); | |
| /* Print information. */ | |
| print_device(busid, idVendor_buf, idProduct_buf, parsable); | |
| print_product_name(product_name, parsable); | |
| printf("\n"); | |
| udev_device_unref(dev); | |
| } | |
| ret = 0; | |
| err_out: | |
| udev_enumerate_unref(enumerate); | |
| udev_unref(udev); | |
| return ret; | |
| } | |
| int usbip_list(int argc, char *argv[]) | |
| { | |
| static const struct option opts[] = { | |
| { "parsable", no_argument, NULL, 'p' }, | |
| { "remote", required_argument, NULL, 'r' }, | |
| { "local", no_argument, NULL, 'l' }, | |
| { "device", no_argument, NULL, 'd' }, | |
| { NULL, 0, NULL, 0 } | |
| }; | |
| bool parsable = false; | |
| int opt; | |
| int ret = -1; | |
| if (usbip_names_init(USBIDS_FILE)) | |
| err("failed to open %s", USBIDS_FILE); | |
| for (;;) { | |
| opt = getopt_long(argc, argv, "pr:ld", opts, NULL); | |
| if (opt == -1) | |
| break; | |
| switch (opt) { | |
| case 'p': | |
| parsable = true; | |
| break; | |
| case 'r': | |
| ret = list_exported_devices(optarg); | |
| goto out; | |
| case 'l': | |
| ret = list_devices(parsable); | |
| goto out; | |
| case 'd': | |
| ret = list_gadget_devices(parsable); | |
| goto out; | |
| default: | |
| goto err_out; | |
| } | |
| } | |
| err_out: | |
| usbip_list_usage(); | |
| out: | |
| usbip_names_free(); | |
| return ret; | |
| } |