This is the mail archive of the ecos-discuss@sourceware.org mailing list for the eCos project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: Scheduler Lock without Kernel


On Tue, 2011-03-01 at 13:25 +0100, Martin Laabs wrote:
> Hi,
> 
> I currently improve the Ethernet driver for the at91sam9 family of CPUs. 
> However - Since this CPU has a cache and is using DMA for data exchange 
> with the MAC I have to lock the scheduler while manipulating these memory 
> structures. (This is to ensure, that the cache is coherent while doing this 
> manipulations)
> Since the code should run with the kernel as well without it I'd like to 
> know whether it's save to call the cyg_scheduler_lock without being in 
> kernel mode.
> But I fear I have to write a macro that distinguish between "with kernel" 
> and "hal only". Am I right? Which symbol is best to distinguish between 
> thees two possibilities?
> ...

Martin!

Attached is my work-in-progress on a AT91 ethernet driver. It works well
with the AT91SAM9 devices, both with a eCos kernel and with Redboot. It
needs the MMU to create a shadow of RAM that is uncached. Anyway, there
might be something useful here for you. There are some private changes
marked "Exegin" that you can remove.

-- 
+------------------------------------
| Daniel Helgason <dhelgason@shaw.ca>

//==========================================================================
//
//      if_at91.c
//
//
//
//==========================================================================
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    Daniel Helgason
// Contributors: Andrew Lunn, John Eigelaar
// Date:         2010-01-01
// Purpose:
// Description:
//
//####DESCRIPTIONEND####
//
//========================================================================*/

#include <pkgconf/system.h>
#include <pkgconf/hal.h>
#include <pkgconf/devs_eth_arm_at91.h>
#include <pkgconf/io_eth_drivers.h>
#if defined(CYGPKG_REDBOOT)
   #include <pkgconf/redboot.h>
#endif

#include <cyg/hal/hal_io.h>
#include <cyg/hal/hal_intr.h>
#include <cyg/hal/hal_arch.h>
#include <cyg/hal/hal_cache.h>
#include <cyg/hal/drv_api.h>
#include <cyg/hal/hal_diag.h>
#include <cyg/infra/cyg_type.h>
#include <cyg/infra/cyg_ass.h>
#include <cyg/infra/diag.h>
#include <cyg/io/eth/netdev.h>
#include <cyg/io/eth/eth_drv.h>
#include <cyg/io/eth/eth_drv_stats.h>
#include <cyg/io/eth_phy.h>
#include <errno.h>
#include <string.h>

#ifndef MIN
#define MIN(x,y) ((x)<(y) ? (x) : (y))
#endif

// Cache translation
#ifndef CYGARC_UNCACHED_ADDRESS
#define CYGARC_UNCACHED_ADDRESS(x) (x)
#endif

#define AT91_ETH_RX_INT_MASK \
          (AT91_EMAC_ISR_RCOM | AT91_EMAC_ISR_RBNA | AT91_EMAC_ISR_ROVR)

/*
#define AT91_ETH_TX_INT_MASK \
          (AT91_EMAC_ISR_TOVR | AT91_EMAC_ISR_TUND | AT91_EMAC_ISR_RTRY | \
           AT91_EMAC_ISR_TBRE | AT91_EMAC_ISR_TCOM)
*/
#define AT91_ETH_TX_INT_MASK \
          (AT91_EMAC_ISR_TUND | AT91_EMAC_ISR_RTRY | \
           AT91_EMAC_ISR_TBRE | AT91_EMAC_ISR_TCOM)

#define AT91_ETH_INT_MASK (AT91_ETH_RX_INT_MASK | AT91_ETH_TX_INT_MASK)

#define AT91_ETH_NEXT_RBD(idx);                         \
  CYG_MACRO_START                                       \
    idx++;                                              \
    if (idx >= CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS)        \
      idx = 0;                                          \
  CYG_MACRO_END

#define AT91_ETH_NEXT_TBD(idx);                         \
  CYG_MACRO_START                                       \
    idx++;                                              \
    if (idx >= CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS)        \
      idx = 0;                                          \
  CYG_MACRO_END

// Set up the level of debug output
#if CYGPKG_DEVS_ETH_ARM_AT91_DEBUG_LEVEL > 0
#define AT91_ETH_DEBUG1(cond, args...)                  \
  CYG_MACRO_START                                       \
    if ((cond))                                         \
      diag_printf(args);                                \
  CYG_MACRO_END
#else
#define AT91_ETH_DEBUG1(args...) CYG_EMPTY_STATEMENT
#endif

#if CYGPKG_DEVS_ETH_ARM_AT91_DEBUG_LEVEL > 1
#define AT91_ETH_DEBUG2(cond, args...)                  \
  CYG_MACRO_START                                       \
    if ((cond))                                         \
      diag_printf(args);                                \
  CYG_MACRO_END
#else
#define AT91_ETH_DEBUG2(args...) CYG_EMPTY_STATEMENT
#endif

//Driver interface callbacks
#define _eth_drv_init(sc,mac) \
  (sc->funs->eth_drv->init)(sc,(unsigned char *)mac)
#define _eth_drv_tx_done(sc,key,status) \
  (sc->funs->eth_drv->tx_done)(sc,key,status)
#define _eth_drv_recv(sc,len) \
  (sc->funs->eth_drv->recv)(sc,len)

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
static cyg_uint32 at91_eth_isr(cyg_vector_t vector, cyg_addrword_t data);
#endif

// --------------------------------------------------------------
// RedBoot configuration options for managing ESAs for us

// BUGBUG dkh - Need better MAC address management.

// Decide whether to have redboot config vars for it...
#if defined(CYGSEM_REDBOOT_FLASH_CONFIG) && defined(CYGPKG_REDBOOT_NETWORKING)
  #include <redboot.h>
  #include <flash_config.h>

  #ifdef CYGSEM_DEVS_ETH_ARM_AT91_REDBOOT_HOLDS_ESA_ETH0
  static unsigned char at91_eth0_mac_addr[6] = { CYGPKG_DEVS_ETH_ARM_AT91_MACADDR };

  RedBoot_config_option("Network hardware address [MAC] for eth0",
                        eth0_esa_data,
                        ALWAYS_ENABLED, true,
                        CONFIG_ESA, at91_eth0_mac_addr);
  #endif
#endif  // CYGPKG_REDBOOT_NETWORKING && CYGSEM_REDBOOT_FLASH_CONFIG

// and initialization code to read them
// - independent of whether we are building RedBoot right now:
#ifdef CYGPKG_DEVS_ETH_ARM_AT91_REDBOOT_HOLDS_ESA
  #include <cyg/hal/hal_if.h>

  #ifndef CONFIG_ESA
    #define CONFIG_ESA (6)
  #endif

  #define CYGHWR_DEVS_ETH_ARM_AT91_GET_ESA( mac_address, ok )           \
  CYG_MACRO_START                                                       \
  ok = CYGACC_CALL_IF_FLASH_CFG_OP( CYGNUM_CALL_IF_FLASH_CFG_GET,       \
                                    "eth0_esa_data",                    \
                                    mac_address,                        \
                                    CONFIG_ESA);                        \
  CYG_MACRO_END
#endif // CYGPKG_DEVS_ETH_AT91_ETH_REDBOOT_HOLDS_ESA

//============================================================================

// Private Data structures

#ifndef AT91_EMAC_RX_BUFF_SIZE
#define AT91_EMAC_RX_BUFF_SIZE  128
#endif

// Receive Buffer Descriptor
typedef struct rbd_s
{
  volatile cyg_uint32 addr;
  volatile cyg_uint32 sr;
} rbd_t;

// Receive Buffer
typedef struct rb_s
{
  cyg_uint8 rb[AT91_EMAC_RX_BUFF_SIZE];
} rb_t;

// Transmit Buffer Descriptor
typedef struct tbd_s
{
  cyg_uint32 addr;
  volatile cyg_uint32 sr;
} tbd_t;

// AT91 Ethernet private data
typedef struct at91_eth_priv_s
{
  cyg_uint32 int_vector;
  char *esa_key;        // RedBoot 'key' for device ESA
  cyg_uint8 *enaddr;
  cyg_uint32 base;      // Base address of device
  eth_phy_access_t *phy;
  rbd_t rbd[CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS] __attribute__ ((aligned (8)));
  rb_t  rb[CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS] __attribute__ ((aligned (8)));
  tbd_t tbd[CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS] __attribute__ ((aligned (8)));
  unsigned long tx_keys[CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS];
  cyg_uint32 isr;
  cyg_uint32 tx_head_idx;
  cyg_uint32 tx_tail_idx;
  cyg_uint32 tx_free;
  cyg_uint32 tx_max_frame_sglen;
  cyg_bool tx_busy;
  cyg_uint32 rx_head_idx;
  cyg_uint32 rx_tail_idx;
  cyg_uint32 rx_frame_cnt;
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
  cyg_interrupt intr;
  cyg_handle_t  intr_handle;
#endif
} at91_eth_priv_t;

// AT91 multicast hash mask (64-bit hash in two 32-bit words)
typedef struct mc_hash_mask_s
{
  cyg_uint32 hi;
  cyg_uint32 lo;
} at91_mc_hash_mask_t;

//============================================================================

// Private Data

// Hash masks for multicast MAC addresses on AT91 EMAC.
static at91_mc_hash_mask_t at91_mc_hash_masks[] =
{
//  high word   low word
  { 0x00000410, 0x41041041 },
  { 0x00000820, 0x82082082 },
  { 0x00001041, 0x04104104 },
  { 0x00002082, 0x08208208 },
  { 0x00004104, 0x10410410 },
  { 0x00008208, 0x20820820 }
};

// Exegin specific. Registered callbacks.
static void    (*at91_eth_rx_callback_func)(void) = NULL;
static void    (*at91_eth_tx_callback_func)(void) = NULL;


//============================================================================
// Exegin specific. Register callbacks.
//

void
at91_eth_register_rx_callback(void (*rx_callback_func)(void))
{
  at91_eth_rx_callback_func = rx_callback_func;
} /* at91_eth_register_rx_callback */

void
at91_eth_register_tx_callback(void (*tx_callback_func)(void))
{
  at91_eth_tx_callback_func = tx_callback_func;
} /* at91_eth_register_tx_callback */

//============================================================================
// PHY access bits and pieces
//

static void
at91_mdio_enable(void)
{
  cyg_uint32 val;
  HAL_READ_UINT32(AT91_EMAC + AT91_EMAC_NCR, val);
  val |= AT91_EMAC_NCR_MPE;    /* enable management port */
  HAL_WRITE_UINT32(AT91_EMAC + AT91_EMAC_NCR, val);
}

static void
at91_mdio_disable(void)
{
  cyg_uint32 val;
  HAL_READ_UINT32(AT91_EMAC + AT91_EMAC_NCR, val);
  val &= ~AT91_EMAC_NCR_MPE;    /* disable management port */
  HAL_WRITE_UINT32(AT91_EMAC + AT91_EMAC_NCR, val);
}

// Write one of the PHY registers via the MII bus
static void
at91_write_phy(int reg_addr, int phy_addr, unsigned short data)
{
  cyg_uint32 val;

  CYG_ASSERTC(reg_addr >= 0 && reg_addr <= AT91_EMAC_MAN_REGA_MASK);
  CYG_ASSERTC(phy_addr >= 0 && phy_addr <= AT91_EMAC_MAN_PHY_MASK);

  val = (AT91_EMAC_MAN_SOF  |
         AT91_EMAC_MAN_WR   |
         AT91_EMAC_MAN_CODE |
         AT91_EMAC_MAN_PHYA(phy_addr) |
         AT91_EMAC_MAN_REGA(reg_addr) |
         AT91_EMAC_MAN_DATA(data));

  HAL_WRITE_UINT32(AT91_EMAC + AT91_EMAC_MAN, val);

  // Wait for operation to complete
  do {
    HAL_READ_UINT32((AT91_EMAC + AT91_EMAC_NSR), val);
  } while(!(val & AT91_EMAC_NSR_IDLE));
}

// Read one of the PHY registers via the MII bus
static bool
at91_read_phy(int reg_addr, int phy_addr, unsigned short *data)
{
  cyg_uint32 val;

  CYG_ASSERTC(reg_addr >= 0 && reg_addr <= AT91_EMAC_MAN_REGA_MASK);
  CYG_ASSERTC(phy_addr >= 0 && phy_addr <= AT91_EMAC_MAN_PHY_MASK);

  val = (AT91_EMAC_MAN_SOF  |
         AT91_EMAC_MAN_RD   |
         AT91_EMAC_MAN_CODE |
         AT91_EMAC_MAN_PHYA(phy_addr) |
         AT91_EMAC_MAN_REGA(reg_addr));


  HAL_WRITE_UINT32(AT91_EMAC + AT91_EMAC_MAN, val);

  // Wait for operation to complete
  do {
    HAL_READ_UINT32((AT91_EMAC + AT91_EMAC_NSR), val);
  } while(!(val & AT91_EMAC_NSR_IDLE));

  HAL_READ_UINT32(AT91_EMAC + AT91_EMAC_MAN, val);
  *data = val & AT91_EMAC_MAN_DATA_MASK;

  return (true);
}

/* Exegin specific. */
/* Give public names to the phy r/w functions for the Q5x firmware. */
void
phy_write(int reg_addr, int phy_addr, unsigned short data)
{
  at91_write_phy(reg_addr, phy_addr, data);
}

bool
phy_read(int reg_addr, int phy_addr, unsigned short *data)
{
  return at91_read_phy(reg_addr, phy_addr, data);
}

// Enable the MDIO bit in MAC control register so that we can talk to
// the PHY. Also set the clock divider so that MDC is less than 2.5MHz.
static void
at91_init_phy(void)
{
  cyg_uint32 cfg;
  cyg_uint32 div;

  HAL_READ_UINT32(AT91_EMAC + AT91_EMAC_NCFG, cfg);
  cfg &= ~AT91_EMAC_NCFG_CLK_MASK;

  div = (CYGNUM_HAL_ARM_AT91_CLOCK_SPEED / 2500000);
  if (div < 8)
    cfg |= AT91_EMAC_NCFG_CLK_HCLK_8;
  else if (div < 16)
    cfg |= AT91_EMAC_NCFG_CLK_HCLK_16;
  else if (div < 32)
    cfg |= AT91_EMAC_NCFG_CLK_HCLK_32;
  else if (div < 64)
    cfg |= AT91_EMAC_NCFG_CLK_HCLK_64;
  else
    CYG_FAIL("Unable to program MII clock");

  HAL_WRITE_UINT32(AT91_EMAC + AT91_EMAC_NCFG, cfg);
}

ETH_PHY_REG_LEVEL_ACCESS_FUNS(at91_phy,
                              at91_init_phy,
                              NULL,
                              at91_write_phy,
                              at91_read_phy);


//======================================================================
// Transmit and Receive buffer-descriptor initialization.

// Initialize the receiver buffer descriptors.
static void
at91_rb_init(at91_eth_priv_t *priv)
{
  int i;

  for (i = 0 ; i < CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS; i++) {
    priv->rbd[i].addr = ((cyg_uint32)&priv->rb[i]) & AT91_EMAC_RBD_ADDR_MASK;
    priv->rbd[i].sr = 0;
  }
  // Set the wrap bit on the last entry.
  priv->rbd[CYGNUM_DEVS_ETH_ARM_AT91_RX_BUFS - 1].addr |= AT91_EMAC_RBD_ADDR_WRAP;

  HAL_DCACHE_STORE(priv->rbd, sizeof(priv->rbd));
}

// Initialize the transmit buffer descriptors.
static void
at91_tb_init(at91_eth_priv_t *priv)
{
  int i;

  for (i = 0 ; i < CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS; i++) {
    priv->tbd[i].addr = 0;
    priv->tbd[i].sr = AT91_EMAC_TBD_SR_USED;
  }
  // Set the wrap bit on the last entry.
  priv->tbd[CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS - 1].sr |= AT91_EMAC_TBD_SR_WRAP;

  HAL_DCACHE_STORE(priv->tbd, sizeof(priv->tbd));
}


//======================================================================
// Enable and Disable of the receiver and transmitter.

static void
at91_disable_rx(at91_eth_priv_t *priv)
{
  cyg_uint32 ctl;

  HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
  ctl &= ~AT91_EMAC_NCR_RE;
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}

static void
at91_disable_tx(at91_eth_priv_t *priv)
{
  cyg_uint32 reg_val;

  // Stop transmitter.
  HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, reg_val);
  reg_val &= ~AT91_EMAC_NCR_TX;
  reg_val |= AT91_EMAC_NCR_THALT;
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, reg_val);

  // Wait for transmitter to stop.
  do {
    HAL_READ_UINT32(priv->base + AT91_EMAC_TSR, reg_val);
  } while (reg_val & AT91_EMAC_TSR_TXIDLE);
}

static void
at91_enable_rx(at91_eth_priv_t *priv)
{
  cyg_uint32 ctl;

  HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
  ctl |= AT91_EMAC_NCR_RE;
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}

static void
at91_enable_tx(at91_eth_priv_t *priv)
{
  cyg_uint32 ctl;

  HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
  if (!(ctl & AT91_EMAC_NCR_TX)) {
    priv->tx_free = CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS - 1;
    priv->tx_head_idx = priv->tx_tail_idx = 0;
    at91_tb_init(priv);
    ctl |= AT91_EMAC_NCR_TX;
    ctl &= ~AT91_EMAC_NCR_THALT;
    HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
  }
}

static void
at91_enable(at91_eth_priv_t *priv)
{
  at91_enable_tx(priv);
  at91_enable_rx(priv);
}

static void
at91_disable(at91_eth_priv_t *priv)
{
  at91_disable_tx(priv);
  at91_disable_rx(priv);
}

static void
at91_start_transmitter(at91_eth_priv_t *priv)
{
  cyg_uint32 ctl;

  HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
  ctl |= AT91_EMAC_NCR_TSTART;
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}


//======================================================================
// Initialization code

// Configure the pins so that the EMAC has control of them.
static void
at91_cfg_pins(void)
{
#ifdef CYGHWR_DEV_ETH_ARM_AT91_USE_RMII
  // RMII doesn't need all the EMAC IO pins
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_EREFCK);

  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERXDV);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX0);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX1);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERXER);

  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETXEN);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX0);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX1);

  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_EMDC);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_EMDIO);
#else
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_EREFCK);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ECRS);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ECOL);

  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERXDV);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX0);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX1);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX2);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERX3);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERXER);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ERXCK);

  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETXEN);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX0);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX1);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX2);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETX3);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_ETXER);

  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_EMDC);
  HAL_ARM_AT91_PIO_CFG(AT91_EMAC_EMDIO);
#endif
}

// Set a specific-address match register to a given MAC address.
// Packets received which match this address will be passed on.
static void
at91_set_mac(at91_eth_priv_t *priv, cyg_uint8 *mac_addr, int sa)
{
  cyg_uint32 mac_hi, mac_lo;

  // Adjust specific-address register index.
  CYG_ASSERTC(sa > 0 && sa < 5);
  sa--;

  // Split MAC address into two words.
  mac_lo = (mac_addr[3] << 24) |
           (mac_addr[2] << 16) |
           (mac_addr[1] <<  8) |
            mac_addr[0];

  mac_hi = (mac_addr[5] <<  8) |
            mac_addr[4];

  // Write MAC address to the specific-address register.
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_SA1L + (8 * sa), mac_lo);
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_SA1H + (8 * sa), mac_hi);
}

// Compute number of bits in word (aka population count).
static int
at91_mc_bitcnt(cyg_uint32 bits)
{
  bits -= (bits >> 1) & 0x55555555;
  bits = ((bits >> 2) & 0x33333333) + (bits & 0x33333333);
  bits = ((bits >> 4) + bits) & 0x0f0f0f0f;
  bits += bits >> 8;
  bits += bits >> 16;

  return bits & 0x3f;
}

// Add a multicast MAC address to the hardware and enable hash matching.
// Packets received which match these addresses will be passed on.
static void
at91_add_mc_mac(at91_eth_priv_t * priv, cyg_uint8 * mc_addr)
{
  cyg_uint32 t, mc_lo, mc_hi, hash_lo, hash_hi;
  int i, bit_idx, bit_cnt;

  // Split multicast mac address into two words.
  mc_lo = (mc_addr[3] << 24) |
          (mc_addr[2] << 16) |
          (mc_addr[1] <<  8) |
           mc_addr[0];

  mc_hi = (mc_addr[5] << 8) |
           mc_addr[4];

  // Build the 6-bit hash_bit index.
  bit_idx = 0;
  for (i = 0; i < 6; i++) {
    bit_cnt =  at91_mc_bitcnt(mc_lo & at91_mc_hash_masks[i].lo);
    bit_cnt += at91_mc_bitcnt(mc_hi & at91_mc_hash_masks[i].hi);

    bit_idx |= (bit_cnt & 1) << i;
  }

  // Read the current 64-bit hardware hash value.
  HAL_READ_UINT32(priv->base + AT91_EMAC_HRB, hash_lo);
  HAL_READ_UINT32(priv->base + AT91_EMAC_HRT, hash_hi);

  // Set a single bit in the hash value according to the index.
  if (bit_idx > 31)
    hash_hi |= 1 << (bit_idx - 32);
  else
    hash_lo |= 1 << bit_idx;

  // Write the new 64-bit hardware hash value.
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_HRB, hash_lo);
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_HRT, hash_hi);

  // Enable hardware to do multicast hash matching.
  HAL_READ_UINT32(priv->base + AT91_EMAC_NCFG, t);
  t |= AT91_EMAC_NCFG_MTI;
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCFG, t);
}

static void
at91_clear_stats(at91_eth_priv_t *priv)
{
  cyg_uint32 ctl;

  HAL_READ_UINT32(priv->base + AT91_EMAC_NCR, ctl);
  ctl |= AT91_EMAC_NCR_CSR;
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCR, ctl);
}

// Initialize and configure the interface for use.
// Interrupts are grabbed, etc. This means the start function has
// little to do except enable the receiver
static bool
at91_eth_init(struct cyg_netdevtab_entry *tab)
{
  struct eth_drv_sc *sc = (struct eth_drv_sc *)tab->device_instance;
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
  unsigned char mac_addr[6] = { CYGPKG_DEVS_ETH_ARM_AT91_MACADDR };
  unsigned char mac_addr_none[6] = { 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
  unsigned short phy_state = 0;
  cyg_uint32 ncfg = 0;
#ifdef CYGHWR_DEVS_ETH_ARM_AT91_GET_ESA
  bool esa_ok = false;
#endif

  AT91_ETH_DEBUG1(true, "AT91_ETH: Initialising at 0x%08x\n", priv->base);

  /* Disable all the interrupts for the moment            */
  /* The Start function actually enables all that we need */
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_IDR, 0x3FFF);

  at91_disable(priv);

  priv->tx_free = CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS - 1;
  priv->tx_head_idx = priv->tx_tail_idx = 0;
  priv->tx_max_frame_sglen = 3;
  priv->rx_head_idx = priv->rx_tail_idx = 0;

  // Enable the clock to the EMAC
  HAL_WRITE_UINT32(AT91_PMC + AT91_PMC_PCER, AT91_PMC_PCER_EMAC);
  HAL_WRITE_UINT32(AT91_PMC + AT91_PMC_PCER, AT91_PMC_PCER_PIOB);
  HAL_WRITE_UINT32(AT91_PMC + AT91_PMC_PCER, AT91_PMC_PCER_PIOA);

  at91_cfg_pins();

  /* Enable IO clock and either MII or RMII interface */
#ifdef CYGHWR_DEV_ETH_ARM_AT91_USE_RMII
  HAL_WRITE_UINT32(priv->base+AT91_EMAC_USRIO,
                   AT91_EMAC_USRIO_CLKEN | AT91_EMAC_USRIO_RMII);
#else
  HAL_WRITE_UINT32(priv->base+AT91_EMAC_USRIO, AT91_EMAC_USRIO_CLKEN);
#endif

  // If we are building an interrupt enabled version,
  // then install the interrupt handler
#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
  AT91_ETH_DEBUG1(true, "AT91_ETH: Installing Interrupts on IRQ %d\n",
                  priv->int_vector);
  cyg_drv_interrupt_create(priv->int_vector,
                           4,
                           (cyg_addrword_t)sc,
                           at91_eth_isr,
                           eth_drv_dsr,
                           &priv->intr_handle,
                           &priv->intr);

  cyg_drv_interrupt_attach(priv->intr_handle);
  cyg_drv_interrupt_unmask(priv->int_vector);
#endif

#ifdef CYGHWR_DEVS_ETH_ARM_AT91_GET_ESA
  // Get MAC address from RedBoot configuration variables.
  CYGHWR_DEVS_ETH_ARM_AT91_GET_ESA(&mac_addr[0], esa_ok);
  // If that fails, then MAC address is unchanged and the
  // MAC address from CDL is used.
    AT91_ETH_DEBUG1(esa_ok, "AT91_ETH: Warning! ESA unknown\n");
#endif

  AT91_ETH_DEBUG1(true, "AT91_ETH: MAC addr: %02x:%02x:%02x:%02x:%02x:%02x\n",
                  mac_addr[0], mac_addr[1], mac_addr[2],
                  mac_addr[3], mac_addr[4], mac_addr[5]);

  // Give the EMAC its MAC address
  at91_set_mac(priv, mac_addr, 1);
  at91_set_mac(priv, mac_addr_none, 2);
  at91_set_mac(priv, mac_addr_none, 3);
  at91_set_mac(priv, mac_addr_none, 4);

  // Setup the receiver buffer descriptors and tell EMAC where they are
  at91_rb_init(priv);
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_RBQP, (cyg_uint32)priv->rbd);

  // Setup the transmit buffer descriptors and tell EMAC where they are
  at91_tb_init(priv);
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_TBQP, (cyg_uint32)priv->tbd);

  // Setup the PHY
  CYG_ASSERTC(priv->phy);
  at91_mdio_enable();
  if (!_eth_phy_init(priv->phy)) {
    at91_mdio_disable();
    return (false);
  }

  // Set EMAC network configuration to match PHY state
  HAL_READ_UINT32(priv->base + AT91_EMAC_NCFG, ncfg);

  phy_state = _eth_phy_state(priv->phy);
//    at91_mdio_disable();
  AT91_ETH_DEBUG1(!(phy_state & ETH_PHY_STAT_LINK), "AT91_ETH: No Link\n");
  if ((phy_state & ETH_PHY_STAT_LINK) != 0) {
    if (((phy_state & ETH_PHY_STAT_100MB) != 0)) {
      AT91_ETH_DEBUG1(true, "AT91_ETH: 100Mbs");
      ncfg |= AT91_EMAC_NCFG_SPD_100Mbps;
    }
    else {
      AT91_ETH_DEBUG1(true, "AT91_ETH: 10Mbps");
      ncfg &= ~(AT91_EMAC_NCFG_SPD_100Mbps);
    }

    if ((phy_state & ETH_PHY_STAT_FDX)) {
      AT91_ETH_DEBUG1(true, " Full Duplex\n");
      ncfg |= AT91_EMAC_NCFG_FD;
    }
    else {
      AT91_ETH_DEBUG1(true, " Half Duplex\n");
      ncfg &= ~(AT91_EMAC_NCFG_FD);
    }
  }

  // Write the network-configuration register
  ncfg |= (AT91_EMAC_NCFG_RLCE);
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCFG, ncfg);

  // Clear the statistics counters
  at91_clear_stats(priv);

  /* Clear the EMAC status registers */
  HAL_READ_UINT32(priv->base + AT91_EMAC_ISR, ncfg);
  HAL_READ_UINT32(priv->base + AT91_EMAC_RSR, ncfg);
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_RSR, ncfg);
  HAL_READ_UINT32(priv->base + AT91_EMAC_TSR, ncfg);
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_TSR, ncfg);

  // Initialize the upper layer driver
  _eth_drv_init(sc, mac_addr);

  return (true);
}

// This function is called to stop the interface.
static void
at91_eth_stop(struct eth_drv_sc *sc)
{
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_IDR, AT91_ETH_INT_MASK);
#endif

  at91_disable(priv);
}

// This function is called to "start up" the interface. It may be called
// multiple times, even when the hardware is already running.
static void
at91_eth_start(struct eth_drv_sc *sc, unsigned char *mac_addr, int flags)
{
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;

  // Enable the receiver and transmitter
  at91_enable(priv);

//#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_IER, AT91_ETH_INT_MASK);
//#endif
}

// This function is called for low level "control" operations
static int
at91_eth_control(struct eth_drv_sc *sc, unsigned long key,
                 void *data, int length)
{
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;

  switch (key) {
    // Set Ethernet MAC address
    case ETH_DRV_SET_MAC_ADDRESS:
    {
      cyg_uint8 *mac_addr = (cyg_uint8 *)data;

      if (mac_addr == NULL || length < ETHER_ADDR_LEN)
        return 1;

      AT91_ETH_DEBUG1(true, "AT91_ETH: set MAC %02x:%02x:%02x:%02x:%02x:%02x\n",
                      mac_addr[0], mac_addr[1], mac_addr[2],
                      mac_addr[3], mac_addr[4], mac_addr[5]);

      at91_eth_stop(sc);
      at91_set_mac(priv, mac_addr, 1);
      at91_eth_start(sc, mac_addr, 0);
      return 0;
    }

#ifdef ETH_DRV_GET_MAC_ADDRESS
    // Get Ethernet MAC address from hardware
    case ETH_DRV_GET_MAC_ADDRESS:
    {
      cyg_uint32 mac_hi, mac_lo;
      cyg_uint8 *mac_addr = (cyg_uint8 *)data;

      if (mac_addr == NULL || length < ETHER_ADDR_LEN)
        return 1;

      HAL_READ_UINT32(priv->base + AT91_EMAC_SA1L, mac_lo);
      HAL_READ_UINT32(priv->base + AT91_EMAC_SA1H, mac_hi);

      mac_addr[0] =         mac_lo & 0xFF;
      mac_addr[1] = (mac_lo >>  8) & 0xFF;
      mac_addr[2] = (mac_lo >> 16) & 0xFF;
      mac_addr[3] = (mac_lo >> 24) & 0xFF;
      mac_addr[4] =         mac_hi & 0xFF;
      mac_addr[5] = (mac_hi >>  8) & 0xFF;

      return 0;
    }
#endif

    // Set specified multicast MAC addresses or clear all
    case ETH_DRV_SET_MC_LIST:
    {
      int mc_cnt;
      cyg_uint32 t;
      struct eth_drv_mc_list *mc_list = (struct eth_drv_mc_list *)data;

      if (mc_list == NULL || length != sizeof(struct eth_drv_mc_list))
        return 1;

      mc_cnt = mc_list->len;
      if (mc_cnt > 0) {
        // Enable recognition of each multicast MAC address in list.
        while (mc_cnt) {
          at91_add_mc_mac(priv, &mc_list->addrs[--mc_cnt][0]);
          AT91_ETH_DEBUG1(true, "AT91_ETH: add multicast MAC "
                                "%02x:%02x:%02x:%02x:%02x:%02x\n",
                          mc_list->addrs[mc_cnt][0],
                          mc_list->addrs[mc_cnt][1],
                          mc_list->addrs[mc_cnt][2],
                          mc_list->addrs[mc_cnt][3],
                          mc_list->addrs[mc_cnt][4],
                          mc_list->addrs[mc_cnt][5]);
        }
      }
      else {
        // Disable recognition of all multicast addresses.
        AT91_ETH_DEBUG1(true, "AT91_ETH: disable all multicast MACs\n");
        HAL_WRITE_UINT32(priv->base + AT91_EMAC_HRB, 0);
        HAL_WRITE_UINT32(priv->base + AT91_EMAC_HRT, 0);
        HAL_READ_UINT32(priv->base + AT91_EMAC_NCFG, t);
        t &= ~AT91_EMAC_NCFG_MTI;
        HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCFG, t);
      }

      return 0;
    }

    // Enable all multicast MAC addresses
    case ETH_DRV_SET_MC_ALL:
    {
      cyg_uint32 t;

      AT91_ETH_DEBUG1(true, "AT91_ETH: enable all multicast MACs\n");
      HAL_WRITE_UINT32(priv->base + AT91_EMAC_HRB, 0xFFFFFFFF);
      HAL_WRITE_UINT32(priv->base + AT91_EMAC_HRT, 0xFFFFFFFF);
      HAL_READ_UINT32(priv->base + AT91_EMAC_NCFG, t);
      t |= AT91_EMAC_NCFG_MTI;
      HAL_WRITE_UINT32(priv->base + AT91_EMAC_NCFG, t);

      return 0;
    }

    // Unknown or unhandled key
    default:
    {
      AT91_ETH_DEBUG1(true, "AT91_ETH: unknown IOCTL key %lx\n", key);
      return 1;
    }
  } // switch
}

// This function is called to see if another packet can be sent.
// It should return the number of packets which can be handled.
// Zero should be returned if the interface is busy and can not send
// any more.
//
// We allocate one buffer descriptor per scatter/gather entry. We start
// with the assumuption that a typical packet will not have more than
// three such entries, but we keep track of the largest number of entries
// ever used and compute the return value based on that.
static int
at91_eth_can_send(struct eth_drv_sc *sc)
{
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;

  if (priv->tx_busy)
    return 0;
  else
    return priv->tx_free / priv->tx_max_frame_sglen;
}


// This routine is called to send data to the hardware
static void
at91_eth_send(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len,
              int total_len, unsigned long key)
{
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
  int i;
  cyg_uint32 sr, head_idx;
  tbd_t *tbd;

  CYG_ASSERT(!priv->tx_busy, "send called when busy\n");

  // BUGBUG dkh - perhaps this needs to be a real check, not an assert
  CYG_ASSERT(priv->tx_free >= sg_len, "not enough free tx descriptors\n");
//  if (sg_len > priv->tx_free) {
//    (sc->funs->eth_drv->tx_done)(sc, key, 1);
//    return;
//  }

  // Keep track of the largest number of scatter/gather entries
  // ever used by a transmitted packet.
  if (sg_len > priv->tx_max_frame_sglen) {
    priv->tx_max_frame_sglen = sg_len;
    AT91_ETH_DEBUG1(true, "AT91_ETH: Tx sg_len increased to %d\n", sg_len);
  }

  // Disable transmit interrupts.
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_IDR, AT91_ETH_TX_INT_MASK);

  head_idx = priv->tx_head_idx;
  for(i = 0; i < sg_len; i++) {
    HAL_DCACHE_STORE(sg_list[i].buf, sg_list[i].len);
    HAL_MEMORY_BARRIER();
    sr = sg_list[i].len & AT91_EMAC_TBD_SR_LEN_MASK;

    // If this is the first buffer in the frame, then keep USED bit on for now.
    if (i == 0)
      sr |= AT91_EMAC_TBD_SR_USED;

    // If this is the last buffer in the frame, then set EOF bit.
    // Also save the eCos key so we can return status to eCos at tx completion.
    if (i == (sg_len - 1)) {
      sr |= AT91_EMAC_TBD_SR_EOF;
      priv->tx_keys[head_idx] = key;
    }

    // If this is the last buffer in the ring, then set the WRAP bit.
    if (head_idx == (CYGNUM_DEVS_ETH_ARM_AT91_TX_BUFS - 1))
      sr |= AT91_EMAC_TBD_SR_WRAP;

    // Write buffer address and status to EMAC descriptor.
    tbd = (tbd_t *)CYGARC_UNCACHED_ADDRESS(&priv->tbd[head_idx]);
    tbd->addr = sg_list[i].buf;
    tbd->sr = sr;
    AT91_ETH_NEXT_TBD(head_idx);
  }

  // Release the frame to the EMAC
  cyg_drv_dsr_lock();
  priv->tx_free -= sg_len;
  tbd = (tbd_t *)CYGARC_UNCACHED_ADDRESS(&priv->tbd[priv->tx_head_idx]);
  tbd->sr &= ~AT91_EMAC_TBD_SR_USED;
  priv->tx_head_idx = head_idx;
  cyg_drv_dsr_unlock();

  // Enable transmit interrupts and start transmit.
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_IER, AT91_ETH_TX_INT_MASK);
  at91_start_transmitter(priv);
}


//======================================================================

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
static cyg_uint32
at91_eth_isr (cyg_vector_t vector, cyg_addrword_t data)
{
  struct eth_drv_sc *sc = (struct eth_drv_sc *)data;
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;

  cyg_interrupt_mask(priv->int_vector);
  cyg_interrupt_acknowledge(priv->int_vector);
  return CYG_ISR_HANDLED | CYG_ISR_CALL_DSR;
}
#endif


/* Handle transmit-complete events. */
static void
at91_eth_tx(struct eth_drv_sc *sc)
{
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
  cyg_uint32 tsr, sr;
  cyg_uint32 processed_buffers;
  cyg_bool tx_err, is_sof;
  tbd_t *tbd;

  // Get the Transmit Status and clear it
  HAL_READ_UINT32(priv->base + AT91_EMAC_TSR, tsr);
  HAL_WRITE_UINT32(priv->base + AT91_EMAC_TSR, tsr);

  AT91_ETH_DEBUG1(true, "AT91_ETH: TxStat Reg = 0x%08x\n", tsr);
  AT91_ETH_DEBUG1(tsr & AT91_EMAC_TSR_OVR, "Tx_UBR ");
  AT91_ETH_DEBUG1(tsr & AT91_EMAC_TSR_COL, "Tx_COL ");
  AT91_ETH_DEBUG2((tsr & AT91_EMAC_TSR_TXIDLE) == 0, "Tx_IDLE ");
  AT91_ETH_DEBUG1(tsr & AT91_EMAC_TSR_RLE, "Tx_RLE ");
  AT91_ETH_DEBUG1(tsr & AT91_EMAC_TSR_BNQ, "Tx_BEX ");
  AT91_ETH_DEBUG1(tsr & AT91_EMAC_TSR_UND, "Tx_UND ");
  AT91_ETH_DEBUG1(tsr & AT91_EMAC_TSR_COMP, "Tx_COMP ");
  AT91_ETH_DEBUG1(true, "\n");

  // Only do work if the transmit-status register indicates work to do.
  if (tsr & (AT91_EMAC_TSR_COMP | AT91_EMAC_TSR_RLE |
             AT91_EMAC_TSR_BNQ | AT91_EMAC_TSR_UND)) {

    if (tsr & (AT91_EMAC_TSR_RLE | AT91_EMAC_TSR_BNQ | AT91_EMAC_TSR_UND))
      tx_err = true;
    else
      tx_err = false;

    priv->tx_busy = true;
    processed_buffers = 0;
    is_sof = true;

    /* For each possible outstanding descriptor. */
    while (priv->tx_tail_idx != priv->tx_head_idx) {
      tbd = (tbd_t *)CYGARC_UNCACHED_ADDRESS(&priv->tbd[priv->tx_tail_idx]);
      sr = tbd->sr;
      AT91_ETH_DEBUG2(true, "AT91_ETH: TX buffer status at %d = 0x%08x\n",
                      priv->tx_tail_idx, sr);

      // If this descriptor is the first in a frame, then check that
      // the EMAC has processed it. If so, record any errors.
      if (is_sof) {
        // If no previous errors were indicated and the EMAC hasn't
        // processed this descriptor yet, then we are caught up with it.
        // Don't look at any remaining descriptors until the EMAC
        // indicates completion.
        if (!tx_err && !(sr & AT91_EMAC_TBD_SR_USED))
          break;

        // Examine the error bits
        if (sr & (AT91_EMAC_TBD_SR_EXHAUST |
                  AT91_EMAC_TBD_SR_TXUNDER | AT91_EMAC_TBD_SR_RTRY)) {
          tx_err = true;
          AT91_ETH_DEBUG1(tx_err, "AT91_ETH: TX frame at %d has error\n",
                          priv->tx_tail_idx);
        }

        // Next buffer, if any, is not SOF
        is_sof = false;
      }

      // If this descriptor is the last in a frame, then notify
      // the TCP/IP stack about the status of the transmit.
      if (sr & AT91_EMAC_TBD_SR_EOF) {
        _eth_drv_tx_done(sc, priv->tx_keys[priv->tx_tail_idx], tx_err);

        // Exegin specific. Do transmit callback.
        if (!tx_err && at91_eth_tx_callback_func)
          (at91_eth_tx_callback_func)();

        // Next buffer, if any, is SOF.
        is_sof = true;
      }

      // Mark descriptor as used, preserving wrap-bit.
      sr &= AT91_EMAC_TBD_SR_WRAP;
      sr |= AT91_EMAC_TBD_SR_USED;
      tbd->sr = sr;
      processed_buffers++;
      AT91_ETH_NEXT_TBD(priv->tx_tail_idx);
    } /* while */

    cyg_drv_dsr_lock();
    priv->tx_free += processed_buffers;

    // If error occured that caused EMAC to reset the TX hardware, then
    // we need to follow the TX hardware pointer.
    if (tx_err) {
      AT91_ETH_DEBUG1(true, "AT91_ETH: TX error - reset TX buffer indexes\n");
      at91_disable_tx(priv);
      at91_enable_tx(priv);
      at91_start_transmitter(priv);
    }
    cyg_drv_dsr_unlock();

    priv->tx_busy = false;
  } // endif transmit-status register indicates work to do
}


/* Return RX descriptors to EMAC, up to but not including, current head */
static void at91_return_rx_bds(at91_eth_priv_t *priv, int rbd_idx)
{
  rbd_t *rbd;

  while (rbd_idx != priv->rx_head_idx) {
    rbd = (rbd_t *)CYGARC_UNCACHED_ADDRESS(&priv->rbd[rbd_idx]);
    rbd->addr &= ~AT91_EMAC_RBD_ADDR_OWNER_SW;
    /* WRITE MEMORY BARRIER */
    HAL_MEMORY_BARRIER();
    AT91_ETH_NEXT_RBD(rbd_idx);
  }
}


/* Handle receive-ready events. */
static void
at91_eth_rx(struct eth_drv_sc *sc)
{
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
  cyg_int32 sof_idx = -1;
  cyg_uint32 rsr, sr;
  rbd_t *rbd;

  // Get the Receive Status and clear it
  HAL_READ_UINT32(priv->base+AT91_EMAC_RSR, rsr);
  HAL_WRITE_UINT32(priv->base+AT91_EMAC_RSR, rsr);

  AT91_ETH_DEBUG1(true, "AT91_ETH: RxStat Reg = 0x%08x\n", rsr);
  AT91_ETH_DEBUG1(rsr & AT91_EMAC_RSR_BNA, "Rx_BNA ");
  AT91_ETH_DEBUG1(rsr & AT91_EMAC_RSR_OVR, "Rx_OVR ");
  AT91_ETH_DEBUG1(rsr & AT91_EMAC_RSR_REC, "Rx_REC ");
  AT91_ETH_DEBUG1(true, "\n");

  /* Service the RX buffers if required */
  if (rsr & AT91_EMAC_RSR_REC) {
    rbd = (rbd_t *)CYGARC_UNCACHED_ADDRESS(&priv->rbd[priv->rx_head_idx]);
    // For all descriptors that the EMAC has given ownership to us.
    while (rbd->addr & AT91_EMAC_RBD_ADDR_OWNER_SW) {
      /* READ MEMORY BARRIER */
      /* HAL_MEMORY_BARRIER(); */
      sr = rbd->sr;

      if (sr & AT91_EMAC_RBD_SR_SOF) {
        if (sof_idx != -1) {
          AT91_ETH_DEBUG2(true, "AT91_ETH: RX discarding fragments\n");
          at91_return_rx_bds(priv, sof_idx);
        }
        sof_idx = priv->rx_head_idx;
      }

      // Advance head ptr here, not at end of while_loop.
      AT91_ETH_NEXT_RBD(priv->rx_head_idx);
      rbd = (rbd_t *)CYGARC_UNCACHED_ADDRESS(&priv->rbd[priv->rx_head_idx]);

      if (sr & AT91_EMAC_RBD_SR_EOF) {
        // This is assumed to be unrecoverable hardware failure.
        CYG_ASSERT(sof_idx != -1, "receiver found EOF but no SOF\n");

        // Exegin specific. Do receive callback.
        if (at91_eth_rx_callback_func)
          (at91_eth_rx_callback_func)();

        priv->rx_tail_idx = sof_idx;
        priv->rx_frame_cnt = sr & AT91_EMAC_RBD_SR_LEN_MASK;
        _eth_drv_recv(sc, priv->rx_frame_cnt);
        at91_return_rx_bds(priv, sof_idx);
        sof_idx = -1;
      }
    }

    // If we found SOF but didn't find matching EOF.
    if (sof_idx != -1) {
      AT91_ETH_DEBUG2(true, "AT91_ETH: RX discarding fragments\n");
      priv->rx_head_idx = sof_idx;
    }
  }
}


/* Called by the TCP/IP stack when it wants us to handle hardware events. */
static void
at91_eth_deliver(struct eth_drv_sc *sc)
{
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
  cyg_uint32 isr, imr;

  do {
    // Get interrupt status. This clears the register.
    HAL_READ_UINT32(priv->base + AT91_EMAC_ISR, isr);
    AT91_ETH_DEBUG2(true, "AT91_ETH: IrqStat Reg = 0x%08x\n", isr);

    // This is assumed to be unrecoverable hardware failure.
    // Unless caused by slow clock?
    CYG_ASSERT(!(isr & AT91_EMAC_ISR_HRESP), "EMAC DMA/bus failed\n");

    // Get interrupt mask.
    HAL_READ_UINT32(priv->base + AT91_EMAC_IMR, imr);

    isr = isr & ~imr & AT91_ETH_INT_MASK;

    if (isr & AT91_ETH_RX_INT_MASK)
      at91_eth_rx(sc);

    if (isr & AT91_ETH_TX_INT_MASK)
      at91_eth_tx(sc);
  } while (isr);

#ifdef CYGINT_IO_ETH_INT_SUPPORT_REQUIRED
  cyg_interrupt_unmask(priv->int_vector);
#endif
}


/* Called by the TCP/IP stack when it wants us to unload receive buffers. */
static void
at91_eth_recv(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len)
{
  at91_eth_priv_t *priv = (at91_eth_priv_t *)sc->driver_private;
  cyg_uint32 rb_len, rb_offset;
  cyg_uint32 sg_idx, sg_offset;
  cyg_uint32 copy_cnt;

  // If the TCP/IP stack is broken or out of buffers.
  CYG_ASSERT(sg_list != NULL, "TCP/IP stack error or out of buffers\n");

  // Init working vars
  sg_idx = sg_offset = rb_offset = 0;

  // Get count of bytes in current buffer.
  rb_len = MIN(priv->rx_frame_cnt, AT91_EMAC_RX_BUFF_SIZE);
  HAL_DCACHE_INVALIDATE(priv->rb[priv->rx_tail_idx].rb, rb_len);

  // For all scatter-gather buffers that we need to fill.
  while (sg_idx < sg_len) {
    copy_cnt = MIN(sg_list[sg_idx].len - sg_offset, rb_len - rb_offset);

    if (copy_cnt && sg_list[sg_idx].buf)
      memcpy(((cyg_uint8 *)sg_list[sg_idx].buf) + sg_offset,
             &priv->rb[priv->rx_tail_idx].rb[rb_offset],
             copy_cnt);

    // Update number of bytes remaining in frame, exit if complete.
    priv->rx_frame_cnt -= copy_cnt;
    if (!priv->rx_frame_cnt)
      break;

    // If sg buffer is completely filled, move to next
    sg_offset += copy_cnt;
    if (sg_offset == sg_list[sg_idx].len) {
      sg_idx++;
      sg_offset = 0;
    }

    // If rx buffer is completely drained, move to next
    rb_offset += copy_cnt;
    if (rb_offset == rb_len) {
      AT91_ETH_NEXT_RBD(priv->rx_tail_idx);
      rb_offset = 0;
      // Get count of bytes in this buffer
      rb_len = MIN(priv->rx_frame_cnt, AT91_EMAC_RX_BUFF_SIZE);
      HAL_DCACHE_INVALIDATE(priv->rb[priv->rx_tail_idx].rb, rb_len);
    }
  }
}


// routine called to handle ethernet controller in polled mode
static void
at91_eth_poll(struct eth_drv_sc *sc)
{
  /* Service the buffers */
  at91_eth_deliver(sc);
}


static int
at91_eth_int_vector(struct eth_drv_sc *sc)
{
  return(CYGNUM_HAL_INTERRUPT_EMAC);
}

at91_eth_priv_t at91_priv_data = {
  .int_vector = CYGNUM_HAL_INTERRUPT_EMAC,
  .base = AT91_EMAC,
  .phy = &at91_phy
};

ETH_DRV_SC(at91_sc,
           &at91_priv_data,       // Driver specific data
           "eth0",                // Name for this interface
           at91_eth_start,
           at91_eth_stop,
           at91_eth_control,
           at91_eth_can_send,
           at91_eth_send,
           at91_eth_recv,
           at91_eth_deliver,
           at91_eth_poll,
           at91_eth_int_vector);

NETDEVTAB_ENTRY(at91_netdev,
                "at91",
                at91_eth_init,
                &at91_sc);

// EOF if_at91.c


-- 
Before posting, please read the FAQ: http://ecos.sourceware.org/fom/ecos
and search the list archive: http://ecos.sourceware.org/ml/ecos-discuss

Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]