USBSendSetLineCoding failing in DeviceRequest with error code 0xe0005000

Hi,

This is the code snippet in my driver for an usb uart device. I am trying to call standard cdc-acm command to set the Line Coding in the device, but fails with this error:

"USBSendSetLineCoding - Failed : 0xe0005000, bytes transferred: 0"

I guess the USB device is returning this error due to incorrect buffer or format. There is no proper documentation on how to use IOMemoryDescriptor when the data has to be passed down in a buffer to the usb stack. (IOUSBHostInterface->DeviceRequest())

Can anyone please point out what is wrong with this code and suggest a right method?

void MyDriver::USBSendSetLineCoding(uint32_t BaudRate, uint8_t StopBits, uint8_t TX_Parity, uint8_t CharLength)
{
kern_return_t ret = kIOReturnSuccess;

LineCoding        *lineParms;
uint16_t        lcLen = sizeof(LineCoding)-1;

lineParms = (LineCoding *)IOMalloc(lcLen);
if (!lineParms)
{
    MyDebugLog("USBSendSetLineCoding - allocate lineParms failed");
    return;
}
bzero(lineParms, lcLen);

lineParms->bCharFormat = StopBits - 2;
lineParms->bParityType = TX_Parity - 1;
lineParms->bDataBits = CharLength;

OSSwapBigToHostInt32(BaudRate);
lineParms->dwDTERate = BaudRate;

IOBufferMemoryDescriptor* bufferDescriptor = nullptr;
_controlInterface->CreateIOBuffer(kIOMemoryDirectionOut, lcLen, &bufferDescriptor);

IOMemoryMap *map = nullptr;

bufferDescriptor->CreateMapping(kIOMemoryMapReadOnly, 0, 0, 0, 0, &map);
if(map == nullptr)
{
    MyDebugLog("USBSendSetLineCoding - Failed to map memory in CreateMapping\n");
    IOFree(lineParms, lcLen);
    bufferDescriptor->release();
    return;
}

uint64_t ptr = map->GetAddress();
if(!ptr)
{
    MyDebugLog("USBSendSetLineCoding - Failed to get Memory Address\n");
    IOFree(lineParms, lcLen);
    bufferDescriptor->release();
    map->release();
    return;
}

memcpy(&ptr, lineParms, lcLen);

uint8_t bmRequestType = kIOUSBDeviceRequestDirectionOut | kIOUSBDeviceRequestTypeClass | kIOUSBDeviceRequestRecipientInterface;
uint16_t wValue = 0;
uint16_t wIndex = _bControlInterfaceNumber;
uint16_t bytesTransferred = 0;

ret = _controlInterface->DeviceRequest(bmRequestType, kUSBSET_LINE_CODING, wValue, wIndex, lcLen, bufferDescriptor, &bytesTransferred, 1000);

IOFree(lineParms, lcLen);
map->release();
bufferDescriptor->release();

if (ret != kIOReturnSuccess) {
    MyDebugLog("USBSendSetLineCoding - Failed : 0x%x, bytes transferred: %d\n", ret, bytesTransferred);
    return;
}
return;
}

I am able to call DeviceRequest() successfully on the same interface for any other setting that requires no data buffer, such as,

"ret = _controlInterface->DeviceRequest(bmRequestType, kUSBSEND_BREAK, wValue, wIndex, 0, NULL, &bytesTransferred, 1000);"  

So I think the "bufferDescriptor" is not properly created or the data is not copied correctly in this function for the failure.

"ret = _controlInterface->DeviceRequest(bmRequestType, kUSBSET_LINE_CODING, wValue, wIndex, lcLen, bufferDescriptor, &bytesTransferred, 1000);"

Any help is very much appreciated. Thanks in advance.

Answered by DTS Engineer in 852472022

Can anyone please point out what is wrong with this code and suggest a right method?

I see a few different issues:

(1) You don't need to do any "extra" work to get access to your buffer, as the IOBufferMemoryDescriptor is basically "an IOMemoryDescriptor that makes it easy to read/write the memory". GetAddressRange actually returns a pointer inside your address space.

(2) You'll be cutting this code out, but for future reference, you should have passed "0" instead of "kIOMemoryMapReadOnly" here.

bufferDescriptor->CreateMapping(kIOMemoryMapReadOnly, 0, 0, 0, 0, &map);

The default behavior here is read/write, which kIOMemoryMapReadOnly is then modifying. I'm actually not sure what the result of your call was, as I would have expected a read-only map to mean you'd crash at memcpy. You didn’t, so I'm not sure what the actual result was, but I suspect you sent NULL data out.

(3) I'm not sure what your final goal here is, but you should not be creating and destroying the buffers returned by CreateIOBuffer. These are relatively "heavy" objects that should be reused.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Can anyone please point out what is wrong with this code and suggest a right method?

I see a few different issues:

(1) You don't need to do any "extra" work to get access to your buffer, as the IOBufferMemoryDescriptor is basically "an IOMemoryDescriptor that makes it easy to read/write the memory". GetAddressRange actually returns a pointer inside your address space.

(2) You'll be cutting this code out, but for future reference, you should have passed "0" instead of "kIOMemoryMapReadOnly" here.

bufferDescriptor->CreateMapping(kIOMemoryMapReadOnly, 0, 0, 0, 0, &map);

The default behavior here is read/write, which kIOMemoryMapReadOnly is then modifying. I'm actually not sure what the result of your call was, as I would have expected a read-only map to mean you'd crash at memcpy. You didn’t, so I'm not sure what the actual result was, but I suspect you sent NULL data out.

(3) I'm not sure what your final goal here is, but you should not be creating and destroying the buffers returned by CreateIOBuffer. These are relatively "heavy" objects that should be reused.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

HI Kevin,

My problem is very simple. How do you package arguments when you call DeviceRequest to set the BaudRate and character formats of the UART?

Basically I am looking for the implementation part of IOUserUSBSerial::HwProgramUART() function.

In other words, I am looking for an equivalent code of previous version of AppleUSBCDCACMDriver's USBSendSetLineCoding() function:

lineParms->bCharFormat = StopBits - 2;
lineParms->bParityType = TX_Parity - 1;
lineParms->bDataBits = CharLength;
OSWriteLittleInt32(lineParms, dwDTERateOffset, BaudRate);

MER = (IOUSBDevRequest*)IOMalloc(sizeof(IOUSBDevRequest));
	if (!MER)
	{
		XTRACE(this, 0, 0, "USBSendSetLineCoding - allocate MER failed");
		return;
	}
	bzero(MER, sizeof(IOUSBDevRequest));
	
 // now build the Management Element Request
	
	MER->bmRequestType = USBmakebmRequestType(kUSBOut, kUSBClass, kUSBInterface);
	MER->bRequest = kUSBSET_LINE_CODING;
	MER->wValue = 0;
	MER->wIndex = fCommInterfaceNumber;
	MER->wLength = lcLen;
	MER->pData = lineParms;
	
	fMERCompletionInfo.parameter = MER;
	
	rc = fControlInterface->GetDevice()->DeviceRequest(MER, &fMERCompletionInfo);

This is all I need. How to do this using the latest Driver Kit architecture?

Once again, I am looking for the code snippet of latest USBSerialDriverKit's

 IOUserUSBSerial::HwProgramUART()

Thanks.

My problem is very simple. How do you package arguments when you call DeviceRequest to set the BaudRate and character formats of the UART?

I haven't tested or debugged this, but the code below should basically be the "corrected" version of your original flow.

IOBufferMemoryDescriptor* bufferDescriptor = nullptr;
if(_controlInterface->CreateIOBuffer(kIOMemoryDirectionInOut, lcLen, &bufferDescriptor != kIOReturnSuccess) {
	//Handle unlikely error...
}

IOAddressSegment seg;
if (bufferDescriptor->GetAddressRange(&seg) != kIOReturnSuccess) {
	bufferDescriptor->release();
	//Handle unlikely error...
}

//ptr gives you read/write access to the memory buffer,
uint8_t* ptr = (uint8_t*)seg.address;

//Copy whatever you want into the buffer.
memcpy(&ptr, lineParms, lcLen);

 
uint8_t bmRequestType = kIOUSBDeviceRequestDirectionOut | kIOUSBDeviceRequestTypeClass | kIOUSBDeviceRequestRecipientInterface;
uint16_t wValue = 0;
uint16_t wIndex = _bControlInterfaceNumber;
uint16_t bytesTransferred = 0;
 
ret = _controlInterface->DeviceRequest(bmRequestType, kUSBSET_LINE_CODING, wValue, wIndex, lcLen, bufferDescriptor, &bytesTransferred, 1000);

One note on building "bmRequestType". Our code and documentation call out the USBmakebmRequestType macro, but that macro does not exist. Both code and the header doc are simply wrong (r.64559670). The DriverKit constants were intentionally defined to remove the need for that macro, so you can just OR them together as you're doing above.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

USBSendSetLineCoding failing in DeviceRequest with error code 0xe0005000
 
 
Q