Skip to content

Commit

Permalink
Merge pull request #23 from xairy/iso
Browse files Browse the repository at this point in the history
Support proxying devices with isochronous IN endpoints
  • Loading branch information
AristoChen authored Aug 6, 2024
2 parents a8b2fde + 4f3a228 commit c6454ce
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 43 deletions.
47 changes: 39 additions & 8 deletions device-libusb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ int hotplug_callback(struct libusb_context *ctx __attribute__((unused)),
struct libusb_device *dev __attribute__((unused)),
libusb_hotplug_event envet __attribute__((unused)),
void *user_data __attribute__((unused))) {
printf("Hotplug event\n");

printf("Hotplug event: device disconnected, stopping proxy...\n");
kill(0, SIGINT);
return 0;
}
Expand Down Expand Up @@ -246,7 +245,7 @@ int control_request(const usb_ctrlrequest *setup_packet, int *nbytes,
}

int send_data(uint8_t endpoint, uint8_t attributes, uint8_t *dataptr,
int length) {
int length, int timeout) {
int transferred;
int attempt = 0;
int result = LIBUSB_SUCCESS;
Expand All @@ -263,7 +262,7 @@ int send_data(uint8_t endpoint, uint8_t attributes, uint8_t *dataptr,
break;
case USB_ENDPOINT_XFER_BULK:
do {
result = libusb_bulk_transfer(dev_handle, endpoint, dataptr, length, &transferred, 0);
result = libusb_bulk_transfer(dev_handle, endpoint, dataptr, length, &transferred, timeout);
//TODO retry transfer if incomplete
if (transferred != length) {
fprintf(stderr, "Incomplete Bulk transfer on EP%02x for attempt %d. length(%d), transferred(%d)\n",
Expand All @@ -285,7 +284,7 @@ int send_data(uint8_t endpoint, uint8_t attributes, uint8_t *dataptr,
&& attempt < MAX_ATTEMPTS);
break;
case USB_ENDPOINT_XFER_INT:
result = libusb_interrupt_transfer(dev_handle, endpoint, dataptr, length, &transferred, 0);
result = libusb_interrupt_transfer(dev_handle, endpoint, dataptr, length, &transferred, timeout);

if (transferred != length)
fprintf(stderr, "Incomplete Interrupt transfer on EP%02x\n", endpoint);
Expand All @@ -300,19 +299,51 @@ int send_data(uint8_t endpoint, uint8_t attributes, uint8_t *dataptr,
return result;
}

void iso_transfer_callback(struct libusb_transfer *transfer) {
int *iso_completed = (int *)transfer->user_data;
*iso_completed = 1;
}

int receive_data(uint8_t endpoint, uint8_t attributes, uint16_t maxPacketSize,
uint8_t **dataptr, int *length, int timeout) {
int result = LIBUSB_SUCCESS;
timeout = 0;
struct libusb_transfer *transfer;
int iso_completed, iso_packets;

int attempt = 0;
switch (attributes & USB_ENDPOINT_XFERTYPE_MASK) {
case USB_ENDPOINT_XFER_CONTROL:
fprintf(stderr, "Can't read on a control endpoint.\n");
break;
case USB_ENDPOINT_XFER_ISOC:
if (verbose_level)
fprintf(stderr, "Isochronous(read) endpoint EP%02x unhandled.\n", endpoint);
*dataptr = new uint8_t[maxPacketSize];
// We could retrieve multiple packets at a time, but then we
// would need to split the received data submit each packet
// separately via Raw Gadget. So retrieve only one packet for
// simplicity.
iso_packets = 1;
transfer = libusb_alloc_transfer(iso_packets);
if (!transfer) {
fprintf(stderr, "Failed to allocate libusb_transfer.\n");
result = LIBUSB_ERROR_OTHER;
}
iso_completed = 0;
libusb_fill_iso_transfer(transfer, dev_handle, endpoint, *dataptr, maxPacketSize,
iso_packets, iso_transfer_callback, &iso_completed, timeout);
libusb_set_iso_packet_lengths(transfer, maxPacketSize / iso_packets);
result = libusb_submit_transfer(transfer);
if (result != LIBUSB_SUCCESS) {
libusb_free_transfer(transfer);
break;
}
while (!iso_completed)
libusb_handle_events_completed(NULL, &iso_completed);
*length = 0;
for (int i = 0; i < iso_packets; i++)
*length += transfer->iso_packet_desc[i].actual_length;
if (result == LIBUSB_SUCCESS && verbose_level > 2)
printf("Received iso data(%d) bytes\n", *length);
libusb_free_transfer(transfer);
break;
case USB_ENDPOINT_XFER_BULK:
*dataptr = new uint8_t[maxPacketSize * 8];
Expand Down
4 changes: 3 additions & 1 deletion device-libusb.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#include "misc.h"

#define USB_REQUEST_TIMEOUT 1000

#define MAX_ATTEMPTS 5

extern libusb_device **devs;
Expand All @@ -23,6 +25,6 @@ void set_interface_alt_setting(int interface, int altsetting);
int control_request(const usb_ctrlrequest *setup_packet, int *nbytes,
unsigned char **dataptr, int timeout);
int send_data(uint8_t endpoint, uint8_t attributes, uint8_t *dataptr,
int length);
int length, int timeout);
int receive_data(uint8_t endpoint, uint8_t attributes, uint16_t maxPacketSize,
uint8_t **dataptr, int *length, int timeout);
15 changes: 15 additions & 0 deletions host-raw-gadget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,11 @@ int usb_raw_ep_read(int fd, struct usb_raw_ep_io *io) {
// Ignore failures caused by device reset.
return rv;
}
else if (errno == EINTR) {
// Ignore failures caused by sending a signal to the
// endpoint threads when shutting them down.
return rv;
}
else if (errno == EBUSY)
return rv;
perror("ioctl(USB_RAW_IOCTL_EP_READ)");
Expand All @@ -130,6 +135,16 @@ int usb_raw_ep_write(int fd, struct usb_raw_ep_io *io) {
// Ignore failures caused by device reset.
return rv;
}
else if (errno == EINTR) {
// Ignore failures caused by sending a signal to the
// endpoint threads when shutting them down.
return rv;
}
else if (errno == EXDEV || errno == ENODATA) {
// Ignore failures caused by sending an isochronous transfer
// too late (dwc3 returns EXDEV, dwc2 returns ENODATA).
return rv;
}
else if (errno == EBUSY)
return rv;
perror("ioctl(USB_RAW_IOCTL_EP_WRITE)");
Expand Down
21 changes: 2 additions & 19 deletions host-raw-gadget.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,33 +86,16 @@ struct usb_raw_eps_info {

/*----------------------------------------------------------------------*/

#define EP_MAX_PACKET_CONTROL 1024
#define EP_MAX_PACKET_BULK 1024
#define EP_MAX_PACKET_INT 8
#define MAX_TRANSFER_SIZE 4096

struct usb_raw_control_event {
struct usb_raw_event inner;
struct usb_ctrlrequest ctrl;
};

struct usb_raw_control_io {
struct usb_raw_ep_io inner;
char data[EP_MAX_PACKET_CONTROL];
};

struct usb_raw_bulk_io {
struct usb_raw_ep_io inner;
char data[EP_MAX_PACKET_BULK];
};

struct usb_raw_int_io {
struct usb_raw_ep_io inner;
char data[EP_MAX_PACKET_INT];
};

struct usb_raw_transfer_io {
struct usb_raw_ep_io inner;
char data[1024];
char data[MAX_TRANSFER_SIZE];
};

/*----------------------------------------------------------------------*/
Expand Down
104 changes: 89 additions & 15 deletions proxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ void printData(struct usb_raw_transfer_io io, __u8 bEndpointAddress, std::string
printf("\n");
}

void noop_signal_handler(int) { }

void *ep_loop_write(void *arg) {
struct thread_info thread_info = *((struct thread_info*) arg);
int fd = thread_info.fd;
Expand All @@ -112,6 +114,10 @@ void *ep_loop_write(void *arg) {
printf("Start writing thread for EP%02x, thread id(%d)\n",
ep.bEndpointAddress, gettid());

// Set a no-op handler for SIGUSR1. Sending this signal to the thread
// will thus interrupt a blocking ioctl call without other side-effects.
signal(SIGUSR1, noop_signal_handler);

while (!please_stop_eps) {
assert(ep_num != -1);
if (data_queue->size() == 0) {
Expand All @@ -134,6 +140,16 @@ void *ep_loop_write(void *arg) {
ep.bEndpointAddress, transfer_type.c_str(), dir.c_str());
break;
}
if (rv < 0 && errno == EINTR) {
printf("EP%x(%s_%s): interface likely changing, stopping thread\n",
ep.bEndpointAddress, transfer_type.c_str(), dir.c_str());
break;
}
if (rv < 0 && (errno == EXDEV || errno == ENODATA)) {
printf("EP%x(%s_%s): missed isochronous timing, ignoring transfer\n",
ep.bEndpointAddress, transfer_type.c_str(), dir.c_str());
continue;
}
else if (rv < 0) {
perror("usb_raw_ep_write()");
exit(EXIT_FAILURE);
Expand All @@ -147,7 +163,7 @@ void *ep_loop_write(void *arg) {
int length = io.inner.length;
unsigned char *data = new unsigned char[length];
memcpy(data, io.data, length);
int rv = send_data(ep.bEndpointAddress, ep.bmAttributes, data, length);
int rv = send_data(ep.bEndpointAddress, ep.bmAttributes, data, length, USB_REQUEST_TIMEOUT);
if (rv == LIBUSB_ERROR_NO_DEVICE) {
printf("EP%x(%s_%s): device likely reset, stopping thread\n",
ep.bEndpointAddress, transfer_type.c_str(), dir.c_str());
Expand Down Expand Up @@ -177,6 +193,10 @@ void *ep_loop_read(void *arg) {
printf("Start reading thread for EP%02x, thread id(%d)\n",
ep.bEndpointAddress, gettid());

// Set a no-op handler for SIGUSR1. Sending this signal to the thread
// will thus interrupt a blocking ioctl call without other side-effects.
signal(SIGUSR1, noop_signal_handler);

while (!please_stop_eps) {
assert(ep_num != -1);
struct usb_raw_transfer_io io;
Expand All @@ -190,7 +210,8 @@ void *ep_loop_read(void *arg) {
continue;
}

int rv = receive_data(ep.bEndpointAddress, ep.bmAttributes, ep.wMaxPacketSize, &data, &nbytes, 0);
int rv = receive_data(ep.bEndpointAddress, ep.bmAttributes, usb_endpoint_maxp(&ep),
&data, &nbytes, USB_REQUEST_TIMEOUT);
if (rv == LIBUSB_ERROR_NO_DEVICE) {
printf("EP%x(%s_%s): device likely reset, stopping thread\n",
ep.bEndpointAddress, transfer_type.c_str(), dir.c_str());
Expand Down Expand Up @@ -319,6 +340,16 @@ void terminate_eps(int fd, int config, int interface, int altsetting) {
for (int i = 0; i < alt->interface.bNumEndpoints; i++) {
struct raw_gadget_endpoint *ep = &alt->endpoints[i];

// Endpoint threads might be blocked either on a Raw Gadget
// ioctl or on a libusb transfer handling. To interrupt the
// former, we send the SIGUSR1 signal to the threads. The
// threads have a no-op handler set for this signal, so the
// ioctl gets interrupted with no other side-effects.
// The libusb transfer handling does get interrupted directly
// and instead times out.
pthread_kill(ep->thread_read, SIGUSR1);
pthread_kill(ep->thread_write, SIGUSR1);

if (ep->thread_read && pthread_join(ep->thread_read, NULL)) {
fprintf(stderr, "Error join thread_read\n");
}
Expand Down Expand Up @@ -405,7 +436,7 @@ void ep0_loop(int fd) {

int rv = -1;
if (event.ctrl.bRequestType & USB_DIR_IN) {
result = control_request(&event.ctrl, &nbytes, &control_data, 1000);
result = control_request(&event.ctrl, &nbytes, &control_data, USB_REQUEST_TIMEOUT);
if (result == 0) {
memcpy(&io.data[0], control_data, nbytes);
io.inner.length = nbytes;
Expand Down Expand Up @@ -445,10 +476,14 @@ void ep0_loop(int fd) {
printData(io, 0x00, "control", "in");

rv = usb_raw_ep0_write(fd, (struct usb_raw_ep_io *)&io);
printf("ep0: transferred %d bytes (in)\n", rv);
if (rv < 0)
printf("ep0: ack failed: %d\n", rv);
else
printf("ep0: transferred %d bytes (in)\n", rv);
}
else {
usb_raw_ep0_stall(fd);
continue;
}
}
else {
Expand Down Expand Up @@ -497,6 +532,10 @@ void ep0_loop(int fd) {

// Ack request after spawning endpoint threads.
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io *)&io);
if (rv < 0)
printf("ep0: ack failed: %d\n", rv);
else
printf("ep0: request acked\n");
}
else if ((event.ctrl.bRequestType & USB_TYPE_MASK) == USB_TYPE_STANDARD &&
event.ctrl.bRequest == USB_REQ_SET_INTERFACE) {
Expand Down Expand Up @@ -552,6 +591,10 @@ void ep0_loop(int fd) {

// Ack request after spawning endpoint threads.
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io *)&io);
if (rv < 0)
printf("ep0: ack failed: %d\n", rv);
else
printf("ep0: request acked\n");
}
else {
if (injection_enabled) {
Expand All @@ -572,20 +615,51 @@ void ep0_loop(int fd) {
}
}

// Retrieve data for sending request to proxied device.
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io *)&io);
if (event.ctrl.wLength == 0) {
// For 0-length request, we can ack or stall the request via
// Raw Gadget, depending on what the proxied device does.

if (verbose_level >= 2)
printData(io, 0x00, "control", "out");

result = control_request(&event.ctrl, &nbytes, &control_data, USB_REQUEST_TIMEOUT);
if (result == 0) {
// Ack the request.
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io *)&io);
if (rv < 0)
printf("ep0: ack failed: %d\n", rv);
else
printf("ep0: request acked\n");
}
else {
// Stall the request.
usb_raw_ep0_stall(fd);
continue;
}
}
else {
// For non-0-length requests, we cannot retrieve the request data
// without acking the request due to the Gadget subsystem limitations.
// Thus, we cannot stall such request for the host even if the proxied
// device stalls. This is not ideal but seems to work fine in practice.

// Retrieve data for sending request to proxied device
// (and ack the request).
rv = usb_raw_ep0_read(fd, (struct usb_raw_ep_io *)&io);
if (rv < 0) {
printf("ep0: ack failed: %d\n", rv);
continue;
}

memcpy(control_data, io.data, event.ctrl.wLength);
if (verbose_level >= 2)
printData(io, 0x00, "control", "out");

if (verbose_level >= 2)
printData(io, 0x00, "control", "out");
memcpy(control_data, io.data, event.ctrl.wLength);

result = control_request(&event.ctrl, &nbytes, &control_data, 1000);
if (result == 0) {
printf("ep0: transferred %d bytes (out)\n", rv);
}
else {
usb_raw_ep0_stall(fd);
result = control_request(&event.ctrl, &nbytes, &control_data, USB_REQUEST_TIMEOUT);
if (result == 0) {
printf("ep0: transferred %d bytes (out)\n", rv);
}
}
}
}
Expand Down

0 comments on commit c6454ce

Please sign in to comment.