У меня есть рабочий драйвер Mac на USB-устройство, которое требует связи через конечные точки прерывания. Вот как я это сделал:
В конечном итоге метод, который хорошо работал для меня, был вариантом 1 (отмечено выше). Как уже отмечалось, у меня возникли проблемы с открытием COM-стиля IOUSBInterfaceInterface для устройства. Со временем стало ясно, что это связано с тем, что HIDManager захватывает устройство. Я не смог вырвать управление устройством из HIDManager после его захвата (даже не вызовет вызов USBInterfaceOpenSeize или вызовы USBDeviceOpenSeize).
Чтобы взять под контроль устройство, мне нужно было его захватить перед HIDManager. Решением этого было написать без кодовое kext (расширение ядра).Kext - это, по сути, набор, который находится в System/Library/Extensions, который содержит (обычно) plist (список свойств) и (иногда) драйвер уровня ядра, среди других элементов. В моем случае я хотел только plist, который дал бы инструкции ядру, на каких устройствах он соответствует. Если данные дают более высокий номер счета, чем HIDManager, тогда я мог бы по существу захватить устройство и использовать драйвер пользовательского пространства для связи с ним.
Kext PLIST написано, с некоторыми деталями конкретного проекта модифицированных, заключается в следующем:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>OSBundleLibraries</key>
<dict>
<key>com.apple.iokit.IOUSBFamily</key>
<string>1.8</string>
<key>com.apple.kernel.libkern</key>
<string>6.0</string>
</dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleGetInfoString</key>
<string>Demi USB Device</string>
<key>CFBundleIdentifier</key>
<string>com.demiart.mydevice</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Demi USB Device</string>
<key>CFBundlePackageType</key>
<string>KEXT</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>
<key>IOKitPersonalities</key>
<dict>
<key>Device Driver</key>
<dict>
<key>CFBundleIdentifier</key>
<string>com.apple.kernel.iokit</string>
<key>IOClass</key>
<string>IOService</string>
<key>IOProviderClass</key>
<string>IOUSBInterface</string>
<key>idProduct</key>
<integer>12345</integer>
<key>idVendor</key>
<integer>67890</integer>
<key>bConfigurationValue</key>
<integer>1</integer>
<key>bInterfaceNumber</key>
<integer>0</integer>
</dict>
</dict>
<key>OSBundleRequired</key>
<string>Local-Root</string>
</dict>
</plist>
Значение idVendor и idProduct дает Kext специфичности и увеличить ее счет зонда достаточно.
Для того, чтобы использовать Kext, следующие вещи необходимо сделать (что мой инсталлятор сделает для клиентов):
- Изменить владельца на корню: колесо (
sudo chown root:wheel DemiUSBDevice.kext
)
- Скопируйте Kext для расширения (
sudo cp DemiUSBDevice.kext /System/Library/Extensions
)
- Вызов утилиты kextload загрузить Kext для немедленного использования без перезагрузки (
sudo kextload -vt /System/Library/Extensions/DemiUSBDevice.kext
)
- Прикоснитесь к Extensions папки так, что следующий перезапуск заставит восстановить кеш (
sudo touch /System/Library/Extensions
)
На этом этапе система должна использовать kext, чтобы HIDManager не мог захватить мое устройство. Теперь, что с этим делать? Как писать и читать?
Ниже приведены некоторые упрощенные фрагменты кода, минус любая обработка ошибок, которые иллюстрируют решение. Перед тем, как приступить к работе с устройством, приложение должно знать, когда устройство подключается (и отделяется). Обратите внимание, что это только для целей иллюстрации - некоторые из переменных уровня класса, некоторые являются глобальными, и т.д. Вот код инициализации, который устанавливает присоединять/отсоединять события вверх:
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/usb/IOUSBLib.h>
#include <mach/mach.h>
#define DEMI_VENDOR_ID 12345
#define DEMI_PRODUCT_ID 67890
void DemiUSBDriver::initialize(void)
{
IOReturn result;
Int32 vendor_id = DEMI_VENDOR_ID;
Int32 product_id = DEMI_PRODUCT_ID;
mach_port_t master_port;
CFMutableDictionaryRef matching_dict;
IONotificationPortRef notify_port;
CFRunLoopSourceRef run_loop_source;
//create a master port
result = IOMasterPort(bootstrap_port, &master_port);
//set up a matching dictionary for the device
matching_dict = IOServiceMatching(kIOUSBDeviceClassName);
//add matching parameters
CFDictionarySetValue(matching_dict, CFSTR(kUSBVendorID),
CFNumberCreate(kCFAllocatorDefault, kCFNumberInt32Type, &vendor_id));
CFDictionarySetValue(matching_dict, CFSTR(kUSBProductID),
CFNumberCreate(kCFAllocatorDefault, kCFNumberInt32Type, &product_id));
//create the notification port and event source
notify_port = IONotificationPortCreate(master_port);
run_loop_source = IONotificationPortGetRunLoopSource(notify_port);
CFRunLoopAddSource(CFRunLoopGetCurrent(), run_loop_source,
kCFRunLoopDefaultMode);
//add an additional reference for a secondary event
// - each consumes a reference...
matching_dict = (CFMutableDictionaryRef)CFRetain(matching_dict);
//add a notification callback for detach event
//NOTE: removed_iter is a io_iterator_t, declared elsewhere
result = IOServiceAddMatchingNotification(notify_port,
kIOTerminatedNotification, matching_dict, device_detach_callback,
NULL, &removed_iter);
//call the callback to 'arm' the notification
device_detach_callback(NULL, removed_iter);
//add a notification callback for attach event
//NOTE: added_iter is a io_iterator_t, declared elsewhere
result = IOServiceAddMatchingNotification(notify_port,
kIOFirstMatchNotification, matching_dict, device_attach_callback,
NULL, &g_added_iter);
if (result)
{
throw Exception("Unable to add attach notification callback.");
}
//call the callback to 'arm' the notification
device_attach_callback(NULL, added_iter);
//'pump' the run loop to handle any previously added devices
service();
}
Есть два метода которые используются как обратные вызовы в этом коде инициализации: device_detach_callback и device_attach_callback (оба объявлены статическими методами). device_detach_callback прост:
//implementation
void DemiUSBDevice::device_detach_callback(void* context, io_iterator_t iterator)
{
IOReturn result;
io_service_t obj;
while ((obj = IOIteratorNext(iterator)))
{
//close all open resources associated with this service/device...
//release the service
result = IOObjectRelease(obj);
}
}
device_attach_callback где большая часть магии происходит. В моем коде я это разбивается на несколько методов, но здесь я представлю его как большой монолитный метод ...:
void DemiUSBDevice::device_attach_callback(void * context,
io_iterator_t iterator)
{
IOReturn result;
io_service_t usb_service;
IOCFPlugInInterface** plugin;
HRESULT hres;
SInt32 score;
UInt16 vendor;
UInt16 product;
IOUSBFindInterfaceRequest request;
io_iterator_t intf_iterator;
io_service_t usb_interface;
UInt8 interface_endpoint_count = 0;
UInt8 pipe_ref = 0xff;
UInt8 direction;
UInt8 number;
UInt8 transfer_type;
UInt16 max_packet_size;
UInt8 interval;
CFRunLoopSourceRef m_event_source;
CFRunLoopSourceRef compl_event_source;
IOUSBDeviceInterface245** dev = NULL;
IOUSBInterfaceInterface245** intf = NULL;
while ((usb_service = IOIteratorNext(iterator)))
{
//create the intermediate plugin
result = IOCreatePlugInInterfaceForService(usb_service,
kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin,
&score);
//get the device interface
hres = (*plugin)->QueryInterface(plugin,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), (void**)&dev);
//release the plugin - no further need for it
IODestroyPlugInInterface(plugin);
//double check ids for correctness
result = (*dev)->GetDeviceVendor(dev, &vendor);
result = (*dev)->GetDeviceProduct(dev, &product);
if ((vendor != DEMI_VENDOR_ID) || (product != DEMI_PRODUCT_ID))
{
continue;
}
//set up interface find request
request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
request.bAlternateSetting = kIOUSBFindInterfaceDontCare;
result = (*dev)->CreateInterfaceIterator(dev, &request, &intf_iterator);
while ((usb_interface = IOIteratorNext(intf_iterator)))
{
//create intermediate plugin
result = IOCreatePlugInInterfaceForService(usb_interface,
kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugin,
&score);
//release the usb interface - not needed
result = IOObjectRelease(usb_interface);
//get the general interface interface
hres = (*plugin)->QueryInterface(plugin, CFUUIDGetUUIDBytes(
kIOUSBInterfaceInterfaceID245), (void**)&intf);
//release the plugin interface
IODestroyPlugInInterface(plugin);
//attempt to open the interface
result = (*intf)->USBInterfaceOpen(intf);
//check that the interrupt endpoints are available on this interface
//calling 0xff invalid...
m_input_pipe = 0xff; //UInt8, pipe from device to Mac
m_output_pipe = 0xff; //UInt8, pipe from Mac to device
result = (*intf)->GetNumEndpoints(intf, &interface_endpoint_count);
if (!result)
{
//check endpoints for direction, type, etc.
//note that pipe_ref == 0 is the control endpoint (we don't want it)
for (pipe_ref = 1; pipe_ref <= interface_endpoint_count; pipe_ref++)
{
result = (*intf)->GetPipeProperties(intf, pipe_ref, &direction,
&number, &transfer_type, &max_packet_size, &interval);
if (result)
{
break;
}
if (transfer_type == kUSBInterrupt)
{
if (direction == kUSBIn)
{
m_input_pipe = pipe_ref;
}
else if (direction == kUSBOut)
{
m_output_pipe = pipe_ref;
}
}
}
}
//set up async completion notifications
result = (*m_intf)->CreateInterfaceAsyncEventSource(m_intf,
&compl_event_source);
CFRunLoopAddSource(CFRunLoopGetCurrent(), compl_event_source,
kCFRunLoopDefaultMode);
break;
}
break;
}
}
На данный момент мы должны иметь число конечных точек прерывания и открытый IOUSBInterfaceInterface к устройству.Асинхронная запись данных может быть сделано путем вызова что-то вроде:
result = (intf)->WritePipeAsync(intf, m_output_pipe,
data, OUTPUT_DATA_BUF_SZ, device_write_completion,
NULL);
где данные представляют собой символ буфера данных, чтобы написать, последний параметр является необязательным объект контекста передать в функцию обратного вызова, и device_write_completion является статическим метод со следующей общей форме:
void DemiUSBDevice::device_write_completion(void* context,
IOReturn result, void* arg0)
{
//...
}
чтение из конечной точки прерывания аналогично:
result = (intf)->ReadPipeAsync(intf, m_input_pipe,
data, INPUT_DATA_BUF_SZ, device_read_completion,
NULL);
, где device_read_completi на имеет следующий вид:
void DemiUSBDevice::device_read_completion(void* context,
IOReturn result, void* arg0)
{
//...
}
Обратите внимание, что для получения этих обратных вызовов цикл выполнения должен быть запущен (see this link for more information about the CFRunLoop). Одним из способов достижения этого является вызов CFRunLoopRun()
после вызова методов асинхронного чтения или записи, в которые в данный момент блокируется основной поток во время цикла запуска. После обработки вашего обратного вызова вы можете позвонить CFRunLoopStop(CFRunLoopGetCurrent())
, чтобы остановить цикл выполнения и выполнить ручное выполнение в основной поток.
Другой вариант (который я делаю в своем коде) - передать объект контекста (с именем «запрос» в следующем примере кода) в методы WritePipeAsync/ReadPipeAsync - этот объект содержит флаг логического завершения (с именем «is_done» в этом примере). После вызова метода чтения/записи, вместо вызова CFRunLoopRun()
, что-то вроде следующего может быть выполнено:
while (!(request->is_done))
{
//run for 1/10 second to handle events
Boolean returnAfterSourceHandled = false;
CFTimeInterval seconds = 0.1;
CFStringRef mode = kCFRunLoopDefaultMode;
CFRunLoopRunInMode(mode, seconds, returnAfterSourceHandled);
}
Это имеет то преимущество, что если у вас есть другие темы, которые используют цикл выполнения не будет преждевременно выход из рынка другая нить останавливает цикл цикла ...
Я надеюсь, что это полезно людям. Мне пришлось тянуть из многих неполных источников, чтобы решить эту проблему, и для этого потребовалась значительная работа, чтобы хорошо работать ...
+1. +100 Этот ответ блестящий, и я очень благодарен за вашу тяжелую работу. – TarkaDaal