From 53fb582e7729d5b7ed40ff04d912fcf5add7ce1c Mon Sep 17 00:00:00 2001
From: Hector Martin
Date: Tue, 28 Apr 2009 19:01:12 +0200
Subject: USB TX/RX, device framework, version packets

---
 client.c    |   1 +
 client.h    |   7 +-
 device.c    | 225 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 device.h    |   6 +-
 log.h       |   4 +-
 main.c      |  15 ++--
 usb-linux.c | 152 +++++++++++++++++++++++++++++++++++++---
 usb.h       |   3 +
 8 files changed, 384 insertions(+), 29 deletions(-)

diff --git a/client.c b/client.c
index c56f8f4..f0868fb 100644
--- a/client.c
+++ b/client.c
@@ -24,4 +24,5 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 #include "log.h"
 #include "usb.h"
+#include "client.h"
 
diff --git a/client.h b/client.h
index d6f61af..2bb3920 100644
--- a/client.h
+++ b/client.h
@@ -18,7 +18,10 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 */
 
-#ifndef __MUX_H__
-#define __MUX_H__
+#ifndef __CLIENT_H__
+#define __CLIENT_H__
 
 #endif
+
+void client_accept(int fd);
+void client_get_fds(struct fdlist *list);
diff --git a/device.c b/device.c
index 659f4ae..79ec00c 100644
--- a/device.c
+++ b/device.c
@@ -18,39 +18,242 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 */
 
+#define _BSD_SOURCE
+
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 
+#include <netinet/in.h>
+#include <netinet/tcp.h>
 #include <stdlib.h>
+#include <string.h>
 #include "device.h"
 #include "usb.h"
 #include "log.h"
 
-int device_id;
-/*
-int get_next_device_id(void)
+int next_device_id;
+
+enum mux_protocol {
+	MUX_PROTO_VERSION = 0,
+	MUX_PROTO_TCP = IPPROTO_TCP,
+};
+
+enum mux_dev_state {
+	MUXDEV_INIT,
+	MUXDEV_ACTIVE,
+	MUXDEV_DEAD
+};
+
+struct mux_header
+{
+	uint32_t protocol;
+	uint32_t length;
+};
+
+struct version_header
+{
+	uint32_t major;
+	uint32_t minor;
+	uint32_t padding;
+};
+
+struct mux_device
+{
+	struct usb_device *usbdev;
+	int id;
+	enum mux_dev_state state;
+};
+
+static int num_devs;
+static struct mux_device *device_list;
+
+static int alloc_device(void)
+{
+	int i;
+	for(i=0; i<num_devs; i++) {
+		if(!device_list[i].usbdev)
+			return i;
+	}
+	num_devs++;
+	device_list = realloc(device_list, sizeof(*device_list) * num_devs);
+	memset(&device_list[num_devs-1], 0, sizeof(*device_list));
+	return num_devs - 1;
+}
+
+static int get_next_device_id(void)
 {
 	int i;
 	while(1) {
 		for(i=0; i<num_devs; i++) {
-			if(device_list[i].dev && device_list[i].id == device_id) {
-				device_id++;
+			if(device_list[i].usbdev && device_list[i].id == next_device_id) {
+				next_device_id++;
 				break;
 			}
 		}
-		if(i < num_devs)
+		if(i >= num_devs)
+			return next_device_id++;
+	}
+}
+
+int send_packet(struct mux_device *dev, enum mux_protocol proto, void *header, void *data, int length)
+{
+	unsigned char *buffer;
+	int hdrlen;
+	int res;
+	
+	switch(proto) {
+		case MUX_PROTO_VERSION:
+			hdrlen = sizeof(struct version_header);
 			break;
+		case MUX_PROTO_TCP:
+			hdrlen = sizeof(struct tcphdr);
+			break;
+		default:
+			usbmuxd_log(LL_ERROR, "Invalid protocol %d for outgoing packet (dev %d hdr %p data %p len %d)", proto, dev->id, header, data, length);
+			return -1;
+	}
+	usbmuxd_log(LL_SPEW, "send_packet(%d, 0x%x, %p, %p, %d)", dev->id, proto, header, data, length);
+	
+	int total = sizeof(struct mux_header) + hdrlen + length;
+	
+	if(total > USB_MTU) {
+		usbmuxd_log(LL_ERROR, "Tried to send packet larger than USB MTU (hdr %d data %d total %d) to device %d", hdrlen, length, total, dev->id);
+		return -1;
 	}
-	return device_id++;
+	
+	buffer = malloc(total);
+	struct mux_header *mhdr = (struct mux_header *)buffer;
+	mhdr->protocol = htonl(proto);
+	mhdr->length = htonl(total);;
+	memcpy(buffer + sizeof(struct mux_header), header, hdrlen);
+	if(data && length)
+		memcpy(buffer + sizeof(struct mux_header) + hdrlen, data, length);
+	
+	if((res = usb_send(dev->usbdev, buffer, total)) < 0) {
+		usbmuxd_log(LL_ERROR, "usb_send failed while sending packet (len %d) to device %d: %d", total, dev->id, res);
+		free(buffer);
+		return res;
+	}
+	return mhdr->length;
 }
-*/
-void device_add(struct usb_device *dev)
+
+static void device_version_input(struct mux_device *dev, struct version_header *vh)
+{
+	if(dev->state != MUXDEV_INIT) {
+		usbmuxd_log(LL_WARNING, "Version packet from already initialized device %d", dev->id);
+		return;
+	}
+	vh->major = ntohl(vh->major);
+	vh->minor = ntohl(vh->minor);
+	if(vh->major != 1 || vh->minor != 0) {
+		usbmuxd_log(LL_ERROR, "Device %d has unknown version %d.%d\n", dev->id, vh->major, vh->minor);
+		return;
+	}
+	usbmuxd_log(LL_NOTICE, "Connected to v%d.%d device %d on location 0x%x with serial number %s", vh->major, vh->minor, dev->id, usb_get_location(dev->usbdev), usb_get_serial(dev->usbdev));
+}
+
+static void device_tcp_input(struct mux_device *dev, struct tcphdr *th, unsigned char *payload, int payload_length)
+{
+
+}
+
+
+void device_data_input(struct usb_device *usbdev, unsigned char *buffer, int length)
+{
+	int i;
+	struct mux_device *dev;
+	for(i=0; i<num_devs; i++) {
+		if(device_list[i].usbdev == usbdev) {
+			dev = &device_list[i];
+			break;
+		}
+	}
+	if(i >= num_devs) {
+		usbmuxd_log(LL_WARNING, "Cannot find device entry for RX input from USB device %p on location 0x%x", usbdev, usb_get_location(usbdev));
+		return;
+	}
+	
+	usbmuxd_log(LL_SPEW, "Mux data input for device %p: %p len %d", dev, buffer, length);
+	
+	struct mux_header *mhdr = (struct mux_header *)buffer;
+	
+	if(ntohl(mhdr->length) != length) {
+		usbmuxd_log(LL_ERROR, "Incoming packet size mismatch (dev %d, expected %d, got %d)", dev->id, ntohl(mhdr->length), length);
+		return;
+	}
+	
+	struct tcphdr *th;
+	unsigned char *payload;
+	int payload_length;
+	
+	switch(ntohl(mhdr->protocol)) {
+		case MUX_PROTO_VERSION:
+			device_version_input(dev, (struct version_header *)(mhdr+1));
+			break;
+		case MUX_PROTO_TCP:
+			th = (struct tcphdr *)(mhdr+1);
+			payload = (unsigned char *)(th+1);
+			payload_length = length - sizeof(struct tcphdr) - sizeof(struct mux_header);
+			device_tcp_input(dev, (struct tcphdr *)(mhdr+1), payload, payload_length);
+			break;
+		default:
+			usbmuxd_log(LL_ERROR, "Incoming packet for device %d has unknown protocol 0x%x)", dev->id, ntohl(mhdr->protocol));
+			break;
+	}
+	
+}
+
+int device_add(struct usb_device *dev)
 {
-	usbmuxd_log(LL_NOTICE, "Connected to new device on location 0x%x with serial number %s", usb_get_location(dev), usb_get_serial(dev));
+	int res;
+	int id = get_next_device_id();
+	int idx = alloc_device();
+	usbmuxd_log(LL_NOTICE, "Connecting to new device on location 0x%x as ID %d", usb_get_location(dev), id);
+	device_list[idx].id = id;
+	device_list[idx].usbdev = dev;
+	device_list[idx].state = MUXDEV_INIT;
+	struct version_header vh;
+	vh.major = htonl(1);
+	vh.minor = htonl(0);
+	vh.padding = 0;
+	if((res = send_packet(&device_list[idx], MUX_PROTO_VERSION, &vh, NULL, 0)) < 0) {
+		usbmuxd_log(LL_ERROR, "Error sending version request packet to device %d\n", id);
+		device_list[idx].usbdev = NULL;
+		device_list[idx].state = MUXDEV_DEAD;
+		return res;
+	}
+	return 0;
 }
 
 void device_remove(struct usb_device *dev)
 {
-	usbmuxd_log(LL_NOTICE, "Removed device on location 0x%x with serial number %s", usb_get_location(dev), usb_get_serial(dev));
+	int i;
+	for(i=0; i<num_devs; i++) {
+		if(device_list[i].usbdev == dev) {
+			usbmuxd_log(LL_NOTICE, "Removed device %d on location 0x%x", device_list[i].id, usb_get_location(dev));
+			device_list[i].usbdev = NULL;
+			return;
+		}
+	}
+	usbmuxd_log(LL_WARNING, "Cannot find device entry while removing USB device %p on location 0x%x", dev, usb_get_location(dev));
+}
+
+void device_init(void)
+{
+	usbmuxd_log(LL_DEBUG, "device_init");
+	num_devs = 1;
+	device_list = malloc(sizeof(*device_list) * num_devs);
+	memset(device_list, 0, sizeof(*device_list) * num_devs);
+	next_device_id = 1;
+}
+
+void device_shutdown(void)
+{
+	int i;
+	usbmuxd_log(LL_DEBUG, "device_shutdown");
+	for(i=0; i<num_devs; i++)
+		device_remove(device_list[i].usbdev);
+	free(device_list);
+	device_list = NULL;
 }
diff --git a/device.h b/device.h
index 11beaea..878688c 100644
--- a/device.h
+++ b/device.h
@@ -23,7 +23,11 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 #include "usb.h"
 
-void device_add(struct usb_device *dev);
+void device_data_input(struct usb_device *dev, unsigned char *buf, int length);
+
+int device_add(struct usb_device *dev);
 void device_remove(struct usb_device *dev);
 
+void device_init(void);
+void device_shutdown(void);
 #endif
diff --git a/log.h b/log.h
index 56ecbf4..f6eb5c1 100644
--- a/log.h
+++ b/log.h
@@ -29,10 +29,12 @@ enum loglevel {
 	LL_INFO,
 	LL_DEBUG,
 	LL_SPEW,
+	LL_FLOOD,
 };
 
 extern int log_level;
 
-void usbmuxd_log(enum loglevel level, const char *fmt, ...);
+void usbmuxd_log(enum loglevel level, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
+
 
 #endif
diff --git a/main.c b/main.c
index bc846cc..7bc8dbe 100644
--- a/main.c
+++ b/main.c
@@ -34,8 +34,9 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 #include "log.h"
 #include "usb.h"
+#include "device.h"
 
-const char *socket_path = "/tmp/usbmuxd"; //TODO: CHANGEME
+static const char *socket_path = "/tmp/usbmuxd"; //TODO: CHANGEME
 
 int create_socket(void) {
 	struct sockaddr_un bind_addr;
@@ -75,17 +76,17 @@ int main_loop(int listenfd)
 	struct fdlist pollfds;
 	
 	while(1) {
-		usbmuxd_log(LL_SPEW, "main_loop iteration");
+		usbmuxd_log(LL_FLOOD, "main_loop iteration");
 		to = usb_get_timeout();
-		usbmuxd_log(LL_SPEW, "USB timeout is %d ms", to);
+		usbmuxd_log(LL_FLOOD, "USB timeout is %d ms", to);
 		
 		fdlist_create(&pollfds);
 		fdlist_add(&pollfds, FD_LISTEN, listenfd, POLLIN);
 		usb_get_fds(&pollfds);
-		usbmuxd_log(LL_SPEW, "fd count is %d", pollfds.count);
+		usbmuxd_log(LL_FLOOD, "fd count is %d", pollfds.count);
 		
 		cnt = poll(pollfds.fds, pollfds.count, to);
-		usbmuxd_log(LL_SPEW, "poll() returned %d", cnt);
+		usbmuxd_log(LL_FLOOD, "poll() returned %d", cnt);
 		
 		if(cnt == 0) {
 			if(usb_process() < 0) {
@@ -122,6 +123,7 @@ int main(int argc, char *argv[])
 	if(listenfd < 0)
 		return 1;
 
+	device_init();
 	usbmuxd_log(LL_INFO, "Initializing USB");
 	if((res = usb_init()) < 0)
 		return 2;
@@ -135,8 +137,9 @@ int main(int argc, char *argv[])
 
 	usbmuxd_log(LL_NOTICE, "usbmux shutting down");
 	usb_shutdown();
+	device_shutdown();
 	usbmuxd_log(LL_NOTICE, "Shutdown complete");
-	
+
 	if(res < 0)
 		return -res;
 	return 0;
diff --git a/usb-linux.c b/usb-linux.c
index 0820ed9..27a7bb1 100644
--- a/usb-linux.c
+++ b/usb-linux.c
@@ -42,13 +42,13 @@ struct usb_device {
 	uint8_t bus, address;
 	char serial[256];
 	int alive;
+	struct libusb_transfer *rx_xfer;
 };
 
-int num_devs;
-int device_id;
-struct usb_device *device_list;
+static int num_devs;
+static struct usb_device *device_list;
 
-struct timeval next_dev_poll_time;
+static struct timeval next_dev_poll_time;
 
 static int alloc_device(void)
 {
@@ -68,11 +68,130 @@ static void usb_disconnect(struct usb_device *dev)
 	if(!dev->dev) {
 		return;
 	}
+	if(dev->rx_xfer) {
+		// kill the rx xfer and try to make sure the rx callback gets called before we free the device
+		struct timeval tv;
+		int res;
+		// TODO: BUG: outstanding TX xfers are not listed but we need to free them
+		libusb_cancel_transfer(dev->rx_xfer);
+		tv.tv_sec = tv.tv_usec = 0;
+		if((res = libusb_handle_events_timeout(NULL, &tv)) < 0) {
+			usbmuxd_log(LL_ERROR, "libusb_handle_events_timeout for device removal failed: %d", res);
+		}
+	}
 	libusb_release_interface(dev->dev, USB_INTERFACE);
 	libusb_close(dev->dev);
 	dev->dev = NULL;
 }
 
+static void tx_callback(struct libusb_transfer *xfer)
+{
+	struct usb_device *dev = xfer->user_data;
+	usbmuxd_log(LL_SPEW, "TX callback dev %d-%d len %d -> %d status %d", dev->bus, dev->address, xfer->length, xfer->actual_length, xfer->status);
+	if(xfer->status != LIBUSB_TRANSFER_COMPLETED) {
+		switch(xfer->status) {
+			case LIBUSB_TRANSFER_COMPLETED: //shut up compiler
+			case LIBUSB_TRANSFER_ERROR:
+				// funny, this happens when we disconnect the device while waiting for a transfer, sometimes
+				usbmuxd_log(LL_INFO, "Device %d-%d TX aborted due to error or disconnect", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_TIMED_OUT:
+				usbmuxd_log(LL_ERROR, "TX transfer timed out for device %d-%d", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_CANCELLED:
+				usbmuxd_log(LL_ERROR, "TX transfer cancelled for device %d-%d", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_STALL:
+				usbmuxd_log(LL_ERROR, "TX transfer stalled for device %d-%d", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_NO_DEVICE:
+				// other times, this happens, and also even when we abort the transfer after device removal
+				usbmuxd_log(LL_INFO, "Device %d-%d TX aborted due to disconnect", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_OVERFLOW:
+				usbmuxd_log(LL_ERROR, "TX transfer overflow for device %d-%d", dev->bus, dev->address);
+				break;
+			// and nothing happens (this never gets called) if the device is freed after a disconnect! (bad)
+		}
+		// we can't usb_disconnect here due to a deadlock, so instead mark it as dead and reap it after processing events
+		// we'll do device_remove there too
+		dev->alive = 0;
+	}
+	free(xfer->buffer);
+	libusb_free_transfer(xfer);
+}
+
+int usb_send(struct usb_device *dev, const unsigned char *buf, int length)
+{
+	int res;
+	struct libusb_transfer *xfer = libusb_alloc_transfer(0);
+	libusb_fill_bulk_transfer(xfer, dev->dev, BULK_OUT, (void*)buf, length, tx_callback, dev, 0);
+	xfer->flags = LIBUSB_TRANSFER_SHORT_NOT_OK;
+	if((res = libusb_submit_transfer(xfer)) < 0) {
+		usbmuxd_log(LL_ERROR, "Failed to submit TX transfer %p len %d to device %d-%d: %d", buf, length, dev->bus, dev->address, res);
+		libusb_free_transfer(xfer);
+		return res;
+	}
+	return 0;
+}
+
+static void rx_callback(struct libusb_transfer *xfer)
+{
+	struct usb_device *dev = xfer->user_data;
+	usbmuxd_log(LL_SPEW, "RX callback dev %d-%d len %d status %d", dev->bus, dev->address, xfer->actual_length, xfer->status);
+	if(xfer->status == LIBUSB_TRANSFER_COMPLETED) {
+		device_data_input(dev, xfer->buffer, xfer->actual_length);
+		libusb_submit_transfer(xfer);
+	} else {
+		switch(xfer->status) {
+			case LIBUSB_TRANSFER_COMPLETED: //shut up compiler
+			case LIBUSB_TRANSFER_ERROR:
+				// funny, this happens when we disconnect the device while waiting for a transfer, sometimes
+				usbmuxd_log(LL_INFO, "Device %d-%d RX aborted due to error or disconnect", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_TIMED_OUT:
+				usbmuxd_log(LL_ERROR, "RX transfer timed out for device %d-%d", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_CANCELLED:
+				usbmuxd_log(LL_ERROR, "RX transfer cancelled for device %d-%d", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_STALL:
+				usbmuxd_log(LL_ERROR, "RX transfer stalled for device %d-%d", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_NO_DEVICE:
+				// other times, this happens, and also even when we abort the transfer after device removal
+				usbmuxd_log(LL_INFO, "Device %d-%d RX aborted due to disconnect", dev->bus, dev->address);
+				break;
+			case LIBUSB_TRANSFER_OVERFLOW:
+				usbmuxd_log(LL_ERROR, "RX transfer overflow for device %d-%d", dev->bus, dev->address);
+				break;
+			// and nothing happens (this never gets called) if the device is freed after a disconnect! (bad)
+		}
+		free(xfer->buffer);
+		dev->rx_xfer = NULL;
+		libusb_free_transfer(xfer);
+		// we can't usb_disconnect here due to a deadlock, so instead mark it as dead and reap it after processing events
+		// we'll do device_remove there too
+		dev->alive = 0;
+	}
+}
+
+static int start_rx(struct usb_device *dev)
+{
+	int res;
+	void *buf;
+	dev->rx_xfer = libusb_alloc_transfer(0);
+	buf = malloc(USB_MTU);
+	libusb_fill_bulk_transfer(dev->rx_xfer, dev->dev, BULK_IN, buf, USB_MTU, rx_callback, dev, 0);
+	if((res = libusb_submit_transfer(dev->rx_xfer)) != 0) {
+		usbmuxd_log(LL_ERROR, "Failed to submit RX transfer to device %d-%d: %d", dev->bus, dev->address, res);
+		libusb_free_transfer(dev->rx_xfer);
+		dev->rx_xfer = NULL;
+		return res;
+	}
+	return 0;
+}
+
 static int usb_discover(void)
 {
 	int cnt, i, j, res;
@@ -135,7 +254,8 @@ static int usb_discover(void)
 		int idx = alloc_device();
 
 		if((res = libusb_get_string_descriptor_ascii(handle, devdesc.iSerialNumber, (uint8_t *)device_list[idx].serial, 256)) <= 0) {
-			usbmuxd_log(LL_WARNING, "Could not get serial number for device %d-%d: %d", USB_INTERFACE, bus, address, res);
+			usbmuxd_log(LL_WARNING, "Could not get serial number for device %d-%d: %d", bus, address, res);
+			libusb_release_interface(handle, USB_INTERFACE);
 			libusb_close(handle);
 			continue;
 		}
@@ -145,7 +265,15 @@ static int usb_discover(void)
 		device_list[idx].dev = handle;
 		device_list[idx].alive = 1;
 		
-		device_add(&device_list[idx]);
+		if(device_add(&device_list[idx]) < 0) {
+			usb_disconnect(&device_list[j]);
+			continue;
+		}
+		if(start_rx(&device_list[idx]) < 0) {
+			device_remove(&device_list[j]);
+			usb_disconnect(&device_list[j]);
+			continue;
+		}
 		valid_count++;
 	}
 	for(j=0; j<num_devs; j++) {
@@ -232,7 +360,7 @@ int usb_get_timeout(void)
 
 int usb_process(void)
 {
-	int res;
+	int i, res;
 	struct timeval tv;
 	tv.tv_sec = tv.tv_usec = 0;
 	res = libusb_handle_events_timeout(NULL, &tv);
@@ -240,6 +368,14 @@ int usb_process(void)
 		usbmuxd_log(LL_ERROR, "libusb_handle_events_timeout failed: %d", res);
 		return res;
 	}
+	// reap devices marked dead due to an RX error
+	for(i=0; i<num_devs; i++) {
+		if(device_list[i].dev && !device_list[i].alive) {
+			device_remove(&device_list[i]);
+			usb_disconnect(&device_list[i]);
+		}
+	}
+
 	if(dev_poll_remain_ms() <= 0) {
 		res = usb_discover();
 		if(res < 0) {
@@ -256,12 +392,12 @@ int usb_init(void)
 	usbmuxd_log(LL_DEBUG, "usb_init for linux / libusb 1.0");
 	
 	res = libusb_init(NULL);
+	//libusb_set_debug(NULL, 3);
 	if(res != 0) {
 		usbmuxd_log(LL_FATAL, "libusb_init failed: %d", res);
 		return -1;
 	}
 	
-	device_id = 1;
 	num_devs = 1;
 	device_list = malloc(sizeof(*device_list) * num_devs);
 	memset(device_list, 0, sizeof(*device_list) * num_devs);
diff --git a/usb.h b/usb.h
index 25243d8..2a29d2f 100644
--- a/usb.h
+++ b/usb.h
@@ -26,6 +26,8 @@ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 #define BULK_IN 0x85
 #define BULK_OUT 0x04
 
+#define USB_MTU 65536
+
 #define VID_APPLE 0x5ac
 #define PID_IPHONE2G 0x1290
 #define PID_ITOUCH1G 0x1291
@@ -42,6 +44,7 @@ const char *usb_get_serial(struct usb_device *dev);
 int usb_get_location(struct usb_device *dev);
 void usb_get_fds(struct fdlist *list);
 int usb_get_timeout(void);
+int usb_send(struct usb_device *dev, const unsigned char *buf, int length);
 int usb_process(void);
 
 #endif
-- 
cgit v1.1-32-gdbae