ReadEp does not work on custom USB class

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

ReadEp does not work on custom USB class

1,334 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by giedrius on Fri Jul 19 05:22:19 MST 2013
Hello,
Hopefully I'm posting in the right section.

I am trying to do double CDC support for the LPC11U37 device. To my understanding the device can support only one CDC class by itself, so I have to build a custom CDC class handler. The first CDC instance is configured to be handled by the hardware, while the second one I am trying to implement as a custom class myself.

Up to this point everything is working (I see two virtual COM ports on PC, I can send and receive data), but the problem comes when I try to read data in control messages for the custom class. Calling ReadEp function in USB_EVT_OUT setup always return 0, although USB analyzer clearly shows that the data have been sent and acknowledged (ACK).

I have attached EP0 handler using
pUsbApi->core->RegisterClassHandler(hUsb, EP0_hdlr, NULL);


The code for EP0_hdlr is this:
ErrorCode_t EP0_hdlr(USBD_HANDLE_T hUsb, void* data, uint32_t event) {
evnts[numEvt++] = event;

if (event == USB_EVT_SETUP) {
USB_SETUP_PACKET packet;
uint32_t len = pUsbApi->hw->ReadSetupPkt(hUsb, USB_ENDPOINT_OUT(0), (uint32_t*)&packet);

if ((packet.bmRequestType.B & 0x7F) == 0x21) { // Type=Class, Recipient=Interface
if (packet.wIndex.W == 2) { // OSC CDC CIF interface (2)
switch (packet.bRequest) {
case 0x20: { // SET_LINE_CODING
if (packet.wLength != 7)
return ERR_USBD_INVALID_REQ;

return LPC_OK;
}
case 0x21: { // GET_LINE_CODING
if (packet.wLength != 7)
return ERR_USBD_INVALID_REQ;

uint8_t lcs[] = { 0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };
pUsbApi->hw->WriteEP(pUsbHandle, USB_ENDPOINT_IN(0), lcs, 7);
return LPC_OK;
}
case 0x22: { // SET_CONTROL_LINE_STATE
if (packet.wLength != 0)
return ERR_USBD_INVALID_REQ;

pUsbApi->hw->WriteEP(pUsbHandle, USB_ENDPOINT_IN(0), NULL, 0);
return LPC_OK;
}
}
}
}
} else if (event == USB_EVT_OUT) {
numEvtOut++;
lenEvtOut += pUsbApi->hw->ReadEP(hUsb, USB_ENDPOINT_OUT(0), (uint8_t*)tmpStpBuf);
} else if (event == USB_EVT_IN) {

}

return ERR_USBD_UNHANDLED;
}


As you can see I have few debug variables: evnts - which logs the incoming events, numEvnt - event count, numEvntOut - number of OUT events and lenEvtOut - ammount of data received in OUT events.

For the evnts I get array [1, 3, 1, 3, 1, 3, 1, 2, 1, 3, 1, 3, 1, 3, 1, 2, 1, 3] which matches the USB analyzer log: 4*GET_LINE_CODING(Setup received(1) + IN sent(3)) 1*SET_LINE_CODING(Setup received + OUT received) 3*GET_LINE_CODING 1*SET_LINE_CODING 1*GET_LINE_CODING.
For the numEvntOut I get, as expected, 2. However lenEvtOut is 0, although it should be 14 (2*7) as two SET_LINE_CODING messages arrived, which had 7 bytes of data each.

Any help is appreciated. Thanks!
0 Kudos
Reply
7 Replies

1,040 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by giedrius on Tue Jul 23 01:58:16 MST 2013

Quote: Tsuneo
You are missing the fact that DATA stage may span over multiple transactions.



Yes, sorry I kind of forgot that when I wrote the post.



Quote: Tsuneo

d) The stack calls both of your callback and CDC API.
Try without CDC API.

Tsuneo



1) The CDC API was used only for interface 0, while interface 2 CDC was implemented by core functions.
2) Same thing if I remove any CDC support, that is I implemented interfaces 0 and 2 with core functions: in EVT_SETUP (SetLineCoding) if I call DataOutStage I get stalls in USBLyzer, however if I call StatusInStage - everything works fine, but that means that I am forcing StatusInStage right after the SETUP stage AS IF to skip the DATA OUT stage.

P.S. Can I use EP0Buf when receiving data (pCtrl->EP0Data.pData = pCtrl->EP0Buf) or is it used by the core for some other reasons and this might cause collisions?
P.P.S. Is there any comprehensive LPC USB documetion, because nor LPC11Uxx User manual, nor online USBD ROM stack documentation explain (almost) nothing what these functions really do. I understand what they sort of *should* do, but it seems that there's more behind the scenes.
0 Kudos
Reply

1,040 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Tsuneo on Mon Jul 22 09:38:42 MST 2013

Quote:
a) I am missing something about the USB communication
b) LPC has buggy functions, that do randomish stuff
c) Magic



d) The stack calls both of your callback and CDC API.
Try without CDC API.

Tsuneo
0 Kudos
Reply

1,040 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Tsuneo on Mon Jul 22 09:32:17 MST 2013

Quote:
2) It looks like the data (from SetLineCoding packet) is correctly written to my buffer
3) However, the data is written to the buffer BEFORE the EVT_OUT is called, which means that

pUsbApi->core->DataOutStage( hUsb ); // receive data

in the EVT_OUT section is never called.


You are missing the fact that DATA stage may span over multiple transactions.

At the DATA stage of Control Write Transfer, the stack calls the callback with EVT_OUT after every reception of single OUT transaction. If the data is greater than bMaxPacketSize0 of EP0, we'll see twice or more EVT_OUT callbacks.

At the entry of the callback(EVT_OUT),
- received data sits on the buffer, specified at SETUP stage ( pCtrl->EP0Data.pData).
- pCtrl->EP0Data.Count holds the remaining bytes to be received at the next OUT transactions.

Therefore, pUsbApi->core->DataOutStage() is called by our code to keep DATA stage, just when there will be following OUT transactions, ie. pCtrl->EP0Data.Count != 0

When pCtrl->EP0Data.Count == 0, the DATA stage completes.
Now that our callback processes the data on the buffer.
And then, move to STATUS stage, by calling pUsbApi->core->StatusInStage()

Tsuneo
0 Kudos
Reply

1,040 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by giedrius on Mon Jul 22 06:55:46 MST 2013
It looks like I've managed to get the ACK for SetLineCoding and read the data, however the way that was required to accomplish this confuses me a lot. Below is my code:

ErrorCode_t EP0_hdlr(USBD_HANDLE_T hUsb, void* data, uint32_t event) {
if (numEvt < 100)
evnts[numEvt] = event;

numEvt++;

USB_CORE_CTRL_T* pCtrl = (USB_CORE_CTRL_T*)hUsb;
static USB_SETUP_PACKET setup;
pUsbApi->hw->ReadSetupPkt( hUsb, USB_ENDPOINT_OUT(0), (uint32_t *)&setup );

switch (event) {
case USB_EVT_SETUP:

pCtrl->EP0Data.Count = setup.wLength;   // Number of bytes to transfer

if ( (setup.bmRequestType.B == REQ_TYPE(REQUEST_HOST_TO_DEVICE,REQUEST_CLASS,REQUEST_TO_INTERFACE) )
  && (setup.bRequest        == 0x20 ) // SetLineCoding
  && (setup.wValue.W        == (0<<8) )    // descriptor type | index
  && (setup.wIndex.W        == 2)
) {
numEvtSetLineCoding++;
tmpStpBuf[0] = 0xFE;

pCtrl->EP0Data.pData = (uint8_t*)tmpStpBuf;
pCtrl->EP0Data.Count = 7;
//pUsbApi->core->DataOutStage( hUsb );
pUsbApi->core->StatusInStage(hUsb);
return LPC_OK;
}

if ( (setup.bmRequestType.B == REQ_TYPE(REQUEST_DEVICE_TO_HOST,REQUEST_CLASS,REQUEST_TO_INTERFACE) )
  && (setup.bRequest        == 0x21 ) // GetLineCoding
  && (setup.wValue.W        == (0<<8) )  // Report type | Report ID
  && (setup.wIndex.W        == 2 )
) {
uint8_t lcs[] = { 0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };

pCtrl->EP0Data.Count = 7;
pCtrl->EP0Data.pData = (uint8_t*)&lcs;
pUsbApi->core->DataInStage(hUsb);

return LPC_OK;
}

if ( (setup.bmRequestType.B == REQ_TYPE(REQUEST_HOST_TO_DEVICE,REQUEST_CLASS,REQUEST_TO_INTERFACE) )
  && (setup.bRequest        == 0x22 ) // SetControlLineState
  && (setup.wIndex.W        == 2)
) {
numEvtSetControlLineState++;

pUsbApi->core->StatusInStage(hUsb);
return LPC_OK;
}

break;
case USB_EVT_OUT:
numEvtOut++;

if (pCtrl->EP0Data.Count > 0) {
pUsbApi->core->DataOutStage(hUsb);
return LPC_OK;
} else {
pUsbApi->core->StatusInStage(hUsb);
return LPC_OK;
}
break;

case USB_EVT_IN:
if (pCtrl->EP0Data.Count > 0) {
pUsbApi->core->DataInStage( hUsb );
return LPC_OK;
} else {
pUsbApi->core->StatusOutStage( hUsb );
return LPC_OK;
}

break;

default:
break;
}

return ERR_USBD_UNHANDLED;
}


The key thing, that allowed to get the ACK response is call to pUsbApi->core->StatusInStage(hUsb); in SetLineCoding part. In theory after you receive the SetLineCoding SETUP packet, device should enter DATA OUT stage and finally after it received all data it should enter STATUS IN stage and send ZLP packet. However in this case after receiving SetLineCoding SETUP packet I configure EP0Data AS IF to prepare it for DATA OUT stage, but after that I set the device to STATUS IN stage AS IF to skip the DATA OUT stage. But it's the only way (that I have found) that works! And it contradicts itself! This really bothers me and I can't seem to understand how does the core work at all. I can only think of a few explanations:
a) I am missing something about the USB communication
b) LPC has buggy functions, that do randomish stuff
c) Magic
0 Kudos
Reply

1,040 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by giedrius on Mon Jul 22 01:35:46 MST 2013
Thank you for the response. I have tried your code and this is what I noticed:
1) I get STALL on every SetLineCode request (trying to force ZLP during SETUP or OUT stage doesn't help either)
2) It looks like the data (from SetLineCoding packet) is correctly written to my buffer
3) However, the data is written to the buffer BEFORE the EVT_OUT is called, which means that

pUsbApi->core->DataOutStage( hUsb );  // receive data


in the EVT_OUT section is never called.
3.1) If I debug at EVT_OUT event, what I get is

pCtrl->EP0Data.Count


is always zero (although it was set to 7 in the EVT_SETUP just a moment ago) and

pCtrl->EP0Data.pData


is &tmpStpBuf + 7, which basically confirms the fact that

pUsbApi->core->DataOutStage( hUsb );


is never manually called and the core itself decides to go into OUT stage, read the data, and call EVT_OUT only when everything is over.

Here is my code:
ErrorCode_t EP0_hdlr(USBD_HANDLE_T hUsb, void* data, uint32_t event) {
evnts[numEvt++] = event;

USB_CORE_CTRL_T* pCtrl = (USB_CORE_CTRL_T*)hUsb;
    static USB_SETUP_PACKET setup;

    switch( event )
    {
        case USB_EVT_SETUP:
                                                    // Get SETUP packet
            pUsbApi->hw->ReadSetupPkt( hUsb, USB_ENDPOINT_OUT(0), (uint32_t *)&setup );
                                                    // pick up Control Read transfer: Get_Descriptor(HID_REPORT)

            pCtrl->EP0Data.Count = setup.wLength;   // Number of bytes to transfer

            if ( (setup.bmRequestType.B == REQ_TYPE(REQUEST_HOST_TO_DEVICE,REQUEST_CLASS,REQUEST_TO_INTERFACE) )
              && (setup.bRequest        == 0x20 ) // SetLineCoding
              && (setup.wValue.W        == (0<<8) )    // descriptor type | index
              && (setup.wIndex.W        == 2)
            ) {
            numEvtSetLineCoding++;
            tmpStpBuf[0] = 0xFE;

            pCtrl->EP0Data.pData = (uint8_t*)tmpStpBuf;
            req_num = REQ_CTRL_WRITE;
            //pUsbApi->core->DataOutStage( hUsb );
            return LPC_OK;
            }

            if ( (setup.bmRequestType.B == REQ_TYPE(REQUEST_DEVICE_TO_HOST,REQUEST_CLASS,REQUEST_TO_INTERFACE) )
              && (setup.bRequest        == 0x21 ) // GetLineCoding
              && (setup.wValue.W        == (0<<8) )  // Report type | Report ID
              && (setup.wIndex.W        == 2 )
            ) {
            uint8_t lcs[] = { 0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08 };

                Control_Read( hUsb, (uint8_t*)&lcs, 7 );
                return LPC_OK;
            }

            break;
        case USB_EVT_OUT:
        numEvtOut++;
        //lenEvtOut += pUsbApi->hw->ReadEP(hUsb, USB_ENDPOINT_OUT(0), (uint8_t*)tmpStpBuf);
            if ( req_num == REQ_CTRL_WRITE ) {
                if (pCtrl->EP0Data.Count) {             // still data to receive ?
                  pUsbApi->core->DataOutStage( hUsb );  // receive data
                }
                if (pCtrl->EP0Data.Count == 0) {        // data complete ?
                  //
                  // process data on the buffer, here
                  //
                  pUsbApi->core->StatusInStage( hUsb );
                //pUsbApi->hw->WriteEP(pUsbHandle, USB_ENDPOINT_IN(0), NULL, 0);
                  req_num = REQ_NO_REQ;
                }
                return LPC_OK;
            }
            break;

        case USB_EVT_IN:
            if ( req_num == REQ_CTRL_READ ) {
                if ( pCtrl->EP0Data.Count ) {
                    pUsbApi->core->DataInStage( hUsb );
                } else {
                    pUsbApi->core->StatusOutStage( hUsb );
                    req_num = REQ_NO_REQ;
                }
                return LPC_OK;
            }
            break;

        default:
            break;
    }

    req_num = REQ_NO_REQ;
    return ERR_USBD_UNHANDLED;
}
0 Kudos
Reply

1,040 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Tsuneo on Sat Jul 20 20:58:51 MST 2013
My previous comment to your post is not seen yet, trapped by forum censorship.
It'll appear after a while, salvaged by the webmaster.


Quote:
To my understanding the device can support only one CDC class by itself


You may have two or more CDC interface pairs using the USB on-chip ROM driver of LPC11Uxx.
In this thread, we discussed a composite device of HID + HID on LPC1347 USB on-chip ROM driver, which has the same interface with LPC11Uxx's.
You may apply this discussion to CDC API, too.
http://knowledgebase.nxp.com/showpost.php?p=19025&postcount=8
http://www.lpcware.com/content/forum/usb-composite-device-on-lpc1347#comment-1119025

Tsuneo
0 Kudos
Reply

1,040 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Tsuneo on Sat Jul 20 20:26:30 MST 2013
To this post, I attached an example of custom Control transfer handler, which is written just with HW and CORE ROM stack, for HID implementation.
http://knowledgebase.nxp.com/showpost.php?p=27859&postcount=9
http://www.lpcware.com/content/forum/lpc11u24-control-endpoint-communication-with-linux-host-machine...

Here is a custom control transfer handler using the ROM stack.
/* local data */

typedef enum {
    REQ_NO_REQ,
    REQ_CTRL_READ,
    REQ_CTRL_WRITE,
    REQ_NO_DATA_CTRL,
} REQ_NUM_T;
static REQ_NUM_T req_num = REQ_NO_REQ;

static void Control_Read( USBD_HANDLE_T hUsb, uint8_t* pData, uint16_t count )
{
    ((USB_CORE_CTRL_T*)hUsb)->EP0Data.pData = pData;
    if ( ((USB_CORE_CTRL_T*)hUsb)->SetupPacket.wLength > HID_ReportDescSize ) {
        ((USB_CORE_CTRL_T*)hUsb)->EP0Data.Count = count;
    }
    pUsbApi->core->DataInStage( hUsb );
    req_num = REQ_CTRL_READ;
}

#define REQ_TYPE( direction, type, recipient )  ((direction<<7)|(type<<5)|recipient)
#define HID_INTERFACE_NUM   0

ErrorCode_t request_Hdlr( USBD_HANDLE_T hUsb, void* data, uint32_t event )
{
    USB_CORE_CTRL_T* pCtrl = (USB_CORE_CTRL_T*)hUsb;
    static USB_SETUP_PACKET setup;

    switch( event )
    {
        case USB_EVT_SETUP:
                                                    // Get SETUP packet
            pUsbApi->hw->ReadSetupPkt( hUsb, USB_ENDPOINT_OUT(0), (uint32_t *)&setup );
                                                    // pick up Control Read transfer: Get_Descriptor(HID_REPORT)

            pCtrl->EP0Data.Count = setup.wLength;   // Number of bytes to transfer

            if ( (setup.bmRequestType.B == REQ_TYPE(REQUEST_DEVICE_TO_HOST,REQUEST_STANDARD,REQUEST_TO_INTERFACE) )
              && (setup.bRequest        == USB_REQUEST_GET_DESCRIPTOR )
              && (setup.wValue.W        == (HID_REPORT_DESCRIPTOR_TYPE<<8) )    // descriptor type | index
              && (setup.wIndex.W        == HID_INTERFACE_NUM )
//              && (setup.wLength         == 0 )
            ) {
                Control_Read( hUsb, (uint8_t*)HID_ReportDescriptor, HID_ReportDescSize );
                return LPC_OK;
            }
                                                    // pick up Control Read transfer: GET_REPORT
            if ( (setup.bmRequestType.B == REQ_TYPE(REQUEST_DEVICE_TO_HOST,REQUEST_CLASS,REQUEST_TO_INTERFACE) )
              && (setup.bRequest        == HID_REQUEST_GET_REPORT )
              && (setup.wValue.W        == (HID_REPORT_INPUT<<8) )  // Report type | Report ID
              && (setup.wIndex.W        == HID_INTERFACE_NUM )
//              && (setup.wLength         == 1 )
            ) {
                Control_Read( hUsb, &loopback_report, 1 );
                return LPC_OK;
            }
                                                    // pick up Control Write transfer: SET_REPORT
            if ( (setup.bmRequestType.B == REQ_TYPE(REQUEST_HOST_TO_DEVICE,REQUEST_CLASS,REQUEST_TO_INTERFACE) )
              && (setup.bRequest        == HID_REQUEST_SET_REPORT )
              && (setup.wValue.W        == (HID_REPORT_OUTPUT<<8) ) // Report type | Report ID
              && (setup.wIndex.W        == HID_INTERFACE_NUM )
              && (setup.wLength         == 1 )
            ) {
                                                    // set a buffer to receive the data
//              pCtrl->EP0Data.pData = pCtrl->EP0Buf;
                pCtrl->EP0Data.pData = &loopback_report;
                req_num = REQ_CTRL_WRITE;
                return LPC_OK;
            }
                                                    // pick up no-data Control transfer
            if ( (setup.bmRequestType.B == REQ_TYPE(REQUEST_HOST_TO_DEVICE,REQUEST_CLASS,REQUEST_TO_INTERFACE) )
              && (setup.bRequest        == HID_REQUEST_SET_IDLE )
              && (setup.wValue.W        == 0 )      // 0 | Report ID
              && (setup.wIndex.W        == HID_INTERFACE_NUM )
              && (setup.wLength         == 0 )
            ) {
                //
                // process this request, here
                //
                if (1) {                                // when processed successfully,
                  pUsbApi->core->StatusInStage( hUsb ); // move to STATUS stage
                } else {
                  pUsbApi->core->StallEp0( hUsb );      // reject this request
                }
                req_num = REQ_NO_REQ;
                return LPC_OK;                          // The process of this request finishes by this code
            }
            break;

        case USB_EVT_OUT:
            if ( req_num == REQ_CTRL_WRITE ) {
                if (pCtrl->EP0Data.Count) {             // still data to receive ?
                  pUsbApi->core->DataOutStage( hUsb );  // receive data
                }
                if (pCtrl->EP0Data.Count == 0) {        // data complete ?
                  //
                  // process data on the buffer, here
                  //
                  pUsbApi->core->StatusInStage( hUsb );
                  req_num = REQ_NO_REQ;
                }
                return LPC_OK;
            }
            break;

        case USB_EVT_IN:
            if ( req_num == REQ_CTRL_READ ) {
                if ( pCtrl->EP0Data.Count ) {
                    pUsbApi->core->DataInStage( hUsb );
                } else {
                    pUsbApi->core->StatusOutStage( hUsb );
                    req_num = REQ_NO_REQ;
                }
                return LPC_OK;
            }
            break;

        default:
            break;
    }

    req_num = REQ_NO_REQ;
    return ERR_USBD_UNHANDLED;
}



As above post tells, I've seen random STALL at STATUS stage of Control Write transfer process on the ROM stack. Not just on above example, on these NXP examples, too.
- USB_ROM_HID: Set_Report
- USB_ROM_CDC: Set_Line_Coding

Tsuneo
0 Kudos
Reply