Sunday, November 3, 2013

STAT_TX and STAT_RX are toggle bits

During a debugging session with the TotalPhase USB sniffer I noticed the reoccurring problem of failed IN transfers.


IN transfers werent taking place, worse they were flagged as STALLS. It occurred to me that the STAT_TX and STAT_RX bits might be at fault. STAT_RX really doesnt matter because only 00 = disabled is the only mode that will stall/halt/nak a setup out transfer.

At stm32_usb_init() I had set STAT_TX to NAK (01). Later after calls to stm32_usb_endp_send_packet() I would write VALID(11) to the 2 relevant bits of the EPR0 register representing STAT_TX. The problem is that a value of 1 for either bit indicates a toggle NOT a set. So it wasnt an error in the documentation - which was true to form. But rather an error in my interpretation of it. Sure enough minicom dumps show no print outs from within IN isr.



As a final test and confirmation I decided to stop execution under gdb and write the value 0x11 to STAT_TX and STAT_RX (bits 4+5 and 12+13) of the  EPR0 register. Just to see if its set or toggled. Sure enough my worst fears were confirmed



This led to writing the following routine to encapsulate the process of properly setting STAT_TX and STAT_RX


/*
 **********************************************************************
 *description:  routine to properly set the EP_TYPE bits in the
 *              Endpoint register.
 *input:        unsigned int    wdEndP     = endpoint number
 *              unsigned int    wdEndPType = the following
 *                              STM32_USB_EPR_EP_TYPE_BULK
 *                              STM32_USB_EPR_EP_TYPE_CONTROL
 *                              STM32_USB_EPR_EP_TYPE_ISO
 *                              STM32_USB_EPR_EP_TYPE_INTERRUPT
 *output:       void
 *
 *note:         the CTR_RX and CTR_TX bits are governed by the rules
 *              1 = dont change 0 = clear
 **********************************************************************
 */
void    stm32_usb_enable_endp(unsigned int wdEndP,\
                              unsigned int wdEndPType)
{
unsigned int    wdTemp;

/*retrieve the current register value*/
wdTemp  = *(PTR_STM32_USB_EP0R + wdEndP);

/*invariants - values that will not modify these reg bits*/
wdTemp  &= ~(STM32_USB_EPR_STAT_TX_MASK  | \
             STM32_USB_EPR_STAT_RX_MASK  | \
            (STM32_USB_EPR_DTOG_TX       | STM32_USB_EPR_DTOG_RX));
wdTemp  |=  (STM32_USB_EPR_CTR_TX        | STM32_USB_EPR_CTR_RX);

/*set EndPType and number*/
wdTemp  |= (wdEndPType << STM32_USB_EPR_EP_TYPE_OFFSET);
wdTemp  |= wdEndP;

/*set the current register value*/
*(PTR_STM32_USB_EP0R + wdEndP) = (wdTemp | wdEndP);
}





/*
 **********************************************************************
 *description:  routine to properly set the RX and TX STAT bits in the
 *              Endpoint register.
 *input:        unsigned int    wdEndP  = endpoint number
 *              unsigned int    wdMask  = the following
 *                              STM32_USB_EPR_CTR_RX
 *                              STM32_USB_EPR_CTR_TX
 *output:       void
 *
 *note:         the CTR_RX and CTR_TX bits are governed by the rules
 *              1 = dont change 0 = clear
 **********************************************************************
 */
void    stm32_usb_clear_epr_ctr(unsigned int wdEndP,\
                                unsigned int wdMask)
{
unsigned int    wdTemp;


/*retrieve the current register value*/
wdTemp  = *(PTR_STM32_USB_EP0R + wdEndP);

#ifdef  DONT_DEBUG
tclib_printf("stm32_usb_clear_epr_ctr initial value =%x\n", wdTemp);
#endif
/*invariants - values that will not modify these reg bits*/
wdTemp  &= ~(STM32_USB_EPR_STAT_TX_MASK  | \
             STM32_USB_EPR_STAT_RX_MASK  | \
            (STM32_USB_EPR_DTOG_TX       | STM32_USB_EPR_DTOG_RX));
wdTemp  |=  (STM32_USB_EPR_CTR_TX        | STM32_USB_EPR_CTR_RX);

wdTemp  &= ~wdMask;

#ifdef  DONT_DEBUG
tclib_printf("stm32_usb_clear_epr_ctr final value =%x\n", wdTemp);
#endif
/*set the current register value*/
*(PTR_STM32_USB_EP0R + wdEndP) = (wdTemp | wdEndP);
}





/*
 **********************************************************************
 *description:  routine to properly set or toggle the DTOG_RX | DTOG_TX
 *              bits of the Endpoint register.
 *input:        unsigned int    wdEndP     = endpoint number
 *              unsigned int    wdNewStat  = the following
 *                                      0  = set to 0
 *                                      1  = set to 1
 *                                     -1  = toggle
 *              unsigned int    wdOffset   = DTOG_RX or DTOG_TX STAT
 *                              STM32_USB_EPR_DTOG_RX_OFFSET
 *                              STM32_USB_EPR_DTOG_TX_OFFSET    
 *output:       void
 *                      
 *note:         the DTOG_RX and DTOG_TX bits are governed by the rules
 *              0 = dont change 1 = TOGGLE
 **********************************************************************
 */
void    stm32_usb_set_epr_dtog(unsigned int wdEndP,\
                               int wdNewStat,\
                               unsigned int wdOffset)
{
unsigned int    wdMask, wdTemp;

/*retrieve the current register value*/
wdMask  = *(PTR_STM32_USB_EP0R + wdEndP);
wdTemp  = wdMask;


/*wdMask has STAT position extracted*/
if(wdOffset == STM32_USB_EPR_DTOG_RX_OFFSET)
        wdMask &= STM32_USB_EPR_DTOG_RX; 
else
        wdMask &= STM32_USB_EPR_DTOG_TX; 


/*normalize then compute XOR of value and mask*/
wdMask >>= wdOffset;
if(wdNewStat != -1 && wdMask == wdNewStat)
        return;

if(wdNewStat == -1)
        wdNewStat = 1;

wdMask  ^= wdNewStat;
wdMask <<= wdOffset;

/*mask out non important bits*/
if(wdOffset == STM32_USB_EPR_DTOG_RX_OFFSET)
        wdMask &= STM32_USB_EPR_DTOG_RX; 
else
        wdMask &= STM32_USB_EPR_DTOG_TX; 


/*invariants - values that will not modify these reg bits*/
wdTemp  &= ~(STM32_USB_EPR_STAT_TX_MASK | \
             STM32_USB_EPR_STAT_RX_MASK | \
            (STM32_USB_EPR_DTOG_TX      | STM32_USB_EPR_DTOG_RX));
wdTemp  |=  (STM32_USB_EPR_CTR_TX       | STM32_USB_EPR_CTR_RX);

/*set user settings*/
wdTemp  |= wdMask;

/*update register*/
*(PTR_STM32_USB_EP0R + wdEndP) = (wdTemp | wdEndP);
}





/*
 **********************************************************************
 *description:  routine to properly set the RX and TX STAT bits in the
 *              Endpoint register.
 *input:        unsigned int    wdEndP     = endpoint number
 *              unsigned int    wdNewStat  = the following
 *                              STM32_USB_EPR_STAT_RX_DISABLE
 *                              STM32_USB_EPR_STAT_TX_DISABLE
 *
 *                              STM32_USB_EPR_STAT_RX_STALL
 *                              STM32_USB_EPR_STAT_TX_STALL
 *
 *                              STM32_USB_EPR_STAT_RX_NAK
 *                              STM32_USB_EPR_STAT_TX_NAK
 *
 *                              STM32_USB_EPR_STAT_RX_VALID
 *                              STM32_USB_EPR_STAT_TX_VALID
 *              unsigned int    wdOffset   = RX or TX STAT
 *                              STM32_USB_EPR_STAT_RX_OFFSET
 *                              STM32_USB_EPR_STAT_TX_OFFSET    
 *output:       void
 *                      
 *note:         the RX_STAT and TX_STAT bits are governed by the rules
 *              0 = dont change 1 = TOGGLE
 *              A major error was not taking this into consideration
 **********************************************************************
 */
void    stm32_usb_set_epr_stat(unsigned int wdEndP,\
                               unsigned int wdNewStat,\
                               unsigned int wdOffset)
{
unsigned int    wdMask, wdTemp;

/*retrieve the current register value*/
wdMask  = *(PTR_STM32_USB_EP0R + wdEndP);
wdTemp  = wdMask;


/*wdMask has STAT position extracted*/
if(wdOffset == STM32_USB_EPR_STAT_RX_OFFSET)
        wdMask &= STM32_USB_EPR_STAT_RX_MASK; 
else
        wdMask &= STM32_USB_EPR_STAT_TX_MASK; 


/*normalize then compute XOR of value and mask*/
wdMask >>= wdOffset;
wdMask  ^= wdNewStat;
wdMask <<= wdOffset;

/*mask out non important bits*/
if(wdOffset == STM32_USB_EPR_STAT_RX_OFFSET)
        wdMask &= STM32_USB_EPR_STAT_RX_MASK; 
else
        wdMask &= STM32_USB_EPR_STAT_TX_MASK; 


/*invariants - values that will not modify these reg bits*/
wdTemp  &= ~(STM32_USB_EPR_STAT_TX_MASK | \
             STM32_USB_EPR_STAT_RX_MASK | \
            (STM32_USB_EPR_DTOG_TX      | STM32_USB_EPR_DTOG_RX));
wdTemp  |=  (STM32_USB_EPR_CTR_TX       | STM32_USB_EPR_CTR_RX);

/*set user settings*/
wdTemp  |= wdMask;

/*update register*/
*(PTR_STM32_USB_EP0R + wdEndP) = (wdTemp | wdEndP);
}





No comments:

Post a Comment