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