forked from Imagelibrary/rtems
1937 lines
66 KiB
C
1937 lines
66 KiB
C
/*===============================================================*\
|
|
| Project: RTEMS support for MPC83xx |
|
|
+-----------------------------------------------------------------+
|
|
| Copyright (c) 2007 |
|
|
| Embedded Brains GmbH |
|
|
| Obere Lagerstr. 30 |
|
|
| D-82178 Puchheim |
|
|
| Germany |
|
|
| rtems@embedded-brains.de |
|
|
+-----------------------------------------------------------------+
|
|
| 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. |
|
|
| |
|
|
+-----------------------------------------------------------------+
|
|
| this file contains the MPC83xx TSEC networking driver |
|
|
\*===============================================================*/
|
|
|
|
#include <machine/rtems-bsd-kernel-space.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <bsp.h>
|
|
#include <bsp/irq.h>
|
|
#include <bsp/tsec.h>
|
|
#include <rtems/error.h>
|
|
#include <rtems/bspIo.h>
|
|
#include <rtems/rtems_bsdnet.h>
|
|
#include <rtems/rtems_mii_ioctl.h>
|
|
#include <errno.h>
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sockio.h>
|
|
#include <sys/mbuf.h>
|
|
#include <net/if.h>
|
|
#include <net/if_arp.h>
|
|
#include <netinet/in.h>
|
|
#include <netinet/if_ether.h>
|
|
#include <stdio.h>
|
|
|
|
#define CLREVENT_IN_IRQ
|
|
|
|
#define TSEC_WATCHDOG_TIMEOUT 5 /* check media every 5 seconds */
|
|
|
|
#ifdef DEBUG
|
|
#define PF(fmt, ...) printk("%s: " fmt, __func__, ##__VA_ARGS__)
|
|
#else
|
|
#define PF(...)
|
|
#endif
|
|
|
|
/*
|
|
* Device data
|
|
*/
|
|
struct tsec_struct {
|
|
struct arpcom arpcom;
|
|
int acceptBroadcast;
|
|
|
|
/*
|
|
* HW links: (filled from rtems_bsdnet_ifconfig
|
|
*/
|
|
volatile tsec_registers *reg_ptr; /* pointer to TSEC register block */
|
|
volatile tsec_registers *mdio_ptr; /* pointer to TSEC register block which is responsible for MDIO communication */
|
|
rtems_irq_number irq_num_tx;
|
|
rtems_irq_number irq_num_rx;
|
|
rtems_irq_number irq_num_err;
|
|
|
|
rtems_interrupt_lock lock;
|
|
|
|
/*
|
|
* BD management
|
|
*/
|
|
int rxBdCount;
|
|
int txBdCount;
|
|
PQBufferDescriptor_t *Rx_Frst_BD;
|
|
PQBufferDescriptor_t *Rx_Last_BD;
|
|
PQBufferDescriptor_t *Rx_NxtUsed_BD; /* First BD, which is in Use */
|
|
PQBufferDescriptor_t *Rx_NxtFill_BD; /* BD to be filled next */
|
|
struct mbuf **Rx_mBuf_Ptr; /* Storage for mbufs */
|
|
|
|
PQBufferDescriptor_t *Tx_Frst_BD;
|
|
PQBufferDescriptor_t *Tx_Last_BD;
|
|
PQBufferDescriptor_t *Tx_NxtUsed_BD; /* First BD, which is in Use */
|
|
PQBufferDescriptor_t *Tx_NxtFill_BD; /* BD to be filled next */
|
|
struct mbuf **Tx_mBuf_Ptr; /* Storage for mbufs */
|
|
/*
|
|
* Daemon IDs
|
|
*/
|
|
rtems_id rxDaemonTid;
|
|
rtems_id txDaemonTid;
|
|
|
|
/*
|
|
* MDIO/Phy info
|
|
*/
|
|
struct rtems_mdio_info mdio_info;
|
|
int phy_default;
|
|
int media_state; /* (last detected) state of media */
|
|
/*
|
|
* statistic counters Rx
|
|
*/
|
|
unsigned long rxInterrupts;
|
|
unsigned long rxErrors;
|
|
/*
|
|
* statistic counters Tx
|
|
*/
|
|
unsigned long txInterrupts;
|
|
unsigned long txErrors;
|
|
};
|
|
|
|
static struct tsec_struct tsec_driver[TSEC_COUNT];
|
|
|
|
/*
|
|
* default numbers for buffers
|
|
*/
|
|
#define RX_BUF_COUNT 64
|
|
#define TX_BUF_COUNT 64
|
|
|
|
/*
|
|
* mask for all Tx interrupts
|
|
*/
|
|
#define IEVENT_TXALL (TSEC_IEVENT_GTSC \
|
|
| TSEC_IEVENT_TXC \
|
|
/*| TSEC_IEVENT_TXB*/ \
|
|
| TSEC_IEVENT_TXF )
|
|
|
|
/*
|
|
* mask for all Rx interrupts
|
|
*/
|
|
#define IEVENT_RXALL (TSEC_IEVENT_RXC \
|
|
/* | TSEC_IEVENT_RXB */ \
|
|
| TSEC_IEVENT_GRSC \
|
|
| TSEC_IEVENT_RXF )
|
|
|
|
/*
|
|
* mask for all Error interrupts
|
|
*/
|
|
#define IEVENT_ERRALL (TSEC_IEVENT_BABR \
|
|
| TSEC_IEVENT_BSY \
|
|
| TSEC_IEVENT_EBERR \
|
|
| TSEC_IEVENT_MSRO \
|
|
| TSEC_IEVENT_BABT \
|
|
| TSEC_IEVENT_TXE \
|
|
| TSEC_IEVENT_LC \
|
|
| TSEC_IEVENT_CRL_XDA \
|
|
| TSEC_IEVENT_XFUN )
|
|
|
|
static void TSEC_IMASK_SET(struct tsec_struct *sc, uint32_t mask, uint32_t val)
|
|
{
|
|
volatile uint32_t *reg = &sc->reg_ptr->imask;
|
|
rtems_interrupt_lock_context lock_context;
|
|
|
|
rtems_interrupt_lock_acquire(&sc->lock, &lock_context);
|
|
*reg = (*reg & ~mask) | (val & mask);
|
|
rtems_interrupt_lock_release(&sc->lock, &lock_context);
|
|
}
|
|
|
|
#define TSEC_ALIGN_BUFFER(buf,align) \
|
|
((void *)( (((uint32_t)(buf))+(align)-1) \
|
|
-(((uint32_t)(buf))+(align)-1)%align))
|
|
|
|
/*
|
|
* RTEMS event used by interrupt handler to signal daemons.
|
|
* This must *not* be the same event used by the TCP/IP task synchronization.
|
|
*/
|
|
#define INTERRUPT_EVENT RTEMS_EVENT_1
|
|
#define FATAL_INT_EVENT RTEMS_EVENT_3
|
|
|
|
/*
|
|
* RTEMS event used to start transmit daemon.
|
|
* This must not be the same as INTERRUPT_EVENT.
|
|
*/
|
|
#define START_TRANSMIT_EVENT RTEMS_EVENT_2
|
|
|
|
static int tsec_ioctl
|
|
(
|
|
struct ifnet *ifp, /* interface information */
|
|
ioctl_command_t command, /* ioctl command code */
|
|
caddr_t data /* optional data */
|
|
);
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_hwinit
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| initialize hardware register |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc /* control structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
volatile tsec_registers *reg_ptr = sc->reg_ptr; /* pointer to TSEC registers*/
|
|
uint8_t *mac_addr;
|
|
size_t i;
|
|
|
|
/* Clear interrupt mask and all pending events */
|
|
reg_ptr->imask = 0;
|
|
reg_ptr->ievent = 0xffffffff;
|
|
|
|
/*
|
|
* init ECNTL register
|
|
* - clear statistics counters
|
|
* - enable statistics
|
|
* NOTE: do not clear bits set in BSP init function
|
|
*/
|
|
reg_ptr->ecntrl = ((reg_ptr->ecntrl & ~TSEC_ECNTRL_AUTOZ)
|
|
| TSEC_ECNTRL_CLRCNT
|
|
| TSEC_ECNTRL_STEN
|
|
| TSEC_ECNTRL_R100M);
|
|
|
|
/*
|
|
* init DMA control register:
|
|
* - enable snooping
|
|
* - write BD status before interrupt request
|
|
* - do not poll TxBD, but wait for TSTAT[THLT] to be written
|
|
*/
|
|
reg_ptr->dmactrl = (TSEC_DMACTL_TDSEN
|
|
| TSEC_DMACTL_TBDSEN
|
|
| TSEC_DMACTL_WWR
|
|
| TSEC_DMACTL_WOP);
|
|
|
|
/*
|
|
* init Attribute register:
|
|
* - enable read snooping for data and BD
|
|
*/
|
|
reg_ptr->attr = (TSEC_ATTR_RDSEN
|
|
| TSEC_ATTR_RBDSEN);
|
|
|
|
|
|
reg_ptr->mrblr = MCLBYTES-64; /* take care of buffer size lost
|
|
* due to alignment */
|
|
|
|
/*
|
|
* init EDIS register: disable all error reportings
|
|
*/
|
|
reg_ptr->edis = (TSEC_EDIS_BSYDIS |
|
|
TSEC_EDIS_EBERRDIS |
|
|
TSEC_EDIS_TXEDIS |
|
|
TSEC_EDIS_LCDIS |
|
|
TSEC_EDIS_CRLXDADIS |
|
|
TSEC_EDIS_FUNDIS);
|
|
/*
|
|
* init minimum frame length register
|
|
*/
|
|
reg_ptr->minflr = 64;
|
|
/*
|
|
* init maximum frame length register
|
|
*/
|
|
reg_ptr->maxfrm = 1536;
|
|
/*
|
|
* define physical address of TBI
|
|
*/
|
|
reg_ptr->tbipa = 0x1e;
|
|
/*
|
|
* init transmit interrupt coalescing register
|
|
*/
|
|
reg_ptr->txic = (TSEC_TXIC_ICEN
|
|
| TSEC_TXIC_ICFCT(2)
|
|
| TSEC_TXIC_ICTT(32));
|
|
/*
|
|
* init receive interrupt coalescing register
|
|
*/
|
|
#if 0
|
|
reg_ptr->rxic = (TSEC_RXIC_ICEN
|
|
| TSEC_RXIC_ICFCT(2)
|
|
| TSEC_RXIC_ICTT(32));
|
|
#else
|
|
reg_ptr->rxic = 0;
|
|
#endif
|
|
/*
|
|
* init MACCFG1 register
|
|
*/
|
|
reg_ptr->maccfg1 = (TSEC_MACCFG1_RX_FLOW
|
|
| TSEC_MACCFG1_TX_FLOW);
|
|
|
|
/*
|
|
* init MACCFG2 register
|
|
*/
|
|
reg_ptr->maccfg2 = ((reg_ptr->maccfg2 & TSEC_MACCFG2_IFMODE_MSK)
|
|
| TSEC_MACCFG2_IFMODE_BYT
|
|
| TSEC_MACCFG2_PRELEN( 7)
|
|
| TSEC_MACCFG2_FULLDUPLEX);
|
|
|
|
/*
|
|
* init station address register
|
|
*/
|
|
mac_addr = sc->arpcom.ac_enaddr;
|
|
|
|
reg_ptr->macstnaddr[0] = ((mac_addr[5] << 24)
|
|
| (mac_addr[4] << 16)
|
|
| (mac_addr[3] << 8)
|
|
| (mac_addr[2] << 0));
|
|
reg_ptr->macstnaddr[1] = ((mac_addr[1] << 24)
|
|
| (mac_addr[0] << 16));
|
|
/*
|
|
* clear hash filters
|
|
*/
|
|
for (i = 0;
|
|
i < sizeof(reg_ptr->iaddr)/sizeof(reg_ptr->iaddr[0]);
|
|
i++) {
|
|
reg_ptr->iaddr[i] = 0;
|
|
}
|
|
for (i = 0;
|
|
i < sizeof(reg_ptr->gaddr)/sizeof(reg_ptr->gaddr[0]);
|
|
i++) {
|
|
reg_ptr->gaddr[i] = 0;
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
| MII Management access functions |
|
|
\***************************************************************************/
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_mdio_init
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| initialize the MIIM interface |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc /* control structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
static const uint8_t divider [] = { 32, 32, 48, 64, 80, 112, 160, 224 };
|
|
size_t n = sizeof(divider) / sizeof(divider [0]);
|
|
size_t i = 0;
|
|
uint32_t mii_clock = UINT32_MAX;
|
|
uint32_t tsec_system_clock = BSP_bus_frequency / 2;
|
|
|
|
/* Set TSEC registers for MDIO communication */
|
|
|
|
/*
|
|
* set clock divider
|
|
*/
|
|
for (i = 0; i < n && mii_clock > 2500000; ++i) {
|
|
mii_clock = tsec_system_clock / divider [i];
|
|
}
|
|
|
|
sc->mdio_ptr->miimcfg = i;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static int tsec_mdio_read
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| read register of a phy |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
int phy, /* PHY number to access or -1 */
|
|
void *uarg, /* unit argument */
|
|
unsigned reg, /* register address */
|
|
uint32_t *pval /* ptr to read buffer */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| 0, if ok, else error |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc = uarg;/* control structure */
|
|
|
|
/* pointer to TSEC registers */
|
|
volatile tsec_registers *reg_ptr = sc->mdio_ptr;
|
|
PF("%u\n", reg);
|
|
|
|
/*
|
|
* make sure we work with a valid phy
|
|
*/
|
|
if (phy == -1) {
|
|
phy = sc->phy_default;
|
|
}
|
|
if ( (phy < 0) || (phy > 31)) {
|
|
/*
|
|
* invalid phy number
|
|
*/
|
|
return EINVAL;
|
|
}
|
|
/*
|
|
* set PHY/reg address
|
|
*/
|
|
reg_ptr->miimadd = (TSEC_MIIMADD_PHY(phy)
|
|
| TSEC_MIIMADD_REGADDR(reg));
|
|
/*
|
|
* start read cycle
|
|
*/
|
|
reg_ptr->miimcom = 0;
|
|
reg_ptr->miimcom = TSEC_MIIMCOM_READ;
|
|
|
|
/*
|
|
* wait for cycle to terminate
|
|
*/
|
|
do {
|
|
rtems_task_wake_after(2);
|
|
} while (0 != (reg_ptr->miimind & TSEC_MIIMIND_BUSY));
|
|
reg_ptr->miimcom = 0;
|
|
/*
|
|
* fetch read data, if available
|
|
*/
|
|
if (pval != NULL) {
|
|
*pval = reg_ptr->miimstat;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static int tsec_mdio_write
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| write register of a phy |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
int phy, /* PHY number to access or -1 */
|
|
void *uarg, /* unit argument */
|
|
unsigned reg, /* register address */
|
|
uint32_t val /* write value */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| 0, if ok, else error |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc = uarg;/* control structure */
|
|
|
|
/* pointer to TSEC registers */
|
|
volatile tsec_registers *reg_ptr = sc->mdio_ptr;
|
|
PF("%u\n", reg);
|
|
|
|
/*
|
|
* make sure we work with a valid phy
|
|
*/
|
|
if (phy == -1) {
|
|
/*
|
|
* set default phy number: 0 for TSEC1, 1 for TSEC2
|
|
*/
|
|
phy = sc->phy_default;
|
|
}
|
|
if ( (phy < 0) || (phy > 31)) {
|
|
/*
|
|
* invalid phy number
|
|
*/
|
|
return EINVAL;
|
|
}
|
|
/*
|
|
* set PHY/reg address
|
|
*/
|
|
reg_ptr->miimadd = (TSEC_MIIMADD_PHY(phy)
|
|
| TSEC_MIIMADD_REGADDR(reg));
|
|
/*
|
|
* start write cycle
|
|
*/
|
|
reg_ptr->miimcon = val;
|
|
|
|
/*
|
|
* wait for cycle to terminate
|
|
*/
|
|
do {
|
|
rtems_task_wake_after(2);
|
|
} while (0 != (reg_ptr->miimind & TSEC_MIIMIND_BUSY));
|
|
reg_ptr->miimcom = 0;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
| RX receive functions |
|
|
\***************************************************************************/
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static rtems_event_set tsec_rx_wait_for_events
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| handle all rx events |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc, /* control structure */
|
|
rtems_event_set event_mask /* events to wait for */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| event set received |
|
|
\*=========================================================================*/
|
|
{
|
|
rtems_event_set events; /* events received */
|
|
/*
|
|
* enable Rx interrupts, make sure this is not interrupted :-)
|
|
*/
|
|
TSEC_IMASK_SET(sc,IEVENT_RXALL,~0);
|
|
|
|
/*
|
|
* wait for events to come in
|
|
*/
|
|
rtems_bsdnet_event_receive(event_mask,
|
|
RTEMS_EVENT_ANY | RTEMS_WAIT,
|
|
RTEMS_NO_TIMEOUT,
|
|
&events);
|
|
return events;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void mpc83xx_rxbd_alloc_clear
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| allocate space for Rx BDs, clear them |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc /* control structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
char *alloc_ptr;
|
|
PQBufferDescriptor_t *BD_ptr;
|
|
/*
|
|
* allocate proper space for Rx BDs
|
|
*/
|
|
alloc_ptr = calloc((sc->rxBdCount+1),sizeof(PQBufferDescriptor_t));
|
|
if (alloc_ptr == NULL) {
|
|
rtems_panic("TSEC: cannot allocate space for Rx BDs");
|
|
}
|
|
alloc_ptr = (void *)((uint32_t )((alloc_ptr + (sizeof(PQBufferDescriptor_t)-1)))
|
|
& ~(sizeof(PQBufferDescriptor_t)-1));
|
|
/*
|
|
* store pointers to certain positions in BD chain
|
|
*/
|
|
sc->Rx_Last_BD = ((PQBufferDescriptor_t *)alloc_ptr)+sc->rxBdCount-1;
|
|
sc->Rx_Frst_BD = (PQBufferDescriptor_t *)alloc_ptr;
|
|
sc->Rx_NxtUsed_BD = sc->Rx_Frst_BD;
|
|
sc->Rx_NxtFill_BD = sc->Rx_Frst_BD;
|
|
|
|
/*
|
|
* clear all BDs
|
|
*/
|
|
for (BD_ptr = sc->Rx_Frst_BD;
|
|
BD_ptr <= sc->Rx_Last_BD;
|
|
BD_ptr++) {
|
|
BD_ptr->status = 0;
|
|
}
|
|
/*
|
|
* Init BD chain registers
|
|
*/
|
|
sc->reg_ptr->rbase = (uint32_t) (sc->Rx_Frst_BD);
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_receive_packets
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| process any received packets |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc /* control structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
PQBufferDescriptor_t *BD_ptr;
|
|
struct mbuf *m,*n;
|
|
bool finished = false;
|
|
uint16_t status;
|
|
struct ether_header *eh;
|
|
int bd_idx;
|
|
|
|
BD_ptr = sc->Rx_NxtUsed_BD;
|
|
|
|
while ((0 == ((status = BD_ptr->status) & BD_EMPTY)) &&
|
|
!finished &&
|
|
(BD_ptr->buffer != NULL)) {
|
|
/*
|
|
* get mbuf associated with BD
|
|
*/
|
|
bd_idx = BD_ptr - sc->Rx_Frst_BD;
|
|
m = sc->Rx_mBuf_Ptr[bd_idx];
|
|
sc->Rx_mBuf_Ptr[bd_idx] = NULL;
|
|
|
|
/*
|
|
* Check that packet is valid
|
|
*/
|
|
if ((status & (BD_LAST |
|
|
BD_FIRST_IN_FRAME |
|
|
BD_LONG |
|
|
BD_NONALIGNED |
|
|
BD_CRC_ERROR |
|
|
BD_OVERRUN ))
|
|
== (BD_LAST |
|
|
BD_FIRST_IN_FRAME ) ) {
|
|
/*
|
|
* send mbuf of this buffer to ether_input()
|
|
*/
|
|
m->m_len = m->m_pkthdr.len = (BD_ptr->length
|
|
- sizeof(uint32_t)
|
|
- sizeof(struct ether_header));
|
|
eh = mtod(m, struct ether_header *);
|
|
m->m_data += sizeof(struct ether_header);
|
|
PF("RX[%08x] (%i)\n", BD_ptr, m->m_len);
|
|
ether_input(&sc->arpcom.ac_if,eh,m);
|
|
}
|
|
else {
|
|
/*
|
|
* throw away mbuf
|
|
*/
|
|
MFREE(m,n);
|
|
(void) n;
|
|
}
|
|
/*
|
|
* mark buffer as non-allocated (for refill)
|
|
*/
|
|
BD_ptr->buffer = NULL;
|
|
/*
|
|
* Advance BD_ptr to next BD
|
|
*/
|
|
BD_ptr = ((BD_ptr == sc->Rx_Last_BD)
|
|
? sc->Rx_Frst_BD
|
|
: BD_ptr+1);
|
|
}
|
|
sc->Rx_NxtUsed_BD = BD_ptr;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_refill_rxbds
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| link new buffers to rx BDs |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc /* control structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
PQBufferDescriptor_t *BD_ptr;
|
|
struct mbuf *m,*n;
|
|
bool finished = false;
|
|
int bd_idx;
|
|
|
|
BD_ptr = sc->Rx_NxtFill_BD;
|
|
while ((BD_ptr->buffer == NULL) &&
|
|
!finished) {
|
|
/*
|
|
* get new mbuf and attach a cluster
|
|
*/
|
|
MGETHDR(m,M_DONTWAIT,MT_DATA);
|
|
if (m != NULL) {
|
|
MCLGET(m,M_DONTWAIT);
|
|
if ((m->m_flags & M_EXT) == 0) {
|
|
MFREE(m,n);
|
|
(void) n;
|
|
m = NULL;
|
|
}
|
|
}
|
|
if (m == NULL) {
|
|
finished = true;
|
|
}
|
|
else {
|
|
bd_idx = BD_ptr - sc->Rx_Frst_BD;
|
|
sc->Rx_mBuf_Ptr[bd_idx] = m;
|
|
|
|
m->m_pkthdr.rcvif= &sc->arpcom.ac_if;
|
|
m->m_data = TSEC_ALIGN_BUFFER(m->m_ext.ext_buf,64);
|
|
BD_ptr->buffer = m->m_data;
|
|
BD_ptr->length = 0;
|
|
BD_ptr->status = (BD_EMPTY
|
|
| BD_INTERRUPT
|
|
| ((BD_ptr == sc->Rx_Last_BD)
|
|
? BD_WRAP
|
|
: 0));
|
|
/*
|
|
* Advance BD_ptr to next BD
|
|
*/
|
|
BD_ptr = ((BD_ptr == sc->Rx_Last_BD)
|
|
? sc->Rx_Frst_BD
|
|
: BD_ptr+1);
|
|
}
|
|
}
|
|
sc->Rx_NxtFill_BD = BD_ptr;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_rxDaemon
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| handle all rx buffers and events |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
void * arg /* argument, is sc structure ptr */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc =
|
|
(struct tsec_struct *)arg;
|
|
bool finished = false;
|
|
#if !defined(CLREVENT_IN_IRQ)
|
|
uint32_t irq_events;
|
|
#endif
|
|
/*
|
|
* enable Rx in MACCFG1 register
|
|
*/
|
|
sc->reg_ptr->maccfg1 |= TSEC_MACCFG1_RXEN;
|
|
while (!finished) {
|
|
/*
|
|
* fetch MBufs, associate them to RxBDs
|
|
*/
|
|
tsec_refill_rxbds(sc);
|
|
/*
|
|
* wait for events to come in
|
|
*/
|
|
tsec_rx_wait_for_events(sc,INTERRUPT_EVENT);
|
|
#if !defined(CLREVENT_IN_IRQ)
|
|
/*
|
|
* clear any pending RX events
|
|
*/
|
|
irq_events = sc->reg_ptr->ievent & IEVENT_RXALL;
|
|
sc->reg_ptr->ievent = irq_events;
|
|
#endif
|
|
/*
|
|
* fetch any completed buffers/packets received
|
|
* and stuff them into the TCP/IP Stack
|
|
*/
|
|
tsec_receive_packets(sc);
|
|
}
|
|
/*
|
|
* disable Rx in MACCFG1 register
|
|
*/
|
|
sc->reg_ptr->maccfg1 &= ~TSEC_MACCFG1_RXEN;
|
|
/*
|
|
* terminate daemon
|
|
*/
|
|
sc->rxDaemonTid = 0;
|
|
rtems_task_exit();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
| TX Transmit functions |
|
|
\***************************************************************************/
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void mpc83xx_txbd_alloc_clear
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| allocate space for Tx BDs, clear them |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc /* control structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
char *alloc_ptr;
|
|
PQBufferDescriptor_t *BD_ptr;
|
|
/*
|
|
* allocate proper space for Tx BDs
|
|
*/
|
|
alloc_ptr = calloc((sc->txBdCount+1),sizeof(PQBufferDescriptor_t));
|
|
if (alloc_ptr == NULL) {
|
|
rtems_panic("TSEC: cannot allocate space for Tx BDs");
|
|
}
|
|
alloc_ptr = (void *)((uint32_t )((alloc_ptr + (sizeof(PQBufferDescriptor_t)-1)))
|
|
& ~(sizeof(PQBufferDescriptor_t)-1));
|
|
/*
|
|
* store pointers to certain positions in BD chain
|
|
*/
|
|
sc->Tx_Last_BD = ((PQBufferDescriptor_t *)alloc_ptr)+sc->txBdCount-1;
|
|
sc->Tx_Frst_BD = (PQBufferDescriptor_t *)alloc_ptr;
|
|
sc->Tx_NxtUsed_BD = sc->Tx_Frst_BD;
|
|
sc->Tx_NxtFill_BD = sc->Tx_Frst_BD;
|
|
|
|
/*
|
|
* clear all BDs
|
|
*/
|
|
for (BD_ptr = sc->Tx_Frst_BD;
|
|
BD_ptr <= sc->Tx_Last_BD;
|
|
BD_ptr++) {
|
|
BD_ptr->status = 0;
|
|
}
|
|
/*
|
|
* Init BD chain registers
|
|
*/
|
|
sc->reg_ptr->tbase = (uint32_t)(sc->Tx_Frst_BD);
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_tx_start
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| start transmission |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct ifnet *ifp
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc = ifp->if_softc;
|
|
|
|
ifp->if_flags |= IFF_OACTIVE;
|
|
|
|
rtems_bsdnet_event_send (sc->txDaemonTid, START_TRANSMIT_EVENT);
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static rtems_event_set tsec_tx_wait_for_events
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| handle all tx events |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc, /* control structure */
|
|
rtems_event_set event_mask /* events to wait for */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| event set received |
|
|
\*=========================================================================*/
|
|
{
|
|
rtems_event_set events; /* events received */
|
|
/*
|
|
* enable Tx interrupts, make sure this is not interrupted :-)
|
|
*/
|
|
TSEC_IMASK_SET(sc,IEVENT_TXALL,~0);
|
|
|
|
/*
|
|
* wait for events to come in
|
|
*/
|
|
rtems_bsdnet_event_receive(event_mask,
|
|
RTEMS_EVENT_ANY | RTEMS_WAIT,
|
|
RTEMS_NO_TIMEOUT,
|
|
&events);
|
|
return events;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_tx_retire
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| handle all tx events |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc /* control structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
PQBufferDescriptor_t *RetBD;
|
|
RetBD = sc->Tx_NxtUsed_BD;
|
|
int bd_idx;
|
|
struct mbuf *m,*n;
|
|
/*
|
|
* check next BDs to be empty
|
|
*/
|
|
while ((RetBD->buffer != NULL) /* BD is filled */
|
|
&& (0 == (RetBD->status & BD_READY ))) {/* BD no longer ready*/
|
|
|
|
bd_idx = RetBD - sc->Tx_Frst_BD;
|
|
m = sc->Tx_mBuf_Ptr[bd_idx];
|
|
sc->Tx_mBuf_Ptr[bd_idx] = NULL;
|
|
|
|
MFREE(m,n);
|
|
(void) n;
|
|
RetBD->buffer = NULL;
|
|
/*
|
|
* Advance CurrBD to next BD
|
|
*/
|
|
RetBD = ((RetBD == sc->Tx_Last_BD)
|
|
? sc->Tx_Frst_BD
|
|
: RetBD+1);
|
|
}
|
|
sc->Tx_NxtUsed_BD = RetBD;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_sendpacket
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| handle all tx events |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc, /* control structure */
|
|
struct mbuf *m /* start of packet to send */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
PQBufferDescriptor_t *FrstBD = NULL;
|
|
PQBufferDescriptor_t *CurrBD;
|
|
uint16_t status;
|
|
struct mbuf *l = NULL; /* ptr to last non-freed (non-empty) mbuf */
|
|
int bd_idx;
|
|
/*
|
|
* get next Tx BD
|
|
*/
|
|
CurrBD = sc->Tx_NxtFill_BD;
|
|
while (m) {
|
|
if(m->m_len == 0) {
|
|
/*
|
|
* Just toss empty mbufs
|
|
*/
|
|
struct mbuf *n;
|
|
MFREE(m, n);
|
|
m = n;
|
|
if(l != NULL) {
|
|
l->m_next = m;
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* this mbuf is non-empty, so send it
|
|
*/
|
|
/*
|
|
* Is CurrBD still in Use/not yet retired?
|
|
*/
|
|
while (CurrBD->buffer != NULL) {
|
|
/*
|
|
* Then try to retire it
|
|
* and to return its mbuf
|
|
*/
|
|
tsec_tx_retire(sc);
|
|
if (CurrBD->buffer != NULL) {
|
|
/*
|
|
* Wait for anything to happen...
|
|
*/
|
|
tsec_tx_wait_for_events(sc,INTERRUPT_EVENT);
|
|
}
|
|
}
|
|
status = ((BD_PAD_CRC | BD_TX_CRC)
|
|
| ((m->m_next == NULL)
|
|
? BD_LAST | BD_INTERRUPT
|
|
: 0)
|
|
| ((CurrBD == sc->Tx_Last_BD) ? BD_WRAP : 0));
|
|
|
|
/*
|
|
* link buffer to BD
|
|
*/
|
|
CurrBD->buffer = mtod(m, void *);
|
|
CurrBD->length = (uint32_t)m->m_len;
|
|
l = m; /* remember: we use this mbuf */
|
|
PF("TX[%08x] (%i)\n", CurrBD, m->m_len);
|
|
|
|
bd_idx = CurrBD - sc->Tx_Frst_BD;
|
|
sc->Tx_mBuf_Ptr[bd_idx] = m;
|
|
|
|
m = m->m_next; /* advance to next mbuf of this packet */
|
|
/*
|
|
* is this the first BD of the packet?
|
|
* then don't set it to "READY" state,
|
|
* and remember this BD position
|
|
*/
|
|
if (FrstBD == NULL) {
|
|
FrstBD = CurrBD;
|
|
}
|
|
else {
|
|
status |= BD_READY;
|
|
}
|
|
CurrBD->status = status;
|
|
/*
|
|
* Advance CurrBD to next BD
|
|
*/
|
|
CurrBD = ((CurrBD == sc->Tx_Last_BD)
|
|
? sc->Tx_Frst_BD
|
|
: CurrBD+1);
|
|
}
|
|
}
|
|
/*
|
|
* mbuf chain of this packet
|
|
* has been translated
|
|
* to BD chain, so set first BD ready now
|
|
*/
|
|
if (FrstBD != NULL) {
|
|
FrstBD->status |= BD_READY;
|
|
}
|
|
sc->Tx_NxtFill_BD = CurrBD;
|
|
/*
|
|
* wake up transmitter (clear TSTAT[THLT])
|
|
*/
|
|
sc->reg_ptr->tstat = TSEC_TSTAT_THLT;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_txDaemon
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| handle all tx events |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
void * arg /* argument, is sc structure ptr */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc =
|
|
(struct tsec_struct *)arg;
|
|
struct ifnet *ifp = &sc->arpcom.ac_if;
|
|
struct mbuf *m;
|
|
bool finished = false;
|
|
#if !defined(CLREVENT_IN_IRQ)
|
|
uint32_t irq_events;
|
|
#endif
|
|
|
|
/*
|
|
* enable Tx in MACCFG1 register
|
|
* FIXME: make this irq save
|
|
*/
|
|
sc->reg_ptr->maccfg1 |= TSEC_MACCFG1_TXEN;
|
|
while (!finished) {
|
|
/*
|
|
* wait for events to come in
|
|
*/
|
|
tsec_tx_wait_for_events(sc,
|
|
START_TRANSMIT_EVENT
|
|
| INTERRUPT_EVENT);
|
|
#if !defined(CLREVENT_IN_IRQ)
|
|
/*
|
|
* clear any pending TX events
|
|
*/
|
|
irq_events = sc->reg_ptr->ievent & IEVENT_TXALL;
|
|
sc->reg_ptr->ievent = irq_events;
|
|
#endif
|
|
/*
|
|
* retire any sent tx BDs
|
|
*/
|
|
tsec_tx_retire(sc);
|
|
/*
|
|
* Send packets till queue is empty
|
|
*/
|
|
do {
|
|
/*
|
|
* Get the next mbuf chain to transmit.
|
|
*/
|
|
IF_DEQUEUE(&ifp->if_snd, m);
|
|
|
|
if (m) {
|
|
tsec_sendpacket(sc,m);
|
|
}
|
|
} while (m != NULL);
|
|
|
|
ifp->if_flags &= ~IFF_OACTIVE;
|
|
}
|
|
/*
|
|
* disable Tx in MACCFG1 register
|
|
*/
|
|
sc->reg_ptr->maccfg1 &= ~TSEC_MACCFG1_TXEN;
|
|
/*
|
|
* terminate daemon
|
|
*/
|
|
sc->txDaemonTid = 0;
|
|
rtems_task_exit();
|
|
}
|
|
|
|
/***************************************************************************\
|
|
| Interrupt handlers and management routines |
|
|
\***************************************************************************/
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_tx_irq_handler
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| handle tx interrupts |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
rtems_irq_hdl_param handle /* handle, is sc structure ptr */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc =
|
|
(struct tsec_struct *)handle;
|
|
#if defined(CLREVENT_IN_IRQ)
|
|
uint32_t irq_events;
|
|
#endif
|
|
|
|
PF("TXIRQ\n");
|
|
sc->txInterrupts++;
|
|
/*
|
|
* disable tx interrupts
|
|
*/
|
|
TSEC_IMASK_SET(sc,IEVENT_TXALL,0);
|
|
|
|
#if defined(CLREVENT_IN_IRQ)
|
|
/*
|
|
* clear any pending TX events
|
|
*/
|
|
irq_events = sc->reg_ptr->ievent & IEVENT_TXALL;
|
|
sc->reg_ptr->ievent = irq_events;
|
|
#endif
|
|
/*
|
|
* wake up tx Daemon
|
|
*/
|
|
rtems_bsdnet_event_send(sc->txDaemonTid, INTERRUPT_EVENT);
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_rx_irq_handler
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| handle rx interrupts |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
rtems_irq_hdl_param handle /* handle, is sc structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc =
|
|
(struct tsec_struct *)handle;
|
|
#if defined(CLREVENT_IN_IRQ)
|
|
uint32_t irq_events;
|
|
#endif
|
|
|
|
sc->rxInterrupts++;
|
|
PF("RXIRQ\n");
|
|
/*
|
|
* disable rx interrupts
|
|
*/
|
|
TSEC_IMASK_SET(sc,IEVENT_RXALL,0);
|
|
#if defined(CLREVENT_IN_IRQ)
|
|
/*
|
|
* clear any pending RX events
|
|
*/
|
|
irq_events = sc->reg_ptr->ievent & IEVENT_RXALL;
|
|
sc->reg_ptr->ievent = irq_events;
|
|
#endif
|
|
/*
|
|
* wake up rx Daemon<
|
|
*/
|
|
rtems_bsdnet_event_send(sc->rxDaemonTid, INTERRUPT_EVENT);
|
|
}
|
|
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_err_irq_handler
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| handle error interrupts |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
rtems_irq_hdl_param handle /* handle, is sc structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc =
|
|
(struct tsec_struct *)handle;
|
|
PF("ERIRQ\n");
|
|
/*
|
|
* clear error events in IEVENT
|
|
*/
|
|
sc->reg_ptr->ievent = IEVENT_ERRALL;
|
|
/*
|
|
* has Rx been stopped? then restart it
|
|
*/
|
|
if (0 != (sc->reg_ptr->rstat & TSEC_RSTAT_QHLT)) {
|
|
sc->rxErrors++;
|
|
sc->reg_ptr->rstat = TSEC_RSTAT_QHLT;
|
|
}
|
|
/*
|
|
* has Tx been stopped? then restart it
|
|
*/
|
|
if (0 != (sc->reg_ptr->tstat & TSEC_TSTAT_THLT)) {
|
|
sc->txErrors++;
|
|
sc->reg_ptr->tstat = TSEC_TSTAT_THLT;
|
|
}
|
|
}
|
|
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static uint32_t tsec_irq_mask
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| determine irq mask for given interrupt number |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
int irqnum,
|
|
struct tsec_struct *sc
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| interrupt mask (for ievent/imask register) |
|
|
\*=========================================================================*/
|
|
{
|
|
return ((irqnum == sc->irq_num_tx)
|
|
? IEVENT_TXALL
|
|
: ((irqnum == sc->irq_num_rx)
|
|
? IEVENT_RXALL
|
|
: ((irqnum == sc->irq_num_err)
|
|
? IEVENT_ERRALL
|
|
: 0)));
|
|
}
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_irq_on
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| enable interrupts in TSEC mask register |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
const
|
|
rtems_irq_connect_data *irq_conn_data /* irq connect data */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc =
|
|
(struct tsec_struct *)(irq_conn_data->handle);
|
|
|
|
TSEC_IMASK_SET(sc,
|
|
tsec_irq_mask(irq_conn_data->name,sc),
|
|
~0);
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_irq_off
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| disable TX interrupts in TSEC mask register |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
const
|
|
rtems_irq_connect_data *irq_conn_data /* irq connect data */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc =
|
|
(struct tsec_struct *)irq_conn_data->handle;
|
|
|
|
TSEC_IMASK_SET(sc,
|
|
tsec_irq_mask(irq_conn_data->name,sc),
|
|
0);
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static int tsec_irq_isOn
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| check state of interrupts in TSEC mask register |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
const
|
|
rtems_irq_connect_data *irq_conn_data /* irq connect data */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc =
|
|
(struct tsec_struct *)irq_conn_data->handle;
|
|
|
|
return (0 != (sc->reg_ptr->imask
|
|
& tsec_irq_mask(irq_conn_data->name,sc)));
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_install_irq_handlers
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| (un-)install the interrupt handlers |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc, /* ptr to control structure */
|
|
bool install /* true: install, false: remove */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
size_t i;
|
|
|
|
rtems_irq_connect_data irq_conn_data[3] = {
|
|
{
|
|
sc->irq_num_tx,
|
|
tsec_tx_irq_handler, /* rtems_irq_hdl */
|
|
(rtems_irq_hdl_param)sc, /* (rtems_irq_hdl_param) */
|
|
tsec_irq_on, /* (rtems_irq_enable) */
|
|
tsec_irq_off, /* (rtems_irq_disable) */
|
|
tsec_irq_isOn /* (rtems_irq_is_enabled) */
|
|
},{
|
|
sc->irq_num_rx,
|
|
tsec_rx_irq_handler, /* rtems_irq_hdl */
|
|
(rtems_irq_hdl_param)sc, /* (rtems_irq_hdl_param) */
|
|
tsec_irq_on, /* (rtems_irq_enable) */
|
|
tsec_irq_off, /* (rtems_irq_disable) */
|
|
tsec_irq_isOn /* (rtems_irq_is_enabled) */
|
|
},{
|
|
sc->irq_num_err,
|
|
tsec_err_irq_handler, /* rtems_irq_hdl */
|
|
(rtems_irq_hdl_param)sc, /* (rtems_irq_hdl_param) */
|
|
tsec_irq_on, /* (rtems_irq_enable) */
|
|
tsec_irq_off, /* (rtems_irq_disable) */
|
|
tsec_irq_isOn /* (rtems_irq_is_enabled) */
|
|
}
|
|
};
|
|
|
|
/*
|
|
* (un-)install handler for Tx/Rx/Error
|
|
*/
|
|
for (i = 0;
|
|
i < sizeof(irq_conn_data)/sizeof(irq_conn_data[0]);
|
|
i++) {
|
|
if (install) {
|
|
if (!BSP_install_rtems_irq_handler (&irq_conn_data[i])) {
|
|
rtems_panic("TSEC: cannot install IRQ handler");
|
|
}
|
|
}
|
|
else {
|
|
if (!BSP_remove_rtems_irq_handler (&irq_conn_data[i])) {
|
|
rtems_panic("TSEC: cannot uninstall IRQ handler");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
| Initialization and interface routines |
|
|
\***************************************************************************/
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_init
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| initialize the driver and the hardware |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
void *arg /* argument pointer, contains *sc */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| zero, if success |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc = (struct tsec_struct *)arg;
|
|
struct ifnet *ifp = &sc->arpcom.ac_if;
|
|
/*
|
|
* check, whether device is not yet running
|
|
*/
|
|
if (0 == sc->rxDaemonTid) {
|
|
/*
|
|
* allocate rx/tx BDs
|
|
*/
|
|
mpc83xx_rxbd_alloc_clear(sc);
|
|
mpc83xx_txbd_alloc_clear(sc);
|
|
/*
|
|
* allocate storage for mbuf ptrs
|
|
*/
|
|
sc->Rx_mBuf_Ptr = calloc(sc->rxBdCount,sizeof(struct mbuf *));
|
|
sc->Tx_mBuf_Ptr = calloc(sc->txBdCount,sizeof(struct mbuf *));
|
|
if ((sc->Rx_mBuf_Ptr == NULL) ||
|
|
(sc->Tx_mBuf_Ptr == NULL)) {
|
|
rtems_panic("TSEC: cannot allocate buffers for mbuf management");
|
|
|
|
}
|
|
|
|
/*
|
|
* initialize TSEC hardware:
|
|
* - set interrupt coalescing to BDCount/8, Time of 8 frames
|
|
* - enable DMA snooping
|
|
*/
|
|
tsec_hwinit(sc);
|
|
/*
|
|
* init access to phys
|
|
*/
|
|
tsec_mdio_init(sc);
|
|
/*
|
|
* Start driver tasks
|
|
*/
|
|
sc->txDaemonTid = rtems_bsdnet_newproc("TStx",
|
|
4096,
|
|
tsec_txDaemon,
|
|
sc);
|
|
sc->rxDaemonTid = rtems_bsdnet_newproc("TSrx", 4096,
|
|
tsec_rxDaemon,
|
|
sc);
|
|
/*
|
|
* install interrupt handlers
|
|
*/
|
|
tsec_install_irq_handlers(sc,true);
|
|
}
|
|
/*
|
|
* Set flags appropriately
|
|
*/
|
|
if(ifp->if_flags & IFF_PROMISC) {
|
|
sc->reg_ptr->rctrl |= TSEC_RCTRL_PROM;
|
|
}
|
|
else {
|
|
sc->reg_ptr->rctrl &= ~TSEC_RCTRL_PROM;
|
|
}
|
|
|
|
#if defined(MPC83XX_BOARD_HSC_CM01)
|
|
/*
|
|
* for HSC CM01: we need to configure the PHY to use maximum skew adjust
|
|
*/
|
|
|
|
tsec_mdio_write(-1,sc,23,0x0100);
|
|
#endif
|
|
|
|
/*
|
|
* init timer so the "watchdog function gets called periodically
|
|
*/
|
|
ifp->if_timer = 1;
|
|
/*
|
|
* Tell the world that we're running.
|
|
*/
|
|
ifp->if_flags |= IFF_RUNNING;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_off
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| deinitialize the driver and the hardware |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc /* ptr to control structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
/*
|
|
* deinitialize driver?
|
|
*/
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_stats
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| print statistics |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct tsec_struct *sc /* ptr to control structure */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| <none> |
|
|
\*=========================================================================*/
|
|
{
|
|
if (sc->phy_default >= 0) {
|
|
int media;
|
|
int result;
|
|
/*
|
|
* fetch/print media info
|
|
*/
|
|
media = IFM_MAKEWORD(0,0,0,sc->phy_default); /* fetch from default phy */
|
|
|
|
result = tsec_ioctl(&(sc->arpcom.ac_if),
|
|
SIOCGIFMEDIA,
|
|
(caddr_t)&media);
|
|
if (result == 0) {
|
|
rtems_ifmedia2str(media,NULL,0);
|
|
printf ("\n");
|
|
} else {
|
|
printf ("PHY communication error\n");
|
|
}
|
|
}
|
|
#if 0 /* print all PHY registers */
|
|
{
|
|
int reg;
|
|
uint32_t reg_val;
|
|
printf("****** PHY register values****\n");
|
|
for (reg = 0;reg <= 31;reg++) {
|
|
tsec_mdio_read(-1,sc,reg,®_val);
|
|
printf("%02d:0x%04x%c",reg,reg_val,
|
|
(((reg % 4) == 3) ? '\n' : ' '));
|
|
}
|
|
}
|
|
#endif
|
|
/*
|
|
* print some statistics
|
|
*/
|
|
printf (" Rx Interrupts:%-8lu", sc->rxInterrupts);
|
|
printf (" Rx Errors:%-8lu", sc->rxErrors);
|
|
printf (" Rx packets:%-8lu\n",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_RPKT]);
|
|
printf (" Rx broadcasts:%-8lu",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_RBCA]);
|
|
printf (" Rx multicasts:%-8lu",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_RMCA]);
|
|
printf (" Giant:%-8lu\n",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_ROVR]);
|
|
printf (" Non-octet:%-8lu",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_RALN]);
|
|
printf (" Bad CRC:%-8lu",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_RFCS]);
|
|
printf (" Overrun:%-8lu\n",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_RDRP]);
|
|
|
|
printf (" Tx Interrupts:%-8lu", sc->txInterrupts);
|
|
printf (" Tx Errors:%-8lu", sc->txErrors);
|
|
printf (" Tx packets:%-8lu\n",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_TPKT]);
|
|
printf (" Deferred:%-8lu",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_TDFR]);
|
|
printf (" Late Collision:%-8lu",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_TLCL]);
|
|
printf ("Retransmit Limit:%-8lu\n",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_TEDF]);
|
|
printf (" Underrun:%-8lu\n",
|
|
sc->reg_ptr->rmon_mib[TSEC_RMON_TUND]);
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static int tsec_ioctl
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| perform io control functions |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct ifnet *ifp, /* interface information */
|
|
ioctl_command_t command, /* ioctl command code */
|
|
caddr_t data /* optional data */
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| zero, if success |
|
|
\*=========================================================================*/
|
|
{
|
|
struct tsec_struct *sc = ifp->if_softc;
|
|
int error = 0;
|
|
|
|
switch(command) {
|
|
/*
|
|
* access PHY via MII
|
|
*/
|
|
case SIOCGIFMEDIA:
|
|
case SIOCSIFMEDIA:
|
|
rtems_mii_ioctl (&(sc->mdio_info),sc,command,(void *)data);
|
|
break;
|
|
case SIOCGIFADDR:
|
|
case SIOCSIFADDR:
|
|
/*
|
|
* pass through to general ether_ioctl
|
|
*/
|
|
ether_ioctl(ifp, command, data);
|
|
break;
|
|
|
|
case SIOCSIFFLAGS:
|
|
/*
|
|
* adjust active state
|
|
*/
|
|
if (ifp->if_flags & IFF_RUNNING) {
|
|
tsec_off(sc);
|
|
}
|
|
if (ifp->if_flags & IFF_UP) {
|
|
tsec_init(sc);
|
|
}
|
|
break;
|
|
|
|
case SIO_RTEMS_SHOW_STATS:
|
|
/*
|
|
* show interface statistics
|
|
*/
|
|
tsec_stats(sc);
|
|
break;
|
|
|
|
/*
|
|
* All sorts of multicast commands need to be added here!
|
|
*/
|
|
default:
|
|
error = EINVAL;
|
|
break;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static int tsec_mode_adapt
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| init the PHY and adapt TSEC settings |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct ifnet *ifp
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| 0, if success |
|
|
\*=========================================================================*/
|
|
{
|
|
int result = 0;
|
|
struct tsec_struct *sc = ifp->if_softc;
|
|
int media = IFM_MAKEWORD( 0, 0, 0, sc->phy_default);
|
|
|
|
/* In case no PHY is available stop now */
|
|
if (sc->phy_default < 0) {
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* fetch media status
|
|
*/
|
|
result = tsec_ioctl(ifp,SIOCGIFMEDIA,(caddr_t)&media);
|
|
if (result != 0) {
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* status is unchanged? then do nothing
|
|
*/
|
|
if (media == sc->media_state) {
|
|
return 0;
|
|
}
|
|
/*
|
|
* otherwise: for the first call, try to negotiate mode
|
|
*/
|
|
if (sc->media_state == 0) {
|
|
/*
|
|
* set media status: set auto negotiation -> start auto-negotiation
|
|
*/
|
|
media = IFM_MAKEWORD(0,IFM_AUTO,0,sc->phy_default);
|
|
result = tsec_ioctl(ifp,SIOCSIFMEDIA,(caddr_t)&media);
|
|
if (result != 0) {
|
|
return result;
|
|
}
|
|
/*
|
|
* check auto-negotiation status
|
|
*/
|
|
media = IFM_MAKEWORD(0,0,0,sc->phy_default);
|
|
result = tsec_ioctl(ifp,SIOCGIFMEDIA,(caddr_t)&media);
|
|
if (result != 0 || IFM_NONE == IFM_SUBTYPE(media)) {
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* now set HW according to media results:
|
|
*/
|
|
/*
|
|
* if we are 1000MBit, then switch IF to byte mode
|
|
*/
|
|
if (IFM_1000_T == IFM_SUBTYPE(media)) {
|
|
sc->reg_ptr->maccfg2 =
|
|
((sc->reg_ptr->maccfg2 & ~TSEC_MACCFG2_IFMODE_MSK)
|
|
| TSEC_MACCFG2_IFMODE_BYT);
|
|
}
|
|
else {
|
|
sc->reg_ptr->maccfg2 =
|
|
((sc->reg_ptr->maccfg2 & ~TSEC_MACCFG2_IFMODE_MSK)
|
|
| TSEC_MACCFG2_IFMODE_NIB);
|
|
}
|
|
/*
|
|
* if we are 10MBit, then switch rate to 10M
|
|
*/
|
|
if (IFM_10_T == IFM_SUBTYPE(media)) {
|
|
sc->reg_ptr->ecntrl &= ~TSEC_ECNTRL_R100M;
|
|
}
|
|
else {
|
|
sc->reg_ptr->ecntrl |= TSEC_ECNTRL_R100M;
|
|
}
|
|
/*
|
|
* if we are half duplex then switch to half duplex
|
|
*/
|
|
if (0 == (IFM_FDX & IFM_OPTIONS(media))) {
|
|
sc->reg_ptr->maccfg2 &= ~TSEC_MACCFG2_FULLDUPLEX;
|
|
}
|
|
else {
|
|
sc->reg_ptr->maccfg2 |= TSEC_MACCFG2_FULLDUPLEX;
|
|
}
|
|
/*
|
|
* store current media state for future compares
|
|
*/
|
|
sc->media_state = media;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*=========================================================================*\
|
|
| Function: |
|
|
\*-------------------------------------------------------------------------*/
|
|
static void tsec_watchdog
|
|
(
|
|
/*-------------------------------------------------------------------------*\
|
|
| Purpose: |
|
|
| periodically poll the PHY. if mode has changed, |
|
|
| then adjust the TSEC settings |
|
|
+---------------------------------------------------------------------------+
|
|
| Input Parameters: |
|
|
\*-------------------------------------------------------------------------*/
|
|
struct ifnet *ifp
|
|
)
|
|
/*-------------------------------------------------------------------------*\
|
|
| Return Value: |
|
|
| 1, if success |
|
|
\*=========================================================================*/
|
|
{
|
|
tsec_mode_adapt(ifp);
|
|
ifp->if_timer = TSEC_WATCHDOG_TIMEOUT;
|
|
}
|
|
|
|
static int tsec_driver_attach(struct rtems_bsdnet_ifconfig *config)
|
|
{
|
|
tsec_config *tsec_cfg = config->drv_ctrl;
|
|
int unitNumber = tsec_cfg->unit_number;
|
|
char *unitName = tsec_cfg->unit_name;
|
|
struct tsec_struct *sc;
|
|
struct ifnet *ifp;
|
|
|
|
/*
|
|
* Is driver free?
|
|
*/
|
|
if ((unitNumber <= 0) || (unitNumber > TSEC_COUNT)) {
|
|
|
|
printk ("Bad TSEC unit number.\n");
|
|
return 0;
|
|
|
|
}
|
|
|
|
sc = &tsec_driver[unitNumber - 1];
|
|
ifp = &sc->arpcom.ac_if;
|
|
|
|
if(ifp->if_softc != NULL) {
|
|
printk ("Driver already in use.\n");
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Process options
|
|
*/
|
|
if(config->hardware_address) {
|
|
memcpy(sc->arpcom.ac_enaddr, config->hardware_address, ETHER_ADDR_LEN);
|
|
}
|
|
else {
|
|
rtems_panic("TSEC: No Ethernet address specified!\n");
|
|
}
|
|
|
|
sc->rxBdCount = (config->rbuf_count > 0) ? config->rbuf_count : RX_BUF_COUNT;
|
|
sc->txBdCount = (config->xbuf_count > 0) ? config->xbuf_count : TX_BUF_COUNT;
|
|
sc->acceptBroadcast = !config->ignore_broadcast;
|
|
|
|
/* get pointer to TSEC register block */
|
|
sc->reg_ptr = tsec_cfg->reg_ptr;
|
|
sc->mdio_ptr = tsec_cfg->mdio_ptr;
|
|
|
|
/* IRQ numbers */
|
|
sc->irq_num_tx = tsec_cfg->irq_num_tx;
|
|
sc->irq_num_rx = tsec_cfg->irq_num_rx;
|
|
sc->irq_num_err = tsec_cfg->irq_num_err;
|
|
|
|
/*
|
|
* setup info about mdio interface
|
|
*/
|
|
sc->mdio_info.mdio_r = tsec_mdio_read;
|
|
sc->mdio_info.mdio_w = tsec_mdio_write;
|
|
sc->mdio_info.has_gmii = 1; /* we support gigabit IF */
|
|
|
|
/* PHY address */
|
|
sc->phy_default = tsec_cfg->phy_default;
|
|
|
|
/*
|
|
* Set up network interface values
|
|
*/
|
|
ifp->if_softc = sc;
|
|
ifp->if_unit = unitNumber;
|
|
ifp->if_name = unitName;
|
|
ifp->if_mtu = (config->mtu > 0) ? config->mtu : ETHERMTU;
|
|
ifp->if_init = tsec_init;
|
|
ifp->if_ioctl = tsec_ioctl;
|
|
ifp->if_start = tsec_tx_start;
|
|
ifp->if_output = ether_output;
|
|
ifp->if_watchdog = tsec_watchdog; /* XXX: timer is set in "init" */
|
|
|
|
ifp->if_flags = (config->ignore_broadcast) ? 0 : IFF_BROADCAST;
|
|
/*ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX;*/
|
|
|
|
if(ifp->if_snd.ifq_maxlen == 0) {
|
|
ifp->if_snd.ifq_maxlen = ifqmaxlen;
|
|
}
|
|
|
|
/*
|
|
* Attach the interface
|
|
*/
|
|
if_attach(ifp);
|
|
|
|
ether_ifattach(ifp);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int tsec_driver_attach_detach(
|
|
struct rtems_bsdnet_ifconfig *config,
|
|
int attaching
|
|
)
|
|
{
|
|
if (attaching) {
|
|
return tsec_driver_attach(config);
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|