/*
 * $Id$
 *
 * darwinusb.c: I/O support for Darwin (Mac OS X) USB
 *
 * Copyright (c) 2004-2006, Florent Pillet.
 *
 * libpisock interface modeled after linuxusb.c by Jeff Dionne and 
 * Kenneth Albanowski
 * Some structures & defines extracted from Linux "visor.c",
 * which is Copyright (C) 1999 - 2003 Greg Kroah-Hartman (greg@kroah.com)
 * KLSI adapter (PalmConnect USB) support implemented thanks for the
 * Linux implementation made by Utz-Uwe Haus (haus@uuhaus.de)
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Library General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 * 
 * This library 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 Library
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 */

/* Theory of operation
 *
 * Darwin IOKit is different from traditional unix i/o. It is much more
 * structured, and also more complex.
 *
 * One of the strengths of IOKit is IOUSBLib which allows talking to USB
 * devices directly from userland code, without the need to write a driver.
 * This is the way we do it here.
 *
 * Here is what we do:
 * - We start a separate thread which will handle the USB communications.  The
 *   main (controlling) thread exposes start, stop, poll, read and write
 *   functions. These function take care of controlling the USB thread.
 * - We register for "device added" notifications. These notifications are
 *   sent by the IOKit to registered clients when a new device shows up. We
 *   use a matching dictionary to restrict the devices we're interested in to
 *   USB devices (IOUSBDevice class)
 * - When we get notified that a new device showed up, we check the USB vendor
 *   ID and product ID and only accept those that are known to be Palm OS
 *   devices
 * - We then examine the device interfaces and select the pipes we're going to
 *   use for input and output.
 * - We register for notifications coming from the device. When the device
 *   goes away, our notification callback is called and we can cleanly close
 *   things.
 * - Once everything is initialized, we fire a first "read" from the read
 *   pipe.  Subsequent reads are fired directly from the previous read's
 *   completion routine [1].
 * - In case the thread or application is aborted, the IOKit will clean things
 *   up for us.
 *
 * [1] Reading is done asynchronously and in a chained way: we fire a read
 *     with ReadPipeAsync(). Once the read completes (or fails), our
 *     completion routine is called. As long as the read is not "aborted"
 *     (which means the device has been disconnected), we fire another read
 *     from the read_completion() function.
 *
 *     All read data fills a buffer which is independantly consumed by the
 *     main thread.  This way, we get the maximum read throughput by making
 *     sure that any data received from the device is fetched as soon as
 *     possible and made available to the main thread.
 *
 *     Writes, in the contrary, are synchronous for now. I can make them async
 *     as well, though I have not explored the implications for the libpisock
 *     code yet.  This could speed things up a bit as well, though.
 */

#include <mach/mach.h>
#include <stdio.h>
#include <pthread.h>

#include <IOKit/IOKitLib.h>
#include <IOKit/IOMessage.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#include <CoreFoundation/CFByteOrder.h>

#include "pi-debug.h"
#include "pi-source.h"
#include "pi-usb.h"
#include "pi-util.h"

/* Define this to make debug logs include USB debug info */
#ifdef PI_DEBUG
    #define DEBUG_USB 1
#endif
#undef DEBUG_USB        /* comment out to leave debug enabled */

/* Macro to log more information when debugging USB. Note that this is for
 * my own use, mostly, as the info logged is primarily being used to
 * debug complex thread/usb issues
 */
#ifdef DEBUG_USB
	#define ULOG(a) LOG(a)
#else
	#define ULOG(a) do {} while(0)
#endif

/* These values are somewhat tricky.  Priming reads with a size of exactly one
 * USB packet works best (no timeouts).  Probably best to leave these as they are.
 */
#define MAX_AUTO_READ_SIZE	4096
#define AUTO_READ_SIZE		64

/* Options */
#if HAVE_PTHREAD
static int accept_multiple_simultaneous_connections = 1;	/* enabled by default when compiling with --enable-threads */
#else
static int accept_multiple_simultaneous_connections = 0;	/* disabled by default, set to 1 to enable */
#endif

/* IOKit interface */
static IONotificationPortRef usb_notify_port;
static io_iterator_t usb_device_added_iter;

/* RunLoop / threading management */
static CFRunLoopRef usb_run_loop = 0;
static pthread_mutex_t usb_run_loop_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_mutex_t usb_thread_ready_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t usb_thread_ready_cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t usb_connections_mutex = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t usb_connection_added_cond = PTHREAD_COND_INITIALIZER;
static pthread_t usb_thread = 0;

/* Device interface linked list */
typedef struct usb_connection_t
{
	struct usb_connection_t *next;		/* linked list */
	struct pi_socket *ps;			/* the pilot-link socket we're associated with (if already paired) */

	/* refcount management */
	pthread_mutex_t ref_count_mutex;
	int ref_count;

	IOUSBInterfaceInterface190 **interface;	/* IOUSBInterface190 is 1.9.0 available on OS X 10.2 and later */
	IOUSBDeviceInterface **device;
	io_object_t device_notification;	/* for device removal */

	unsigned short vendorID;		/* connected USB device vendor ID */
	unsigned short productID;		/* connected USB device product ID */
	unsigned short dev_flags;		/* copy of the flags from the acceptedDevices structure */

	int opened;				/* set to != 0 if the connection is opened */
	int device_present;
	int read_pending;			/* set to 1 when a prime_read() has been issued and the read_completion() has not been called yet */
	int in_pipe_ref;			/* pipe for reads */
	int in_pipe_bulk_size;		/* max packet size of bulk packets on input */
	int out_pipe_ref;			/* pipe for writes */
	int out_pipe_bulk_size;			/* size of bulk packets on the out pipe (used when talking with a PalmConnect USB serial adapter) */

	/* these provide hints about the size of the next read */
	int auto_read_size;			/* if != 0, prime reads to the input pipe to get data permanently */
	int read_ahead_size;			/* when waiting for big chunks of data, used as a hint to make bigger read requests */
	int last_read_ahead_size;		/* also need this to properly compute the size of the next read */

	unsigned long total_bytes_read;		/* total number of bytes received since connection was opened */
	unsigned long total_bytes_written;	/* total number of bytes sent since connection was opened */

	pthread_mutex_t read_queue_mutex;
	pthread_cond_t read_queue_data_avail_cond;
	char *read_queue;			/* stores completed reads, grows by 64k chunks */
	size_t read_queue_size;
	size_t read_queue_used;

	char read_buffer[MAX_AUTO_READ_SIZE];
} usb_connection_t;

static usb_connection_t *usb_connections = NULL;	/* linked list of active connections */

/* USB control requests we send to the devices
 * Got them from linux/drivers/usb/serial/visor.h
 */
#define	GENERIC_REQUEST_BYTES_AVAILABLE		0x01
#define	GENERIC_CLOSE_NOTIFICATION		0x02
#define VISOR_GET_CONNECTION_INFORMATION	0x03
#define PALM_GET_EXT_CONNECTION_INFORMATION	0x04

/* Structures defining the info a device returns
 * Got them from linux/drivers/usb/serial/visor.h
 */
typedef struct
{
	UInt16 num_ports;
	struct
	{
		UInt8 port_function_id;
		UInt8 port;
	} connections[8];
} visor_connection_info;

/* struct visor_connection_info.connection[x].port defines: */
#define VISOR_ENDPOINT_1			0x01
#define VISOR_ENDPOINT_2			0x02

/* struct visor_connection_info.connection[x].port_function_id defines: */
#define VISOR_FUNCTION_GENERIC			0x00
#define VISOR_FUNCTION_DEBUGGER			0x01
#define VISOR_FUNCTION_HOTSYNC			0x02
#define VISOR_FUNCTION_CONSOLE			0x03
#define VISOR_FUNCTION_REMOTE_FILE_SYS		0x04

typedef struct
{
	UInt8 num_ports;
	UInt8 endpoint_numbers_different;
	UInt16 reserved1;
 	struct
	{
		UInt32 port_function_id;
		UInt8 port;
		UInt8 endpoint_info;
		UInt16 reserved;
	} connections[8];
} palm_ext_connection_info;

/* PalmConnect USB specific information
 * Extracted from Linux kl5kusb105 Copyright (C) 2001 Utz-Uwe Haus <haus@uuhaus.de>
 * Using documentation Utz-Uwe provided
 */
enum {
	/* Values for KLSI_GET/SET_COMM_DESCRIPTOR */
	KLSI_BAUD_115200 = 0,
	KLSI_BAUD_57600  = 1,
 	KLSI_BAUD_38400  = 2,
	KLSI_BAUD_28800  = 3,
	KLSI_BAUD_19200  = 4,
	KLSI_BAUD_14400  = 5,
	KLSI_BAUD_9600   = 6,
	KLSI_BAUD_7200   = 7,
	KLSI_BAUD_4800   = 8,
	KLSI_BAUD_2400   = 9,
	KLSI_BAUD_1200   = 10,
	KLSI_BAUD_600    = 11,
	KLSI_BAUD_230400 = 12,
	
	KLSI_PARITY_NONE = 0,
	KLSI_PARITY_ODD  = 1,
	KLSI_PARITY_EVEN = 2,
	KLSI_PARITY_MARK = 3,
	
	KLSI_STOPBITS_0 = 0,
	KLSI_STOPBITS_2 = 2,
	
	/* Handshake values for KLSI_GET_HANDSHAKE_LINES */
	KLSI_GETHS_DCD = 0x80,		/* Data Carrier Detect */
	KLSI_GETHS_RI  = 0x40,		/* Ring Indicator */
	KLSI_GETHS_DSR = 0x20,		/* Data Set Ready */
	KLSI_GETHS_CTS = 0x10,		/* Clear To Send */
	
	/* Handshake values for KLST_SET_HANDSHAKE_LINES */
	KLSI_SETHS_RTS = 0x02,		/* Ready To Send */
	KLSI_SETHS_DTR = 0x01,		/* Data Terminal Ready */
	
	/* Flow control values */
	KLSI_FLOW_USE_RTS = 0x01,	/* use RTS/CTS */
	KLSI_FLOW_USE_DSR = 0x02,	/* use DSR/CD */
	KLSI_FLOW_USE_XON = 0x04	/* use XON/XOFF */
};

#define KLSI_GET_COMM_DESCRIPTOR		0
#define KLSI_SET_COMM_DESCRIPTOR		1
#define KLSI_GET_HANDSHAKE_LINES		2
#define KLSI_SET_HANDSHAKE_LINES		3
#define KLSI_GET_FLOWCONTROL			4
#define	KLSI_SET_FLOWCONTROL			5

typedef struct
{
	unsigned char	DCBLength	__attribute__((packed));
	unsigned char	BaudRateIndex	__attribute__((packed));
	unsigned char	DataBits	__attribute__((packed));
	unsigned char	Parity		__attribute__((packed));
	unsigned char	StopBits	__attribute__((packed));
} klsi_port_settings;

/* Some vendor and product codes we use */
#define	VENDOR_SONY			0x054c
#define	VENDOR_KEYSPAN			0x06cd
#define	VENDOR_HANDSPRING		0x082d
#define	VENDOR_PALMONE			0x0830
#define	VENDOR_TAPWAVE			0x12ef
#define PRODUCT_PALMCONNECT_USB		0x0080
#define	PRODUCT_HANDSPRING_VISOR	0x0100
#define PRODUCT_SONY_CLIE_3_5		0x0038

/* This table helps us determine whether a connecting USB device is
 * one we'd like to talk to. Don't forget to update it as new
 * devices come out. To accept ALL the devices from a vendor, add
 * an entry with the vendorID and 0xFFFF as productID.
 */

#define FLAG_ANSWERS_CONN_INFO		0x0001		/* device is known to answer connection information requests */
#define FLAG_USE_FIRST_PAIR		0x0002		/* thanks to dumb programmers at Palm, the connection information doesn't match the actual pipes being used. If this flag is set, always try to use the first endoints pair */
#define FLAG_USE_SECOND_PAIR		0x0004		/* ditto */
#define FLAG_ANSWERS_PALM_CONN_INFO	0x0008		/* means that if the device doesn't answer PALM_EXT_CONNECTION_INFORMATION, don't try the visor variant (for stupid PalmOne handhelds) */
#define FLAG_REJECT			0x8000		/* device is known but not supported yet */

static struct {
	unsigned short vendorID;
	unsigned short productID;
	unsigned short flags;
}
acceptedDevices[] = {
	/* Sony */
	{0x054c, 0x0038},	/* Sony Palm OS 3.5 devices, S300 */
	{0x054c, 0x0066},	/* Sony T, S320, SJ series, and other Palm OS 4.0 devices */
	{0x054c, 0x0095},	/* Sony S360 */
	{0x054c, 0x000a},	/* Sony NR and other Palm OS 4.1 devices */
	{0x054c, 0x009a},	/* Sony NR70V/U */
	{0x054c, 0x00da},	/* Sony NX */
	{0x054c, 0x00e9},	/* Sony NZ */
	{0x054c, 0x0144},	/* Sony UX */
	{0x054c, 0x0169},	/* Sony TJ */

	/* Keyspan serial-to-USB PDA adapter */
	{0x06cd, 0x0103, FLAG_REJECT},	/* ID sent by an adapter which firmware has not been uploaded yet */
	{0x06cd, 0x0104, FLAG_REJECT},	/* ID sent by an adapter with proper firmware uploaded */

	/* AlphaSmart */
	{0x081e, 0xdf00},	/* Dana */

	/* HANDSPRING (vendor 0x082d) */
	{0x082d, 0x0100},	/* Visor */
	{0x082d, 0x0200},	/* Treo */
	{0x082d, 0x0300, FLAG_ANSWERS_CONN_INFO},	/* Treo 600 */

	/* PalmOne, Palm Inc */
	{0x0830, 0x0001, FLAG_ANSWERS_CONN_INFO},	/* m500 */
	{0x0830, 0x0002, FLAG_ANSWERS_CONN_INFO},	/* m505 */
	{0x0830, 0x0003, FLAG_ANSWERS_CONN_INFO},	/* m515 */
	{0x0830, 0x0010},
	{0x0830, 0x0011},
	{0x0830, 0x0020},	/* i705 */
	{0x0830, 0x0030},
	{0x0830, 0x0031},	/* Tungsten|W */
	{0x0830, 0x0040},	/* m125 */
	{0x0830, 0x0050},	/* m130 */
	{0x0830, 0x0051},
	{0x0830, 0x0052},
	{0x0830, 0x0053},
	{0x0830, 0x0060, FLAG_ANSWERS_CONN_INFO},	/* Tungsten series, Zire 71 */
	{0x0830, 0x0061, FLAG_ANSWERS_PALM_CONN_INFO | FLAG_USE_FIRST_PAIR},	/* Zire 22, 31, 72, T|5, T|X, LifeDrive, Treo 650 -- for T|X and LD, they don't answer to the PALM_EXT_CONNECTION_INFORMATION control code. In this case we revert to using the first pair of pipes */
	{0x0830, 0x0062},
	{0x0830, 0x0063},
	{0x0830, 0x0070, FLAG_ANSWERS_CONN_INFO},	/* Zire */
	{0x0830, 0x0071},
	{0x0830, 0x0080},	/* palmOne serial adapter */
	{0x0830, 0x0099},
	{0x0830, 0x0100},

	/* GARMIN */
	{0x091e, 0x0004},	/* IQUE 3600 */

	/* Kyocera */
	{0x0c88, 0x0021},	/* 7135 Smartphone */
	{0x0c88, 0xa226},	/* 6035 Smartphone */

	/* Tapwave */
	{0x12ef, 0x0100, FLAG_ANSWERS_CONN_INFO},	/* Zodiac, Zodiac2 */

	/* ACEECA */
	{0x4766, 0x0001},	/* MEZ1000 */
	
	/* Fossil */
	{0x0e67, 0x0002},	/* Abacus wrist PDA */
	
	/* Samsung */
	{0x04e8, 0x8001}	/* I330 */
};


/* local prototypes */
static int change_refcount(usb_connection_t *c, int increment);
static void stop_listening(usb_connection_t *c);
static IOReturn control_request (IOUSBDeviceInterface **dev, UInt8 requestType, UInt8 request, UInt16 value, UInt16 index, void *pData, UInt16 maxReplyLength);
static void device_added (void *refCon, io_iterator_t iterator);
static void device_notification (usb_connection_t *connexion, io_service_t service, natural_t messageType, void *messageArgument);
static void read_completion (usb_connection_t *connexion, IOReturn result, void *arg0);
static int accepts_device (unsigned short vendor, unsigned short product, unsigned short *flags);
static IOReturn	configure_device (IOUSBDeviceInterface **dev, unsigned short vendor, unsigned short product, unsigned short flags, int *port_number, int *input_pipe_number, int *output_pipe_number, int *pipe_info_retrieved);
static IOReturn	find_interfaces (usb_connection_t *usb, IOUSBDeviceInterface **dev, unsigned short vendor, unsigned short product, unsigned short accept_flags, int port_number, int input_pipe_number, int output_pipe_number, int pipe_info_retrieved);
static int prime_read (usb_connection_t *connexion);
static IOReturn	read_visor_connection_information (IOUSBDeviceInterface **dev, int *port_number, int *input_pipe, int *output_pipe);
static IOReturn	decode_generic_connection_information(palm_ext_connection_info *ci, int *port_number, int *input_pipe, int *output_pipe);
static IOReturn	read_generic_connection_information (IOUSBDeviceInterface **dev, int *port_number, int *input_pipe_number, int *output_pipe_number);
static IOReturn klsi_set_portspeed(IOUSBDeviceInterface **dev, int speed);


/***************************************************************************/
/*                                                                         */
/*                GLOBAL DARWINUSB OPTIONS FOR CLIENT CODE                 */
/*                                                                         */
/***************************************************************************/
void
darwinusb_setoptions(int multiple_connections_support)
{
	accept_multiple_simultaneous_connections = multiple_connections_support;
}


/***************************************************************************/
/*                                                                         */
/*                   CONNECTIONS LINKED LIST MANAGEMENT                    */
/*                                                                         */
/***************************************************************************/
static void
add_connection(usb_connection_t *c)
{
	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: adding connection %p to linked list\n", c));
	pthread_mutex_lock(&usb_connections_mutex);
	c->next = usb_connections;
	usb_connections = c;
	//pthread_cond_signal(&usb_connection_added_cond);
	pthread_mutex_unlock(&usb_connections_mutex);
}

static int
remove_connection(usb_connection_t *c)
{
	/* remove the connection from the linked list if it exists
	 * and return != 0. Otherwise return 0. Don't free the
	 * connection structure or variables.
	 */
	usb_connection_t *previous = NULL, *elem;
	pthread_mutex_lock(&usb_connections_mutex);
	elem = usb_connections;
	while (elem && elem != c)
	{
		previous = elem;
		elem = elem->next;
	}
	if (elem)
	{
		if (previous)
			previous->next = elem->next;
		else
			usb_connections = elem->next;
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: removed connection %p from linked list\n", c));
	}
	pthread_mutex_unlock(&usb_connections_mutex);
	return elem != NULL;
}

static int
device_already_in_use(IOUSBDeviceInterface **device)
{
	usb_connection_t *elem;
	pthread_mutex_lock(&usb_connections_mutex);
	elem = usb_connections;
	while (elem != NULL && elem->device != device)
		elem = elem->next;
	pthread_mutex_unlock(&usb_connections_mutex);
	return elem != NULL;
}

static usb_connection_t*
connection_for_socket(pi_socket_t *ps)
{
	/* if there is no active connection associated with this socket,
	 * try to associate the first unassociated connection
	 */
	usb_connection_t *c = ((pi_usb_data_t *)ps->device->data)->ref;
	if (change_refcount(c, +1) > 0)
		return c;

	pthread_mutex_lock(&usb_connections_mutex);
	c = usb_connections;
	while (c && (c->ps != NULL || c->opened == 0 || c->total_bytes_read == 0))	/* skip connections which are being disposed of (opened=0) */
		c = c->next;
	if (change_refcount(c, +1) <= 0)
		c = NULL;
	else
	{
		c->ps = ps;
		((pi_usb_data_t *)ps->device->data)->ref = c;
	}
	pthread_mutex_unlock(&usb_connections_mutex);
	return c;
}

static int
change_refcount(usb_connection_t *c, int increment)
{
	/* update the refcount on the connection structure. If the refcount becomes
	 * zero, call the stop_listening() function
	 */
	int rc;
	if (c == NULL)
		return 0;
	if (pthread_mutex_lock(&c->ref_count_mutex) != 0)
	{
		LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: connection %p, can't lock ref_count_mutex (ref_count=%d)\n",c,c->ref_count_mutex));
		return 0;
	}
	rc = c->ref_count = c->ref_count + increment;
	if (rc < 0)
		LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: connection %p's refcount became < 0 (%d)\n", c, c->ref_count));
	if (rc == 0)
	{
		if (remove_connection(c))
			stop_listening(c);
	}
	else
		pthread_mutex_unlock(&c->ref_count_mutex);
	return rc;
}

/***************************************************************************/
/*                                                                         */
/*                             INTERNAL ROUTINES                           */
/*                                                                         */
/***************************************************************************/
static int
start_listening(void)
{
	mach_port_t masterPort;
	CFMutableDictionaryRef matchingDict;
	CFRunLoopSourceRef runLoopSource;
	kern_return_t kr;

	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: start_listening for connections\n"));

	/* first create a master_port for my task. IOMainPort replaces
	 * IOMasterPort on macOS 12+; fall back on older systems. */
#if defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && \
    __MAC_OS_X_VERSION_MIN_REQUIRED >= 120000
	kr = IOMainPort (MACH_PORT_NULL, &masterPort);
#else
	kr = IOMasterPort (MACH_PORT_NULL, &masterPort);
#endif
	if (kr || !masterPort)
	{
		LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: couldn't create a master IOKit Port(%08x)\n", kr));
		return PI_ERR_GENERIC_SYSTEM;
	}

	/* Set up the matching criteria for the devices we're interested in
	 * Interested in instances of class IOUSBDevice and its subclasses
	 * Since we are supporting many USB devices, we just get notifications
	 * for all USB devices and sort out the ones that we want later.
	 */
	matchingDict = IOServiceMatching (kIOUSBDeviceClassName);
	if (!matchingDict)
	{
		LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: can't create a USB matching dictionary\n"));
		mach_port_deallocate (mach_task_self(), masterPort);
		return PI_ERR_GENERIC_SYSTEM;
	}

	/* Create a notification port and add its run loop event source to our run loop
	 * This is how async notifications get set up.
	 */
	usb_notify_port = IONotificationPortCreate (masterPort);
	runLoopSource = IONotificationPortGetRunLoopSource (usb_notify_port);
	CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);

	/* Set up a notifications to be called when a raw device is first matched by I/O Kit */
	kr = IOServiceAddMatchingNotification (
			usb_notify_port,
			kIOFirstMatchNotification,
			matchingDict,
			device_added,
			NULL,
			&usb_device_added_iter);

	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: added service matching notification (kr=0x%08lx)\n", kr));

	/* Iterate once to get already-present devices and arm the notification */
	device_added (NULL, usb_device_added_iter);

	/* Now done with the master_port */
	mach_port_deallocate (mach_task_self(), masterPort);

	return 0;
}

static void
stop_listening(usb_connection_t *c)
{
	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: stop_listening for connection %p\n",c));

	c->opened = 0;
	c->in_pipe_ref = 0;
	c->out_pipe_ref = 0;

	if (c->ps)
	{
		/* we do this because if the connection abruptly ends before pilot-link notices,
		 * we want to avoid pi_close() trying to do a dlp_EndOfSync()
		 */
		c->ps->state = PI_SOCK_CLOSE;
		if (c->ps->device != NULL && c->ps->device->data != NULL)
			((pi_usb_data_t *)c->ps->device->data)->ref = NULL;
	}
	c->ps = NULL;

	if (c->device_notification)
	{
		IOObjectRelease (c->device_notification);
		c->device_notification = 0;
	}

	if (c->interface)
	{
		(*c->interface)->USBInterfaceClose (c->interface);
		(*c->interface)->Release(c->interface);
		c->interface = NULL;
	}

	if (c->device)
	{
		(*c->device)->USBDeviceClose (c->device);
		(*c->device)->Release(c->device);
		c->device = NULL;
	}

	pthread_mutex_destroy(&c->read_queue_mutex);
	pthread_mutex_destroy(&c->ref_count_mutex);
	free(c);
}

static void *
usb_thread_run(void *foo)
{
	if (start_listening() == 0)
	{
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: usb_thread_run, starting...\n"));

		/* obtain the CFRunLoop for this thread */
		pthread_mutex_lock(&usb_run_loop_mutex);
		usb_run_loop = CFRunLoopGetCurrent();
		pthread_mutex_unlock(&usb_run_loop_mutex);

		/* signal main thread that init was successful */
		pthread_mutex_lock(&usb_thread_ready_mutex);
		pthread_cond_broadcast(&usb_thread_ready_cond);
		pthread_mutex_unlock(&usb_thread_ready_mutex);

		CFRunLoopRun();

		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: usb_thread_run, done with runloop\n"));

		pthread_mutex_lock(&usb_run_loop_mutex);
		usb_run_loop = 0;
		pthread_mutex_unlock(&usb_run_loop_mutex);

		if (usb_device_added_iter)
		{
			IOObjectRelease (usb_device_added_iter);
			usb_device_added_iter = 0;
		}

		if (usb_notify_port)
		{
			IONotificationPortDestroy(usb_notify_port);
			usb_notify_port = NULL;
		}

		/* decrement the refcount of each structure. If there
		 * was a pending read, decrement once more since
		 * prime_read() increments the refcount
		 */
		usb_connection_t *elem, *next, *prev = NULL;
		pthread_mutex_lock(&usb_connections_mutex);
		elem = usb_connections;
		while (elem != NULL)
		{
			next = elem->next;
			pthread_mutex_lock(&elem->ref_count_mutex);
			elem->ref_count--;
			if (elem->read_pending)
				elem->ref_count--;
			if (elem < 0)
				LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: while stopping usb_thread, connection %p's refcount became < 0 (%d)", elem, elem->ref_count));
			if (elem == 0)
			{
				if (prev != NULL)
					prev->next = next;
				else
					usb_connections = next;
				stop_listening(elem);
				elem = NULL;
			}
			else
			{
				pthread_mutex_unlock(&elem->ref_count_mutex);
				prev = elem;
			}
		}
		pthread_mutex_unlock(&usb_connections_mutex);

		usb_thread = 0;
		usb_run_loop = NULL;

		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: usb_thread_run, exited.\n"));
	}
	else
	{
		/* signal main thread that init failed */
		usb_thread = 0;
		usb_run_loop = NULL;
		pthread_mutex_lock(&usb_thread_ready_mutex);
		pthread_cond_signal(&usb_thread_ready_cond);
		pthread_mutex_unlock(&usb_thread_ready_mutex);
	}
	return NULL;
}

static void
device_added (void *refCon, io_iterator_t iterator)
{
	kern_return_t kr;
	io_service_t ioDevice;
	IOCFPlugInInterface **plugInInterface = NULL;
	IOUSBDeviceInterface **dev = NULL;
	HRESULT res;
	SInt32 score;
	UInt16 vendor, product;
	unsigned short accept_flags;
	int port_number = 0xff,
		input_pipe_number = 0xff,
		output_pipe_number = 0xff,
		pipe_info_retrieved = 0;

	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: device_added\n"));

	while ((ioDevice = IOIteratorNext (iterator)))
	{
		if (usb_connections != NULL && !accept_multiple_simultaneous_connections)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: new device plugged but we already have a running connection\n"));
			IOObjectRelease (ioDevice);
			continue;
		}

		kr = IOCreatePlugInInterfaceForService (ioDevice, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score);
		if (kr != kIOReturnSuccess || !plugInInterface)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: -> unable to create a plugin (kr=0x%08x)\n", kr));
			IOObjectRelease (ioDevice);
			continue;
		}

		res = (*plugInInterface)->QueryInterface (plugInInterface, CFUUIDGetUUIDBytes (kIOUSBDeviceInterfaceID), (LPVOID *)&dev);
		(*plugInInterface)->Release (plugInInterface);
		if (res || !dev)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: couldn't create a device interface (res=0x%08x)\n", (int) res));
			IOObjectRelease (ioDevice);
			continue;
		}

		/* make sure this device is not already being handled (this may happen
		 * with some handhelds that reconnect immediately after disconnecting, like the T5)
		 */
		if (device_already_in_use(dev))
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: device %p already in use, skipping it\n"));
			IOObjectRelease (ioDevice);
			continue;
		}

		kr = (*dev)->GetDeviceVendor (dev, &vendor);
		kr = (*dev)->GetDeviceProduct (dev, &product);
		if (accepts_device(vendor, product, &accept_flags) == 0)
		{
			ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: not accepting device (vendor=0x%04x product=0x%04x)\n", vendor, product));
			(*dev)->Release(dev);
			IOObjectRelease (ioDevice);
			continue;
		}

		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: Accepted USB device, vendor: 0x%04x product: 0x%04x\n", vendor, product));

		kr = (*dev)->USBDeviceOpen (dev);
		if (kr != kIOReturnSuccess)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: unable to open device (kr=0x%08x)\n", kr));
			(*dev)->Release(dev);
			IOObjectRelease (ioDevice);
			continue;
		}

		/* configure the device and query for its preferred I/O pipes */
		kr = configure_device (dev, vendor, product, accept_flags, &port_number, &input_pipe_number, &output_pipe_number, &pipe_info_retrieved);
		if (kr != kIOReturnSuccess)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, (kr == kIOReturnNotReady)
				?	"darwinusb: device not ready to synchonize\n"
				:	"darwinusb: unable to configure device (kr=0x%08x)\n", kr));
			(*dev)->USBDeviceClose (dev);
			(*dev)->Release (dev);
			IOObjectRelease (ioDevice);
			continue;
		}

		/* allocate and initialize the USB connection structure */
		usb_connection_t *c = malloc(sizeof(usb_connection_t));
		if (c == NULL)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "out of memory"));
			(*dev)->USBDeviceClose (dev);
			(*dev)->Release (dev);
			IOObjectRelease (ioDevice);
			break;
		}

		memset(c, 0, sizeof(usb_connection_t));
		c->auto_read_size = AUTO_READ_SIZE;
		c->device = dev;
		c->ref_count = 1;
		c->opened = 1;
		c->device_present = 1;
		c->vendorID = vendor;
		c->productID = product;
		c->dev_flags = accept_flags;

		/* try to locate the pipes we need to talk to the device */
		kr = find_interfaces(c, dev, vendor, product, accept_flags, port_number, input_pipe_number, output_pipe_number, pipe_info_retrieved);
		if (kr != kIOReturnSuccess)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: unable to find interfaces (kr=0x%08x)\n", kr));
			free(c);
			(*dev)->USBDeviceClose (dev);
			(*dev)->Release (dev);
			IOObjectRelease (ioDevice);
			continue;
		}

		/* Just like with service matching notifications, we need to create an event source and add it
		 * to our run loop in order to receive async completion notifications.
		 */
		CFRunLoopSourceRef runLoopSource;
		kr = (*c->interface)->CreateInterfaceAsyncEventSource (c->interface, &runLoopSource);
		if (kr != kIOReturnSuccess)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: Unable to create async event source (%08x)\n", kr));
			free(c);
			(*dev)->USBDeviceClose (dev);
			(*dev)->Release (dev);
			IOObjectRelease (ioDevice);
			continue;
		}
		CFRunLoopAddSource (CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);

		LOG((PI_DBG_DEV, PI_DBG_LVL_INFO, "darwinusb: USBConnection %p OPENED c->in_pipe_ref=%d c->out_pipe_ref=%d\n",c,c->in_pipe_ref,c->out_pipe_ref));

		/* Register for an interest notification for this device,
		 * so we get notified when it goes away
		 */
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: registering for disconnect notification\n"));
		kr = IOServiceAddInterestNotification(
				usb_notify_port,
				ioDevice,
				kIOGeneralInterest,
				(IOServiceInterestCallback)device_notification,
				(void *)c,
				&c->device_notification);
		IOObjectRelease(ioDevice);

		/* add the device to our linked list, then only start reading data
		 * (order of operations matters here to avoid race conditions)
		 */
		pthread_mutex_init(&c->read_queue_mutex, NULL);
		pthread_mutex_init(&c->ref_count_mutex, NULL);
		pthread_cond_init(&c->read_queue_data_avail_cond, NULL);

		add_connection(c);
		prime_read(c);
	}
}

static IOReturn
configure_device(IOUSBDeviceInterface **di,
				 unsigned short vendor,
				 unsigned short product,
				 unsigned short flags,
				 int *port_number,
				 int *input_pipe_number,
				 int *output_pipe_number,
				 int *pipe_info_retrieved)
{
	UInt8 numConf, conf, deviceClass;
	IOReturn kr;
	IOUSBConfigurationDescriptorPtr confDesc;

	/* Get the device class. Most handhelds are registered as composite devices
	 * and therefore already opened & configured by OS X drivers! It seems that
	 * reconfiguring them as we did before is what caused some Sony devices to
	 * refuse talking to us.
	 */
	kr = (*di)->GetDeviceClass (di, &deviceClass);
	if (kr != kIOReturnSuccess)
		return kr;

	if (deviceClass != kUSBCompositeClass)
	{
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: Not a composite device: performing confiuration\n"));

		kr = (*di)->GetNumberOfConfigurations (di, &numConf);
		if (!numConf)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: device has zero configurations!\n"));
			return -1;
		}

		/* try all possible configurations if the first one fails
		 * (shouldn't happen in most cases though)
		 */
		for (conf=0; conf < numConf; conf++)
		{
			kr = (*di)->GetConfigurationDescriptorPtr(di, 0, &confDesc);
			if (kr)
			{
				LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: unable to get config descriptor for index %d (err=%08x numConf=%d)\n",
					0, kr, (int)numConf));
				continue;
			}

			kr = (*di)->SetConfiguration(di, confDesc->bConfigurationValue);
			if (kr == kIOReturnSuccess)
			{
				LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: successfully set configuration %d\n",(int)confDesc->bConfigurationValue));
				break;
			}

			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: unable to set configuration to value %d (err=%08x numConf=%d)\n",
				(int)confDesc->bConfigurationValue, kr, (int)numConf));
		}
		if (conf == numConf)
			return kr;
	}

	if (vendor == VENDOR_PALMONE && product == PRODUCT_PALMCONNECT_USB)
	{
		kr = klsi_set_portspeed(di, 9600);
		*input_pipe_number = 0x01;
		*output_pipe_number = 0x02;
		*pipe_info_retrieved = 1;
		return kr;
	}

	/* Try reading pipe information. Most handhelds support a control request that returns info about the ports and
	 * pipes. We first try the generic control code, and if it doesn't work we try the Visor one which seems to be
	 * supported by some devices Also, we can detect that a T5 / LifeDrive is in "wait" mode
	 * (device appears on USB but not synchronizing) and in this case we return a kIOReturnNotReady code.
	 */
	kr = read_generic_connection_information (di, port_number, input_pipe_number, output_pipe_number);
	if (kr != kIOReturnSuccess && kr != kIOReturnNotReady && !(flags & FLAG_ANSWERS_PALM_CONN_INFO))
		kr = read_visor_connection_information (di, port_number, input_pipe_number, output_pipe_number);
	if (kr == kIOReturnNotReady)
		return kr;

	/* With some devices (Palm) we need to hardcode the location of the pipes to use
	 * because the Palm engineers had the good idea to mismatch the connection information data
	 * and the actual pipes to use
	 */
	if (flags & (FLAG_USE_FIRST_PAIR | FLAG_USE_SECOND_PAIR))
	{
		*pipe_info_retrieved = 1;
		return kIOReturnSuccess;
	}

	/* For device which we know return connection information, we want it to be returned to
	 * consider the device alive
	 */
	if (kr != kIOReturnSuccess && (flags & FLAG_ANSWERS_CONN_INFO))
		return kIOReturnNotReady;

	*pipe_info_retrieved = (kr == kIOReturnSuccess);

	/* query bytes available. Not that we really care, but most devices expect to receive this
	 * before they agree on talking to us.
	 */
	if (vendor != VENDOR_TAPWAVE)
	{
		unsigned char ba[2];
		kr = control_request (di, 0xc2, GENERIC_REQUEST_BYTES_AVAILABLE, 0, 0, &ba[0] , 2);
		if (kr != kIOReturnSuccess)
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: GENERIC_REQUEST_BYTES_AVAILABLE failed (err=%08x)\n", kr));
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "GENERIC_REQUEST_BYTES_AVAILABLE returns 0x%02x%02x\n", ba[0], ba[1]));
	}

	return kIOReturnSuccess;
}

static IOReturn
find_interfaces(usb_connection_t *c,
				IOUSBDeviceInterface **di,
				unsigned short vendor,
				unsigned short product,
				unsigned short accept_flags,
				int port_number,
				int input_pipe_number,
				int output_pipe_number,
				int pipe_info_retrieved)
{
	IOReturn kr;
	io_iterator_t iterator;
	io_service_t usbInterface;
	HRESULT res;
	SInt32 score;
	UInt8 intfClass, intfSubClass, intfNumEndpoints;
	int pipeRef, pass;
	IOUSBFindInterfaceRequest request;
	IOCFPlugInInterface **plugInInterface = NULL;
	UInt8 direction, number, transferType, interval;
	UInt16 maxPacketSize;

	request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
	request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
	request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
	request.bAlternateSetting = kIOUSBFindInterfaceDontCare;

	kr = (*di)->CreateInterfaceIterator (di, &request, &iterator);

	while ((usbInterface = IOIteratorNext (iterator)))
	{
		kr = IOCreatePlugInInterfaceForService (usbInterface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score);
		kr = IOObjectRelease (usbInterface);				// done with the usbInterface object now that I have the plugin
		if (kr != kIOReturnSuccess || !plugInInterface)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: unable to create a plugin (%08x)\n", kr));
			continue;
		}

		/* we have the interface plugin: we now need the interface interface */
		res = (*plugInInterface)->QueryInterface (plugInInterface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), (LPVOID *) &c->interface);
		(*plugInInterface)->Release (plugInInterface);			/* done with this */
		if (res || c->interface == NULL)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: couldn't create an IOUSBInterfaceInterface (%08x)\n", (int) res));
			continue;
		}

		/* get the interface class and subclass */
		kr = (*c->interface)->GetInterfaceClass (c->interface, &intfClass);
		kr = (*c->interface)->GetInterfaceSubClass (c->interface, &intfSubClass);
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: interface class %d, subclass %d\n", intfClass, intfSubClass));

		/* Now open the interface. This will cause the pipes to be instantiated that are
		 * associated with the endpoints defined in the interface descriptor.
		 */
		kr = (*c->interface)->USBInterfaceOpen (c->interface);
		if (kr != kIOReturnSuccess)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: unable to open interface (%08x)\n", kr));
			(*c->interface)->Release (c->interface);
			c->interface = NULL;
			continue;
		}

		kr = (*c->interface)->GetNumEndpoints (c->interface, &intfNumEndpoints);
		if (kr != kIOReturnSuccess)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: unable to get number of endpoints (%08x)\n", kr));
			(*c->interface)->USBInterfaceClose (c->interface);
			(*c->interface)->Release (c->interface);
			c->interface = NULL;
			continue;
		}
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: interface has %d endpoints\n", intfNumEndpoints));

		/* If device didn't answer to the connection_information request,
		 * try to read a preamble sent by the device. This is sent by devices which chipsets
		 * don't support the vendor control endpoint requests. Look for the first pipe on
		 * which a preamble is available.
		 */
		if (!pipe_info_retrieved)
		{
			int reqTimeout;
			int preambleFound = 0;
			for (reqTimeout = 100; reqTimeout <= 300 && !preambleFound; reqTimeout += 100)
			{
				LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: checking for pipe_info sent by device with timeout %dms\n",reqTimeout));
				for (pipeRef = 1; pipeRef <= intfNumEndpoints; pipeRef++)
				{
					kr = (*c->interface)->GetPipeProperties (c->interface, pipeRef, &direction, &number,
										  &transferType, &maxPacketSize, &interval);
					if (kr != kIOReturnSuccess)
						continue;
					if (direction == kUSBIn)
					{
						UInt32 size = sizeof(c->read_buffer)-1;
						LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: trying pipe %d type=%d\n", pipeRef, (int)transferType));
						if (transferType == kUSBBulk)
							kr = (*c->interface)->ReadPipeTO (c->interface, pipeRef, &c->read_buffer, &size, reqTimeout, 250);
						else
							kr = (*c->interface)->ReadPipe (c->interface, pipeRef, &c->read_buffer, &size);

						if (kr == kIOReturnSuccess && size >= 8)
						{
							/* got something! */
							LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: got %d bytes there!\n", (int)size));
							CHECK(PI_DBG_DEV, PI_DBG_LVL_DEBUG, pi_dumpdata(c->read_buffer, size));
							if (!memcmp(c->read_buffer, "VNDR10", 6))
							{
								/* VNDR version 1.0 */
								palm_ext_connection_info ci;
								memcpy(&ci, &c->read_buffer[6], sizeof(ci));
								decode_generic_connection_information(&ci, &port_number, &input_pipe_number, &output_pipe_number);
								preambleFound = 1;
								break;
							}
						}
					}
				}
			}
		}

		/* Locate the pipes we're going to use for reading and writing.
		 * We have four chances to find the right pipes:
		 * 1. If we got a hint from the device with input/output pipes, we try this one first.
		 * 2. If we didn't get both pipes, try using the port number hint. There is at least one recent
		 *    device (LifeDrive) on which the port_number hint is actually an endpoint pair index.
		 *    The code below tries to cope with that.
		 * 3. If we're still missing one or two pipes, give a second try looking for pipes with a
		 *    64 bytes transfer size
		 * 4. Finally of this failed, forget about the transfer size and take the first ones that
		 *    come (i.e. Tungsten W has a 64 bytes IN pipe and a 32 bytes OUT pipe).
		 */
		for (pass=1; pass <= 4 && (c->in_pipe_ref==0 || c->out_pipe_ref==0); pass++)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: pass %d looking for pipes, port_number=0x%02x, input_pipe_number=0x%02x, output_pipe_number=0x%02x\n",
				pass, port_number, input_pipe_number, output_pipe_number));
			c->in_pipe_ref = 0;
			c->out_pipe_ref = 0;

			int input_pipes_seen = 0;
			int output_pipes_seen = 0;
			
			for (pipeRef = 1; pipeRef <= intfNumEndpoints; pipeRef++)
			{
				kr = (*c->interface)->GetPipeProperties (c->interface, pipeRef, &direction, &number,
									  &transferType, &maxPacketSize, &interval);
				if (kr != kIOReturnSuccess)
				{
					LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: unable to get properties of pipe %d (kr=0x%08x)\n", pipeRef, kr));
				}
				else
				{
					int pair_index = (direction == kUSBIn) ? ++input_pipes_seen : ++output_pipes_seen;
					
					LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG,
						"darwinusb: pipe %d: direction=0x%02x, number=0x%02x, transferType=0x%02x, maxPacketSize=%d, interval=0x%02x, pair_index=%d\n",
						pipeRef,(int)direction,(int)number,(int)transferType,(int)maxPacketSize,(int)interval,pair_index));
					if (c->in_pipe_ref == 0 &&
					    direction == kUSBIn &&
					    transferType == kUSBBulk)
					{
						if ((pass == 1 && input_pipe_number != 0xff && number == input_pipe_number) ||
						    (pass == 1 && port_number == 0xff && input_pipe_number == 0xff && (accept_flags & FLAG_USE_FIRST_PAIR)) ||
						    (pass == 2 && port_number != 0xff && number == port_number) ||
						    (pass == 3 && ((port_number != 0xff && pair_index == port_number) || (port_number == 0xff && (maxPacketSize == 64 || maxPacketSize == 512)))) ||
						     pass == 4)
						{
							c->in_pipe_ref = pipeRef;
							c->in_pipe_bulk_size = maxPacketSize;
						}
					}
					else if (c->out_pipe_ref == 0 &&
					         direction == kUSBOut &&
					         transferType == kUSBBulk)
					{
						if ((pass == 1 && output_pipe_number != 0xff && number == output_pipe_number) ||
						    (pass == 1 && port_number == 0xff && input_pipe_number == 0xff && (accept_flags & FLAG_USE_FIRST_PAIR)) ||
						    (pass == 2 && port_number != 0xff && number == port_number) ||
						    (pass == 3 && ((port_number != 0xff && pair_index == port_number) || (port_number == 0xff && (maxPacketSize == 64 || maxPacketSize == 512)))) ||
						     pass == 4)
						{
							c->out_pipe_ref = pipeRef;
							c->out_pipe_bulk_size = maxPacketSize;
						}
					}
				}
			}
		}

		if (c->in_pipe_ref && c->out_pipe_ref)
			return kIOReturnSuccess;

		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: couldn't find any suitable pipes pair on this interface\n"));
		(*c->interface)->USBInterfaceClose (c->interface);
		(*c->interface)->Release (c->interface);
		c->interface = NULL;
		c->in_pipe_ref = 0;
		c->out_pipe_ref = 0;
	}

	return c->interface==NULL ? -1 : kIOReturnSuccess;
}

static IOReturn
control_request(IOUSBDeviceInterface **dev, UInt8 requestType, UInt8 request, UInt16 value, UInt16 index, void *pData, UInt16 maxReplyLength)
{
	IOReturn kr;
	IOUSBDevRequest req;
	void *pReply = pData;

	if (!pReply && maxReplyLength)
		pReply = malloc(maxReplyLength);

	req.bmRequestType = requestType;	/* i.e. 0xc2=kUSBIn, kUSBVendor, kUSBEndpoint */
	req.bRequest = request;
	req.wValue = value;
	req.wIndex = index;
	req.wLength = maxReplyLength;
	req.pData = pReply;
	req.wLenDone = 0;

	kr = (*dev)->DeviceRequest (dev, &req);

	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: control_request(0x%02x) wLenDone=%d kr=0x%08lx\n", (int)request, req.wLenDone, kr));

	if (pReply && !pData)
		free (pReply);

	return kr;
}

static IOReturn
read_visor_connection_information (IOUSBDeviceInterface **dev, int *port_number, int *input_pipe, int *output_pipe)
{
	int i;
	kern_return_t kr;
	visor_connection_info ci;

	memset(&ci, 0, sizeof(ci));
	kr = control_request (dev, 0xc2, VISOR_GET_CONNECTION_INFORMATION, 0, 0, &ci, sizeof(ci));
	if (kr != kIOReturnSuccess)
	{
		LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: VISOR_GET_CONNECTION_INFORMATION failed (err=%08x)\n", kr));
	}
	else
	{
		CHECK(PI_DBG_DEV, PI_DBG_LVL_DEBUG, pi_dumpdata((const char *)&ci, sizeof(ci)));
		ci.num_ports = CFSwapInt16LittleToHost(ci.num_ports);		/* number of ports is little-endian */
		if (ci.num_ports > 8)
			ci.num_ports = 8;
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: VISOR_GET_CONNECTION_INFORMATION, num_ports=%d\n", ci.num_ports));
		kr = kIOReturnNotReady;
		for (i=0; i < ci.num_ports; i++)
		{
			[[maybe_unused]] char *function_str;
			switch (ci.connections[i].port_function_id)
			{
				case VISOR_FUNCTION_GENERIC:
					function_str = "GENERIC";
					break;
				case VISOR_FUNCTION_DEBUGGER:
					function_str = "DEBUGGER";
					break;
				case VISOR_FUNCTION_HOTSYNC:
					function_str = "HOTSYNC";
					if (port_number)
						*port_number = ci.connections[i].port;
					kr = kIOReturnSuccess;
					break;
				case VISOR_FUNCTION_CONSOLE:
					function_str = "CONSOLE";
					break;
				case VISOR_FUNCTION_REMOTE_FILE_SYS:
					function_str = "REMOTE_FILE_SYSTEM";
					break;
				default:
					function_str = "UNKNOWN";
					break;
			}
			LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "\t[%d] port_function_id=0x%02x (%s)\n", i, ci.connections[i].port_function_id, function_str));
			LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "\t[%d] port=%d\n", i, ci.connections[i].port));
		}
	}
	return kr;
}

static IOReturn
decode_generic_connection_information(palm_ext_connection_info *ci, int *port_number, int *input_pipe, int *output_pipe)
{
	int i;

	CHECK(PI_DBG_DEV, PI_DBG_LVL_DEBUG, pi_dumpdata((const char *)ci, sizeof(palm_ext_connection_info)));
	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: decode_generic_connection_information num_ports=%d, endpoint_numbers_different=%d\n", ci->num_ports, ci->endpoint_numbers_different));

	/* Devices that don't actually implement PALM_GET_EXT_CONNECTION_INFORMATION
	 * (e.g. Handspring Treo 90) can leave num_ports holding whatever was in
	 * the buffer; clamp it to the size of the connections[] array to avoid
	 * walking off the end of the struct.
	 */
	if (ci->num_ports > (sizeof(ci->connections) / sizeof(ci->connections[0])))
		ci->num_ports = sizeof(ci->connections) / sizeof(ci->connections[0]);

	for (i=0; i < ci->num_ports; i++)
	{
		UInt32 port_function_id = CFSwapInt32LittleToHost(ci->connections[i].port_function_id);
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "\t[%d] port_function_id=0x%08lx ('%4.4s')\n", i, port_function_id, (char*)&port_function_id));
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "\t[%d] port=%d\n", i, ci->connections[i].port));
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "\t[%d] endpoint_info=%d (0x%02x)\n", i, ci->connections[i].endpoint_info, (int)ci->connections[i].endpoint_info));
		if (port_function_id == 'rfsL')
		{
			/* This is a T5 in USB connected but not synchronizing:
			 * don't bother trying to talk to it
			 */
			return kIOReturnNotReady;
		}
		if (port_function_id == 'sync' || port_function_id == 'ppp_')
		{
			/* we found the port/pipes to use for synchronization
			 * If endpoint_numbers_different is != 0, then the number of each
			 * endpoint to use for IN and OUT is stored in endpoint_info.
			 * Otherwise, the port number (same for both endpoints) is in
			 * port.
			 */
			if (!ci->endpoint_numbers_different)
			{
				if (port_number)
					*port_number = ci->connections[i].port;
			}
			else if (ci->connections[i].endpoint_info)
			{
				if (input_pipe)
					*input_pipe = ci->connections[i].endpoint_info >> 4;
				if (output_pipe)
					*output_pipe = ci->connections[i].endpoint_info & 0x0f;
			}
		}
	}

	return kIOReturnSuccess;
}

static IOReturn
read_generic_connection_information (IOUSBDeviceInterface **dev, int *port_number, int *input_pipe, int *output_pipe)
{
	kern_return_t  kr;
	palm_ext_connection_info ci;

	memset(&ci, 0, sizeof(ci));
	kr = control_request (dev, 0xc2, PALM_GET_EXT_CONNECTION_INFORMATION, 0, 0, &ci, sizeof(ci));
	if (kr != kIOReturnSuccess)
	{
		LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: PALM_GET_EXT_CONNECTION_INFORMATION failed (err=%08x)\n", kr));
		return kr;
	}
	return decode_generic_connection_information(&ci, port_number, input_pipe, output_pipe);
}

static IOReturn
klsi_set_portspeed(IOUSBDeviceInterface **dev, int speed)
{
	/* set the comms speed for a KLSI serial adapter (PalmConnect USB) */
	kern_return_t kr;
	klsi_port_settings settings = {5, KLSI_BAUD_9600, 8, KLSI_PARITY_NONE, KLSI_STOPBITS_0};
	switch (speed)
	{
		case 230400:	settings.BaudRateIndex = KLSI_BAUD_230400;	break;
		case 115200:	settings.BaudRateIndex = KLSI_BAUD_115200;	break;
		case 57600:	settings.BaudRateIndex = KLSI_BAUD_57600;	break;
		case 38400:	settings.BaudRateIndex = KLSI_BAUD_38400;	break;
		case 28800:	settings.BaudRateIndex = KLSI_BAUD_28800;	break;
		case 19200:	settings.BaudRateIndex = KLSI_BAUD_19200;	break;
		case 14400:	settings.BaudRateIndex = KLSI_BAUD_14400;	break;
		case 9600:	settings.BaudRateIndex = KLSI_BAUD_9600;	break;
		case 7200:	settings.BaudRateIndex = KLSI_BAUD_7200;	break;
		case 4800:	settings.BaudRateIndex = KLSI_BAUD_4800;	break;
		case 2400:	settings.BaudRateIndex = KLSI_BAUD_2400;	break;
		case 1200:	settings.BaudRateIndex = KLSI_BAUD_1200;	break;
		case 600:	settings.BaudRateIndex = KLSI_BAUD_600;		break;
		default:							break;
	}

	kr = control_request(dev, 0x40, KLSI_SET_COMM_DESCRIPTOR, 0, 0, &settings, 5);
	if (kr == kIOReturnSuccess)
	{
		kr = control_request(dev, 0x40, KLSI_SET_COMM_DESCRIPTOR, 0, 0, &settings, 5);
		if (kr == kIOReturnSuccess)
			kr = control_request(dev, 0x40, KLSI_SET_FLOWCONTROL, (speed > 9600) ? KLSI_FLOW_USE_RTS : 0, 0, NULL, 0);

		control_request(dev, 0x40, KLSI_SET_HANDSHAKE_LINES, KLSI_SETHS_DTR | KLSI_SETHS_RTS, 0, NULL, 0);
	}
	return kr;
}

static void
device_notification(usb_connection_t *c, io_service_t service, natural_t messageType, void *messageArgument)
{
	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: device_notification (c=%p messageType=0x%08lx)\n", c, messageType));

	if (messageType == kIOMessageServiceIsTerminated && c != NULL)
	{
		c->device_present = 0;
		c->opened = 0;		/* so that stop_listening() does'nt try to send the control_request */
		if (change_refcount(c,-1) > 0)
		{
			/* In case the reading thread is waiting for data,
			 * we need to raise the usb_data_available cond once.
			 * since darwin_usb_read tests usb.opened, it will
			 * gracefully exit during a data wait.
			 */
			ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: device_notification signaling c->read_queue_data_avail_cond for c=%p\n",c));
			pthread_mutex_lock (&c->read_queue_mutex);
			pthread_cond_signal (&c->read_queue_data_avail_cond);
		    	pthread_mutex_unlock (&c->read_queue_mutex);
		}
	}
}

static void
read_completion (usb_connection_t *c, IOReturn result, void *arg0)
{
	size_t bytes_read = (size_t) arg0;

	ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: read_completion(c=%p, result=0x%08lx, bytes_read=%d)\n", c, (long)result, bytes_read));

	if (!c->opened)
	{
	    	change_refcount(c, -1);
	    	return;
	}

	if (result != kIOReturnSuccess)
	{
		LOG((PI_DBG_DEV, PI_DBG_LVL_WARN, "darwinusb: async read completion(%p) received error code 0x%08x\n", c, result));
	}

	if (bytes_read)
	{
		if (c->vendorID == VENDOR_PALMONE && c->productID == PRODUCT_PALMCONNECT_USB)
		{
			/* decode PalmConnect USB frame */
			if (bytes_read < 2)
				bytes_read = 0;
			else
			{
				int data_size = (int)c->read_buffer[0] | ((int)c->read_buffer[1] << 8);
				if ((data_size + 2) > bytes_read)
				{
					LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: invalid PalmConnect packet (%d bytes, says %d content bytes)\n",
						bytes_read, data_size));
					bytes_read = 0;
				} else {
					memmove(&c->read_buffer[0], &c->read_buffer[2], data_size);
					bytes_read = data_size;
				}
			}
		}
		if (bytes_read > 0)
		{
			pthread_mutex_lock(&c->read_queue_mutex);
			if (c->read_queue == NULL)
			{
				c->read_queue_size = ((bytes_read + 0xfffe) & ~0xffff) - 1;		/* 64k chunks */
				c->read_queue = (char *) malloc (c->read_queue_size);
				c->read_queue_used = 0;
			}
			else if ((c->read_queue_used + bytes_read) > c->read_queue_size)
			{
				c->read_queue_size += ((bytes_read + 0xfffe) & ~0xffff) - 1;
				c->read_queue = (char *) realloc (c->read_queue, c->read_queue_size);
			}
			if (c->read_queue)
			{
				memcpy(c->read_queue + c->read_queue_used, c->read_buffer, bytes_read);
				c->read_queue_used += bytes_read;
				ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: signaling c->read_queue_data_avail_cond for c=%p\n",c));
				pthread_cond_signal(&c->read_queue_data_avail_cond);
			}
			else
			{
				c->read_queue_used = 0;
				c->read_queue_size = 0;
			}
			pthread_mutex_unlock(&c->read_queue_mutex);

			if (c->total_bytes_read == 0)
			{
				/* the connection is now considered live */
				pthread_mutex_lock(&usb_connections_mutex);
				pthread_cond_signal(&usb_connection_added_cond);
				pthread_mutex_unlock(&usb_connections_mutex);
			}
			c->total_bytes_read += (unsigned long)bytes_read;
		}
	}

	if (result != kIOReturnAborted && c->opened && usb_run_loop)
	{
		if (result != kIOReturnSuccess)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: clearing input pipe stall\n"));
			(*c->interface)->ClearPipeStallBothEnds (c->interface, c->in_pipe_ref);
		}
		prime_read(c);
	}

	change_refcount(c, -1);
}

static int
prime_read(usb_connection_t *c)
{
	/* increment refcount */
	if (pthread_mutex_lock(&c->ref_count_mutex) != 0)
	{
		/* c became invalid? */
        	LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb:  prime_read(%p): can't lock c->ref_count_mutex (structure freed?)\n", c));
        	return 0;
	}
	c->ref_count++;
	pthread_mutex_unlock(&c->ref_count_mutex);

	if (c->opened)
	{
		/* select a correct read size (always use a multiple of the USB packet size) */
		if (c->vendorID == VENDOR_PALMONE && c->productID == PRODUCT_PALMCONNECT_USB)
		{
			/* with PalmConnect USB, always use 64 bytes */
			c->last_read_ahead_size = 64;
		}
		else
		{
			c->last_read_ahead_size = c->read_ahead_size & ~(c->in_pipe_bulk_size-1);
			if (c->last_read_ahead_size <= 0)
				c->last_read_ahead_size = c->auto_read_size;
			if (c->last_read_ahead_size > MAX_AUTO_READ_SIZE)
				c->last_read_ahead_size = MAX_AUTO_READ_SIZE;
			else if (c->last_read_ahead_size < c->in_pipe_bulk_size)
				c->last_read_ahead_size = c->in_pipe_bulk_size;			// USB packet size
		}

		ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: prime_read(%p) for %d bytes\n", c, c->last_read_ahead_size));

		IOReturn kr = (*c->interface)->ReadPipeAsyncTO (c->interface, c->in_pipe_ref,
				c->read_buffer, c->last_read_ahead_size, 5000, 5000, (IOAsyncCallback1)&read_completion, (void *)c);

		if (kr == kIOUSBPipeStalled || kr == kIOUSBTransactionTimeout || kr == kIOUSBDataToggleErr)
		{
			// this code may be removed later as we are now using ReadPipeAsyncTO which is not
			// supposed to return them (it calls the completion callback instead)
			ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: stalled -- clearing stall and re-priming\n"));
			(*c->interface)->ClearPipeStall (c->interface, c->in_pipe_ref);
			kr = (*c->interface)->ReadPipeAsyncTO (c->interface, c->in_pipe_ref,
					c->read_buffer, c->last_read_ahead_size, 5000, 5000, (IOAsyncCallback1)&read_completion, (void *)c);
		}
		if (kr == kIOReturnSuccess)
		{
			c->read_pending = 1;
			return 1;
		}
		LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb:  prime_read(%p): ReadPipeAsync returned error 0x%08x\n", c, kr));
	}

	change_refcount(c, -1);
	return 0;
}

static int
accepts_device(unsigned short vendor, unsigned short product, unsigned short *flags)
{
	int i;
	for (i=0; i < (int)(sizeof(acceptedDevices) / sizeof(acceptedDevices[0])); i++)
	{
		if (vendor == acceptedDevices[i].vendorID)
		{
			if (acceptedDevices[i].flags & FLAG_REJECT)
				return 0;
			if (acceptedDevices[i].productID == 0xffff || product == acceptedDevices[i].productID)
			{
				if (flags)
					*flags = acceptedDevices[i].flags;
				return 1;
			}
		}
	}
	return 0;
}


/***************************************************************************/
/*                                                                         */
/*                      ENTRY POINTS CALLED BY LIBPISOCK                   */
/*                                                                         */
/***************************************************************************/
static int
u_flush(pi_socket_t *ps, int flags)
{
	usb_connection_t *c = ((pi_usb_data_t *)ps->device->data)->ref;
	if (change_refcount(c, +1) <= 0)
	    return PI_ERR_SOCK_DISCONNECTED;
	if (!c->opened)
	{
		change_refcount(c, -1);
		return PI_ERR_SOCK_DISCONNECTED;
	}
	if (flags & PI_FLUSH_INPUT)
	{
		pthread_mutex_lock(&c->read_queue_mutex);
		c->read_queue_used = 0;
		pthread_mutex_unlock(&c->read_queue_mutex);
	}
	change_refcount(c, -1);
	return 0;
}

static int
u_open(struct pi_socket *ps, struct pi_sockaddr *addr, size_t addrlen)
{
	pthread_mutex_lock(&usb_thread_ready_mutex);
	if (usb_thread == 0)
	{
		/* thread doesn't exist yet: create it and wait for
		 * the init phase to be either successful or failed
		 */
		pthread_create(&usb_thread, NULL, usb_thread_run, NULL);
		pthread_cond_wait(&usb_thread_ready_cond, &usb_thread_ready_mutex);
		pthread_mutex_unlock(&usb_thread_ready_mutex);
		if (usb_thread != 0)
			return 1;

		errno = EINVAL;
		return pi_set_error(ps->sd, PI_ERR_GENERIC_SYSTEM);
	}
	pthread_mutex_unlock(&usb_thread_ready_mutex);
	return 1;
}

static int
u_close(struct pi_socket *ps)
{
	usb_connection_t *c = ((pi_usb_data_t *)ps->device->data)->ref;
	ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_close(ps=%p c=%p\n",ps,c));
	if (c && change_refcount(c, 1) > 0) 
	{
		// @@@ TODO: for KLSI, we should set total_bytes_read to 0 so that next time we receive data,
		// @@@ a new connection is declared `live'
		//if (c->vendorID == VENDOR_PALMONE && c->productID == PRODUCT_PALMCONNECT_USB)
		//	control_request(c->device, 0x40, KLSI_SET_HANDSHAKE_LINES, KLSI_SETHS_RTS, 0, NULL, 0);

		c->opened = 0;		/* set opened to 0 so that other threads don't try to acquire this connection, as it is on the way out */
		c->total_bytes_read = 0;
		c->total_bytes_written = 0;
		c->ps = NULL;
		change_refcount(c, -2);	/* decrement current refcount + disconnect */
	}
	return close(ps->sd);
}

static int
u_wait_for_device(struct pi_socket *ps, int *timeout)
{
	usb_connection_t *c = connection_for_socket(ps);
	struct timespec to, to_expiration;

	ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_wait_for_device(ps=%p c=%p, timeout=%d)\n",ps,c,timeout ? *timeout : 0));

	if (timeout && *timeout)
	{
		pi_timeout_to_timespec(*timeout, &to_expiration);
		to.tv_sec = *timeout / 1000;
		to.tv_nsec = (*timeout % 1000) * 1000 * 1000;
	}

	if (c == NULL)
	{
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_wait_for_device -> waiting for a connection to come up\n"));
		pthread_mutex_lock(&usb_connections_mutex);
		if (timeout && *timeout)
		{
			if (pthread_cond_timedwait_relative_np(&usb_connection_added_cond, &usb_connections_mutex, &to) == ETIMEDOUT)
//			if (pthread_cond_timedwait(&usb_connection_added_cond, &usb_connections_mutex, &when) == ETIMEDOUT)
			{
				LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_wait_for_device -> connection wait timed out\n"));
				pthread_mutex_unlock(&usb_connections_mutex);
				return PI_ERR_SOCK_TIMEOUT;
			}
		}
		else
			pthread_cond_wait(&usb_connection_added_cond, &usb_connections_mutex);
		pthread_mutex_unlock(&usb_connections_mutex);

		c = connection_for_socket(ps);

		ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_wait_for_device -> end of wait, c=%p\n",c));
		if (c && c->vendorID==VENDOR_PALMONE && c->productID==PRODUCT_PALMCONNECT_USB)
		{
			/* when working with a PalmConnect USB, make sure we use the right speed 
			 * then let the adapter send us data
			 */
			klsi_set_portspeed(c->device, ((pi_usb_data_t *)ps->device->data)->rate);
		}

		if (timeout && *timeout)
		{
			/* if there was a timeout, compute the remaining timeout time */
			*timeout = pi_timespec_to_timeout(&to_expiration);
		}
	}
	return (c != NULL);
}

static int
u_poll(struct pi_socket *ps, int timeout)
{
	usb_connection_t *c = connection_for_socket(ps);
	if (c == NULL)
		return PI_ERR_SOCK_DISCONNECTED;

	struct timespec to;

	ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_poll(ps=%p c=%p, timeout=%d)\n",ps,c,timeout));

	if (timeout)
	{
		to.tv_sec = timeout / 1000;
		to.tv_nsec = (timeout % 1000) * 1000 * 1000;
	}

	pthread_mutex_lock(&c->read_queue_mutex);
	int available = c->read_queue_used;
	if (!available)
	{
		ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_poll -> waiting data to be available for c=%p\n",c));
		if (timeout)
		{
			if (pthread_cond_timedwait_relative_np(&c->read_queue_data_avail_cond, &c->read_queue_mutex, &to) == ETIMEDOUT)
			{
				ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_poll -> data wait timed out\n"));
				available = PI_ERR_SOCK_TIMEOUT;
			}
			else
				available = c->read_queue_used;
		}
		else
		{
			/* wait forever for some data to arrive */
			pthread_cond_wait(&c->read_queue_data_avail_cond, &c->read_queue_mutex);
			available = c->read_queue_used;
		}
	}
	pthread_mutex_unlock(&c->read_queue_mutex);
	change_refcount(c, -1);

	ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_poll -> end, c=%p, result=%d\n",c,available));

	return available;
}

static ssize_t
u_write(struct pi_socket *ps, const unsigned char *buf, size_t len, int flags)
{
	IOReturn kr;
	size_t	transferred_bytes = 0,
		data_size;
	usb_connection_t *c = ((pi_usb_data_t *)ps->device->data)->ref;
	if (change_refcount(c,+1)<=0 || !c->opened)
	{
		/* make sure we report broken connections */
		if (ps->state == PI_SOCK_CONN_ACCEPT || ps->state == PI_SOCK_CONN_INIT)
			ps->state = PI_SOCK_CONN_BREAK;
		change_refcount(c, -1);
		return PI_ERR_SOCK_DISCONNECTED;
	}

	ULOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_write(ps=%p, c=%p, len=%d, flags=%d)\n",ps,c,(int)len,flags));

	if (c->vendorID == VENDOR_PALMONE && c->productID == PRODUCT_PALMCONNECT_USB)
	{
		/* format packets for the PalmConnect USB adapter */
		unsigned char *packet = malloc(c->out_pipe_bulk_size);
		if (packet == NULL)
			return pi_set_error(ps->sd, PI_ERR_GENERIC_MEMORY);

		while (len > 0)
		{
			data_size = len;
			if (data_size > (size_t)(c->out_pipe_bulk_size - 2))
				data_size = (size_t)(c->out_pipe_bulk_size - 2);
			packet[0] = data_size;
			packet[1] = data_size >> 8;
			memcpy(&packet[2], &buf[transferred_bytes], data_size);

			kr = (*c->interface)->WritePipeTO(c->interface, c->out_pipe_ref, packet, c->out_pipe_bulk_size, 5000, 5000);
			if (kr != kIOReturnSuccess) {
				LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: darwin_usb_write(): WritePipe returned kr=0x%08lx\n", kr));
				break;
			}

			c->total_bytes_written += (unsigned long)data_size;
			transferred_bytes += data_size;
			len -= data_size;
		}
		free(packet);
	}
	else
	{
		kr = (*c->interface)->WritePipeTO(c->interface, c->out_pipe_ref, (void *)buf, len, 5000, 5000);
		if (kr != kIOReturnSuccess)
		{
			LOG((PI_DBG_DEV, PI_DBG_LVL_ERR, "darwinusb: darwin_usb_write(): WritePipe returned kr=0x%08lx\n", kr));
		}
		else
		{
			c->total_bytes_written += (unsigned long)len;
			transferred_bytes = len;
		}
	}

	if (change_refcount(c, -1) <= 0)
		return PI_ERR_SOCK_DISCONNECTED;

	return transferred_bytes;
}

static ssize_t
u_read(struct pi_socket *ps, pi_buffer_t *buf, size_t len, int flags)
{
	int timeout = ((struct pi_usb_data *)ps->device->data)->timeout;
	int timed_out = 0;
	usb_connection_t *c = ((pi_usb_data_t *)ps->device->data)->ref;

	if (change_refcount(c,+1)<=0 || !c->opened)
	{
		/* make sure we report broken connections */
		if (ps->state == PI_SOCK_CONN_ACCEPT || ps->state == PI_SOCK_CONN_INIT)
			ps->state = PI_SOCK_CONN_BREAK;
		if (c != NULL)
			change_refcount(c, -1);
		return PI_ERR_SOCK_DISCONNECTED;
	}

	if (pi_buffer_expect (buf, len) == NULL)
	{
		errno = ENOMEM;
		change_refcount(c, -1);
		return pi_set_error(ps->sd, PI_ERR_GENERIC_MEMORY);
	}

#ifdef DEBUG_USB
	struct timeval startTime, endTime;
	gettimeofday(&startTime, NULL);
	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_read(ps=%p, c=%p, len=%d, timeout=%d, flags=%d)\n", ps, c, (int)len, timeout, flags));
#endif

	pthread_mutex_lock(&c->read_queue_mutex);

	if (flags == PI_MSG_PEEK && len > 256)
		len = 256;

	if (c->read_queue_used < len)
	{
		struct timeval now;
		struct timespec when;
		gettimeofday(&now, NULL);
		when.tv_sec = now.tv_sec + timeout / 1000;
		when.tv_nsec = now.tv_usec + (timeout % 1000) * 1000 * 1000;
		if (when.tv_nsec >= 1000000000)
		{
			when.tv_nsec -= 1000000000;
			when.tv_sec++;
		}
		do
		{
			/* next prime_read() will use a bigger read request */
			c->read_ahead_size = len - c->read_queue_used - c->last_read_ahead_size;
			if (timeout)
			{
				if (pthread_cond_timedwait(&c->read_queue_data_avail_cond, &c->read_queue_mutex, &when) == ETIMEDOUT)
				{
					timed_out = 1;
					break;
				}
			}
			else
				pthread_cond_wait(&c->read_queue_data_avail_cond, &c->read_queue_mutex);
		}
		while (c->opened && (c->read_queue_used < len || (flags == PI_MSG_PEEK && c->read_queue_used >= 256)));

		c->read_ahead_size = 0;
	}

	if (!c->opened)
	{
		/* make sure we report broken connections */
		if (ps->state == PI_SOCK_CONN_ACCEPT || ps->state == PI_SOCK_CONN_INIT)
			ps->state = PI_SOCK_CONN_BREAK;
		len = PI_ERR_SOCK_DISCONNECTED;
	}
	else
	{
		if (c->read_queue_used < len)
			len = c->read_queue_used;

		if (len)
		{
			pi_buffer_append (buf, c->read_queue, len);

			if (flags != PI_MSG_PEEK)
			{
				c->read_queue_used -= len;
				if (c->read_queue_used > 0)
					memmove(c->read_queue, c->read_queue + len, c->read_queue_used);
				if ((c->read_queue_size - c->read_queue_used) > (16L * 65535L))
				{
					/* if we have more than 1M free in the read queue, we'd better
					 * shrink the buffer
					 */
					c->read_queue_size = ((c->read_queue_used + 0xfffe) & ~0xffff) - 1;
					c->read_queue = (char *) realloc (c->read_queue, c->read_queue_size);
				}
			}
		}
		else if (timed_out)
			len = PI_ERR_SOCK_TIMEOUT;
	}

#ifdef DEBUG_USB
	double a,b;
	gettimeofday(&endTime, NULL);
	a = (double)startTime.tv_sec + (double)startTime.tv_usec / (double)1000000;
	b = (double)endTime.tv_sec + (double)endTime.tv_usec / (double)1000000;
	if (len >= 0) {
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: -> u_read complete (bytes_read=%d, remaining bytes in queue=%d) in %.06fs\n",
		     len, c->read_queue_used,b-a));
	} else {
		LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: -> u_read end with error (err=%d) in %.06fs\n",
		     len,b-a));
	}
#endif

	pthread_mutex_unlock(&c->read_queue_mutex);
	if (change_refcount(c, -1) <= 0)
		len = PI_ERR_SOCK_DISCONNECTED;
	return len;
}

static int
u_changebaud(pi_socket_t *ps)
{
	/* Change the baud rate. This is only useful for serial-to-USB adapters,
	 * as these adapters need to know which rate we use to talk to the device.
	 * We currently only support the PalmConnect USB adapter.
	 */
	pi_usb_data_t *data = (pi_usb_data_t *)ps->device->data;
	usb_connection_t *c = data->ref;
	if (c == NULL)
		return PI_ERR_SOCK_DISCONNECTED;

	LOG((PI_DBG_DEV, PI_DBG_LVL_DEBUG, "darwinusb: u_changebaud(ps=%p, c=%p, rate=%d)\n", ps, c, data->rate));

	if (c->vendorID == VENDOR_PALMONE && c->productID == PRODUCT_PALMCONNECT_USB)
		return klsi_set_portspeed(c->device, data->rate);

	return 0;
}

void
pi_usb_impl_init (struct pi_usb_impl *impl)
{
	impl->open		= u_open;
	impl->close		= u_close;
	impl->write		= u_write;
	impl->read		= u_read;
	impl->flush		= u_flush;
	impl->poll		= u_poll;
	impl->wait_for_device	= u_wait_for_device;
	impl->changebaud	= u_changebaud;
	impl->control_request	= NULL;   /* that is, until we factor out common code */
	impl->interrupt_read	= NULL;
}

/* vi: set ts=8 sw=4 sts=4 noexpandtab: cin */
/* ex: set tabstop=4 expandtab: */
/* Local Variables: */
/* indent-tabs-mode: t */
/* c-basic-offset: 8 */
/* End: */
