//==========================================================================
//
//      if_stm32-lwip.c
//
//      lwIP-specific Ethernet driver for ST STM32 family on-chip EMAC.
//
//==========================================================================
// ####ECOSGPLCOPYRIGHTBEGIN####                                            
// -------------------------------------------                              
// This file is part of eCos, the Embedded Configurable Operating System.   
// Copyright (C) 2007, 2008, 2009 eCosCentric Limited                             
//
// eCos is free software; you can redistribute it and/or modify it under    
// the terms of the GNU General Public License as published by the Free     
// Software Foundation; either version 2 or (at your option) any later      
// version.                                                                 
//
// eCos is distributed in the hope that it will be useful, but WITHOUT      
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or    
// FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License    
// for more details.                                                        
//
// You should have received a copy of the GNU General Public License        
// along with eCos; if not, write to the Free Software Foundation, Inc.,    
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.            
//
// As a special exception, if other files instantiate templates or use      
// macros or inline functions from this file, or you compile this file      
// and link it with other works to produce a work based on this file,       
// this file does not by itself cause the resulting work to be covered by   
// the GNU General Public License. However the source code for this file    
// must still be made available in accordance with section (3) of the GNU   
// General Public License v2.                                               
//
// This exception does not invalidate any other reasons why a work based    
// on this file might be covered by the GNU General Public License.         
// -------------------------------------------                              
// ####ECOSGPLCOPYRIGHTEND####                                              
//==========================================================================
//#####DESCRIPTIONBEGIN####
//
// Author(s):    nickg
// Contributors:
// Date:         2009-06-08
// Description:  hardware driver for STM32 ethernet devices specifically
//               and solely for lwIP TCP/IP stack.
//
//####DESCRIPTIONEND####
//==========================================================================

#include <pkgconf/system.h>
#include <pkgconf/hal.h>
#include <pkgconf/devs_eth_cortexm_stm32.h>
#include <pkgconf/io_eth_drivers.h>

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

#include <pkgconf/net_lwip.h>
#include "lwip/opt.h"
#include "lwip/err.h"
#include "lwip/netif.h"
#include "lwip/pbuf.h"

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

#ifdef CYGACC_CALL_IF_DELAY_US
# define STM32_ETH_DELAY_US(us) CYGACC_CALL_IF_DELAY_US(us)
#else
# include <cyg/hal/hal_diag.h>
# define STM32_ETH_DELAY_US(us) HAL_DELAY_US(us)
#endif

#ifndef CYGARC_PHYSICAL_ADDRESS
#define CYGARC_PHYSICAL_ADDRESS(__x) (__x)
#endif

#ifndef CYGARC_UNCACHED_ADDRESS
#define CYGARC_UNCACHED_ADDRESS(__x) (__x)
#endif

#if defined(HAL_DCACHE_LINE_SIZE) && (HAL_DCACHE_LINE_SIZE > 4)
# define BD_ALIGNMENT (HAL_DCACHE_LINE_SIZE)
#else
# define BD_ALIGNMENT (4)
#endif

//==========================================================================
// Debug support

#if 0
#define eth_diag( __fmt, ... ) diag_printf("STM32_ETH: %30s[%4d]: " __fmt "\n", __FUNCTION__, __LINE__, ## __VA_ARGS__ )
#define eth_dump_buf( __buf, __len ) diag_dump_buf( __buf, __len )
#else
#define eth_diag( __fmt, ... ) 
#define eth_dump_buf( __buf, __len ) 
#endif

//#define phy_debug_printf eth_diag
#define phy_debug_printf( __fmt, ... )

//======================================================================
// Include common stuff after debug macro defs. This includes PHY
// access code and other routines that are common between all driver
// types.

#include "stm32_common.h"

//======================================================================
// Private device structure

typedef struct stm32_eth
{
    stm32_eth_common    common;         // Common data
    
    // LWIP interfacing

    struct netif        netif;          // LwIP structure
    
    // Transmit handling

    struct pbuf         *tx_pbuf;       // Current tx PBUF
    volatile cyg_uint8  tx_can_send;    // Transmitter free
    volatile cyg_uint8  tx_index;       // Next TXBD to use
    
    volatile buffer_descriptor  txbds[CYGNUM_DEVS_ETH_CORTEXM_STM32_TX_BUFS] CYGBLD_ATTRIB_ALIGN(BD_ALIGNMENT);
    
    // Receive handling

    volatile buffer_descriptor  rxbds[CYGNUM_DEVS_ETH_CORTEXM_STM32_RX_BUFS] CYGBLD_ATTRIB_ALIGN(BD_ALIGNMENT);

    cyg_uint8           rx_ran_out_of_buffers;  // need to replenish descriptor ring
    cyg_uint8           rx_next_buffer;         // next buffer to process
    
    // Shared data

    cyg_uint8           mac[6];                 // MAC address
    cyg_uint8           started;                // driver started?
    
    cyg_handle_t        interrupt_handle;
    cyg_interrupt       interrupt_data;
    
} stm32_eth;

//==========================================================================
// Instantiate the current single MAC device.

static stm32_eth stm32_eth_info =
{
    .common.base        = CYGHWR_HAL_STM32_ETH,
    .common.vector      = CYGNUM_HAL_INTERRUPT_ETH,
    .common.phy         = &stm32_phy,
};

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

#define stm32_eth_show_txbds( __eth ) stm32_eth_show_bds( eth->txbds, CYGNUM_DEVS_ETH_CORTEXM_STM32_TX_BUFS, "TXBDs");
#define stm32_eth_show_rxbds( __eth ) stm32_eth_show_bds( eth->rxbds, CYGNUM_DEVS_ETH_CORTEXM_STM32_RX_BUFS, "RXBDs");

//==========================================================================
// Define a type for pool used for RX packet data pbufs.
//
// Unhelpfully lwIP requires the payload to directly follow the pbuf.
// This has consequences for CPUs with caches as we can't share the
// struct pbuf with the payload of any *preceding* packet. This means
// we waste HAL_DCACHE_LINE_SIZE - sizeof(struct pbuf) bytes :(.
// Fixing this requirement in lwIP would require some major surgery
// that is best done upstream, and has been discussed there so may
// resolved in due course.  BD_ALIGNMENT is also suitable to use as
// the alignment here.

struct stm32_pbuf_pool_and_payload {
    struct pbuf pbuf;
    char payload[CYGNUM_LWIP_PBUF_POOL_BUFSIZE];
} CYGBLD_ATTRIB_ALIGN(BD_ALIGNMENT);

struct stm32_pbuf_pool_and_payload stm32_pbuf_pool[CYGNUM_LWIP_PBUF_POOL_SIZE] CYGBLD_ATTRIB_ALIGN(BD_ALIGNMENT);

#define NEXT_RXBD(current) (((current) == (CYGNUM_DEVS_ETH_CORTEXM_STM32_RX_BUFS - 1)) ? 0 : ((current) + 1))
#define PREV_RXBD(current) (((current) == 0) ? (CYGNUM_DEVS_ETH_CORTEXM_STM32_RX_BUFS - 1) : ((current) - 1))
#define NEXT_TXBD(current) (((current) == (CYGNUM_DEVS_ETH_CORTEXM_STM32_TX_BUFS - 1)) ? 0 : ((current) + 1))
#define PREV_TXBD(current) (((current) == 0) ? (CYGNUM_DEVS_ETH_CORTEXM_STM32_TX_BUFS - 1) : ((current) - 1))

#define RXBD_BUFFER_ADDR(rxbd) ((cyg_uint8*)((rxbd).buffer1))

//==========================================================================
// Forward definitions

static cyg_uint32 stm32_eth_isr(cyg_vector_t vector, cyg_addrword_t data);
static void       stm32_eth_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data);
static void       stm32_eth_deliver_tx(stm32_eth *eth, cyg_bool check_sr);

//==========================================================================
/* lwIP PBUF_POOL creation override */

__externC struct pbuf *
cyg_stm32_eth_lwip_pool_init(void)
{
    struct pbuf *p;
    cyg_ucount32 i;

    for (i=0; i<CYGNUM_LWIP_PBUF_POOL_SIZE; i++)
    {
        p = &stm32_pbuf_pool[i].pbuf;
        p->payload = stm32_pbuf_pool[i].payload;
        p->next = &stm32_pbuf_pool[i+1].pbuf;
        p->len = p->tot_len = CYGNUM_LWIP_PBUF_POOL_BUFSIZE;
        p->flags = PBUF_FLAG_POOL;
    }

    // Last pbuf's next field in fact gets set to NULL to denote end.
    p->next = NULL;

    return &stm32_pbuf_pool[0].pbuf;
}

//==========================================================================
/* Hook when freeing a pool pbuf. */
/* This is called inside a SYS_ARCH_PROTECT, so we should be m-t safe */

__externC void
cyg_stm32_eth_lwip_pool_free_hook( struct pbuf *p )
{
    struct  stm32_eth*    eth = &stm32_eth_info;
    cyg_uint8 i, this_rxbd;
    struct pbuf *new_p;

    CYG_UNUSED_PARAM(struct pbuf *, p);

    eth_diag( "Freeing pbuf @ 0x%08x, with payload 0x%08x", (unsigned)p, (unsigned)p->payload);

    if (eth->rx_ran_out_of_buffers)
    {
        // Look through the complete list of rxbds for buffers that need refilling. Start
        // from the next rx buffer, as we need to prioritise rxbds that will be reused
        // sooner.
        for (i=CYGNUM_DEVS_ETH_CORTEXM_STM32_RX_BUFS, this_rxbd=eth->rx_next_buffer; i > 0; i--)
        {
            // In the deliver function, we set the buffer field to
            // NULL if the previous pool alloc failed, so that's what to look for.
            if (RXBD_BUFFER_ADDR(eth->rxbds[this_rxbd]) == NULL)
            {
                new_p = pbuf_alloc(PBUF_RAW, CYGNUM_LWIP_PBUF_POOL_BUFSIZE, PBUF_POOL);
                if (!new_p)
                {
                    eth_diag( "No pool pbufs (yet) to refill rxbd[%d]", this_rxbd);
                    // Nothing more we can do with this one or any others.
                    // rx_ran_out_of_buffers will remain set.
                    break;
                }
                eth_diag( "Refilled rxbd[%d] with pbuf @ 0x%08x (payload 0x%08x)", this_rxbd,
                     (unsigned)new_p, new_p ? (unsigned)(new_p->payload) : 0);
                eth->rxbds[this_rxbd].ctrl = CYGHWR_HAL_STM32_ETH_RDES0_OWN;
                eth->rxbds[this_rxbd].bufsize = CYGHWR_HAL_STM32_ETH_RDES1_RBS1(CYGNUM_LWIP_PBUF_POOL_BUFSIZE);
                eth->rxbds[this_rxbd].buffer1 = (cyg_uint32)CYGARC_PHYSICAL_ADDRESS(new_p->payload);
            }
            this_rxbd = NEXT_RXBD(this_rxbd);
        }

        if (0 == i)
        {
            // so we did scan through all the rxbds and successfully allocate any empty buffers. Good!
            eth->rx_ran_out_of_buffers = 0;
        }
    }
}


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

__externC void cyg_lwip_eth_ecos_init(void)
{
    stm32_eth           *eth = &stm32_eth_info;
    cyg_uint32          base = eth->common.base;
    err_t               err;
    char                do_dhcp;
    int                 i;

    eth_diag("base %08x vector %d", eth->common.base, eth->common.vector);
    
    stm32_eth_cfg( &eth->common );

    stm32_eth_init( &eth->common );

    // Create and attach interrupt
    cyg_drv_interrupt_create(eth->common.vector,
                             CYGNUM_DEVS_ETH_CORTEXM_STM32_INTRPRI,
                             (CYG_ADDRWORD) eth,
                             stm32_eth_isr,
                             stm32_eth_dsr,
                             &eth->interrupt_handle,
                             &eth->interrupt_data);
    cyg_drv_interrupt_attach(eth->interrupt_handle);
    cyg_drv_interrupt_unmask(eth->common.vector);


    // Fetch and set MAC address
    
    stm32_eth_get_config_mac_address(eth->mac);

    stm32_eth_set_mac( &eth->common, eth->mac );

    
    // Initialize TX buffer descriptors.

    for( i = 0; i < CYGNUM_DEVS_ETH_CORTEXM_STM32_TX_BUFS; i++ )
    {
        eth->txbds[i].ctrl = 0;
    }
    eth->txbds[i-1].ctrl = CYGHWR_HAL_STM32_ETH_TDES0_TER;    
    HAL_WRITE_UINT32( base+CYGHWR_HAL_STM32_ETH_DMATDLAR, (cyg_uint32)CYGARC_PHYSICAL_ADDRESS(&eth->txbds[0]) );

    
    // Initialize RX buffer descriptors

    for( i = 0; i < CYGNUM_DEVS_ETH_CORTEXM_STM32_RX_BUFS; i++ )
    {
        struct pbuf *p = pbuf_alloc(PBUF_RAW, CYGNUM_LWIP_PBUF_POOL_BUFSIZE, PBUF_POOL);

        if( p == 0 )
        {
            eth_diag("Failed to allocate initial rx pool pbufs");
            return;
        }
        eth->rxbds[i].ctrl = CYGHWR_HAL_STM32_ETH_RDES0_OWN;
        eth->rxbds[i].bufsize = CYGHWR_HAL_STM32_ETH_RDES1_RBS1(CYGNUM_LWIP_PBUF_POOL_BUFSIZE);
        eth->rxbds[i].buffer1 =(cyg_uint32)CYGARC_PHYSICAL_ADDRESS(p->payload);        
    }
    eth->rxbds[i-1].bufsize |= CYGHWR_HAL_STM32_ETH_RDES1_RER;
    HAL_WRITE_UINT32( base+CYGHWR_HAL_STM32_ETH_DMARDLAR, (cyg_uint32)CYGARC_PHYSICAL_ADDRESS(&eth->rxbds[0]) );

    
    // Set up PHY

    if( !stm32_eth_phy_init( &eth->common ) )
    {
        eth_diag("Phy initialization failed");
        return;
    }

    eth->tx_can_send = 1;

    // Initialize upper level driver

    err = cyg_lwip_eth_drv_init_netif((void *)eth, eth->mac, &eth->netif, &do_dhcp);

    if (ERR_OK == err)
    {
        // Enable rx/tx operation.
        stm32_eth_enable(&eth->common);
        eth->started = 1;

#ifdef CYGFUN_LWIP_DHCP
        //
        // we call this after the driver was started successfully
        //
        if (do_dhcp)
            cyg_lwip_dhcp_init(&eth->netif);
#endif
    }

    eth_diag("Done");
    
    return;
}

//==========================================================================
// The RX interrupt triggers whenever a whole frame has been received (or
// when an error has occurred). The hardware may be busy receiving into the
// next buffer.
//
// Because of the ring buffer several packets may arrive before the cpu gets
// around to processing them. It is possible to save a little bit of load
// by masking the interrupt inside the ethernet device, then doing the
// unmask inside the deliver() code.
//
// The TX interrupt triggers when a whole frame has been sent. Since at present
// we only send one frame at a time, the TX hardware will be idle.

static cyg_uint32
stm32_eth_isr(cyg_vector_t vector, cyg_addrword_t data)
{
    stm32_eth *eth = (stm32_eth *)data;

//    eth_diag("vector %d", vector);

    if(0) {
        cyg_uint32 sr;
        HAL_READ_UINT32( eth->common.base+CYGHWR_HAL_STM32_ETH_DMASR, sr );
        eth_diag("sr %08x", sr );
    }

    cyg_drv_interrupt_mask(eth->common.vector);

    // Ack now, to prevent issues with other interrupts.
    cyg_drv_interrupt_acknowledge(eth->common.vector);

    return (CYG_ISR_HANDLED|CYG_ISR_CALL_DSR);  // Run the DSR
}

static void
stm32_eth_dsr(cyg_vector_t vector, cyg_ucount32 count, cyg_addrword_t data)
{
    // There is no need to worry about cleaning up the buffer descriptors.
    // The hardware will have set the ownership bit, which is the only one
    // which needs handling.
//    eth_diag("vector %d", vector);    

    cyg_lwip_eth_dsr();
}

//==========================================================================
// The driver only supports one transmit at a time, and a global is used
// to keep track of whether or not a transmit is in progress.

static __inline__ int
stm32_eth_can_send(struct stm32_eth* eth)
{
    return eth->tx_can_send;
}

//==========================================================================
//
// cyg_lwip_eth_low_level_output():
//
// This is the function called by higher layers which should do the actual
// transmission of the packet. The packet is contained in the pbuf that is
// passed to the function. This pbuf might be chained.


__externC err_t
cyg_lwip_eth_low_level_output(struct netif *netif, struct pbuf *p)
{
    stm32_eth *eth = (stm32_eth *)netif->state;
    cyg_uint32 index;
    cyg_uint32 last_index;
    struct pbuf *q;

    eth_diag("pbuf %08x", p );
    
    // Sanity check p. Don't waste code returning on an error like this.
    // Just don't screw up in the first place!
    if (!p)
    {
        eth_diag("passed NULL pbuf");
        CYG_FAIL("cyg_lwip_eth_low_level_output passed NULL pbuf");
    }

    if (!p->len || !p->tot_len)
    {
        eth_diag("passed empty pbuf");
        CYG_FAIL("cyg_lwip_eth_low_level_output passed empty pbuf");
    }

    if (!eth->started)
        return ERR_IF;

    // Can we send?
    if (stm32_eth_can_send(eth) <= 0)
    {
        eth_diag("could not send");

        // If we can't send, we need to call the delivery function to make
        // sure that the reason we can't send isn't just because we are
        // either higher priority than the normal thread doing delivery,
        // and thus preventing it delivering; or that we _are_ the
        // delivery thread, in which case we would never see it complete
        // even if it had, because clearly this thread has been "doing
        // stuff" if it has stuff to send.
        stm32_eth_deliver_tx(eth, true);

#ifdef CYGIMP_IO_ETH_DRIVERS_LWIP_TX_FULL_WAIT
        while (stm32_eth_can_send(eth) <= 0)
        {
            // As above, call the delivery function each time. We may
            // be the delivery thread anyway, so if we didn't, looping here
            // and not calling it would otherwise cause everything to freeze.
            stm32_eth_deliver_tx(eth, true);

# if (CYGNUM_IO_ETH_DRIVERS_LWIP_TX_FULL_WAIT_DELAY > 0)
            // Give others a chance to run
            STM32_ETH_DELAY_US(CYGNUM_IO_ETH_DRIVERS_LWIP_TX_FULL_WAIT_DELAY);
# endif
        }
#else // !CYGIMP_IO_ETH_DRIVERS_LWIP_TX_FULL_WAIT
        /* Otherwise if we still can't send, just drop the packet completely. 
         * We claim success because there isn't necessarily really an error as such.
         * We may just be generating data too fast.
         */
        if (stm32_eth_can_send(eth) <= 0) {
            eth_diag("Couldn't send packet. Dropping");
            return ERR_OK;
        }
#endif
        eth_diag("Can now send");
    }

    cyg_uint32 pbuf_chain_length=1;
    
    // Quickly need to check the pbuf chain is not too long for us.
    // We could probably do this at the same time as the loop where
    // we fill in the txbds below, but to be honest, the chains should
    // usually be short enough (typically not a chain at all!) that 
    // this is faster.
    for (q=p->next; q; q=q->next)
        if (q->len)
            pbuf_chain_length++;

    if (pbuf_chain_length > (CYGNUM_DEVS_ETH_CORTEXM_STM32_TX_BUFS))
        return ERR_IF;

    eth->tx_can_send    = 0;
    /* In the following line, last_index doesn't really need set, but it
     * silences a compiler warning.
     */
    last_index = index = eth->tx_index;

    // incr ref count of pbuf, so that caller's free will not really free up
    // memory. At least not until packet is definitely sent.
    pbuf_ref(p);
    eth->tx_pbuf = p;

    cyg_uint32 ctrl = CYGHWR_HAL_STM32_ETH_TDES0_OWN|CYGHWR_HAL_STM32_ETH_TDES0_FS;
    for (q=p; q; q=q->next)
    {
        // Don't waste a txbd on empty chain entries
        if (q->len)
        {
            eth_diag("pbuf %08x payload 0x%08x len %d", (cyg_uint32)q, q->payload, q->len );
//            eth_dump_buf( q->payload, q->len );
            
            eth->txbds[index].buffer1 = (cyg_uint32)CYGARC_PHYSICAL_ADDRESS(q->payload);
            eth->txbds[index].bufsize = CYGHWR_HAL_STM32_ETH_TDES1_TBS1(q->len);
            eth->txbds[index].ctrl &= CYGHWR_HAL_STM32_ETH_TDES0_TER; // Clear all except TER bits
            eth->txbds[index].ctrl |= ctrl;                             // OR in control bits
            ctrl &= ~CYGHWR_HAL_STM32_ETH_TDES0_FS;
                
            last_index = index;
            index = NEXT_TXBD(index);
        }
    }

    // Set own, interrupt and last bits in last descriptor of frame
    eth->txbds[last_index].ctrl |= CYGHWR_HAL_STM32_ETH_TDES0_IC  |
                                   CYGHWR_HAL_STM32_ETH_TDES0_LS  ;

    eth->tx_index = index;

    // Ensure writeback cache data is written to physical memory
#ifdef HAL_DCACHE_SYNC
    HAL_DCACHE_SYNC();
#endif

    stm32_eth_tx_start( &eth->common );
    
    return ERR_OK;
}


//==========================================================================
// Perform deliveries on the transmit side. Called in delivery thread
// context.

static void
stm32_eth_deliver_tx(stm32_eth *eth, cyg_bool check_sr)
{
    if( check_sr )
    {
        cyg_uint32 sr;
        cyg_uint32 base = eth->common.base;
        
        HAL_READ_UINT32( base+CYGHWR_HAL_STM32_ETH_DMASR, sr );

//        eth_diag("sr %08x", sr);
        
        if( sr & CYGHWR_HAL_STM32_ETH_DMASR_TS )
        {
            // End of frame transmission
            HAL_WRITE_UINT32( base+CYGHWR_HAL_STM32_ETH_DMASR,
                              CYGHWR_HAL_STM32_ETH_DMASR_TS);
        }
        else
            return;
    }
    
    eth_diag("tx done");

    // Only need to free if the original pbuf was used and therefore pbuf_ref()d.
    if (eth->tx_pbuf)
    {
        stm32_eth_tx_stop( &eth->common );        
        pbuf_free(eth->tx_pbuf);
        eth->tx_pbuf = NULL;
        eth->tx_can_send = 1;
    }
    else
    {
#ifdef CYGPKG_INFRA_DEBUG
        // This is an important thing to catch, but could arise for all sorts
        // of reasons.
        diag_printf("STM32 eth: delivered tx but no tx_pbuf?!");
#endif
    }
}

//==========================================================================
// Perform deliveries on the RX side. Called in delivery thread context.


static void
stm32_eth_deliver_rx( stm32_eth *eth )
{
    int cache_state;
    int current_rxbd;
    int start_of_frame = -1;
    cyg_uint32 ctrl;
    cyg_uint32 owner_mac;


    eth_diag("rx_rdy");
    
    // Invalidate the cache's view of the rx buffer data space as this will
    // have changed. The rx buffer descriptors will be ok as they are accessed
    // via uncached space, and are in their own cache lines; but we have to
    // invalidate everything anyway. Since we invalidate, we need to sync dirty
    // cache lines to memory first.
    HAL_DCACHE_IS_ENABLED(cache_state);
        
    if (cache_state) {
        cyg_drv_isr_lock(); // Need ISRs off during cache sync/invalidate
        HAL_DCACHE_SYNC();
        HAL_DCACHE_INVALIDATE_ALL();
        cyg_drv_isr_unlock();
    }

    // Loop while there are non-empty packets. This has to be done with a bit
    // of care. Packets should be processed in the right order where possible,
    // which means keeping track of the last packet received.
    current_rxbd = eth->rx_next_buffer;
    for (;;)
    {
        
        // We work on the assumption that the driver and the hardware agree
        // which rxbd is being filled next. Things will go wrong if this isn't true.
        // So we always assume the current rxbd should be the start, and you keep
        // going till you reached the End-Of-Frame.
        //
        // The exception is for error frames, where there can be starts of frames
        // but no end. This would be indicated by reaching either a new start of
        // frame or an empty rxbd. (An empty rxbd means it is pending rx and thus
        // marked as owned by EMAC, not software).
        // We also count rxbds with NULL buffer as empty, as those are buffers where
        // we weren't able to reallocate a pbuf to replace the payload. The MAC
        // will halt (and would report BNA if we let it) if one is reached.

        ctrl = eth->rxbds[current_rxbd].ctrl;
        owner_mac = ctrl & CYGHWR_HAL_STM32_ETH_RDES0_OWN;

        if ( owner_mac )
        {
            if ( start_of_frame >= 0 )
            {
                // We had found a start of frame. There are two possible reasons
                // we could find an empty rxbd now:
                // 1) The frame is in transit, and the EMAC is still filling the buffers.
                // 2) It's an error frame and will never complete.
                // We would purge it if we could be sure about 2, but it might be 1.
                // So we leave it for now. The next RX interrupt on frame completion
                // will cause us to notice.

                eth_diag("Had found SOF @ rxbd[%d], but now found empty rxbd[%d] - in transit?",
                     start_of_frame, current_rxbd);
                // leave eth->rx_next_buffer untouched
            }
            return;
        } // if

        // Otherwise, this is owned by software

        // If payload is NULL, then the hardware will also have
        // stopped here, We have a go allocating a pbuf again, just in
        // case.  Any partially received frame is toast for definite.
        
        if ( NULL == RXBD_BUFFER_ADDR(eth->rxbds[current_rxbd]) )
        {
            struct pbuf *newp;

            // Return any packet fragments that had started to be part of a frame to the hardware.
            if (start_of_frame >= 0)
            {
                for ( ; start_of_frame != current_rxbd; start_of_frame = NEXT_RXBD(start_of_frame) )
                {
                    eth->rxbds[start_of_frame].ctrl = CYGHWR_HAL_STM32_ETH_RDES0_OWN;
                }
            }

            newp = pbuf_alloc(PBUF_RAW, CYGNUM_LWIP_PBUF_POOL_BUFSIZE, PBUF_POOL);

            eth_diag("After hitting sw-owned NULL packet at rxbd[%d], attempting refill with pbuf @ 0x%08x (payload 0x%08x)",
                 current_rxbd, (unsigned)newp, newp ? (unsigned)(newp->payload) : 0);

            if (newp)
            {
                // we can only really replace it if we succeeded of course.
                eth->rxbds[current_rxbd].buffer1 = (cyg_uint32)CYGARC_PHYSICAL_ADDRESS(newp->payload);
                eth->rxbds[current_rxbd].ctrl = CYGHWR_HAL_STM32_ETH_RDES0_OWN;
                stm32_eth_rx_start( &eth->common ); // prod hardware to restart
            }

            eth->rx_next_buffer = current_rxbd; // hardware will start from here next time
            return;
        }

        if ( ctrl & CYGHWR_HAL_STM32_ETH_RDES0_FS )
        {
            // start of a new frame
            eth_diag("Found SOF at rxbd[%d] (old SOF==rxbd[%d])", current_rxbd, start_of_frame);
            if ( start_of_frame >= 0 )
            {
                // Erk. We already found a start of frame. That must mean the old one
                // is bogus. Probably a bad packet.
                // We need to kill those packet fragments. This is easy - just set the
                // rxbd as owned by the EMAC again.

                for ( ; start_of_frame != current_rxbd; start_of_frame = NEXT_RXBD(start_of_frame) )
                {
                    eth->rxbds[start_of_frame].ctrl = CYGHWR_HAL_STM32_ETH_RDES0_OWN;
                }
            }
            else
                start_of_frame = current_rxbd;
            eth->rx_next_buffer = start_of_frame; // new baseline
        } // if


        // End of frame. A whole valid packet.
        if ( ctrl & CYGHWR_HAL_STM32_ETH_RDES0_LS )
        {
            struct pbuf *p, *first_pbuf, *newp;
            int len = CYGHWR_HAL_STM32_ETH_RDES0_FL(ctrl);

            // Found end of frame. Might be same slot as SOF, and that's fine.
            // But if we didn't find the start....
            if ( start_of_frame < 0 )
            {
                // Oh dear. We found EOF, but no SOF. Something's gone wrong.
                eth_diag("Eek! EOF at rxbd[%d] but no SOF!", current_rxbd);
                CYG_FAIL("RXBD EOF but no SOF");
            }

            eth_diag("Detected whole frame. SOF @ rxbd[%d], EOF @ rxbd[%d], len %d",
                 start_of_frame, current_rxbd, len);

            // rewind rxbd to start of frame. We don't mind since we know we've got a consistent frame.
            current_rxbd = start_of_frame;

            // The pbuf structure always precedes the pbuf payload, from the way we set the pool up.
            p = first_pbuf = (struct pbuf *)(RXBD_BUFFER_ADDR(eth->rxbds[start_of_frame]) - sizeof(struct pbuf));

            first_pbuf->tot_len = len;

            for (;;)
            {
                if ( p->payload != RXBD_BUFFER_ADDR(eth->rxbds[current_rxbd]) )
                {
                    eth_diag("Eeek! Expected pbuf (==%08x) payload (==%08x) is not what's in rxbd[%d] (==%08x)",
                             (unsigned)p, (unsigned)p->payload, current_rxbd, eth->rxbds[current_rxbd].buffer1);
                    CYG_FAIL("Pbuf payload mismatch with rxbd buffer");
                }

                ctrl = eth->rxbds[current_rxbd].ctrl;

                eth_diag("Adding pbuf 0x%08x from rxbd[%d] with buffer 0x%08x length %d ctrl 0x%08x to chain",
                         (unsigned)p, current_rxbd, (unsigned)RXBD_BUFFER_ADDR(eth->rxbds[current_rxbd]),
                         CYGHWR_HAL_STM32_ETH_RDES0_FL(ctrl), ctrl);
//                eth_dump_buf( RXBD_BUFFER_ADDR(eth->rxbds[current_rxbd]), CYGHWR_HAL_STM32_ETH_RDES0_FL(ctrl) );

                // Put back a replacement pool pbuf payload into the rxbd
                newp = pbuf_alloc(PBUF_RAW, CYGNUM_LWIP_PBUF_POOL_BUFSIZE, PBUF_POOL);

                if (newp)
                {
                    // we can only really replace it if we succeeded of course.
                    eth_diag("Replenish rxbd[%d] with pbuf 0x%08x payload 0x%08x", current_rxbd, newp, newp->payload );
                    eth->rxbds[current_rxbd].buffer1 = (cyg_uint32)CYGARC_PHYSICAL_ADDRESS(newp->payload);
                    eth->rxbds[current_rxbd].ctrl = CYGHWR_HAL_STM32_ETH_RDES0_OWN;
                    stm32_eth_rx_start( &eth->common );
                }
                else
                {
                    eth_diag("No pool pbufs left, keeping rxbd[%d] as non-owned", current_rxbd);
                    // Set buffer as NULL and don't mark owned, so we know later which rxbds need refilling.
                    eth->rxbds[current_rxbd].buffer1 = 0;
                    eth->rxbds[current_rxbd].ctrl = 0;
                    eth->rx_ran_out_of_buffers = 1;
                }

                if ( ctrl & CYGHWR_HAL_STM32_ETH_RDES0_LS )
                {
                    p->len = len;
                    p->next = NULL;
                    break; // finished the frame
                }
                else
                {
                    p->len = CYGNUM_LWIP_PBUF_POOL_BUFSIZE;
                    len -= CYGNUM_LWIP_PBUF_POOL_BUFSIZE;

                    // Now link to payload of next rxbd in frame
                    current_rxbd = NEXT_RXBD(current_rxbd);
                    // The pbuf structure always precedes the pbuf payload, from the way we set the pool up.
                    p->next = (struct pbuf *)(RXBD_BUFFER_ADDR(eth->rxbds[current_rxbd]) - sizeof(struct pbuf));
                    p = p->next;
                    p->tot_len = len;
                } // else
            } // for

            // Send this packet, i.e. pbuf chain, up.
            cyg_lwip_eth_drv_ecosif_input( &eth->netif, first_pbuf );

            // Reset pointers for next packet
            current_rxbd = eth->rx_next_buffer = NEXT_RXBD(current_rxbd);
            start_of_frame = -1;
        } // if
        else // Not end of frame - a starting or intermediate packet frag
        {
            if (start_of_frame < 0)
            {
                eth_diag("Eek! found packet frag at rxbd[%d] with no start found", current_rxbd);
//                CYG_FAIL("Packet frag found with no start");
                // Just return the frame to the hardware
                eth->rxbds[current_rxbd].ctrl = CYGHWR_HAL_STM32_ETH_RDES0_OWN;                
            }
            current_rxbd = NEXT_RXBD(current_rxbd);
        }
    } // for

} /* stm32_eth_deliver_rx() */


//==========================================================================
// The delivery function is called from thread context, after the DSR has woken
// up the packet handling thread. It needs to report completed transmits so
// that higher-level code can release the tx pbuf if needed, and report all
// received frames.

__externC void
cyg_lwip_eth_run_deliveries(void)
{
    stm32_eth *eth = &stm32_eth_info;
    cyg_uint32 base = eth->common.base;

    {
        cyg_uint32 sr;
        
        HAL_READ_UINT32( base+CYGHWR_HAL_STM32_ETH_DMASR, sr );

        {
            static cyg_uint32 sr0;
            if( sr != sr0 )
            {
                eth_diag("sr %08x", sr);
                sr0 = sr;
            }
        }

        // Clear summary bit
        if( sr & CYGHWR_HAL_STM32_ETH_DMASR_NIS )
            HAL_WRITE_UINT32( base+CYGHWR_HAL_STM32_ETH_DMASR,
                              CYGHWR_HAL_STM32_ETH_DMASR_NIS);
        
        if( sr & CYGHWR_HAL_STM32_ETH_DMASR_TS )
        {
            // End of frame transmission
            stm32_eth_deliver_tx( eth, false );
            HAL_WRITE_UINT32( base+CYGHWR_HAL_STM32_ETH_DMASR,
                              CYGHWR_HAL_STM32_ETH_DMASR_TS   |
                              CYGHWR_HAL_STM32_ETH_DMASR_TBUS |                              
                              CYGHWR_HAL_STM32_ETH_DMASR_ETS  );
        }

        if( sr & CYGHWR_HAL_STM32_ETH_DMASR_RS )
        {
            // End of frame reception
            stm32_eth_deliver_rx( eth );
            HAL_WRITE_UINT32( base+CYGHWR_HAL_STM32_ETH_DMASR,
                              CYGHWR_HAL_STM32_ETH_DMASR_RS);
        }

        // TODO: report any errors
    }
    
    // Allow more interrupts.
    cyg_drv_interrupt_unmask(eth->common.vector);
}

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

// SET_MAC_ADDRESS is straightforward, it just requires updating two registers.

__externC int
cyg_lwip_eth_ioctl(struct netif *netif, unsigned long key, void* data, int data_length)
{
    stm32_eth*    eth = (stm32_eth*) netif->state;

    eth_diag("key %lx", key );
    
    switch(key)
    {
    case ETH_DRV_SET_MAC_ADDRESS:
    {
        memcpy(eth->mac, data, 6);
        stm32_eth_set_mac(&eth->common, eth->mac);
        return 0;
    }

    default:
        return 1;
    }
}

//==========================================================================
// End of if_stm32.c
