usar una puedo pudo mac leer insertado formatear externo duro disco crear copiar computadora como carpeta borrar archivos c++ macos usb hid kernel-extension

c++ - una - no puedo copiar archivos a mi disco duro externo mac



Lectura y escritura en puntos finales de interrupciĆ³n USB(HID) en Mac (3)

Ahora tengo un controlador de Mac en funcionamiento para un dispositivo USB que requiere comunicación a través de puntos finales de interrupción. Así es como lo hice:

En última instancia, el método que funcionó bien para mí fue la opción 1 (señalada anteriormente). Como se mencionó, tuve problemas al abrir el Interfaz de interfaz IOUSB estilo COM en el dispositivo. Con el tiempo, quedó claro que esto se debía a que HIDManager capturaba el dispositivo. No pude arrebatar el control del dispositivo desde HIDManager una vez que se capturó (ni siquiera la llamada USBInterfaceOpenSeize o las llamadas USBDeviceOpenSeize funcionarían).

Para tomar el control del dispositivo necesitaba agarrarlo antes de HIDManager. La solución a esto fue escribir un kext sin código (extensión del kernel). Un kext es esencialmente un paquete que se encuentra en Sistema / Biblioteca / Extensiones que contiene (generalmente) un plist (lista de propiedades) y (ocasionalmente) un controlador de nivel de kernel, entre otros elementos. En mi caso, solo quería el plist, que daría las instrucciones al kernel sobre qué dispositivos coincide. Si los datos dan un puntaje de prueba más alto que el HIDManager, entonces esencialmente podría capturar el dispositivo y usar un controlador de espacio de usuario para comunicarme con él.

El kext plist escrito, con algunos detalles específicos del proyecto modificados, es el siguiente:

<?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>

Los valores idVendor e idProduct dan la especificidad de kext y aumentan suficientemente su puntaje de la sonda.

Para usar el kext, se deben hacer las siguientes cosas (lo que mi instalador hará para los clientes):

  1. Cambie el propietario a root: wheel ( sudo chown root:wheel DemiUSBDevice.kext )
  2. Copie el kext a las extensiones ( sudo cp DemiUSBDevice.kext /System/Library/Extensions )
  3. Llame a la utilidad kextload para cargar el kext para uso inmediato sin reinicio ( sudo kextload -vt /System/Library/Extensions/DemiUSBDevice.kext )
  4. Toque la carpeta Extensiones para que el próximo reinicio forzará una reconstrucción de caché ( sudo touch /System/Library/Extensions )

En este punto, el sistema debe usar el kext para evitar que el HIDManager capture mi dispositivo. Ahora, ¿qué hacer con él? ¿Cómo escribir y leer desde él?

Los siguientes son algunos fragmentos simplificados de mi código, menos cualquier manejo de errores, que ilustran la solución. Antes de poder hacer algo con el dispositivo, la aplicación debe saber cuándo se conecta (y se separa) el dispositivo. Tenga en cuenta que esto es solo para fines de ilustración: algunas de las variables son de nivel de clase, otras son globales, etc. Aquí está el código de inicialización que configura los eventos de adjuntar / separar:

#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(); }

Hay dos métodos que se utilizan como devoluciones de llamada en este código de inicialización: device_detach_callback y device_attach_callback (ambos declarados en métodos estáticos). device_detach_callback es sencillo:

//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 es donde ocurre la mayor parte de la magia. En mi código, lo he dividido en varios métodos, pero aquí lo presentaré como un gran método monolítico ...

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; } }

En este punto, deberíamos tener los números de los puntos finales de interrupción y una interfaz de interfaz de IOUSB abierta al dispositivo. Una escritura asincrónica de datos se puede hacer llamando a algo como:

result = (intf)->WritePipeAsync(intf, m_output_pipe, data, OUTPUT_DATA_BUF_SZ, device_write_completion, NULL);

donde los datos son un búfer de caracteres para escribir, el parámetro final es un objeto de contexto opcional para pasar a la devolución de llamada, y device_write_completion es un método estático con la siguiente forma general:

void DemiUSBDevice::device_write_completion(void* context, IOReturn result, void* arg0) { //... }

La lectura desde el punto final de la interrupción es similar:

result = (intf)->ReadPipeAsync(intf, m_input_pipe, data, INPUT_DATA_BUF_SZ, device_read_completion, NULL);

donde device_read_completion es de la siguiente forma:

void DemiUSBDevice::device_read_completion(void* context, IOReturn result, void* arg0) { //... }

Tenga en cuenta que para recibir estas devoluciones de llamada, el bucle de ejecución debe estar en ejecución ( consulte este enlace para obtener más información sobre el CFRunLoop ). Una forma de lograr esto es llamar a CFRunLoopRun() después de llamar a los métodos de lectura o escritura asíncronos en los que los bloques de subproceso principales se ejecutan mientras se ejecuta el bucle de ejecución. Después de manejar su devolución de llamada, puede llamar a CFRunLoopStop(CFRunLoopGetCurrent()) para detener el ciclo de ejecución y la ejecución de la mano al hilo principal.

Otra alternativa (lo que hago en mi código) es pasar un objeto de contexto (llamado ''solicitud'' en el siguiente ejemplo de código) a los métodos WritePipeAsync / ReadPipeAsync: este objeto contiene un indicador de finalización booleana (llamado ''is_done'' en este ejemplo) . Después de llamar al método de lectura / escritura, en lugar de llamar a CFRunLoopRun() , se puede ejecutar algo como lo siguiente:

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); }

Esto tiene la ventaja de que si tiene otros subprocesos que usan el bucle de ejecución, no saldrá prematuramente si otro subproceso detiene el bucle de ejecución ...

Espero que esto sea de ayuda para las personas. Tuve que extraer muchas fuentes incompletas para resolver este problema y esto requirió un trabajo considerable para funcionar bien ...

Estoy intentando comunicarme con un dispositivo USB bastante específico y estoy desarrollando códigos de Windows y Mac para hacerlo.

El dispositivo es un dispositivo USB con una interfaz HID (clase 3) con dos puntos finales, una entrada de interrupción y una salida de interrupción. La naturaleza del dispositivo es tal que los datos se envían desde el dispositivo en el punto final de entrada solo cuando se solicitan datos desde el host: el host le envía los datos a los que responde el dispositivo en su punto final de interrupción de entrada. Obtener datos en el dispositivo (una escritura) es mucho más simple ...

El código para Windows es bastante sencillo: recibo un identificador del dispositivo y luego llamo a ReadFile o WriteFile. Al parecer, gran parte del comportamiento asíncrono subyacente se abstrae. Parece que funciona bien.

En Mac, sin embargo, es un poco más pegajoso. He intentado varias cosas, ninguna de las cuales ha sido completamente exitosa, pero aquí están las dos cosas que parecían más prometedoras ...

1.) Intente obtener acceso al dispositivo (como USB) a través de IOUSBInterfaceInterface, itere a través de los puntos finales para determinar los puntos finales de entrada y salida y (con suerte) use ReadPipe y WritePipe para comunicarse. Desafortunadamente, no puedo abrir la interfaz una vez que la tengo, y el valor de retorno (kIOReturnExclusiveAccess) indica que algo ya tiene el dispositivo abierto exclusivamente. He intentado usar IOUSBinterfaceInterface183, para poder llamar a USBInterfaceOpenSeize, pero eso da como resultado el mismo valor de error de retorno.

--- actualización 30/7/2010 ---
Aparentemente, el IOUSBHIDDriver de Apple coincide temprano con el dispositivo y esto es lo que probablemente está impidiendo abrir la interfaz de interfaz de usuario IOUSB. A partir de algunas investigaciones, parece que la forma más común de evitar que el IOUSBHIDDriver coincida es escribir un kext sin código (extensión del kernel) con una puntuación de sondeo más alta. Esto coincidiría pronto, evitando que el IOUSBHIDDriver abra el dispositivo y, en teoría, debería permitirme abrir la interfaz y escribir y leer directamente a los puntos finales. Esto está bien, pero preferiría no tener que instalar algo adicional en la máquina del usuario. Si alguien sabe de una alternativa sólida, estaría agradecido por la información.

2.) Abra el dispositivo como un IOHIDDeviceInterface122 (o posterior). Para leer, configuro un puerto asíncrono, una fuente de eventos y un método de devolución de llamada para que se llame cuando los datos estén listos, cuando los datos se envíen desde el dispositivo en el punto final de la interrupción de entrada. Sin embargo, para escribir los datos que necesita el dispositivo, para inicializar una respuesta no puedo encontrar una manera. Estoy perplejo. Por lo general, setReport escribe en el punto final de control, además necesito una escritura que no espere ninguna respuesta directa, ningún bloqueo.

He mirado alrededor en línea y he probado muchas cosas, pero ninguna de ellas me está dando éxito. ¿Algún consejo? No puedo usar gran parte del código de HIDManager de Apple ya que gran parte de eso es 10.5+ y mi aplicación también debe funcionar en 10.4.


Después de leer esta pregunta unas cuantas veces y pensarlo un poco, pensé en otra solución para emular el comportamiento de lectura de bloqueo, pero usando el administrador HID en lugar de reemplazarlo.

Una función de lectura de bloqueo puede registrar una devolución de llamada de entrada para el dispositivo, registrar el dispositivo en el bucle de ejecución actual y luego bloquearlo llamando a CFRunLoopRun (). La devolución de llamada de entrada puede luego copiar el informe en un búfer compartido y llamar a CFRunLoopStop (), lo que hace que CFRunLoopRun () regrese, por lo tanto, desbloqueando read (). Luego, lea () puede devolver el informe a la persona que llama.

El primer problema que se me ocurre es el caso en el que el dispositivo ya está programado en un ciclo de ejecución. Programar y luego cancelar la programación del dispositivo en la función de lectura puede tener efectos adversos. Pero eso solo sería un problema si la aplicación intenta utilizar llamadas síncronas y asíncronas en el mismo dispositivo.

Lo segundo que viene a la mente es el caso en el que el código de llamada ya tiene un ciclo de ejecución en ejecución (por ejemplo, las aplicaciones Cocoa y Qt). Pero, la documentación para CFRunLoopStop () parece indicar que las llamadas anidadas a CFRunLoopRun () se manejan correctamente. Por lo tanto, debería estar bien.

Aquí hay un poco de código simplificado para ir con eso. Acabo de implementar algo similar en mi biblioteca HID y parece funcionar, aunque no lo he probado exhaustivamente.

/* An IN report callback that stops its run loop when called. This is purely for emulating blocking behavior in the read() method */ static void input_oneshot(void* context, IOReturn result, void* deviceRef, IOHIDReportType type, uint32_t reportID, uint8_t* report, CFIndex length) { buffer_type *const buffer = static_cast<HID::buffer_type*>(context); /* If the report is valid, copy it into the caller''s buffer The Report ID is prepended to the buffer so the caller can identify the report */ if( buffer ) { buffer->clear(); // Return an empty buffer on error if( !result && report && deviceRef ) { buffer->reserve(length+1); buffer->push_back(reportID); buffer->insert(buffer->end(), report, report+length); } } CFRunLoopStop(CFRunLoopGetCurrent()); } // Block while waiting for an IN interrupt report bool read(buffer_type& buffer) { uint8_t _bufferInput[_lengthInputBuffer]; // Register a callback IOHIDDeviceRegisterInputReportCallback(deviceRef, _bufferInput, _lengthInputBuffer, input_oneshot, &buffer); // Schedule the device on the current run loop IOHIDDeviceScheduleWithRunLoop(deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); // Trap in the run loop until a report is received CFRunLoopRun(); // The run loop has returned, so unschedule the device IOHIDDeviceUnscheduleFromRunLoop(deviceRef, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode); if( buffer.size() ) return true; return false; }


Me encontré con este mismo acceso exclusivo kIOReturn. En lugar de combatirlo (construyendo un kext, etc). Encontré el dispositivo y utilicé los api de POSIX.

//My funcation was named differently, but I''m using this for continuity.. void DemiUSBDevice::device_attach_callback(void * context, io_iterator_t iterator) { DeviceManager *deviceManager = (__bridge DADeviceManager *)context; io_registry_entry_t device; while ((device = IOIteratorNext(iterator))) { CFTypeRef prop; prop = IORegistryEntrySearchCFProperty(device, kIOServicePlane, CFSTR(kIODialinDeviceKey), kCFAllocatorDefault, kIORegistryIterateRecursively); if(prop){ deviceManager->devPath = (__bridge NSString *)prop; [deviceManager performSelector:@selector(openDevice)]; } } }

una vez que se establece devPath, puede abrir y leer / escribir.

int dfd; dfd = open([devPath UTF8String], O_RDWR | O_NOCTTY | O_NDELAY); if (dfd == -1) { //Could not open the port. NSLog(@"open_port: Unable to open %@", devPath); return; } else { fcntl(fd, F_SETFL, 0); }