diff options
Diffstat (limited to '')
54 files changed, 8969 insertions, 0 deletions
diff --git a/tools/usb/.gitignore b/tools/usb/.gitignore new file mode 100644 index 000000000..fce1ef5a9 --- /dev/null +++ b/tools/usb/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +ffs-test +testusb diff --git a/tools/usb/Build b/tools/usb/Build new file mode 100644 index 000000000..2ad6f9745 --- /dev/null +++ b/tools/usb/Build @@ -0,0 +1,2 @@ +testusb-y += testusb.o +ffs-test-y += ffs-test.o diff --git a/tools/usb/Makefile b/tools/usb/Makefile new file mode 100644 index 000000000..c6235667d --- /dev/null +++ b/tools/usb/Makefile @@ -0,0 +1,51 @@ +# SPDX-License-Identifier: GPL-2.0 +# Makefile for USB tools +include ../scripts/Makefile.include + +bindir ?= /usr/bin + +ifeq ($(srctree),) +srctree := $(patsubst %/,%,$(dir $(CURDIR))) +srctree := $(patsubst %/,%,$(dir $(srctree))) +endif + +# Do not use make's built-in rules +# (this improves performance and avoids hard-to-debug behaviour); +MAKEFLAGS += -r + +override CFLAGS += -O2 -Wall -Wextra -g -D_GNU_SOURCE -I$(OUTPUT)include -I$(srctree)/tools/include +override LDFLAGS += -lpthread + +ALL_TARGETS := testusb ffs-test +ALL_PROGRAMS := $(patsubst %,$(OUTPUT)%,$(ALL_TARGETS)) + +all: $(ALL_PROGRAMS) + +export srctree OUTPUT CC LD CFLAGS +include $(srctree)/tools/build/Makefile.include + +TESTUSB_IN := $(OUTPUT)testusb-in.o +$(TESTUSB_IN): FORCE + $(Q)$(MAKE) $(build)=testusb +$(OUTPUT)testusb: $(TESTUSB_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) + +FFS_TEST_IN := $(OUTPUT)ffs-test-in.o +$(FFS_TEST_IN): FORCE + $(Q)$(MAKE) $(build)=ffs-test +$(OUTPUT)ffs-test: $(FFS_TEST_IN) + $(QUIET_LINK)$(CC) $(CFLAGS) $< -o $@ $(LDFLAGS) + +clean: + rm -f $(ALL_PROGRAMS) + find $(or $(OUTPUT),.) -name '*.o' -delete -o -name '\.*.d' -delete -o -name '\.*.o.cmd' -delete + +install: $(ALL_PROGRAMS) + install -d -m 755 $(DESTDIR)$(bindir); \ + for program in $(ALL_PROGRAMS); do \ + install $$program $(DESTDIR)$(bindir); \ + done + +FORCE: + +.PHONY: all install clean FORCE prepare diff --git a/tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c b/tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c new file mode 100644 index 000000000..aaca1f44e --- /dev/null +++ b/tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c @@ -0,0 +1,380 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to <http://unlicense.org/> + */ + +#define _BSD_SOURCE /* for endian.h */ + +#include <endian.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/poll.h> +#include <unistd.h> +#include <stdbool.h> +#include <sys/eventfd.h> + +#include "libaio.h" +#define IOCB_FLAG_RESFD (1 << 0) + +#include <linux/usb/functionfs.h> + +#define BUF_LEN 8192 +#define BUFS_MAX 128 +#define AIO_MAX (BUFS_MAX*2) + +/******************** Descriptors and Strings *******************************/ + +static const struct { + struct usb_functionfs_descs_head_v2 header; + __le32 fs_count; + __le32 hs_count; + struct { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio bulk_sink; + struct usb_endpoint_descriptor_no_audio bulk_source; + } __attribute__ ((__packed__)) fs_descs, hs_descs; +} __attribute__ ((__packed__)) descriptors = { + .header = { + .magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2), + .flags = htole32(FUNCTIONFS_HAS_FS_DESC | + FUNCTIONFS_HAS_HS_DESC), + .length = htole32(sizeof(descriptors)), + }, + .fs_count = htole32(3), + .fs_descs = { + .intf = { + .bLength = sizeof(descriptors.fs_descs.intf), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = 1, + }, + .bulk_sink = { + .bLength = sizeof(descriptors.fs_descs.bulk_sink), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .bulk_source = { + .bLength = sizeof(descriptors.fs_descs.bulk_source), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + }, + .hs_count = htole32(3), + .hs_descs = { + .intf = { + .bLength = sizeof(descriptors.hs_descs.intf), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = 1, + }, + .bulk_sink = { + .bLength = sizeof(descriptors.hs_descs.bulk_sink), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = htole16(512), + }, + .bulk_source = { + .bLength = sizeof(descriptors.hs_descs.bulk_source), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = htole16(512), + }, + }, +}; + +#define STR_INTERFACE "AIO Test" + +static const struct { + struct usb_functionfs_strings_head header; + struct { + __le16 code; + const char str1[sizeof(STR_INTERFACE)]; + } __attribute__ ((__packed__)) lang0; +} __attribute__ ((__packed__)) strings = { + .header = { + .magic = htole32(FUNCTIONFS_STRINGS_MAGIC), + .length = htole32(sizeof(strings)), + .str_count = htole32(1), + .lang_count = htole32(1), + }, + .lang0 = { + htole16(0x0409), /* en-us */ + STR_INTERFACE, + }, +}; + +/********************** Buffer structure *******************************/ + +struct io_buffer { + struct iocb **iocb; + unsigned char **buf; + unsigned cnt; + unsigned len; + unsigned requested; +}; + +/******************** Endpoints handling *******************************/ + +static void display_event(struct usb_functionfs_event *event) +{ + static const char *const names[] = { + [FUNCTIONFS_BIND] = "BIND", + [FUNCTIONFS_UNBIND] = "UNBIND", + [FUNCTIONFS_ENABLE] = "ENABLE", + [FUNCTIONFS_DISABLE] = "DISABLE", + [FUNCTIONFS_SETUP] = "SETUP", + [FUNCTIONFS_SUSPEND] = "SUSPEND", + [FUNCTIONFS_RESUME] = "RESUME", + }; + switch (event->type) { + case FUNCTIONFS_BIND: + case FUNCTIONFS_UNBIND: + case FUNCTIONFS_ENABLE: + case FUNCTIONFS_DISABLE: + case FUNCTIONFS_SETUP: + case FUNCTIONFS_SUSPEND: + case FUNCTIONFS_RESUME: + printf("Event %s\n", names[event->type]); + } +} + +static void handle_ep0(int ep0, bool *ready) +{ + int ret; + struct usb_functionfs_event event; + + ret = read(ep0, &event, sizeof(event)); + if (!ret) { + perror("unable to read event from ep0"); + return; + } + display_event(&event); + switch (event.type) { + case FUNCTIONFS_SETUP: + if (event.u.setup.bRequestType & USB_DIR_IN) + write(ep0, NULL, 0); + else + read(ep0, NULL, 0); + break; + + case FUNCTIONFS_ENABLE: + *ready = true; + break; + + case FUNCTIONFS_DISABLE: + *ready = false; + break; + + default: + break; + } +} + +void init_bufs(struct io_buffer *iobuf, unsigned n, unsigned len) +{ + unsigned i; + iobuf->buf = malloc(n*sizeof(*iobuf->buf)); + iobuf->iocb = malloc(n*sizeof(*iobuf->iocb)); + iobuf->cnt = n; + iobuf->len = len; + iobuf->requested = 0; + for (i = 0; i < n; ++i) { + iobuf->buf[i] = malloc(len*sizeof(**iobuf->buf)); + iobuf->iocb[i] = malloc(sizeof(**iobuf->iocb)); + } + iobuf->cnt = n; +} + +void delete_bufs(struct io_buffer *iobuf) +{ + unsigned i; + for (i = 0; i < iobuf->cnt; ++i) { + free(iobuf->buf[i]); + free(iobuf->iocb[i]); + } + free(iobuf->buf); + free(iobuf->iocb); +} + +int main(int argc, char *argv[]) +{ + int ret; + unsigned i, j; + char *ep_path; + + int ep0, ep1; + + io_context_t ctx; + + int evfd; + fd_set rfds; + + struct io_buffer iobuf[2]; + int actual = 0; + bool ready; + + if (argc != 2) { + printf("ffs directory not specified!\n"); + return 1; + } + + ep_path = malloc(strlen(argv[1]) + 4 /* "/ep#" */ + 1 /* '\0' */); + if (!ep_path) { + perror("malloc"); + return 1; + } + + /* open endpoint files */ + sprintf(ep_path, "%s/ep0", argv[1]); + ep0 = open(ep_path, O_RDWR); + if (ep0 < 0) { + perror("unable to open ep0"); + return 1; + } + if (write(ep0, &descriptors, sizeof(descriptors)) < 0) { + perror("unable do write descriptors"); + return 1; + } + if (write(ep0, &strings, sizeof(strings)) < 0) { + perror("unable to write strings"); + return 1; + } + sprintf(ep_path, "%s/ep1", argv[1]); + ep1 = open(ep_path, O_RDWR); + if (ep1 < 0) { + perror("unable to open ep1"); + return 1; + } + + free(ep_path); + + memset(&ctx, 0, sizeof(ctx)); + /* setup aio context to handle up to AIO_MAX requests */ + if (io_setup(AIO_MAX, &ctx) < 0) { + perror("unable to setup aio"); + return 1; + } + + evfd = eventfd(0, 0); + if (evfd < 0) { + perror("unable to open eventfd"); + return 1; + } + + for (i = 0; i < sizeof(iobuf)/sizeof(*iobuf); ++i) + init_bufs(&iobuf[i], BUFS_MAX, BUF_LEN); + + while (1) { + FD_ZERO(&rfds); + FD_SET(ep0, &rfds); + FD_SET(evfd, &rfds); + + ret = select(((ep0 > evfd) ? ep0 : evfd)+1, + &rfds, NULL, NULL, NULL); + if (ret < 0) { + if (errno == EINTR) + continue; + perror("select"); + break; + } + + if (FD_ISSET(ep0, &rfds)) + handle_ep0(ep0, &ready); + + /* we are waiting for function ENABLE */ + if (!ready) + continue; + + /* + * when we're preparing new data to submit, + * second buffer being transmitted + */ + for (i = 0; i < sizeof(iobuf)/sizeof(*iobuf); ++i) { + if (iobuf[i].requested) + continue; + /* prepare requests */ + for (j = 0; j < iobuf[i].cnt; ++j) { + io_prep_pwrite(iobuf[i].iocb[j], ep1, + iobuf[i].buf[j], + iobuf[i].len, 0); + /* enable eventfd notification */ + iobuf[i].iocb[j]->u.c.flags |= IOCB_FLAG_RESFD; + iobuf[i].iocb[j]->u.c.resfd = evfd; + } + /* submit table of requests */ + ret = io_submit(ctx, iobuf[i].cnt, iobuf[i].iocb); + if (ret >= 0) { + iobuf[i].requested = ret; + printf("submit: %d requests buf: %d\n", ret, i); + } else + perror("unable to submit requests"); + } + + /* if event is ready to read */ + if (!FD_ISSET(evfd, &rfds)) + continue; + + uint64_t ev_cnt; + ret = read(evfd, &ev_cnt, sizeof(ev_cnt)); + if (ret < 0) { + perror("unable to read eventfd"); + break; + } + + struct io_event e[BUFS_MAX]; + /* we read aio events */ + ret = io_getevents(ctx, 1, BUFS_MAX, e, NULL); + if (ret > 0) /* if we got events */ + iobuf[actual].requested -= ret; + + /* if all req's from iocb completed */ + if (!iobuf[actual].requested) + actual = (actual + 1)%(sizeof(iobuf)/sizeof(*iobuf)); + } + + /* free resources */ + + for (i = 0; i < sizeof(iobuf)/sizeof(*iobuf); ++i) + delete_bufs(&iobuf[i]); + io_destroy(ctx); + + close(ep1); + close(ep0); + + return 0; +} diff --git a/tools/usb/ffs-aio-example/multibuff/host_app/Makefile b/tools/usb/ffs-aio-example/multibuff/host_app/Makefile new file mode 100644 index 000000000..df36e4c28 --- /dev/null +++ b/tools/usb/ffs-aio-example/multibuff/host_app/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 +CC = gcc +LIBUSB_CFLAGS = $(shell pkg-config --cflags libusb-1.0) +LIBUSB_LIBS = $(shell pkg-config --libs libusb-1.0) +WARNINGS = -Wall -Wextra +CFLAGS = $(LIBUSB_CFLAGS) $(WARNINGS) +LDFLAGS = $(LIBUSB_LIBS) + +all: test +%: %.c + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +clean: + $(RM) test diff --git a/tools/usb/ffs-aio-example/multibuff/host_app/test.c b/tools/usb/ffs-aio-example/multibuff/host_app/test.c new file mode 100644 index 000000000..2cbcce6e8 --- /dev/null +++ b/tools/usb/ffs-aio-example/multibuff/host_app/test.c @@ -0,0 +1,175 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to <http://unlicense.org/> + */ + +#include <libusb.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#define VENDOR 0x1d6b +#define PRODUCT 0x0105 + +#define BUF_LEN 8192 + +/* + * struct test_state - describes test program state + * @list: list of devices returned by libusb_get_device_list function + * @found: pointer to struct describing tested device + * @ctx: context, set to NULL + * @handle: handle of tested device + * @attached: indicates that device was attached to kernel, and has to be + * reattached at the end of test program + */ + +struct test_state { + libusb_device *found; + libusb_context *ctx; + libusb_device_handle *handle; + int attached; +}; + +/* + * test_init - initialize test program + */ + +int test_init(struct test_state *state) +{ + int i, ret; + ssize_t cnt; + libusb_device **list; + + state->found = NULL; + state->ctx = NULL; + state->handle = NULL; + state->attached = 0; + + ret = libusb_init(&state->ctx); + if (ret) { + printf("cannot init libusb: %s\n", libusb_error_name(ret)); + return 1; + } + + cnt = libusb_get_device_list(state->ctx, &list); + if (cnt <= 0) { + printf("no devices found\n"); + goto error1; + } + + for (i = 0; i < cnt; ++i) { + libusb_device *dev = list[i]; + struct libusb_device_descriptor desc; + ret = libusb_get_device_descriptor(dev, &desc); + if (ret) { + printf("unable to get device descriptor: %s\n", + libusb_error_name(ret)); + goto error2; + } + if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT) { + state->found = dev; + break; + } + } + + if (!state->found) { + printf("no devices found\n"); + goto error2; + } + + ret = libusb_open(state->found, &state->handle); + if (ret) { + printf("cannot open device: %s\n", libusb_error_name(ret)); + goto error2; + } + + if (libusb_claim_interface(state->handle, 0)) { + ret = libusb_detach_kernel_driver(state->handle, 0); + if (ret) { + printf("unable to detach kernel driver: %s\n", + libusb_error_name(ret)); + goto error3; + } + state->attached = 1; + ret = libusb_claim_interface(state->handle, 0); + if (ret) { + printf("cannot claim interface: %s\n", + libusb_error_name(ret)); + goto error4; + } + } + + return 0; + +error4: + if (state->attached == 1) + libusb_attach_kernel_driver(state->handle, 0); + +error3: + libusb_close(state->handle); + +error2: + libusb_free_device_list(list, 1); + +error1: + libusb_exit(state->ctx); + return 1; +} + +/* + * test_exit - cleanup test program + */ + +void test_exit(struct test_state *state) +{ + libusb_release_interface(state->handle, 0); + if (state->attached == 1) + libusb_attach_kernel_driver(state->handle, 0); + libusb_close(state->handle); + libusb_exit(state->ctx); +} + +int main(void) +{ + struct test_state state; + struct libusb_config_descriptor *conf; + struct libusb_interface_descriptor const *iface; + unsigned char addr; + + if (test_init(&state)) + return 1; + + libusb_get_config_descriptor(state.found, 0, &conf); + iface = &conf->interface[0].altsetting[0]; + addr = iface->endpoint[0].bEndpointAddress; + + while (1) { + static unsigned char buffer[BUF_LEN]; + int bytes; + libusb_bulk_transfer(state.handle, addr, buffer, BUF_LEN, + &bytes, 500); + } + test_exit(&state); +} diff --git a/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c new file mode 100644 index 000000000..1f44a2981 --- /dev/null +++ b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c @@ -0,0 +1,368 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to <http://unlicense.org/> + */ + +#define _BSD_SOURCE /* for endian.h */ + +#include <endian.h> +#include <errno.h> +#include <fcntl.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <sys/poll.h> +#include <unistd.h> +#include <stdbool.h> +#include <sys/eventfd.h> + +#include "libaio.h" +#define IOCB_FLAG_RESFD (1 << 0) + +#include <linux/usb/functionfs.h> + +#define BUF_LEN 8192 + +/******************** Descriptors and Strings *******************************/ + +static const struct { + struct usb_functionfs_descs_head_v2 header; + __le32 fs_count; + __le32 hs_count; + struct { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio bulk_sink; + struct usb_endpoint_descriptor_no_audio bulk_source; + } __attribute__ ((__packed__)) fs_descs, hs_descs; +} __attribute__ ((__packed__)) descriptors = { + .header = { + .magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2), + .flags = htole32(FUNCTIONFS_HAS_FS_DESC | + FUNCTIONFS_HAS_HS_DESC), + .length = htole32(sizeof(descriptors)), + }, + .fs_count = htole32(3), + .fs_descs = { + .intf = { + .bLength = sizeof(descriptors.fs_descs.intf), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = 1, + }, + .bulk_sink = { + .bLength = sizeof(descriptors.fs_descs.bulk_sink), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .bulk_source = { + .bLength = sizeof(descriptors.fs_descs.bulk_source), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + }, + .hs_count = htole32(3), + .hs_descs = { + .intf = { + .bLength = sizeof(descriptors.hs_descs.intf), + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = 1, + }, + .bulk_sink = { + .bLength = sizeof(descriptors.hs_descs.bulk_sink), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = htole16(512), + }, + .bulk_source = { + .bLength = sizeof(descriptors.hs_descs.bulk_source), + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = htole16(512), + }, + }, +}; + +#define STR_INTERFACE "AIO Test" + +static const struct { + struct usb_functionfs_strings_head header; + struct { + __le16 code; + const char str1[sizeof(STR_INTERFACE)]; + } __attribute__ ((__packed__)) lang0; +} __attribute__ ((__packed__)) strings = { + .header = { + .magic = htole32(FUNCTIONFS_STRINGS_MAGIC), + .length = htole32(sizeof(strings)), + .str_count = htole32(1), + .lang_count = htole32(1), + }, + .lang0 = { + htole16(0x0409), /* en-us */ + STR_INTERFACE, + }, +}; + +/******************** Endpoints handling *******************************/ + +static void display_event(struct usb_functionfs_event *event) +{ + static const char *const names[] = { + [FUNCTIONFS_BIND] = "BIND", + [FUNCTIONFS_UNBIND] = "UNBIND", + [FUNCTIONFS_ENABLE] = "ENABLE", + [FUNCTIONFS_DISABLE] = "DISABLE", + [FUNCTIONFS_SETUP] = "SETUP", + [FUNCTIONFS_SUSPEND] = "SUSPEND", + [FUNCTIONFS_RESUME] = "RESUME", + }; + switch (event->type) { + case FUNCTIONFS_BIND: + case FUNCTIONFS_UNBIND: + case FUNCTIONFS_ENABLE: + case FUNCTIONFS_DISABLE: + case FUNCTIONFS_SETUP: + case FUNCTIONFS_SUSPEND: + case FUNCTIONFS_RESUME: + printf("Event %s\n", names[event->type]); + } +} + +static void handle_ep0(int ep0, bool *ready) +{ + struct usb_functionfs_event event; + int ret; + + struct pollfd pfds[1]; + pfds[0].fd = ep0; + pfds[0].events = POLLIN; + + ret = poll(pfds, 1, 0); + + if (ret && (pfds[0].revents & POLLIN)) { + ret = read(ep0, &event, sizeof(event)); + if (!ret) { + perror("unable to read event from ep0"); + return; + } + display_event(&event); + switch (event.type) { + case FUNCTIONFS_SETUP: + if (event.u.setup.bRequestType & USB_DIR_IN) + write(ep0, NULL, 0); + else + read(ep0, NULL, 0); + break; + + case FUNCTIONFS_ENABLE: + *ready = true; + break; + + case FUNCTIONFS_DISABLE: + *ready = false; + break; + + default: + break; + } + } +} + +int main(int argc, char *argv[]) +{ + int i, ret; + char *ep_path; + + int ep0; + int ep[2]; + + io_context_t ctx; + + int evfd; + fd_set rfds; + + char *buf_in, *buf_out; + struct iocb *iocb_in, *iocb_out; + int req_in = 0, req_out = 0; + bool ready; + + if (argc != 2) { + printf("ffs directory not specified!\n"); + return 1; + } + + ep_path = malloc(strlen(argv[1]) + 4 /* "/ep#" */ + 1 /* '\0' */); + if (!ep_path) { + perror("malloc"); + return 1; + } + + /* open endpoint files */ + sprintf(ep_path, "%s/ep0", argv[1]); + ep0 = open(ep_path, O_RDWR); + if (ep0 < 0) { + perror("unable to open ep0"); + return 1; + } + if (write(ep0, &descriptors, sizeof(descriptors)) < 0) { + perror("unable do write descriptors"); + return 1; + } + if (write(ep0, &strings, sizeof(strings)) < 0) { + perror("unable to write strings"); + return 1; + } + for (i = 0; i < 2; ++i) { + sprintf(ep_path, "%s/ep%d", argv[1], i+1); + ep[i] = open(ep_path, O_RDWR); + if (ep[i] < 0) { + printf("unable to open ep%d: %s\n", i+1, + strerror(errno)); + return 1; + } + } + + free(ep_path); + + memset(&ctx, 0, sizeof(ctx)); + /* setup aio context to handle up to 2 requests */ + if (io_setup(2, &ctx) < 0) { + perror("unable to setup aio"); + return 1; + } + + evfd = eventfd(0, 0); + if (evfd < 0) { + perror("unable to open eventfd"); + return 1; + } + + /* alloc buffers and requests */ + buf_in = malloc(BUF_LEN); + buf_out = malloc(BUF_LEN); + iocb_in = malloc(sizeof(*iocb_in)); + iocb_out = malloc(sizeof(*iocb_out)); + + while (1) { + FD_ZERO(&rfds); + FD_SET(ep0, &rfds); + FD_SET(evfd, &rfds); + + ret = select(((ep0 > evfd) ? ep0 : evfd)+1, + &rfds, NULL, NULL, NULL); + if (ret < 0) { + if (errno == EINTR) + continue; + perror("select"); + break; + } + + if (FD_ISSET(ep0, &rfds)) + handle_ep0(ep0, &ready); + + /* we are waiting for function ENABLE */ + if (!ready) + continue; + + /* if something was submitted we wait for event */ + if (FD_ISSET(evfd, &rfds)) { + uint64_t ev_cnt; + ret = read(evfd, &ev_cnt, sizeof(ev_cnt)); + if (ret < 0) { + perror("unable to read eventfd"); + break; + } + + struct io_event e[2]; + /* we wait for one event */ + ret = io_getevents(ctx, 1, 2, e, NULL); + /* if we got event */ + for (i = 0; i < ret; ++i) { + if (e[i].obj->aio_fildes == ep[0]) { + printf("ev=in; ret=%lu\n", e[i].res); + req_in = 0; + } else if (e[i].obj->aio_fildes == ep[1]) { + printf("ev=out; ret=%lu\n", e[i].res); + req_out = 0; + } + } + } + + if (!req_in) { /* if IN transfer not requested*/ + /* prepare write request */ + io_prep_pwrite(iocb_in, ep[0], buf_in, BUF_LEN, 0); + /* enable eventfd notification */ + iocb_in->u.c.flags |= IOCB_FLAG_RESFD; + iocb_in->u.c.resfd = evfd; + /* submit table of requests */ + ret = io_submit(ctx, 1, &iocb_in); + if (ret >= 0) { /* if ret > 0 request is queued */ + req_in = 1; + printf("submit: in\n"); + } else + perror("unable to submit request"); + } + if (!req_out) { /* if OUT transfer not requested */ + /* prepare read request */ + io_prep_pread(iocb_out, ep[1], buf_out, BUF_LEN, 0); + /* enable eventfs notification */ + iocb_out->u.c.flags |= IOCB_FLAG_RESFD; + iocb_out->u.c.resfd = evfd; + /* submit table of requests */ + ret = io_submit(ctx, 1, &iocb_out); + if (ret >= 0) { /* if ret > 0 request is queued */ + req_out = 1; + printf("submit: out\n"); + } else + perror("unable to submit request"); + } + } + + /* free resources */ + + io_destroy(ctx); + + free(buf_in); + free(buf_out); + free(iocb_in); + free(iocb_out); + + for (i = 0; i < 2; ++i) + close(ep[i]); + close(ep0); + + return 0; +} diff --git a/tools/usb/ffs-aio-example/simple/host_app/Makefile b/tools/usb/ffs-aio-example/simple/host_app/Makefile new file mode 100644 index 000000000..c3523837c --- /dev/null +++ b/tools/usb/ffs-aio-example/simple/host_app/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0-only +CC = gcc +LIBUSB_CFLAGS = $(shell pkg-config --cflags libusb-1.0) +LIBUSB_LIBS = $(shell pkg-config --libs libusb-1.0) +WARNINGS = -Wall -Wextra +CFLAGS = $(LIBUSB_CFLAGS) $(WARNINGS) +LDFLAGS = $(LIBUSB_LIBS) + +all: test +%: %.c + $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS) + +clean: + $(RM) test diff --git a/tools/usb/ffs-aio-example/simple/host_app/test.c b/tools/usb/ffs-aio-example/simple/host_app/test.c new file mode 100644 index 000000000..aed86ffff --- /dev/null +++ b/tools/usb/ffs-aio-example/simple/host_app/test.c @@ -0,0 +1,178 @@ +/* + * This is free and unencumbered software released into the public domain. + * + * Anyone is free to copy, modify, publish, use, compile, sell, or + * distribute this software, either in source code form or as a compiled + * binary, for any purpose, commercial or non-commercial, and by any + * means. + * + * In jurisdictions that recognize copyright laws, the author or authors + * of this software dedicate any and all copyright interest in the + * software to the public domain. We make this dedication for the benefit + * of the public at large and to the detriment of our heirs and + * successors. We intend this dedication to be an overt act of + * relinquishment in perpetuity of all present and future rights to this + * software under copyright law. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + * OTHER DEALINGS IN THE SOFTWARE. + * + * For more information, please refer to <http://unlicense.org/> + */ + +#include <libusb.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#define VENDOR 0x1d6b +#define PRODUCT 0x0105 + +#define BUF_LEN 8192 + +/* + * struct test_state - describes test program state + * @list: list of devices returned by libusb_get_device_list function + * @found: pointer to struct describing tested device + * @ctx: context, set to NULL + * @handle: handle of tested device + * @attached: indicates that device was attached to kernel, and has to be + * reattached at the end of test program + */ + +struct test_state { + libusb_device *found; + libusb_context *ctx; + libusb_device_handle *handle; + int attached; +}; + +/* + * test_init - initialize test program + */ + +int test_init(struct test_state *state) +{ + int i, ret; + ssize_t cnt; + libusb_device **list; + + state->found = NULL; + state->ctx = NULL; + state->handle = NULL; + state->attached = 0; + + ret = libusb_init(&state->ctx); + if (ret) { + printf("cannot init libusb: %s\n", libusb_error_name(ret)); + return 1; + } + + cnt = libusb_get_device_list(state->ctx, &list); + if (cnt <= 0) { + printf("no devices found\n"); + goto error1; + } + + for (i = 0; i < cnt; ++i) { + libusb_device *dev = list[i]; + struct libusb_device_descriptor desc; + ret = libusb_get_device_descriptor(dev, &desc); + if (ret) { + printf("unable to get device descriptor: %s\n", + libusb_error_name(ret)); + goto error2; + } + if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT) { + state->found = dev; + break; + } + } + + if (!state->found) { + printf("no devices found\n"); + goto error2; + } + + ret = libusb_open(state->found, &state->handle); + if (ret) { + printf("cannot open device: %s\n", libusb_error_name(ret)); + goto error2; + } + + if (libusb_claim_interface(state->handle, 0)) { + ret = libusb_detach_kernel_driver(state->handle, 0); + if (ret) { + printf("unable to detach kernel driver: %s\n", + libusb_error_name(ret)); + goto error3; + } + state->attached = 1; + ret = libusb_claim_interface(state->handle, 0); + if (ret) { + printf("cannot claim interface: %s\n", + libusb_error_name(ret)); + goto error4; + } + } + + return 0; + +error4: + if (state->attached == 1) + libusb_attach_kernel_driver(state->handle, 0); + +error3: + libusb_close(state->handle); + +error2: + libusb_free_device_list(list, 1); + +error1: + libusb_exit(state->ctx); + return 1; +} + +/* + * test_exit - cleanup test program + */ + +void test_exit(struct test_state *state) +{ + libusb_release_interface(state->handle, 0); + if (state->attached == 1) + libusb_attach_kernel_driver(state->handle, 0); + libusb_close(state->handle); + libusb_exit(state->ctx); +} + +int main(void) +{ + struct test_state state; + struct libusb_config_descriptor *conf; + struct libusb_interface_descriptor const *iface; + unsigned char in_addr, out_addr; + + if (test_init(&state)) + return 1; + + libusb_get_config_descriptor(state.found, 0, &conf); + iface = &conf->interface[0].altsetting[0]; + in_addr = iface->endpoint[0].bEndpointAddress; + out_addr = iface->endpoint[1].bEndpointAddress; + + while (1) { + static unsigned char buffer[BUF_LEN]; + int bytes; + libusb_bulk_transfer(state.handle, in_addr, buffer, BUF_LEN, + &bytes, 500); + libusb_bulk_transfer(state.handle, out_addr, buffer, BUF_LEN, + &bytes, 500); + } + test_exit(&state); +} diff --git a/tools/usb/ffs-test.c b/tools/usb/ffs-test.c new file mode 100644 index 000000000..22b938fbd --- /dev/null +++ b/tools/usb/ffs-test.c @@ -0,0 +1,681 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ffs-test.c -- user mode filesystem api for usb composite function + * + * Copyright (C) 2010 Samsung Electronics + * Author: Michal Nazarewicz <mina86@mina86.com> + */ + +/* $(CROSS_COMPILE)cc -Wall -Wextra -g -o ffs-test ffs-test.c -lpthread */ + + +#define _DEFAULT_SOURCE /* for endian.h */ + +#include <endian.h> +#include <errno.h> +#include <fcntl.h> +#include <pthread.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <tools/le_byteshift.h> + +#include "../../include/uapi/linux/usb/functionfs.h" + + +/******************** Little Endian Handling ********************************/ + +/* + * cpu_to_le16/32 are used when initializing structures, a context where a + * function call is not allowed. To solve this, we code cpu_to_le16/32 in a way + * that allows them to be used when initializing structures. + */ + +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define cpu_to_le16(x) (x) +#define cpu_to_le32(x) (x) +#else +#define cpu_to_le16(x) ((((x) >> 8) & 0xffu) | (((x) & 0xffu) << 8)) +#define cpu_to_le32(x) \ + ((((x) & 0xff000000u) >> 24) | (((x) & 0x00ff0000u) >> 8) | \ + (((x) & 0x0000ff00u) << 8) | (((x) & 0x000000ffu) << 24)) +#endif + +#define le32_to_cpu(x) le32toh(x) +#define le16_to_cpu(x) le16toh(x) + +/******************** Messages and Errors ***********************************/ + +static const char argv0[] = "ffs-test"; + +static unsigned verbosity = 7; + +static void _msg(unsigned level, const char *fmt, ...) +{ + if (level < 2) + level = 2; + else if (level > 7) + level = 7; + + if (level <= verbosity) { + static const char levels[8][6] = { + [2] = "crit:", + [3] = "err: ", + [4] = "warn:", + [5] = "note:", + [6] = "info:", + [7] = "dbg: " + }; + + int _errno = errno; + va_list ap; + + fprintf(stderr, "%s: %s ", argv0, levels[level]); + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + if (fmt[strlen(fmt) - 1] != '\n') { + char buffer[128]; + strerror_r(_errno, buffer, sizeof buffer); + fprintf(stderr, ": (-%d) %s\n", _errno, buffer); + } + + fflush(stderr); + } +} + +#define die(...) (_msg(2, __VA_ARGS__), exit(1)) +#define err(...) _msg(3, __VA_ARGS__) +#define warn(...) _msg(4, __VA_ARGS__) +#define note(...) _msg(5, __VA_ARGS__) +#define info(...) _msg(6, __VA_ARGS__) +#define debug(...) _msg(7, __VA_ARGS__) + +#define die_on(cond, ...) do { \ + if (cond) \ + die(__VA_ARGS__); \ + } while (0) + + +/******************** Descriptors and Strings *******************************/ + +static const struct { + struct usb_functionfs_descs_head_v2 header; + __le32 fs_count; + __le32 hs_count; + __le32 ss_count; + struct { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio sink; + struct usb_endpoint_descriptor_no_audio source; + } __attribute__((packed)) fs_descs, hs_descs; + struct { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio sink; + struct usb_ss_ep_comp_descriptor sink_comp; + struct usb_endpoint_descriptor_no_audio source; + struct usb_ss_ep_comp_descriptor source_comp; + } ss_descs; +} __attribute__((packed)) descriptors = { + .header = { + .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2), + .flags = cpu_to_le32(FUNCTIONFS_HAS_FS_DESC | + FUNCTIONFS_HAS_HS_DESC | + FUNCTIONFS_HAS_SS_DESC), + .length = cpu_to_le32(sizeof descriptors), + }, + .fs_count = cpu_to_le32(3), + .fs_descs = { + .intf = { + .bLength = sizeof descriptors.fs_descs.intf, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = 1, + }, + .sink = { + .bLength = sizeof descriptors.fs_descs.sink, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* .wMaxPacketSize = autoconfiguration (kernel) */ + }, + .source = { + .bLength = sizeof descriptors.fs_descs.source, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + /* .wMaxPacketSize = autoconfiguration (kernel) */ + }, + }, + .hs_count = cpu_to_le32(3), + .hs_descs = { + .intf = { + .bLength = sizeof descriptors.fs_descs.intf, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = 1, + }, + .sink = { + .bLength = sizeof descriptors.hs_descs.sink, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), + }, + .source = { + .bLength = sizeof descriptors.hs_descs.source, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(512), + .bInterval = 1, /* NAK every 1 uframe */ + }, + }, + .ss_count = cpu_to_le32(5), + .ss_descs = { + .intf = { + .bLength = sizeof descriptors.fs_descs.intf, + .bDescriptorType = USB_DT_INTERFACE, + .bNumEndpoints = 2, + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, + .iInterface = 1, + }, + .sink = { + .bLength = sizeof descriptors.hs_descs.sink, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), + }, + .sink_comp = { + .bLength = USB_DT_SS_EP_COMP_SIZE, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = 0, + }, + .source = { + .bLength = sizeof descriptors.hs_descs.source, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = cpu_to_le16(1024), + .bInterval = 1, /* NAK every 1 uframe */ + }, + .source_comp = { + .bLength = USB_DT_SS_EP_COMP_SIZE, + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 0, + .bmAttributes = 0, + .wBytesPerInterval = 0, + }, + }, +}; + +static size_t descs_to_legacy(void **legacy, const void *descriptors_v2) +{ + const unsigned char *descs_end, *descs_start; + __u32 length, fs_count = 0, hs_count = 0, count; + + /* Read v2 header */ + { + const struct { + const struct usb_functionfs_descs_head_v2 header; + const __le32 counts[]; + } __attribute__((packed)) *const in = descriptors_v2; + const __le32 *counts = in->counts; + __u32 flags; + + if (le32_to_cpu(in->header.magic) != + FUNCTIONFS_DESCRIPTORS_MAGIC_V2) + return 0; + length = le32_to_cpu(in->header.length); + if (length <= sizeof in->header) + return 0; + length -= sizeof in->header; + flags = le32_to_cpu(in->header.flags); + if (flags & ~(FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC | + FUNCTIONFS_HAS_SS_DESC)) + return 0; + +#define GET_NEXT_COUNT_IF_FLAG(ret, flg) do { \ + if (!(flags & (flg))) \ + break; \ + if (length < 4) \ + return 0; \ + ret = le32_to_cpu(*counts); \ + length -= 4; \ + ++counts; \ + } while (0) + + GET_NEXT_COUNT_IF_FLAG(fs_count, FUNCTIONFS_HAS_FS_DESC); + GET_NEXT_COUNT_IF_FLAG(hs_count, FUNCTIONFS_HAS_HS_DESC); + GET_NEXT_COUNT_IF_FLAG(count, FUNCTIONFS_HAS_SS_DESC); + + count = fs_count + hs_count; + if (!count) + return 0; + descs_start = (const void *)counts; + +#undef GET_NEXT_COUNT_IF_FLAG + } + + /* + * Find the end of FS and HS USB descriptors. SS descriptors + * are ignored since legacy format does not support them. + */ + descs_end = descs_start; + do { + if (length < *descs_end) + return 0; + length -= *descs_end; + descs_end += *descs_end; + } while (--count); + + /* Allocate legacy descriptors and copy the data. */ + { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + struct { + struct usb_functionfs_descs_head header; + __u8 descriptors[]; + } __attribute__((packed)) *out; +#pragma GCC diagnostic pop + + length = sizeof out->header + (descs_end - descs_start); + out = malloc(length); + out->header.magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC); + out->header.length = cpu_to_le32(length); + out->header.fs_count = cpu_to_le32(fs_count); + out->header.hs_count = cpu_to_le32(hs_count); + memcpy(out->descriptors, descs_start, descs_end - descs_start); + *legacy = out; + } + + return length; +} + + +#define STR_INTERFACE_ "Source/Sink" + +static const struct { + struct usb_functionfs_strings_head header; + struct { + __le16 code; + const char str1[sizeof STR_INTERFACE_]; + } __attribute__((packed)) lang0; +} __attribute__((packed)) strings = { + .header = { + .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC), + .length = cpu_to_le32(sizeof strings), + .str_count = cpu_to_le32(1), + .lang_count = cpu_to_le32(1), + }, + .lang0 = { + cpu_to_le16(0x0409), /* en-us */ + STR_INTERFACE_, + }, +}; + +#define STR_INTERFACE strings.lang0.str1 + + +/******************** Files and Threads Handling ****************************/ + +struct thread; + +static ssize_t read_wrap(struct thread *t, void *buf, size_t nbytes); +static ssize_t write_wrap(struct thread *t, const void *buf, size_t nbytes); +static ssize_t ep0_consume(struct thread *t, const void *buf, size_t nbytes); +static ssize_t fill_in_buf(struct thread *t, void *buf, size_t nbytes); +static ssize_t empty_out_buf(struct thread *t, const void *buf, size_t nbytes); + + +static struct thread { + const char *const filename; + size_t buf_size; + + ssize_t (*in)(struct thread *, void *, size_t); + const char *const in_name; + + ssize_t (*out)(struct thread *, const void *, size_t); + const char *const out_name; + + int fd; + pthread_t id; + void *buf; + ssize_t status; +} threads[] = { + { + "ep0", 4 * sizeof(struct usb_functionfs_event), + read_wrap, NULL, + ep0_consume, "<consume>", + 0, 0, NULL, 0 + }, + { + "ep1", 8 * 1024, + fill_in_buf, "<in>", + write_wrap, NULL, + 0, 0, NULL, 0 + }, + { + "ep2", 8 * 1024, + read_wrap, NULL, + empty_out_buf, "<out>", + 0, 0, NULL, 0 + }, +}; + + +static void init_thread(struct thread *t) +{ + t->buf = malloc(t->buf_size); + die_on(!t->buf, "malloc"); + + t->fd = open(t->filename, O_RDWR); + die_on(t->fd < 0, "%s", t->filename); +} + +static void cleanup_thread(void *arg) +{ + struct thread *t = arg; + int ret, fd; + + fd = t->fd; + if (t->fd < 0) + return; + t->fd = -1; + + /* test the FIFO ioctls (non-ep0 code paths) */ + if (t != threads) { + ret = ioctl(fd, FUNCTIONFS_FIFO_STATUS); + if (ret < 0) { + /* ENODEV reported after disconnect */ + if (errno != ENODEV) + err("%s: get fifo status", t->filename); + } else if (ret) { + warn("%s: unclaimed = %d\n", t->filename, ret); + if (ioctl(fd, FUNCTIONFS_FIFO_FLUSH) < 0) + err("%s: fifo flush", t->filename); + } + } + + if (close(fd) < 0) + err("%s: close", t->filename); + + free(t->buf); + t->buf = NULL; +} + +static void *start_thread_helper(void *arg) +{ + const char *name, *op, *in_name, *out_name; + struct thread *t = arg; + ssize_t ret; + + info("%s: starts\n", t->filename); + in_name = t->in_name ? t->in_name : t->filename; + out_name = t->out_name ? t->out_name : t->filename; + + pthread_cleanup_push(cleanup_thread, arg); + + for (;;) { + pthread_testcancel(); + + ret = t->in(t, t->buf, t->buf_size); + if (ret > 0) { + ret = t->out(t, t->buf, ret); + name = out_name; + op = "write"; + } else { + name = in_name; + op = "read"; + } + + if (ret > 0) { + /* nop */ + } else if (!ret) { + debug("%s: %s: EOF", name, op); + break; + } else if (errno == EINTR || errno == EAGAIN) { + debug("%s: %s", name, op); + } else { + warn("%s: %s", name, op); + break; + } + } + + pthread_cleanup_pop(1); + + t->status = ret; + info("%s: ends\n", t->filename); + return NULL; +} + +static void start_thread(struct thread *t) +{ + debug("%s: starting\n", t->filename); + + die_on(pthread_create(&t->id, NULL, start_thread_helper, t) < 0, + "pthread_create(%s)", t->filename); +} + +static void join_thread(struct thread *t) +{ + int ret = pthread_join(t->id, NULL); + + if (ret < 0) + err("%s: joining thread", t->filename); + else + debug("%s: joined\n", t->filename); +} + + +static ssize_t read_wrap(struct thread *t, void *buf, size_t nbytes) +{ + return read(t->fd, buf, nbytes); +} + +static ssize_t write_wrap(struct thread *t, const void *buf, size_t nbytes) +{ + return write(t->fd, buf, nbytes); +} + + +/******************** Empty/Fill buffer routines ****************************/ + +/* 0 -- stream of zeros, 1 -- i % 63, 2 -- pipe */ +enum pattern { PAT_ZERO, PAT_SEQ, PAT_PIPE }; +static enum pattern pattern; + +static ssize_t +fill_in_buf(struct thread *ignore, void *buf, size_t nbytes) +{ + size_t i; + __u8 *p; + + (void)ignore; + + switch (pattern) { + case PAT_ZERO: + memset(buf, 0, nbytes); + break; + + case PAT_SEQ: + for (p = buf, i = 0; i < nbytes; ++i, ++p) + *p = i % 63; + break; + + case PAT_PIPE: + return fread(buf, 1, nbytes, stdin); + } + + return nbytes; +} + +static ssize_t +empty_out_buf(struct thread *ignore, const void *buf, size_t nbytes) +{ + const __u8 *p; + __u8 expected; + ssize_t ret; + size_t len; + + (void)ignore; + + switch (pattern) { + case PAT_ZERO: + expected = 0; + for (p = buf, len = 0; len < nbytes; ++p, ++len) + if (*p) + goto invalid; + break; + + case PAT_SEQ: + for (p = buf, len = 0; len < nbytes; ++p, ++len) + if (*p != len % 63) { + expected = len % 63; + goto invalid; + } + break; + + case PAT_PIPE: + ret = fwrite(buf, nbytes, 1, stdout); + if (ret > 0) + fflush(stdout); + break; + +invalid: + err("bad OUT byte %zd, expected %02x got %02x\n", + len, expected, *p); + for (p = buf, len = 0; len < nbytes; ++p, ++len) { + if (0 == (len % 32)) + fprintf(stderr, "%4zd:", len); + fprintf(stderr, " %02x", *p); + if (31 == (len % 32)) + fprintf(stderr, "\n"); + } + fflush(stderr); + errno = EILSEQ; + return -1; + } + + return len; +} + + +/******************** Endpoints routines ************************************/ + +static void handle_setup(const struct usb_ctrlrequest *setup) +{ + printf("bRequestType = %d\n", setup->bRequestType); + printf("bRequest = %d\n", setup->bRequest); + printf("wValue = %d\n", le16_to_cpu(setup->wValue)); + printf("wIndex = %d\n", le16_to_cpu(setup->wIndex)); + printf("wLength = %d\n", le16_to_cpu(setup->wLength)); +} + +static ssize_t +ep0_consume(struct thread *ignore, const void *buf, size_t nbytes) +{ + static const char *const names[] = { + [FUNCTIONFS_BIND] = "BIND", + [FUNCTIONFS_UNBIND] = "UNBIND", + [FUNCTIONFS_ENABLE] = "ENABLE", + [FUNCTIONFS_DISABLE] = "DISABLE", + [FUNCTIONFS_SETUP] = "SETUP", + [FUNCTIONFS_SUSPEND] = "SUSPEND", + [FUNCTIONFS_RESUME] = "RESUME", + }; + + const struct usb_functionfs_event *event = buf; + size_t n; + + (void)ignore; + + for (n = nbytes / sizeof *event; n; --n, ++event) + switch (event->type) { + case FUNCTIONFS_BIND: + case FUNCTIONFS_UNBIND: + case FUNCTIONFS_ENABLE: + case FUNCTIONFS_DISABLE: + case FUNCTIONFS_SETUP: + case FUNCTIONFS_SUSPEND: + case FUNCTIONFS_RESUME: + printf("Event %s\n", names[event->type]); + if (event->type == FUNCTIONFS_SETUP) + handle_setup(&event->u.setup); + break; + + default: + printf("Event %03u (unknown)\n", event->type); + } + + return nbytes; +} + +static void ep0_init(struct thread *t, bool legacy_descriptors) +{ + void *legacy; + ssize_t ret; + size_t len; + + if (legacy_descriptors) { + info("%s: writing descriptors\n", t->filename); + goto legacy; + } + + info("%s: writing descriptors (in v2 format)\n", t->filename); + ret = write(t->fd, &descriptors, sizeof descriptors); + + if (ret < 0 && errno == EINVAL) { + warn("%s: new format rejected, trying legacy\n", t->filename); +legacy: + len = descs_to_legacy(&legacy, &descriptors); + if (len) { + ret = write(t->fd, legacy, len); + free(legacy); + } + } + die_on(ret < 0, "%s: write: descriptors", t->filename); + + info("%s: writing strings\n", t->filename); + ret = write(t->fd, &strings, sizeof strings); + die_on(ret < 0, "%s: write: strings", t->filename); +} + + +/******************** Main **************************************************/ + +int main(int argc, char **argv) +{ + bool legacy_descriptors; + unsigned i; + + legacy_descriptors = argc > 2 && !strcmp(argv[1], "-l"); + + init_thread(threads); + ep0_init(threads, legacy_descriptors); + + for (i = 1; i < sizeof threads / sizeof *threads; ++i) + init_thread(threads + i); + + for (i = 1; i < sizeof threads / sizeof *threads; ++i) + start_thread(threads + i); + + start_thread_helper(threads); + + for (i = 1; i < sizeof threads / sizeof *threads; ++i) + join_thread(threads + i); + + return 0; +} diff --git a/tools/usb/hcd-tests.sh b/tools/usb/hcd-tests.sh new file mode 100644 index 000000000..73f914d13 --- /dev/null +++ b/tools/usb/hcd-tests.sh @@ -0,0 +1,274 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 +# +# test types can be passed on the command line: +# +# - control: any device can do this +# - out, in: out needs 'bulk sink' firmware, in needs 'bulk src' +# - iso-out, iso-in: out needs 'iso sink' firmware, in needs 'iso src' +# - halt: needs bulk sink+src, tests halt set/clear from host +# - unlink: needs bulk sink and/or src, test HCD unlink processing +# - loop: needs firmware that will buffer N transfers +# +# run it for hours, days, weeks. +# + +# +# this default provides a steady test load for a bulk device +# +TYPES='control out in' +#TYPES='control out in halt' + +# +# to test HCD code +# +# - include unlink tests +# - add some ${RANDOM}ness +# - connect several devices concurrently (same HC) +# - keep HC's IRQ lines busy with unrelated traffic (IDE, net, ...) +# - add other concurrent system loads +# + +declare -i COUNT BUFLEN + +COUNT=50000 +BUFLEN=2048 + +# NOTE: the 'in' and 'out' cases are usually bulk, but can be +# set up to use interrupt transfers by 'usbtest' module options + + +if [ "$DEVICE" = "" ]; then + echo "testing ALL recognized usbtest devices" + echo "" + TEST_ARGS="-a" +else + TEST_ARGS="" +fi + +do_test () +{ + if ! ./testusb $TEST_ARGS -s $BUFLEN -c $COUNT $* 2>/dev/null + then + echo "FAIL" + exit 1 + fi +} + +ARGS="$*" + +if [ "$ARGS" = "" ]; +then + ARGS="$TYPES" +fi + +# FIXME use /sys/bus/usb/device/$THIS/bConfigurationValue to +# check and change configs + +CONFIG='' + +check_config () +{ + if [ "$CONFIG" = "" ]; then + CONFIG=$1 + echo "assuming $CONFIG configuration" + return + fi + if [ "$CONFIG" = $1 ]; then + return + fi + + echo "** device must be in $1 config, but it's $CONFIG instead" + exit 1 +} + + +echo "TESTING: $ARGS" + +while : true +do + echo $(date) + + for TYPE in $ARGS + do + # restore defaults + COUNT=5000 + BUFLEN=2048 + + # FIXME automatically multiply COUNT by 10 when + # /sys/bus/usb/device/$THIS/speed == "480" + +# COUNT=50000 + + case $TYPE in + control) + # any device, in any configuration, can use this. + echo '** Control test cases:' + + echo "test 9: ch9 postconfig" + do_test -t 9 -c 5000 + echo "test 10: control queueing" + do_test -t 10 -c 5000 + + # this relies on some vendor-specific commands + echo "test 14: control writes" + do_test -t 14 -c 15000 -s 256 -v 1 + + echo "test 21: control writes, unaligned" + do_test -t 21 -c 100 -s 256 -v 1 + + ;; + + out) + check_config sink-src + echo '** Host Write (OUT) test cases:' + + echo "test 1: $COUNT transfers, same size" + do_test -t 1 + echo "test 3: $COUNT transfers, variable/short size" + do_test -t 3 -v 421 + + COUNT=100 + echo "test 17: $COUNT transfers, unaligned DMA map by core" + do_test -t 17 + + echo "test 19: $COUNT transfers, unaligned DMA map by usb_alloc_coherent" + do_test -t 19 + + COUNT=2000 + echo "test 5: $COUNT scatterlists, same size entries" + do_test -t 5 + + # try to trigger short OUT processing bugs + echo "test 7a: $COUNT scatterlists, variable size/short entries" + do_test -t 7 -v 579 + BUFLEN=4096 + echo "test 7b: $COUNT scatterlists, variable size/bigger entries" + do_test -t 7 -v 41 + BUFLEN=64 + echo "test 7c: $COUNT scatterlists, variable size/micro entries" + do_test -t 7 -v 63 + ;; + + iso-out) + check_config sink-src + echo '** Host ISOCHRONOUS Write (OUT) test cases:' + + # at peak iso transfer rates: + # - usb 2.0 high bandwidth, this is one frame. + # - usb 1.1, it's twenty-four frames. + BUFLEN=24500 + + COUNT=1000 + +# COUNT=10000 + + echo "test 15: $COUNT transfers, same size" + # do_test -t 15 -g 3 -v 0 + BUFLEN=32768 + do_test -t 15 -g 8 -v 0 + + # FIXME it'd make sense to have an iso OUT test issuing + # short writes on more packets than the last one + + COUNT=100 + echo "test 22: $COUNT transfers, non aligned" + do_test -t 22 -g 8 -v 0 + + ;; + + in) + check_config sink-src + echo '** Host Read (IN) test cases:' + + # NOTE: these "variable size" reads are just multiples + # of 512 bytes, no EOVERFLOW testing is done yet + + echo "test 2: $COUNT transfers, same size" + do_test -t 2 + echo "test 4: $COUNT transfers, variable size" + do_test -t 4 + + COUNT=100 + echo "test 18: $COUNT transfers, unaligned DMA map by core" + do_test -t 18 + + echo "test 20: $COUNT transfers, unaligned DMA map by usb_alloc_coherent" + do_test -t 20 + + COUNT=2000 + echo "test 6: $COUNT scatterlists, same size entries" + do_test -t 6 + echo "test 8: $COUNT scatterlists, variable size entries" + do_test -t 8 + ;; + + iso-in) + check_config sink-src + echo '** Host ISOCHRONOUS Read (IN) test cases:' + + # at peak iso transfer rates: + # - usb 2.0 high bandwidth, this is one frame. + # - usb 1.1, it's twenty-four frames. + BUFLEN=24500 + + COUNT=1000 + +# COUNT=10000 + + echo "test 16: $COUNT transfers, same size" + # do_test -t 16 -g 3 -v 0 + BUFLEN=32768 + do_test -t 16 -g 8 -v 0 + + # FIXME since iso expects faults, it'd make sense + # to have an iso IN test issuing short reads ... + + COUNT=100 + echo "test 23: $COUNT transfers, unaligned" + do_test -t 23 -g 8 -v 0 + + ;; + + halt) + # NOTE: sometimes hardware doesn't cooperate well with halting + # endpoints from the host side. so long as mass-storage class + # firmware can halt them from the device, don't worry much if + # you can't make this test work on your device. + COUNT=2000 + echo "test 13: $COUNT halt set/clear" + do_test -t 13 + ;; + + unlink) + COUNT=2000 + echo "test 11: $COUNT read unlinks" + do_test -t 11 + + echo "test 12: $COUNT write unlinks" + do_test -t 12 + ;; + + loop) + # defaults need too much buffering for ez-usb devices + BUFLEN=2048 + COUNT=32 + + # modprobe g_zero qlen=$COUNT buflen=$BUFLEN loopdefault + check_config loopback + + # FIXME someone needs to write and merge a version of this + + echo "write $COUNT buffers of $BUFLEN bytes, read them back" + + echo "write $COUNT variable size buffers, read them back" + + ;; + + *) + echo "Don't understand test type $TYPE" + exit 1; + esac + echo '' + done +done diff --git a/tools/usb/testusb.c b/tools/usb/testusb.c new file mode 100644 index 000000000..cbaa1b9fd --- /dev/null +++ b/tools/usb/testusb.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* $(CROSS_COMPILE)cc -Wall -Wextra -g -lpthread -o testusb testusb.c */ + +/* + * Copyright (c) 2002 by David Brownell + * Copyright (c) 2010 by Samsung Electronics + * Author: Michal Nazarewicz <mina86@mina86.com> + */ + +/* + * This program issues ioctls to perform the tests implemented by the + * kernel driver. It can generate a variety of transfer patterns; you + * should make sure to test both regular streaming and mixes of + * transfer sizes (including short transfers). + * + * For more information on how this can be used and on USB testing + * refer to <URL:http://www.linux-usb.org/usbtest/>. + */ + +#include <stdio.h> +#include <string.h> +#include <ftw.h> +#include <stdlib.h> +#include <pthread.h> +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <sys/ioctl.h> +#include <linux/usbdevice_fs.h> + +/*-------------------------------------------------------------------------*/ + +#define TEST_CASES 30 + +// FIXME make these public somewhere; usbdevfs.h? + +struct usbtest_param { + // inputs + unsigned test_num; /* 0..(TEST_CASES-1) */ + unsigned iterations; + unsigned length; + unsigned vary; + unsigned sglen; + + // outputs + struct timeval duration; +}; +#define USBTEST_REQUEST _IOWR('U', 100, struct usbtest_param) + +/*-------------------------------------------------------------------------*/ + +/* #include <linux/usb_ch9.h> */ + +#define USB_DT_DEVICE 0x01 +#define USB_DT_INTERFACE 0x04 + +#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */ +#define USB_CLASS_VENDOR_SPEC 0xff + + +struct usb_device_descriptor { + __u8 bLength; + __u8 bDescriptorType; + __u16 bcdUSB; + __u8 bDeviceClass; + __u8 bDeviceSubClass; + __u8 bDeviceProtocol; + __u8 bMaxPacketSize0; + __u16 idVendor; + __u16 idProduct; + __u16 bcdDevice; + __u8 iManufacturer; + __u8 iProduct; + __u8 iSerialNumber; + __u8 bNumConfigurations; +} __attribute__ ((packed)); + +struct usb_interface_descriptor { + __u8 bLength; + __u8 bDescriptorType; + + __u8 bInterfaceNumber; + __u8 bAlternateSetting; + __u8 bNumEndpoints; + __u8 bInterfaceClass; + __u8 bInterfaceSubClass; + __u8 bInterfaceProtocol; + __u8 iInterface; +} __attribute__ ((packed)); + +enum usb_device_speed { + USB_SPEED_UNKNOWN = 0, /* enumerating */ + USB_SPEED_LOW, USB_SPEED_FULL, /* usb 1.1 */ + USB_SPEED_HIGH, /* usb 2.0 */ + USB_SPEED_WIRELESS, /* wireless (usb 2.5) */ + USB_SPEED_SUPER, /* usb 3.0 */ + USB_SPEED_SUPER_PLUS, /* usb 3.1 */ +}; + +/*-------------------------------------------------------------------------*/ + +static char *speed (enum usb_device_speed s) +{ + switch (s) { + case USB_SPEED_UNKNOWN: return "unknown"; + case USB_SPEED_LOW: return "low"; + case USB_SPEED_FULL: return "full"; + case USB_SPEED_HIGH: return "high"; + case USB_SPEED_WIRELESS: return "wireless"; + case USB_SPEED_SUPER: return "super"; + case USB_SPEED_SUPER_PLUS: return "super-plus"; + default: return "??"; + } +} + +struct testdev { + struct testdev *next; + char *name; + pthread_t thread; + enum usb_device_speed speed; + unsigned ifnum : 8; + unsigned forever : 1; + int test; + + struct usbtest_param param; +}; +static struct testdev *testdevs; + +static int testdev_ffs_ifnum(FILE *fd) +{ + union { + char buf[255]; + struct usb_interface_descriptor intf; + } u; + + for (;;) { + if (fread(u.buf, 1, 1, fd) != 1) + return -1; + if (fread(u.buf + 1, (unsigned char)u.buf[0] - 1, 1, fd) != 1) + return -1; + + if (u.intf.bLength == sizeof u.intf + && u.intf.bDescriptorType == USB_DT_INTERFACE + && u.intf.bNumEndpoints == 2 + && u.intf.bInterfaceClass == USB_CLASS_VENDOR_SPEC + && u.intf.bInterfaceSubClass == 0 + && u.intf.bInterfaceProtocol == 0) + return (unsigned char)u.intf.bInterfaceNumber; + } +} + +static int testdev_ifnum(FILE *fd) +{ + struct usb_device_descriptor dev; + + if (fread(&dev, sizeof dev, 1, fd) != 1) + return -1; + + if (dev.bLength != sizeof dev || dev.bDescriptorType != USB_DT_DEVICE) + return -1; + + /* FX2 with (tweaked) bulksrc firmware */ + if (dev.idVendor == 0x0547 && dev.idProduct == 0x1002) + return 0; + + /*----------------------------------------------------*/ + + /* devices that start up using the EZ-USB default device and + * which we can use after loading simple firmware. hotplug + * can fxload it, and then run this test driver. + * + * we return false positives in two cases: + * - the device has a "real" driver (maybe usb-serial) that + * renumerates. the device should vanish quickly. + * - the device doesn't have the test firmware installed. + */ + + /* generic EZ-USB FX controller */ + if (dev.idVendor == 0x0547 && dev.idProduct == 0x2235) + return 0; + + /* generic EZ-USB FX2 controller */ + if (dev.idVendor == 0x04b4 && dev.idProduct == 0x8613) + return 0; + + /* CY3671 development board with EZ-USB FX */ + if (dev.idVendor == 0x0547 && dev.idProduct == 0x0080) + return 0; + + /* Keyspan 19Qi uses an21xx (original EZ-USB) */ + if (dev.idVendor == 0x06cd && dev.idProduct == 0x010b) + return 0; + + /*----------------------------------------------------*/ + + /* "gadget zero", Linux-USB test software */ + if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a0) + return 0; + + /* user mode subset of that */ + if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a4) + return testdev_ffs_ifnum(fd); + /* return 0; */ + + /* iso version of usermode code */ + if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a3) + return 0; + + /* some GPL'd test firmware uses these IDs */ + + if (dev.idVendor == 0xfff0 && dev.idProduct == 0xfff0) + return 0; + + /*----------------------------------------------------*/ + + /* iBOT2 high speed webcam */ + if (dev.idVendor == 0x0b62 && dev.idProduct == 0x0059) + return 0; + + /*----------------------------------------------------*/ + + /* the FunctionFS gadget can have the source/sink interface + * anywhere. We look for an interface descriptor that match + * what we expect. We ignore configuratiens thou. */ + + if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4ac + && (dev.bDeviceClass == USB_CLASS_PER_INTERFACE + || dev.bDeviceClass == USB_CLASS_VENDOR_SPEC)) + return testdev_ffs_ifnum(fd); + + return -1; +} + +static int find_testdev(const char *name, const struct stat *sb, int flag) +{ + FILE *fd; + int ifnum; + struct testdev *entry; + + (void)sb; /* unused */ + + if (flag != FTW_F) + return 0; + + fd = fopen(name, "rb"); + if (!fd) { + perror(name); + return 0; + } + + ifnum = testdev_ifnum(fd); + fclose(fd); + if (ifnum < 0) + return 0; + + entry = calloc(1, sizeof *entry); + if (!entry) + goto nomem; + + entry->name = strdup(name); + if (!entry->name) { + free(entry); +nomem: + perror("malloc"); + return 0; + } + + entry->ifnum = ifnum; + entry->next = testdevs; + testdevs = entry; + return 0; +} + +static int +usbdev_ioctl (int fd, int ifno, unsigned request, void *param) +{ + struct usbdevfs_ioctl wrapper; + + wrapper.ifno = ifno; + wrapper.ioctl_code = request; + wrapper.data = param; + + return ioctl (fd, USBDEVFS_IOCTL, &wrapper); +} + +static void *handle_testdev (void *arg) +{ + struct testdev *dev = arg; + int fd, i; + int status; + + if ((fd = open (dev->name, O_RDWR)) < 0) { + perror ("can't open dev file r/w"); + return 0; + } + + status = ioctl(fd, USBDEVFS_GET_SPEED, NULL); + if (status < 0) + fprintf(stderr, "USBDEVFS_GET_SPEED failed %d\n", status); + else + dev->speed = status; + fprintf(stderr, "%s speed\t%s\t%u\n", + speed(dev->speed), dev->name, dev->ifnum); + +restart: + for (i = 0; i < TEST_CASES; i++) { + if (dev->test != -1 && dev->test != i) + continue; + dev->param.test_num = i; + + status = usbdev_ioctl (fd, dev->ifnum, + USBTEST_REQUEST, &dev->param); + if (status < 0 && errno == EOPNOTSUPP) + continue; + + /* FIXME need a "syslog it" option for background testing */ + + /* NOTE: each thread emits complete lines; no fragments! */ + if (status < 0) { + char buf [80]; + int err = errno; + + if (strerror_r (errno, buf, sizeof buf)) { + snprintf (buf, sizeof buf, "error %d", err); + errno = err; + } + printf ("%s test %d --> %d (%s)\n", + dev->name, i, errno, buf); + } else + printf ("%s test %d, %4d.%.06d secs\n", dev->name, i, + (int) dev->param.duration.tv_sec, + (int) dev->param.duration.tv_usec); + + fflush (stdout); + } + if (dev->forever) + goto restart; + + close (fd); + return arg; +} + +static const char *usb_dir_find(void) +{ + static char udev_usb_path[] = "/dev/bus/usb"; + + if (access(udev_usb_path, F_OK) == 0) + return udev_usb_path; + + return NULL; +} + +static int parse_num(unsigned *num, const char *str) +{ + unsigned long val; + char *end; + + errno = 0; + val = strtoul(str, &end, 0); + if (errno || *end || val > UINT_MAX) + return -1; + *num = val; + return 0; +} + +int main (int argc, char **argv) +{ + + int c; + struct testdev *entry; + char *device; + const char *usb_dir = NULL; + int all = 0, forever = 0, not = 0; + int test = -1 /* all */; + struct usbtest_param param; + + /* pick defaults that works with all speeds, without short packets. + * + * Best per-frame data rates: + * super speed,bulk 1024 * 16 * 8 = 131072 + * interrupt 1024 * 3 * 8 = 24576 + * high speed, bulk 512 * 13 * 8 = 53248 + * interrupt 1024 * 3 * 8 = 24576 + * full speed, bulk/intr 64 * 19 = 1216 + * interrupt 64 * 1 = 64 + * low speed, interrupt 8 * 1 = 8 + */ + param.iterations = 1000; + param.length = 1024; + param.vary = 1024; + param.sglen = 32; + + /* for easy use when hotplugging */ + device = getenv ("DEVICE"); + + while ((c = getopt (argc, argv, "D:aA:c:g:hlns:t:v:")) != EOF) + switch (c) { + case 'D': /* device, if only one */ + device = optarg; + continue; + case 'A': /* use all devices with specified USB dir */ + usb_dir = optarg; + /* FALL THROUGH */ + case 'a': /* use all devices */ + device = NULL; + all = 1; + continue; + case 'c': /* count iterations */ + if (parse_num(¶m.iterations, optarg)) + goto usage; + continue; + case 'g': /* scatter/gather entries */ + if (parse_num(¶m.sglen, optarg)) + goto usage; + continue; + case 'l': /* loop forever */ + forever = 1; + continue; + case 'n': /* no test running! */ + not = 1; + continue; + case 's': /* size of packet */ + if (parse_num(¶m.length, optarg)) + goto usage; + continue; + case 't': /* run just one test */ + test = atoi (optarg); + if (test < 0) + goto usage; + continue; + case 'v': /* vary packet size by ... */ + if (parse_num(¶m.vary, optarg)) + goto usage; + continue; + case '?': + case 'h': + default: +usage: + fprintf (stderr, + "usage: %s [options]\n" + "Options:\n" + "\t-D dev only test specific device\n" + "\t-A usb-dir\n" + "\t-a test all recognized devices\n" + "\t-l loop forever(for stress test)\n" + "\t-t testnum only run specified case\n" + "\t-n no test running, show devices to be tested\n" + "Case arguments:\n" + "\t-c iterations default 1000\n" + "\t-s transfer length default 1024\n" + "\t-g sglen default 32\n" + "\t-v vary default 1024\n", + argv[0]); + return 1; + } + if (optind != argc) + goto usage; + if (!all && !device) { + fprintf (stderr, "must specify '-a' or '-D dev', " + "or DEVICE=/dev/bus/usb/BBB/DDD in env\n"); + goto usage; + } + + /* Find usb device subdirectory */ + if (!usb_dir) { + usb_dir = usb_dir_find(); + if (!usb_dir) { + fputs ("USB device files are missing\n", stderr); + return -1; + } + } + + /* collect and list the test devices */ + if (ftw (usb_dir, find_testdev, 3) != 0) { + fputs ("ftw failed; are USB device files missing?\n", stderr); + return -1; + } + + /* quit, run single test, or create test threads */ + if (!testdevs && !device) { + fputs ("no test devices recognized\n", stderr); + return -1; + } + if (not) + return 0; + if (testdevs && !testdevs->next && !device) + device = testdevs->name; + for (entry = testdevs; entry; entry = entry->next) { + int status; + + entry->param = param; + entry->forever = forever; + entry->test = test; + + if (device) { + if (strcmp (entry->name, device)) + continue; + return handle_testdev (entry) != entry; + } + status = pthread_create (&entry->thread, 0, handle_testdev, entry); + if (status) + perror ("pthread_create"); + } + if (device) { + struct testdev dev; + + /* kernel can recognize test devices we don't */ + fprintf (stderr, "%s: %s may see only control tests\n", + argv [0], device); + + memset (&dev, 0, sizeof dev); + dev.name = device; + dev.param = param; + dev.forever = forever; + dev.test = test; + return handle_testdev (&dev) != &dev; + } + + /* wait for tests to complete */ + for (entry = testdevs; entry; entry = entry->next) { + void *retval; + + if (pthread_join (entry->thread, &retval)) + perror ("pthread_join"); + /* testing errors discarded! */ + } + + return 0; +} diff --git a/tools/usb/usbip/.gitignore b/tools/usb/usbip/.gitignore new file mode 100644 index 000000000..597361a96 --- /dev/null +++ b/tools/usb/usbip/.gitignore @@ -0,0 +1,33 @@ +# SPDX-License-Identifier: GPL-2.0-only +Makefile +Makefile.in +aclocal.m4 +autom4te.cache/ +compile +config.guess +config.h +config.h.in +config.log +config.status +config.sub +configure +depcomp +install-sh +libsrc/Makefile +libsrc/Makefile.in +libtool +ltmain.sh +missing +src/Makefile +src/Makefile.in +stamp-h1 +libsrc/libusbip.la +libsrc/libusbip_la-names.lo +libsrc/libusbip_la-sysfs_utils.lo +libsrc/libusbip_la-usbip_common.lo +libsrc/libusbip_la-usbip_device_driver.lo +libsrc/libusbip_la-usbip_host_common.lo +libsrc/libusbip_la-usbip_host_driver.lo +libsrc/libusbip_la-vhci_driver.lo +src/usbip +src/usbipd diff --git a/tools/usb/usbip/AUTHORS b/tools/usb/usbip/AUTHORS new file mode 100644 index 000000000..a27ea8d03 --- /dev/null +++ b/tools/usb/usbip/AUTHORS @@ -0,0 +1,3 @@ +Takahiro Hirofuchi +Robert Leibl +matt mooney <mfm@muteddisk.com> diff --git a/tools/usb/usbip/COPYING b/tools/usb/usbip/COPYING new file mode 100644 index 000000000..c5611e48a --- /dev/null +++ b/tools/usb/usbip/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + <signature of Ty Coon>, 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/tools/usb/usbip/INSTALL b/tools/usb/usbip/INSTALL new file mode 100644 index 000000000..d3c5b40a9 --- /dev/null +++ b/tools/usb/usbip/INSTALL @@ -0,0 +1,237 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, +2006, 2007 Free Software Foundation, Inc. + +This file is free documentation; the Free Software Foundation gives +unlimited permission to copy, distribute and modify it. + +Basic Installation +================== + +Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + +The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package. + + 4. Type `make install' to install the programs and any data files and + documentation. + + 5. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 6. Often, you can also type `make uninstall' to remove the installed + files again. + +Compilers and Options +===================== + +Some systems require unusual options for compilation or linking that the +`configure' script does not know about. Run `./configure --help' for +details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + +You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + +Installation Names +================== + +By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX'. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + +Optional Features +================= + +Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + +Specifying the System Type +========================== + +There may be some features `configure' cannot figure out automatically, +but needs to determine by the type of machine the package will run on. +Usually, assuming the package is built to be run on the _same_ +architectures, `configure' can figure that out, but if it prints a +message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + +If you want to set default values for `configure' scripts to share, you +can create a site shell script called `config.site' that gives default +values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + +Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + +`configure' recognizes the following options to control how it operates. + +`--help' +`-h' + Print a summary of the options to `configure', and exit. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/tools/usb/usbip/Makefile.am b/tools/usb/usbip/Makefile.am new file mode 100644 index 000000000..5961e9c18 --- /dev/null +++ b/tools/usb/usbip/Makefile.am @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: GPL-2.0 +SUBDIRS := libsrc src +includedir = @includedir@/usbip +include_HEADERS := $(addprefix libsrc/, \ + usbip_common.h vhci_driver.h usbip_host_driver.h \ + list.h sysfs_utils.h usbip_host_common.h) + +dist_man_MANS := $(addprefix doc/, usbip.8 usbipd.8) diff --git a/tools/usb/usbip/README b/tools/usb/usbip/README new file mode 100644 index 000000000..2fc021c0e --- /dev/null +++ b/tools/usb/usbip/README @@ -0,0 +1,257 @@ +# +# README for usbip-utils +# +# Copyright (C) 2011 matt mooney <mfm@muteddisk.com> +# 2005-2008 Takahiro Hirofuchi + +[Overview] +USB/IP protocol allows to pass USB device from server to client over the +network. Server is a machine which provides (shares) a USB device. Client is +a machine which uses USB device provided by server over the network. +The USB device may be either physical device connected to a server or +software entity created on a server using USB gadget subsystem. +Whole project consists of four parts: + + - usbip-vhci + A client side kernel module which provides a virtual USB Host Controller + and allows to import a USB device from a remote machine. + + - usbip-host (stub driver) + A server side module which provides a USB device driver which can be + bound to a physical USB device to make it exportable. + + - usbip-vudc + A server side module which provides a virtual USB Device Controller and allows + to export a USB device created using USB Gadget Subsystem. + + - usbip-utils + A set of userspace tools used to handle connection and management. + Used on both sides. + +[Requirements] + - USB/IP device drivers + Found in the drivers/usb/usbip/ directory of the Linux kernel tree. + + - libudev >= 2.0 + libudev library + + - libwrap0-dev + tcp wrapper library + + - gcc >= 4.0 + + - libtool, automake >= 1.9, autoconf >= 2.5.0, pkg-config + +[Optional] + - hwdata + Contains USB device identification data. + + +[Install] + 0. Generate configuration scripts. + $ ./autogen.sh + + 1. Compile & install the userspace utilities. + $ ./configure [--with-tcp-wrappers=no] [--with-usbids-dir=<dir>] + $ make install + + 2. Compile & install USB/IP drivers. + + +[Usage] +On a server side there are two entities which can be shared. +First of them is physical usb device connected to the machine. +To make it available below steps should be executed: + + server:# (Physically attach your USB device.) + + server:# insmod usbip-core.ko + server:# insmod usbip-host.ko + + server:# usbipd -D + - Start usbip daemon. + + server:# usbip list -l + - List driver assignments for USB devices. + + server:# usbip bind --busid 1-2 + - Bind usbip-host.ko to the device with busid 1-2. + - The USB device 1-2 is now exportable to other hosts! + - Use `usbip unbind --busid 1-2' to stop exporting the device. + +Second of shareable entities is USB Gadget created using USB Gadget Subsystem +on a server machine. To make it available below steps should be executed: + + server:# (Create your USB gadget) + - Currently the most preferable way of creating a new USB gadget + is ConfigFS Composite Gadget. Please refer to its documentation + for details. + - See vudc_server_example.sh for a short example of USB gadget creation + + server:# insmod usbip-core.ko + server:# insmod usbip-vudc.ko + - To create more than one instance of vudc use num module param + + server:# (Bind gadget to one of available vudc) + - Assign your new gadget to USB/IP UDC + - Using ConfigFS interface you may do this simply by: + server:# cd /sys/kernel/config/usb_gadget/<gadget_name> + server:# echo "usbip-vudc.0" > UDC + + server:# usbipd -D --device + - Start usbip daemon. + +To attach new device to client machine below commands should be used: + + client:# insmod usbip-core.ko + client:# insmod vhci-hcd.ko + + client:# usbip list --remote <host> + - List exported USB devices on the <host>. + + client:# usbip attach --remote <host> --busid 1-2 + - Connect the remote USB device. + - When using vudc on a server side busid is really vudc instance name. + For example: usbip-vudc.0 + + client:# usbip port + - Show virtual port status. + + client:# usbip detach --port <port> + - Detach the USB device. + + +[Example] +--------------------------- + SERVER SIDE +--------------------------- +Physically attach your USB devices to this host. + + trois:# insmod path/to/usbip-core.ko + trois:# insmod path/to/usbip-host.ko + trois:# usbipd -D + +In another terminal, let's look up what USB devices are physically +attached to this host. + + trois:# usbip list -l + Local USB devices + ================= + - busid 1-1 (05a9:a511) + 1-1:1.0 + + - busid 3-2 (0711:0902) + 3-2:1.0 + + - busid 3-3.1 (08bb:2702) + 3-3.1:1.0 + 3-3.1:1.1 + + - busid 3-3.2 (04bb:0206) + 3-3.2:1.0 + + - busid 3-3 (0409:0058) + 3-3:1.0 + + - busid 4-1 (046d:08b2) + 4-1:1.0 + 4-1:1.1 + 4-1:1.2 + + - busid 5-2 (058f:9254) + 5-2:1.0 + +A USB storage device of busid 3-3.2 is now bound to the usb-storage +driver. To export this device, we first mark the device as +"exportable"; the device is bound to the usbip-host driver. Please +remember you can not export a USB hub. + +Mark the device of busid 3-3.2 as exportable: + + trois:# usbip --debug bind --busid 3-3.2 + ... + usbip debug: usbip_bind.c:162:[unbind_other] 3-3.2:1.0 -> usb-storage + ... + bind device on busid 3-3.2: complete + + trois:# usbip list -l + Local USB devices + ================= + ... + + - busid 3-3.2 (04bb:0206) + 3-3.2:1.0 + ... + +--------------------------- + CLIENT SIDE +--------------------------- +First, let's list available remote devices that are marked as +exportable on the host. + + deux:# insmod path/to/usbip-core.ko + deux:# insmod path/to/vhci-hcd.ko + + deux:# usbip list --remote 10.0.0.3 + Exportable USB devices + ====================== + - 10.0.0.3 + 1-1: Prolific Technology, Inc. : unknown product (067b:3507) + : /sys/devices/pci0000:00/0000:00:1f.2/usb1/1-1 + : (Defined at Interface level) / unknown subclass / unknown protocol (00/00/00) + : 0 - Mass Storage / SCSI / Bulk (Zip) (08/06/50) + + 1-2.2.1: Apple Computer, Inc. : unknown product (05ac:0203) + : /sys/devices/pci0000:00/0000:00:1f.2/usb1/1-2/1-2.2/1-2.2.1 + : (Defined at Interface level) / unknown subclass / unknown protocol (00/00/00) + : 0 - Human Interface Devices / Boot Interface Subclass / Keyboard (03/01/01) + + 1-2.2.3: OmniVision Technologies, Inc. : OV511+ WebCam (05a9:a511) + : /sys/devices/pci0000:00/0000:00:1f.2/usb1/1-2/1-2.2/1-2.2.3 + : (Defined at Interface level) / unknown subclass / unknown protocol (00/00/00) + : 0 - Vendor Specific Class / unknown subclass / unknown protocol (ff/00/00) + + 3-1: Logitech, Inc. : QuickCam Pro 4000 (046d:08b2) + : /sys/devices/pci0000:00/0000:00:1e.0/0000:02:0a.0/usb3/3-1 + : (Defined at Interface level) / unknown subclass / unknown protocol (00/00/00) + : 0 - Data / unknown subclass / unknown protocol (0a/ff/00) + : 1 - Audio / Control Device / unknown protocol (01/01/00) + : 2 - Audio / Streaming / unknown protocol (01/02/00) + +Attach a remote USB device: + + deux:# usbip attach --remote 10.0.0.3 --busid 1-1 + port 0 attached + +Show the devices attached to this client: + + deux:# usbip port + Port 00: <Port in Use> at Full Speed(12Mbps) + Prolific Technology, Inc. : unknown product (067b:3507) + 6-1 -> usbip://10.0.0.3:3240/1-1 (remote bus/dev 001/004) + 6-1:1.0 used by usb-storage + /sys/class/scsi_device/0:0:0:0/device + /sys/class/scsi_host/host0/device + /sys/block/sda/device + +Detach the imported device: + + deux:# usbip detach --port 0 + port 0 detached + + +[Checklist] + - See 'Debug Tips' on the project wiki. + - http://usbip.wiki.sourceforge.net/how-to-debug-usbip + - usbip-host.ko must be bound to the target device. + - See /sys/kernel/debug/usb/devices and find "Driver=..." lines of the device. + - Target USB gadget must be bound to vudc + (using USB gadget susbsys, not usbip bind command) + - Shutdown firewall. + - usbip now uses TCP port 3240. + - Disable SELinux. + - Check the kernel and daemon messages. + + +[Contact] + Mailing List: linux-usb@vger.kernel.org diff --git a/tools/usb/usbip/autogen.sh b/tools/usb/usbip/autogen.sh new file mode 100755 index 000000000..e1112d3fc --- /dev/null +++ b/tools/usb/usbip/autogen.sh @@ -0,0 +1,9 @@ +#!/bin/sh -x + +#aclocal +#autoheader +#libtoolize --copy --force +#automake-1.9 -acf +#autoconf + +autoreconf -i -f -v diff --git a/tools/usb/usbip/cleanup.sh b/tools/usb/usbip/cleanup.sh new file mode 100755 index 000000000..8028c3a5c --- /dev/null +++ b/tools/usb/usbip/cleanup.sh @@ -0,0 +1,13 @@ +#!/bin/sh +# SPDX-License-Identifier: GPL-2.0 + +if [ -r Makefile ]; then + make distclean +fi + +FILES="aclocal.m4 autom4te.cache compile config.guess config.h.in config.log \ + config.status config.sub configure cscope.out depcomp install-sh \ + libsrc/Makefile libsrc/Makefile.in libtool ltmain.sh Makefile \ + Makefile.in missing src/Makefile src/Makefile.in" + +rm -vRf $FILES diff --git a/tools/usb/usbip/configure.ac b/tools/usb/usbip/configure.ac new file mode 100644 index 000000000..607d05c5c --- /dev/null +++ b/tools/usb/usbip/configure.ac @@ -0,0 +1,111 @@ +dnl Process this file with autoconf to produce a configure script. + +AC_PREREQ(2.59) +AC_INIT([usbip-utils], [2.0], [linux-usb@vger.kernel.org]) +AC_DEFINE([USBIP_VERSION], [0x00000111], [binary-coded decimal version number]) + +CURRENT=0 +REVISION=1 +AGE=0 +AC_SUBST([LIBUSBIP_VERSION], [$CURRENT:$REVISION:$AGE], [library version]) + +AC_CONFIG_SRCDIR([src/usbipd.c]) +AC_CONFIG_HEADERS([config.h]) + +AM_INIT_AUTOMAKE([foreign]) +LT_INIT + +# Silent build for automake >= 1.11 +m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + +AC_SUBST([EXTRA_CFLAGS], ["-Wall -Werror -Wextra -std=gnu99"]) + +# Checks for programs. +AC_PROG_CC +AC_PROG_INSTALL +AC_PROG_MAKE_SET + +# Checks for header files. +AC_HEADER_DIRENT +AC_HEADER_STDC +AC_CHECK_HEADERS([arpa/inet.h fcntl.h netdb.h netinet/in.h stdint.h stdlib.h dnl + string.h sys/socket.h syslog.h unistd.h]) + +# Checks for typedefs, structures, and compiler characteristics. +AC_TYPE_INT32_T +AC_TYPE_SIZE_T +AC_TYPE_SSIZE_T +AC_TYPE_UINT16_T +AC_TYPE_UINT32_T +AC_TYPE_UINT8_T + +# Checks for library functions. +AC_FUNC_REALLOC +AC_CHECK_FUNCS([memset mkdir regcomp socket strchr strerror strstr dnl + strtoul]) + +AC_CHECK_HEADER([libudev.h], + [AC_CHECK_LIB([udev], [udev_new], + [LIBS="$LIBS -ludev"], + [AC_MSG_ERROR([Missing udev library!])])], + [AC_MSG_ERROR([Missing /usr/include/libudev.h])]) + +# Checks for libwrap library. +AC_MSG_CHECKING([whether to use the libwrap (TCP wrappers) library]) +AC_ARG_WITH([tcp-wrappers], + [AS_HELP_STRING([--with-tcp-wrappers], + [use the libwrap (TCP wrappers) library])], + dnl [ACTION-IF-GIVEN] + [if test "$withval" = "yes"; then + AC_MSG_RESULT([yes]) + AC_MSG_CHECKING([for hosts_access in -lwrap]) + saved_LIBS="$LIBS" + LIBS="-lwrap $saved_LIBS" + AC_TRY_LINK( + [int hosts_access(); int allow_severity, deny_severity;], + [hosts_access()], + [AC_MSG_RESULT([yes]); + AC_DEFINE([HAVE_LIBWRAP], [1], + [use tcp wrapper]) wrap_LIB="-lwrap"], + [AC_MSG_RESULT([not found]); exit 1]) + else + AC_MSG_RESULT([no]); + fi], + dnl [ACTION-IF-NOT-GIVEN] + [AC_MSG_RESULT([(default)]) + AC_MSG_CHECKING([for hosts_access in -lwrap]) + saved_LIBS="$LIBS" + LIBS="-lwrap $saved_LIBS" + AC_TRY_LINK( + [int hosts_access(); int allow_severity, deny_severity;], + [hosts_access()], + [AC_MSG_RESULT([yes]); + AC_DEFINE([HAVE_LIBWRAP], [1], [use tcp wrapper])], + [AC_MSG_RESULT([no]); LIBS="$saved_LIBS"])]) + +# Sets directory containing usb.ids. +AC_ARG_WITH([usbids-dir], + [AS_HELP_STRING([--with-usbids-dir=DIR], + [where usb.ids is found (default /usr/share/hwdata/)])], + [USBIDS_DIR=$withval], [USBIDS_DIR="/usr/share/hwdata/"]) +AC_SUBST([USBIDS_DIR]) + +# use _FORTIFY_SOURCE +AC_MSG_CHECKING([whether to use fortify]) +AC_ARG_WITH([fortify], + [AS_HELP_STRING([--with-fortify], + [use _FORTIFY_SROUCE option when compiling)])], + dnl [ACTION-IF-GIVEN] + [if test "$withval" = "yes"; then + AC_MSG_RESULT([yes]) + CFLAGS="$CFLAGS -D_FORTIFY_SOURCE -O" + else + AC_MSG_RESULT([no]) + CFLAGS="$CFLAGS -U_FORTIFY_SOURCE" + fi + ], + dnl [ACTION-IF-NOT-GIVEN] + [AC_MSG_RESULT([default])]) + +AC_CONFIG_FILES([Makefile libsrc/Makefile src/Makefile]) +AC_OUTPUT diff --git a/tools/usb/usbip/doc/usbip.8 b/tools/usb/usbip/doc/usbip.8 new file mode 100644 index 000000000..1f26e4a00 --- /dev/null +++ b/tools/usb/usbip/doc/usbip.8 @@ -0,0 +1,137 @@ +.TH USBIP "8" "February 2009" "usbip" "System Administration Utilities" +.SH NAME +usbip \- manage USB/IP devices +.SH SYNOPSIS +.B usbip +[\fIoptions\fR] <\fIcommand\fR> <\fIargs\fR> + +.SH DESCRIPTION +On a USB/IP server, devices can be listed, bound, and unbound using +this program. On a USB/IP client, devices exported by USB/IP servers +can be listed, attached and detached. + +.SH OPTIONS +.HP +\fB\-\-debug\fR +.IP +Print debugging information. +.PP + +.HP +\fB\-\-log\fR +.IP +Log to syslog. +.PP + +.HP +\fB\-\-tcp-port PORT\fR +.IP +Connect to PORT on remote host (used for attach and list --remote). +.PP + +.SH COMMANDS +.HP +\fBversion\fR +.IP +Show version and exit. +.PP + +.HP +\fBhelp\fR [\fIcommand\fR] +.IP +Print the program help message, or help on a specific command, and +then exit. +.PP + +.HP +\fBattach\fR \-\-remote=<\fIhost\fR> \-\-busid=<\fIbus_id\fR> +.IP +Attach a remote USB device. +.PP + +.HP +\fBattach\fR \-\-remote=<\fIhost\fR> \-\-device=<\fIdev_id\fR> +.IP +Attach a remote USB gadget. +Only used when the remote usbipd is in device mode. +.PP + +.HP +\fBdetach\fR \-\-port=<\fIport\fR> +.IP +Detach an imported USB device/gadget. +.PP + +.HP +\fBbind\fR \-\-busid=<\fIbusid\fR> +.IP +Make a device exportable. +.PP + +.HP +\fBunbind\fR \-\-busid=<\fIbusid\fR> +.IP +Stop exporting a device so it can be used by a local driver. +.PP + +.HP +\fBlist\fR \-\-remote=<\fIhost\fR> +.IP +List USB devices exported by a remote host. +.PP + +.HP +\fBlist\fR \-\-device +.IP +List USB gadgets of local usbip-vudc. +Only used when the local usbipd is in device mode. +Note that this can not list usbip-vudc USB gadgets of the remote device mode usbipd. +.PP + +.HP +\fBlist\fR \-\-local +.IP +List local USB devices. +.PP + +.HP +\fBport\fR +.IP +List imported devices/gadgets. +.PP + + +.SH EXAMPLES + + client:# usbip list --remote=server + - List devices exported by remote server. + + client:# modprobe vhci-hcd + + client:# usbip attach --remote=server --busid=1-2 + - Connect the remote USB device. + + client:# usbip port + - List imported devices/gadgets. + + client:# usbip detach --port=0 + - Detach the usb device. + +The following example shows the usage of device mode + + server:# usbip list --device + - List gadgets exported by local usbipd server. + + client:# modprobe vhci-hcd + + client:# usbip attach --remote=server --device=usbip-vudc.0 + - Connect the remote USB gadget. + + client:# usbip port + - List imported devices/gadgets. + + client:# usbip detach --port=0 + - Detach the usb gadget. + +.SH "SEE ALSO" +\fBusbipd\fP\fB(8)\fB\fP diff --git a/tools/usb/usbip/doc/usbipd.8 b/tools/usb/usbip/doc/usbipd.8 new file mode 100644 index 000000000..d974394f8 --- /dev/null +++ b/tools/usb/usbip/doc/usbipd.8 @@ -0,0 +1,117 @@ +.TH USBIP "8" "February 2009" "usbip" "System Administration Utilities" +.SH NAME +usbipd \- USB/IP server daemon +.SH SYNOPSIS +.B usbipd +[\fIoptions\fR] + +.SH DESCRIPTION +.B usbipd +provides USB/IP clients access to exported USB devices. + +Devices have to explicitly be exported using +.B usbip bind +before usbipd makes them available to other hosts. + +The daemon accepts connections from USB/IP clients +on TCP port 3240 by default. + +.SH OPTIONS +.HP +\fB\-4\fR, \fB\-\-ipv4\fR +.IP +Bind to IPv4. Default is both. +.PP + +.HP +\fB\-6\fR, \fB\-\-ipv6\fR +.IP +Bind to IPv6. Default is both. +.PP + +.HP +\fB\-e\fR, \fB\-\-device\fR +.IP +Run in device mode. Rather than drive an attached device, create a virtual UDC to bind gadgets to. +.PP + +.HP +\fB\-D\fR, \fB\-\-daemon\fR +.IP +Run as a daemon process. +.PP + +.HP +\fB\-d\fR, \fB\-\-debug\fR +.IP +Print debugging information. +.PP + +.HP +\fB\-PFILE\fR, \fB\-\-pid FILE\fR +.IP +Write process id to FILE. +.br +If no FILE specified, use /var/run/usbipd.pid +.PP + +\fB\-tPORT\fR, \fB\-\-tcp\-port PORT\fR +.IP +Listen on TCP/IP port PORT. +.PP + +\fB\-h\fR, \fB\-\-help\fR +.IP +Print the program help message and exit. +.PP + +.HP +\fB\-v\fR, \fB\-\-version\fR +.IP +Show version. +.PP + +.SH LIMITATIONS + +.B usbipd +offers no authentication or authorization for USB/IP. Any +USB/IP client can connect and use exported devices. + +.SH EXAMPLES + + server:# modprobe usbip-host + + server:# usbipd -D + - Start usbip daemon. + + server:# usbip list --local + - List driver assignments for usb devices. + + server:# usbip bind --busid=1-2 + - Bind usbip-host.ko to the device of busid 1-2. + - A usb device 1-2 is now exportable to other hosts! + - Use 'usbip unbind --busid=1-2' when you want to shutdown exporting and use the device locally. + +The following example shows the usage of device mode + + server:# modprobe usbip-vudc + - Use /sys/class/udc/ interface. + - usbip-host is independent of this module. + + server:# usbipd -e -D + - Start usbip daemon in device mode. + + server:# modprobe g_mass_storage file=/tmp/tmp.img + - Bind a gadget to usbip-vudc. + - in this example, a mass storage gadget is bound. + + server:# usbip list --device + - List gadgets exported by local usbipd server. + + server:# modprobe -r g_mass_storage + - Unbind a gadget from usbip-vudc. + - in this example, the previous mass storage gadget is unbound. + +.SH "SEE ALSO" +\fBusbip\fP\fB(8)\fB\fP + diff --git a/tools/usb/usbip/libsrc/Makefile.am b/tools/usb/usbip/libsrc/Makefile.am new file mode 100644 index 000000000..dabd2c91d --- /dev/null +++ b/tools/usb/usbip/libsrc/Makefile.am @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 +libusbip_la_CPPFLAGS = -DUSBIDS_FILE='"@USBIDS_DIR@/usb.ids"' +libusbip_la_CFLAGS = @EXTRA_CFLAGS@ +libusbip_la_LDFLAGS = -version-info @LIBUSBIP_VERSION@ + +lib_LTLIBRARIES := libusbip.la +libusbip_la_SOURCES := names.c names.h usbip_host_driver.c usbip_host_driver.h \ + usbip_device_driver.c usbip_device_driver.h \ + usbip_common.c usbip_common.h usbip_host_common.h \ + usbip_host_common.c vhci_driver.c vhci_driver.h \ + sysfs_utils.c sysfs_utils.h diff --git a/tools/usb/usbip/libsrc/list.h b/tools/usb/usbip/libsrc/list.h new file mode 100644 index 000000000..9cca24255 --- /dev/null +++ b/tools/usb/usbip/libsrc/list.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _LIST_H +#define _LIST_H + +/* Stripped down implementation of linked list taken + * from the Linux Kernel. + */ + +/* + * Simple doubly linked list implementation. + * + * Some of the internal functions ("__xxx") are useful when + * manipulating whole lists rather than single entries, as + * sometimes we already know the next/prev entries and we can + * generate better code by using them directly rather than + * using the generic single-entry routines. + */ + +struct list_head { + struct list_head *next, *prev; +}; + +#define LIST_HEAD_INIT(name) { &(name), &(name) } + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +static inline void INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +/* + * Insert a new entry between two known consecutive entries. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_add(struct list_head *new, + struct list_head *prev, + struct list_head *next) +{ + next->prev = new; + new->next = next; + new->prev = prev; + prev->next = new; +} + +/** + * list_add - add a new entry + * @new: new entry to be added + * @head: list head to add it after + * + * Insert a new entry after the specified head. + * This is good for implementing stacks. + */ +static inline void list_add(struct list_head *new, struct list_head *head) +{ + __list_add(new, head, head->next); +} + +/* + * Delete a list entry by making the prev/next entries + * point to each other. + * + * This is only for internal list manipulation where we know + * the prev/next entries already! + */ +static inline void __list_del(struct list_head * prev, struct list_head * next) +{ + next->prev = prev; + prev->next = next; +} + +#define POISON_POINTER_DELTA 0 +#define LIST_POISON1 ((void *) 0x00100100 + POISON_POINTER_DELTA) +#define LIST_POISON2 ((void *) 0x00200200 + POISON_POINTER_DELTA) + +static inline void __list_del_entry(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); +} + +/** + * list_del - deletes entry from list. + * @entry: the element to delete from the list. + * Note: list_empty() on entry does not return true after this, the entry is + * in an undefined state. + */ +static inline void list_del(struct list_head *entry) +{ + __list_del(entry->prev, entry->next); + entry->next = LIST_POISON1; + entry->prev = LIST_POISON2; +} + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_head within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); pos = pos->next) + +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) + +/** + * container_of - cast a member of a structure out to the containing structure + * @ptr: the pointer to the member. + * @type: the type of the container struct this is embedded in. + * @member: the name of the member within the struct. + * + */ +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#endif diff --git a/tools/usb/usbip/libsrc/names.c b/tools/usb/usbip/libsrc/names.c new file mode 100644 index 000000000..aba7f4188 --- /dev/null +++ b/tools/usb/usbip/libsrc/names.c @@ -0,0 +1,486 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * names.c -- USB name database manipulation routines + * + * Copyright (C) 1999, 2000 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * Copyright (C) 2005 Takahiro Hirofuchi + * - names_deinit() is added. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <dirent.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <ctype.h> + +#include "names.h" +#include "usbip_common.h" + +struct vendor { + struct vendor *next; + u_int16_t vendorid; + char name[1]; +}; + +struct product { + struct product *next; + u_int16_t vendorid, productid; + char name[1]; +}; + +struct class { + struct class *next; + u_int8_t classid; + char name[1]; +}; + +struct subclass { + struct subclass *next; + u_int8_t classid, subclassid; + char name[1]; +}; + +struct protocol { + struct protocol *next; + u_int8_t classid, subclassid, protocolid; + char name[1]; +}; + +struct genericstrtable { + struct genericstrtable *next; + unsigned int num; + char name[1]; +}; + + +#define HASH1 0x10 +#define HASH2 0x02 +#define HASHSZ 16 + +static unsigned int hashnum(unsigned int num) +{ + unsigned int mask1 = HASH1 << 27, mask2 = HASH2 << 27; + + for (; mask1 >= HASH1; mask1 >>= 1, mask2 >>= 1) + if (num & mask1) + num ^= mask2; + return num & (HASHSZ-1); +} + + +static struct vendor *vendors[HASHSZ] = { NULL, }; +static struct product *products[HASHSZ] = { NULL, }; +static struct class *classes[HASHSZ] = { NULL, }; +static struct subclass *subclasses[HASHSZ] = { NULL, }; +static struct protocol *protocols[HASHSZ] = { NULL, }; + +const char *names_vendor(u_int16_t vendorid) +{ + struct vendor *v; + + v = vendors[hashnum(vendorid)]; + for (; v; v = v->next) + if (v->vendorid == vendorid) + return v->name; + return NULL; +} + +const char *names_product(u_int16_t vendorid, u_int16_t productid) +{ + struct product *p; + + p = products[hashnum((vendorid << 16) | productid)]; + for (; p; p = p->next) + if (p->vendorid == vendorid && p->productid == productid) + return p->name; + return NULL; +} + +const char *names_class(u_int8_t classid) +{ + struct class *c; + + c = classes[hashnum(classid)]; + for (; c; c = c->next) + if (c->classid == classid) + return c->name; + return NULL; +} + +const char *names_subclass(u_int8_t classid, u_int8_t subclassid) +{ + struct subclass *s; + + s = subclasses[hashnum((classid << 8) | subclassid)]; + for (; s; s = s->next) + if (s->classid == classid && s->subclassid == subclassid) + return s->name; + return NULL; +} + +const char *names_protocol(u_int8_t classid, u_int8_t subclassid, + u_int8_t protocolid) +{ + struct protocol *p; + + p = protocols[hashnum((classid << 16) | (subclassid << 8) + | protocolid)]; + for (; p; p = p->next) + if (p->classid == classid && p->subclassid == subclassid && + p->protocolid == protocolid) + return p->name; + return NULL; +} + +/* add a cleanup function by takahiro */ +struct pool { + struct pool *next; + void *mem; +}; + +static struct pool *pool_head; + +static void *my_malloc(size_t size) +{ + struct pool *p; + + p = calloc(1, sizeof(struct pool)); + if (!p) + return NULL; + + p->mem = calloc(1, size); + if (!p->mem) { + free(p); + return NULL; + } + + p->next = pool_head; + pool_head = p; + + return p->mem; +} + +void names_free(void) +{ + struct pool *pool; + + if (!pool_head) + return; + + for (pool = pool_head; pool != NULL; ) { + struct pool *tmp; + + if (pool->mem) + free(pool->mem); + + tmp = pool; + pool = pool->next; + free(tmp); + } +} + +static int new_vendor(const char *name, u_int16_t vendorid) +{ + struct vendor *v; + unsigned int h = hashnum(vendorid); + + v = vendors[h]; + for (; v; v = v->next) + if (v->vendorid == vendorid) + return -1; + v = my_malloc(sizeof(struct vendor) + strlen(name)); + if (!v) + return -1; + strcpy(v->name, name); + v->vendorid = vendorid; + v->next = vendors[h]; + vendors[h] = v; + return 0; +} + +static int new_product(const char *name, u_int16_t vendorid, + u_int16_t productid) +{ + struct product *p; + unsigned int h = hashnum((vendorid << 16) | productid); + + p = products[h]; + for (; p; p = p->next) + if (p->vendorid == vendorid && p->productid == productid) + return -1; + p = my_malloc(sizeof(struct product) + strlen(name)); + if (!p) + return -1; + strcpy(p->name, name); + p->vendorid = vendorid; + p->productid = productid; + p->next = products[h]; + products[h] = p; + return 0; +} + +static int new_class(const char *name, u_int8_t classid) +{ + struct class *c; + unsigned int h = hashnum(classid); + + c = classes[h]; + for (; c; c = c->next) + if (c->classid == classid) + return -1; + c = my_malloc(sizeof(struct class) + strlen(name)); + if (!c) + return -1; + strcpy(c->name, name); + c->classid = classid; + c->next = classes[h]; + classes[h] = c; + return 0; +} + +static int new_subclass(const char *name, u_int8_t classid, u_int8_t subclassid) +{ + struct subclass *s; + unsigned int h = hashnum((classid << 8) | subclassid); + + s = subclasses[h]; + for (; s; s = s->next) + if (s->classid == classid && s->subclassid == subclassid) + return -1; + s = my_malloc(sizeof(struct subclass) + strlen(name)); + if (!s) + return -1; + strcpy(s->name, name); + s->classid = classid; + s->subclassid = subclassid; + s->next = subclasses[h]; + subclasses[h] = s; + return 0; +} + +static int new_protocol(const char *name, u_int8_t classid, u_int8_t subclassid, + u_int8_t protocolid) +{ + struct protocol *p; + unsigned int h = hashnum((classid << 16) | (subclassid << 8) + | protocolid); + + p = protocols[h]; + for (; p; p = p->next) + if (p->classid == classid && p->subclassid == subclassid + && p->protocolid == protocolid) + return -1; + p = my_malloc(sizeof(struct protocol) + strlen(name)); + if (!p) + return -1; + strcpy(p->name, name); + p->classid = classid; + p->subclassid = subclassid; + p->protocolid = protocolid; + p->next = protocols[h]; + protocols[h] = p; + return 0; +} + +static void parse(FILE *f) +{ + char buf[512], *cp; + unsigned int linectr = 0; + int lastvendor = -1; + int lastclass = -1; + int lastsubclass = -1; + int lasthut = -1; + int lastlang = -1; + unsigned int u; + + while (fgets(buf, sizeof(buf), f)) { + linectr++; + /* remove line ends */ + cp = strchr(buf, '\r'); + if (cp) + *cp = 0; + cp = strchr(buf, '\n'); + if (cp) + *cp = 0; + if (buf[0] == '#' || !buf[0]) + continue; + cp = buf; + if (buf[0] == 'P' && buf[1] == 'H' && buf[2] == 'Y' && + buf[3] == 'S' && buf[4] == 'D' && + buf[5] == 'E' && buf[6] == 'S' && /*isspace(buf[7])*/ + buf[7] == ' ') { + continue; + } + if (buf[0] == 'P' && buf[1] == 'H' && + buf[2] == 'Y' && /*isspace(buf[3])*/ buf[3] == ' ') { + continue; + } + if (buf[0] == 'B' && buf[1] == 'I' && buf[2] == 'A' && + buf[3] == 'S' && /*isspace(buf[4])*/ buf[4] == ' ') { + continue; + } + if (buf[0] == 'L' && /*isspace(buf[1])*/ buf[1] == ' ') { + lasthut = lastclass = lastvendor = lastsubclass = -1; + /* + * set 1 as pseudo-id to indicate that the parser is + * in a `L' section. + */ + lastlang = 1; + continue; + } + if (buf[0] == 'C' && /*isspace(buf[1])*/ buf[1] == ' ') { + /* class spec */ + cp = buf+2; + while (isspace(*cp)) + cp++; + if (!isxdigit(*cp)) { + err("Invalid class spec at line %u", linectr); + continue; + } + u = strtoul(cp, &cp, 16); + while (isspace(*cp)) + cp++; + if (!*cp) { + err("Invalid class spec at line %u", linectr); + continue; + } + if (new_class(cp, u)) + err("Duplicate class spec at line %u class %04x %s", + linectr, u, cp); + dbg("line %5u class %02x %s", linectr, u, cp); + lasthut = lastlang = lastvendor = lastsubclass = -1; + lastclass = u; + continue; + } + if (buf[0] == 'A' && buf[1] == 'T' && isspace(buf[2])) { + /* audio terminal type spec */ + continue; + } + if (buf[0] == 'H' && buf[1] == 'C' && buf[2] == 'C' + && isspace(buf[3])) { + /* HID Descriptor bCountryCode */ + continue; + } + if (isxdigit(*cp)) { + /* vendor */ + u = strtoul(cp, &cp, 16); + while (isspace(*cp)) + cp++; + if (!*cp) { + err("Invalid vendor spec at line %u", linectr); + continue; + } + if (new_vendor(cp, u)) + err("Duplicate vendor spec at line %u vendor %04x %s", + linectr, u, cp); + dbg("line %5u vendor %04x %s", linectr, u, cp); + lastvendor = u; + lasthut = lastlang = lastclass = lastsubclass = -1; + continue; + } + if (buf[0] == '\t' && isxdigit(buf[1])) { + /* product or subclass spec */ + u = strtoul(buf+1, &cp, 16); + while (isspace(*cp)) + cp++; + if (!*cp) { + err("Invalid product/subclass spec at line %u", + linectr); + continue; + } + if (lastvendor != -1) { + if (new_product(cp, lastvendor, u)) + err("Duplicate product spec at line %u product %04x:%04x %s", + linectr, lastvendor, u, cp); + dbg("line %5u product %04x:%04x %s", linectr, + lastvendor, u, cp); + continue; + } + if (lastclass != -1) { + if (new_subclass(cp, lastclass, u)) + err("Duplicate subclass spec at line %u class %02x:%02x %s", + linectr, lastclass, u, cp); + dbg("line %5u subclass %02x:%02x %s", linectr, + lastclass, u, cp); + lastsubclass = u; + continue; + } + if (lasthut != -1) { + /* do not store hut */ + continue; + } + if (lastlang != -1) { + /* do not store langid */ + continue; + } + err("Product/Subclass spec without prior Vendor/Class spec at line %u", + linectr); + continue; + } + if (buf[0] == '\t' && buf[1] == '\t' && isxdigit(buf[2])) { + /* protocol spec */ + u = strtoul(buf+2, &cp, 16); + while (isspace(*cp)) + cp++; + if (!*cp) { + err("Invalid protocol spec at line %u", + linectr); + continue; + } + if (lastclass != -1 && lastsubclass != -1) { + if (new_protocol(cp, lastclass, lastsubclass, + u)) + err("Duplicate protocol spec at line %u class %02x:%02x:%02x %s", + linectr, lastclass, lastsubclass, + u, cp); + dbg("line %5u protocol %02x:%02x:%02x %s", + linectr, lastclass, lastsubclass, u, cp); + continue; + } + err("Protocol spec without prior Class and Subclass spec at line %u", + linectr); + continue; + } + if (buf[0] == 'H' && buf[1] == 'I' && + buf[2] == 'D' && /*isspace(buf[3])*/ buf[3] == ' ') { + continue; + } + if (buf[0] == 'H' && buf[1] == 'U' && + buf[2] == 'T' && /*isspace(buf[3])*/ buf[3] == ' ') { + lastlang = lastclass = lastvendor = lastsubclass = -1; + /* + * set 1 as pseudo-id to indicate that the parser is + * in a `HUT' section. + */ + lasthut = 1; + continue; + } + if (buf[0] == 'R' && buf[1] == ' ') + continue; + + if (buf[0] == 'V' && buf[1] == 'T') + continue; + + err("Unknown line at line %u", linectr); + } +} + + +int names_init(char *n) +{ + FILE *f; + + f = fopen(n, "r"); + if (!f) + return errno; + + parse(f); + fclose(f); + return 0; +} diff --git a/tools/usb/usbip/libsrc/names.h b/tools/usb/usbip/libsrc/names.h new file mode 100644 index 000000000..b39958230 --- /dev/null +++ b/tools/usb/usbip/libsrc/names.h @@ -0,0 +1,26 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * names.h -- USB name database manipulation routines + * + * Copyright (C) 1999, 2000 Thomas Sailer (sailer@ife.ee.ethz.ch) + * + * Copyright (C) 2005 Takahiro Hirofuchi + * - names_free() is added. + */ + +#ifndef _NAMES_H +#define _NAMES_H + +#include <sys/types.h> + +/* used by usbip_common.c */ +extern const char *names_vendor(u_int16_t vendorid); +extern const char *names_product(u_int16_t vendorid, u_int16_t productid); +extern const char *names_class(u_int8_t classid); +extern const char *names_subclass(u_int8_t classid, u_int8_t subclassid); +extern const char *names_protocol(u_int8_t classid, u_int8_t subclassid, + u_int8_t protocolid); +extern int names_init(char *n); +extern void names_free(void); + +#endif /* _NAMES_H */ diff --git a/tools/usb/usbip/libsrc/sysfs_utils.c b/tools/usb/usbip/libsrc/sysfs_utils.c new file mode 100644 index 000000000..14d5e67d3 --- /dev/null +++ b/tools/usb/usbip/libsrc/sysfs_utils.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> + +#include "sysfs_utils.h" +#include "usbip_common.h" + +int write_sysfs_attribute(const char *attr_path, const char *new_value, + size_t len) +{ + int fd; + int length; + + fd = open(attr_path, O_WRONLY); + if (fd < 0) { + dbg("error opening attribute %s", attr_path); + return -1; + } + + length = write(fd, new_value, len); + if (length < 0) { + dbg("error writing to attribute %s", attr_path); + close(fd); + return -1; + } + + close(fd); + + return 0; +} diff --git a/tools/usb/usbip/libsrc/sysfs_utils.h b/tools/usb/usbip/libsrc/sysfs_utils.h new file mode 100644 index 000000000..0cd5f17e7 --- /dev/null +++ b/tools/usb/usbip/libsrc/sysfs_utils.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __SYSFS_UTILS_H +#define __SYSFS_UTILS_H + +int write_sysfs_attribute(const char *attr_path, const char *new_value, + size_t len); + +#endif diff --git a/tools/usb/usbip/libsrc/usbip_common.c b/tools/usb/usbip/libsrc/usbip_common.c new file mode 100644 index 000000000..b8d7d4805 --- /dev/null +++ b/tools/usb/usbip/libsrc/usbip_common.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2005-2007 Takahiro Hirofuchi + */ + +#include <libudev.h> +#include "usbip_common.h" +#include "names.h" + +#undef PROGNAME +#define PROGNAME "libusbip" + +int usbip_use_syslog; +int usbip_use_stderr; +int usbip_use_debug; + +extern struct udev *udev_context; + +struct speed_string { + int num; + char *speed; + char *desc; +}; + +static const struct speed_string speed_strings[] = { + { USB_SPEED_UNKNOWN, "unknown", "Unknown Speed"}, + { USB_SPEED_LOW, "1.5", "Low Speed(1.5Mbps)" }, + { USB_SPEED_FULL, "12", "Full Speed(12Mbps)" }, + { USB_SPEED_HIGH, "480", "High Speed(480Mbps)" }, + { USB_SPEED_WIRELESS, "53.3-480", "Wireless"}, + { USB_SPEED_SUPER, "5000", "Super Speed(5000Mbps)" }, + { 0, NULL, NULL } +}; + +struct portst_string { + int num; + char *desc; +}; + +static struct portst_string portst_strings[] = { + { SDEV_ST_AVAILABLE, "Device Available" }, + { SDEV_ST_USED, "Device in Use" }, + { SDEV_ST_ERROR, "Device Error"}, + { VDEV_ST_NULL, "Port Available"}, + { VDEV_ST_NOTASSIGNED, "Port Initializing"}, + { VDEV_ST_USED, "Port in Use"}, + { VDEV_ST_ERROR, "Port Error"}, + { 0, NULL} +}; + +const char *usbip_status_string(int32_t status) +{ + for (int i = 0; portst_strings[i].desc != NULL; i++) + if (portst_strings[i].num == status) + return portst_strings[i].desc; + + return "Unknown Status"; +} + +const char *usbip_speed_string(int num) +{ + for (int i = 0; speed_strings[i].speed != NULL; i++) + if (speed_strings[i].num == num) + return speed_strings[i].desc; + + return "Unknown Speed"; +} + +struct op_common_status_string { + int num; + char *desc; +}; + +static struct op_common_status_string op_common_status_strings[] = { + { ST_OK, "Request Completed Successfully" }, + { ST_NA, "Request Failed" }, + { ST_DEV_BUSY, "Device busy (exported)" }, + { ST_DEV_ERR, "Device in error state" }, + { ST_NODEV, "Device not found" }, + { ST_ERROR, "Unexpected response" }, + { 0, NULL} +}; + +const char *usbip_op_common_status_string(int status) +{ + for (int i = 0; op_common_status_strings[i].desc != NULL; i++) + if (op_common_status_strings[i].num == status) + return op_common_status_strings[i].desc; + + return "Unknown Op Common Status"; +} + +#define DBG_UDEV_INTEGER(name)\ + dbg("%-20s = %x", to_string(name), (int) udev->name) + +#define DBG_UINF_INTEGER(name)\ + dbg("%-20s = %x", to_string(name), (int) uinf->name) + +void dump_usb_interface(struct usbip_usb_interface *uinf) +{ + char buff[100]; + + usbip_names_get_class(buff, sizeof(buff), + uinf->bInterfaceClass, + uinf->bInterfaceSubClass, + uinf->bInterfaceProtocol); + dbg("%-20s = %s", "Interface(C/SC/P)", buff); +} + +void dump_usb_device(struct usbip_usb_device *udev) +{ + char buff[100]; + + dbg("%-20s = %s", "path", udev->path); + dbg("%-20s = %s", "busid", udev->busid); + + usbip_names_get_class(buff, sizeof(buff), + udev->bDeviceClass, + udev->bDeviceSubClass, + udev->bDeviceProtocol); + dbg("%-20s = %s", "Device(C/SC/P)", buff); + + DBG_UDEV_INTEGER(bcdDevice); + + usbip_names_get_product(buff, sizeof(buff), + udev->idVendor, + udev->idProduct); + dbg("%-20s = %s", "Vendor/Product", buff); + + DBG_UDEV_INTEGER(bNumConfigurations); + DBG_UDEV_INTEGER(bNumInterfaces); + + dbg("%-20s = %s", "speed", + usbip_speed_string(udev->speed)); + + DBG_UDEV_INTEGER(busnum); + DBG_UDEV_INTEGER(devnum); +} + + +int read_attr_value(struct udev_device *dev, const char *name, + const char *format) +{ + const char *attr; + int num = 0; + int ret; + + attr = udev_device_get_sysattr_value(dev, name); + if (!attr) { + err("udev_device_get_sysattr_value failed"); + goto err; + } + + /* The client chooses the device configuration + * when attaching it so right after being bound + * to usbip-host on the server the device will + * have no configuration. + * Therefore, attributes such as bConfigurationValue + * and bNumInterfaces will not exist and sscanf will + * fail. Check for these cases and don't treat them + * as errors. + */ + + ret = sscanf(attr, format, &num); + if (ret < 1) { + if (strcmp(name, "bConfigurationValue") && + strcmp(name, "bNumInterfaces")) { + err("sscanf failed for attribute %s", name); + goto err; + } + } + +err: + + return num; +} + + +int read_attr_speed(struct udev_device *dev) +{ + const char *speed; + + speed = udev_device_get_sysattr_value(dev, "speed"); + if (!speed) { + err("udev_device_get_sysattr_value failed"); + goto err; + } + + for (int i = 0; speed_strings[i].speed != NULL; i++) { + if (!strcmp(speed, speed_strings[i].speed)) + return speed_strings[i].num; + } + +err: + + return USB_SPEED_UNKNOWN; +} + +#define READ_ATTR(object, type, dev, name, format) \ + do { \ + (object)->name = (type) read_attr_value(dev, to_string(name), \ + format); \ + } while (0) + + +int read_usb_device(struct udev_device *sdev, struct usbip_usb_device *udev) +{ + uint32_t busnum, devnum; + const char *path, *name; + + READ_ATTR(udev, uint8_t, sdev, bDeviceClass, "%02x\n"); + READ_ATTR(udev, uint8_t, sdev, bDeviceSubClass, "%02x\n"); + READ_ATTR(udev, uint8_t, sdev, bDeviceProtocol, "%02x\n"); + + READ_ATTR(udev, uint16_t, sdev, idVendor, "%04x\n"); + READ_ATTR(udev, uint16_t, sdev, idProduct, "%04x\n"); + READ_ATTR(udev, uint16_t, sdev, bcdDevice, "%04x\n"); + + READ_ATTR(udev, uint8_t, sdev, bConfigurationValue, "%02x\n"); + READ_ATTR(udev, uint8_t, sdev, bNumConfigurations, "%02x\n"); + READ_ATTR(udev, uint8_t, sdev, bNumInterfaces, "%02x\n"); + + READ_ATTR(udev, uint8_t, sdev, devnum, "%d\n"); + udev->speed = read_attr_speed(sdev); + + path = udev_device_get_syspath(sdev); + name = udev_device_get_sysname(sdev); + + strncpy(udev->path, path, SYSFS_PATH_MAX - 1); + udev->path[SYSFS_PATH_MAX - 1] = '\0'; + strncpy(udev->busid, name, SYSFS_BUS_ID_SIZE - 1); + udev->busid[SYSFS_BUS_ID_SIZE - 1] = '\0'; + + sscanf(name, "%u-%u", &busnum, &devnum); + udev->busnum = busnum; + + return 0; +} + +int read_usb_interface(struct usbip_usb_device *udev, int i, + struct usbip_usb_interface *uinf) +{ + char busid[SYSFS_BUS_ID_SIZE]; + int size; + struct udev_device *sif; + + size = snprintf(busid, sizeof(busid), "%s:%d.%d", + udev->busid, udev->bConfigurationValue, i); + if (size < 0 || (unsigned int)size >= sizeof(busid)) { + err("busid length %i >= %lu or < 0", size, + (long unsigned)sizeof(busid)); + return -1; + } + + sif = udev_device_new_from_subsystem_sysname(udev_context, "usb", busid); + if (!sif) { + err("udev_device_new_from_subsystem_sysname %s failed", busid); + return -1; + } + + READ_ATTR(uinf, uint8_t, sif, bInterfaceClass, "%02x\n"); + READ_ATTR(uinf, uint8_t, sif, bInterfaceSubClass, "%02x\n"); + READ_ATTR(uinf, uint8_t, sif, bInterfaceProtocol, "%02x\n"); + + return 0; +} + +int usbip_names_init(char *f) +{ + return names_init(f); +} + +void usbip_names_free(void) +{ + names_free(); +} + +void usbip_names_get_product(char *buff, size_t size, uint16_t vendor, + uint16_t product) +{ + const char *prod, *vend; + + prod = names_product(vendor, product); + if (!prod) + prod = "unknown product"; + + + vend = names_vendor(vendor); + if (!vend) + vend = "unknown vendor"; + + snprintf(buff, size, "%s : %s (%04x:%04x)", vend, prod, vendor, product); +} + +void usbip_names_get_class(char *buff, size_t size, uint8_t class, + uint8_t subclass, uint8_t protocol) +{ + const char *c, *s, *p; + + if (class == 0 && subclass == 0 && protocol == 0) { + snprintf(buff, size, "(Defined at Interface level) (%02x/%02x/%02x)", class, subclass, protocol); + return; + } + + p = names_protocol(class, subclass, protocol); + if (!p) + p = "unknown protocol"; + + s = names_subclass(class, subclass); + if (!s) + s = "unknown subclass"; + + c = names_class(class); + if (!c) + c = "unknown class"; + + snprintf(buff, size, "%s / %s / %s (%02x/%02x/%02x)", c, s, p, class, subclass, protocol); +} diff --git a/tools/usb/usbip/libsrc/usbip_common.h b/tools/usb/usbip/libsrc/usbip_common.h new file mode 100644 index 000000000..73a367a7f --- /dev/null +++ b/tools/usb/usbip/libsrc/usbip_common.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2005-2007 Takahiro Hirofuchi + */ + +#ifndef __USBIP_COMMON_H +#define __USBIP_COMMON_H + +#include <libudev.h> + +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <syslog.h> +#include <unistd.h> +#include <linux/usb/ch9.h> +#include <linux/usbip.h> + +#ifndef USBIDS_FILE +#define USBIDS_FILE "/usr/share/hwdata/usb.ids" +#endif + +#ifndef VHCI_STATE_PATH +#define VHCI_STATE_PATH "/var/run/vhci_hcd" +#endif + +#define VUDC_DEVICE_DESCR_FILE "dev_desc" + +/* kernel module names */ +#define USBIP_CORE_MOD_NAME "usbip-core" +#define USBIP_HOST_DRV_NAME "usbip-host" +#define USBIP_DEVICE_DRV_NAME "usbip-vudc" +#define USBIP_VHCI_DRV_NAME "vhci_hcd" + +/* sysfs constants */ +#define SYSFS_MNT_PATH "/sys" +#define SYSFS_BUS_NAME "bus" +#define SYSFS_BUS_TYPE "usb" +#define SYSFS_DRIVERS_NAME "drivers" + +#define SYSFS_PATH_MAX 256 +#define SYSFS_BUS_ID_SIZE 32 + +/* Defines for op_code status in server/client op_common PDUs */ +#define ST_OK 0x00 +#define ST_NA 0x01 + /* Device requested for import is not available */ +#define ST_DEV_BUSY 0x02 + /* Device requested for import is in error state */ +#define ST_DEV_ERR 0x03 +#define ST_NODEV 0x04 +#define ST_ERROR 0x05 + +extern int usbip_use_syslog; +extern int usbip_use_stderr; +extern int usbip_use_debug ; + +#define PROGNAME "usbip" + +#define pr_fmt(fmt) "%s: %s: " fmt "\n", PROGNAME +#define dbg_fmt(fmt) pr_fmt("%s:%d:[%s] " fmt), "debug", \ + __FILE__, __LINE__, __func__ + +#define err(fmt, args...) \ + do { \ + if (usbip_use_syslog) { \ + syslog(LOG_ERR, pr_fmt(fmt), "error", ##args); \ + } \ + if (usbip_use_stderr) { \ + fprintf(stderr, pr_fmt(fmt), "error", ##args); \ + } \ + } while (0) + +#define info(fmt, args...) \ + do { \ + if (usbip_use_syslog) { \ + syslog(LOG_INFO, pr_fmt(fmt), "info", ##args); \ + } \ + if (usbip_use_stderr) { \ + fprintf(stderr, pr_fmt(fmt), "info", ##args); \ + } \ + } while (0) + +#define dbg(fmt, args...) \ + do { \ + if (usbip_use_debug) { \ + if (usbip_use_syslog) { \ + syslog(LOG_DEBUG, dbg_fmt(fmt), ##args); \ + } \ + if (usbip_use_stderr) { \ + fprintf(stderr, dbg_fmt(fmt), ##args); \ + } \ + } \ + } while (0) + +#define BUG() \ + do { \ + err("sorry, it's a bug!"); \ + abort(); \ + } while (0) + +struct usbip_usb_interface { + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t padding; /* alignment */ +} __attribute__((packed)); + +struct usbip_usb_device { + char path[SYSFS_PATH_MAX]; + char busid[SYSFS_BUS_ID_SIZE]; + + uint32_t busnum; + uint32_t devnum; + uint32_t speed; + + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bConfigurationValue; + uint8_t bNumConfigurations; + uint8_t bNumInterfaces; +} __attribute__((packed)); + +#define to_string(s) #s + +void dump_usb_interface(struct usbip_usb_interface *); +void dump_usb_device(struct usbip_usb_device *); +int read_usb_device(struct udev_device *sdev, struct usbip_usb_device *udev); +int read_attr_value(struct udev_device *dev, const char *name, + const char *format); +int read_usb_interface(struct usbip_usb_device *udev, int i, + struct usbip_usb_interface *uinf); + +const char *usbip_speed_string(int num); +const char *usbip_status_string(int32_t status); +const char *usbip_op_common_status_string(int status); + +int usbip_names_init(char *); +void usbip_names_free(void); +void usbip_names_get_product(char *buff, size_t size, uint16_t vendor, + uint16_t product); +void usbip_names_get_class(char *buff, size_t size, uint8_t class, + uint8_t subclass, uint8_t protocol); + +#endif /* __USBIP_COMMON_H */ diff --git a/tools/usb/usbip/libsrc/usbip_device_driver.c b/tools/usb/usbip/libsrc/usbip_device_driver.c new file mode 100644 index 000000000..927a151fa --- /dev/null +++ b/tools/usb/usbip/libsrc/usbip_device_driver.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> + * 2015 Samsung Electronics + * Author: Igor Kotrasinski <i.kotrasinsk@samsung.com> + * + * Based on tools/usb/usbip/libsrc/usbip_host_driver.c, which is: + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#include <fcntl.h> +#include <string.h> +#include <linux/usb/ch9.h> + +#include <unistd.h> + +#include "usbip_host_common.h" +#include "usbip_device_driver.h" + +#undef PROGNAME +#define PROGNAME "libusbip" + +#define copy_descr_attr16(dev, descr, attr) \ + ((dev)->attr = le16toh((descr)->attr)) \ + +#define copy_descr_attr(dev, descr, attr) \ + ((dev)->attr = (descr)->attr) \ + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +static struct { + enum usb_device_speed speed; + const char *name; +} speed_names[] = { + { + .speed = USB_SPEED_UNKNOWN, + .name = "UNKNOWN", + }, + { + .speed = USB_SPEED_LOW, + .name = "low-speed", + }, + { + .speed = USB_SPEED_FULL, + .name = "full-speed", + }, + { + .speed = USB_SPEED_HIGH, + .name = "high-speed", + }, + { + .speed = USB_SPEED_WIRELESS, + .name = "wireless", + }, + { + .speed = USB_SPEED_SUPER, + .name = "super-speed", + }, +}; + +static +int read_usb_vudc_device(struct udev_device *sdev, struct usbip_usb_device *dev) +{ + const char *path, *name; + char filepath[SYSFS_PATH_MAX]; + struct usb_device_descriptor descr; + unsigned int i; + FILE *fd = NULL; + struct udev_device *plat; + const char *speed; + size_t ret; + + plat = udev_device_get_parent(sdev); + path = udev_device_get_syspath(plat); + snprintf(filepath, SYSFS_PATH_MAX, "%s/%s", + path, VUDC_DEVICE_DESCR_FILE); + fd = fopen(filepath, "r"); + if (!fd) + return -1; + ret = fread((char *) &descr, sizeof(descr), 1, fd); + if (ret != 1) { + err("Cannot read vudc device descr file: %s", strerror(errno)); + goto err; + } + fclose(fd); + + copy_descr_attr(dev, &descr, bDeviceClass); + copy_descr_attr(dev, &descr, bDeviceSubClass); + copy_descr_attr(dev, &descr, bDeviceProtocol); + copy_descr_attr(dev, &descr, bNumConfigurations); + copy_descr_attr16(dev, &descr, idVendor); + copy_descr_attr16(dev, &descr, idProduct); + copy_descr_attr16(dev, &descr, bcdDevice); + + strncpy(dev->path, path, SYSFS_PATH_MAX - 1); + dev->path[SYSFS_PATH_MAX - 1] = '\0'; + + dev->speed = USB_SPEED_UNKNOWN; + speed = udev_device_get_sysattr_value(sdev, "current_speed"); + if (speed) { + for (i = 0; i < ARRAY_SIZE(speed_names); i++) { + if (!strcmp(speed_names[i].name, speed)) { + dev->speed = speed_names[i].speed; + break; + } + } + } + + /* Only used for user output, little sense to output them in general */ + dev->bNumInterfaces = 0; + dev->bConfigurationValue = 0; + dev->busnum = 0; + + name = udev_device_get_sysname(plat); + strncpy(dev->busid, name, SYSFS_BUS_ID_SIZE - 1); + dev->busid[SYSFS_BUS_ID_SIZE - 1] = '\0'; + return 0; +err: + fclose(fd); + return -1; +} + +static int is_my_device(struct udev_device *dev) +{ + const char *driver; + + driver = udev_device_get_property_value(dev, "USB_UDC_NAME"); + return driver != NULL && !strcmp(driver, USBIP_DEVICE_DRV_NAME); +} + +static int usbip_device_driver_open(struct usbip_host_driver *hdriver) +{ + int ret; + + hdriver->ndevs = 0; + INIT_LIST_HEAD(&hdriver->edev_list); + + ret = usbip_generic_driver_open(hdriver); + if (ret) + err("please load " USBIP_CORE_MOD_NAME ".ko and " + USBIP_DEVICE_DRV_NAME ".ko!"); + + return ret; +} + +struct usbip_host_driver device_driver = { + .edev_list = LIST_HEAD_INIT(device_driver.edev_list), + .udev_subsystem = "udc", + .ops = { + .open = usbip_device_driver_open, + .close = usbip_generic_driver_close, + .refresh_device_list = usbip_generic_refresh_device_list, + .get_device = usbip_generic_get_device, + .read_device = read_usb_vudc_device, + .is_my_device = is_my_device, + }, +}; diff --git a/tools/usb/usbip/libsrc/usbip_device_driver.h b/tools/usb/usbip/libsrc/usbip_device_driver.h new file mode 100644 index 000000000..1ce0bbd75 --- /dev/null +++ b/tools/usb/usbip/libsrc/usbip_device_driver.h @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015 Karol Kosik <karo9@interia.eu> + * 2015 Samsung Electronics + * Author: Igor Kotrasinski <i.kotrasinsk@samsung.com> + * + * Based on tools/usb/usbip/libsrc/usbip_host_driver.c, which is: + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#ifndef __USBIP_DEVICE_DRIVER_H +#define __USBIP_DEVICE_DRIVER_H + +#include <stdint.h> +#include "usbip_common.h" +#include "usbip_host_common.h" +#include "list.h" + +extern struct usbip_host_driver device_driver; + +#endif /* __USBIP_DEVICE_DRIVER_H */ diff --git a/tools/usb/usbip/libsrc/usbip_host_common.c b/tools/usb/usbip/libsrc/usbip_host_common.c new file mode 100644 index 000000000..ca78aa368 --- /dev/null +++ b/tools/usb/usbip/libsrc/usbip_host_common.c @@ -0,0 +1,282 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski <i.kotrasinsk@samsung.com> + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * Refactored from usbip_host_driver.c, which is: + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <errno.h> +#include <unistd.h> + +#include <libudev.h> + +#include "usbip_common.h" +#include "usbip_host_common.h" +#include "list.h" +#include "sysfs_utils.h" + +extern struct udev *udev_context; + +static int32_t read_attr_usbip_status(struct usbip_usb_device *udev) +{ + char status_attr_path[SYSFS_PATH_MAX]; + int size; + int fd; + int length; + char status[2] = { 0 }; + int value = 0; + + size = snprintf(status_attr_path, sizeof(status_attr_path), + "%s/usbip_status", udev->path); + if (size < 0 || (unsigned int)size >= sizeof(status_attr_path)) { + err("usbip_status path length %i >= %lu or < 0", size, + (long unsigned)sizeof(status_attr_path)); + return -1; + } + + + fd = open(status_attr_path, O_RDONLY); + if (fd < 0) { + err("error opening attribute %s", status_attr_path); + return -1; + } + + length = read(fd, status, 1); + if (length < 0) { + err("error reading attribute %s", status_attr_path); + close(fd); + return -1; + } + + value = atoi(status); + close(fd); + return value; +} + +static +struct usbip_exported_device *usbip_exported_device_new( + struct usbip_host_driver *hdriver, const char *sdevpath) +{ + struct usbip_exported_device *edev = NULL; + struct usbip_exported_device *edev_old; + size_t size; + int i; + + edev = calloc(1, sizeof(struct usbip_exported_device)); + + edev->sudev = + udev_device_new_from_syspath(udev_context, sdevpath); + if (!edev->sudev) { + err("udev_device_new_from_syspath: %s", sdevpath); + goto err; + } + + if (hdriver->ops.read_device(edev->sudev, &edev->udev) < 0) + goto err; + + edev->status = read_attr_usbip_status(&edev->udev); + if (edev->status < 0) + goto err; + + /* reallocate buffer to include usb interface data */ + size = sizeof(struct usbip_exported_device) + + edev->udev.bNumInterfaces * sizeof(struct usbip_usb_interface); + + edev_old = edev; + edev = realloc(edev, size); + if (!edev) { + edev = edev_old; + dbg("realloc failed"); + goto err; + } + + for (i = 0; i < edev->udev.bNumInterfaces; i++) { + /* vudc does not support reading interfaces */ + if (!hdriver->ops.read_interface) + break; + hdriver->ops.read_interface(&edev->udev, i, &edev->uinf[i]); + } + + return edev; +err: + if (edev->sudev) + udev_device_unref(edev->sudev); + if (edev) + free(edev); + + return NULL; +} + +static int refresh_exported_devices(struct usbip_host_driver *hdriver) +{ + struct usbip_exported_device *edev; + struct udev_enumerate *enumerate; + struct udev_list_entry *devices, *dev_list_entry; + struct udev_device *dev; + const char *path; + + enumerate = udev_enumerate_new(udev_context); + udev_enumerate_add_match_subsystem(enumerate, hdriver->udev_subsystem); + 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_context, + path); + if (dev == NULL) + continue; + + /* Check whether device uses usbip driver. */ + if (hdriver->ops.is_my_device(dev)) { + edev = usbip_exported_device_new(hdriver, path); + if (!edev) { + dbg("usbip_exported_device_new failed"); + continue; + } + + list_add(&edev->node, &hdriver->edev_list); + hdriver->ndevs++; + } + } + + return 0; +} + +static void usbip_exported_device_destroy(struct list_head *devs) +{ + struct list_head *i, *tmp; + struct usbip_exported_device *edev; + + list_for_each_safe(i, tmp, devs) { + edev = list_entry(i, struct usbip_exported_device, node); + list_del(i); + free(edev); + } +} + +int usbip_generic_driver_open(struct usbip_host_driver *hdriver) +{ + int rc; + + udev_context = udev_new(); + if (!udev_context) { + err("udev_new failed"); + return -1; + } + + rc = refresh_exported_devices(hdriver); + if (rc < 0) + goto err; + return 0; +err: + udev_unref(udev_context); + return -1; +} + +void usbip_generic_driver_close(struct usbip_host_driver *hdriver) +{ + if (!hdriver) + return; + + usbip_exported_device_destroy(&hdriver->edev_list); + + udev_unref(udev_context); +} + +int usbip_generic_refresh_device_list(struct usbip_host_driver *hdriver) +{ + int rc; + + usbip_exported_device_destroy(&hdriver->edev_list); + + hdriver->ndevs = 0; + INIT_LIST_HEAD(&hdriver->edev_list); + + rc = refresh_exported_devices(hdriver); + if (rc < 0) + return -1; + + return 0; +} + +int usbip_export_device(struct usbip_exported_device *edev, int sockfd) +{ + char attr_name[] = "usbip_sockfd"; + char sockfd_attr_path[SYSFS_PATH_MAX]; + int size; + char sockfd_buff[30]; + int ret; + + if (edev->status != SDEV_ST_AVAILABLE) { + dbg("device not available: %s", edev->udev.busid); + switch (edev->status) { + case SDEV_ST_ERROR: + dbg("status SDEV_ST_ERROR"); + ret = ST_DEV_ERR; + break; + case SDEV_ST_USED: + dbg("status SDEV_ST_USED"); + ret = ST_DEV_BUSY; + break; + default: + dbg("status unknown: 0x%x", edev->status); + ret = -1; + } + return ret; + } + + /* only the first interface is true */ + size = snprintf(sockfd_attr_path, sizeof(sockfd_attr_path), "%s/%s", + edev->udev.path, attr_name); + if (size < 0 || (unsigned int)size >= sizeof(sockfd_attr_path)) { + err("exported device path length %i >= %lu or < 0", size, + (long unsigned)sizeof(sockfd_attr_path)); + return -1; + } + + size = snprintf(sockfd_buff, sizeof(sockfd_buff), "%d\n", sockfd); + if (size < 0 || (unsigned int)size >= sizeof(sockfd_buff)) { + err("socket length %i >= %lu or < 0", size, + (long unsigned)sizeof(sockfd_buff)); + return -1; + } + + ret = write_sysfs_attribute(sockfd_attr_path, sockfd_buff, + strlen(sockfd_buff)); + if (ret < 0) { + err("write_sysfs_attribute failed: sockfd %s to %s", + sockfd_buff, sockfd_attr_path); + return ret; + } + + info("connect: %s", edev->udev.busid); + + return ret; +} + +struct usbip_exported_device *usbip_generic_get_device( + struct usbip_host_driver *hdriver, int num) +{ + struct list_head *i; + struct usbip_exported_device *edev; + int cnt = 0; + + list_for_each(i, &hdriver->edev_list) { + edev = list_entry(i, struct usbip_exported_device, node); + if (num == cnt) + return edev; + cnt++; + } + + return NULL; +} diff --git a/tools/usb/usbip/libsrc/usbip_host_common.h b/tools/usb/usbip/libsrc/usbip_host_common.h new file mode 100644 index 000000000..f46967c0a --- /dev/null +++ b/tools/usb/usbip/libsrc/usbip_host_common.h @@ -0,0 +1,92 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2015-2016 Samsung Electronics + * Igor Kotrasinski <i.kotrasinsk@samsung.com> + * Krzysztof Opasiak <k.opasiak@samsung.com> + * + * Refactored from usbip_host_driver.c, which is: + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#ifndef __USBIP_HOST_COMMON_H +#define __USBIP_HOST_COMMON_H + +#include <stdint.h> +#include <libudev.h> +#include <errno.h> +#include "list.h" +#include "usbip_common.h" +#include "sysfs_utils.h" + +struct usbip_host_driver; + +struct usbip_host_driver_ops { + int (*open)(struct usbip_host_driver *hdriver); + void (*close)(struct usbip_host_driver *hdriver); + int (*refresh_device_list)(struct usbip_host_driver *hdriver); + struct usbip_exported_device * (*get_device)( + struct usbip_host_driver *hdriver, int num); + + int (*read_device)(struct udev_device *sdev, + struct usbip_usb_device *dev); + int (*read_interface)(struct usbip_usb_device *udev, int i, + struct usbip_usb_interface *uinf); + int (*is_my_device)(struct udev_device *udev); +}; + +struct usbip_host_driver { + int ndevs; + /* list of exported device */ + struct list_head edev_list; + const char *udev_subsystem; + struct usbip_host_driver_ops ops; +}; + +struct usbip_exported_device { + struct udev_device *sudev; + int32_t status; + struct usbip_usb_device udev; + struct list_head node; + struct usbip_usb_interface uinf[]; +}; + +/* External API to access the driver */ +static inline int usbip_driver_open(struct usbip_host_driver *hdriver) +{ + if (!hdriver->ops.open) + return -EOPNOTSUPP; + return hdriver->ops.open(hdriver); +} + +static inline void usbip_driver_close(struct usbip_host_driver *hdriver) +{ + if (!hdriver->ops.close) + return; + hdriver->ops.close(hdriver); +} + +static inline int usbip_refresh_device_list(struct usbip_host_driver *hdriver) +{ + if (!hdriver->ops.refresh_device_list) + return -EOPNOTSUPP; + return hdriver->ops.refresh_device_list(hdriver); +} + +static inline struct usbip_exported_device * +usbip_get_device(struct usbip_host_driver *hdriver, int num) +{ + if (!hdriver->ops.get_device) + return NULL; + return hdriver->ops.get_device(hdriver, num); +} + +/* Helper functions for implementing driver backend */ +int usbip_generic_driver_open(struct usbip_host_driver *hdriver); +void usbip_generic_driver_close(struct usbip_host_driver *hdriver); +int usbip_generic_refresh_device_list(struct usbip_host_driver *hdriver); +int usbip_export_device(struct usbip_exported_device *edev, int sockfd); +struct usbip_exported_device *usbip_generic_get_device( + struct usbip_host_driver *hdriver, int num); + +#endif /* __USBIP_HOST_COMMON_H */ diff --git a/tools/usb/usbip/libsrc/usbip_host_driver.c b/tools/usb/usbip/libsrc/usbip_host_driver.c new file mode 100644 index 000000000..573e73ec3 --- /dev/null +++ b/tools/usb/usbip/libsrc/usbip_host_driver.c @@ -0,0 +1,53 @@ +// 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 <unistd.h> +#include <libudev.h> + +#include "usbip_host_common.h" +#include "usbip_host_driver.h" + +#undef PROGNAME +#define PROGNAME "libusbip" + +static int is_my_device(struct udev_device *dev) +{ + const char *driver; + + driver = udev_device_get_driver(dev); + return driver != NULL && !strcmp(driver, USBIP_HOST_DRV_NAME); +} + +static int usbip_host_driver_open(struct usbip_host_driver *hdriver) +{ + int ret; + + hdriver->ndevs = 0; + INIT_LIST_HEAD(&hdriver->edev_list); + + ret = usbip_generic_driver_open(hdriver); + if (ret) + err("please load " USBIP_CORE_MOD_NAME ".ko and " + USBIP_HOST_DRV_NAME ".ko!"); + return ret; +} + +struct usbip_host_driver host_driver = { + .edev_list = LIST_HEAD_INIT(host_driver.edev_list), + .udev_subsystem = "usb", + .ops = { + .open = usbip_host_driver_open, + .close = usbip_generic_driver_close, + .refresh_device_list = usbip_generic_refresh_device_list, + .get_device = usbip_generic_get_device, + .read_device = read_usb_device, + .read_interface = read_usb_interface, + .is_my_device = is_my_device, + }, +}; diff --git a/tools/usb/usbip/libsrc/usbip_host_driver.h b/tools/usb/usbip/libsrc/usbip_host_driver.h new file mode 100644 index 000000000..6ba996c5a --- /dev/null +++ b/tools/usb/usbip/libsrc/usbip_host_driver.h @@ -0,0 +1,20 @@ +/* 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> + */ + +#ifndef __USBIP_HOST_DRIVER_H +#define __USBIP_HOST_DRIVER_H + +#include <stdint.h> +#include "usbip_common.h" +#include "list.h" +#include "usbip_host_common.h" + +extern struct usbip_host_driver host_driver; + +#endif /* __USBIP_HOST_DRIVER_H */ diff --git a/tools/usb/usbip/libsrc/vhci_driver.c b/tools/usb/usbip/libsrc/vhci_driver.c new file mode 100644 index 000000000..8159fd986 --- /dev/null +++ b/tools/usb/usbip/libsrc/vhci_driver.c @@ -0,0 +1,467 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2005-2007 Takahiro Hirofuchi + */ + +#include "usbip_common.h" +#include "vhci_driver.h" +#include <limits.h> +#include <netdb.h> +#include <libudev.h> +#include <dirent.h> +#include "sysfs_utils.h" + +#undef PROGNAME +#define PROGNAME "libusbip" + +struct usbip_vhci_driver *vhci_driver; +struct udev *udev_context; + +static struct usbip_imported_device * +imported_device_init(struct usbip_imported_device *idev, char *busid) +{ + struct udev_device *sudev; + + sudev = udev_device_new_from_subsystem_sysname(udev_context, + "usb", busid); + if (!sudev) { + dbg("udev_device_new_from_subsystem_sysname failed: %s", busid); + goto err; + } + read_usb_device(sudev, &idev->udev); + udev_device_unref(sudev); + + return idev; + +err: + return NULL; +} + +static int parse_status(const char *value) +{ + int ret = 0; + char *c; + + /* skip a header line */ + c = strchr(value, '\n'); + if (!c) + return -1; + c++; + + while (*c != '\0') { + int port, status, speed, devid; + int sockfd; + char lbusid[SYSFS_BUS_ID_SIZE]; + struct usbip_imported_device *idev; + char hub[3]; + + ret = sscanf(c, "%2s %d %d %d %x %u %31s\n", + hub, &port, &status, &speed, + &devid, &sockfd, lbusid); + + if (ret < 5) { + dbg("sscanf failed: %d", ret); + BUG(); + } + + dbg("hub %s port %d status %d speed %d devid %x", + hub, port, status, speed, devid); + dbg("sockfd %u lbusid %s", sockfd, lbusid); + + /* if a device is connected, look at it */ + idev = &vhci_driver->idev[port]; + memset(idev, 0, sizeof(*idev)); + + if (strncmp("hs", hub, 2) == 0) + idev->hub = HUB_SPEED_HIGH; + else /* strncmp("ss", hub, 2) == 0 */ + idev->hub = HUB_SPEED_SUPER; + + idev->port = port; + idev->status = status; + + idev->devid = devid; + + idev->busnum = (devid >> 16); + idev->devnum = (devid & 0x0000ffff); + + 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; +} + +#define MAX_STATUS_NAME 18 + +static int refresh_imported_device_list(void) +{ + const char *attr_status; + char status[MAX_STATUS_NAME+1] = "status"; + int i, ret; + + for (i = 0; i < vhci_driver->ncontrollers; i++) { + if (i > 0) + snprintf(status, sizeof(status), "status.%d", i); + + attr_status = udev_device_get_sysattr_value(vhci_driver->hc_device, + status); + if (!attr_status) { + err("udev_device_get_sysattr_value failed"); + return -1; + } + + dbg("controller %d", i); + + ret = parse_status(attr_status); + if (ret != 0) + return ret; + } + + return 0; +} + +static int get_nports(struct udev_device *hc_device) +{ + const char *attr_nports; + + attr_nports = udev_device_get_sysattr_value(hc_device, "nports"); + if (!attr_nports) { + err("udev_device_get_sysattr_value nports failed"); + return -1; + } + + return (int)strtoul(attr_nports, NULL, 10); +} + +static int vhci_hcd_filter(const struct dirent *dirent) +{ + return !strncmp(dirent->d_name, "vhci_hcd.", 9); +} + +static int get_ncontrollers(void) +{ + struct dirent **namelist; + struct udev_device *platform; + int n; + + platform = udev_device_get_parent(vhci_driver->hc_device); + if (platform == NULL) + return -1; + + n = scandir(udev_device_get_syspath(platform), &namelist, vhci_hcd_filter, NULL); + if (n < 0) + err("scandir failed"); + else { + for (int i = 0; i < n; i++) + free(namelist[i]); + free(namelist); + } + + return n; +} + +/* + * Read the given port's record. + * + * To avoid buffer overflow we will read the entire line and + * validate each part's size. The initial buffer is padded by 4 to + * accommodate the 2 spaces, 1 newline and an additional character + * which is needed to properly validate the 3rd part without it being + * truncated to an acceptable length. + */ +static int read_record(int rhport, char *host, unsigned long host_len, + char *port, unsigned long port_len, char *busid) +{ + int part; + FILE *file; + char path[PATH_MAX+1]; + char *buffer, *start, *end; + char delim[] = {' ', ' ', '\n'}; + int max_len[] = {(int)host_len, (int)port_len, SYSFS_BUS_ID_SIZE}; + size_t buffer_len = host_len + port_len + SYSFS_BUS_ID_SIZE + 4; + + buffer = malloc(buffer_len); + if (!buffer) + return -1; + + snprintf(path, PATH_MAX, VHCI_STATE_PATH"/port%d", rhport); + + file = fopen(path, "r"); + if (!file) { + err("fopen"); + free(buffer); + return -1; + } + + if (fgets(buffer, buffer_len, file) == NULL) { + err("fgets"); + free(buffer); + fclose(file); + return -1; + } + fclose(file); + + /* validate the length of each of the 3 parts */ + start = buffer; + for (part = 0; part < 3; part++) { + end = strchr(start, delim[part]); + if (end == NULL || (end - start) > max_len[part]) { + free(buffer); + return -1; + } + start = end + 1; + } + + if (sscanf(buffer, "%s %s %s\n", host, port, busid) != 3) { + err("sscanf"); + free(buffer); + return -1; + } + + free(buffer); + + return 0; +} + +/* ---------------------------------------------------------------------- */ + +int usbip_vhci_driver_open(void) +{ + int nports; + struct udev_device *hc_device; + + udev_context = udev_new(); + if (!udev_context) { + err("udev_new failed"); + return -1; + } + + /* will be freed in usbip_driver_close() */ + hc_device = + udev_device_new_from_subsystem_sysname(udev_context, + USBIP_VHCI_BUS_TYPE, + USBIP_VHCI_DEVICE_NAME); + if (!hc_device) { + err("udev_device_new_from_subsystem_sysname failed"); + goto err; + } + + nports = get_nports(hc_device); + if (nports <= 0) { + err("no available ports"); + goto err; + } + dbg("available ports: %d", nports); + + vhci_driver = calloc(1, sizeof(struct usbip_vhci_driver) + + nports * sizeof(struct usbip_imported_device)); + if (!vhci_driver) { + err("vhci_driver allocation failed"); + goto err; + } + + vhci_driver->nports = nports; + vhci_driver->hc_device = hc_device; + vhci_driver->ncontrollers = get_ncontrollers(); + dbg("available controllers: %d", vhci_driver->ncontrollers); + + if (vhci_driver->ncontrollers <=0) { + err("no available usb controllers"); + goto err; + } + + if (refresh_imported_device_list()) + goto err; + + return 0; + +err: + udev_device_unref(hc_device); + + if (vhci_driver) + free(vhci_driver); + + vhci_driver = NULL; + + udev_unref(udev_context); + + return -1; +} + + +void usbip_vhci_driver_close(void) +{ + if (!vhci_driver) + return; + + udev_device_unref(vhci_driver->hc_device); + + free(vhci_driver); + + vhci_driver = NULL; + + udev_unref(udev_context); +} + + +int usbip_vhci_refresh_device_list(void) +{ + + if (refresh_imported_device_list()) + goto err; + + return 0; +err: + dbg("failed to refresh device list"); + return -1; +} + + +int usbip_vhci_get_free_port(uint32_t speed) +{ + for (int i = 0; i < vhci_driver->nports; i++) { + + switch (speed) { + case USB_SPEED_SUPER: + if (vhci_driver->idev[i].hub != HUB_SPEED_SUPER) + continue; + break; + default: + if (vhci_driver->idev[i].hub != HUB_SPEED_HIGH) + continue; + break; + } + + if (vhci_driver->idev[i].status == VDEV_ST_NULL) + return vhci_driver->idev[i].port; + } + + return -1; +} + +int usbip_vhci_attach_device2(uint8_t port, int sockfd, uint32_t devid, + uint32_t speed) { + char buff[200]; /* what size should be ? */ + char attach_attr_path[SYSFS_PATH_MAX]; + char attr_attach[] = "attach"; + const char *path; + int ret; + + snprintf(buff, sizeof(buff), "%u %d %u %u", + port, sockfd, devid, speed); + dbg("writing: %s", buff); + + path = udev_device_get_syspath(vhci_driver->hc_device); + snprintf(attach_attr_path, sizeof(attach_attr_path), "%s/%s", + path, attr_attach); + dbg("attach attribute path: %s", attach_attr_path); + + ret = write_sysfs_attribute(attach_attr_path, buff, strlen(buff)); + if (ret < 0) { + dbg("write_sysfs_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) +{ + char detach_attr_path[SYSFS_PATH_MAX]; + char attr_detach[] = "detach"; + char buff[200]; /* what size should be ? */ + const char *path; + int ret; + + snprintf(buff, sizeof(buff), "%u", port); + dbg("writing: %s", buff); + + path = udev_device_get_syspath(vhci_driver->hc_device); + snprintf(detach_attr_path, sizeof(detach_attr_path), "%s/%s", + path, attr_detach); + dbg("detach attribute path: %s", detach_attr_path); + + ret = write_sysfs_attribute(detach_attr_path, buff, strlen(buff)); + if (ret < 0) { + dbg("write_sysfs_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, sizeof(host), serv, sizeof(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; +} diff --git a/tools/usb/usbip/libsrc/vhci_driver.h b/tools/usb/usbip/libsrc/vhci_driver.h new file mode 100644 index 000000000..6c9aca216 --- /dev/null +++ b/tools/usb/usbip/libsrc/vhci_driver.h @@ -0,0 +1,67 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2005-2007 Takahiro Hirofuchi + */ + +#ifndef __VHCI_DRIVER_H +#define __VHCI_DRIVER_H + +#include <libudev.h> +#include <stdint.h> + +#include "usbip_common.h" + +#define USBIP_VHCI_BUS_TYPE "platform" +#define USBIP_VHCI_DEVICE_NAME "vhci_hcd.0" + +enum hub_speed { + HUB_SPEED_HIGH = 0, + HUB_SPEED_SUPER, +}; + +struct usbip_imported_device { + enum hub_speed hub; + uint8_t port; + uint32_t status; + + uint32_t devid; + + uint8_t busnum; + uint8_t devnum; + + /* usbip_class_device list */ + struct usbip_usb_device udev; +}; + +struct usbip_vhci_driver { + + /* /sys/devices/platform/vhci_hcd */ + struct udev_device *hc_device; + + int ncontrollers; + int nports; + struct usbip_imported_device idev[]; +}; + + +extern struct usbip_vhci_driver *vhci_driver; + +int usbip_vhci_driver_open(void); +void usbip_vhci_driver_close(void); + +int usbip_vhci_refresh_device_list(void); + + +int usbip_vhci_get_free_port(uint32_t speed); +int usbip_vhci_attach_device2(uint8_t port, int sockfd, uint32_t devid, + uint32_t speed); + +/* will be removed */ +int usbip_vhci_attach_device(uint8_t port, int sockfd, uint8_t busnum, + uint8_t devnum, uint32_t speed); + +int usbip_vhci_detach_device(uint8_t port); + +int usbip_vhci_imported_device_dump(struct usbip_imported_device *idev); + +#endif /* __VHCI_DRIVER_H */ diff --git a/tools/usb/usbip/src/Makefile.am b/tools/usb/usbip/src/Makefile.am new file mode 100644 index 000000000..e26f39e05 --- /dev/null +++ b/tools/usb/usbip/src/Makefile.am @@ -0,0 +1,12 @@ +# SPDX-License-Identifier: GPL-2.0 +AM_CPPFLAGS = -I$(top_srcdir)/libsrc -DUSBIDS_FILE='"@USBIDS_DIR@/usb.ids"' +AM_CFLAGS = @EXTRA_CFLAGS@ +LDADD = $(top_builddir)/libsrc/libusbip.la + +sbin_PROGRAMS := usbip usbipd + +usbip_SOURCES := usbip.h utils.h usbip.c utils.c usbip_network.c \ + usbip_attach.c usbip_detach.c usbip_list.c \ + usbip_bind.c usbip_unbind.c usbip_port.c + +usbipd_SOURCES := usbip_network.h usbipd.c usbip_network.c diff --git a/tools/usb/usbip/src/usbip.c b/tools/usb/usbip/src/usbip.c new file mode 100644 index 000000000..f7c7220d9 --- /dev/null +++ b/tools/usb/usbip/src/usbip.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * command structure borrowed from udev + * (git://git.kernel.org/pub/scm/linux/hotplug/udev.git) + * + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#include <stdio.h> +#include <stdlib.h> + +#include <getopt.h> +#include <syslog.h> + +#include "usbip_common.h" +#include "usbip_network.h" +#include "usbip.h" + +static int usbip_help(int argc, char *argv[]); +static int usbip_version(int argc, char *argv[]); + +static const char usbip_version_string[] = PACKAGE_STRING; + +static const char usbip_usage_string[] = + "usbip [--debug] [--log] [--tcp-port PORT] [version]\n" + " [help] <command> <args>\n"; + +static void usbip_usage(void) +{ + printf("usage: %s", usbip_usage_string); +} + +struct command { + const char *name; + int (*fn)(int argc, char *argv[]); + const char *help; + void (*usage)(void); +}; + +static const struct command cmds[] = { + { + .name = "help", + .fn = usbip_help, + .help = NULL, + .usage = NULL + }, + { + .name = "version", + .fn = usbip_version, + .help = NULL, + .usage = NULL + }, + { + .name = "attach", + .fn = usbip_attach, + .help = "Attach a remote USB device", + .usage = usbip_attach_usage + }, + { + .name = "detach", + .fn = usbip_detach, + .help = "Detach a remote USB device", + .usage = usbip_detach_usage + }, + { + .name = "list", + .fn = usbip_list, + .help = "List exportable or local USB devices", + .usage = usbip_list_usage + }, + { + .name = "bind", + .fn = usbip_bind, + .help = "Bind device to " USBIP_HOST_DRV_NAME ".ko", + .usage = usbip_bind_usage + }, + { + .name = "unbind", + .fn = usbip_unbind, + .help = "Unbind device from " USBIP_HOST_DRV_NAME ".ko", + .usage = usbip_unbind_usage + }, + { + .name = "port", + .fn = usbip_port_show, + .help = "Show imported USB devices", + .usage = NULL + }, + { NULL, NULL, NULL, NULL } +}; + +static int usbip_help(int argc, char *argv[]) +{ + const struct command *cmd; + int i; + int ret = 0; + + if (argc > 1 && argv++) { + for (i = 0; cmds[i].name != NULL; i++) + if (!strcmp(cmds[i].name, argv[0]) && cmds[i].usage) { + cmds[i].usage(); + goto done; + } + ret = -1; + } + + usbip_usage(); + printf("\n"); + for (cmd = cmds; cmd->name != NULL; cmd++) + if (cmd->help != NULL) + printf(" %-10s %s\n", cmd->name, cmd->help); + printf("\n"); +done: + return ret; +} + +static int usbip_version(int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + printf(PROGNAME " (%s)\n", usbip_version_string); + return 0; +} + +static int run_command(const struct command *cmd, int argc, char *argv[]) +{ + dbg("running command: `%s'", cmd->name); + return cmd->fn(argc, argv); +} + +int main(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "debug", no_argument, NULL, 'd' }, + { "log", no_argument, NULL, 'l' }, + { "tcp-port", required_argument, NULL, 't' }, + { NULL, 0, NULL, 0 } + }; + + char *cmd; + int opt; + int i, rc = -1; + + usbip_use_stderr = 1; + opterr = 0; + for (;;) { + opt = getopt_long(argc, argv, "+dlt:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'd': + usbip_use_debug = 1; + break; + case 'l': + usbip_use_syslog = 1; + openlog("", LOG_PID, LOG_USER); + break; + case 't': + usbip_setup_port_number(optarg); + break; + case '?': + printf("usbip: invalid option\n"); + /* Terminate after printing error */ + /* FALLTHRU */ + default: + usbip_usage(); + goto out; + } + } + + cmd = argv[optind]; + if (cmd) { + for (i = 0; cmds[i].name != NULL; i++) + if (!strcmp(cmds[i].name, cmd)) { + argc -= optind; + argv += optind; + optind = 0; + rc = run_command(&cmds[i], argc, argv); + goto out; + } + } + + /* invalid command */ + usbip_help(0, NULL); +out: + return (rc > -1 ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/tools/usb/usbip/src/usbip.h b/tools/usb/usbip/src/usbip.h new file mode 100644 index 000000000..e31779290 --- /dev/null +++ b/tools/usb/usbip/src/usbip.h @@ -0,0 +1,28 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#ifndef __USBIP_H +#define __USBIP_H + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +/* usbip commands */ +int usbip_attach(int argc, char *argv[]); +int usbip_detach(int argc, char *argv[]); +int usbip_list(int argc, char *argv[]); +int usbip_bind(int argc, char *argv[]); +int usbip_unbind(int argc, char *argv[]); +int usbip_port_show(int argc, char *argv[]); + +void usbip_attach_usage(void); +void usbip_detach_usage(void); +void usbip_list_usage(void); +void usbip_bind_usage(void); +void usbip_unbind_usage(void); + +#endif /* __USBIP_H */ diff --git a/tools/usb/usbip/src/usbip_attach.c b/tools/usb/usbip/src/usbip_attach.c new file mode 100644 index 000000000..b4aeb9f1f --- /dev/null +++ b/tools/usb/usbip/src/usbip_attach.c @@ -0,0 +1,243 @@ +// 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/stat.h> + +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> + +#include <fcntl.h> +#include <getopt.h> +#include <unistd.h> +#include <errno.h> + +#include "vhci_driver.h" +#include "usbip_common.h" +#include "usbip_network.h" +#include "usbip.h" + +static const char usbip_attach_usage_string[] = + "usbip attach <args>\n" + " -r, --remote=<host> The machine with exported USB devices\n" + " -b, --busid=<busid> Busid of the device on <host>\n" + " -d, --device=<devid> Id of the virtual UDC on <host>\n"; + +void usbip_attach_usage(void) +{ + printf("usage: %s", usbip_attach_usage_string); +} + +#define MAX_BUFF 100 +static int record_connection(char *host, char *port, char *busid, int rhport) +{ + int fd; + char path[PATH_MAX+1]; + char buff[MAX_BUFF+1]; + int ret; + + ret = mkdir(VHCI_STATE_PATH, 0700); + if (ret < 0) { + /* if VHCI_STATE_PATH exists, then it better be a directory */ + if (errno == EEXIST) { + struct stat s; + + ret = stat(VHCI_STATE_PATH, &s); + if (ret < 0) + return -1; + if (!(s.st_mode & S_IFDIR)) + return -1; + } else + return -1; + } + + snprintf(path, PATH_MAX, VHCI_STATE_PATH"/port%d", rhport); + + fd = open(path, O_WRONLY|O_CREAT|O_TRUNC, S_IRWXU); + if (fd < 0) + return -1; + + snprintf(buff, MAX_BUFF, "%s %s %s\n", + host, port, busid); + + ret = write(fd, buff, strlen(buff)); + if (ret != (ssize_t) strlen(buff)) { + close(fd); + return -1; + } + + close(fd); + + return 0; +} + +static int import_device(int sockfd, struct usbip_usb_device *udev) +{ + int rc; + int port; + uint32_t speed = udev->speed; + + rc = usbip_vhci_driver_open(); + if (rc < 0) { + err("open vhci_driver"); + goto err_out; + } + + do { + port = usbip_vhci_get_free_port(speed); + if (port < 0) { + err("no free port"); + goto err_driver_close; + } + + dbg("got free port %d", port); + + rc = usbip_vhci_attach_device(port, sockfd, udev->busnum, + udev->devnum, udev->speed); + if (rc < 0 && errno != EBUSY) { + err("import device"); + goto err_driver_close; + } + } while (rc < 0); + + usbip_vhci_driver_close(); + + return port; + +err_driver_close: + usbip_vhci_driver_close(); +err_out: + return -1; +} + +static int query_import_device(int sockfd, char *busid) +{ + int rc; + struct op_import_request request; + struct op_import_reply reply; + uint16_t code = OP_REP_IMPORT; + int status; + + memset(&request, 0, sizeof(request)); + memset(&reply, 0, sizeof(reply)); + + /* send a request */ + rc = usbip_net_send_op_common(sockfd, OP_REQ_IMPORT, 0); + if (rc < 0) { + err("send op_common"); + return -1; + } + + strncpy(request.busid, busid, SYSFS_BUS_ID_SIZE-1); + + PACK_OP_IMPORT_REQUEST(0, &request); + + rc = usbip_net_send(sockfd, (void *) &request, sizeof(request)); + if (rc < 0) { + err("send op_import_request"); + return -1; + } + + /* receive a reply */ + rc = usbip_net_recv_op_common(sockfd, &code, &status); + if (rc < 0) { + err("Attach Request for %s failed - %s\n", + busid, usbip_op_common_status_string(status)); + return -1; + } + + rc = usbip_net_recv(sockfd, (void *) &reply, sizeof(reply)); + if (rc < 0) { + err("recv op_import_reply"); + return -1; + } + + PACK_OP_IMPORT_REPLY(0, &reply); + + /* check the reply */ + if (strncmp(reply.udev.busid, busid, SYSFS_BUS_ID_SIZE)) { + err("recv different busid %s", reply.udev.busid); + return -1; + } + + /* import a device */ + return import_device(sockfd, &reply.udev); +} + +static int attach_device(char *host, char *busid) +{ + int sockfd; + int rc; + int rhport; + + sockfd = usbip_net_tcp_connect(host, usbip_port_string); + if (sockfd < 0) { + err("tcp connect"); + return -1; + } + + rhport = query_import_device(sockfd, busid); + if (rhport < 0) + return -1; + + close(sockfd); + + rc = record_connection(host, usbip_port_string, busid, rhport); + if (rc < 0) { + err("record connection"); + return -1; + } + + return 0; +} + +int usbip_attach(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "remote", required_argument, NULL, 'r' }, + { "busid", required_argument, NULL, 'b' }, + { "device", required_argument, NULL, 'd' }, + { NULL, 0, NULL, 0 } + }; + char *host = NULL; + char *busid = NULL; + int opt; + int ret = -1; + + for (;;) { + opt = getopt_long(argc, argv, "d:r:b:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'r': + host = optarg; + break; + case 'd': + case 'b': + busid = optarg; + break; + default: + goto err_out; + } + } + + if (!host || !busid) + goto err_out; + + ret = attach_device(host, busid); + goto out; + +err_out: + usbip_attach_usage(); +out: + return ret; +} diff --git a/tools/usb/usbip/src/usbip_bind.c b/tools/usb/usbip/src/usbip_bind.c new file mode 100644 index 000000000..f1cf9225a --- /dev/null +++ b/tools/usb/usbip/src/usbip_bind.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#include <libudev.h> + +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <getopt.h> + +#include "usbip_common.h" +#include "utils.h" +#include "usbip.h" +#include "sysfs_utils.h" + +enum unbind_status { + UNBIND_ST_OK, + UNBIND_ST_USBIP_HOST, + UNBIND_ST_FAILED +}; + +static const char usbip_bind_usage_string[] = + "usbip bind <args>\n" + " -b, --busid=<busid> Bind " USBIP_HOST_DRV_NAME ".ko to device " + "on <busid>\n"; + +void usbip_bind_usage(void) +{ + printf("usage: %s", usbip_bind_usage_string); +} + +/* call at unbound state */ +static int bind_usbip(char *busid) +{ + char attr_name[] = "bind"; + char bind_attr_path[SYSFS_PATH_MAX]; + int rc = -1; + + snprintf(bind_attr_path, sizeof(bind_attr_path), "%s/%s/%s/%s/%s/%s", + SYSFS_MNT_PATH, SYSFS_BUS_NAME, SYSFS_BUS_TYPE, + SYSFS_DRIVERS_NAME, USBIP_HOST_DRV_NAME, attr_name); + + rc = write_sysfs_attribute(bind_attr_path, busid, strlen(busid)); + if (rc < 0) { + err("error binding device %s to driver: %s", busid, + strerror(errno)); + return -1; + } + + return 0; +} + +/* buggy driver may cause dead lock */ +static int unbind_other(char *busid) +{ + enum unbind_status status = UNBIND_ST_OK; + + char attr_name[] = "unbind"; + char unbind_attr_path[SYSFS_PATH_MAX]; + int rc = -1; + + struct udev *udev; + struct udev_device *dev; + const char *driver; + const char *bDevClass; + + /* Create libudev context. */ + udev = udev_new(); + + /* Get the device. */ + dev = udev_device_new_from_subsystem_sysname(udev, "usb", busid); + if (!dev) { + dbg("unable to find device with bus ID %s", busid); + goto err_close_busid_dev; + } + + /* Check what kind of device it is. */ + bDevClass = udev_device_get_sysattr_value(dev, "bDeviceClass"); + if (!bDevClass) { + dbg("unable to get bDevClass device attribute"); + goto err_close_busid_dev; + } + + if (!strncmp(bDevClass, "09", strlen(bDevClass))) { + dbg("skip unbinding of hub"); + goto err_close_busid_dev; + } + + /* Get the device driver. */ + driver = udev_device_get_driver(dev); + if (!driver) { + /* No driver bound to this device. */ + goto out; + } + + if (!strncmp(USBIP_HOST_DRV_NAME, driver, + strlen(USBIP_HOST_DRV_NAME))) { + /* Already bound to usbip-host. */ + status = UNBIND_ST_USBIP_HOST; + goto out; + } + + /* Unbind device from driver. */ + snprintf(unbind_attr_path, sizeof(unbind_attr_path), "%s/%s/%s/%s/%s/%s", + SYSFS_MNT_PATH, SYSFS_BUS_NAME, SYSFS_BUS_TYPE, + SYSFS_DRIVERS_NAME, driver, attr_name); + + rc = write_sysfs_attribute(unbind_attr_path, busid, strlen(busid)); + if (rc < 0) { + err("error unbinding device %s from driver", busid); + goto err_close_busid_dev; + } + + goto out; + +err_close_busid_dev: + status = UNBIND_ST_FAILED; +out: + udev_device_unref(dev); + udev_unref(udev); + + return status; +} + +static int bind_device(char *busid) +{ + int rc; + struct udev *udev; + struct udev_device *dev; + const char *devpath; + + /* Check whether the device with this bus ID exists. */ + udev = udev_new(); + dev = udev_device_new_from_subsystem_sysname(udev, "usb", busid); + if (!dev) { + err("device with the specified bus ID does not exist"); + return -1; + } + devpath = udev_device_get_devpath(dev); + udev_unref(udev); + + /* If the device is already attached to vhci_hcd - bail out */ + if (strstr(devpath, USBIP_VHCI_DRV_NAME)) { + err("bind loop detected: device: %s is attached to %s\n", + devpath, USBIP_VHCI_DRV_NAME); + return -1; + } + + rc = unbind_other(busid); + if (rc == UNBIND_ST_FAILED) { + err("could not unbind driver from device on busid %s", busid); + return -1; + } else if (rc == UNBIND_ST_USBIP_HOST) { + err("device on busid %s is already bound to %s", busid, + USBIP_HOST_DRV_NAME); + return -1; + } + + rc = modify_match_busid(busid, 1); + if (rc < 0) { + err("unable to bind device on %s", busid); + return -1; + } + + rc = bind_usbip(busid); + if (rc < 0) { + err("could not bind device to %s", USBIP_HOST_DRV_NAME); + modify_match_busid(busid, 0); + return -1; + } + + info("bind device on busid %s: complete", busid); + + return 0; +} + +int usbip_bind(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "busid", required_argument, NULL, 'b' }, + { NULL, 0, NULL, 0 } + }; + + int opt; + int ret = -1; + + for (;;) { + opt = getopt_long(argc, argv, "b:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'b': + ret = bind_device(optarg); + goto out; + default: + goto err_out; + } + } + +err_out: + usbip_bind_usage(); +out: + return ret; +} diff --git a/tools/usb/usbip/src/usbip_detach.c b/tools/usb/usbip/src/usbip_detach.c new file mode 100644 index 000000000..aec993159 --- /dev/null +++ b/tools/usb/usbip/src/usbip_detach.c @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#include <ctype.h> +#include <limits.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <getopt.h> +#include <unistd.h> + +#include "vhci_driver.h" +#include "usbip_common.h" +#include "usbip_network.h" +#include "usbip.h" + +static const char usbip_detach_usage_string[] = + "usbip detach <args>\n" + " -p, --port=<port> " USBIP_VHCI_DRV_NAME + " port the device is on\n"; + +void usbip_detach_usage(void) +{ + printf("usage: %s", usbip_detach_usage_string); +} + +static int detach_port(char *port) +{ + int ret = 0; + uint8_t portnum; + char path[PATH_MAX+1]; + int i; + struct usbip_imported_device *idev; + int found = 0; + + unsigned int port_len = strlen(port); + + for (unsigned int i = 0; i < port_len; i++) + if (!isdigit(port[i])) { + err("invalid port %s", port); + return -1; + } + + portnum = atoi(port); + + ret = usbip_vhci_driver_open(); + if (ret < 0) { + err("open vhci_driver"); + return -1; + } + + /* check for invalid port */ + for (i = 0; i < vhci_driver->nports; i++) { + idev = &vhci_driver->idev[i]; + + if (idev->port == portnum) { + found = 1; + if (idev->status != VDEV_ST_NULL) + break; + info("Port %d is already detached!\n", idev->port); + goto call_driver_close; + } + } + + if (!found) { + err("Invalid port %s > maxports %d", + port, vhci_driver->nports); + goto call_driver_close; + } + + /* remove the port state file */ + snprintf(path, PATH_MAX, VHCI_STATE_PATH"/port%d", portnum); + + remove(path); + rmdir(VHCI_STATE_PATH); + + ret = usbip_vhci_detach_device(portnum); + if (ret < 0) { + ret = -1; + err("Port %d detach request failed!\n", portnum); + goto call_driver_close; + } + info("Port %d is now detached!\n", portnum); + +call_driver_close: + usbip_vhci_driver_close(); + + return ret; +} + +int usbip_detach(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "port", required_argument, NULL, 'p' }, + { NULL, 0, NULL, 0 } + }; + int opt; + int ret = -1; + + for (;;) { + opt = getopt_long(argc, argv, "p:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'p': + ret = detach_port(optarg); + goto out; + default: + goto err_out; + } + } + +err_out: + usbip_detach_usage(); +out: + return ret; +} diff --git a/tools/usb/usbip/src/usbip_list.c b/tools/usb/usbip/src/usbip_list.c new file mode 100644 index 000000000..3d810bcca --- /dev/null +++ b/tools/usb/usbip/src/usbip_list.c @@ -0,0 +1,373 @@ +// 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" + " -d, --device List the local USB gadgets bound to usbip-vudc\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; +} diff --git a/tools/usb/usbip/src/usbip_network.c b/tools/usb/usbip/src/usbip_network.c new file mode 100644 index 000000000..ed4dc8c14 --- /dev/null +++ b/tools/usb/usbip/src/usbip_network.c @@ -0,0 +1,303 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#include <sys/socket.h> + +#include <string.h> + +#include <arpa/inet.h> +#include <netdb.h> +#include <netinet/tcp.h> +#include <unistd.h> + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + +#include "usbip_common.h" +#include "usbip_network.h" + +int usbip_port = 3240; +char *usbip_port_string = "3240"; + +void usbip_setup_port_number(char *arg) +{ + dbg("parsing port arg '%s'", arg); + char *end; + unsigned long int port = strtoul(arg, &end, 10); + + if (end == arg) { + err("port: could not parse '%s' as a decimal integer", arg); + return; + } + + if (*end != '\0') { + err("port: garbage at end of '%s'", arg); + return; + } + + if (port > UINT16_MAX) { + err("port: %s too high (max=%d)", + arg, UINT16_MAX); + return; + } + + usbip_port = port; + usbip_port_string = arg; + info("using port %d (\"%s\")", usbip_port, usbip_port_string); +} + +uint32_t usbip_net_pack_uint32_t(int pack, uint32_t num) +{ + uint32_t i; + + if (pack) + i = htonl(num); + else + i = ntohl(num); + + return i; +} + +uint16_t usbip_net_pack_uint16_t(int pack, uint16_t num) +{ + uint16_t i; + + if (pack) + i = htons(num); + else + i = ntohs(num); + + return i; +} + +void usbip_net_pack_usb_device(int pack, struct usbip_usb_device *udev) +{ + udev->busnum = usbip_net_pack_uint32_t(pack, udev->busnum); + udev->devnum = usbip_net_pack_uint32_t(pack, udev->devnum); + udev->speed = usbip_net_pack_uint32_t(pack, udev->speed); + + udev->idVendor = usbip_net_pack_uint16_t(pack, udev->idVendor); + udev->idProduct = usbip_net_pack_uint16_t(pack, udev->idProduct); + udev->bcdDevice = usbip_net_pack_uint16_t(pack, udev->bcdDevice); +} + +void usbip_net_pack_usb_interface(int pack __attribute__((unused)), + struct usbip_usb_interface *udev + __attribute__((unused))) +{ + /* uint8_t members need nothing */ +} + +static ssize_t usbip_net_xmit(int sockfd, void *buff, size_t bufflen, + int sending) +{ + ssize_t nbytes; + ssize_t total = 0; + + if (!bufflen) + return 0; + + do { + if (sending) + nbytes = send(sockfd, buff, bufflen, 0); + else + nbytes = recv(sockfd, buff, bufflen, MSG_WAITALL); + + if (nbytes <= 0) + return -1; + + buff = (void *)((intptr_t) buff + nbytes); + bufflen -= nbytes; + total += nbytes; + + } while (bufflen > 0); + + return total; +} + +ssize_t usbip_net_recv(int sockfd, void *buff, size_t bufflen) +{ + return usbip_net_xmit(sockfd, buff, bufflen, 0); +} + +ssize_t usbip_net_send(int sockfd, void *buff, size_t bufflen) +{ + return usbip_net_xmit(sockfd, buff, bufflen, 1); +} + +static inline void usbip_net_pack_op_common(int pack, + struct op_common *op_common) +{ + op_common->version = usbip_net_pack_uint16_t(pack, op_common->version); + op_common->code = usbip_net_pack_uint16_t(pack, op_common->code); + op_common->status = usbip_net_pack_uint32_t(pack, op_common->status); +} + +int usbip_net_send_op_common(int sockfd, uint32_t code, uint32_t status) +{ + struct op_common op_common; + int rc; + + memset(&op_common, 0, sizeof(op_common)); + + op_common.version = USBIP_VERSION; + op_common.code = code; + op_common.status = status; + + usbip_net_pack_op_common(1, &op_common); + + rc = usbip_net_send(sockfd, &op_common, sizeof(op_common)); + if (rc < 0) { + dbg("usbip_net_send failed: %d", rc); + return -1; + } + + return 0; +} + +int usbip_net_recv_op_common(int sockfd, uint16_t *code, int *status) +{ + struct op_common op_common; + int rc; + + memset(&op_common, 0, sizeof(op_common)); + + rc = usbip_net_recv(sockfd, &op_common, sizeof(op_common)); + if (rc < 0) { + dbg("usbip_net_recv failed: %d", rc); + goto err; + } + + usbip_net_pack_op_common(0, &op_common); + + if (op_common.version != USBIP_VERSION) { + err("USBIP Kernel and tool version mismatch: %d %d:", + op_common.version, USBIP_VERSION); + goto err; + } + + switch (*code) { + case OP_UNSPEC: + break; + default: + if (op_common.code != *code) { + dbg("unexpected pdu %#0x for %#0x", op_common.code, + *code); + /* return error status */ + *status = ST_ERROR; + goto err; + } + } + + *status = op_common.status; + + if (op_common.status != ST_OK) { + dbg("request failed at peer: %d", op_common.status); + goto err; + } + + *code = op_common.code; + + return 0; +err: + return -1; +} + +int usbip_net_set_reuseaddr(int sockfd) +{ + const int val = 1; + int ret; + + ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); + if (ret < 0) + dbg("setsockopt: SO_REUSEADDR"); + + return ret; +} + +int usbip_net_set_nodelay(int sockfd) +{ + const int val = 1; + int ret; + + ret = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val)); + if (ret < 0) + dbg("setsockopt: TCP_NODELAY"); + + return ret; +} + +int usbip_net_set_keepalive(int sockfd) +{ + const int val = 1; + int ret; + + ret = setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof(val)); + if (ret < 0) + dbg("setsockopt: SO_KEEPALIVE"); + + return ret; +} + +int usbip_net_set_v6only(int sockfd) +{ + const int val = 1; + int ret; + + ret = setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, &val, sizeof(val)); + if (ret < 0) + dbg("setsockopt: IPV6_V6ONLY"); + + return ret; +} + +/* + * IPv6 Ready + */ +int usbip_net_tcp_connect(char *hostname, char *service) +{ + struct addrinfo hints, *res, *rp; + int sockfd; + int ret; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + /* get all possible addresses */ + ret = getaddrinfo(hostname, service, &hints, &res); + if (ret < 0) { + dbg("getaddrinfo: %s service %s: %s", hostname, service, + gai_strerror(ret)); + return ret; + } + + /* try the addresses */ + for (rp = res; rp; rp = rp->ai_next) { + sockfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sockfd < 0) + continue; + + /* should set TCP_NODELAY for usbip */ + usbip_net_set_nodelay(sockfd); + /* TODO: write code for heartbeat */ + usbip_net_set_keepalive(sockfd); + + if (connect(sockfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; + + close(sockfd); + } + + freeaddrinfo(res); + + if (!rp) + return EAI_SYSTEM; + + return sockfd; +} diff --git a/tools/usb/usbip/src/usbip_network.h b/tools/usb/usbip/src/usbip_network.h new file mode 100644 index 000000000..83b4c5344 --- /dev/null +++ b/tools/usb/usbip/src/usbip_network.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (C) 2005-2007 Takahiro Hirofuchi + */ + +#ifndef __USBIP_NETWORK_H +#define __USBIP_NETWORK_H + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#include <sys/types.h> + +#include <stdint.h> + +extern int usbip_port; +extern char *usbip_port_string; +void usbip_setup_port_number(char *arg); + +/* ---------------------------------------------------------------------- */ +/* Common header for all the kinds of PDUs. */ +struct op_common { + uint16_t version; + +#define OP_REQUEST (0x80 << 8) +#define OP_REPLY (0x00 << 8) + uint16_t code; + + /* status codes defined in usbip_common.h */ + uint32_t status; /* op_code status (for reply) */ + +} __attribute__((packed)); + +/* ---------------------------------------------------------------------- */ +/* Dummy Code */ +#define OP_UNSPEC 0x00 +#define OP_REQ_UNSPEC OP_UNSPEC +#define OP_REP_UNSPEC OP_UNSPEC + +/* ---------------------------------------------------------------------- */ +/* Retrieve USB device information. (still not used) */ +#define OP_DEVINFO 0x02 +#define OP_REQ_DEVINFO (OP_REQUEST | OP_DEVINFO) +#define OP_REP_DEVINFO (OP_REPLY | OP_DEVINFO) + +struct op_devinfo_request { + char busid[SYSFS_BUS_ID_SIZE]; +} __attribute__((packed)); + +struct op_devinfo_reply { + struct usbip_usb_device udev; + struct usbip_usb_interface uinf[]; +} __attribute__((packed)); + +/* ---------------------------------------------------------------------- */ +/* Import a remote USB device. */ +#define OP_IMPORT 0x03 +#define OP_REQ_IMPORT (OP_REQUEST | OP_IMPORT) +#define OP_REP_IMPORT (OP_REPLY | OP_IMPORT) + +struct op_import_request { + char busid[SYSFS_BUS_ID_SIZE]; +} __attribute__((packed)); + +struct op_import_reply { + struct usbip_usb_device udev; +// struct usbip_usb_interface uinf[]; +} __attribute__((packed)); + +#define PACK_OP_IMPORT_REQUEST(pack, request) do {\ +} while (0) + +#define PACK_OP_IMPORT_REPLY(pack, reply) do {\ + usbip_net_pack_usb_device(pack, &(reply)->udev);\ +} while (0) + +/* ---------------------------------------------------------------------- */ +/* Export a USB device to a remote host. */ +#define OP_EXPORT 0x06 +#define OP_REQ_EXPORT (OP_REQUEST | OP_EXPORT) +#define OP_REP_EXPORT (OP_REPLY | OP_EXPORT) + +struct op_export_request { + struct usbip_usb_device udev; +} __attribute__((packed)); + +struct op_export_reply { + int returncode; +} __attribute__((packed)); + + +#define PACK_OP_EXPORT_REQUEST(pack, request) do {\ + usbip_net_pack_usb_device(pack, &(request)->udev);\ +} while (0) + +#define PACK_OP_EXPORT_REPLY(pack, reply) do {\ +} while (0) + +/* ---------------------------------------------------------------------- */ +/* un-Export a USB device from a remote host. */ +#define OP_UNEXPORT 0x07 +#define OP_REQ_UNEXPORT (OP_REQUEST | OP_UNEXPORT) +#define OP_REP_UNEXPORT (OP_REPLY | OP_UNEXPORT) + +struct op_unexport_request { + struct usbip_usb_device udev; +} __attribute__((packed)); + +struct op_unexport_reply { + int returncode; +} __attribute__((packed)); + +#define PACK_OP_UNEXPORT_REQUEST(pack, request) do {\ + usbip_net_pack_usb_device(pack, &(request)->udev);\ +} while (0) + +#define PACK_OP_UNEXPORT_REPLY(pack, reply) do {\ +} while (0) + +/* ---------------------------------------------------------------------- */ +/* Negotiate IPSec encryption key. (still not used) */ +#define OP_CRYPKEY 0x04 +#define OP_REQ_CRYPKEY (OP_REQUEST | OP_CRYPKEY) +#define OP_REP_CRYPKEY (OP_REPLY | OP_CRYPKEY) + +struct op_crypkey_request { + /* 128bit key */ + uint32_t key[4]; +} __attribute__((packed)); + +struct op_crypkey_reply { + uint32_t __reserved; +} __attribute__((packed)); + + +/* ---------------------------------------------------------------------- */ +/* Retrieve the list of exported USB devices. */ +#define OP_DEVLIST 0x05 +#define OP_REQ_DEVLIST (OP_REQUEST | OP_DEVLIST) +#define OP_REP_DEVLIST (OP_REPLY | OP_DEVLIST) + +struct op_devlist_request { +} __attribute__((packed)); + +struct op_devlist_reply { + uint32_t ndev; + /* followed by reply_extra[] */ +} __attribute__((packed)); + +struct op_devlist_reply_extra { + struct usbip_usb_device udev; + struct usbip_usb_interface uinf[]; +} __attribute__((packed)); + +#define PACK_OP_DEVLIST_REQUEST(pack, request) do {\ +} while (0) + +#define PACK_OP_DEVLIST_REPLY(pack, reply) do {\ + (reply)->ndev = usbip_net_pack_uint32_t(pack, (reply)->ndev);\ +} while (0) + +uint32_t usbip_net_pack_uint32_t(int pack, uint32_t num); +uint16_t usbip_net_pack_uint16_t(int pack, uint16_t num); +void usbip_net_pack_usb_device(int pack, struct usbip_usb_device *udev); +void usbip_net_pack_usb_interface(int pack, struct usbip_usb_interface *uinf); + +ssize_t usbip_net_recv(int sockfd, void *buff, size_t bufflen); +ssize_t usbip_net_send(int sockfd, void *buff, size_t bufflen); +int usbip_net_send_op_common(int sockfd, uint32_t code, uint32_t status); +int usbip_net_recv_op_common(int sockfd, uint16_t *code, int *status); +int usbip_net_set_reuseaddr(int sockfd); +int usbip_net_set_nodelay(int sockfd); +int usbip_net_set_keepalive(int sockfd); +int usbip_net_set_v6only(int sockfd); +int usbip_net_tcp_connect(char *hostname, char *port); + +#endif /* __USBIP_NETWORK_H */ diff --git a/tools/usb/usbip/src/usbip_port.c b/tools/usb/usbip/src/usbip_port.c new file mode 100644 index 000000000..4d14387df --- /dev/null +++ b/tools/usb/usbip/src/usbip_port.c @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#include "vhci_driver.h" +#include "usbip_common.h" + +static int list_imported_devices(void) +{ + int i; + struct usbip_imported_device *idev; + int ret; + + if (usbip_names_init(USBIDS_FILE)) + err("failed to open %s", USBIDS_FILE); + + ret = usbip_vhci_driver_open(); + if (ret < 0) { + err("open vhci_driver"); + goto err_names_free; + } + + printf("Imported USB devices\n"); + printf("====================\n"); + + for (i = 0; i < vhci_driver->nports; i++) { + idev = &vhci_driver->idev[i]; + + if (usbip_vhci_imported_device_dump(idev) < 0) + goto err_driver_close; + } + + usbip_vhci_driver_close(); + usbip_names_free(); + + return ret; + +err_driver_close: + usbip_vhci_driver_close(); +err_names_free: + usbip_names_free(); + return -1; +} + +int usbip_port_show(__attribute__((unused)) int argc, + __attribute__((unused)) char *argv[]) +{ + int ret; + + ret = list_imported_devices(); + if (ret < 0) + err("list imported devices"); + + return ret; +} diff --git a/tools/usb/usbip/src/usbip_unbind.c b/tools/usb/usbip/src/usbip_unbind.c new file mode 100644 index 000000000..66a44d4a0 --- /dev/null +++ b/tools/usb/usbip/src/usbip_unbind.c @@ -0,0 +1,129 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#include <libudev.h> + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include <getopt.h> + +#include "usbip_common.h" +#include "utils.h" +#include "usbip.h" +#include "sysfs_utils.h" + +static const char usbip_unbind_usage_string[] = + "usbip unbind <args>\n" + " -b, --busid=<busid> Unbind " USBIP_HOST_DRV_NAME ".ko from " + "device on <busid>\n"; + +void usbip_unbind_usage(void) +{ + printf("usage: %s", usbip_unbind_usage_string); +} + +static int unbind_device(char *busid) +{ + char bus_type[] = "usb"; + int rc, ret = -1; + + char unbind_attr_name[] = "unbind"; + char unbind_attr_path[SYSFS_PATH_MAX]; + char rebind_attr_name[] = "rebind"; + char rebind_attr_path[SYSFS_PATH_MAX]; + + struct udev *udev; + struct udev_device *dev; + const char *driver; + + /* Create libudev context. */ + udev = udev_new(); + + /* Check whether the device with this bus ID exists. */ + dev = udev_device_new_from_subsystem_sysname(udev, "usb", busid); + if (!dev) { + err("device with the specified bus ID does not exist"); + goto err_close_udev; + } + + /* Check whether the device is using usbip-host driver. */ + driver = udev_device_get_driver(dev); + if (!driver || strcmp(driver, "usbip-host")) { + err("device is not bound to usbip-host driver"); + goto err_close_udev; + } + + /* Unbind device from driver. */ + snprintf(unbind_attr_path, sizeof(unbind_attr_path), "%s/%s/%s/%s/%s/%s", + SYSFS_MNT_PATH, SYSFS_BUS_NAME, bus_type, SYSFS_DRIVERS_NAME, + USBIP_HOST_DRV_NAME, unbind_attr_name); + + rc = write_sysfs_attribute(unbind_attr_path, busid, strlen(busid)); + if (rc < 0) { + err("error unbinding device %s from driver", busid); + goto err_close_udev; + } + + /* Notify driver of unbind. */ + rc = modify_match_busid(busid, 0); + if (rc < 0) { + err("unable to unbind device on %s", busid); + goto err_close_udev; + } + + /* Trigger new probing. */ + snprintf(rebind_attr_path, sizeof(unbind_attr_path), "%s/%s/%s/%s/%s/%s", + SYSFS_MNT_PATH, SYSFS_BUS_NAME, bus_type, SYSFS_DRIVERS_NAME, + USBIP_HOST_DRV_NAME, rebind_attr_name); + + rc = write_sysfs_attribute(rebind_attr_path, busid, strlen(busid)); + if (rc < 0) { + err("error rebinding"); + goto err_close_udev; + } + + ret = 0; + info("unbind device on busid %s: complete", busid); + +err_close_udev: + udev_device_unref(dev); + udev_unref(udev); + + return ret; +} + +int usbip_unbind(int argc, char *argv[]) +{ + static const struct option opts[] = { + { "busid", required_argument, NULL, 'b' }, + { NULL, 0, NULL, 0 } + }; + + int opt; + int ret = -1; + + for (;;) { + opt = getopt_long(argc, argv, "b:", opts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case 'b': + ret = unbind_device(optarg); + goto out; + default: + goto err_out; + } + } + +err_out: + usbip_unbind_usage(); +out: + return ret; +} diff --git a/tools/usb/usbip/src/usbipd.c b/tools/usb/usbip/src/usbipd.c new file mode 100644 index 000000000..48398a78e --- /dev/null +++ b/tools/usb/usbip/src/usbipd.c @@ -0,0 +1,686 @@ +// 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> + */ + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +#define _GNU_SOURCE +#include <errno.h> +#include <unistd.h> +#include <netdb.h> +#include <string.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netinet/in.h> + +#ifdef HAVE_LIBWRAP +#include <tcpd.h> +#endif + +#include <getopt.h> +#include <signal.h> +#include <poll.h> + +#include "usbip_host_driver.h" +#include "usbip_host_common.h" +#include "usbip_device_driver.h" +#include "usbip_common.h" +#include "usbip_network.h" +#include "list.h" + +#undef PROGNAME +#define PROGNAME "usbipd" +#define MAXSOCKFD 20 + +#define MAIN_LOOP_TIMEOUT 10 + +#define DEFAULT_PID_FILE "/var/run/" PROGNAME ".pid" + +static const char usbip_version_string[] = PACKAGE_STRING; + +static const char usbipd_help_string[] = + "usage: usbipd [options]\n" + "\n" + " -4, --ipv4\n" + " Bind to IPv4. Default is both.\n" + "\n" + " -6, --ipv6\n" + " Bind to IPv6. Default is both.\n" + "\n" + " -e, --device\n" + " Run in device mode.\n" + " Rather than drive an attached device, create\n" + " a virtual UDC to bind gadgets to.\n" + "\n" + " -D, --daemon\n" + " Run as a daemon process.\n" + "\n" + " -d, --debug\n" + " Print debugging information.\n" + "\n" + " -PFILE, --pid FILE\n" + " Write process id to FILE.\n" + " If no FILE specified, use " DEFAULT_PID_FILE "\n" + "\n" + " -tPORT, --tcp-port PORT\n" + " Listen on TCP/IP port PORT.\n" + "\n" + " -h, --help\n" + " Print this help.\n" + "\n" + " -v, --version\n" + " Show version.\n"; + +static struct usbip_host_driver *driver; + +static void usbipd_help(void) +{ + printf("%s\n", usbipd_help_string); +} + +static int recv_request_import(int sockfd) +{ + struct op_import_request req; + struct usbip_exported_device *edev; + struct usbip_usb_device pdu_udev; + struct list_head *i; + int found = 0; + int status = ST_OK; + int rc; + + memset(&req, 0, sizeof(req)); + + rc = usbip_net_recv(sockfd, &req, sizeof(req)); + if (rc < 0) { + dbg("usbip_net_recv failed: import request"); + return -1; + } + PACK_OP_IMPORT_REQUEST(0, &req); + + list_for_each(i, &driver->edev_list) { + edev = list_entry(i, struct usbip_exported_device, node); + if (!strncmp(req.busid, edev->udev.busid, SYSFS_BUS_ID_SIZE)) { + info("found requested device: %s", req.busid); + found = 1; + break; + } + } + + if (found) { + /* should set TCP_NODELAY for usbip */ + usbip_net_set_nodelay(sockfd); + + /* export device needs a TCP/IP socket descriptor */ + status = usbip_export_device(edev, sockfd); + if (status < 0) + status = ST_NA; + } else { + info("requested device not found: %s", req.busid); + status = ST_NODEV; + } + + rc = usbip_net_send_op_common(sockfd, OP_REP_IMPORT, status); + if (rc < 0) { + dbg("usbip_net_send_op_common failed: %#0x", OP_REP_IMPORT); + return -1; + } + + if (status) { + dbg("import request busid %s: failed", req.busid); + return -1; + } + + memcpy(&pdu_udev, &edev->udev, sizeof(pdu_udev)); + usbip_net_pack_usb_device(1, &pdu_udev); + + rc = usbip_net_send(sockfd, &pdu_udev, sizeof(pdu_udev)); + if (rc < 0) { + dbg("usbip_net_send failed: devinfo"); + return -1; + } + + dbg("import request busid %s: complete", req.busid); + + return 0; +} + +static int send_reply_devlist(int connfd) +{ + struct usbip_exported_device *edev; + struct usbip_usb_device pdu_udev; + struct usbip_usb_interface pdu_uinf; + struct op_devlist_reply reply; + struct list_head *j; + int rc, i; + + /* + * Exclude devices that are already exported to a client from + * the exportable device list to avoid: + * - import requests for devices that are exported only to + * fail the request. + * - revealing devices that are imported by a client to + * another client. + */ + + reply.ndev = 0; + /* number of exported devices */ + list_for_each(j, &driver->edev_list) { + edev = list_entry(j, struct usbip_exported_device, node); + if (edev->status != SDEV_ST_USED) + reply.ndev += 1; + } + info("exportable devices: %d", reply.ndev); + + rc = usbip_net_send_op_common(connfd, OP_REP_DEVLIST, ST_OK); + if (rc < 0) { + dbg("usbip_net_send_op_common failed: %#0x", OP_REP_DEVLIST); + return -1; + } + PACK_OP_DEVLIST_REPLY(1, &reply); + + rc = usbip_net_send(connfd, &reply, sizeof(reply)); + if (rc < 0) { + dbg("usbip_net_send failed: %#0x", OP_REP_DEVLIST); + return -1; + } + + list_for_each(j, &driver->edev_list) { + edev = list_entry(j, struct usbip_exported_device, node); + if (edev->status == SDEV_ST_USED) + continue; + + dump_usb_device(&edev->udev); + memcpy(&pdu_udev, &edev->udev, sizeof(pdu_udev)); + usbip_net_pack_usb_device(1, &pdu_udev); + + rc = usbip_net_send(connfd, &pdu_udev, sizeof(pdu_udev)); + if (rc < 0) { + dbg("usbip_net_send failed: pdu_udev"); + return -1; + } + + for (i = 0; i < edev->udev.bNumInterfaces; i++) { + dump_usb_interface(&edev->uinf[i]); + memcpy(&pdu_uinf, &edev->uinf[i], sizeof(pdu_uinf)); + usbip_net_pack_usb_interface(1, &pdu_uinf); + + rc = usbip_net_send(connfd, &pdu_uinf, + sizeof(pdu_uinf)); + if (rc < 0) { + err("usbip_net_send failed: pdu_uinf"); + return -1; + } + } + } + + return 0; +} + +static int recv_request_devlist(int connfd) +{ + struct op_devlist_request req; + int rc; + + memset(&req, 0, sizeof(req)); + + rc = usbip_net_recv(connfd, &req, sizeof(req)); + if (rc < 0) { + dbg("usbip_net_recv failed: devlist request"); + return -1; + } + + rc = send_reply_devlist(connfd); + if (rc < 0) { + dbg("send_reply_devlist failed"); + return -1; + } + + return 0; +} + +static int recv_pdu(int connfd) +{ + uint16_t code = OP_UNSPEC; + int ret; + int status; + + ret = usbip_net_recv_op_common(connfd, &code, &status); + if (ret < 0) { + dbg("could not receive opcode: %#0x", code); + return -1; + } + + ret = usbip_refresh_device_list(driver); + if (ret < 0) { + dbg("could not refresh device list: %d", ret); + return -1; + } + + info("received request: %#0x(%d)", code, connfd); + switch (code) { + case OP_REQ_DEVLIST: + ret = recv_request_devlist(connfd); + break; + case OP_REQ_IMPORT: + ret = recv_request_import(connfd); + break; + case OP_REQ_DEVINFO: + case OP_REQ_CRYPKEY: + default: + err("received an unknown opcode: %#0x", code); + ret = -1; + } + + if (ret == 0) + info("request %#0x(%d): complete", code, connfd); + else + info("request %#0x(%d): failed", code, connfd); + + return ret; +} + +#ifdef HAVE_LIBWRAP +static int tcpd_auth(int connfd) +{ + struct request_info request; + int rc; + + request_init(&request, RQ_DAEMON, PROGNAME, RQ_FILE, connfd, 0); + fromhost(&request); + rc = hosts_access(&request); + if (rc == 0) + return -1; + + return 0; +} +#endif + +static int do_accept(int listenfd) +{ + int connfd; + struct sockaddr_storage ss; + socklen_t len = sizeof(ss); + char host[NI_MAXHOST], port[NI_MAXSERV]; + int rc; + + memset(&ss, 0, sizeof(ss)); + + connfd = accept(listenfd, (struct sockaddr *)&ss, &len); + if (connfd < 0) { + err("failed to accept connection"); + return -1; + } + + rc = getnameinfo((struct sockaddr *)&ss, len, host, sizeof(host), + port, sizeof(port), NI_NUMERICHOST | NI_NUMERICSERV); + if (rc) + err("getnameinfo: %s", gai_strerror(rc)); + +#ifdef HAVE_LIBWRAP + rc = tcpd_auth(connfd); + if (rc < 0) { + info("denied access from %s", host); + close(connfd); + return -1; + } +#endif + info("connection from %s:%s", host, port); + + return connfd; +} + +int process_request(int listenfd) +{ + pid_t childpid; + int connfd; + + connfd = do_accept(listenfd); + if (connfd < 0) + return -1; + childpid = fork(); + if (childpid == 0) { + close(listenfd); + recv_pdu(connfd); + exit(0); + } + close(connfd); + return 0; +} + +static void addrinfo_to_text(struct addrinfo *ai, char buf[], + const size_t buf_size) +{ + char hbuf[NI_MAXHOST]; + char sbuf[NI_MAXSERV]; + int rc; + + buf[0] = '\0'; + + rc = getnameinfo(ai->ai_addr, ai->ai_addrlen, hbuf, sizeof(hbuf), + sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV); + if (rc) + err("getnameinfo: %s", gai_strerror(rc)); + + snprintf(buf, buf_size, "%s:%s", hbuf, sbuf); +} + +static int listen_all_addrinfo(struct addrinfo *ai_head, int sockfdlist[], + int maxsockfd) +{ + struct addrinfo *ai; + int ret, nsockfd = 0; + const size_t ai_buf_size = NI_MAXHOST + NI_MAXSERV + 2; + char ai_buf[ai_buf_size]; + + for (ai = ai_head; ai && nsockfd < maxsockfd; ai = ai->ai_next) { + int sock; + + addrinfo_to_text(ai, ai_buf, ai_buf_size); + dbg("opening %s", ai_buf); + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock < 0) { + err("socket: %s: %d (%s)", + ai_buf, errno, strerror(errno)); + continue; + } + + usbip_net_set_reuseaddr(sock); + usbip_net_set_nodelay(sock); + /* We use seperate sockets for IPv4 and IPv6 + * (see do_standalone_mode()) */ + usbip_net_set_v6only(sock); + + ret = bind(sock, ai->ai_addr, ai->ai_addrlen); + if (ret < 0) { + err("bind: %s: %d (%s)", + ai_buf, errno, strerror(errno)); + close(sock); + continue; + } + + ret = listen(sock, SOMAXCONN); + if (ret < 0) { + err("listen: %s: %d (%s)", + ai_buf, errno, strerror(errno)); + close(sock); + continue; + } + + info("listening on %s", ai_buf); + sockfdlist[nsockfd++] = sock; + } + + return nsockfd; +} + +static struct addrinfo *do_getaddrinfo(char *host, int ai_family) +{ + struct addrinfo hints, *ai_head; + int rc; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = ai_family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE; + + rc = getaddrinfo(host, usbip_port_string, &hints, &ai_head); + if (rc) { + err("failed to get a network address %s: %s", usbip_port_string, + gai_strerror(rc)); + return NULL; + } + + return ai_head; +} + +static void signal_handler(int i) +{ + dbg("received '%s' signal", strsignal(i)); +} + +static void set_signal(void) +{ + struct sigaction act; + + memset(&act, 0, sizeof(act)); + act.sa_handler = signal_handler; + sigemptyset(&act.sa_mask); + sigaction(SIGTERM, &act, NULL); + sigaction(SIGINT, &act, NULL); + act.sa_handler = SIG_IGN; + sigaction(SIGCHLD, &act, NULL); +} + +static const char *pid_file; + +static void write_pid_file(void) +{ + if (pid_file) { + dbg("creating pid file %s", pid_file); + FILE *fp; + + fp = fopen(pid_file, "w"); + if (!fp) { + err("pid_file: %s: %d (%s)", + pid_file, errno, strerror(errno)); + return; + } + fprintf(fp, "%d\n", getpid()); + fclose(fp); + } +} + +static void remove_pid_file(void) +{ + if (pid_file) { + dbg("removing pid file %s", pid_file); + unlink(pid_file); + } +} + +static int do_standalone_mode(int daemonize, int ipv4, int ipv6) +{ + struct addrinfo *ai_head; + int sockfdlist[MAXSOCKFD]; + int nsockfd, family; + int i, terminate; + struct pollfd *fds; + struct timespec timeout; + sigset_t sigmask; + + if (usbip_driver_open(driver)) + return -1; + + if (daemonize) { + if (daemon(0, 0) < 0) { + err("daemonizing failed: %s", strerror(errno)); + usbip_driver_close(driver); + return -1; + } + umask(0); + usbip_use_syslog = 1; + } + set_signal(); + write_pid_file(); + + info("starting " PROGNAME " (%s)", usbip_version_string); + + /* + * To suppress warnings on systems with bindv6only disabled + * (default), we use seperate sockets for IPv6 and IPv4 and set + * IPV6_V6ONLY on the IPv6 sockets. + */ + if (ipv4 && ipv6) + family = AF_UNSPEC; + else if (ipv4) + family = AF_INET; + else + family = AF_INET6; + + ai_head = do_getaddrinfo(NULL, family); + if (!ai_head) { + usbip_driver_close(driver); + return -1; + } + nsockfd = listen_all_addrinfo(ai_head, sockfdlist, + sizeof(sockfdlist) / sizeof(*sockfdlist)); + freeaddrinfo(ai_head); + if (nsockfd <= 0) { + err("failed to open a listening socket"); + usbip_driver_close(driver); + return -1; + } + + dbg("listening on %d address%s", nsockfd, (nsockfd == 1) ? "" : "es"); + + fds = calloc(nsockfd, sizeof(struct pollfd)); + for (i = 0; i < nsockfd; i++) { + fds[i].fd = sockfdlist[i]; + fds[i].events = POLLIN; + } + timeout.tv_sec = MAIN_LOOP_TIMEOUT; + timeout.tv_nsec = 0; + + sigfillset(&sigmask); + sigdelset(&sigmask, SIGTERM); + sigdelset(&sigmask, SIGINT); + + terminate = 0; + while (!terminate) { + int r; + + r = ppoll(fds, nsockfd, &timeout, &sigmask); + if (r < 0) { + dbg("%s", strerror(errno)); + terminate = 1; + } else if (r) { + for (i = 0; i < nsockfd; i++) { + if (fds[i].revents & POLLIN) { + dbg("read event on fd[%d]=%d", + i, sockfdlist[i]); + process_request(sockfdlist[i]); + } + } + } else { + dbg("heartbeat timeout on ppoll()"); + } + } + + info("shutting down " PROGNAME); + free(fds); + usbip_driver_close(driver); + + return 0; +} + +int main(int argc, char *argv[]) +{ + static const struct option longopts[] = { + { "ipv4", no_argument, NULL, '4' }, + { "ipv6", no_argument, NULL, '6' }, + { "daemon", no_argument, NULL, 'D' }, + { "daemon", no_argument, NULL, 'D' }, + { "debug", no_argument, NULL, 'd' }, + { "device", no_argument, NULL, 'e' }, + { "pid", optional_argument, NULL, 'P' }, + { "tcp-port", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 } + }; + + enum { + cmd_standalone_mode = 1, + cmd_help, + cmd_version + } cmd; + + int daemonize = 0; + int ipv4 = 0, ipv6 = 0; + int opt, rc = -1; + + pid_file = NULL; + + usbip_use_stderr = 1; + usbip_use_syslog = 0; + + if (geteuid() != 0) + err("not running as root?"); + + cmd = cmd_standalone_mode; + driver = &host_driver; + for (;;) { + opt = getopt_long(argc, argv, "46DdeP::t:hv", longopts, NULL); + + if (opt == -1) + break; + + switch (opt) { + case '4': + ipv4 = 1; + break; + case '6': + ipv6 = 1; + break; + case 'D': + daemonize = 1; + break; + case 'd': + usbip_use_debug = 1; + break; + case 'h': + cmd = cmd_help; + break; + case 'P': + pid_file = optarg ? optarg : DEFAULT_PID_FILE; + break; + case 't': + usbip_setup_port_number(optarg); + break; + case 'v': + cmd = cmd_version; + break; + case 'e': + driver = &device_driver; + break; + case '?': + usbipd_help(); + default: + goto err_out; + } + } + + if (!ipv4 && !ipv6) + ipv4 = ipv6 = 1; + + switch (cmd) { + case cmd_standalone_mode: + rc = do_standalone_mode(daemonize, ipv4, ipv6); + remove_pid_file(); + break; + case cmd_version: + printf(PROGNAME " (%s)\n", usbip_version_string); + rc = 0; + break; + case cmd_help: + usbipd_help(); + rc = 0; + break; + default: + usbipd_help(); + goto err_out; + } + +err_out: + return (rc > -1 ? EXIT_SUCCESS : EXIT_FAILURE); +} diff --git a/tools/usb/usbip/src/utils.c b/tools/usb/usbip/src/utils.c new file mode 100644 index 000000000..76a2e1247 --- /dev/null +++ b/tools/usb/usbip/src/utils.c @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#include "usbip_common.h" +#include "utils.h" +#include "sysfs_utils.h" + +int modify_match_busid(char *busid, int add) +{ + char attr_name[] = "match_busid"; + char command[SYSFS_BUS_ID_SIZE + 4]; + char match_busid_attr_path[SYSFS_PATH_MAX]; + int rc; + int cmd_size; + + snprintf(match_busid_attr_path, sizeof(match_busid_attr_path), + "%s/%s/%s/%s/%s/%s", SYSFS_MNT_PATH, SYSFS_BUS_NAME, + SYSFS_BUS_TYPE, SYSFS_DRIVERS_NAME, USBIP_HOST_DRV_NAME, + attr_name); + + if (add) + cmd_size = snprintf(command, SYSFS_BUS_ID_SIZE + 4, "add %s", + busid); + else + cmd_size = snprintf(command, SYSFS_BUS_ID_SIZE + 4, "del %s", + busid); + + rc = write_sysfs_attribute(match_busid_attr_path, command, + cmd_size); + if (rc < 0) { + dbg("failed to write match_busid: %s", strerror(errno)); + return -1; + } + + return 0; +} diff --git a/tools/usb/usbip/src/utils.h b/tools/usb/usbip/src/utils.h new file mode 100644 index 000000000..4fc13854f --- /dev/null +++ b/tools/usb/usbip/src/utils.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2011 matt mooney <mfm@muteddisk.com> + * 2005-2007 Takahiro Hirofuchi + */ + +#ifndef __UTILS_H +#define __UTILS_H + +int modify_match_busid(char *busid, int add); + +#endif /* __UTILS_H */ + diff --git a/tools/usb/usbip/vudc/vudc_server_example.sh b/tools/usb/usbip/vudc/vudc_server_example.sh new file mode 100755 index 000000000..fed53f51e --- /dev/null +++ b/tools/usb/usbip/vudc/vudc_server_example.sh @@ -0,0 +1,107 @@ +#!/bin/bash + +################################################################################ +# This is free and unencumbered software released into the public domain. +# +# Anyone is free to copy, modify, publish, use, compile, sell, or +# distribute this software, either in source code form or as a compiled +# binary, for any purpose, commercial or non-commercial, and by any +# means. +# +# In jurisdictions that recognize copyright laws, the author or authors +# of this software dedicate any and all copyright interest in the +# software to the public domain. We make this dedication for the benefit +# of the public at large and to the detriment of our heirs and +# successors. We intend this dedication to be an overt act of +# relinquishment in perpetuity of all present and future rights to this +# software under copyright law. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +# OTHER DEALINGS IN THE SOFTWARE. +# +# For more information, please refer to <https://unlicense.org/> +################################################################################ + +################################################################################ +# This is a sample script which shows how to use vUDC with ConfigFS gadgets +################################################################################ + +# Stop script on error +set -e + +################################################################################ +# Create your USB gadget +# You may use bare ConfigFS interface (as below) +# or libusbgx or gt toool +# Instead of ConfigFS gadgets you may use any of legacy gadgets. +################################################################################ +CONFIGFS_MOUNT_POINT="/sys/kernel/config" +GADGET_NAME="g1" +ID_VENDOR="0x1d6b" +ID_PRODUCT="0x0104" + +cd ${CONFIGFS_MOUNT_POINT}/usb_gadget +# Create a new USB gadget +mkdir ${GADGET_NAME} +cd ${GADGET_NAME} + +# This gadget contains one function - ACM (serial port over USB) +FUNC_DIR="functions/acm.ser0" +mkdir ${FUNC_DIR} + +# Just one configuration +mkdir configs/c.1 +ln -s ${FUNC_DIR} configs/c.1 + +# Set our gadget identity +echo ${ID_VENDOR} > idVendor +echo ${ID_PRODUCT} > idProduct + +################################################################################ +# Load vudc-module if vudc is not available +# You may change value of num param to get more than one vUDC instance +################################################################################ +[[ -d /sys/class/udc/usbip-vudc.0 ]] || modprobe usbip-vudc num=1 + +################################################################################ +# Bind gadget to our vUDC +# By default we bind to first one but you may change this if you would like +# to use more than one instance +################################################################################ +echo "usbip-vudc.0" > UDC + +################################################################################ +# Let's now run our usbip daemon in a USB device mode +################################################################################ +usbipd --device & + +################################################################################ +# Now your USB gadget is available using USB/IP protocol. +# To prepare your client, you should ensure that usbip-vhci module is inside +# your kernel. If it's not then you can load it: +# +# $ modprobe usbip-vhci +# +# To check availability of your gadget you may try to list devices exported +# on a remote server: +# +# $ modprobe usbip-vhci +# $ usbip list -r $SERVER_IP +# Exportable USB devices +# ====================== +# usbipd: info: request 0x8005(6): complete +# - 127.0.0.1 +# usbip-vudc.0: Linux Foundation : unknown product (1d6b:0104) +# : /sys/devices/platform/usbip-vudc.0 +# : (Defined at Interface level) (00/00/00) +# +# To attach this device to your client you may use: +# +# $ usbip attach -r $SERVER_IP -d usbip-vudc.0 +# +################################################################################ |