1

I'm currently experimenting with Apple's I/O Kit to develop a kernel module. I'm trying to create a device driver for a USB Wireless Card (TP-Link WN722N-v1) using IOUSBHostDevice. I managed to get the kext loaded and it probes correctly with the card, but I need to send the firmware to the card, which I am trying to do in probe(). I don't have the perfect knowledge of how everything goes, but it seems that I need to send a request to the configuration endpoint 0, but the ways I have tried I get kernel panic, and the other examples I have are using an obsolete DeviceRequest with no exchangeable arguments with the new deviceRequest. I have a reference for writing the firmware from github project "BrcmPatchRAM" but all the forks use old IOUSBInterface instead of IOUSBHostInterface. I have search this site for similar questions but found none helpful in this matter, I also searched Apple developer website documentation and it references also the old ways of DeviceRequest. Which is the right way of re-writing this for example:

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

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.

Here is a section of my messy code:

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

1 Answers1

1

Putting together a few pieces here:

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.

This gives us a pretty big clue that your issue is with the memory buffer. So let's see where the creation and prepare() are happening:

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

So you're creating an input (read) memory descriptor.

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

kIODirectionNone just uses the creation direction, so you're preparing for reading data from the device.

And then, the I/O:

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

This means your bmRequestType is 0x40, and therefore does not have the 0x80 bit set. This means it's host-to-device, i.e. an output/write.

So you've got a direction mismatch: you're preparing some memory for reading from the USB device and then try to use it to write to the device. This won't work.

A few more comments:

  • An OSData isn't really an ideal candidate for I/O buffer allocation. If you want to allocate system memory for I/O, use an IOBufferMemoryDescriptor. If ar9271_fw is a statically allocated array, you can also simply wrap it using IOMemoryDescriptor::withAddress() - this avoids the copy that OSData::withBytes() performs.
  • You're performing I/O while holding the IOLockLock(fCompletionLock); lock. This isn't a great idea even when launching async I/O, but it looks like you're using the blocking version of deviceRequest? That's almost certainly not what you want: the function call can literally take seconds to complete or fail. You should not be holding locks for that long.
pmdj
  • 22,018
  • 3
  • 52
  • 103
  • Thanks for this, I manage to change the outcome, but I did not fix the problem. At least I know what things do. I changed my code from kIODirectionIn to kIODirectionOut and changed the 0x40 to 0x80, this removed the error related to prepared() not being called, although it still doesn't write the firmware. The error is related to pipes. What is this request good for? Should it be for reading or for writing? – Abel Espinosa Jan 29 '20 at 14:53