SOEM EtherCAT Master library for STM Nucleo F767ZI
Dependents: EasyCAT_LAB_simple EasyCAT_LAB_very_simple EasyCAT_LAB
- This repository contains the SOEM (Simple Open EtherCAT® Master) library by rt-labs, that has been ported in the ecosystem by AB&T Tecnologie Informatiche.
- It has been developed for the EasyCAT LAB , a complete educational and experimental EtherCAT® system, composed of one master and two slaves .
- The EasyCAT LAB is provided as a kit by AB&T Tecnologie Informatiche, to allow everybody to have an educational EtherCAT® system up and running in a matter of minutes.
Warning
- Currently only the Nucleo STM32F767ZI board is supported.
Diff: oshw/nicdrv.cpp
- Revision:
- 0:543d6784d4cc
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/oshw/nicdrv.cpp Tue Jun 11 10:29:09 2019 +0000 @@ -0,0 +1,782 @@ +/* + * Licensed under the GNU General Public License version 2 with exceptions. See + * LICENSE file in the project root for full license information + */ + +/** \file + * \brief + * EtherCAT RAW socket driver. + * + * Low level interface functions to send and receive EtherCAT packets. + * EtherCAT has the property that packets are only send by the master, + * and the send packets always return in the receive buffer. + * There can be multiple packets "on the wire" before they return. + * To combine the received packets with the original send packets a buffer + * system is installed. The identifier is put in the index item of the + * EtherCAT header. The index is stored and compared when a frame is received. + * If there is a match the packet can be combined with the transmit packet + * and returned to the higher level function. + * + * The socket layer can exhibit a reversal in the packet order (rare). + * If the Tx order is A-B-C the return order could be A-C-B. The indexed buffer + * will reorder the packets automatically. + * + * The "redundant" option will configure two sockets and two NIC interfaces. + * Slaves are connected to both interfaces, one on the IN port and one on the + * OUT port. Packets are send via both interfaces. Any one of the connections + * (also an interconnect) can be removed and the slaves are still serviced with + * packets. The software layer will detect the possible failure modes and + * compensate. If needed the packets from interface A are resent through interface B. + * This layer is fully transparent for the higher layers. + */ + + +#include "mbed.h" + +#include <stdio.h> +#include <string.h> + +#include "osal.h" +#include "oshw.h" + +#ifndef MAX +#define MAX(a,b) (((a) > (b)) ? (a) : (b)) +#define MIN(a,b) (((a) < (b)) ? (a) : (b)) +#endif + + + +/** Redundancy modes */ +enum +{ + /** No redundancy, single NIC mode */ + ECT_RED_NONE, + /** Double redundant NIC connection */ + ECT_RED_DOUBLE +}; + +/** Primary source MAC address used for EtherCAT. + * This address is not the MAC address used from the NIC. + * EtherCAT does not care about MAC addressing, but it is used here to + * differentiate the route the packet traverses through the EtherCAT + * segment. This is needed to find out the packet flow in redundant + * configurations. */ +const uint16 priMAC[3] = { 0x0101, 0x0101, 0x0101 }; +/** Secondary source MAC address used for EtherCAT. */ +const uint16 secMAC[3] = { 0x0404, 0x0404, 0x0404 }; + +/** second MAC word is used for identification */ +#define RX_PRIM priMAC[1] +/** second MAC word is used for identification */ +#define RX_SEC secMAC[1] + + + + +//****************************************************************************** +// driver used: +// \mbed-os-5.5.7\targets\TARGET_STM\TARGET_STM32F7\device\stm32f7xx_hal_eth.c +//****************************************************************************** + + +GPIO_InitTypeDef GPIO_InitStructure; + + + +ETH_HandleTypeDef hEth; // ethernet interface handle structure + +ETH_DMADescTypeDef DMATxDescTab[ETH_TXBUFNB]; // dma tx descriptors +ETH_DMADescTypeDef DMARxDescTab[ETH_RXBUFNB]; // dma rx descriptors + +uint8_t DmaTxBuff[ETH_TXBUFNB][ETH_MAX_PACKET_SIZE]; // dma tx buffers +uint8_t DmaRxBuff[ETH_RXBUFNB][ETH_MAX_PACKET_SIZE]; // dma rx buffers + + + +HAL_StatusTypeDef HalStatus; // response from Hal functions + + +//------------------------------------------------------------------------------ + +void EthInit() +{ + // HAL status codes + // + // HAL_OK = 0x00U, + // HAL_ERROR = 0x01U, + // HAL_BUSY = 0x02U, + // HAL_TIMEOUT = 0x03U + + + uint8_t MACAddr[6] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC}; // mac address + + hEth.State = HAL_ETH_STATE_RESET; // don't know if it is useful !!! + + hEth.Instance = ETH; + hEth.Init.AutoNegotiation = ETH_AUTONEGOTIATION_DISABLE; + hEth.Init.Speed = ETH_SPEED_100M; + hEth.Init.DuplexMode = ETH_MODE_FULLDUPLEX; + hEth.Init.PhyAddress = 0x00; + hEth.Init.MACAddr = &MACAddr[0]; + hEth.Init.RxMode = ETH_RXPOLLING_MODE; + hEth.Init.ChecksumMode = ETH_CHECKSUM_BY_HARDWARE; + hEth.Init.MediaInterface = ETH_MEDIA_INTERFACE_RMII; + + HalStatus = HAL_ETH_Init(&hEth); + NVIC_DisableIRQ(ETH_IRQn); // we don't use interrupt + if (HalStatus != HAL_OK) + { + printf ("HAL_ETH_Init err %d\n", HalStatus); + } + + HalStatus = HAL_ETH_DMATxDescListInit(&hEth, &DMATxDescTab[0], &DmaTxBuff[0][0], ETH_TXBUFNB); + if (HalStatus != HAL_OK) + { + printf ("HAL_ETH_DMATxDescListInit err %d\n", HalStatus); + } + + HalStatus = HAL_ETH_DMARxDescListInit(&hEth, &DMARxDescTab[0], &DmaRxBuff[0][0], ETH_RXBUFNB); + if (HalStatus != HAL_OK) + { + printf ("HAL_ETH_DMARxDescListInit err %d\n", HalStatus); + } + + HalStatus = HAL_ETH_Start(&hEth); + if (HalStatus != HAL_OK) + { + printf ("HAL_ETH_Start err %d\n", HalStatus); + } +} + +//------------------------------------------------------------------------------ + +int EthWrPacket (uint8_t* pBuff, uint16_t Len) +{ + uint8_t* pDmaBuff; + + if ((hEth.TxDesc->Status & ETH_DMATXDESC_OWN) == (uint32_t)RESET) + + { + pDmaBuff = (uint8_t*)(hEth.TxDesc->Buffer1Addr); + + memcpy (pDmaBuff, pBuff, Len); + + HalStatus = HAL_ETH_TransmitFrame(&hEth, Len); + if (HalStatus != HAL_OK) + { + printf ("HAL_ETH_TransmitFrame err %d\n", HalStatus); + return NULL; + } + + return Len; + } + + else + { + return NULL; + } +} + +//------------------------------------------------------------------------------ + +int EthRdPacket(uint8_t* pBuff) +{ + int Len; + uint8_t* pDmaBuff; + + HalStatus = HAL_ETH_GetReceivedFrame(&hEth); // check if a packet has been received + + if (HalStatus == HAL_OK) // packet received + { + Len = hEth.RxFrameInfos.length; // packet lenght + pDmaBuff = (uint8_t*)hEth.RxFrameInfos.buffer; // DMA buffer pointer + + memcpy (pBuff, pDmaBuff, Len); // read the data + + // release the descriptor + hEth.RxFrameInfos.FSRxDesc->Status |= ETH_DMARXDESC_OWN; + hEth.RxFrameInfos.SegCount = 0; // reset segment count + + return Len; // return the number of bytes read + } + + else + { + return NULL; // no packet received + } +} + +//***************************************************************************** +// +//***************************************************************************** + + + +static void ecx_clear_rxbufstat(int *rxbufstat) +{ + int i; + for(i = 0; i < EC_MAXBUF; i++) + { + rxbufstat[i] = EC_BUF_EMPTY; + } +} + +/** Basic setup to connect NIC to socket. + * @param[in] port = port context struct + * @param[in] ifname = Name of NIC device, f.e. "eth0" + * @param[in] secondary = if >0 then use secondary stack instead of primary + * @return >0 if succeeded + */ +int ecx_setupnic(ecx_portt *port, const char *ifname, int secondary) +{ + int i; + int rVal; + int *psock; + + EthInit(); + + if (rVal != 0) + return 0; + + if (secondary) + { + /* secondary port struct available? */ + if (port->redport) + { + /* when using secondary socket it is automatically a redundant setup */ + psock = &(port->redport->sockhandle); + *psock = -1; + port->redstate = ECT_RED_DOUBLE; + port->redport->stack.sock = &(port->redport->sockhandle); + port->redport->stack.txbuf = &(port->txbuf); + port->redport->stack.txbuflength = &(port->txbuflength); + port->redport->stack.tempbuf = &(port->redport->tempinbuf); + port->redport->stack.rxbuf = &(port->redport->rxbuf); + port->redport->stack.rxbufstat = &(port->redport->rxbufstat); + port->redport->stack.rxsa = &(port->redport->rxsa); + ecx_clear_rxbufstat(&(port->redport->rxbufstat[0])); + } + else + { + /* fail */ + return 0; + } + } + else + { + port->sockhandle = -1; + port->lastidx = 0; + port->redstate = ECT_RED_NONE; + port->stack.sock = &(port->sockhandle); + port->stack.txbuf = &(port->txbuf); + port->stack.txbuflength = &(port->txbuflength); + port->stack.tempbuf = &(port->tempinbuf); + port->stack.rxbuf = &(port->rxbuf); + port->stack.rxbufstat = &(port->rxbufstat); + port->stack.rxsa = &(port->rxsa); + ecx_clear_rxbufstat(&(port->rxbufstat[0])); + psock = &(port->sockhandle); + } + + /* setup ethernet headers in tx buffers so we don't have to repeat it */ + for (i = 0; i < EC_MAXBUF; i++) + { + ec_setupheader(&(port->txbuf[i])); + port->rxbufstat[i] = EC_BUF_EMPTY; + } + ec_setupheader(&(port->txbuf2)); + + return 1; +} + +/** Close sockets used + * @param[in] port = port context struct + * @return 0 + */ +int ecx_closenic(ecx_portt *port) +{ + if (port->sockhandle >= 0) + { + close(port->sockhandle); + } + if ((port->redport) && (port->redport->sockhandle >= 0)) + { + close(port->redport->sockhandle); + } + return 0; +} + +/** Fill buffer with ethernet header structure. + * Destination MAC is always broadcast. + * Ethertype is always ETH_P_ECAT. + * @param[out] p = buffer + */ + +void ec_setupheader(void *p) +{ + ec_etherheadert *bp; + + bp = (ec_etherheadert*)p; + + + bp->da0 = oshw_htons(0xffff); + bp->da1 = oshw_htons(0xffff); + bp->da2 = oshw_htons(0xffff); + bp->sa0 = oshw_htons(priMAC[0]); + bp->sa1 = oshw_htons(priMAC[1]); + bp->sa2 = oshw_htons(priMAC[2]); + bp->etype = oshw_htons(ETH_P_ECAT); +} + +/** Get new frame identifier index and allocate corresponding rx buffer. + * @param[in] port = port context struct + * @return new index. + */ +int ecx_getindex(ecx_portt *port) +{ + int idx; + int cnt; + + // mtx_lock (port->getindex_mutex); //******// + + idx = port->lastidx + 1; + /* index can't be larger than buffer array */ + if (idx >= EC_MAXBUF) + { + idx = 0; + } + cnt = 0; + /* try to find unused index */ + while ((port->rxbufstat[idx] != EC_BUF_EMPTY) && (cnt < EC_MAXBUF)) + { + idx++; + cnt++; + if (idx >= EC_MAXBUF) + { + idx = 0; + } + } + port->rxbufstat[idx] = EC_BUF_ALLOC; + if (port->redstate != ECT_RED_NONE) + { + port->redport->rxbufstat[idx] = EC_BUF_ALLOC; + } + port->lastidx = idx; + + // mtx_unlock (port->getindex_mutex); //******// + + return idx; +} + +/** Set rx buffer status. + * @param[in] port = port context struct + * @param[in] idx = index in buffer array + * @param[in] bufstat = status to set + */ +void ecx_setbufstat(ecx_portt *port, int idx, int bufstat) +{ + port->rxbufstat[idx] = bufstat; + if (port->redstate != ECT_RED_NONE) + { + port->redport->rxbufstat[idx] = bufstat; + } +} + +/** Transmit buffer over socket (non blocking). + * @param[in] port = port context struct + * @param[in] idx = index in tx buffer array + * @param[in] stacknumber = 0=Primary 1=Secondary stack + * @return socket send result + */ +int ecx_outframe(ecx_portt *port, int idx, int stacknumber) +{ + int lp, rval; + ec_stackT *stack; + + if (!stacknumber) + { + stack = &(port->stack); + } + else + { + stack = &(port->redport->stack); + } + lp = (*stack->txbuflength)[idx]; + (*stack->rxbufstat)[idx] = EC_BUF_TX; + + rval = EthWrPacket ((*stack->txbuf)[idx], lp); + + return rval; +} + +/** Transmit buffer over socket (non blocking). + * @param[in] port = port context struct + * @param[in] idx = index in tx buffer array + * @return socket send result + */ +int ecx_outframe_red(ecx_portt *port, int idx) +{ + ec_comt *datagramP; + ec_etherheadert *ehp; + int rval; + + ehp = (ec_etherheadert *)&(port->txbuf[idx]); + /* rewrite MAC source address 1 to primary */ + ehp->sa1 = oshw_htons(priMAC[1]); + /* transmit over primary socket*/ + rval = ecx_outframe(port, idx, 0); + if (port->redstate != ECT_RED_NONE) + { + // mtx_lock (port->tx_mutex); //******// + ehp = (ec_etherheadert *)&(port->txbuf2); + /* use dummy frame for secondary socket transmit (BRD) */ + datagramP = (ec_comt*)&(port->txbuf2[ETH_HEADERSIZE]); + /* write index to frame */ + datagramP->index = idx; + /* rewrite MAC source address 1 to secondary */ + ehp->sa1 = oshw_htons(secMAC[1]); + /* transmit over secondary socket */ + //send(sockhandle2, &ec_txbuf2, ec_txbuflength2 , 0); + // OBS! redundant not ACTIVE for BFIN, just added to compile + + port->redport->rxbufstat[idx] = EC_BUF_TX; + + EthWrPacket((uint8_t*)&(port->txbuf2), port->txbuflength2); + + // mtx_unlock (port->tx_mutex); //******// + } + + return rval; +} + +/** Non blocking read of socket. Put frame in temporary buffer. + * @param[in] port = port context struct + * @param[in] stacknumber = 0=primary 1=secondary stack + * @return >0 if frame is available and read + */ +static int ecx_recvpkt(ecx_portt *port, int stacknumber) +{ + int lp, bytesrx; + ec_stackT *stack; + + if (!stacknumber) + { + stack = &(port->stack); + } + else + { + stack = &(port->redport->stack); + } + lp = sizeof(port->tempinbuf); + + bytesrx = EthRdPacket(*stack->tempbuf); + + + port->tempinbufs = bytesrx; + + return (bytesrx > 0); +} + +/** Non blocking receive frame function. Uses RX buffer and index to combine + * read frame with transmitted frame. To compensate for received frames that + * are out-of-order all frames are stored in their respective indexed buffer. + * If a frame was placed in the buffer previously, the function retrieves it + * from that buffer index without calling ec_recvpkt. If the requested index + * is not already in the buffer it calls ec_recvpkt to fetch it. There are + * three options now, 1 no frame read, so exit. 2 frame read but other + * than requested index, store in buffer and exit. 3 frame read with matching + * index, store in buffer, set completed flag in buffer status and exit. + * + * @param[in] port = port context struct + * @param[in] idx = requested index of frame + * @param[in] stacknumber = 0=primary 1=secondary stack + * @return Workcounter if a frame is found with corresponding index, otherwise + * EC_NOFRAME or EC_OTHERFRAME. + */ +int ecx_inframe(ecx_portt *port, int idx, int stacknumber) +{ + uint16 l; + int rval; + uint8 idxf; + ec_etherheadert *ehp; + ec_comt *ecp; + ec_stackT *stack; + ec_bufT *rxbuf; + + if (!stacknumber) + { + stack = &(port->stack); + } + else + { + stack = &(port->redport->stack); + } + rval = EC_NOFRAME; + rxbuf = &(*stack->rxbuf)[idx]; + /* check if requested index is already in buffer ? */ + if ((idx < EC_MAXBUF) && ( (*stack->rxbufstat)[idx] == EC_BUF_RCVD)) + { + l = (*rxbuf)[0] + ((uint16)((*rxbuf)[1] & 0x0f) << 8); + /* return WKC */ + rval = ((*rxbuf)[l] + ((uint16)(*rxbuf)[l + 1] << 8)); + /* mark as completed */ + (*stack->rxbufstat)[idx] = EC_BUF_COMPLETE; + } + else + { + // mtx_lock (port->rx_mutex); //******// + /* non blocking call to retrieve frame from socket */ + if (ecx_recvpkt(port, stacknumber)) + { + rval = EC_OTHERFRAME; + ehp =(ec_etherheadert*)(stack->tempbuf); + /* check if it is an EtherCAT frame */ + if (ehp->etype == oshw_htons(ETH_P_ECAT)) + { + ecp =(ec_comt*)(&(*stack->tempbuf)[ETH_HEADERSIZE]); + l = etohs(ecp->elength) & 0x0fff; + idxf = ecp->index; + /* found index equals requested index ? */ + if (idxf == idx) + { + /* yes, put it in the buffer array (strip ethernet header) */ + memcpy(rxbuf, &(*stack->tempbuf)[ETH_HEADERSIZE], (*stack->txbuflength)[idx] - ETH_HEADERSIZE); + /* return WKC */ + rval = ((*rxbuf)[l] + ((uint16)((*rxbuf)[l + 1]) << 8)); + /* mark as completed */ + (*stack->rxbufstat)[idx] = EC_BUF_COMPLETE; + /* store MAC source word 1 for redundant routing info */ + (*stack->rxsa)[idx] = oshw_ntohs(ehp->sa1); + } + else + { + /* check if index exist and someone is waiting for it */ + if (idxf < EC_MAXBUF && (*stack->rxbufstat)[idxf] == EC_BUF_TX) + { + rxbuf = &(*stack->rxbuf)[idxf]; + /* put it in the buffer array (strip ethernet header) */ + memcpy(rxbuf, &(*stack->tempbuf)[ETH_HEADERSIZE], (*stack->txbuflength)[idxf] - ETH_HEADERSIZE); + /* mark as received */ + (*stack->rxbufstat)[idxf] = EC_BUF_RCVD; + (*stack->rxsa)[idxf] = oshw_ntohs(ehp->sa1); + } + else + { + /* strange things happened */ + } + } + } + } + // mtx_unlock (port->rx_mutex); //******// + + } + + /* WKC if matching frame found */ + return rval; +} + + +/** Blocking redundant receive frame function. If redundant mode is not active then + * it skips the secondary stack and redundancy functions. In redundant mode it waits + * for both (primary and secondary) frames to come in. The result goes in an decision + * tree that decides, depending on the route of the packet and its possible missing arrival, + * how to reroute the original packet to get the data in an other try. + * + * @param[in] port = port context struct + * @param[in] idx = requested index of frame + * @param[in] timer = absolute timeout time + * @return Workcounter if a frame is found with corresponding index, otherwise + * EC_NOFRAME. + */ +static int ecx_waitinframe_red(ecx_portt *port, int idx, osal_timert timer) +{ + int wkc = EC_NOFRAME; + int wkc2 = EC_NOFRAME; + int primrx, secrx; + + /* if not in redundant mode then always assume secondary is OK */ + if (port->redstate == ECT_RED_NONE) + { + wkc2 = 0; + } + do + { + /* only read frame if not already in */ + if (wkc <= EC_NOFRAME) + { + wkc = ecx_inframe(port, idx, 0); + } + /* only try secondary if in redundant mode */ + if (port->redstate != ECT_RED_NONE) + { + /* only read frame if not already in */ + if (wkc2 <= EC_NOFRAME) + wkc2 = ecx_inframe(port, idx, 1); + } + /* wait for both frames to arrive or timeout */ + } while (((wkc <= EC_NOFRAME) || (wkc2 <= EC_NOFRAME)) && (osal_timer_is_expired(&timer) == FALSE)); + + /* only do redundant functions when in redundant mode */ + if (port->redstate != ECT_RED_NONE) + { + /* primrx if the received MAC source on primary socket */ + primrx = 0; + if (wkc > EC_NOFRAME) + { + primrx = port->rxsa[idx]; + } + /* secrx if the received MAC source on psecondary socket */ + secrx = 0; + if (wkc2 > EC_NOFRAME) + { + secrx = port->redport->rxsa[idx]; + } + /* primary socket got secondary frame and secondary socket got primary frame */ + /* normal situation in redundant mode */ + if ( ((primrx == RX_SEC) && (secrx == RX_PRIM)) ) + { + /* copy secondary buffer to primary */ + memcpy(&(port->rxbuf[idx]), &(port->redport->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE); + wkc = wkc2; + } + /* primary socket got nothing or primary frame, and secondary socket got secondary frame */ + /* we need to resend TX packet */ + if ( ((primrx == 0) && (secrx == RX_SEC)) || + ((primrx == RX_PRIM) && (secrx == RX_SEC)) ) + { + osal_timert read_timer; + + /* If both primary and secondary have partial connection retransmit the primary received + * frame over the secondary socket. The result from the secondary received frame is a combined + * frame that traversed all slaves in standard order. */ + if ( (primrx == RX_PRIM) && (secrx == RX_SEC) ) + { + /* copy primary rx to tx buffer */ + memcpy(&(port->txbuf[idx][ETH_HEADERSIZE]), &(port->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE); + } + + //osal_timer_start(&read_timer, EC_TIMEOUTRET); + + osal_timer_start(&read_timer, 500); + + /* resend secondary tx */ + ecx_outframe(port, idx, 1); + do + { + /* retrieve frame */ + wkc2 = ecx_inframe(port, idx, 1); + } while ((wkc2 <= EC_NOFRAME) && (osal_timer_is_expired(&read_timer) == FALSE)); + if (wkc2 > EC_NOFRAME) + { + /* copy secondary result to primary rx buffer */ + memcpy(&(port->rxbuf[idx]), &(port->redport->rxbuf[idx]), port->txbuflength[idx] - ETH_HEADERSIZE); + wkc = wkc2; + } + } + } + + /* return WKC or EC_NOFRAME */ + return wkc; +} + +/** Blocking receive frame function. Calls ec_waitinframe_red(). + * @param[in] port = port context struct + * @param[in] idx = requested index of frame + * @param[in] timeout = timeout in us + * @return Workcounter if a frame is found with corresponding index, otherwise + * EC_NOFRAME. + */ +int ecx_waitinframe(ecx_portt *port, int idx, int timeout) +{ + int wkc; + + osal_timert timer; + osal_timer_start (&timer, timeout); + wkc = ecx_waitinframe_red(port, idx, timer); + + return wkc; +} + +/** Blocking send and receive frame function. Used for non processdata frames. + * A datagram is build into a frame and transmitted via this function. It waits + * for an answer and returns the workcounter. The function retries if time is + * left and the result is WKC=0 or no frame received. + * + * The function calls ec_outframe_red() and ec_waitinframe_red(). + * + * @param[in] port = port context struct + * @param[in] idx = index of frame + * @param[in] timeout = timeout in us + * @return Workcounter or EC_NOFRAME + */ +int ecx_srconfirm(ecx_portt *port, int idx, int timeout) +{ + int wkc = EC_NOFRAME; + + osal_timert timer; + osal_timer_start(&timer, timeout); + + do + { + osal_timert read_timer; + + /* tx frame on primary and if in redundant mode a dummy on secondary */ + ecx_outframe_red(port, idx); + osal_timer_start(&read_timer, MIN(timeout, EC_TIMEOUTRET)); + + /* get frame from primary or if in redundant mode possibly from secondary */ + wkc = ecx_waitinframe_red(port, idx, read_timer); + /* wait for answer with WKC>0 or otherwise retry until timeout */ + } while ((wkc <= EC_NOFRAME) && (osal_timer_is_expired(&timer) == FALSE)); + + return wkc; +} + + +#ifdef EC_VER1 +int ec_setupnic(const char *ifname, int secondary) +{ + return ecx_setupnic(&ecx_port, ifname, secondary); +} + +int ec_closenic(void) +{ + return ecx_closenic(&ecx_port); +} + +int ec_getindex(void) +{ + return ecx_getindex(&ecx_port); +} + +void ec_setbufstat(int idx, int bufstat) +{ + ecx_setbufstat(&ecx_port, idx, bufstat); +} + +int ec_outframe(int idx, int stacknumber) +{ + return ecx_outframe(&ecx_port, idx, stacknumber); +} + +int ec_outframe_red(int idx) +{ + return ecx_outframe_red(&ecx_port, idx); +} + +int ec_inframe(int idx, int stacknumber) +{ + return ecx_inframe(&ecx_port, idx, stacknumber); +} + +int ec_waitinframe(int idx, int timeout) +{ + return ecx_waitinframe(&ecx_port, idx, timeout); +} + +int ec_srconfirm(int idx, int timeout) +{ + return ecx_srconfirm(&ecx_port, idx, timeout); +} +#endif +