/* * Authorship * ---------- * This software ('mvme3100' RTEMS BSP) was created by * * Till Straumann , 2005-2007, * Stanford Linear Accelerator Center, Stanford University. * * Acknowledgement of sponsorship * ------------------------------ * The 'mvme3100' BSP was produced by * the Stanford Linear Accelerator Center, Stanford University, * under Contract DE-AC03-76SFO0515 with the Department of Energy. * * Government disclaimer of liability * ---------------------------------- * Neither the United States nor the United States Department of Energy, * nor any of their employees, makes any warranty, express or implied, or * assumes any legal liability or responsibility for the accuracy, * completeness, or usefulness of any data, apparatus, product, or process * disclosed, or represents that its use would not infringe privately owned * rights. * * Stanford disclaimer of liability * -------------------------------- * Stanford University makes no representations or warranties, express or * implied, nor assumes any liability for the use of this software. * * Stanford disclaimer of copyright * -------------------------------- * Stanford University, owner of the copyright, hereby disclaims its * copyright and all other rights in this software. Hence, anyone may * freely use it for any purpose without restriction. * * Maintenance of notices * ---------------------- * In the interest of clarity regarding the origin and status of this * SLAC software, this and all the preceding Stanford University notices * are to remain affixed to any copy or derivative of this software made * or distributed by the recipient and are to be affixed to any copy of * software made or distributed by the recipient that contains a copy or * derivative of this software. * * ------------------ SLAC Software Notices, Set 4 OTT.002a, 2004 FEB 03 */ #define __INSIDE_RTEMS_BSD_TCPIP_STACK__ #include #include #include #include #include #include #include #include #include #ifndef KERNEL #define KERNEL #endif #ifndef _KERNEL #define _KERNEL #endif #include #include #include #include #include #include #include #include #include #include #include #define STATIC #define PARANOIA #undef DEBUG #ifdef TEST_MII_TIMING #include SPR_RO(TBRL) static inline uint32_t tb_rd() { return _read_TBRL(); } #endif struct tsec_private; /* Forward declarations */ static void phy_init_irq( int install, struct tsec_private *mp, void (*tsec_lisr)(rtems_irq_hdl_param) ); static void phy_en_irq(struct tsec_private *mp); static void phy_en_irq_at_phy(struct tsec_private *mp); static void phy_dis_irq(struct tsec_private *mp); static void phy_dis_irq_at_phy(struct tsec_private *mp); static int phy_irq_pending(struct tsec_private *mp); static uint32_t phy_ack_irq(struct tsec_private *mp); static void tsec_update_mcast(struct ifnet *ifp); #if defined(PARANOIA) || defined(DEBUG) void tsec_dump_tring(struct tsec_private *mp); void tsec_dump_rring(struct tsec_private *mp); #endif #ifdef DEBUG #ifdef TSEC_RX_RING_SIZE #undef TSEC_RX_RING_SIZE #endif #define TSEC_RX_RING_SIZE 4 #ifdef TSEC_TX_RING_SIZE #undef TSEC_TX_RING_SIZE #endif #define TSEC_TX_RING_SIZE 2 #else #ifndef TSEC_RX_RING_SIZE #define TSEC_RX_RING_SIZE 40 #endif #ifndef TSEC_TX_RING_SIZE #define TSEC_TX_RING_SIZE 200 #endif #endif /********** Helper Macros and Definitions ******/ /* * Align 'p' up to a multiple of 'a' which must be * a power of two. Result is cast to (uintptr_t) */ #define ALIGNTO(p,a) ((((uintptr_t)(p)) + (a) - 1) & ~((a)-1)) /* * Not obvious from the doc: RX buffers apparently must be 32-byte * aligned :-(; TX buffers have no alignment requirement. * I found this by testing, T.S, 11/2007 */ #define RX_BUF_ALIGNMENT 32 /* * Alignment req. for buffer descriptors (BDs) */ #define BD_ALIGNMENT 8 #define ETH_RX_OFFSET 0 #define ETH_CRC_LEN 0 #define CPU2BUS_ADDR(x) (x) #define BUS2CPU_ADDR(x) (x) /* * Whether to automatically try to reclaim descriptors * when enqueueing new packets */ #if 1 #define TSEC_CLEAN_ON_SEND(mp) (BSP_tsec_swipe_tx(mp)) #else #define TSEC_CLEAN_ON_SEND(mp) (-1) #endif #define TX_AVAILABLE_RING_SIZE(mp) (mp)->tx_ring_size #define DRVNAME "tsec" /* * Event(s) posted by ISRs to driver task */ #define EV_PER_UNIT 1 #define TSEC_ETH_EVENT( unit ) ( 1 << (EV_PER_UNIT * (unit) )) #if EV_PER_UNIT > 1 #define TSEC_PHY_EVENT( unit ) ( 1 << (EV_PER_UNIT * (unit) + 1)) #endif #define EV_IS_ETH(ev) ( (ev) & 1 ) #if EV_PER_UNIT > 1 #define EV_IS_PHY(ev) ( (ev) & 2 ) #endif #ifndef MAXEBERRS #define MAXEBERRS 10 #endif #define EV_IS_ANY(ev) ( (ev) & ((1<flags ); } static inline void bd_wrfl(TSEC_BD *bd, uint16_t v) { st_be16( &bd->flags, v ); } static inline void bd_setfl(TSEC_BD *bd, uint16_t v) { bd_wrfl(bd, bd_rdfl(bd) | v ); } static inline void bd_clrfl(TSEC_BD *bd, uint16_t v) { bd_wrfl(bd, bd_rdfl(bd) & ~v ); } static inline void bd_cslfl(TSEC_BD *bd, uint16_t s, uint16_t c) { bd_wrfl( bd, ( bd_rdfl(bd) & ~c ) | s ); } static inline uint32_t bd_rdbuf(TSEC_BD *bd) { return BUS2CPU_ADDR( ld_be32( &bd->buf ) ); } static inline void bd_wrbuf(TSEC_BD *bd, uint32_t addr) { st_be32( &bd->buf, CPU2BUS_ADDR(addr) ); } /* BD bit definitions */ #define TSEC_TXBD_R ((uint16_t)(1<<(15- 0))) #define TSEC_TXBD_PAD_CRC ((uint16_t)(1<<(15- 1))) #define TSEC_TXBD_W ((uint16_t)(1<<(15- 2))) #define TSEC_TXBD_I ((uint16_t)(1<<(15- 3))) #define TSEC_TXBD_L ((uint16_t)(1<<(15- 4))) #define TSEC_TXBD_TC ((uint16_t)(1<<(15- 5))) #define TSEC_TXBD_DEF ((uint16_t)(1<<(15- 6))) #define TSEC_TXBD_TO1 ((uint16_t)(1<<(15- 7))) #define TSEC_TXBD_HFE_LC ((uint16_t)(1<<(15- 8))) #define TSEC_TXBD_RL ((uint16_t)(1<<(15- 9))) #define TSEC_TXBD_RC(x) ((uint16_t)(((x)>>2)&0xf)) #define TSEC_TXBD_UN ((uint16_t)(1<<(15-14))) #define TSEC_TXBD_TXTRUNC ((uint16_t)(1<<(15-15))) #define TSEC_TXBD_ERRS (TSEC_TXBD_RL | TSEC_TXBD_UN | TSEC_TXBD_TXTRUNC) #define TSEC_RXBD_E ((uint16_t)(1<<(15- 0))) #define TSEC_RXBD_RO1 ((uint16_t)(1<<(15- 1))) #define TSEC_RXBD_W ((uint16_t)(1<<(15- 2))) #define TSEC_RXBD_I ((uint16_t)(1<<(15- 3))) #define TSEC_RXBD_L ((uint16_t)(1<<(15- 4))) #define TSEC_RXBD_F ((uint16_t)(1<<(15- 5))) #define TSEC_RXBD_M ((uint16_t)(1<<(15- 7))) #define TSEC_RXBD_BC ((uint16_t)(1<<(15- 8))) #define TSEC_RXBD_MC ((uint16_t)(1<<(15- 9))) #define TSEC_RXBD_LG ((uint16_t)(1<<(15-10))) #define TSEC_RXBD_NO ((uint16_t)(1<<(15-11))) #define TSEC_RXBD_SH ((uint16_t)(1<<(15-12))) #define TSEC_RXBD_CR ((uint16_t)(1<<(15-13))) #define TSEC_RXBD_OV ((uint16_t)(1<<(15-14))) #define TSEC_RXBD_TR ((uint16_t)(1<<(15-15))) #define TSEC_RXBD_ERROR \ (TSEC_RXBD_LG | TSEC_RXBD_NO | TSEC_RXBD_SH | TSEC_RXBD_CR | TSEC_RXBD_OV | TSEC_RXBD_TR ) /* Driver 'private' data */ #define NUM_MC_HASHES 256 struct tsec_private { FEC_Enet_Base base; /* Controller base address */ FEC_Enet_Base phy_base; /* Phy base address (not necessarily identical * with controller base address); * e.g., phy attached to 2nd controller may be * connected to mii bus of 1st controller. */ unsigned phy; /* Phy address on mii bus */ unsigned unit; /* Driver instance (one-based */ int isfec; /* Set if a FEC (not TSEC) controller */ struct tsec_softc *sc; /* Pointer to BSD driver struct */ TSEC_BD *ring_area; /* Not necessarily aligned */ TSEC_BD *tx_ring; /* Aligned array of TX BDs */ void **tx_ring_user; /* Array of user pointers (1 per BD) */ unsigned tx_ring_size; unsigned tx_head; /* first 'dirty' BD; chip is working on */ unsigned tx_tail; /* BDs between head and tail */ unsigned tx_avail; /* Number of available/free TX BDs */ TSEC_BD *rx_ring; /* Aligned array of RX BDs */ void **rx_ring_user; /* Array of user pointers (1 per BD) */ unsigned rx_tail; /* Where we left off scanning for full bufs */ unsigned rx_ring_size; void (*isr)(void*); void *isr_arg; void (*cleanup_txbuf) /* Callback to cleanup TX ring */ (void*, void*, int); void *cleanup_txbuf_arg; void *(*alloc_rxbuf) /* Callback for allocating RX buffer */ (int *psize, uintptr_t *paddr); void (*consume_rxbuf) /* callback to consume RX buffer */ (void*, void*, int); void *consume_rxbuf_arg; rtems_id tid; /* driver task ID */ uint32_t irq_mask; uint32_t irq_mask_cache; uint32_t irq_pending; rtems_event_set event; /* Task synchronization events */ struct { /* Statistics */ unsigned xirqs; unsigned rirqs; unsigned eirqs; unsigned lirqs; unsigned maxchain; unsigned packet; unsigned odrops; unsigned repack; unsigned eberrs; unsigned dmarst; } stats; uint16_t mc_refcnt[NUM_MC_HASHES]; }; #define NEXT_TXI(mp, i) (((i)+1) < (mp)->tx_ring_size ? (i)+1 : 0 ) #define NEXT_RXI(mp, i) (((i)+1) < (mp)->rx_ring_size ? (i)+1 : 0 ) /* Stuff needed for bsdnet support */ struct tsec_bsdsupp { int oif_flags; /* old / cached if_flags */ }; /* bsdnet driver data */ struct tsec_softc { struct arpcom arpcom; struct tsec_bsdsupp bsd; struct tsec_private pvt; }; /* BSP glue information */ typedef struct tsec_bsp_config { uint32_t base; int xirq, rirq, eirq; uint32_t phy_base; int phy_addr; } TsecBspConfig; /********** Global Variables ********************/ /* You may override base addresses * externally - but you must * then also define TSEC_NUM_DRIVER_SLOTS. */ #ifndef TSEC_CONFIG static TsecBspConfig tsec_config[] = { { base: BSP_8540_CCSR_BASE + 0x24000, xirq: BSP_CORE_IRQ_LOWEST_OFFSET + 13, rirq: BSP_CORE_IRQ_LOWEST_OFFSET + 14, eirq: BSP_CORE_IRQ_LOWEST_OFFSET + 18, phy_base: BSP_8540_CCSR_BASE + 0x24000, phy_addr: 1, }, { base: BSP_8540_CCSR_BASE + 0x25000, xirq: BSP_CORE_IRQ_LOWEST_OFFSET + 19, rirq: BSP_CORE_IRQ_LOWEST_OFFSET + 20, eirq: BSP_CORE_IRQ_LOWEST_OFFSET + 23, /* all PHYs are on the 1st adapter's mii bus */ phy_base: BSP_8540_CCSR_BASE + 0x24000, phy_addr: 2, }, }; #define TSEC_CONFIG tsec_config #endif #ifndef TSEC_NUM_DRIVER_SLOTS #define TSEC_NUM_DRIVER_SLOTS (sizeof(TSEC_CONFIG)/sizeof(TSEC_CONFIG[0])) #endif /* Driver data structs */ STATIC struct tsec_softc theTsecEths[TSEC_NUM_DRIVER_SLOTS] = { {{{0}}} }; /* Bsdnet driver task ID; since the BSD stack is single-threaded * there is no point having multiple tasks. A single * task handling all adapters (attached to BSD stack) * is good enough. * Note that an adapter might well be used independently * from the BSD stack (use the low-level driver interface) * and be serviced by a separate task. */ STATIC rtems_id tsec_tid = 0; /* If we anticipate using adapters independently * from the BSD stack AND if all PHYs are on a single * adapter's MII bus THEN we must mutex-protect * that MII bus. * If not all of these conditions hold then you * may define TSEC_CONFIG_NO_PHY_REGLOCK and * avoid the creation and use of a mutex. */ #ifndef TSEC_CONFIG_NO_PHY_REGLOCK /* * PHY register access protection mutex; * multiple instances of tsec hardware * may share e.g., the first tsec's registers * for accessing the mii bus where all PHYs * may be connected. If we would only deal * with BSD networking then using the normal * networking semaphore would be OK. However, * we want to support standalone drivers and * therefore might require a separate lock. */ STATIC rtems_id tsec_mtx = 0; #define REGLOCK() do { \ if ( RTEMS_SUCCESSFUL != rtems_semaphore_obtain(tsec_mtx, RTEMS_WAIT, RTEMS_NO_TIMEOUT) ) \ rtems_panic(DRVNAME": unable to lock phy register protection mutex"); \ } while (0) #define REGUNLOCK() rtems_semaphore_release(tsec_mtx) #else #define REGLOCK() do { } while (0) #define REGUNLOCK() do { } while (0) #endif static void tsec_xisr(rtems_irq_hdl_param arg); static void tsec_risr(rtems_irq_hdl_param arg); static void tsec_eisr(rtems_irq_hdl_param arg); static void tsec_lisr(rtems_irq_hdl_param arg); static void noop(const rtems_irq_connect_data *unused) { } static int nopf(const rtems_irq_connect_data *unused) { return -1; } /********** Low-level Driver API ****************/ /* * This API provides driver access to applications that * want to use e.g., the second ethernet interface * independently from the BSD TCP/IP stack. E.g., for * raw ethernet packet communication... */ /* * Descriptor scavenger; cleanup the TX ring, passing all buffers * that have been sent to the cleanup_tx() callback. * This routine is called from BSP_tsec_send_buf(), BSP_tsec_init_hw(), * BSP_tsec_stop_hw(). * * RETURNS: number of buffers processed. */ int BSP_tsec_swipe_tx(struct tsec_private *mp) { int rval = 0; int i; TSEC_BD *bd; uint16_t flags; void *u; #if DEBUG > 2 printf("Swipe TX entering:\n"); tsec_dump_tring(mp); #endif for ( i = mp->tx_head; bd_rdbuf( (bd = &mp->tx_ring[i]) ); i = NEXT_TXI(mp, i) ) { flags = bd_rdfl( bd ); if ( (TSEC_TXBD_R & flags) ) { /* nothing more to clean */ break; } /* tx_ring_user[i] is only set on the last descriptor in a chain; * we only count errors in the last descriptor; */ if ( (u=mp->tx_ring_user[i]) ) { mp->cleanup_txbuf(u, mp->cleanup_txbuf_arg, (flags & TSEC_TXBD_ERRS)); mp->tx_ring_user[i] = 0; } bd_wrbuf( bd, 0 ); mp->tx_avail++; rval++; } mp->tx_head = i; #if DEBUG > 2 tsec_dump_tring(mp); printf("Swipe TX leaving\n"); #endif return rval; } /* * Reset the controller and bring into a known state; * all interrupts are off */ STATIC void tsec_reset_hw(struct tsec_private *mp) { FEC_Enet_Base b = mp->base; /* Make sure all interrupts are off */ fec_wr(b, TSEC_IMASK, TSEC_IMASK_NONE); #ifndef TSEC_CONFIG_NO_PHY_REGLOCK /* don't bother disabling irqs in the PHY if this is * called before the mutex is created; * the PHY ISR is not hooked yet and there can be no * interrupts... */ if ( tsec_mtx ) #endif phy_dis_irq_at_phy( mp ); mp->irq_mask_cache = 0; /* Follow the manual resetting the chip */ /* Do graceful stop (if not in stop condition already) */ if ( ! (TSEC_DMACTRL_GTS & fec_rd(b, TSEC_DMACTRL)) ) { /* Make sure GTSC is clear */ fec_wr(b, TSEC_IEVENT, TSEC_IEVENT_GTSC); fec_set(b, TSEC_DMACTRL, TSEC_DMACTRL_GTS); while ( ! (TSEC_IEVENT_GTSC & fec_rd(b, TSEC_IEVENT)) ) /* wait */; } /* Clear RX/TX enable in MAC */ fec_clr(b, TSEC_MACCFG1, TSEC_MACCFG1_RX_EN | TSEC_MACCFG1_TX_EN); /* wait for > 8ms */ rtems_task_wake_after(1); /* set GRS if not already stopped */ if ( ! (TSEC_DMACTRL_GRS & fec_rd(b, TSEC_DMACTRL)) ) { /* Make sure GRSC is clear */ fec_wr(b, TSEC_IEVENT, TSEC_IEVENT_GRSC); fec_set(b, TSEC_DMACTRL, TSEC_DMACTRL_GRS); while ( ! (TSEC_IEVENT_GRSC & fec_rd(b, TSEC_IEVENT)) ) /* wait */; } fec_set(b, TSEC_MACCFG1, TSEC_MACCFG1_SOFT_RESET); fec_clr(b, TSEC_MACCFG1, TSEC_MACCFG1_SOFT_RESET); /* clear all irqs */ fec_wr (b, TSEC_IEVENT, TSEC_IEVENT_ALL); } /* Helper to hook/unhook interrupts */ static void install_remove_isrs(int install, struct tsec_private *mp, uint32_t irq_mask) { rtems_irq_connect_data xxx; int installed = 0; int line; int unit = mp->unit; xxx.on = noop; xxx.off = noop; xxx.isOn = nopf; xxx.handle = mp; if ( irq_mask & TSEC_TXIRQ ) { xxx.name = TSEC_CONFIG[unit-1].xirq; xxx.hdl = tsec_xisr; if ( ! (install ? BSP_install_rtems_irq_handler( &xxx ) : BSP_remove_rtems_irq_handler( &xxx ) ) ) { rtems_panic(DRVNAME": Unable to install TX ISR\n"); } installed++; } if ( (irq_mask & TSEC_RXIRQ) ) { if ( (line = TSEC_CONFIG[unit-1].rirq) < 0 && ! installed ) { /* have no dedicated RX IRQ line; install TX ISR if not already done */ line = TSEC_CONFIG[unit-1].xirq; } xxx.name = line; xxx.hdl = tsec_risr; if ( ! (install ? BSP_install_rtems_irq_handler( &xxx ) : BSP_remove_rtems_irq_handler( &xxx ) ) ) { rtems_panic(DRVNAME": Unable to install RX ISR\n"); } installed++; } if ( (line = TSEC_CONFIG[unit-1].eirq) < 0 && ! installed ) { /* have no dedicated RX IRQ line; install TX ISR if not already done */ line = TSEC_CONFIG[unit-1].xirq; } xxx.name = line; xxx.hdl = tsec_eisr; if ( ! (install ? BSP_install_rtems_irq_handler( &xxx ) : BSP_remove_rtems_irq_handler( &xxx ) ) ) { rtems_panic(DRVNAME": Unable to install ERR ISR\n"); } if ( irq_mask & TSEC_LINK_INTR ) { phy_init_irq( install, mp, tsec_lisr ); } } /* * Setup an interface. * Allocates resources for descriptor rings and sets up the driver software structure. * * Arguments: * unit: * interface # (1..2). The interface must not be attached to BSD already. * * driver_tid: * ISR posts RTEMS event # ('unit' - 1) to task with ID 'driver_tid' and disables interrupts * from this interface. * * void (*cleanup_txbuf)(void *user_buf, void *cleanup_txbuf_arg, int error_on_tx_occurred): * Pointer to user-supplied callback to release a buffer that had been sent * by BSP_tsec_send_buf() earlier. The callback is passed 'cleanup_txbuf_arg' * and a flag indicating whether the send had been successful. * The driver no longer accesses 'user_buf' after invoking this callback. * CONTEXT: This callback is executed either by BSP_tsec_swipe_tx() or * BSP_tsec_send_buf(), BSP_tsec_init_hw(), BSP_tsec_stop_hw() (the latter * ones calling BSP_tsec_swipe_tx()). * void *cleanup_txbuf_arg: * Closure argument that is passed on to 'cleanup_txbuf()' callback; * * void *(*alloc_rxbuf)(int *p_size, uintptr_t *p_data_addr), * Pointer to user-supplied callback to allocate a buffer for subsequent * insertion into the RX ring by the driver. * RETURNS: opaque handle to the buffer (which may be a more complex object * such as an 'mbuf'). The handle is not used by the driver directly * but passed back to the 'consume_rxbuf()' callback. * Size of the available data area and pointer to buffer's data area * in '*psize' and '*p_data_area', respectively. * If no buffer is available, this routine should return NULL in which * case the driver drops the last packet and re-uses the last buffer * instead of handing it out to 'consume_rxbuf()'. * CONTEXT: Called when initializing the RX ring (BSP_tsec_init_hw()) or when * swiping it (BSP_tsec_swipe_rx()). * * * void (*consume_rxbuf)(void *user_buf, void *consume_rxbuf_arg, int len); * Pointer to user-supplied callback to pass a received buffer back to * the user. The driver no longer accesses the buffer after invoking this * callback (with 'len'>0, see below). 'user_buf' is the buffer handle * previously generated by 'alloc_rxbuf()'. * The callback is passed 'cleanup_rxbuf_arg' and a 'len' * argument giving the number of bytes that were received. * 'len' may be <=0 in which case the 'user_buf' argument is NULL. * 'len' == 0 means that the last 'alloc_rxbuf()' had failed, * 'len' < 0 indicates a receiver error. In both cases, the last packet * was dropped/missed and the last buffer will be re-used by the driver. * NOTE: the data are 'prefixed' with two bytes, i.e., the ethernet packet header * is stored at offset 2 in the buffer's data area. Also, the FCS (4 bytes) * is appended. 'len' accounts for both. * CONTEXT: Called from BSP_tsec_swipe_rx(). * void *cleanup_rxbuf_arg: * Closure argument that is passed on to 'consume_rxbuf()' callback; * * rx_ring_size, tx_ring_size: * How many big to make the RX and TX descriptor rings. Note that the sizes * may be 0 in which case a reasonable default will be used. * If either ring size is < 0 then the RX or TX will be disabled. * Note that it is illegal in this case to use BSP_tsec_swipe_rx() or * BSP_tsec_swipe_tx(), respectively. * * irq_mask: * Interrupts to enable. OR of flags from above. * */ static struct tsec_private * tsec_setup_internal( int unit, rtems_id driver_tid, void (*isr)(void *), void * isr_arg, void (*cleanup_txbuf)(void *user_buf, void *cleanup_txbuf_arg, int error_on_tx_occurred), void * cleanup_txbuf_arg, void * (*alloc_rxbuf)(int *p_size, uintptr_t *p_data_addr), void (*consume_rxbuf)(void *user_buf, void *consume_rxbuf_arg, int len), void * consume_rxbuf_arg, int rx_ring_size, int tx_ring_size, int irq_mask ) { struct tsec_private *mp; int i; struct ifnet *ifp; if ( unit <= 0 || unit > TSEC_NUM_DRIVER_SLOTS ) { printk(DRVNAME": Bad unit number %i; must be 1..%i\n", unit, TSEC_NUM_DRIVER_SLOTS); return 0; } ifp = &theTsecEths[unit-1].arpcom.ac_if; if ( ifp->if_init ) { if ( ifp->if_init ) { printk(DRVNAME": instance %i already attached.\n", unit); return 0; } } if ( rx_ring_size < 0 && tx_ring_size < 0 ) return 0; mp = &theTsecEths[unit - 1].pvt; memset(mp, 0, sizeof(*mp)); mp->sc = &theTsecEths[unit - 1]; mp->unit = unit; mp->base = (FEC_Enet_Base)TSEC_CONFIG[unit-1].base; mp->phy_base = (FEC_Enet_Base)TSEC_CONFIG[unit-1].phy_base; mp->phy = TSEC_CONFIG[unit-1].phy_addr; mp->tid = driver_tid; /* use odd event flags for link status IRQ */ mp->event = TSEC_ETH_EVENT((unit-1)); mp->cleanup_txbuf = cleanup_txbuf; mp->cleanup_txbuf_arg = cleanup_txbuf_arg; mp->alloc_rxbuf = alloc_rxbuf; mp->consume_rxbuf = consume_rxbuf; mp->consume_rxbuf_arg = consume_rxbuf_arg; /* stop hw prior to setting ring-size to anything nonzero * so that the rings are not swept. */ BSP_tsec_stop_hw(mp); if ( 0 == rx_ring_size ) rx_ring_size = TSEC_RX_RING_SIZE; if ( 0 == tx_ring_size ) tx_ring_size = TSEC_TX_RING_SIZE; mp->rx_ring_size = rx_ring_size < 0 ? 0 : rx_ring_size; mp->tx_ring_size = tx_ring_size < 0 ? 0 : tx_ring_size; /* allocate ring area; add 1 entry -- room for alignment */ assert( !mp->ring_area ); mp->ring_area = malloc( sizeof(*mp->ring_area) * (mp->rx_ring_size + mp->tx_ring_size + 1), M_DEVBUF, M_WAIT ); assert( mp->ring_area ); mp->tx_ring_user = malloc( sizeof(*mp->tx_ring_user) * (mp->rx_ring_size + mp->tx_ring_size), M_DEVBUF, M_WAIT ); assert( mp->tx_ring_user ); mp->rx_ring_user = mp->tx_ring_user + mp->tx_ring_size; /* Initialize TX ring */ mp->tx_ring = (TSEC_BD *) ALIGNTO(mp->ring_area,BD_ALIGNMENT); mp->rx_ring = mp->tx_ring + mp->tx_ring_size; for ( i=0; itx_ring_size; i++ ) { bd_wrbuf( &mp->tx_ring[i], 0 ); bd_wrfl( &mp->tx_ring[i], TSEC_TXBD_I ); mp->tx_ring_user[i] = 0; } /* set wrap-around flag on last BD */ if ( mp->tx_ring_size ) bd_setfl( &mp->tx_ring[i-1], TSEC_TXBD_W ); mp->tx_tail = mp->tx_head = 0; mp->tx_avail = mp->tx_ring_size; /* Initialize RX ring (buffers are allocated later) */ for ( i=0; irx_ring_size; i++ ) { bd_wrbuf( &mp->rx_ring[i], 0 ); bd_wrfl( &mp->rx_ring[i], TSEC_RXBD_I ); mp->rx_ring_user[i] = 0; } /* set wrap-around flag on last BD */ if ( mp->rx_ring_size ) bd_setfl( &mp->rx_ring[i-1], TSEC_RXBD_W ); if ( irq_mask ) { if ( rx_ring_size == 0 ) irq_mask &= ~ TSEC_RXIRQ; if ( tx_ring_size == 0 ) irq_mask &= ~ TSEC_TXIRQ; } #ifndef TSEC_CONFIG_NO_PHY_REGLOCK if ( ! tsec_mtx ) { rtems_status_code sc; rtems_id new_mtx; rtems_interrupt_level l; sc = rtems_semaphore_create( rtems_build_name('t','s','e','X'), 1, RTEMS_BINARY_SEMAPHORE | RTEMS_PRIORITY | RTEMS_INHERIT_PRIORITY | RTEMS_DEFAULT_ATTRIBUTES, 0, &new_mtx); if ( RTEMS_SUCCESSFUL != sc ) { rtems_error(sc,DRVNAME": creating mutex\n"); rtems_panic("unable to proceed\n"); } rtems_interrupt_disable( l ); if ( ! tsec_mtx ) { tsec_mtx = new_mtx; new_mtx = 0; } rtems_interrupt_enable( l ); if ( new_mtx ) { /* another task was faster installing the mutex */ rtems_semaphore_delete( new_mtx ); } } #endif if ( irq_mask ) { install_remove_isrs( 1, mp, irq_mask ); } mp->irq_mask = irq_mask; /* mark as used */ ifp->if_init = (void*)(-1); return mp; } struct tsec_private * BSP_tsec_setup( int unit, rtems_id driver_tid, void (*cleanup_txbuf)(void *user_buf, void *cleanup_txbuf_arg, int error_on_tx_occurred), void * cleanup_txbuf_arg, void * (*alloc_rxbuf)(int *p_size, uintptr_t *p_data_addr), void (*consume_rxbuf)(void *user_buf, void *consume_rxbuf_arg, int len), void * consume_rxbuf_arg, int rx_ring_size, int tx_ring_size, int irq_mask ) { if ( irq_mask && ! driver_tid ) { printk(DRVNAME": must supply a TID if irq_mask not zero\n"); return 0; } return tsec_setup_internal( unit, driver_tid, 0, 0, cleanup_txbuf, cleanup_txbuf_arg, alloc_rxbuf, consume_rxbuf, consume_rxbuf_arg, rx_ring_size, tx_ring_size, irq_mask ); } struct tsec_private * BSP_tsec_setup_1( int unit, void (*isr)(void*), void * isr_arg, void (*cleanup_txbuf)(void *user_buf, void *cleanup_txbuf_arg, int error_on_tx_occurred), void * cleanup_txbuf_arg, void * (*alloc_rxbuf)(int *p_size, uintptr_t *p_data_addr), void (*consume_rxbuf)(void *user_buf, void *consume_rxbuf_arg, int len), void * consume_rxbuf_arg, int rx_ring_size, int tx_ring_size, int irq_mask ) { if ( irq_mask && ! isr ) { printk(DRVNAME": must supply a ISR if irq_mask not zero\n"); return 0; } return tsec_setup_internal( unit, 0, isr, isr_arg, cleanup_txbuf, cleanup_txbuf_arg, alloc_rxbuf, consume_rxbuf, consume_rxbuf_arg, rx_ring_size, tx_ring_size, irq_mask ); } void BSP_tsec_reset_stats(struct tsec_private *mp) { FEC_Enet_Base b = mp->base; int i; memset( &mp->stats, 0, sizeof( mp->stats ) ); if ( mp->isfec ) return; for ( i=TSEC_TR64; i<=TSEC_TFRG; i+=4 ) fec_wr( b, i, 0 ); } /* * retrieve media status from the PHY * and set duplex mode in MACCFG2 based * on the result. * * RETURNS: media word (or -1 if BSP_tsec_media_ioctl() fails) */ static int mac_set_duplex(struct tsec_private *mp) { int media = IFM_MAKEWORD(0, 0, 0, 0); if ( 0 == BSP_tsec_media_ioctl(mp, SIOCGIFMEDIA, &media)) { if ( IFM_LINK_OK & media ) { /* update duplex setting in MACCFG2 */ if ( IFM_FDX & media ) { fec_set( mp->base, TSEC_MACCFG2, TSEC_MACCFG2_FD ); } else { fec_clr( mp->base, TSEC_MACCFG2, TSEC_MACCFG2_FD ); } } return media; } return -1; } /* * Initialize interface hardware * * 'mp' handle obtained by from BSP_tsec_setup(). * 'promisc' whether to set promiscuous flag. * 'enaddr' pointer to six bytes with MAC address. Read * from the device if NULL. */ void BSP_tsec_init_hw(struct tsec_private *mp, int promisc, unsigned char *enaddr) { FEC_Enet_Base b = mp->base; unsigned i; uint32_t v; int sz; BSP_tsec_stop_hw(mp); #ifdef PARANOIA assert( mp->tx_avail == mp->tx_ring_size ); assert( mp->tx_head == mp->tx_tail ); for ( i=0; itx_ring_size; i++ ) { assert( mp->tx_ring_user[i] == 0 ); } #endif /* make sure RX ring is filled */ for ( i=0; irx_ring_size; i++ ) { uintptr_t data_area; if ( ! (mp->rx_ring_user[i] = mp->alloc_rxbuf( &sz, &data_area)) ) { rtems_panic(DRVNAME": unable to fill RX ring"); } if ( data_area & (RX_BUF_ALIGNMENT-1) ) rtems_panic(DRVNAME": RX buffers must be %i-byte aligned", RX_BUF_ALIGNMENT); bd_wrbuf( &mp->rx_ring[i], data_area ); st_be16 ( &mp->rx_ring[i].len, sz ); bd_setfl( &mp->rx_ring[i], TSEC_RXBD_E | TSEC_RXBD_I ); } mp->tx_tail = mp->tx_head = 0; mp->rx_tail = 0; /* tell chip what the ring areas are */ fec_wr( b, TSEC_TBASE, (uint32_t)mp->tx_ring ); fec_wr( b, TSEC_RBASE, (uint32_t)mp->rx_ring ); /* clear and disable IRQs */ fec_wr( b, TSEC_IEVENT, TSEC_IEVENT_ALL ); fec_wr( b, TSEC_IMASK, TSEC_IMASK_NONE ); mp->irq_mask_cache = 0; /* bring other regs. into a known state */ fec_wr( b, TSEC_EDIS, 0 ); if ( !mp->isfec ) fec_wr( b, TSEC_ECNTRL, TSEC_ECNTRL_CLRCNT | TSEC_ECNTRL_STEN ); fec_wr( b, TSEC_MINFLR, 64 ); fec_wr( b, TSEC_PTV, 0 ); v = TSEC_DMACTRL_WWR; if ( mp->tx_ring_size ) v |= TSEC_DMACTRL_TDSEN | TSEC_DMACTRL_TBDSEN | TSEC_DMACTRL_WOP; fec_wr( b, TSEC_DMACTRL, v ); fec_wr( b, TSEC_FIFO_PAUSE_CTRL, 0 ); fec_wr( b, TSEC_FIFO_TX_THR, 256 ); fec_wr( b, TSEC_FIFO_TX_STARVE, 128 ); fec_wr( b, TSEC_FIFO_TX_STARVE_SHUTOFF, 256 ); fec_wr( b, TSEC_TCTRL, 0 ); if ( !mp->isfec ) { /* FIXME: use IRQ coalescing ? not sure how to * set the timer (bad if it depends on the speed * setting). */ fec_wr( b, TSEC_TXIC, 0); } fec_wr( b, TSEC_OSTBD, 0 ); fec_wr( b, TSEC_RCTRL, (promisc ? TSEC_RCTRL_PROM : 0) ); fec_wr( b, TSEC_RSTAT, TSEC_RSTAT_QHLT ); if ( !mp->isfec ) { /* FIXME: use IRQ coalescing ? not sure how to * set the timer (bad if it depends on the speed * setting). */ fec_wr( b, TSEC_RXIC, 0 ); } fec_wr( b, TSEC_MRBLR, sz & ~63 ); /* Reset config. as per manual */ fec_wr( b, TSEC_IPGIFG, 0x40605060 ); fec_wr( b, TSEC_HAFDUP, 0x00a1f037 ); fec_wr( b, TSEC_MAXFRM, 1536 ); if ( enaddr ) { union { uint32_t u; uint16_t s[2]; uint8_t c[4]; } x; fec_wr( b, TSEC_MACSTNADDR1, ld_le32( (volatile uint32_t*)(enaddr + 2) ) ); x.s[0] = ld_le16( (volatile uint16_t *)(enaddr) ); fec_wr( b, TSEC_MACSTNADDR2, x.u ); } for ( i=0; i<8*4; i+=4 ) { fec_wr( b, TSEC_IADDR0 + i, 0 ); } BSP_tsec_mcast_filter_clear(mp); BSP_tsec_reset_stats(mp); fec_wr( b, TSEC_ATTR, (TSEC_ATTR_RDSEN | TSEC_ATTR_RBDSEN) ); fec_wr( b, TSEC_ATTRELI, 0 ); /* The interface type is probably board dependent; leave alone... v = mp->isfec ? TSEC_MACCFG2_IF_MODE_MII : TSEC_MACCFG2_IF_MODE_GMII; */ fec_clr( b, TSEC_MACCFG2, TSEC_MACCFG2_PREAMBLE_15 | TSEC_MACCFG2_HUGE_FRAME | TSEC_MACCFG2_LENGTH_CHECK ); fec_set( b, TSEC_MACCFG2, TSEC_MACCFG2_PREAMBLE_7 | TSEC_MACCFG2_PAD_CRC ); mac_set_duplex( mp ); v = 0; if ( mp->rx_ring_size ) { v |= TSEC_MACCFG1_RX_EN; } if ( mp->tx_ring_size ) { v |= TSEC_MACCFG1_TX_EN; } fec_wr( b, TSEC_MACCFG1, v); /* The following sequency (FWIW) ensures that * * - PHY and TSEC interrupts are enabled atomically * - IRQS are not globally off while accessing the PHY * (slow MII) */ if ( (TSEC_LINK_INTR & mp->irq_mask) ) { /* disable PHY irq at PIC (fast) */ phy_dis_irq( mp ); /* enable PHY irq (MII operation, slow) */ phy_en_irq_at_phy (mp ); } BSP_tsec_enable_irq_mask( mp, mp->irq_mask ); } static void hash_prog(struct tsec_private *mp, uint32_t tble, const uint8_t *enaddr, int accept) { uint8_t s; uint32_t reg, bit; s = ether_crc32_le(enaddr, ETHER_ADDR_LEN); /* bit-reverse */ s = ((s&0x0f) << 4) | ((s&0xf0) >> 4); s = ((s&0x33) << 2) | ((s&0xcc) >> 2); s = ((s&0x55) << 1) | ((s&0xaa) >> 1); reg = tble + ((s >> (5-2)) & ~3); bit = 1 << (31 - (s & 31)); if ( accept ) { if ( 0 == mp->mc_refcnt[s]++ ) fec_set( mp->base, reg, bit ); } else { if ( mp->mc_refcnt[s] > 0 && 0 == --mp->mc_refcnt[s] ) fec_clr( mp->base, reg, bit ); } } void BSP_tsec_mcast_filter_clear(struct tsec_private *mp) { int i; for ( i=0; i<8*4; i+=4 ) { fec_wr( mp->base, TSEC_GADDR0 + i, 0 ); } for ( i=0; imc_refcnt[i] = 0; } void BSP_tsec_mcast_filter_accept_all(struct tsec_private *mp) { int i; for ( i=0; i<8*4; i+=4 ) { fec_wr( mp->base, TSEC_GADDR0 + i, 0xffffffff ); } for ( i=0; imc_refcnt[i]++; } static void mcast_filter_prog(struct tsec_private *mp, uint8_t *enaddr, int accept) { static const uint8_t bcst[6]={0xff, 0xff, 0xff, 0xff, 0xff, 0xff}; if ( ! (enaddr[0] & 0x01) ) { /* not a multicast address; ignore */ return; } if ( 0 == memcmp( enaddr, bcst, sizeof(bcst) ) ) { /* broadcast; ignore */ return; } hash_prog(mp, TSEC_GADDR0, enaddr, accept); } void BSP_tsec_mcast_filter_accept_add(struct tsec_private *mp, uint8_t *enaddr) { mcast_filter_prog(mp, enaddr, 1 /* accept */); } void BSP_tsec_mcast_filter_accept_del(struct tsec_private *mp, uint8_t *enaddr) { mcast_filter_prog(mp, enaddr, 0 /* delete */); } void BSP_tsec_dump_stats(struct tsec_private *mp, FILE *f) { FEC_Enet_Base b; if ( !mp ) mp = &theTsecEths[0].pvt; if ( ! f ) f = stdout; fprintf(f, DRVNAME"%i Statistics:\n", mp->unit); b = mp->base; fprintf(f, "TX IRQS: %u\n", mp->stats.xirqs); fprintf(f, "RX IRQS: %u\n", mp->stats.rirqs); fprintf(f, "ERR IRQS: %u\n", mp->stats.eirqs); fprintf(f, "bus errs: %u\n", mp->stats.eberrs); fprintf(f, "dmawhack: %u\n", mp->stats.dmarst); fprintf(f, "LNK IRQS: %u\n", mp->stats.lirqs); fprintf(f, "maxchain: %u\n", mp->stats.maxchain); fprintf(f, "xpackets: %u\n", mp->stats.packet); fprintf(f, "odrops: %u\n", mp->stats.odrops); fprintf(f, "repack: %u\n", mp->stats.repack); if ( mp->isfec ) { fprintf(f,"FEC has no HW counters\n"); return; } fprintf(f,"TSEC MIB counters (modulo 2^32):\n"); fprintf(f,"RX bytes %"PRIu32"\n", fec_rd( b, TSEC_RBYT )); fprintf(f,"RX pkts %"PRIu32"\n", fec_rd( b, TSEC_RPKT )); fprintf(f,"RX FCS errs %"PRIu32"\n", fec_rd( b, TSEC_RFCS )); fprintf(f,"RX mcst pkts %"PRIu32"\n", fec_rd( b, TSEC_RMCA )); fprintf(f,"RX bcst pkts %"PRIu32"\n", fec_rd( b, TSEC_RBCA )); fprintf(f,"RX pse frms %"PRIu32"\n", fec_rd( b, TSEC_RXPF )); fprintf(f,"RX drop %"PRIu32"\n", fec_rd( b, TSEC_RDRP )); fprintf(f,"TX bytes %"PRIu32"\n", fec_rd( b, TSEC_TBYT )); fprintf(f,"TX pkts %"PRIu32"\n", fec_rd( b, TSEC_TPKT )); fprintf(f,"TX FCS errs %"PRIu32"\n", fec_rd( b, TSEC_TFCS )); fprintf(f,"TX mcst pkts %"PRIu32"\n", fec_rd( b, TSEC_TMCA )); fprintf(f,"TX bcst pkts %"PRIu32"\n", fec_rd( b, TSEC_TBCA )); fprintf(f,"TX pse frms %"PRIu32"\n", fec_rd( b, TSEC_TXPF )); fprintf(f,"TX drop %"PRIu32"\n", fec_rd( b, TSEC_TDRP )); fprintf(f,"TX coll %"PRIu32"\n", fec_rd( b, TSEC_TSCL )); fprintf(f,"TX mcoll %"PRIu32"\n", fec_rd( b, TSEC_TMCL )); fprintf(f,"TX late coll %"PRIu32"\n", fec_rd( b, TSEC_TLCL )); fprintf(f,"TX exc coll %"PRIu32"\n", fec_rd( b, TSEC_TXCL )); fprintf(f,"TX defer %"PRIu32"\n", fec_rd( b, TSEC_TDFR )); fprintf(f,"TX exc defer %"PRIu32"\n", fec_rd( b, TSEC_TEDF )); fprintf(f,"TX oversz %"PRIu32"\n", fec_rd( b, TSEC_TOVR )); fprintf(f,"TX undersz %"PRIu32"\n", fec_rd( b, TSEC_TUND )); } /* * Shutdown hardware and clean out the rings */ void BSP_tsec_stop_hw(struct tsec_private *mp) { unsigned i; /* stop and reset hardware */ tsec_reset_hw( mp ); if ( mp->tx_ring_size ) { /* should be OK to clear all ownership flags */ for ( i=0; itx_ring_size; i++ ) { bd_clrfl( &mp->tx_ring[i], TSEC_TXBD_R ); } BSP_tsec_swipe_tx(mp); #if DEBUG > 0 tsec_dump_tring(mp); fflush(stderr); fflush(stdout); #endif #ifdef PARANOIA assert( mp->tx_avail == mp->tx_ring_size ); assert( mp->tx_head == mp->tx_tail ); for ( i=0; itx_ring_size; i++ ) { assert( !bd_rdbuf( & mp->tx_ring[i] ) ); assert( !mp->tx_ring_user[i] ); } #endif } if ( mp->rx_ring_size ) { for ( i=0; irx_ring_size; i++ ) { bd_clrfl( &mp->rx_ring[i], TSEC_RXBD_E ); bd_wrbuf( &mp->rx_ring[i], 0 ); if ( mp->rx_ring_user[i] ) mp->consume_rxbuf( mp->rx_ring_user[i], mp->consume_rxbuf_arg, 0 ); mp->rx_ring_user[i] = 0; } } } /* * calls BSP_tsec_stop_hw(), releases all resources and marks the interface * as unused. * RETURNS 0 on success, nonzero on failure. * NOTE: the handle MUST NOT be used after successful execution of this * routine. */ int BSP_tsec_detach(struct tsec_private *mp) { if ( ! mp || !mp->sc || ! mp->sc->arpcom.ac_if.if_init ) { fprintf(stderr,"Unit not setup -- programming error!\n"); return -1; } BSP_tsec_stop_hw(mp); install_remove_isrs( 0, mp, mp->irq_mask ); free( (void*)mp->ring_area, M_DEVBUF ); free( (void*)mp->tx_ring_user, M_DEVBUF ); memset(mp, 0, sizeof(*mp)); __asm__ __volatile__("":::"memory"); /* mark as unused */ mp->sc->arpcom.ac_if.if_init = 0; return 0; } /* * Enqueue a mbuf chain or a raw data buffer for transmission; * RETURN: #bytes sent or -1 if there are not enough free descriptors * * If 'len' is <=0 then 'm_head' is assumed to point to a mbuf chain. * OTOH, a raw data packet (or a different type of buffer) * may be sent (non-BSD driver) by pointing data_p to the start of * the data and passing 'len' > 0. * 'm_head' is passed back to the 'cleanup_txbuf()' callback. * * Comments: software cache-flushing incurs a penalty if the * packet cannot be queued since it is flushed anyways. * The algorithm is slightly more efficient in the normal * case, though. * * RETURNS: # bytes enqueued to device for transmission or -1 if no * space in the TX ring was available. */ #if 0 #define NEXT_TXD(mp, bd) ((bd_rdfl( bd ) & TSEC_TXBD_W) ? mp->tx_ring : (bd + 1)) #endif /* * allocate a new cluster and copy an existing chain there; * old chain is released... */ static struct mbuf * repackage_chain(struct mbuf *m_head) { struct mbuf *m; MGETHDR(m, M_DONTWAIT, MT_DATA); if ( !m ) { goto bail; } MCLGET(m, M_DONTWAIT); if ( !(M_EXT & m->m_flags) ) { m_freem(m); m = 0; goto bail; } m_copydata(m_head, 0, MCLBYTES, mtod(m, caddr_t)); m->m_pkthdr.len = m->m_len = m_head->m_pkthdr.len; bail: m_freem(m_head); return m; } static inline unsigned tsec_assign_desc( TSEC_BD *bd, uint32_t buf, unsigned len, uint32_t flags) { st_be16 ( &bd->len, (uint16_t)len ); bd_wrbuf( bd, buf ); bd_cslfl( bd, flags, TSEC_TXBD_R | TSEC_TXBD_L ); return len; } int BSP_tsec_send_buf(struct tsec_private *mp, void *m_head, void *data_p, int len) { int rval; register TSEC_BD *bd; register unsigned l,d,t; register struct mbuf *m1; int nmbs; int ismbuf = (len <= 0); #if DEBUG > 2 printf("send entering...\n"); tsec_dump_tring(mp); #endif /* Only way to get here is when we discover that the mbuf chain * is too long for the tx ring */ startover: rval = 0; #ifdef PARANOIA assert(m_head); #endif /* if no descriptor is available; try to wipe the queue */ if ( (mp->tx_avail < 1) && TSEC_CLEAN_ON_SEND(mp)<=0 ) { return -1; } t = mp->tx_tail; #ifdef PARANOIA assert( ! bd_rdbuf( &mp->tx_ring[t] ) ); assert( ! mp->tx_ring_user[t] ); #endif if ( ! (m1 = m_head) ) return 0; if ( ismbuf ) { /* find first mbuf with actual data */ while ( 0 == m1->m_len ) { if ( ! (m1 = m1->m_next) ) { /* end reached and still no data to send ?? */ m_freem(m_head); return 0; } } } /* Don't use the first descriptor yet because BSP_tsec_swipe_tx() * needs bd->buf == NULL as a marker. Hence, we * start with the second mbuf and fill the first descriptor * last. */ l = t; d = NEXT_TXI(mp,t); mp->tx_avail--; nmbs = 1; if ( ismbuf ) { register struct mbuf *m; for ( m=m1->m_next; m; m=m->m_next ) { if ( 0 == m->m_len ) continue; /* skip empty mbufs */ nmbs++; if ( mp->tx_avail < 1 && TSEC_CLEAN_ON_SEND(mp)<=0 ) { /* not enough descriptors; cleanup... * the first slot was never used, so we start * at mp->d_tx_h->next; */ for ( l = NEXT_TXI(mp, t); l!=d; l=NEXT_TXI(mp, l) ) { bd = & mp->tx_ring[l]; #ifdef PARANOIA assert( mp->tx_ring_user[l] == 0 ); #endif bd_wrbuf( bd, 0 ); bd_clrfl( bd, TSEC_TXBD_R | TSEC_TXBD_L ); mp->tx_avail++; } mp->tx_avail++; if ( nmbs > TX_AVAILABLE_RING_SIZE(mp) ) { /* this chain will never fit into the ring */ if ( nmbs > mp->stats.maxchain ) mp->stats.maxchain = nmbs; mp->stats.repack++; if ( ! (m_head = repackage_chain(m_head)) ) { /* no cluster available */ mp->stats.odrops++; #ifdef PARANOIA printf("send return 0\n"); tsec_dump_tring(mp); #endif return 0; } #ifdef PARANOIA printf("repackaged; start over\n"); #endif goto startover; } #ifdef PARANOIA printf("send return -1\n"); tsec_dump_tring(mp); #endif return -1; } mp->tx_avail--; #ifdef PARANOIA assert( ! mp->tx_ring_user[d] ); if ( d == t ) { tsec_dump_tring(mp); printf("l %i, d %i, t %i, nmbs %i\n", l,d,t, nmbs); } else assert( d != t ); assert( ! bd_rdbuf( &mp->tx_ring[d] ) ); #endif /* fill this slot */ rval += tsec_assign_desc( &mp->tx_ring[d], mtod(m, uint32_t), m->m_len, TSEC_TXBD_R); FLUSH_BUF(mtod(m, uint32_t), m->m_len); l = d; d = NEXT_TXI(mp, d); } /* fill first slot - don't release to DMA yet */ rval += tsec_assign_desc( &mp->tx_ring[t], mtod(m1, uint32_t), m1->m_len, 0); FLUSH_BUF(mtod(m1, uint32_t), m1->m_len); } else { /* fill first slot with raw buffer - don't release to DMA yet */ rval += tsec_assign_desc( &mp->tx_ring[t], (uint32_t)data_p, len, 0); FLUSH_BUF( (uint32_t)data_p, len); } /* tag last slot; this covers the case where 1st==last */ bd_setfl( &mp->tx_ring[l], TSEC_TXBD_L ); /* mbuf goes into last desc */ mp->tx_ring_user[l] = m_head; membarrier(); /* turn over the whole chain by flipping ownership of the first desc */ bd_setfl( &mp->tx_ring[t], TSEC_TXBD_R ); membarrier(); #if DEBUG > 2 printf("send return (l=%i, t=%i, d=%i) %i\n", l, t, d, rval); tsec_dump_tring(mp); #endif /* notify the device */ fec_wr( mp->base, TSEC_TSTAT, TSEC_TSTAT_THLT ); /* Update softc */ mp->stats.packet++; if ( nmbs > mp->stats.maxchain ) mp->stats.maxchain = nmbs; /* remember new tail */ mp->tx_tail = d; return rval; /* #bytes sent */ } /* * Retrieve all received buffers from the RX ring, replacing them * by fresh ones (obtained from the alloc_rxbuf() callback). The * received buffers are passed to consume_rxbuf(). * * RETURNS: number of buffers processed. */ int BSP_tsec_swipe_rx(struct tsec_private *mp) { int rval = 0, err; unsigned i; uint16_t flags; TSEC_BD *bd; void *newbuf; int sz; uint16_t len; uintptr_t baddr; i = mp->rx_tail; bd = mp->rx_ring + i; flags = bd_rdfl( bd ); while ( ! (TSEC_RXBD_E & flags) ) { /* err is not valid if not qualified by TSEC_RXBD_L */ if ( ( err = (TSEC_RXBD_ERROR & flags) ) ) { /* make sure error is < 0 */ err |= 0xffff0000; /* pass 'L' flag out so they can verify... */ err |= (flags & TSEC_RXBD_L); } #ifdef PARANOIA assert( flags & TSEC_RXBD_L ); assert( mp->rx_ring_user[i] ); #endif if ( err || !(newbuf = mp->alloc_rxbuf(&sz, &baddr)) ) { /* drop packet and recycle buffer */ newbuf = mp->rx_ring_user[i]; mp->consume_rxbuf( 0, mp->consume_rxbuf_arg, err ); } else { len = ld_be16( &bd->len ); #ifdef PARANOIA assert( 0 == (baddr & (RX_BUF_ALIGNMENT-1)) ); assert( len > 0 ); #endif mp->consume_rxbuf( mp->rx_ring_user[i], mp->consume_rxbuf_arg, len ); mp->rx_ring_user[i] = newbuf; st_be16( &bd->len, sz ); bd_wrbuf( bd, baddr ); } RTEMS_COMPILER_MEMORY_BARRIER(); bd_wrfl( &mp->rx_ring[i], (flags & ~TSEC_RXBD_ERROR) | TSEC_RXBD_E ); rval++; i = NEXT_RXI( mp, i ); bd = mp->rx_ring + i; flags = bd_rdfl( bd ); } fec_wr( mp->base, TSEC_RSTAT, TSEC_RSTAT_QHLT ); mp->rx_tail = i; return rval; } /* read ethernet address from hw to buffer */ void BSP_tsec_read_eaddr(struct tsec_private *mp, unsigned char *eaddr) { union { uint32_t u; uint16_t s[2]; uint8_t c[4]; } x; st_le32( (volatile uint32_t *)(eaddr+2), fec_rd(mp->base, TSEC_MACSTNADDR1) ); x.u = fec_rd(mp->base, TSEC_MACSTNADDR2); st_le16( (volatile uint16_t *)(eaddr), x.s[0]); } /* mdio / mii interface wrappers for rtems_mii_ioctl API */ /* * Busy-wait -- this can take a while: I measured ~550 timebase-ticks * @333333333Hz, TB divisor is 8 -> 13us */ static inline void mii_wait(FEC_Enet_Base b) { while ( (TSEC_MIIMIND_BUSY & fec_rd( b, TSEC_MIIMIND )) ) ; } /* MII operations are rather slow :-( */ static int tsec_mdio_rd(int phyidx, void *uarg, unsigned reg, uint32_t *pval) { uint32_t v; #ifdef TEST_MII_TIMING uint32_t t; #endif struct tsec_private *mp = uarg; FEC_Enet_Base b = mp->phy_base; if ( phyidx != 0 ) return -1; /* only one phy supported for now */ /* write phy and register address */ fec_wr( b, TSEC_MIIMADD, TSEC_MIIMADD_ADDR(mp->phy,reg) ); /* clear READ bit */ v = fec_rd( b, TSEC_MIIMCOM ); fec_wr( b, TSEC_MIIMCOM, v & ~TSEC_MIIMCOM_READ ); #ifdef TEST_MII_TIMING t = tb_rd(); #endif /* trigger READ operation by READ-bit 0-->1 transition */ fec_wr( b, TSEC_MIIMCOM, v | TSEC_MIIMCOM_READ ); /* (busy) wait for completion */ mii_wait( b ); /* Ugly workaround: I observed that the link status * is not correctly reported when the link changes to * a good status - a failed link is reported until * we read twice :-( */ if ( MII_BMSR == reg ) { /* trigger a second read operation */ fec_wr( b, TSEC_MIIMCOM, v & ~TSEC_MIIMCOM_READ ); fec_wr( b, TSEC_MIIMCOM, v | TSEC_MIIMCOM_READ ); mii_wait( b ); } #ifdef TEST_MII_TIMING t = tb_rd() - t; printf("Reading MII took %"PRIi32"\n", t); #endif /* return result */ *pval = fec_rd( b, TSEC_MIIMSTAT ) & 0xffff; return 0; } STATIC int tsec_mdio_wr(int phyidx, void *uarg, unsigned reg, uint32_t val) { #ifdef TEST_MII_TIMING uint32_t t; #endif struct tsec_private *mp = uarg; FEC_Enet_Base b = mp->phy_base; if ( phyidx != 0 ) return -1; /* only one phy supported for now */ #ifdef TEST_MII_TIMING t = tb_rd(); #endif fec_wr( b, TSEC_MIIMADD, TSEC_MIIMADD_ADDR(mp->phy,reg) ); fec_wr( b, TSEC_MIIMCON, val & 0xffff ); mii_wait( b ); #ifdef TEST_MII_TIMING t = tb_rd() - t; printf("Writing MII took %"PRIi32"\n", t); #endif return 0; } /* Public, locked versions */ uint32_t BSP_tsec_mdio_rd(struct tsec_private *mp, unsigned reg) { uint32_t val, rval; REGLOCK(); rval = tsec_mdio_rd(0, mp, reg, &val ) ? -1 : val; REGUNLOCK(); return rval; } int BSP_tsec_mdio_wr(struct tsec_private *mp, unsigned reg, uint32_t val) { int rval; REGLOCK(); rval = tsec_mdio_wr(0, mp, reg, val); REGUNLOCK(); return rval; } static struct rtems_mdio_info tsec_mdio = { mdio_r: tsec_mdio_rd, mdio_w: tsec_mdio_wr, has_gmii: 1, }; /* * read/write media word. * 'cmd': can be SIOCGIFMEDIA, SIOCSIFMEDIA, 0 or 1. The latter * are aliased to the former for convenience. * 'parg': pointer to media word. * * RETURNS: 0 on success, nonzero on error */ int BSP_tsec_media_ioctl(struct tsec_private *mp, int cmd, int *parg) { int rval; /* alias cmd == 0,1 for convenience when calling from shell */ switch ( cmd ) { case 0: cmd = SIOCGIFMEDIA; break; case 1: cmd = SIOCSIFMEDIA; case SIOCGIFMEDIA: case SIOCSIFMEDIA: break; default: return -1; } REGLOCK(); tsec_mdio.has_gmii = mp->isfec ? 0 : 1; rval = rtems_mii_ioctl(&tsec_mdio, mp, cmd, parg); REGUNLOCK(); return rval; } /* Interrupt related routines */ /* * When it comes to interrupts the chip has two rather * annoying features: * 1 once an IRQ is pending, clearing the IMASK does not * de-assert the interrupt line. * 2 the chip has three physical interrupt lines even though * all events are reported in a single register. Rather * useless; we must hook 3 ISRs w/o any real benefit. * In fact, it makes our life a bit more difficult: * * Hence, for (1) we would have to mask interrupts at the PIC * but to re-enable them we would have to do that three times * because of (2). * * Therefore, we take the following approach: * * ISR masks interrupts on the TSEC, acks/clears them * and stores the acked irqs in the device struct where * it is picked up by BSP_tsec_ack_irq_mask(). * */ static inline uint32_t tsec_dis_irqs(struct tsec_private *mp, uint32_t mask) { uint32_t rval; rval = mp->irq_mask_cache; if ( (TSEC_LINK_INTR & mask & mp->irq_mask_cache) ) phy_dis_irq( mp ); mp->irq_mask_cache = rval & ~mask; fec_wr( mp->base, TSEC_IMASK, (mp->irq_mask_cache & ~TSEC_LINK_INTR) ); return rval; } static inline uint32_t tsec_dis_clr_irqs(struct tsec_private *mp) { uint32_t rval; FEC_Enet_Base b = mp->base; rval = fec_rd( b, TSEC_IEVENT); /* Make sure we mask out the link intr */ rval &= ~TSEC_LINK_INTR; tsec_dis_irqs( mp, rval ); fec_wr( b, TSEC_IEVENT, rval ); return rval; } /* * We have 3 different ISRs just so we can count * interrupt types independently... */ static void tsec_xisr(rtems_irq_hdl_param arg) { struct tsec_private *mp = (struct tsec_private *)arg; rtems_interrupt_level l; rtems_interrupt_disable( l ); mp->irq_pending |= tsec_dis_clr_irqs( mp ); rtems_interrupt_enable( l ); mp->stats.xirqs++; if ( mp->isr ) mp->isr( mp->isr_arg ); else rtems_bsdnet_event_send( mp->tid, mp->event ); } static void tsec_risr(rtems_irq_hdl_param arg) { struct tsec_private *mp = (struct tsec_private *)arg; rtems_interrupt_level l; rtems_interrupt_disable( l ); mp->irq_pending |= tsec_dis_clr_irqs( mp ); rtems_interrupt_enable( l ); mp->stats.rirqs++; if ( mp->isr ) mp->isr( mp->isr_arg ); else rtems_bsdnet_event_send( mp->tid, mp->event ); } static void tsec_eisr(rtems_irq_hdl_param arg) { struct tsec_private *mp = (struct tsec_private *)arg; rtems_interrupt_level l; uint32_t pending; rtems_interrupt_disable( l ); /* make local copy since ISR may ack and clear mp->pending; * also, we want the fresh bits not the ORed state including * the past... */ mp->irq_pending |= (pending = tsec_dis_clr_irqs( mp )); rtems_interrupt_enable( l ); mp->stats.eirqs++; if ( mp->isr ) mp->isr( mp->isr_arg ); else rtems_bsdnet_event_send( mp->tid, mp->event ); if ( (TSEC_IEVENT_TXE & pending) ) { if ( (TSEC_IEVENT_EBERR & pending) && ++mp->stats.eberrs > MAXEBERRS ) { printk(DRVNAME" BAD error: max # of DMA bus errors reached\n"); } else { /* Restart DMA -- do we have to clear DMACTRL[GTS], too ?? */ fec_wr( mp->base, TSEC_TSTAT, TSEC_TSTAT_THLT ); mp->stats.dmarst++; } } } static void tsec_lisr(rtems_irq_hdl_param arg) { struct tsec_private *mp = (struct tsec_private *)arg; rtems_interrupt_level l; if ( phy_irq_pending( mp ) ) { rtems_interrupt_disable( l ); tsec_dis_irqs( mp, TSEC_LINK_INTR ); mp->irq_pending |= TSEC_LINK_INTR; rtems_interrupt_enable( l ); mp->stats.lirqs++; if ( mp->isr ) mp->isr( mp->isr_arg ); else rtems_bsdnet_event_send( mp->tid, mp->event ); } } /* Enable interrupts at device */ void BSP_tsec_enable_irq_mask(struct tsec_private *mp, uint32_t mask) { rtems_interrupt_level l; mask &= mp->irq_mask; rtems_interrupt_disable( l ); if ( (TSEC_LINK_INTR & mask) && ! (TSEC_LINK_INTR & mp->irq_mask_cache) ) phy_en_irq( mp ); mp->irq_mask_cache |= mask; fec_wr( mp->base, TSEC_IMASK, (mp->irq_mask_cache & ~TSEC_LINK_INTR) ); rtems_interrupt_enable( l ); } void BSP_tsec_enable_irqs(struct tsec_private *mp) { BSP_tsec_enable_irq_mask(mp, -1); } /* Disable interrupts at device */ uint32_t BSP_tsec_disable_irq_mask(struct tsec_private *mp, uint32_t mask) { uint32_t rval; rtems_interrupt_level l; rtems_interrupt_disable( l ); rval = tsec_dis_irqs(mp, mask); rtems_interrupt_enable( l ); return rval; } void BSP_tsec_disable_irqs(struct tsec_private *mp) { rtems_interrupt_level l; rtems_interrupt_disable( l ); tsec_dis_irqs(mp, -1); rtems_interrupt_enable( l ); } /* * Acknowledge (and clear) interrupts. * RETURNS: interrupts that were raised. */ uint32_t BSP_tsec_ack_irq_mask(struct tsec_private *mp, uint32_t mask) { uint32_t rval; rtems_interrupt_level l; rtems_interrupt_disable( l ); rval = mp->irq_pending; mp->irq_pending &= ~ mask; rtems_interrupt_enable( l ); if ( (rval & TSEC_LINK_INTR & mask) ) { /* interacting with the PHY is slow so * we do it only if we have to... */ phy_ack_irq( mp ); } return rval & mp->irq_mask; } uint32_t BSP_tsec_ack_irqs(struct tsec_private *mp) { return BSP_tsec_ack_irq_mask(mp, -1); } /* Retrieve the driver daemon TID that was passed to * BSP_tsec_setup(). */ rtems_id BSP_tsec_get_tid(struct tsec_private *mp) { return mp->tid; } struct tsec_private * BSP_tsec_getp(unsigned index) { if ( index >= TSEC_NUM_DRIVER_SLOTS ) return 0; return & theTsecEths[index].pvt; } /* * * Example driver task loop (note: no synchronization of * buffer access shown!). * RTEMS_EVENTx = 0,1 or 2 depending on IF unit. * * / * setup (obtain handle) and initialize hw here * / * * do { * / * ISR disables IRQs and posts event * / * rtems_event_receive( RTEMS_EVENTx, RTEMS_WAIT | RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT, &evs ); * irqs = BSP_tsec_ack_irqs(handle); * if ( irqs & BSP_TSEC_IRQ_TX ) { * BSP_tsec_swipe_tx(handle); / * cleanup_txbuf() callback executed * / * } * if ( irqs & BSP_TSEC_IRQ_RX ) { * BSP_tsec_swipe_rx(handle); / * alloc_rxbuf() and consume_rxbuf() executed * / * } * BSP_tsec_enable_irq_mask(handle, -1); * } while (1); * */ /* BSDNET SUPPORT/GLUE ROUTINES */ STATIC void tsec_stop(struct tsec_softc *sc) { BSP_tsec_stop_hw(&sc->pvt); sc->arpcom.ac_if.if_timer = 0; } /* allocate a mbuf for RX with a properly aligned data buffer * RETURNS 0 if allocation fails */ static void * alloc_mbuf_rx(int *psz, uintptr_t *paddr) { struct mbuf *m; unsigned long l,o; MGETHDR(m, M_DONTWAIT, MT_DATA); if ( !m ) return 0; MCLGET(m, M_DONTWAIT); if ( ! (m->m_flags & M_EXT) ) { m_freem(m); return 0; } o = mtod(m, unsigned long); l = ALIGNTO(o, RX_BUF_ALIGNMENT) - o; /* align start of buffer */ m->m_data += l; /* reduced length */ l = MCLBYTES - l; m->m_len = m->m_pkthdr.len = l; *psz = m->m_len; *paddr = mtod(m, unsigned long); return (void*) m; } static void consume_rx_mbuf(void *buf, void *arg, int len) { struct ifnet *ifp = arg; struct mbuf *m = buf; if ( len <= 0 ) { ifp->if_iqdrops++; if ( len < 0 ) { ifp->if_ierrors++; } if ( m ) m_freem(m); } else { struct ether_header *eh; eh = (struct ether_header *)(mtod(m, unsigned long) + ETH_RX_OFFSET); m->m_len = m->m_pkthdr.len = len - sizeof(struct ether_header) - ETH_RX_OFFSET - ETH_CRC_LEN; m->m_data += sizeof(struct ether_header) + ETH_RX_OFFSET; m->m_pkthdr.rcvif = ifp; ifp->if_ipackets++; ifp->if_ibytes += m->m_pkthdr.len; /* send buffer upwards */ if (0) { /* Low-level debugging */ int i; for (i=0; i<13; i++) { printf("%02X:",((char*)eh)[i]); } printf("%02X\n",((char*)eh)[i]); for (i=0; im_len; i++) { if ( !(i&15) ) printf("\n"); printf("0x%02x ",mtod(m,char*)[i]); } printf("\n"); } if (0) /* Low-level debugging/testing without bsd stack */ m_freem(m); else ether_input(ifp, eh, m); } } static void release_tx_mbuf(void *buf, void *arg, int err) { struct ifnet *ifp = arg; struct mbuf *mb = buf; if ( err ) { ifp->if_oerrors++; } else { ifp->if_opackets++; } ifp->if_obytes += mb->m_pkthdr.len; m_freem(mb); } /* BSDNET DRIVER CALLBACKS */ static void tsec_init(void *arg) { struct tsec_softc *sc = arg; struct ifnet *ifp = &sc->arpcom.ac_if; int media; BSP_tsec_init_hw(&sc->pvt, ifp->if_flags & IFF_PROMISC, sc->arpcom.ac_enaddr); /* Determine initial link status and block sender if there is no link */ media = IFM_MAKEWORD(0, 0, 0, 0); if ( 0 == BSP_tsec_media_ioctl(&sc->pvt, SIOCGIFMEDIA, &media) ) { if ( (IFM_LINK_OK & media) ) { ifp->if_flags &= ~IFF_OACTIVE; } else { ifp->if_flags |= IFF_OACTIVE; } } tsec_update_mcast(ifp); ifp->if_flags |= IFF_RUNNING; sc->arpcom.ac_if.if_timer = 0; } /* bsdnet driver entry to start transmission */ static void tsec_start(struct ifnet *ifp) { struct tsec_softc *sc = ifp->if_softc; struct mbuf *m = 0; while ( ifp->if_snd.ifq_head ) { IF_DEQUEUE( &ifp->if_snd, m ); if ( BSP_tsec_send_buf(&sc->pvt, m, 0, 0) < 0 ) { IF_PREPEND( &ifp->if_snd, m); ifp->if_flags |= IFF_OACTIVE; break; } /* need to do this really only once * but it's cheaper this way. */ ifp->if_timer = 2*IFNET_SLOWHZ; } } /* bsdnet driver entry; */ static void tsec_watchdog(struct ifnet *ifp) { struct tsec_softc *sc = ifp->if_softc; ifp->if_oerrors++; printk(DRVNAME"%i: watchdog timeout; resetting\n", ifp->if_unit); tsec_init(sc); tsec_start(ifp); } static void tsec_update_mcast(struct ifnet *ifp) { struct tsec_softc *sc = ifp->if_softc; struct ether_multi *enm; struct ether_multistep step; if ( IFF_ALLMULTI & ifp->if_flags ) { BSP_tsec_mcast_filter_accept_all( &sc->pvt ); } else { BSP_tsec_mcast_filter_clear( &sc->pvt ); ETHER_FIRST_MULTI(step, (struct arpcom *)ifp, enm); while ( enm ) { if ( memcmp(enm->enm_addrlo, enm->enm_addrhi, ETHER_ADDR_LEN) ) assert( !"Should never get here; IFF_ALLMULTI should be set!" ); BSP_tsec_mcast_filter_accept_add(&sc->pvt, enm->enm_addrlo); ETHER_NEXT_MULTI(step, enm); } } } /* bsdnet driver ioctl entry */ static int tsec_ioctl(struct ifnet *ifp, ioctl_command_t cmd, caddr_t data) { struct tsec_softc *sc = ifp->if_softc; struct ifreq *ifr = (struct ifreq *)data; #if 0 uint32_t v; #endif int error = 0; int f; switch ( cmd ) { case SIOCSIFFLAGS: f = ifp->if_flags; if ( f & IFF_UP ) { if ( ! ( f & IFF_RUNNING ) ) { tsec_init(sc); } else { if ( (f & IFF_PROMISC) != (sc->bsd.oif_flags & IFF_PROMISC) ) { /* Hmm - the manual says we must change the RCTRL * register only after a reset or if DMACTRL[GRS] * is cleared which is the normal operating * condition. I hope this is legal ?? */ if ( (f & IFF_PROMISC) ) { fec_set( sc->pvt.base, TSEC_RCTRL, TSEC_RCTRL_PROM ); } else { fec_clr( sc->pvt.base, TSEC_RCTRL, TSEC_RCTRL_PROM ); } } /* FIXME: other flag changes are ignored/unimplemented */ } } else { if ( f & IFF_RUNNING ) { tsec_stop(sc); ifp->if_flags &= ~(IFF_RUNNING | IFF_OACTIVE); } } sc->bsd.oif_flags = ifp->if_flags; break; case SIOCGIFMEDIA: case SIOCSIFMEDIA: error = BSP_tsec_media_ioctl(&sc->pvt, cmd, &ifr->ifr_media); break; case SIOCADDMULTI: case SIOCDELMULTI: error = (cmd == SIOCADDMULTI) ? ether_addmulti(ifr, &sc->arpcom) : ether_delmulti(ifr, &sc->arpcom); if (error == ENETRESET) { if (ifp->if_flags & IFF_RUNNING) { tsec_update_mcast(ifp); } error = 0; } break; case SIO_RTEMS_SHOW_STATS: BSP_tsec_dump_stats( &sc->pvt, stdout ); break; default: error = ether_ioctl(ifp, cmd, data); break; } return error; } /* DRIVER TASK */ /* Daemon task does all the 'interrupt' work */ static void tsec_daemon(void *arg) { struct tsec_softc *sc; struct ifnet *ifp; rtems_event_set evs; for (;;) { rtems_bsdnet_event_receive( EV_MSK, RTEMS_WAIT | RTEMS_EVENT_ANY, RTEMS_NO_TIMEOUT, &evs ); evs &= EV_MSK; for ( sc = theTsecEths; evs; evs>>=EV_PER_UNIT, sc++ ) { if ( EV_IS_ANY(evs) ) { register uint32_t x; ifp = &sc->arpcom.ac_if; if ( !(ifp->if_flags & IFF_UP) ) { tsec_stop(sc); ifp->if_flags &= ~(IFF_UP|IFF_RUNNING); continue; } if ( !(ifp->if_flags & IFF_RUNNING) ) { /* event could have been pending at the time hw was stopped; * just ignore... */ continue; } x = BSP_tsec_ack_irqs(&sc->pvt); if ( TSEC_LINK_INTR & x ) { /* phy status changed */ int media; #ifdef DEBUG printf("LINK INTR\n"); #endif if ( -1 != (media = mac_set_duplex( &sc->pvt )) ) { #ifdef DEBUG rtems_ifmedia2str( media, 0, 0 ); printf("\n"); #endif if ( IFM_LINK_OK & media ) { ifp->if_flags &= ~IFF_OACTIVE; tsec_start(ifp); } else { /* stop sending */ ifp->if_flags |= IFF_OACTIVE; } } } /* free tx chain */ if ( (TSEC_TXIRQ & x) && BSP_tsec_swipe_tx(&sc->pvt) ) { ifp->if_flags &= ~IFF_OACTIVE; if ( TX_AVAILABLE_RING_SIZE(&sc->pvt) == sc->pvt.tx_avail ) ifp->if_timer = 0; tsec_start(ifp); } if ( (TSEC_RXIRQ & x) ) BSP_tsec_swipe_rx(&sc->pvt); BSP_tsec_enable_irq_mask(&sc->pvt, -1); } } } } /* PUBLIC RTEMS BSDNET ATTACH FUNCTION */ int rtems_tsec_attach(struct rtems_bsdnet_ifconfig *ifcfg, int attaching) { char *unitName; int unit,i,cfgUnits; struct tsec_softc *sc; struct ifnet *ifp; unit = rtems_bsdnet_parse_driver_name(ifcfg, &unitName); if ( unit <= 0 || unit > TSEC_NUM_DRIVER_SLOTS ) { printk(DRVNAME": Bad unit number %i; must be 1..%i\n", unit, TSEC_NUM_DRIVER_SLOTS); return 1; } sc = &theTsecEths[unit-1]; ifp = &sc->arpcom.ac_if; if ( attaching ) { if ( ifp->if_init ) { printk(DRVNAME": instance %i already attached.\n", unit); return -1; } for ( i=cfgUnits = 0; irbuf_count, ifcfg->xbuf_count, TSEC_RXIRQ | TSEC_TXIRQ | TSEC_LINK_INTR) ) { return -1; } if ( nmbclusters < sc->pvt.rx_ring_size * cfgUnits + 60 /* arbitrary */ ) { printk(DRVNAME"%i: (tsec ethernet) Your application has not enough mbuf clusters\n", unit); printk( " configured for this driver.\n"); return -1; } if ( ifcfg->hardware_address ) { memcpy(sc->arpcom.ac_enaddr, ifcfg->hardware_address, ETHER_ADDR_LEN); } else { /* read back from hardware assuming that MotLoad already had set it up */ BSP_tsec_read_eaddr(&sc->pvt, sc->arpcom.ac_enaddr); } ifp->if_softc = sc; ifp->if_unit = unit; ifp->if_name = unitName; ifp->if_mtu = ifcfg->mtu ? ifcfg->mtu : ETHERMTU; ifp->if_init = tsec_init; ifp->if_ioctl = tsec_ioctl; ifp->if_start = tsec_start; ifp->if_output = ether_output; /* * While nonzero, the 'if->if_timer' is decremented * (by the networking code) at a rate of IFNET_SLOWHZ (1hz) and 'if_watchdog' * is called when it expires. * If either of those fields is 0 the feature is disabled. */ ifp->if_watchdog = tsec_watchdog; ifp->if_timer = 0; sc->bsd.oif_flags = /* ... */ ifp->if_flags = IFF_BROADCAST | IFF_MULTICAST | IFF_SIMPLEX; /* * if unset, this set to 10Mbps by ether_ifattach; seems to be unused by bsdnet stack; * could be updated along with phy speed, though... ifp->if_baudrate = 10000000; */ /* NOTE: ether_output drops packets if ifq_len >= ifq_maxlen * but this is the packet count, not the fragment count! ifp->if_snd.ifq_maxlen = sc->pvt.tx_ring_size; */ ifp->if_snd.ifq_maxlen = ifqmaxlen; #ifdef TSEC_DETACH_HACK if ( !ifp->if_addrlist ) /* do only the first time [reattach hack] */ #endif { if_attach(ifp); ether_ifattach(ifp); } } else { #ifdef TSEC_DETACH_HACK if ( !ifp->if_init ) { printk(DRVNAME": instance %i not attached.\n", unit); return -1; } return tsec_detach(sc); #else printk(DRVNAME": interface detaching not implemented\n"); return -1; #endif } return 0; } /* PHY stuff should really not be in this driver but separate :-( * However, I don't have time right now to implement clean * boundaries: * - PHY driver should only know about the PHY * - TSEC driver only provides MII access and knows * how to deal with a PHY interrupt. * - BSP knows how interrupts are routed. E.g., the MVME3100 * shares a single IRQ line among 3 PHYs (for the three ports) * and provides a special 'on-board' register for determining * what PHY raised an interrupt w/o the need to do any MII IO. * Integrating all these bits in a clean way is not as easy as * just hacking away, sorry... */ /* * Broadcom 54xx PHY register definitions. Unfriendly Broadcom doesn't * release specs for their products :-( -- valuable info comes from * the linux driver by * Maciej W. Rozycki * Amy Fong */ #define BCM54xx_GBCR 0x09 /* gigabit control */ #define BCM54xx_GBCR_FD (1<<9) /* full-duplex cap. */ #define BCM54xx_ECR 0x10 /* extended control */ #define BCM54xx_ECR_IM (1<<12) /* IRQ mask */ #define BCM54xx_ECR_IF (1<<12) /* IRQ force */ #define BCM54xx_ESR 0x11 /* extended status */ #define BCM54xx_ESR_IRQ (1<<12) /* IRQ pending */ #define BCM54xx_AUXCR 0x18 /* AUX control */ #define BCM54xx_AUXCR_PWR10BASET (1<<2) #define BCM54xx_AUXST 0x19 /* AUX status */ #define BCM54xx_AUXST_LNKMM (7<<8) /* link mode mask */ /* link mode (linux' syngem_phy.c helped here...) * * 0: no link * 1: 10BT half * 2: 10BT full * 3: 100BT half * 4: 100BT half * 5: 100BT full * 6: 1000BT full * 7: 1000BT full */ #define BCM54xx_ISR 0x1a /* IRQ status */ #define BCM54xx_IMR 0x1b /* IRQ mask */ #define BCM54xx_IRQ_CRC (1<< 0) /* CRC error */ #define BCM54xx_IRQ_LNK (1<< 1) /* LINK status chg. */ #define BCM54xx_IRQ_SPD (1<< 2) /* SPEED change */ #define BCM54xx_IRQ_DUP (1<< 3) /* LINK status chg. */ #define BCM54xx_IRQ_LRS (1<< 4) /* Lcl. RX status chg.*/ #define BCM54xx_IRQ_RRS (1<< 5) /* Rem. RX status chg.*/ #define BCM54xx_IRQ_SSE (1<< 6) /* Scrambler sync err */ #define BCM54xx_IRQ_UHCD (1<< 7) /* Unsupp. HCD neg. */ #define BCM54xx_IRQ_NHCD (1<< 8) /* No HCD */ #define BCM54xx_IRQ_HCDL (1<< 9) /* No HCD Link */ #define BCM54xx_IRQ_ANPR (1<<10) /* Aneg. pg. req. */ #define BCM54xx_IRQ_LC (1<<11) /* All ctrs. < 128 */ #define BCM54xx_IRQ_HC (1<<12) /* Ctr > 32768 */ #define BCM54xx_IRQ_MDIX (1<<13) /* MDIX status chg. */ #define BCM54xx_IRQ_PSERR (1<<14) /* Pair swap error */ #define PHY_IRQS ( BCM54xx_IRQ_LNK | BCM54xx_IRQ_SPD | BCM54xx_IRQ_DUP ) static void phy_en_irq_at_phy( struct tsec_private *mp ) { uint32_t ecr; REGLOCK(); tsec_mdio_rd( 0, mp, BCM54xx_ECR, &ecr ); ecr &= ~BCM54xx_ECR_IM; tsec_mdio_wr( 0, mp, BCM54xx_ECR, ecr ); REGUNLOCK(); } static void phy_dis_irq_at_phy( struct tsec_private *mp ) { uint32_t ecr; REGLOCK(); tsec_mdio_rd( 0, mp, BCM54xx_ECR, &ecr ); ecr |= BCM54xx_ECR_IM; tsec_mdio_wr( 0, mp, BCM54xx_ECR, ecr ); REGUNLOCK(); } static void phy_init_irq( int install, struct tsec_private *mp, void (*isr)(rtems_irq_hdl_param) ) { uint32_t v; rtems_irq_connect_data xxx; xxx.on = noop; xxx.off = noop; xxx.isOn = nopf; xxx.name = BSP_PHY_IRQ; xxx.handle = mp; xxx.hdl = isr; phy_dis_irq_at_phy( mp ); REGLOCK(); /* Select IRQs we want */ tsec_mdio_wr( 0, mp, BCM54xx_IMR, ~ PHY_IRQS ); /* clear pending irqs */ tsec_mdio_rd( 0, mp, BCM54xx_ISR, &v ); REGUNLOCK(); /* install shared ISR */ if ( ! (install ? BSP_install_rtems_shared_irq_handler( &xxx ) : BSP_remove_rtems_irq_handler( &xxx )) ) { rtems_panic( "Unable to %s shared irq handler (PHY)\n", install ? "install" : "remove" ); } } /* Because on the MVME3100 multiple PHYs (belonging to different * TSEC instances) share a single interrupt line and we want * to disable interrupts at the PIC rather than in the individual * PHYs (because access to those is slow) we must implement * nesting... */ STATIC int phy_irq_dis_level = 0; /* assume 'en_irq' / 'dis_irq' cannot be interrupted. * Either because they are called from an ISR (all * tsec + phy isrs must have the same priority) or * from a IRQ-protected section of code */ static void phy_en_irq(struct tsec_private *mp) { phy_irq_dis_level &= ~(1<unit); if ( 0 == phy_irq_dis_level ) { BSP_enable_irq_at_pic( BSP_PHY_IRQ ); } } static void phy_dis_irq(struct tsec_private *mp) { phy_irq_dis_level |= (1<unit); BSP_disable_irq_at_pic( BSP_PHY_IRQ ); } static int phy_irq_pending(struct tsec_private *mp) { /* MVME3100 speciality: we can check for a pending * PHY IRQ w/o having to access the MII bus :-) */ return in_8( BSP_MVME3100_IRQ_DETECT_REG ) & (1 << (mp->unit - 1)); } static uint32_t phy_ack_irq(struct tsec_private *mp) { uint32_t v; REGLOCK(); tsec_mdio_rd( 0, mp, BCM54xx_ISR, &v ); REGUNLOCK(); #ifdef DEBUG printf("phy_ack_irq: 0x%08"PRIx32"\n", v); #endif return v; } #if defined(PARANOIA) || defined(DEBUG) static void dumpbd(TSEC_BD *bd) { printf("Flags 0x%04"PRIx16", len 0x%04"PRIx16", buf 0x%08"PRIx32"\n", bd_rdfl( bd ), ld_be16( &bd->len ), bd_rdbuf( bd ) ); } void tsec_dump_rring(struct tsec_private *mp) { int i; TSEC_BD *bd; if ( !mp ) { printf("Neet tsec_private * arg\n"); return; } for ( i=0; irx_ring_size; i++ ) { bd = &mp->rx_ring[i]; printf("[%i]: ", i); dumpbd(bd); } } void tsec_dump_tring(struct tsec_private *mp) { int i; TSEC_BD *bd; if ( !mp ) { printf("Neet tsec_private * arg\n"); return; } for ( i=0; itx_ring_size; i++ ) { bd = &mp->tx_ring[i]; printf("[%i]: ", i); dumpbd(bd); } printf("Avail: %i; Head %i; Tail %i\n", mp->tx_avail, mp->tx_head, mp->tx_tail); } #endif #ifdef DEBUG #undef free #undef malloc #include void cleanup_txbuf_test(void *u, void *a, int err) { printf("cleanup_txbuf_test (releasing buf 0x%8p)\n", u); free(u); if ( err ) printf("cleanup_txbuf_test: an error was detected\n"); } void *alloc_rxbuf_test(int *p_size, uintptr_t *p_data_addr) { void *rval; *p_size = 1600; rval = malloc( *p_size + RX_BUF_ALIGNMENT ); *p_data_addr = (uintptr_t)ALIGNTO(rval,RX_BUF_ALIGNMENT); /* PRIxPTR is still broken :-( */ printf("alloc_rxxbuf_test: allocated %i bytes @0x%8p/0x%08"PRIx32"\n", *p_size, rval, (uint32_t)*p_data_addr); return rval; } void consume_rxbuf_test(void *user_buf, void *consume_rxbuf_arg, int len) { int i; uintptr_t d = (uintptr_t)ALIGNTO(user_buf,RX_BUF_ALIGNMENT); /* PRIxPTR is still broken */ printf("consuming rx buf 0x%8p (data@ 0x%08"PRIx32")\n",user_buf, (uint32_t)d); if ( len > 32 ) len = 32; if ( len < 0 ) printf("consume_rxbuf_test: ERROR occurred: 0x%x\n", len); else { printf("RX:"); for ( i=0; i