Author Topic: Ethernet problem with GCC project  (Read 8472 times)

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Ethernet problem with GCC project
« on: November 15, 2008, 10:00:01 PM »
Hi All

It has been noticed that the SAM7X Ethernet connection may not work when newer GCC versions are used [depending on optimisation setting..]. Here is the result of an investigation and a change that can be made to correct the situation.

It was found that the PHY was being recognised correctly and, although the rest of the system seemed fine, the Ethernet interrupt routine was never being called (although the received frames could be found in memory!)




In sam7x.c, routine fnConfigEthernet() you will find the following lines of code:

    fnEnterInterrupt(EMAC, PRIORITY_EMAC, EMAC_Interrupt);
    EMAC_IER = (TCOMP | RCOMP);                                          // enable EMAC interrupts
    EMAC_NCR |= (EMAC_TE | EMAC_RE | CLRSTAT);                           // enable transmitter and receiver
    fnEnterInterrupt(PIOB, PRIORITY_PIOB, PortB_Interrupt);


The compiler was however not calling the subroutine fnEnterInterrupt() but rather was 'in-lining' the routine code. This means that the code in reality has the contents of this routine inserted (twice) - this looks more like optimisation for speed than for space (!):

static void fnEnterInterrupt(unsigned long ulInterruptSource, unsigned char ucPriority, void (*InterruptFunc)(void))
{
    unsigned long *ptrIntReg = ADD_AIC_SMR0;
    unsigned long ulMask = 0x01;

    while (!(ulInterruptSource & ulMask)) {
        ulMask <<= 1;
        ptrIntReg++;
    }

    *ptrIntReg = ucPriority;                                             // set the priority (and level sensitivity)
    ptrIntReg += (ADD_AIC_SVR0 - ADD_AIC_SMR0);
    *ptrIntReg = (unsigned long)InterruptFunc;                           // enter the handling interrupt routine in the vector table
    AIC_IDCR = ulInterruptSource;                                        // disable the interrupt
    AIC_ICCR = ulInterruptSource;                                        // clear the interrupt
    AIC_IECR = ulInterruptSource;                                        // enable interrupt to core
}



But the optimiser is also deciding that the three last instructions of the first block of in-lined code are unnecessary due to the fact that the registers are written with different values in the next block. The result is that these three instructions are missing and the EMAC interrupt is never enabled (although the following interrupt for the PHY interrupt line is correctly enabled).

This is presumably due to an 'improved' optimization level in the newer GCC version.

To stop it doing this, these three registers (in sam7x.h) need to be defined as volatile types as follows:

#define AIC_IECR                         *(volatile unsigned long*)(AIC_PERIPHERAL_BLOCK + 0x120) // Interrupt Enable Command Register (write only)
#define AIC_IDCR                         *(volatile unsigned long*)(AIC_PERIPHERAL_BLOCK + 0x124) // Interrupt Disable Command Register (write only)
#define AIC_ICCR                         *(volatile unsigned long*)(AIC_PERIPHERAL_BLOCK + 0x128) // Interrupt Clear Command Register (write only)



In fact, it is best to define all registers which have read-only or write-only properties as volatile. However I don't expect any others to actually cause any problems in this case.

For a discussion as to why all registers are not generally set as volatile, see the following:
http://www.utasker.com/forum/index.php?topic=172.msg629#msg629


Regards

Mark