forked from Imagelibrary/rtems
Add and use <machine/rtems-bsd-kernel-space.h> and <machine/rtems-bsd-user-space.h> similar to the libbsd to avoid command line defines and defines scattered throught the code base. Simplify cpukit/libnetworking/Makefile.am. Update #3375.
962 lines
28 KiB
C
962 lines
28 KiB
C
/*
|
|
* Driver for Xilinx plb temac v3.00a
|
|
*
|
|
* Author: Keith Robertson <kjrobert@alumni.uwaterloo.ca>
|
|
* Copyright (c) 2007 Linn Products Ltd, Scotland.
|
|
*
|
|
* The license and distribution terms for this file may be
|
|
* found in the file LICENSE in this distribution or at
|
|
* http://www.rtems.org/license/LICENSE.
|
|
*
|
|
*/
|
|
|
|
#include <machine/rtems-bsd-kernel-space.h>
|
|
#define PPC_HAS_CLASSIC_EXCEPTIONS FALSE
|
|
|
|
#include <bsp.h>
|
|
#include <rtems/bspIo.h>
|
|
#include <rtems/rtems_bsdnet.h>
|
|
#include <inttypes.h>
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/mbuf.h>
|
|
|
|
#include <sys/socket.h>
|
|
#include <sys/sockio.h>
|
|
#include <net/if.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
|
|
#include <xiltemac.h>
|
|
#include <rtems/irq.h>
|
|
|
|
/* Reading/Writing memory mapped i/o */
|
|
#define IN32(aPtr) ((uint32_t)( *((volatile uint32_t *)(aPtr))) )
|
|
#define OUT32(aPtr, aValue) (*((volatile uint32_t *)(aPtr)) = (uint32_t)aValue)
|
|
#define NUM_XILTEMAC_UNITS 2
|
|
|
|
/* Why isn't this defined in stdio.h like it's supposed to be? */
|
|
extern int snprintf(char*, size_t, const char*, ...);
|
|
|
|
extern rtems_isr xilTemacIsr( void *handle );
|
|
extern void xilTemacIsrOn(const rtems_irq_connect_data *);
|
|
extern void xilTemacIsrOff(const rtems_irq_connect_data *);
|
|
extern int xilTemacIsrIsOn(const rtems_irq_connect_data *);
|
|
|
|
void xilTemacInit( void *voidptr );
|
|
void xilTemacReset(struct ifnet *ifp);
|
|
void xilTemacStop(struct ifnet *ifp);
|
|
void xilTemacSend(struct ifnet *ifp);
|
|
void xilTemacStart(struct ifnet *ifp);
|
|
void xilTemacSetMacAddress(struct ifnet *ifp, unsigned char* aAddr);
|
|
void xilTemacPrintStats(struct ifnet *ifp);
|
|
|
|
void xilTemacRxThread( void *ignore );
|
|
void xilTemacTxThread( void *ignore );
|
|
|
|
static struct XilTemac gXilTemac[ NUM_XILTEMAC_UNITS ];
|
|
|
|
static rtems_id gXilRxThread = 0;
|
|
static rtems_id gXilTxThread = 0;
|
|
|
|
/*
|
|
** Events, one per unit. The event is sent to the rx task from the isr
|
|
** or from the stack to the tx task whenever a unit needs service. The
|
|
** rx/tx tasks identify the requesting unit(s) by their particular
|
|
** events so only requesting units are serviced.
|
|
*/
|
|
|
|
static rtems_event_set gUnitSignals[ NUM_XILTEMAC_UNITS ]= { RTEMS_EVENT_1,
|
|
RTEMS_EVENT_2 };
|
|
|
|
static uint32_t xilTemacTxFifoVacancyBytes(uint32_t aBaseAddr)
|
|
{
|
|
uint32_t ipisr = IN32(aBaseAddr + XTE_IPISR_OFFSET);
|
|
uint32_t bytes = 0;
|
|
if(ipisr & XTE_IPXR_XMIT_LFIFO_FULL_MASK) {
|
|
/* If there's no room in the transmit length fifo, then any room in the
|
|
* data fifo is irrelevant, return 0 */
|
|
} else {
|
|
bytes = IN32(aBaseAddr + XTE_PFIFO_TX_VACANCY_OFFSET);
|
|
bytes &= XTE_PFIFO_COUNT_MASK;
|
|
bytes *= 8;
|
|
}
|
|
return bytes;
|
|
}
|
|
|
|
static void xilTemacFifoRead64(uint32_t aBaseAddr, uint32_t* aBuf, uint32_t aBytes)
|
|
{
|
|
uint32_t numqwords = aBytes / 8;
|
|
uint32_t xtrabytes = aBytes % 8;
|
|
uint32_t i;
|
|
|
|
for(i = 0; i < numqwords; i++)
|
|
{
|
|
aBuf[ (i*2) ] = IN32(aBaseAddr + XTE_PFIFO_RX_DATA_OFFSET);
|
|
aBuf[ (i*2)+1 ] = IN32(aBaseAddr + XTE_PFIFO_RX_DATA_OFFSET + 4);
|
|
}
|
|
|
|
/* If there was a non qword sized read */
|
|
if( xtrabytes != 0 )
|
|
{
|
|
uint32_t lastdwordMS = IN32(aBaseAddr + XTE_PFIFO_RX_DATA_OFFSET);
|
|
uint32_t lastdwordLS = IN32(aBaseAddr + XTE_PFIFO_RX_DATA_OFFSET + 4);
|
|
uint8_t* finalbytes = (uint8_t *)&aBuf[ (numqwords*2) ];
|
|
uint8_t* ptr8;
|
|
int32_t offset = 0;
|
|
|
|
ptr8 = (uint8_t *)&lastdwordMS;
|
|
if( xtrabytes >= 4 )
|
|
{
|
|
finalbytes[ offset++ ] = ptr8[0];
|
|
finalbytes[ offset++ ] = ptr8[1];
|
|
finalbytes[ offset++ ] = ptr8[2];
|
|
finalbytes[ offset++ ] = ptr8[3];
|
|
|
|
xtrabytes -= 4;
|
|
ptr8 = (uint8_t *)&lastdwordLS;
|
|
}
|
|
|
|
if( xtrabytes == 1 )
|
|
{
|
|
finalbytes[ offset++ ] = ptr8[0];
|
|
}
|
|
else if ( xtrabytes == 2 )
|
|
{
|
|
finalbytes[ offset++ ] = ptr8[0];
|
|
finalbytes[ offset++ ] = ptr8[1];
|
|
}
|
|
else if ( xtrabytes == 3 )
|
|
{
|
|
finalbytes[ offset++ ] = ptr8[0];
|
|
finalbytes[ offset++ ] = ptr8[1];
|
|
finalbytes[ offset++ ] = ptr8[2];
|
|
}
|
|
}
|
|
}
|
|
|
|
static void xilTemacFifoWrite64(uint32_t aBaseAddr, uint32_t* aBuf, uint32_t aBytes)
|
|
{
|
|
uint32_t numqwords = aBytes / 8;
|
|
uint32_t xtrabytes = aBytes % 8;
|
|
uint32_t i;
|
|
|
|
for(i = 0; i < numqwords; i++ ) {
|
|
OUT32(aBaseAddr + XTE_PFIFO_TX_DATA_OFFSET , aBuf[ (i*2) ]);
|
|
OUT32(aBaseAddr + XTE_PFIFO_TX_DATA_OFFSET + 4, aBuf[ (i*2)+1 ]);
|
|
}
|
|
|
|
/* If there was a non word sized write */
|
|
if( xtrabytes != 0 ) {
|
|
uint32_t lastdwordMS = 0;
|
|
uint32_t lastdwordLS = 0;
|
|
uint8_t* finalbytes = (uint8_t *)&aBuf[ (numqwords*2) ];
|
|
uint8_t* ptr8;
|
|
int32_t offset = 0;
|
|
|
|
ptr8 = (uint8_t *)&lastdwordMS;
|
|
|
|
if( xtrabytes >= 4 ) {
|
|
ptr8[0] = finalbytes[ offset++ ];
|
|
ptr8[1] = finalbytes[ offset++ ];
|
|
ptr8[2] = finalbytes[ offset++ ];
|
|
ptr8[3] = finalbytes[ offset++ ];
|
|
|
|
xtrabytes -= 4;
|
|
|
|
ptr8 = (uint8_t *)&lastdwordLS;
|
|
}
|
|
|
|
if( xtrabytes == 1 ) {
|
|
ptr8[0] = finalbytes[ offset++ ];
|
|
}
|
|
else if ( xtrabytes == 2 ) {
|
|
ptr8[0] = finalbytes[ offset++ ];
|
|
ptr8[1] = finalbytes[ offset++ ];
|
|
}
|
|
else if ( xtrabytes == 3 ) {
|
|
ptr8[0] = finalbytes[ offset++ ];
|
|
ptr8[1] = finalbytes[ offset++ ];
|
|
ptr8[2] = finalbytes[ offset++ ];
|
|
}
|
|
|
|
OUT32(aBaseAddr + XTE_PFIFO_TX_DATA_OFFSET, lastdwordMS);
|
|
OUT32(aBaseAddr + XTE_PFIFO_TX_DATA_OFFSET + 4, lastdwordLS);
|
|
}
|
|
}
|
|
|
|
void xilTemacStop(struct ifnet *ifp)
|
|
{
|
|
struct XilTemac* xilTemac = ifp->if_softc;
|
|
uint32_t base = xilTemac->iAddr;
|
|
|
|
/* Disable ipif interrupts */
|
|
OUT32(base + XTE_DGIE_OFFSET, 0);
|
|
|
|
/* Disable the receiver */
|
|
uint32_t rxc1 = IN32(base + XTE_ERXC1_OFFSET);
|
|
rxc1 &= ~XTE_ERXC1_RXEN_MASK;
|
|
OUT32(base + XTE_ERXC1_OFFSET, rxc1);
|
|
|
|
/* If receiver was receiving a packet when we disabled it, it will be
|
|
* rejected, clear appropriate status bit */
|
|
uint32_t ipisr = IN32(base + XTE_IPISR_OFFSET);
|
|
if( ipisr & XTE_IPXR_RECV_REJECT_MASK ) {
|
|
OUT32(base + XTE_IPISR_OFFSET, XTE_IPXR_RECV_REJECT_MASK);
|
|
}
|
|
|
|
#if PPC_HAS_CLASSIC_EXCEPTIONS
|
|
if( xilTemac->iOldHandler )
|
|
{
|
|
opb_intc_set_vector( xilTemac->iOldHandler, xilTemac->iIsrVector, NULL );
|
|
xilTemac->iOldHandler = 0;
|
|
}
|
|
#else
|
|
if( xilTemac->iOldHandler.name != 0)
|
|
{
|
|
BSP_install_rtems_irq_handler (&xilTemac->iOldHandler);
|
|
}
|
|
#endif
|
|
|
|
ifp->if_flags &= ~IFF_RUNNING;
|
|
}
|
|
|
|
void xilTemacStart(struct ifnet *ifp)
|
|
{
|
|
if( (ifp->if_flags & IFF_RUNNING) == 0 )
|
|
{
|
|
struct XilTemac* xilTemac = ifp->if_softc;
|
|
uint32_t base = xilTemac->iAddr;
|
|
|
|
/* Reset plb temac */
|
|
OUT32(base + XTE_DSR_OFFSET, XTE_DSR_RESET_MASK);
|
|
/* Don't have usleep on rtems 4.6
|
|
usleep(1);
|
|
*/
|
|
/* @ fastest ppc clock of 500 MHz = 2ns clk */
|
|
uint32_t i = 0;
|
|
for( i = 0; i < 1 * 500; i++) {
|
|
}
|
|
|
|
/* Reset hard temac */
|
|
OUT32(base + XTE_CR_OFFSET, XTE_CR_HTRST_MASK);
|
|
/* Don't have usleep on rtems 4.6
|
|
usleep(4);
|
|
*/
|
|
for( i = 0; i < 4 * 500; i++) {
|
|
}
|
|
|
|
/* Disable the receiver -- no need to disable xmit as we control that ;) */
|
|
uint32_t rxc1 = IN32(base + XTE_ERXC1_OFFSET);
|
|
rxc1 &= ~XTE_ERXC1_RXEN_MASK;
|
|
OUT32(base + XTE_ERXC1_OFFSET, rxc1);
|
|
|
|
/* If receiver was receiving a packet when we disabled it, it will be
|
|
* rejected, clear appropriate status bit */
|
|
uint32_t ipisr = IN32(base + XTE_IPISR_OFFSET);
|
|
if( ipisr & XTE_IPXR_RECV_REJECT_MASK ) {
|
|
OUT32(base + XTE_IPISR_OFFSET, XTE_IPXR_RECV_REJECT_MASK);
|
|
}
|
|
|
|
/* Setup IPIF interrupt enables */
|
|
uint32_t dier = XTE_DXR_CORE_MASK | XTE_DXR_DPTO_MASK | XTE_DXR_TERR_MASK;
|
|
dier |= XTE_DXR_RECV_FIFO_MASK | XTE_DXR_SEND_FIFO_MASK;
|
|
OUT32(base + XTE_DIER_OFFSET, dier);
|
|
|
|
/* Set the mac address */
|
|
xilTemacSetMacAddress( ifp, xilTemac->iArpcom.ac_enaddr);
|
|
|
|
/* Set the link speed */
|
|
uint32_t emcfg = IN32(base + XTE_ECFG_OFFSET);
|
|
printk("xiltemacStart, default linkspeed: %08" PRIx32 "\n", emcfg);
|
|
emcfg = (emcfg & ~XTE_ECFG_LINKSPD_MASK) | XTE_ECFG_LINKSPD_100;
|
|
OUT32(base + XTE_ECFG_OFFSET, emcfg);
|
|
|
|
/* Set phy divisor and enable mdio. For a plb bus freq of 150MHz (the
|
|
maximum as of Virtex4 Fx), a divisor of 29 gives a mdio clk freq of
|
|
2.5MHz (see Xilinx docs for equation), the maximum in the phy standard.
|
|
For slower plb frequencies, slower mkdio clks will result. They may not
|
|
be optimal, but they should work. */
|
|
uint32_t divisor = 29;
|
|
OUT32(base + XTE_EMC_OFFSET, divisor | XTE_EMC_MDIO_MASK);
|
|
|
|
#if PPC_HAS_CLASSIC_EXCEPTIONS /* old connect code */
|
|
/* Connect isr vector */
|
|
rtems_status_code sc;
|
|
extern rtems_isr xilTemacIsr( rtems_vector_number aVector );
|
|
sc = opb_intc_set_vector( xilTemacIsr, xilTemac->iIsrVector, &xilTemac->iOldHandler );
|
|
if( sc != RTEMS_SUCCESSFUL )
|
|
{
|
|
xilTemac->iOldHandler = 0;
|
|
printk("%s: Could not set interrupt vector for interface '%s' opb_intc_set_vector ret: %d\n", DRIVER_PREFIX, xilTemac->iUnitName, sc );
|
|
assert(0);
|
|
}
|
|
#else
|
|
{
|
|
rtems_irq_connect_data IrqConnData;
|
|
|
|
/*
|
|
*get old irq handler
|
|
*/
|
|
xilTemac->iOldHandler.name = xilTemac->iIsrVector;
|
|
if (!BSP_get_current_rtems_irq_handler (&xilTemac->iOldHandler)) {
|
|
xilTemac->iOldHandler.name = 0;
|
|
printk("%s: Unable to detect previous Irq handler\n",DRIVER_PREFIX);
|
|
rtems_fatal_error_occurred(1);
|
|
}
|
|
|
|
IrqConnData.on = xilTemacIsrOn;
|
|
IrqConnData.off = xilTemacIsrOff;
|
|
IrqConnData.isOn = xilTemacIsrIsOn;
|
|
IrqConnData.name = xilTemac->iIsrVector;
|
|
IrqConnData.hdl = xilTemacIsr;
|
|
IrqConnData.handle = xilTemac;
|
|
|
|
if (!BSP_install_rtems_irq_handler (&IrqConnData)) {
|
|
printk("%s: Unable to connect Irq handler\n",DRIVER_PREFIX);
|
|
rtems_fatal_error_occurred(1);
|
|
}
|
|
}
|
|
#endif
|
|
/* Enable promiscuous mode -- The temac only supports full duplex, which
|
|
means we're plugged into a switch. Thus promiscuous mode simply means
|
|
we get all multicast addresses*/
|
|
OUT32(base + XTE_EAFM_OFFSET, XTE_EAFM_EPPRM_MASK);
|
|
|
|
/* Setup and enable receiver */
|
|
rxc1 = XTE_ERXC1_RXFCS_MASK | XTE_ERXC1_RXEN_MASK | XTE_ERXC1_RXVLAN_MASK;
|
|
OUT32(base + XTE_ERXC1_OFFSET, rxc1);
|
|
|
|
/* Setup and enable transmitter */
|
|
uint32_t txc = XTE_ETXC_TXEN_MASK | XTE_ETXC_TXVLAN_MASK;
|
|
OUT32(base + XTE_ETXC_OFFSET, txc);
|
|
|
|
/* Enable interrupts for temac */
|
|
uint32_t ipier = IN32(base + XTE_IPIER_OFFSET);
|
|
ipier |= (XTE_IPXR_XMIT_ERROR_MASK);
|
|
ipier |= (XTE_IPXR_RECV_ERROR_MASK | XTE_IPXR_RECV_DONE_MASK);
|
|
ipier |= (XTE_IPXR_AUTO_NEG_MASK);
|
|
OUT32(base + XTE_IPIER_OFFSET, ipier);
|
|
|
|
printk("%s: xiltemacStart, ipier: %08" PRIx32 "\n",DRIVER_PREFIX, ipier);
|
|
|
|
/* Enable device global interrutps */
|
|
OUT32(base + XTE_DGIE_OFFSET, XTE_DGIE_ENABLE_MASK);
|
|
ifp->if_flags |= IFF_RUNNING;
|
|
}
|
|
}
|
|
|
|
void xilTemacInit( void *voidptr )
|
|
{
|
|
}
|
|
|
|
void xilTemacReset(struct ifnet *ifp)
|
|
{
|
|
xilTemacStop( ifp );
|
|
xilTemacStart( ifp );
|
|
}
|
|
|
|
void xilTemacSetMacAddress(struct ifnet *ifp, unsigned char* aAddr)
|
|
{
|
|
struct XilTemac* xilTemac = ifp->if_softc;
|
|
uint32_t base = xilTemac->iAddr;
|
|
|
|
/* You can't change the mac address while the card is in operation */
|
|
if( (ifp->if_flags & IFF_RUNNING) != 0 ) {
|
|
printk("%s: attempted to change MAC while up, interface '%s'\n", DRIVER_PREFIX, xilTemac->iUnitName );
|
|
assert(0);
|
|
}
|
|
uint32_t mac;
|
|
mac = aAddr[0] & 0x000000FF;
|
|
mac |= aAddr[1] << 8;
|
|
mac |= aAddr[2] << 16;
|
|
mac |= aAddr[3] << 24;
|
|
OUT32(base + XTE_EUAW0_OFFSET, mac);
|
|
|
|
mac = IN32(base + XTE_EUAW1_OFFSET);
|
|
mac &= ~XTE_EUAW1_MASK;
|
|
mac |= aAddr[4] & 0x000000FF;
|
|
mac |= aAddr[5] << 8;
|
|
OUT32(base + XTE_EUAW1_OFFSET, mac);
|
|
}
|
|
|
|
void xilTemacPrintStats( struct ifnet *ifp )
|
|
{
|
|
struct XilTemac* xilTemac = ifp->if_softc;
|
|
|
|
printf("\n");
|
|
printf("%s: Statistics for interface '%s'\n", DRIVER_PREFIX, xilTemac->iUnitName );
|
|
|
|
printf("%s: Ipif Interrupts: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iInterrupts);
|
|
printf("%s: Rx Interrupts: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iRxInterrupts);
|
|
printf("%s: Rx Rejected Interrupts: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iRxRejectedInterrupts);
|
|
printf("%s: Rx Rej Invalid Frame: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iRxRejectedInvalidFrame);
|
|
printf("%s: Rx Rej Data Fifo Full: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iRxRejectedDataFifoFull);
|
|
printf("%s:Rx Rej Length Fifo Full: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iRxRejectedLengthFifoFull);
|
|
printf("%s: Rx Stray Events: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iRxStrayEvents);
|
|
printf("%s: Rx Max Drained: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iRxMaxDrained);
|
|
printf("%s: Tx Interrupts: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iTxInterrupts);
|
|
printf("%s: Tx Max Drained: %lu\n", DRIVER_PREFIX, xilTemac->iStats.iTxMaxDrained);
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
static void xilTemacIsrSingle(struct XilTemac* xilTemac)
|
|
{
|
|
uint32_t base = xilTemac->iAddr;
|
|
uint32_t disr = IN32( base + XTE_DISR_OFFSET );
|
|
struct ifnet* ifp = xilTemac->iIfp;
|
|
|
|
if( disr && (ifp->if_flags & IFF_RUNNING) == 0 ) {
|
|
/* some interrupt status bits are asserted but card is down */
|
|
printk("%s: Fatal error, disr 0 or this emac not running\n", DRIVER_PREFIX);
|
|
/*assert(0);*/
|
|
} else {
|
|
/* Handle all error conditions first */
|
|
if( disr & (XTE_DXR_DPTO_MASK | XTE_DXR_TERR_MASK |
|
|
XTE_DXR_RECV_FIFO_MASK | XTE_DXR_SEND_FIFO_MASK) ) {
|
|
printk("%s: Fatal Bus error, disr: %08" PRIx32 "\n", DRIVER_PREFIX, disr);
|
|
/*assert(0);*/
|
|
}
|
|
if( disr & XTE_DXR_CORE_MASK ) {
|
|
/* Normal case, temac interrupt */
|
|
uint32_t ipisr = IN32(base + XTE_IPISR_OFFSET);
|
|
uint32_t ipier = IN32(base + XTE_IPIER_OFFSET);
|
|
uint32_t newipier = ipier;
|
|
uint32_t pending = ipisr & ipier;
|
|
xilTemac->iStats.iInterrupts++;
|
|
|
|
/* Check for all fatal errors, even if that error is not enabled in ipier */
|
|
if(ipisr & XTE_IPXR_FIFO_FATAL_ERROR_MASK) {
|
|
printk("%s: Fatal Fifo Error ipisr: %08" PRIx32 "\n", DRIVER_PREFIX, ipisr);
|
|
/*assert(0);*/
|
|
}
|
|
|
|
if(pending & XTE_IPXR_RECV_DONE_MASK) {
|
|
/* We've received a packet
|
|
- inc stats
|
|
- disable rx interrupt
|
|
- signal rx thread to empty out fifo
|
|
(rx thread must renable interrupt)
|
|
*/
|
|
xilTemac->iStats.iRxInterrupts++;
|
|
|
|
newipier &= ~XTE_IPXR_RECV_DONE_MASK;
|
|
|
|
rtems_bsdnet_event_send(gXilRxThread, xilTemac->iIoEvent);
|
|
}
|
|
if(pending & XTE_IPXR_XMIT_DONE_MASK) {
|
|
/* We've transmitted a packet. This interrupt is only ever enabled in
|
|
the ipier if the tx thread didn't have enough space in the data fifo
|
|
or the tplr fifo. If that's the case, we:
|
|
- inc stats
|
|
- disable tx interrupt
|
|
- signal tx thread that a transmit has completed and thus there is now
|
|
room to send again.
|
|
*/
|
|
xilTemac->iStats.iTxInterrupts++;
|
|
|
|
newipier &= ~XTE_IPXR_XMIT_DONE_MASK;
|
|
|
|
rtems_bsdnet_event_send(gXilTxThread, xilTemac->iIoEvent);
|
|
}
|
|
if(pending & XTE_IPXR_RECV_DROPPED_MASK) {
|
|
/* A packet was dropped (because it was invalid, or receiving it
|
|
have overflowed one of the rx fifo's).
|
|
- Increment stats.
|
|
- Clear interrupt condition.
|
|
*/
|
|
uint32_t toggle = 0;
|
|
if(pending & XTE_IPXR_RECV_REJECT_MASK) {
|
|
xilTemac->iStats.iRxRejectedInvalidFrame++;
|
|
toggle |= XTE_IPXR_RECV_REJECT_MASK;
|
|
}
|
|
if(pending & XTE_IPXR_RECV_PFIFO_ABORT_MASK) {
|
|
xilTemac->iStats.iRxRejectedDataFifoFull++;
|
|
toggle |= XTE_IPXR_RECV_PFIFO_ABORT_MASK;
|
|
}
|
|
if(pending & XTE_IPXR_RECV_LFIFO_ABORT_MASK) {
|
|
xilTemac->iStats.iRxRejectedLengthFifoFull++;
|
|
toggle |= XTE_IPXR_RECV_LFIFO_ABORT_MASK;
|
|
}
|
|
xilTemac->iStats.iRxRejectedInterrupts++;
|
|
OUT32(base + XTE_IPISR_OFFSET, toggle);
|
|
}
|
|
if(pending & XTE_IPXR_AUTO_NEG_MASK) {
|
|
printk("%s: Autonegotiation finished\n", DRIVER_PREFIX);
|
|
OUT32(base + XTE_IPISR_OFFSET, XTE_IPXR_AUTO_NEG_MASK);
|
|
}
|
|
if(newipier != ipier) {
|
|
OUT32(base + XTE_IPIER_OFFSET, newipier);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if PPC_HAS_CLASSIC_EXCEPTIONS
|
|
rtems_isr xilTemacIsr( rtems_vector_number aVector )
|
|
{
|
|
struct XilTemac* xilTemac;
|
|
int i;
|
|
|
|
for( i=0; i< NUM_XILTEMAC_UNITS; i++ ) {
|
|
xilTemac = &gXilTemac[i];
|
|
|
|
if( xilTemac->iIsPresent ) {
|
|
xilTemacIsrSingle(xilTemac);
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
rtems_isr xilTemacIsr(void *handle )
|
|
{
|
|
struct XilTemac* xilTemac = (struct XilTemac*)handle;
|
|
|
|
xilTemacIsrSingle(xilTemac);
|
|
}
|
|
|
|
void xilTemacIsrOn(const rtems_irq_connect_data *unused)
|
|
{
|
|
}
|
|
|
|
void xilTemacIsrOff(const rtems_irq_connect_data *unused)
|
|
{
|
|
}
|
|
|
|
int xilTemacIsrIsOn(const rtems_irq_connect_data *unused)
|
|
{
|
|
return 1;
|
|
}
|
|
#endif
|
|
|
|
|
|
static int32_t xilTemacSetMulticastFilter(struct ifnet *ifp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int xilTemacIoctl(struct ifnet* ifp, ioctl_command_t aCommand, caddr_t aData)
|
|
{
|
|
struct XilTemac* xilTemac = ifp->if_softc;
|
|
int32_t error = 0;
|
|
|
|
switch(aCommand) {
|
|
case SIOCGIFADDR:
|
|
case SIOCSIFADDR:
|
|
ether_ioctl(ifp, aCommand, aData);
|
|
break;
|
|
|
|
case SIOCSIFFLAGS:
|
|
switch(ifp->if_flags & (IFF_UP | IFF_RUNNING))
|
|
{
|
|
case IFF_RUNNING:
|
|
xilTemacStop(ifp);
|
|
break;
|
|
|
|
case IFF_UP:
|
|
xilTemacStart(ifp);
|
|
break;
|
|
|
|
case IFF_UP | IFF_RUNNING:
|
|
xilTemacReset(ifp);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SIOCADDMULTI:
|
|
case SIOCDELMULTI: {
|
|
struct ifreq* ifr = (struct ifreq*) aData;
|
|
error = ((aCommand == SIOCADDMULTI) ?
|
|
( ether_addmulti(ifr, &(xilTemac->iArpcom)) ) :
|
|
( ether_delmulti(ifr, &(xilTemac->iArpcom)))
|
|
);
|
|
/* ENETRESET indicates that driver should update its multicast filters */
|
|
if(error == ENETRESET)
|
|
{
|
|
error = xilTemacSetMulticastFilter( ifp );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case SIO_RTEMS_SHOW_STATS:
|
|
xilTemacPrintStats( ifp );
|
|
break;
|
|
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
return error;
|
|
}
|
|
|
|
void xilTemacSend(struct ifnet* ifp)
|
|
{
|
|
struct XilTemac* xilTemac = ifp->if_softc;
|
|
|
|
/* wake up tx thread w/ outbound interface's signal */
|
|
rtems_bsdnet_event_send( gXilTxThread, xilTemac->iIoEvent );
|
|
|
|
ifp->if_flags |= IFF_OACTIVE;
|
|
}
|
|
|
|
/* align the tx buffer to 32 bytes just for kicks, should make it more
|
|
* cache friendly */
|
|
static unsigned char gTxBuf[2048] __attribute__ ((aligned (32)));
|
|
|
|
static void xilTemacSendPacket(struct ifnet *ifp, struct mbuf* aMbuf)
|
|
{
|
|
struct XilTemac *xilTemac = ifp->if_softc;
|
|
struct mbuf *n = aMbuf;
|
|
uint32_t len = 0;
|
|
|
|
#ifdef DEBUG
|
|
printk("SendPacket\n");
|
|
printk("TXD: 0x%08x\n", (int32_t) n->m_data);
|
|
#endif
|
|
|
|
/* assemble the packet into the tx buffer */
|
|
for(;;) {
|
|
#ifdef DEBUG
|
|
uint32_t i = 0;
|
|
printk("MBUF: 0x%08x : ", (int32_t) n->m_data);
|
|
for (i=0;i<n->m_len;i+=2) {
|
|
printk("%02x%02x ", mtod(n, unsigned char*)[i], mtod(n, unsigned char*)[i+1]);
|
|
}
|
|
printk("\n");
|
|
#endif
|
|
|
|
if( n->m_len > 0 ) {
|
|
memcpy( &gTxBuf[ len ], (char *)n->m_data, n->m_len);
|
|
len += n->m_len;
|
|
}
|
|
if( (n = n->m_next) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
xilTemacFifoWrite64( xilTemac->iAddr, (uint32_t*)gTxBuf, len );
|
|
/* Set the Transmit Packet Length Register which registers the packet
|
|
* length, enqueues the packet and signals the xmit unit to start
|
|
* sending. */
|
|
OUT32(xilTemac->iAddr + XTE_TPLR_OFFSET, len);
|
|
|
|
#ifdef DEBUG
|
|
printk("%s: txpkt, len %d\n", DRIVER_PREFIX, len );
|
|
memset(gTxBuf, 0, len);
|
|
#endif
|
|
}
|
|
|
|
static void xilTemacTxThreadSingle(struct ifnet* ifp)
|
|
{
|
|
struct XilTemac* xilTemac = ifp->if_softc;
|
|
struct mbuf* m;
|
|
uint32_t base = xilTemac->iAddr;
|
|
|
|
#ifdef DEBUG
|
|
printk("%s: tx send packet, interface '%s'\n", DRIVER_PREFIX, xilTemac->iUnitName );
|
|
#endif
|
|
|
|
/* Send packets till mbuf queue empty or tx fifo full */
|
|
for(;;) {
|
|
uint32_t i = 0;
|
|
|
|
/* 1) clear out any statuses from previously sent tx frames */
|
|
while( IN32(base + XTE_IPISR_OFFSET) & XTE_IPXR_XMIT_DONE_MASK ) {
|
|
IN32(base + XTE_TSR_OFFSET);
|
|
OUT32(base + XTE_IPISR_OFFSET, XTE_IPXR_XMIT_DONE_MASK);
|
|
i++;
|
|
}
|
|
if( i > xilTemac->iStats.iTxMaxDrained ) {
|
|
xilTemac->iStats.iTxMaxDrained = i;
|
|
}
|
|
|
|
/* 2) Check if enough space in tx data fifo _and_ tx tplr for an entire
|
|
ethernet frame */
|
|
if( xilTemacTxFifoVacancyBytes( xilTemac->iAddr ) <= ifp->if_mtu ) {
|
|
/* 2a) If not, enable transmit done interrupt and break out of loop to
|
|
wait for space */
|
|
uint32_t ipier = IN32(base + XTE_IPIER_OFFSET);
|
|
ipier |= (XTE_IPXR_XMIT_DONE_MASK);
|
|
OUT32(base + XTE_IPIER_OFFSET, ipier);
|
|
break;
|
|
}
|
|
|
|
/* 3) Contuine to dequeue mbuf chains till none left */
|
|
IF_DEQUEUE( &(ifp->if_snd), m);
|
|
if( !m ) {
|
|
break;
|
|
}
|
|
|
|
/* 4) Send dequeued mbuf chain */
|
|
xilTemacSendPacket( ifp, m );
|
|
|
|
/* 5) Free mbuf chain */
|
|
m_freem( m );
|
|
}
|
|
ifp->if_flags &= ~IFF_OACTIVE;
|
|
}
|
|
|
|
void xilTemacTxThread( void *ignore )
|
|
{
|
|
struct XilTemac *xilTemac;
|
|
struct ifnet *ifp;
|
|
|
|
rtems_event_set events;
|
|
int i;
|
|
|
|
for(;;) {
|
|
/* Wait for:
|
|
- notification from stack of packet to send OR
|
|
- notification from interrupt handler that there is space available to
|
|
send already queued packets
|
|
*/
|
|
rtems_bsdnet_event_receive( RTEMS_ALL_EVENTS,
|
|
RTEMS_EVENT_ANY | RTEMS_WAIT,
|
|
RTEMS_NO_TIMEOUT,
|
|
&events );
|
|
|
|
for(i=0; i< NUM_XILTEMAC_UNITS; i++) {
|
|
xilTemac = &gXilTemac[i];
|
|
|
|
if( xilTemac->iIsPresent ) {
|
|
ifp = xilTemac->iIfp;
|
|
|
|
if( (ifp->if_flags & IFF_RUNNING) ) {
|
|
|
|
if( events & xilTemac->iIoEvent ) {
|
|
xilTemacTxThreadSingle(ifp);
|
|
}
|
|
|
|
} else {
|
|
printk("%s: xilTemacTxThread: event received for device: %s, but device not active\n",
|
|
DRIVER_PREFIX, xilTemac->iUnitName);
|
|
assert(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void xilTemacRxThreadSingle(struct ifnet* ifp)
|
|
{
|
|
struct XilTemac* xilTemac = ifp->if_softc;
|
|
|
|
uint32_t npkts = 0;
|
|
#ifdef DEBUG
|
|
printk("%s: rxthread, packet rx on interface %s\n", DRIVER_PREFIX, xilTemac->iUnitName );
|
|
#endif
|
|
|
|
uint32_t base = xilTemac->iAddr;
|
|
|
|
/* While RECV_DONE_MASK in ipisr stays set */
|
|
while( IN32(base + XTE_IPISR_OFFSET) & XTE_IPXR_RECV_DONE_MASK ) {
|
|
|
|
/* 1) Read the length of the packet */
|
|
uint32_t bytes = IN32(base + XTE_RPLR_OFFSET);
|
|
|
|
/* 2) Read the Read Status Register (which contains no information). When
|
|
* all of these in the fifo have been read, then XTE_IPXR_RECV_DONE_MASK
|
|
* will stay turned off, after it's written to */
|
|
IN32(base + XTE_RSR_OFFSET);
|
|
npkts++;
|
|
|
|
struct mbuf* m;
|
|
struct ether_header* eh;
|
|
|
|
/* 3) Get some memory from the ip stack to store the packet in */
|
|
MGETHDR(m, M_WAIT, MT_DATA);
|
|
MCLGET(m, M_WAIT);
|
|
|
|
m->m_pkthdr.rcvif = ifp;
|
|
|
|
/* 4) Copy the packet into the ip stack's memory */
|
|
xilTemacFifoRead64( base, mtod(m, uint32_t*), bytes);
|
|
|
|
m->m_len = bytes - sizeof(struct ether_header);
|
|
m->m_pkthdr.len = bytes - sizeof(struct ether_header);
|
|
|
|
eh = mtod(m, struct ether_header*);
|
|
|
|
m->m_data += sizeof(struct ether_header);
|
|
|
|
/* 5) Tell the ip stack about the received packet */
|
|
ether_input(ifp, eh, m);
|
|
|
|
/* 6) Try and turn off XTE_IPXR_RECV_DONE bit in the ipisr. If there's
|
|
* still more packets (ie RSR ! empty), then it will stay asserted. If
|
|
* there's no more packets, this will turn it off.
|
|
*/
|
|
OUT32(base + XTE_IPISR_OFFSET, XTE_IPXR_RECV_DONE_MASK);
|
|
}
|
|
|
|
/* End) All Rx packets serviced, renable rx interrupt */
|
|
uint32_t ipier = IN32(base + XTE_IPIER_OFFSET);
|
|
ipier |= XTE_IPXR_RECV_DONE_MASK;
|
|
OUT32(base + XTE_IPIER_OFFSET, ipier);
|
|
|
|
#ifdef DEBUG
|
|
printk("%s: rxthread, retrieved %d packets\n", DRIVER_PREFIX, npkts );
|
|
#endif
|
|
if(npkts > xilTemac->iStats.iRxMaxDrained) {
|
|
xilTemac->iStats.iRxMaxDrained = npkts;
|
|
}
|
|
/* ??) Very very occasionally, under extremely high stress, I get a situation
|
|
* where we process no packets. That is, the rx thread was evented, but
|
|
* there was no packet available. I'm not sure how this happens. Ideally,
|
|
* it shouldn't ocurr, and I suspect a minor bug in the driver. However, for
|
|
* me it's happenning 3 times in several hunderd million interrupts. Nothing
|
|
* bad happens, as long as we don't read from the rx fifo's if nothing is
|
|
* there. It is just not as efficient as possible (rx thread being evented
|
|
* pointlessly) and a bit disconcerting about how it's ocurring.
|
|
* The best way to reproduce this is to have two clients run:
|
|
* $ ping <host> -f -s 65507
|
|
* This flood pings the device from two clients with the maximum size ping
|
|
* packet. It absolutely hammers the device under test. Eventually, (if
|
|
* you leave it running overnight for instance), you'll get a couple of these
|
|
* stray rx events. */
|
|
if(npkts == 0) {
|
|
/*printk("%s: RxThreadSingle: fatal error: event received, but no packets available\n", DRIVER_PREFIX);
|
|
assert(0); */
|
|
xilTemac->iStats.iRxStrayEvents++;
|
|
}
|
|
}
|
|
|
|
void xilTemacRxThread( void *ignore )
|
|
{
|
|
struct XilTemac* xilTemac;
|
|
struct ifnet* ifp;
|
|
int i;
|
|
rtems_event_set events;
|
|
|
|
#ifdef DEBUG
|
|
printk("%s: xilTemacRxThread running\n", DRIVER_PREFIX );
|
|
#endif
|
|
|
|
for(;;) {
|
|
rtems_bsdnet_event_receive( RTEMS_ALL_EVENTS,
|
|
RTEMS_WAIT | RTEMS_EVENT_ANY,
|
|
RTEMS_NO_TIMEOUT,
|
|
&events);
|
|
|
|
#ifdef DEBUG
|
|
printk("%s: rxthread, wakeup\n", DRIVER_PREFIX );
|
|
#endif
|
|
|
|
for(i=0; i< NUM_XILTEMAC_UNITS; i++) {
|
|
xilTemac = &gXilTemac[i];
|
|
|
|
if( xilTemac->iIsPresent ) {
|
|
ifp = xilTemac->iIfp;
|
|
|
|
if( (ifp->if_flags & IFF_RUNNING) != 0 ) {
|
|
if( events & xilTemac->iIoEvent ) {
|
|
xilTemacRxThreadSingle(ifp);
|
|
}
|
|
}
|
|
else {
|
|
printk("%s: rxthread, interface %s present but not running\n", DRIVER_PREFIX, xilTemac->iUnitName );
|
|
assert(0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int xilTemac_driver_attach(struct rtems_bsdnet_ifconfig* aBsdConfig, int aDummy)
|
|
{
|
|
struct ifnet* ifp;
|
|
int32_t mtu;
|
|
int32_t unit;
|
|
char* unitName;
|
|
struct XilTemac* xilTemac;
|
|
|
|
unit = rtems_bsdnet_parse_driver_name(aBsdConfig, &unitName);
|
|
if(unit < 0 )
|
|
{
|
|
printk("%s: Interface Unit number < 0\n", DRIVER_PREFIX );
|
|
return 0;
|
|
}
|
|
|
|
if( aBsdConfig->bpar == 0 )
|
|
{
|
|
printk("%s: Did not specify base address for device '%s'", DRIVER_PREFIX, unitName );
|
|
return 0;
|
|
}
|
|
|
|
if( aBsdConfig->hardware_address == NULL )
|
|
{
|
|
printk("%s: No MAC address given for interface '%s'\n", DRIVER_PREFIX, unitName );
|
|
return 0;
|
|
}
|
|
|
|
xilTemac = &gXilTemac[ unit ];
|
|
memset(xilTemac, 0, sizeof(struct XilTemac));
|
|
|
|
xilTemac->iIsPresent = 1;
|
|
|
|
snprintf( xilTemac->iUnitName, MAX_UNIT_BYTES, "%s%" PRId32, unitName, unit );
|
|
|
|
xilTemac->iIfp = &(xilTemac->iArpcom.ac_if);
|
|
ifp = &(xilTemac->iArpcom.ac_if);
|
|
xilTemac->iAddr = aBsdConfig->bpar;
|
|
xilTemac->iIoEvent = gUnitSignals[ unit ];
|
|
xilTemac->iIsrVector = aBsdConfig->irno;
|
|
|
|
memcpy( xilTemac->iArpcom.ac_enaddr, aBsdConfig->hardware_address, ETHER_ADDR_LEN);
|
|
|
|
if( aBsdConfig->mtu )
|
|
{
|
|
mtu = aBsdConfig->mtu;
|
|
}
|
|
else
|
|
{
|
|
mtu = ETHERMTU;
|
|
}
|
|
|
|
ifp->if_softc = xilTemac;
|
|
ifp->if_unit = unit;
|
|
ifp->if_name = unitName;
|
|
ifp->if_mtu = mtu;
|
|
ifp->if_init = xilTemacInit;
|
|
ifp->if_ioctl = xilTemacIoctl;
|
|
ifp->if_start = xilTemacSend;
|
|
ifp->if_output = ether_output;
|
|
|
|
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
|
|
|
|
if(ifp->if_snd.ifq_maxlen == 0)
|
|
{
|
|
ifp->if_snd.ifq_maxlen = ifqmaxlen;
|
|
}
|
|
|
|
if_attach(ifp);
|
|
ether_ifattach(ifp);
|
|
|
|
/* create shared rx & tx threads */
|
|
if( (gXilRxThread == 0) && (gXilTxThread == 0) )
|
|
{
|
|
printk("%s: Creating shared RX/TX threads\n", DRIVER_PREFIX );
|
|
gXilRxThread = rtems_bsdnet_newproc("xerx", 4096, xilTemacRxThread, NULL );
|
|
gXilTxThread = rtems_bsdnet_newproc("xetx", 4096, xilTemacTxThread, NULL );
|
|
}
|
|
|
|
printk("%s: Initializing driver for '%s'\n", DRIVER_PREFIX, xilTemac->iUnitName );
|
|
|
|
printk("%s: base address 0x%08X, intnum 0x%02X, \n",
|
|
DRIVER_PREFIX,
|
|
aBsdConfig->bpar,
|
|
aBsdConfig->irno );
|
|
|
|
return 1;
|
|
}
|
|
|