From 368867f06effaef98552d4703aefee6088cccb8f Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Tue, 24 Sep 2013 09:03:16 -0600 Subject: [PATCH] ENCx24J600 UDP backlog support from Maz Holtzberg --- ChangeLog | 2 + drivers/net/Kconfig | 10 + drivers/net/encx24j600.c | 483 +++++++++++++++++++++++++++++---------- 3 files changed, 379 insertions(+), 116 deletions(-) diff --git a/ChangeLog b/ChangeLog index ca84670995..bd7c978a7d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -5621,4 +5621,6 @@ From Rashid Fatah (2013-9-23). * arch/arm/src/sama5/sam_hsmci.c: TX DMA disabled. It is just not reliable. No idea why. RX DMA is still used (2013-9-23). + * driver/net/encx24j600.c: UDP/RXAVAIL backlog support from Max + Holtzberg (2013-9-24). diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 133a7cc8ce..49957149d9 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -93,6 +93,7 @@ config ENCX24J600 bool "Microchip ENCX24J600 support" default n select SPI + select NET_RXAVAIL ---help--- References: ENC424J600/624J600 Data Sheet Stand-Alone 10/100 Ethernet Controller @@ -123,6 +124,15 @@ config ENCX24J600_FREQUENCY ---help--- Define to use a different bus frequency +config ENCX24J600_NDESCR + int "Descriptor Count" + default 16 + ---help--- + Defines how many descriptors are preallocated for the + transmission and reception queues. + The ENC has a relative large packet buffer of 24kB which can + be used to buffer multiple packets silmutaneously + config ENCX24J600_STATS bool "Network statistics support" default n diff --git a/drivers/net/encx24j600.c b/drivers/net/encx24j600.c index f4d163a338..1b724fa0bc 100644 --- a/drivers/net/encx24j600.c +++ b/drivers/net/encx24j600.c @@ -2,7 +2,7 @@ * drivers/net/encx24j600.c * * Copyright (C) 2013 UVC Ingenieure. All rights reserved. - * Author: Max Holtberg + * Author: Max Holztberg * * References: * - ENC424J600/624J600 Data Sheet, Stand-Alone 10/100 Ethernet Controller @@ -151,6 +151,10 @@ #define ENC_TXTIMEOUT (60*CLK_TCK) +/* RX timeout (Time packets are held in the RX queue until they are dropped) */ + +#define ENC_RXTIMEOUT MSEC2TICK(2000) + /* Poll timeout */ #define ENC_POLLTIMEOUT MSEC2TICK(50) @@ -160,7 +164,6 @@ /* Packet memory layout */ #define PKTMEM_ALIGNED_BUFSIZE ((CONFIG_NET_BUFSIZE + 1) & ~1) -#define PKTMEM_NDESCR ((PKTMEM_SIZE / 2) / PKTMEM_ALIGNED_BUFSIZE) #define PKTMEM_RX_START (PKTMEM_START + PKTMEM_SIZE / 2) /* Followed by RX buffer */ #define PKTMEM_RX_END (PKTMEM_START + PKTMEM_SIZE - 2) /* RX buffer goes to the end of SRAM */ @@ -204,6 +207,7 @@ struct enc_descr_s struct enc_descr_next *flink; uint16_t addr; uint16_t len; + uint32_t ts; /* Timestamp of reception for timeout */ }; /* The enc_driver_s encapsulates all state information for a single hardware @@ -232,9 +236,10 @@ struct enc_driver_s struct work_s towork; /* Tx timeout work queue support */ struct work_s pollwork; /* Poll timeout work queue support */ - struct enc_descr_s descralloc[PKTMEM_NDESCR]; - sq_queue_t freedescr; /* The free descriptor list */ - sq_queue_t txqueue; /* Enqueued descriptors waiting for transmition */ + struct enc_descr_s descralloc[CONFIG_ENCX24J600_NDESCR]; + sq_queue_t freedescr; /* The free descriptor list */ + sq_queue_t txqueue; /* Enqueued descriptors waiting for transmition */ + sq_queue_t rxqueue; /* Unhandled incoming packets waiting for reception */ /* This is the contained SPI driver intstance */ @@ -242,7 +247,7 @@ struct enc_driver_s /* This holds the information visible to uIP/NuttX */ - struct uip_driver_s dev; /* Interface understood by uIP */ + struct uip_driver_s dev; /* Interface understood by uIP */ /* Statistics */ @@ -312,12 +317,18 @@ static int enc_txenqueue(FAR struct enc_driver_s *priv); static int enc_transmit(FAR struct enc_driver_s *priv); static int enc_uiptxpoll(struct uip_driver_s *dev); +/* Common RX logic */ + +static void enc_rxldpkt(FAR struct enc_driver_s *priv, struct enc_descr_s *descr); +static void enc_rxrmpkt(FAR struct enc_driver_s *priv, struct enc_descr_s *descr); +static void enc_rxdispatch(FAR struct enc_driver_s *priv); + /* Interrupt handling */ static void enc_linkstatus(FAR struct enc_driver_s *priv); static void enc_txif(FAR struct enc_driver_s *priv); -static void enc_rxdispatch(FAR struct enc_driver_s *priv); static void enc_pktif(FAR struct enc_driver_s *priv); +static void enc_rxabtif(FAR struct enc_driver_s *priv); static void enc_irqworker(FAR void *arg); static int enc_interrupt(int irq, FAR void *context); @@ -333,6 +344,7 @@ static void enc_polltimer(int argc, uint32_t arg, ...); static int enc_ifup(struct uip_driver_s *dev); static int enc_ifdown(struct uip_driver_s *dev); static int enc_txavail(struct uip_driver_s *dev); +static int enc_rxavail(struct uip_driver_s *dev); #ifdef CONFIG_NET_IGMP static int enc_addmac(struct uip_driver_s *dev, FAR const uint8_t *mac); static int enc_rmmac(struct uip_driver_s *dev, FAR const uint8_t *mac); @@ -1140,6 +1152,7 @@ static int enc_txenqueue(FAR struct enc_driver_s *priv) } else { + nlldbg("no free descriptors\n"); ret = -ENOMEM; } @@ -1214,6 +1227,8 @@ static void enc_linkstatus(FAR struct enc_driver_s *priv) { uint16_t regval; + nllvdbg("link status changed\n"); + /* Before transmitting the first packet after link establishment or * auto-negotiation, the MAC duplex configuration must be manually set to * match the duplex configuration of the PHY. To do this, configure @@ -1276,6 +1291,105 @@ static void enc_txif(FAR struct enc_driver_s *priv) (void)uip_poll(&priv->dev, enc_uiptxpoll); } +/**************************************************************************** + * Function: enc_rxldpkt + * + * Description: + * Load packet from the enc's RX buffer to the uip d_buf. + * + * Parameters: + * priv - Reference to the driver state structure + * descr - Reference to the descriptor that should be loaded + * + * Returned Value: + * None + * + * Assumptions: + * Interrupts are enabled but the caller holds the uIP lock. + * + ****************************************************************************/ + +static void enc_rxldpkt(FAR struct enc_driver_s *priv, + struct enc_descr_s *descr) +{ + DEBUGASSERT(priv != NULL && descr != NULL); + + nllvdbg("load packet @%04x len: %d\n", descr->addr, descr->len); + + /* Set the rx data pointer to the start of the received packet (ERXRDPT) */ + + enc_cmd(priv, ENC_WRXRDPT, descr->addr); + + /* Save the packet length (without the 4 byte CRC) in priv->dev.d_len */ + + priv->dev.d_len = descr->len - 4; + + /* Copy the data data from the receive buffer to priv->dev.d_buf */ + + enc_rdbuffer(priv, priv->dev.d_buf, priv->dev.d_len); + + enc_dumppacket("loaded RX packet", priv->dev.d_buf, priv->dev.d_len); +} + + { + nlldbg("Unsupported packet type dropped (%02x)\n", htons(BUF->type)); + } +} + +/**************************************************************************** + * Function: enc_rxrmpkt + * + * Description: + * Remove packet from the RX queue and free the block of memory in the enc's + * SRAM. + * + * Parameters: + * priv - Reference to the driver state structure + * descr - Reference to the descriptor that should be freed + * + * Returned Value: + * None + * + * Assumptions: + * Interrupts are enabled but the caller holds the uIP lock. + * + ****************************************************************************/ + +static void enc_rxrmpkt(FAR struct enc_driver_s *priv, struct enc_descr_s *descr) +{ + uint16_t addr; + + nllvdbg("free descr: %p\n", descr); + + /* If it is the last descriptor in the queue, advance ERXTAIL. + * This way it is possible that gaps occcur. Maybe pending packets + * can be reordered th enc's DMA to free RX space? + */ + + if (descr == (struct enc_descr_s*)sq_peek(&priv->rxqueue)) + { + /* @REVISIT wrap around? */ + + addr = descr->addr + descr->len - 2; + + nllvdbg("set ERXTAIL to %04x\n", addr); + + enc_wrreg(priv, ENC_ERXTAIL, addr); + + /* Remove packet from RX queue */ + + sq_remfirst(&priv->rxqueue); + } + else + { + /* Remove packet from RX queue */ + + sq_rem((sq_entry_t*)descr, &priv->rxqueue); + } + + sq_addlast((sq_entry_t*)descr, &priv->freedescr); +} + /**************************************************************************** * Function: enc_rxdispatch * @@ -1295,45 +1409,87 @@ static void enc_txif(FAR struct enc_driver_s *priv) static void enc_rxdispatch(FAR struct enc_driver_s *priv) { - /* We only accept IP packets of the configured type and ARP packets */ + struct enc_descr_s *descr; + struct enc_descr_s *next; + + int ret = ERROR; + + /* Process the RX queue */ + + descr = (struct enc_descr_s*)sq_peek(&priv->rxqueue); + + while (descr != NULL) + { + /* Store the next pointer, because removing the item from list will set + * flink to NULL + */ + + next = (struct enc_descr_s*)sq_next(descr); + + /* Load the packet from the enc's SRAM */ + + enc_rxldpkt(priv, descr); + + /* We only accept IP packets of the configured type and ARP packets */ #ifdef CONFIG_NET_IPv6 - if (BUF->type == HTONS(UIP_ETHTYPE_IP6)) + if (BUF->type == HTONS(UIP_ETHTYPE_IP6)) #else - if (BUF->type == HTONS(UIP_ETHTYPE_IP)) + if (BUF->type == HTONS(UIP_ETHTYPE_IP)) #endif - { - nllvdbg("IP packet received (%02x)\n", BUF->type); - uip_arp_ipin(&priv->dev); - uip_input(&priv->dev); - - /* If the above function invocation resulted in data that should be - * sent out on the network, the field d_len will set to a value > 0. - */ - - if (priv->dev.d_len > 0) { - uip_arp_out(&priv->dev); - enc_txenqueue(priv); + nllvdbg("Try to process IP packet (%02x)\n", BUF->type); + + uip_arp_ipin(&priv->dev); + ret = uip_input(&priv->dev); + + if (ret == OK || (clock_systimer() - descr->ts) > ENC_RXTIMEOUT) + { + /* If packet has been sucessfully processed or has timed out, + * free it. + */ + + enc_rxrmpkt(priv, descr); + } + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (priv->dev.d_len > 0) + { + uip_arp_out(&priv->dev); + enc_txenqueue(priv); + } } - } - else if (BUF->type == htons(UIP_ETHTYPE_ARP)) - { - nllvdbg("ARP packet received (%02x)\n", BUF->type); - uip_arp_arpin(&priv->dev); + else if (BUF->type == htons(UIP_ETHTYPE_ARP)) + { + nllvdbg("ARP packet received (%02x)\n", BUF->type); + uip_arp_arpin(&priv->dev); - /* If the above function invocation resulted in data that should be - * sent out on the network, the field d_len will set to a value > 0. - */ + /* ARP packets are freed immediately */ - if (priv->dev.d_len > 0) - { - enc_txenqueue(priv); - } - } - else - { - nlldbg("Unsupported packet type dropped (%02x)\n", htons(BUF->type)); + enc_rxrmpkt(priv, descr); + + /* If the above function invocation resulted in data that should be + * sent out on the network, the field d_len will set to a value > 0. + */ + + if (priv->dev.d_len > 0) + { + enc_txenqueue(priv); + } + } + else + { + /* free unsupported packet */ + + enc_rxrmpkt(priv, descr); + + nlldbg("Unsupported packet type dropped (%02x)\n", htons(BUF->type)); + } + + descr = next; } } @@ -1356,94 +1512,156 @@ static void enc_rxdispatch(FAR struct enc_driver_s *priv) static void enc_pktif(FAR struct enc_driver_s *priv) { + struct enc_descr_s *descr; uint8_t rsv[8]; uint16_t pktlen; uint32_t rxstat; + uint16_t curpkt; + int pktcnt; DEBUGASSERT(priv->nextpkt >= PKTMEM_RX_START && priv->nextpkt <= PKTMEM_RX_END); - /* Set the rx data pointer to the start of the received packet (ERXRDPT) */ - - enc_cmd(priv, ENC_WRXRDPT, priv->nextpkt); - - /* Read the next packet pointer and the 6 byte read status vector (RSV) - * at the beginning of the received packet. (ERXRDPT should auto-increment - * and wrap to the beginning of the read buffer as necessary) + /* Enqueue all pending packets to the RX queue until PKTCNT == 0 or + * no more descriptors are available. */ - enc_rdbuffer(priv, rsv, 8); + pktcnt = (enc_rdreg(priv, ENC_ESTAT) & ESTAT_PKTCNT_MASK) >> ESTAT_PKTCNT_SHIFT; - /* Decode the new next packet pointer, and the RSV. The - * RSV is encoded as: - * - * Bits 0-15: Indicates length of the received frame. This includes the - * destination address, source address, type/length, data, - * padding and CRC fields. This field is stored in little- - * endian format. - * Bits 16-47: Bit encoded RX status. - */ - - priv->nextpkt = (uint16_t)rsv[1] << 8 | (uint16_t)rsv[0]; - pktlen = (uint16_t)rsv[3] << 8 | (uint16_t)rsv[2]; - rxstat = (uint32_t)rsv[7] << 24 | (uint32_t)rsv[6] << 16 | - (uint32_t)rsv[5] << 8 | (uint32_t)rsv[4]; - - nllvdbg("Receiving packet, nextpkt: %04x pktlen: %d rxstat: %08x\n", - priv->nextpkt, pktlen, rxstat); - - /* Check if the packet was received OK */ - - if ((rxstat & RXSTAT_OK) == 0) + while (pktcnt > 0) { - nlldbg("ERROR: RXSTAT: %08x\n", rxstat); + curpkt = priv->nextpkt; -#ifdef CONFIG_ENCX24J600_STATS - priv->stats.rxnotok++; -#endif - } + /* Set the rx data pointer to the start of the received packet (ERXRDPT) */ - /* Check for a usable packet length (4 added for the CRC) */ + enc_cmd(priv, ENC_WRXRDPT, curpkt); - else if (pktlen > (CONFIG_NET_BUFSIZE + 4) || pktlen <= (UIP_LLH_LEN + 4)) - { - nlldbg("Bad packet size dropped (%d)\n", pktlen); -#ifdef CONFIG_ENCX24J600_STATS - priv->stats.rxpktlen++; -#endif - } - - /* Otherwise, read and process the packet */ - - else - { - /* Save the packet length (without the 4 byte CRC) in priv->dev.d_len */ - - priv->dev.d_len = pktlen - 4; - - /* Copy the data data from the receive buffer to priv->dev.d_buf. - * ERXRDPT should be correctly positioned from the last call to to - * enc_rdbuffer (above). + /* Read the next packet pointer and the 6 byte read status vector (RSV) + * at the beginning of the received packet. (ERXRDPT should auto-increment + * and wrap to the beginning of the read buffer as necessary) */ - enc_rdbuffer(priv, priv->dev.d_buf, priv->dev.d_len); - enc_dumppacket("Received Packet", priv->dev.d_buf, priv->dev.d_len); + enc_rdbuffer(priv, rsv, 8); - /* Dispatch the packet to uIP */ + /* Decode the new next packet pointer, and the RSV. The + * RSV is encoded as: + * + * Bits 0-15: Indicates length of the received frame. This includes the + * destination address, source address, type/length, data, + * padding and CRC fields. This field is stored in little- + * endian format. + * Bits 16-47: Bit encoded RX status. + */ - enc_rxdispatch(priv); + priv->nextpkt = (uint16_t)rsv[1] << 8 | (uint16_t)rsv[0]; + pktlen = (uint16_t)rsv[3] << 8 | (uint16_t)rsv[2]; + rxstat = (uint32_t)rsv[7] << 24 | (uint32_t)rsv[6] << 16 | + (uint32_t)rsv[5] << 8 | (uint32_t)rsv[4]; + + nllvdbg("Receiving packet, nextpkt: %04x pktlen: %d rxstat: %08x pktcnt: %d\n", + priv->nextpkt, pktlen, rxstat, pktcnt); + + /* Check if the packet was received OK */ + + if ((rxstat & RXSTAT_OK) == 0) + { + nlldbg("ERROR: RXSTAT: %08x\n", rxstat); + +#ifdef CONFIG_ENCX24J600_STATS + priv->stats.rxnotok++; +#endif + } + + /* Check for a usable packet length (4 added for the CRC) */ + + else if (pktlen > (CONFIG_NET_BUFSIZE + 4) || pktlen <= (UIP_LLH_LEN + 4)) + { + nlldbg("Bad packet size dropped (%d)\n", pktlen); +#ifdef CONFIG_ENCX24J600_STATS + priv->stats.rxpktlen++; +#endif + } + + /* Otherwise, enqueue the packet to the RX queue */ + + else + { + descr = (struct enc_descr_s*)sq_remfirst(&priv->freedescr); + + if (descr != NULL) + { + nllvdbg("allocated RX descriptor: %p\n", descr); + + /* Set current timestamp */ + + descr->ts = clock_systimer(); + + /* Store the start address of the frame without the enc's header */ + + descr->addr = curpkt + 8; + + descr->len = pktlen; + + /* Enqueue packet */ + + sq_addlast((sq_entry_t*)descr, &priv->rxqueue); + + } + else + { + nlldbg("no free descriptors\n"); + } + } + + + /* Decrement PKTCNT */ + + enc_bfs(priv, ENC_ECON1, ECON1_PKTDEC); + + /* Read out again, maybe there has another packet arrived */ + + pktcnt = (enc_rdreg(priv, ENC_ESTAT) & ESTAT_PKTCNT_MASK) >> ESTAT_PKTCNT_SHIFT; } - /* Once the whole frame has been processed, the final value of ERXTAIL should - * be equal to (NextPacketPointer - 2). - */ + /* Process the RX queue */ - /* @TODO check if no special handling needed (skip odd addresses?) */ - enc_wrreg(priv, ENC_ERXTAIL, priv->nextpkt - 2); + enc_rxdispatch(priv); +} - /* Decrement the packet counter indicate we are done with this packet */ +/**************************************************************************** + * Function: enc_rxabtif + * + * Description: + * An interrupt was received indicating the abortion of an RX packet + * + * "The receive abort interrupt occurs when the reception of a frame has been + * aborted. A frame being received is aborted when the Head Pointer attempts + * to overrun the Tail Pointer, or when the packet counter has reached FFh. + * In either case, the receive buffer is full and cannot fit the incoming + * frame, so the packet has been dropped. + * This interrupt does not occur when packets are dropped due to the receive + * filters rejecting a packet. The interrupt should be cleared by software + * once it has been serviced." + * + * + * Parameters: + * priv - Reference to the driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Interrupts are enabled but the caller holds the uIP lock. + * + ****************************************************************************/ + +static void enc_rxabtif(FAR struct enc_driver_s *priv) +{ + /* Free the last received packet from the RX queue */ enc_bfs(priv, ENC_ECON1, ECON1_PKTDEC); + nlldbg("rx abort\n"); + enc_rxrmpkt(priv, (struct enc_descr_s*)sq_peek(&priv->rxqueue)); } /**************************************************************************** @@ -1557,7 +1775,6 @@ static void enc_irqworker(FAR void *arg) } #ifdef CONFIG_ENCX24J600_STATS - /* The transmit abort interrupt occurs when the transmission of a frame * has been aborted. An abort can occur for any of the following reasons: * @@ -1584,6 +1801,7 @@ static void enc_irqworker(FAR void *arg) priv->stats.txerifs++; enc_bfc(priv, ENC_EIR, EIR_TXABTIF); /* Clear the TXABTIF interrupt */ } +#endif /* The receive abort interrupt occurs when the reception of a frame has * been aborted. A frame being received is aborted when the Head Pointer @@ -1600,13 +1818,18 @@ static void enc_irqworker(FAR void *arg) if ((eir & EIR_RXABTIF) != 0) /* Receive Abort */ { +#ifdef CONFIG_ENCX24J600_STATS priv->stats.rxerifs++; +#endif + enc_rxabtif(priv); enc_bfc(priv, ENC_EIR, EIR_RXABTIF); /* Clear the RXABTIF interrupt */ } -#endif - } + /* Enable GPIO interrupts */ + + priv->lower->enable(priv->lower); + /* Enable Ethernet interrupts */ enc_bfs(priv, ENC_EIE, EIE_INTIE); @@ -1615,10 +1838,6 @@ static void enc_irqworker(FAR void *arg) enc_unlock(priv); uip_unlock(lock); - - /* Enable GPIO interrupts */ - - priv->lower->enable(priv->lower); } /**************************************************************************** @@ -1686,10 +1905,9 @@ static void enc_toworker(FAR void *arg) nlldbg("Tx timeout\n"); DEBUGASSERT(priv); - /* Get exclusive access to both uIP and the SPI bus. */ + /* Get exclusive access to uIP. */ lock = uip_lock(); - enc_lock(priv); /* Increment statistics and dump debug info */ @@ -1711,9 +1929,8 @@ static void enc_toworker(FAR void *arg) (void)uip_poll(&priv->dev, enc_uiptxpoll); - /* Release lock on the SPI bus and uIP */ + /* Release uIP */ - enc_unlock(priv); uip_unlock(lock); } @@ -2041,6 +2258,38 @@ static int enc_txavail(struct uip_driver_s *dev) return OK; } +/**************************************************************************** + * Function: enc_rxavail + * + * Description: + * Driver callback invoked when new TX data is available. This is a + * stimulus perform an out-of-cycle poll and, thereby, reduce the TX + * latency. + * + * Parameters: + * dev - Reference to the NuttX driver state structure + * + * Returned Value: + * None + * + * Assumptions: + * Called in normal user mode + * + ****************************************************************************/ + +static int enc_rxavail(struct uip_driver_s *dev) +{ + FAR struct enc_driver_s *priv = (FAR struct enc_driver_s *)dev->d_private; + + if (!sq_empty(&priv->rxqueue)) + { + nlldbg("RX queue not empty, trying to dispatch\n"); + enc_rxdispatch(priv); + } + + return OK; +} + /**************************************************************************** * Function: enc_addmac * @@ -2269,7 +2518,7 @@ static int enc_reset(FAR struct enc_driver_s *priv) int ret; uint16_t regval; - nlldbg("Reset\n"); + nllvdbg("Reset\n"); /* configure SPI for the ENCX24J600 */ @@ -2322,8 +2571,9 @@ static int enc_reset(FAR struct enc_driver_s *priv) sq_init(&priv->freedescr); sq_init(&priv->txqueue); + sq_init(&priv->rxqueue); - for (i = 0; i < PKTMEM_NDESCR; i++) + for (i = 0; i < CONFIG_ENCX24J600_NDESCR; i++) { priv->descralloc[i].addr = PKTMEM_START + PKTMEM_ALIGNED_BUFSIZE * i; sq_addlast((sq_entry_t*)&priv->descralloc[i], &priv->freedescr); @@ -2346,7 +2596,7 @@ static int enc_reset(FAR struct enc_driver_s *priv) } while ((regval & PHSTAT1_ANDONE) != 0); - nlldbg("Auto-negotation completed\n"); + nllvdbg("Auto-negotation completed\n"); enc_linkstatus(priv); @@ -2397,6 +2647,7 @@ int enc_initialize(FAR struct spi_dev_s *spi, priv->dev.d_ifup = enc_ifup; /* I/F up (new IP address) callback */ priv->dev.d_ifdown = enc_ifdown; /* I/F down callback */ priv->dev.d_txavail = enc_txavail; /* New TX data callback */ + priv->dev.d_rxavail = enc_rxavail; /* RX wating callback */ #ifdef CONFIG_NET_IGMP priv->dev.d_addmac = enc_addmac; /* Add multicast MAC address */ priv->dev.d_rmmac = enc_rmmac; /* Remove multicast MAC address */