如何使用 IOUSBHostDevice / IOUSBHostInterface 的 deviceRequest?

How to use deviceRequest of IOUSBHostDevice / IOUSBHostInterface?

我目前正在试验使用 Apple 的 I/O 工具包来开发内核模块。我正在尝试使用 IOUSBHostDevice 为 USB 无线网卡 (TP-Link WN722N-v1) 创建设备驱动程序。 我设法加载了 kext,它用卡正确探测,但我需要将固件发送到卡,这是我试图在 probe() 中做的。 我不完全了解一切如何进行,但似乎我需要向配置端点 0 发送请求,但我尝试过的方法导致内核崩溃,而我的其他示例使用的是过时的DeviceRequest 与新的 deviceRequest 没有可交换的参数。 我有一个从 github 项目 "BrcmPatchRAM" 编写固件的参考,但所有分支都使用旧的 IOUSBInterface 而不是 IOUSBHostInterface。 我在这个网站上搜索过类似的问题,但发现 none 在这件事上很有帮助,我还搜索了 Apple 开发者网站文档,它也引用了 DeviceRequest 的旧方法。 这是重写这个的正确方法,例如:

IOReturn USBInterfaceShim::hciCommand(void* command, UInt16 length)
{
    IOUSBDevRequest request =
    {
        .bmRequestType = USBmakebmRequestType(kUSBOut, kUSBClass, kUSBDevice),
        .bRequest = 0,
        .wValue = 0,
        .wIndex = 0,
        .wLength = length,
        .pData = command
    };
    return m_pInterface->DeviceRequest(&request);
}

使用此函数的一些其他实现在加载驱动程序时给我一个错误,并告诉我我在调用 prepare 之前发送请求,但我确实在发送请求之前调用了 prepare。

下面是我的一段乱码:

<pre>IOReturn AirPort_Atheros9271::pipeCommand(UInt8 requestType, UInt8 command, UInt16 address, IOMemoryDescriptor *buffer)
{
    DeviceRequest request;
    request.bmRequestType = requestType;
    request.bRequest = command;
    request.wValue = address;
    request.wIndex = 0;
    request.wLength = buffer->getLength();

    uint32_t bytesTransferred;
    return fInterface->deviceRequest(request, buffer, bytesTransferred, kUSBHostStandardRequestCompletionTimeout);
}

IOReturn AirPort_Atheros9271::pipeCommand(UInt8 requestType, UInt8 command, UInt16 address, void *buffer, UInt16 length)
{
    DeviceRequest request;
    request.bmRequestType = requestType;
    request.bRequest = command;
    request.wValue = address;
    request.wIndex = 0;
    request.wLength = length;

    uint32_t bytesTransferred;
    return fInterface->deviceRequest(request, buffer, bytesTransferred, kUSBHostStandardRequestCompletionTimeout);
}

bool TL_WN722N::performUpgrade()
{
    IOLog("TL_WN722N::[%04x:%04x]: Performing firmware upgrade.\n", fVendorId, fProductId);

    OSData *firmwareData = OSData::withBytes(ar9271_fw, ar9271_fw_len);
    IOMemoryDescriptor *buffer = IOMemoryDescriptor::withAddress((void*)firmwareData->getBytesNoCopy(), firmwareData->getLength(), kIODirectionIn);
    bool success = true;
    IOLog("TL_WN722N::[%04x:%04x]: I have firmwareData and created the buffer\n", fVendorId, fProductId);
    IOLockLock(fCompletionLock);

    IOReturn result;
    if (buffer!=NULL)
    {
        IOLog("TL_WN722N::[%04x:%04x]: Buffer is not null, now calling prepare()\n", fVendorId, fProductId);
        if ((result = buffer->prepare(kIODirectionNone)) == kIOReturnSuccess)
        {
            IOLog("TL_WN722N::[%04x:%04x]: prepare() called and pass! Piping I - writing firmware\n", fVendorId, fProductId);
            IOLog("TL_WN722N::[%04x:%04x]: Resultado de prepare: Result: %d\n", fVendorId, fProductId,result);

            IOLog("TL_WN722N::[%04x:%04x]: About to excecute pipeCommand\n", fVendorId, fProductId);
            result = pipeCommand(0x40, FIRMWARE_DOWNLOAD, AR9271_FIRMWARE >> 8, buffer); //TODO: EXPLOTION LINE
            if (result>0) {
                IOLog("TL_WN722N::[%04x:%04x]: Resultado de pipeCommand: %d\n", fVendorId, fProductId,result);
            }

            if (result != kIOReturnSuccess)
                IOLog("TL_WN722N::[%04x:%04x]: Unable to write the firmware (0x%08x).\n", fVendorId, fProductId, result);
            else
            {
                if ((result = pipeCommand(0x40, FIRMWARE_DOWNLOAD_COMP, AR9271_FIRMWARE_TEXT >> 8, NULL, 0)) != kIOReturnSuccess)
                    IOLog("TL_WN722N::[%04x:%04x]: Unable to write the firmware complete sequence (0x%08x).\n", fVendorId, fProductId, result);
                else
                {
                    IOLog("TL_WN722N::[%04x:%04x]: Success in writing the firmware sequence.\n", fVendorId, fProductId);
                    success = true;
                }
            }
        }
        else
            IOLog("TL_WN722N::[%04x:%04x]: Failed to prepare write memory buffer (0x%08x).\n", fVendorId, fProductId, result);

        if ((result = buffer->complete()) != kIOReturnSuccess)
            IOLog("TL_WN722N::[%04x:%04x]: Failed to complete write memory buffer (0x%08x).\n", fVendorId, fProductId, result);


    }
    else
        IOLog("TL_WN722N::[%04x:%04x]: Unable to allocate write memory buffer.\n", fVendorId, fProductId);

    IOLockUnlock(fCompletionLock);
    OSSafeReleaseNULL(buffer);
    OSSafeReleaseNULL(firmwareData);
    return success;
}</pre>

这里整理几篇:

Using some other implementation of this function gives me an error when loading the driver and tells me that I am sending the request before calling prepare, but I did called prepare before sending the request.

这给了我们一个很大的线索,表明您的问题出在内存缓冲区上。那么让我们看看创建和 prepare() 发生在哪里:

    IOMemoryDescriptor *buffer = IOMemoryDescriptor::withAddress(
        (void*)firmwareData->getBytesNoCopy(), firmwareData->getLength(),
        kIODirectionIn);
    //  ^^^^^^^^^^^^^^^^

因此您正在创建输入(读取)内存描述符。

        if ((result = buffer->prepare(kIODirectionNone)) == kIOReturnSuccess)

kIODirectionNone只是使用创建方向,所以你准备从设备读取数据。

然后,I/O:

            result = pipeCommand(0x40, FIRMWARE_DOWNLOAD, AR9271_FIRMWARE >> 8, buffer); //TODO: EXPLOTION LINE
    // --------------------------^^^^

这意味着您的 bmRequestType0x40,因此没有设置 0x80 位。这意味着它是主机到设备,即 output/write.

所以你的方向不匹配:你正在准备一些内存用于从 USB 设备读取,然后尝试使用它写入设备。这行不通。

补充几点:

  • OSData 并不是 I/O 缓冲区分配的理想候选者。如果要为 I/O 分配系统内存,请使用 IOBufferMemoryDescriptor。如果 ar9271_fw 是静态分配的数组,您也可以使用 IOMemoryDescriptor::withAddress() 简单地包装它 - 这避免了 OSData::withBytes() 执行的复制。
  • 您正在执行 I/O,同时持有 IOLockLock(fCompletionLock); 锁。即使在启动异步 I/O 时,这也不是一个好主意,但看起来您正在使用 deviceRequest 的阻塞版本?这几乎肯定不是您想要的:函数调用实际上可能需要 才能完成或失败。你不应该持有锁那么久。