You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1969 lines
66 KiB
1969 lines
66 KiB
/*
|
|
* $Id: darwinusb.c,v 1.66 2006/11/09 10:26:21 fpillet Exp $
|
|
*
|
|
* 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 */
|
|
kr = IOMasterPort (MACH_PORT_NULL, &masterPort);
|
|
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++)
|
|
{
|
|
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));
|
|
|
|
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 */
|
|
}
|
|
|
|
/* 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: */
|