我正在尝试与相当具体的USB设备进行通信,并开发Windows和Mac代码 .
该设备是带有HID接口(3级)的USB设备,带有两个 endpoints ,一个中断输入和一个中断输出 . 设备的性质使得只有在从主机请求数据时才从输入 endpoints 上的设备发送数据:主机向其发送设备在其输入中断 endpoints 上响应的数据 . 将数据传输到设备(写入)要简单得多......
Windows的代码非常简单:我获得了设备的句柄,然后调用ReadFile或WriteFile . 显然,大部分底层异步行为都被抽象出来了 . 它似乎工作正常 .
然而,在Mac上,它有点粘 . 我已经尝试过很多东西,但都没有完全成功,但这里有两件似乎最有希望的东西......
1.)尝试通过IOUSBInterfaceInterface访问设备(作为USB),遍历 endpoints 以确定输入和输出 endpoints ,并(希望)使用ReadPipe和WritePipe进行通信 . 不幸的是,我无法打开界面,返回值(kIOReturnExclusiveAccess)注意到某些东西已经让设备独占打开 . 我尝试过使用IOUSBinterfaceInterface183,这样我就可以调用USBInterfaceOpenSeize,但是会产生相同的返回错误值 .
---更新7/30/2010 ---
显然,Apple IOUSBHIDDriver会早期与设备匹配,这可能会阻止打开IOUSBInterfaceInterface . 从一些挖掘中可以看出,防止IOUSBHIDDriver匹配的常见方法是编写具有更高探测分数的无代码kext(内核扩展) . 这将提前匹配,防止IOUSBHIDDriver打开设备,理论上应该允许我打开接口并直接写入和读取 endpoints . 这没关系,但我更希望不必在用户机器上安装额外的东西 . 如果有人知道一个可靠的选择,我会感谢这些信息 .
2.)将设备作为IOHIDDeviceInterface122(或更高版本)打开 . 为了读取,我设置了一个异步端口,事件源和回调方法,在数据就绪时调用 - 当数据从输入中断 endpoints 上的设备发送时 . 但是,要写入数据 - 设备需要 - 来初始化响应我找不到办法 . 我很难过 . setReport通常写入控制 endpoints ,而且我需要一个不期望任何直接响应,没有阻塞的写入 .
我在网上看了看并尝试了很多东西,但没有一个能给我带来成功 . 任何建议?我不能使用很多Apple HIDManager代码,因为其中大部分是10.5,我的应用程序也必须在10.4上运行 .
3 回答
我现在有一个工作的Mac驱动程序到USB设备,需要通过中断 endpoints 进行通信 . 我是这样做的:
最终,对我来说效果很好的方法是选项1(如上所述) . 如上所述,我在向设备打开COM样式的IOUSBInterfaceInterface时遇到了问题 . 随着时间的推移,这很明显是由于HIDManager捕获设备 . 一旦被捕获,我就无法从HIDManager中夺取设备的控制权(甚至USBInterfaceOpenSeize调用或USBDeviceOpenSeize调用都不起作用) .
要控制设备,我需要在HIDManager之前抓取它 . 解决方法是编写无代码kext(内核扩展) . kext本质上是一个包含在System / Library / Extensions中的包,它包含(通常)plist(属性列表)和(偶尔)内核级驱动程序以及其他项目 . 在我的情况下,我只想要plist,它会向内核提供与它匹配的设备的指令 . 如果数据提供的探测分数高于HIDManager,那么我基本上可以捕获设备并使用用户空间驱动程序与之通信 .
编写的kext plist,修改了一些项目特定的细节,如下所示:
idVendor和idProduct值赋予kext特异性并充分提高其探测分数 .
为了使用kext,需要完成以下事项(我的安装程序将为客户端执行此操作):
将所有者更改为root:wheel(
sudo chown root:wheel DemiUSBDevice.kext
)将kext复制到Extensions(
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捕获我的设备 . 现在,该怎么办?如何写入和读取?
以下是我的代码的一些简化片段,减去任何错误处理,说明了解决方案 . 在能够对设备执行任何操作之前,应用程序需要知道设备何时连接(和分离) . 请注意,这仅仅是为了说明的目的 - 一些变量是类级别的,一些是全局的,等等 . 以下是设置attach / detach事件的初始化代码:
在此初始化代码中有两种方法用作回调:device_detach_callback和device_attach_callback(两者都在静态方法中声明) . device_detach_callback非常简单:
device_attach_callback是大多数魔法发生的地方 . 在我的代码中,我将其分解为多种方法,但在这里我将它呈现为一个巨大的单片方法......:
此时,我们应该有设备的中断 endpoints 号和一个打开的IOUSBInterfaceInterface . 可以通过调用以下内容来完成异步数据写入:
其中data是要写入的数据的char缓冲区,final参数是传递给回调的可选上下文对象,device_write_completion是一个静态方法,具有以下一般形式:
从中断 endpoints 读取类似:
其中device_read_completion具有以下形式:
请注意,要接收这些回调,必须运行运行循环(see this link for more information about the CFRunLoop) . 实现此目的的一种方法是在调用异步读取或写入方法之后调用
CFRunLoopRun()
,此时主线程在运行循环运行时阻塞 . 处理完回调后,您可以调用CFRunLoopStop(CFRunLoopGetCurrent())
来停止运行循环并将执行交回主线程 .另一个替代方法(我在我的代码中执行)是将上下文对象(在以下代码示例中名为'request')传递到WritePipeAsync / ReadPipeAsync方法 - 此对象包含布尔完成标志(在此示例中名为'is_done') . 在调用read / write方法之后,不是调用
CFRunLoopRun()
,而是执行以下类似的操作:这样做的好处是,如果你有其他线程使用运行循环,如果另一个线程停止运行循环,你将不会过早退出...
我希望这对人们有所帮助 . 我不得不从许多不完整的消息来源解决这个问题,这需要相当多的工作来运行良好...
在阅读了几次这个问题并稍微思考之后,我想到了另一种模拟阻塞读取行为的解决方案,但是使用HID管理器而不是替换它 .
阻塞读取功能可以为设备注册输入回调,在当前运行循环中注册设备,然后通过调用CFRunLoopRun()来阻止 . 然后,输入回调可以将报告复制到共享缓冲区并调用CFRunLoopStop(),这会导致CFRunLoopRun()返回,从而解除对read()的阻塞 . 然后,read()可以将报告返回给调用者 .
我能想到的第一个问题是设备已经在运行循环中安排的情况 . 在读取功能中调度然后取消调度设备可能会产生不利影响 . 但是,如果应用程序试图在同一设备上同时使用同步和异步调用,那只会是一个问题 .
我想到的第二件事是调用代码已经运行了一个运行循环(例如Cocoa和Qt应用程序) . 但是,CFRunLoopStop()的文档似乎表明正确处理了对CFRunLoopRun()的嵌套调用 . 所以,应该没问题 .
这里有一些简化的代码 . 我刚刚在HID Library中实现了类似的东西,它似乎有效,尽管我还没有广泛测试过 .
我遇到了同样的kIOReturnExclusiveAccess . 而不是打架( Build 一个kext等) . 我找到了设备并使用了POSIX api .
设置devPath后,您可以调用open和read / write ..