594 lines
17 KiB
C++
594 lines
17 KiB
C++
/*
|
|
* Copyright (C) 2008 The Android Open Source Project
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
|
|
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*/
|
|
|
|
#include <inttypes.h>
|
|
#include <stdio.h>
|
|
#include <CoreFoundation/CoreFoundation.h>
|
|
#include <IOKit/IOKitLib.h>
|
|
#include <IOKit/IOCFPlugIn.h>
|
|
#include <IOKit/usb/IOUSBLib.h>
|
|
#include <IOKit/IOMessage.h>
|
|
#include <mach/mach_port.h>
|
|
|
|
#include <memory>
|
|
|
|
#include "usb.h"
|
|
|
|
|
|
/*
|
|
* Internal helper functions and associated definitions.
|
|
*/
|
|
|
|
#if TRACE_USB
|
|
#define WARN(x...) fprintf(stderr, x)
|
|
#else
|
|
#define WARN(x...)
|
|
#endif
|
|
|
|
#define ERR(x...) fprintf(stderr, "ERROR: " x)
|
|
|
|
/** An open usb device */
|
|
struct usb_handle
|
|
{
|
|
int success;
|
|
ifc_match_func callback;
|
|
usb_ifc_info info;
|
|
|
|
UInt8 bulkIn;
|
|
UInt8 bulkOut;
|
|
IOUSBInterfaceInterface190 **interface;
|
|
unsigned int zero_mask;
|
|
};
|
|
|
|
class OsxUsbTransport : public Transport {
|
|
public:
|
|
OsxUsbTransport(std::unique_ptr<usb_handle> handle) : handle_(std::move(handle)) {}
|
|
~OsxUsbTransport() override = default;
|
|
|
|
ssize_t Read(void* data, size_t len) override;
|
|
ssize_t Write(const void* data, size_t len) override;
|
|
int Close() override;
|
|
|
|
private:
|
|
std::unique_ptr<usb_handle> handle_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(OsxUsbTransport);
|
|
};
|
|
|
|
/** Try out all the interfaces and see if there's a match. Returns 0 on
|
|
* success, -1 on failure. */
|
|
static int try_interfaces(IOUSBDeviceInterface182 **dev, usb_handle *handle) {
|
|
IOReturn kr;
|
|
IOUSBFindInterfaceRequest request;
|
|
io_iterator_t iterator;
|
|
io_service_t usbInterface;
|
|
IOCFPlugInInterface **plugInInterface;
|
|
IOUSBInterfaceInterface190 **interface = NULL;
|
|
HRESULT result;
|
|
SInt32 score;
|
|
UInt8 interfaceNumEndpoints;
|
|
UInt8 configuration;
|
|
|
|
// Placing the constant KIOUSBFindInterfaceDontCare into the following
|
|
// fields of the IOUSBFindInterfaceRequest structure will allow us to
|
|
// find all of the interfaces
|
|
request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
|
|
request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
|
|
request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
|
|
request.bAlternateSetting = kIOUSBFindInterfaceDontCare;
|
|
|
|
// SetConfiguration will kill an existing UMS connection, so let's
|
|
// not do this if not necessary.
|
|
configuration = 0;
|
|
(*dev)->GetConfiguration(dev, &configuration);
|
|
if (configuration != 1)
|
|
(*dev)->SetConfiguration(dev, 1);
|
|
|
|
// Get an iterator for the interfaces on the device
|
|
kr = (*dev)->CreateInterfaceIterator(dev, &request, &iterator);
|
|
|
|
if (kr != 0) {
|
|
ERR("Couldn't create a device interface iterator: (%08x)\n", kr);
|
|
return -1;
|
|
}
|
|
|
|
while ((usbInterface = IOIteratorNext(iterator))) {
|
|
// Create an intermediate plugin
|
|
kr = IOCreatePlugInInterfaceForService(
|
|
usbInterface,
|
|
kIOUSBInterfaceUserClientTypeID,
|
|
kIOCFPlugInInterfaceID,
|
|
&plugInInterface,
|
|
&score);
|
|
|
|
// No longer need the usbInterface object now that we have the plugin
|
|
(void) IOObjectRelease(usbInterface);
|
|
|
|
if ((kr != 0) || (!plugInInterface)) {
|
|
WARN("Unable to create plugin (%08x)\n", kr);
|
|
continue;
|
|
}
|
|
|
|
// Now create the interface interface for the interface
|
|
result = (*plugInInterface)->QueryInterface(
|
|
plugInInterface,
|
|
CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID),
|
|
(LPVOID*) &interface);
|
|
|
|
// No longer need the intermediate plugin
|
|
(*plugInInterface)->Release(plugInInterface);
|
|
|
|
if (result || !interface) {
|
|
ERR("Couldn't create interface interface: (%08x)\n",
|
|
(unsigned int) result);
|
|
// continue so we can try the next interface
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Now open the interface. This will cause the pipes
|
|
* associated with the endpoints in the interface descriptor
|
|
* to be instantiated.
|
|
*/
|
|
|
|
/*
|
|
* TODO: Earlier comments here indicated that it was a bad
|
|
* idea to just open any interface, because opening "mass
|
|
* storage endpoints" is bad. However, the only way to find
|
|
* out if an interface does bulk in or out is to open it, and
|
|
* the framework in this application wants to be told about
|
|
* bulk in / out before deciding whether it actually wants to
|
|
* use the interface. Maybe something needs to be done about
|
|
* this situation.
|
|
*/
|
|
|
|
kr = (*interface)->USBInterfaceOpen(interface);
|
|
|
|
if (kr != 0) {
|
|
WARN("Could not open interface: (%08x)\n", kr);
|
|
(void) (*interface)->Release(interface);
|
|
// continue so we can try the next interface
|
|
continue;
|
|
}
|
|
|
|
// Get the number of endpoints associated with this interface.
|
|
kr = (*interface)->GetNumEndpoints(interface, &interfaceNumEndpoints);
|
|
|
|
if (kr != 0) {
|
|
ERR("Unable to get number of endpoints: (%08x)\n", kr);
|
|
goto next_interface;
|
|
}
|
|
|
|
// Get interface class, subclass and protocol
|
|
if ((*interface)->GetInterfaceClass(interface, &handle->info.ifc_class) != 0 ||
|
|
(*interface)->GetInterfaceSubClass(interface, &handle->info.ifc_subclass) != 0 ||
|
|
(*interface)->GetInterfaceProtocol(interface, &handle->info.ifc_protocol) != 0)
|
|
{
|
|
ERR("Unable to get interface class, subclass and protocol\n");
|
|
goto next_interface;
|
|
}
|
|
|
|
handle->info.has_bulk_in = 0;
|
|
handle->info.has_bulk_out = 0;
|
|
|
|
// Iterate over the endpoints for this interface and see if there
|
|
// are any that do bulk in/out.
|
|
for (UInt8 endpoint = 1; endpoint <= interfaceNumEndpoints; endpoint++) {
|
|
UInt8 transferType;
|
|
UInt16 maxPacketSize;
|
|
UInt8 interval;
|
|
UInt8 number;
|
|
UInt8 direction;
|
|
|
|
kr = (*interface)->GetPipeProperties(interface, endpoint,
|
|
&direction,
|
|
&number, &transferType, &maxPacketSize, &interval);
|
|
|
|
if (kr == 0) {
|
|
if (transferType != kUSBBulk) {
|
|
continue;
|
|
}
|
|
|
|
if (direction == kUSBIn) {
|
|
handle->info.has_bulk_in = 1;
|
|
handle->bulkIn = endpoint;
|
|
} else if (direction == kUSBOut) {
|
|
handle->info.has_bulk_out = 1;
|
|
handle->bulkOut = endpoint;
|
|
}
|
|
|
|
if (handle->info.ifc_protocol == 0x01) {
|
|
handle->zero_mask = maxPacketSize - 1;
|
|
}
|
|
} else {
|
|
ERR("could not get pipe properties for endpoint %u (%08x)\n", endpoint, kr);
|
|
}
|
|
|
|
if (handle->info.has_bulk_in && handle->info.has_bulk_out) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (handle->callback(&handle->info) == 0) {
|
|
handle->interface = interface;
|
|
handle->success = 1;
|
|
|
|
/*
|
|
* Clear both the endpoints, because it has been observed
|
|
* that the Mac may otherwise (incorrectly) start out with
|
|
* them in bad state.
|
|
*/
|
|
|
|
if (handle->info.has_bulk_in) {
|
|
kr = (*interface)->ClearPipeStallBothEnds(interface,
|
|
handle->bulkIn);
|
|
if (kr != 0) {
|
|
ERR("could not clear input pipe; result %x, ignoring...\n", kr);
|
|
}
|
|
}
|
|
|
|
if (handle->info.has_bulk_out) {
|
|
kr = (*interface)->ClearPipeStallBothEnds(interface,
|
|
handle->bulkOut);
|
|
if (kr != 0) {
|
|
ERR("could not clear output pipe; result %x, ignoring....\n", kr);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
next_interface:
|
|
(*interface)->USBInterfaceClose(interface);
|
|
(*interface)->Release(interface);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/** Try out the given device and see if there's a match. Returns 0 on
|
|
* success, -1 on failure.
|
|
*/
|
|
static int try_device(io_service_t device, usb_handle *handle) {
|
|
kern_return_t kr;
|
|
IOCFPlugInInterface **plugin = NULL;
|
|
IOUSBDeviceInterface182 **dev = NULL;
|
|
SInt32 score;
|
|
HRESULT result;
|
|
UInt8 serialIndex;
|
|
UInt32 locationId;
|
|
|
|
// Create an intermediate plugin.
|
|
kr = IOCreatePlugInInterfaceForService(device,
|
|
kIOUSBDeviceUserClientTypeID,
|
|
kIOCFPlugInInterfaceID,
|
|
&plugin, &score);
|
|
|
|
if ((kr != 0) || (plugin == NULL)) {
|
|
ERR("Unable to create a plug-in (%08x)\n", kr);
|
|
goto error;
|
|
}
|
|
|
|
// Now create the device interface.
|
|
result = (*plugin)->QueryInterface(plugin,
|
|
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (LPVOID*) &dev);
|
|
if ((result != 0) || (dev == NULL)) {
|
|
ERR("Couldn't create a device interface (%08x)\n", (int) result);
|
|
goto error;
|
|
}
|
|
|
|
/*
|
|
* We don't need the intermediate interface after the device interface
|
|
* is created.
|
|
*/
|
|
IODestroyPlugInInterface(plugin);
|
|
|
|
// So, we have a device, finally. Grab its vitals.
|
|
|
|
|
|
kr = (*dev)->USBDeviceOpen(dev);
|
|
if (kr != 0) {
|
|
WARN("USBDeviceOpen");
|
|
goto out;
|
|
}
|
|
|
|
kr = (*dev)->GetDeviceVendor(dev, &handle->info.dev_vendor);
|
|
if (kr != 0) {
|
|
ERR("GetDeviceVendor");
|
|
goto error;
|
|
}
|
|
|
|
kr = (*dev)->GetDeviceProduct(dev, &handle->info.dev_product);
|
|
if (kr != 0) {
|
|
ERR("GetDeviceProduct");
|
|
goto error;
|
|
}
|
|
|
|
kr = (*dev)->GetDeviceClass(dev, &handle->info.dev_class);
|
|
if (kr != 0) {
|
|
ERR("GetDeviceClass");
|
|
goto error;
|
|
}
|
|
|
|
kr = (*dev)->GetDeviceSubClass(dev, &handle->info.dev_subclass);
|
|
if (kr != 0) {
|
|
ERR("GetDeviceSubClass");
|
|
goto error;
|
|
}
|
|
|
|
kr = (*dev)->GetDeviceProtocol(dev, &handle->info.dev_protocol);
|
|
if (kr != 0) {
|
|
ERR("GetDeviceProtocol");
|
|
goto error;
|
|
}
|
|
|
|
kr = (*dev)->GetLocationID(dev, &locationId);
|
|
if (kr != 0) {
|
|
ERR("GetLocationId");
|
|
goto error;
|
|
}
|
|
snprintf(handle->info.device_path, sizeof(handle->info.device_path),
|
|
"usb:%" PRIu32 "X", (unsigned int)locationId);
|
|
|
|
kr = (*dev)->USBGetSerialNumberStringIndex(dev, &serialIndex);
|
|
|
|
if (serialIndex > 0) {
|
|
IOUSBDevRequest req;
|
|
UInt16 buffer[256];
|
|
|
|
req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
|
|
req.bRequest = kUSBRqGetDescriptor;
|
|
req.wValue = (kUSBStringDesc << 8) | serialIndex;
|
|
//language ID (en-us) for serial number string
|
|
req.wIndex = 0x0409;
|
|
req.pData = buffer;
|
|
req.wLength = sizeof(buffer);
|
|
kr = (*dev)->DeviceRequest(dev, &req);
|
|
|
|
if (kr == kIOReturnSuccess && req.wLenDone > 0) {
|
|
int i, count;
|
|
|
|
// skip first word, and copy the rest to the serial string, changing shorts to bytes.
|
|
count = (req.wLenDone - 1) / 2;
|
|
for (i = 0; i < count; i++)
|
|
handle->info.serial_number[i] = buffer[i + 1];
|
|
handle->info.serial_number[i] = 0;
|
|
}
|
|
} else {
|
|
// device has no serial number
|
|
handle->info.serial_number[0] = 0;
|
|
}
|
|
handle->info.writable = 1;
|
|
|
|
if (try_interfaces(dev, handle)) {
|
|
goto error;
|
|
}
|
|
|
|
out:
|
|
|
|
(*dev)->USBDeviceClose(dev);
|
|
(*dev)->Release(dev);
|
|
return 0;
|
|
|
|
error:
|
|
|
|
if (dev != NULL) {
|
|
(*dev)->USBDeviceClose(dev);
|
|
(*dev)->Release(dev);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
/** Initializes the USB system. Returns 0 on success, -1 on error. */
|
|
static int init_usb(ifc_match_func callback, std::unique_ptr<usb_handle>* handle) {
|
|
int ret = -1;
|
|
CFMutableDictionaryRef matchingDict;
|
|
kern_return_t result;
|
|
io_iterator_t iterator;
|
|
usb_handle h;
|
|
|
|
h.success = 0;
|
|
h.callback = callback;
|
|
|
|
/*
|
|
* Create our matching dictionary to find appropriate devices.
|
|
* IOServiceAddMatchingNotification consumes the reference, so we
|
|
* do not need to release it.
|
|
*/
|
|
matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
|
|
|
|
if (matchingDict == NULL) {
|
|
ERR("Couldn't create USB matching dictionary.\n");
|
|
return -1;
|
|
}
|
|
|
|
result = IOServiceGetMatchingServices(
|
|
kIOMasterPortDefault, matchingDict, &iterator);
|
|
|
|
if (result != 0) {
|
|
ERR("Could not create iterator.");
|
|
return -1;
|
|
}
|
|
|
|
for (;;) {
|
|
if (! IOIteratorIsValid(iterator)) {
|
|
/*
|
|
* Apple documentation advises resetting the iterator if
|
|
* it should become invalid during iteration.
|
|
*/
|
|
IOIteratorReset(iterator);
|
|
continue;
|
|
}
|
|
|
|
io_service_t device = IOIteratorNext(iterator);
|
|
|
|
if (device == 0) {
|
|
break;
|
|
}
|
|
|
|
if (try_device(device, &h) != 0) {
|
|
IOObjectRelease(device);
|
|
ret = -1;
|
|
break;
|
|
}
|
|
|
|
if (h.success) {
|
|
handle->reset(new usb_handle);
|
|
memcpy(handle->get(), &h, sizeof(usb_handle));
|
|
ret = 0;
|
|
break;
|
|
}
|
|
|
|
IOObjectRelease(device);
|
|
}
|
|
|
|
IOObjectRelease(iterator);
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Definitions of this file's public functions.
|
|
*/
|
|
|
|
Transport* usb_open(ifc_match_func callback) {
|
|
std::unique_ptr<usb_handle> handle;
|
|
|
|
if (init_usb(callback, &handle) < 0) {
|
|
/* Something went wrong initializing USB. */
|
|
return nullptr;
|
|
}
|
|
|
|
return new OsxUsbTransport(std::move(handle));
|
|
}
|
|
|
|
int OsxUsbTransport::Close() {
|
|
/* TODO: Something better here? */
|
|
return 0;
|
|
}
|
|
|
|
ssize_t OsxUsbTransport::Read(void* data, size_t len) {
|
|
IOReturn result;
|
|
UInt32 numBytes = len;
|
|
|
|
if (len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (handle_ == nullptr) {
|
|
return -1;
|
|
}
|
|
|
|
if (handle_->interface == nullptr) {
|
|
ERR("usb_read interface was null\n");
|
|
return -1;
|
|
}
|
|
|
|
if (handle_->bulkIn == 0) {
|
|
ERR("bulkIn endpoint not assigned\n");
|
|
return -1;
|
|
}
|
|
|
|
result = (*handle_->interface)->ReadPipe(handle_->interface, handle_->bulkIn, data, &numBytes);
|
|
|
|
if (result == 0) {
|
|
return (int) numBytes;
|
|
} else {
|
|
ERR("usb_read failed with status %x\n", result);
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
ssize_t OsxUsbTransport::Write(const void* data, size_t len) {
|
|
IOReturn result;
|
|
|
|
if (len == 0) {
|
|
return 0;
|
|
}
|
|
|
|
if (handle_ == NULL) {
|
|
return -1;
|
|
}
|
|
|
|
if (handle_->interface == NULL) {
|
|
ERR("usb_write interface was null\n");
|
|
return -1;
|
|
}
|
|
|
|
if (handle_->bulkOut == 0) {
|
|
ERR("bulkOut endpoint not assigned\n");
|
|
return -1;
|
|
}
|
|
|
|
#if 0
|
|
result = (*handle_->interface)->WritePipe(
|
|
handle_->interface, handle_->bulkOut, (void *)data, len);
|
|
#else
|
|
/* Attempt to work around crashes in the USB driver that may be caused
|
|
* by trying to write too much data at once. The kernel IOCopyMapper
|
|
* panics if a single iovmAlloc needs more than half of its mapper pages.
|
|
*/
|
|
const int maxLenToSend = 1048576; // 1 MiB
|
|
int lenRemaining = len;
|
|
result = 0;
|
|
while (lenRemaining > 0) {
|
|
int lenToSend = lenRemaining > maxLenToSend
|
|
? maxLenToSend : lenRemaining;
|
|
|
|
result = (*handle_->interface)->WritePipe(
|
|
handle_->interface, handle_->bulkOut, (void *)data, lenToSend);
|
|
if (result != 0) break;
|
|
|
|
lenRemaining -= lenToSend;
|
|
data = (const char*)data + lenToSend;
|
|
}
|
|
#endif
|
|
|
|
#if 0
|
|
if ((result == 0) && (handle_->zero_mask)) {
|
|
/* we need 0-markers and our transfer */
|
|
if(!(len & handle_->zero_mask)) {
|
|
result = (*handle_->interface)->WritePipe(
|
|
handle_->interface, handle_->bulkOut, (void *)data, 0);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (result != 0) {
|
|
ERR("usb_write failed with status %x\n", result);
|
|
return -1;
|
|
}
|
|
|
|
return len;
|
|
}
|