/* * Copyright (C) 2001-2005 by egnite Software GmbH. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ * */ /*! * \file arch/avr/dev/nicrtl.c * \brief AVR network driver for RTL8019AS. * * \verbatim * $Id: nicrtl.c 4711 2012-10-07 14:38:49Z haraldkipp $ * \endverbatim */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rtlregs.h" #ifdef NUTDEBUG #include #include #endif #ifndef NUT_THREAD_NICRXSTACK #define NUT_THREAD_NICRXSTACK 256 #endif /* * Determine ports, which had not been explicitely configured. */ #if (RTL_RESET_AVRPORT == AVRPORTB) #define RTL_RESET_PORT PORTB #define RTL_RESET_DDR DDRB #elif (RTL_RESET_AVRPORT == AVRPORTD) #define RTL_RESET_PORT PORTD #define RTL_RESET_DDR DDRD #elif (RTL_RESET_AVRPORT == AVRPORTE) #define RTL_RESET_PORT PORTE #define RTL_RESET_DDR DDRE #elif (RTL_RESET_AVRPORT == AVRPORTF) #define RTL_RESET_PORT PORTF #define RTL_RESET_DDR DDRF #endif /* RTL_RESET_AVRPORT */ /*! * \brief Interrupt used. */ #if (RTL_SIGNAL_IRQ == INT0) #define RTL_SIGNAL sig_INTERRUPT0 #ifdef __AVR_ENHANCED__ #define RTL_RISING_EDGE_MODE() sbi(EICRA, ISC00); sbi(EICRA, ISC01) #endif #elif (RTL_SIGNAL_IRQ == INT1) #define RTL_SIGNAL sig_INTERRUPT1 #ifdef __AVR_ENHANCED__ #define RTL_RISING_EDGE_MODE() sbi(EICRA, ISC10); sbi(EICRA, ISC11) #endif #elif (RTL_SIGNAL_IRQ == INT2) #define RTL_SIGNAL sig_INTERRUPT2 #ifdef __AVR_ENHANCED__ #define RTL_RISING_EDGE_MODE() sbi(EICRA, ISC20); sbi(EICRA, ISC21) #endif #elif (RTL_SIGNAL_IRQ == INT3) #define RTL_SIGNAL sig_INTERRUPT3 #ifdef __AVR_ENHANCED__ #define RTL_RISING_EDGE_MODE() sbi(EICRA, ISC30); sbi(EICRA, ISC31) #endif #elif (RTL_SIGNAL_IRQ == INT4) #define RTL_SIGNAL sig_INTERRUPT4 #ifdef __AVR_ENHANCED__ #define RTL_RISING_EDGE_MODE() sbi(EICRB, ISC40); sbi(EICRB, ISC41) #endif #elif (RTL_SIGNAL_IRQ == INT6) #define RTL_SIGNAL sig_INTERRUPT6 #ifdef __AVR_ENHANCED__ #define RTL_RISING_EDGE_MODE() sbi(EICRB, ISC60); sbi(EICRB, ISC61) #endif #elif (RTL_SIGNAL_IRQ == INT7) #define RTL_SIGNAL sig_INTERRUPT7 #ifdef __AVR_ENHANCED__ #define RTL_RISING_EDGE_MODE() sbi(EICRB, ISC70); sbi(EICRB, ISC71) #endif #else #define RTL_SIGNAL sig_INTERRUPT5 #ifdef __AVR_ENHANCED__ #define RTL_RISING_EDGE_MODE() sbi(EICRB, ISC50); sbi(EICRB, ISC51) #endif #endif /* RTL_SIGNAL_IRQ */ /*! * \brief Size of a single ring buffer page. */ #define NIC_PAGE_SIZE 0x100 /*! * \brief First ring buffer page address. */ #define NIC_START_PAGE 0x40 /*! * \brief Last ring buffer page address plus 1. */ #define NIC_STOP_PAGE 0x60 /*! * \brief Number of pages in a single transmit buffer. * * This should be at least the MTU size. */ #define NIC_TX_PAGES 6 /*! * \brief Number of transmit buffers. * * The initial value had been 2. The idea was to use two alternating * buffers. However, this had never been implemented and we took over * Bengt Florin's change, defining 1 transmit buffer only and give * more buffer space to the receiver. * * The controller memory layout is now * * - 0x4000..0x45ff 1.5K bytes transmit buffer * - 0x4600..0x5fff 6.5K bytes receive buffer * * The routines are still not using the buffers in an optimal way, * as transmission is limited to 1 packet at a time. In fact several * smaller packets would fit into the 1.5 kByte buffer. On the other * hand, filling the buffer with more than one transmission packet * may result in other bad effects, like pulling the IORDY line more * often. */ #define NIC_TX_BUFFERS 1 #define NIC_FIRST_TX_PAGE NIC_START_PAGE #define NIC_FIRST_RX_PAGE (NIC_FIRST_TX_PAGE + NIC_TX_PAGES * NIC_TX_BUFFERS) #define NIC_CR_PAGE0 (0) #define NIC_CR_PAGE1 (NIC_CR_PS0) #define NIC_CR_PAGE2 (NIC_CR_PS1) #define NIC_CR_PAGE3 (NIC_CR_PS1 | NIC_CR_PS0) /* * This delay has been added by Bengt Florin and is used to minimize * the effect of the IORDY line during reads. Bengt contributed a * more versatile loop, which unfortunately wasn't portable to the * ImageCraft compiler. * * Both versions depend on the CPU clock and had been tested with * 14.7456 MHz. */ static INLINE void Delay16Cycles(void) { _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); _NOP(); } /*! * \addtogroup xgNicRtl */ /*@{*/ /*! * Realtek packet header. */ struct nic_pkt_header { uint8_t ph_status; /*!< \brief Status, contents of RSR register */ uint8_t ph_nextpg; /*!< \brief Page for next packet */ uint16_t ph_size; /*!< \brief Size of header and packet in octets */ }; #define NICINB(reg) (*((volatile uint8_t *)RTL_BASE_ADDR + reg)) #define NICOUTB(reg, val) (*((volatile uint8_t *)RTL_BASE_ADDR + reg) = val) /*! * \brief Reset the Ethernet controller. * */ static int NicReset(void) { uint8_t i; uint8_t j; /* * Toggle the hardware reset line. Since Ethernut version 1.3 the * hardware reset pin of the nic is no longer connected to bit 4 * on port E, but wired to the board reset line. */ #ifdef RTL_RESET_BIT sbi(RTL_RESET_DDR, RTL_RESET_BIT); sbi(RTL_RESET_PORT, RTL_RESET_BIT); NutDelay(WAIT100); cbi(RTL_RESET_PORT, RTL_RESET_BIT); NutDelay(WAIT250); NutDelay(WAIT250); #endif /* * Do the software reset by reading from the reset register followed * by writing to the reset register. Wait until the controller enters * the reset state. */ for (j = 0; j < 20; j++) { i = NICINB(NIC_RESET); NutDelay(WAIT5); NICOUTB(NIC_RESET, i); for (i = 0; i < 20; i++) { NutDelay(WAIT50); /* * We got the reset bit. However, Ethernut 1.1 may * still fail because the NIC hasn't got it's hardware * reset and the data lines remain in tristate. So we * read noise instead of the register. To solve this * problem, we will verify the NIC's id. */ if ((NICINB(NIC_PG0_ISR) & NIC_ISR_RST) != 0 && /* */ NICINB(NIC_PG0_RBCR0) == 0x50 && /* */ NICINB(NIC_PG0_RBCR1) == 0x70) return 0; } } return -1; } /* * Fires up the network interface. NIC interrupts * should have been disabled when calling this * function. */ static int NicStart(const uint8_t * mac) { uint8_t i; if (NicReset()) { return -1; } /* * Mask all interrupts and clear any interrupt status flag to set the * INT pin back to low. */ NICOUTB(NIC_PG0_IMR, 0); NICOUTB(NIC_PG0_ISR, 0xff); /* * During reset the nic loaded its initial configuration from an * external eeprom. On the ethernut board we do not have any * configuration eeprom, but simply tied the eeprom data line to * high level. So we have to clear some bits in the configuration * register. Switch to register page 3. */ NICOUTB(NIC_CR, NIC_CR_STP | NIC_CR_RD2 | NIC_CR_PS0 | NIC_CR_PS1); /* * The nic configuration registers are write protected unless both * EEM bits are set to 1. */ NICOUTB(NIC_PG3_EECR, NIC_EECR_EEM0 | NIC_EECR_EEM1); /* * Network media had been set to 10Base2 by the virtual EEPROM and * will be set now to auto detect. This will initiate a link test. * We don't force 10BaseT, because this would disable the link test. */ NICOUTB(NIC_PG3_CONFIG2, NIC_CONFIG2_BSELB); /* * Disable sleep and power down. * * The virtual EEPROM (resistor tight to VCC) will set all bits of * CONFIG3 to 1. Unfortunately we are not able to modify the full * duplex bit. The only solution is to use a real EEPROM or emulate * one. */ NICOUTB(NIC_PG3_CONFIG3, NIC_CONFIG3_LEDS1 | NIC_CONFIG3_LEDS1); /* * Reenable write protection of the nic configuration registers * and wait for link test to complete. */ NICOUTB(NIC_PG3_EECR, 0); NutDelay(255); /* * Switch to register page 0 and set data configuration register * to byte-wide DMA transfers, normal operation (no loopback), * send command not executed and 8 byte fifo threshold. */ NICOUTB(NIC_CR, NIC_CR_STP | NIC_CR_RD2); NICOUTB(NIC_PG0_DCR, NIC_DCR_LS | NIC_DCR_FT1); /* * Clear remote dma byte count register. */ NICOUTB(NIC_PG0_RBCR0, 0); NICOUTB(NIC_PG0_RBCR1, 0); /* * Temporarily set receiver to monitor mode and transmitter to * internal loopback mode. Incoming packets will not be stored * in the nic ring buffer and no data will be send to the network. */ NICOUTB(NIC_PG0_RCR, NIC_RCR_MON); NICOUTB(NIC_PG0_TCR, NIC_TCR_LB0); /* * Configure the nic's ring buffer page layout. * NIC_PG0_BNRY: Last page read. * NIC_PG0_PSTART: First page of receiver buffer. * NIC_PG0_PSTOP: Last page of receiver buffer. */ NICOUTB(NIC_PG0_TPSR, NIC_FIRST_TX_PAGE); NICOUTB(NIC_PG0_BNRY, NIC_STOP_PAGE - 1); NICOUTB(NIC_PG0_PSTART, NIC_FIRST_RX_PAGE); NICOUTB(NIC_PG0_PSTOP, NIC_STOP_PAGE); /* * Once again clear interrupt status register. */ NICOUTB(NIC_PG0_ISR, 0xff); /* * Switch to register page 1 and copy our MAC address into the nic. * We are still in stop mode. */ NICOUTB(NIC_CR, NIC_CR_STP | NIC_CR_RD2 | NIC_CR_PS0); for (i = 0; i < 6; i++) NICOUTB(NIC_PG1_PAR0 + i, mac[i]); /* * Clear multicast filter bits to disable all packets. */ for (i = 0; i < 8; i++) NICOUTB(NIC_PG1_MAR0 + i, 0); /* * Set current page pointer to one page after the boundary pointer. */ NICOUTB(NIC_PG1_CURR, NIC_FIRST_RX_PAGE); /* * Switch back to register page 0, remaining in stop mode. */ NICOUTB(NIC_CR, NIC_CR_STP | NIC_CR_RD2); /* * Take receiver out of monitor mode and enable it for accepting * broadcasts. */ NICOUTB(NIC_PG0_RCR, NIC_RCR_AB); /* * Clear all interrupt status flags and enable interrupts. */ NICOUTB(NIC_PG0_ISR, 0xff); /* Note: transmitter if polled, thus no NIC_IMR_PTXE */ NICOUTB(NIC_PG0_IMR, NIC_IMR_PRXE | NIC_IMR_RXEE | NIC_IMR_TXEE | NIC_IMR_OVWE); /* * Fire up the nic by clearing the stop bit and setting the start bit. * To activate the local receive dma we must also take the nic out of * the local loopback mode. */ NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_RD2); NICOUTB(NIC_PG0_TCR, 0); NutDelay(255); return 0; } /*! * Complete remote DMA. */ static void NicCompleteDma(void) { uint8_t i; /* * Complete remote dma. */ NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_RD2); /* * Check that we have a DMA complete flag. */ for (i = 0; i <= 20; i++) if (NICINB(NIC_PG0_ISR) & NIC_ISR_RDC) break; /* * Reset remote dma complete flag. */ NICOUTB(NIC_PG0_ISR, NIC_ISR_RDC); } /* * Write data block to the NIC. */ static void NicWrite(uint8_t * buf, uint16_t len) { register uint16_t l = len - 1; register uint8_t ih = (uint16_t) l >> 8; register uint8_t il = (uint8_t) l; if (!len) return; do { do { NICOUTB(NIC_IOPORT, *buf++); } while (il-- != 0); } while (ih-- != 0); } /* * Read data block from the NIC. */ static void NicRead(uint8_t * buf, uint16_t len) { register uint16_t l = len - 1; register uint8_t ih = (uint16_t) l >> 8; register uint8_t il = (uint8_t) l; if (!len) return; do { do { *buf++ = NICINB(NIC_IOPORT); } while (il-- != 0); } while (ih-- != 0); } /*! * \brief Load a packet into the nic's transmit ring buffer. * * * \param base NIC hardware base address. * \param nb Network buffer structure containing the packet to be sent. * The structure must have been allocated by a previous * call NutNetBufAlloc(). * * \return 0 on success, -1 in case of any errors. Errors * will automatically release the network buffer * structure. */ static int NicPutPacket(NETBUF * nb) { uint16_t sz; uint16_t i; uint8_t padding = 0; /* * Calculate the number of bytes to be send. Do not * send packets larger than 1514 bytes. * * The previous version was wrong by specifying a maximum * of 1518, because it didn't take the CRC into account, * which is generated by the hardware and automatically * appended. Thanks to Bengt Florin, who discovered this. */ sz = nb->nb_dl.sz + nb->nb_nw.sz + nb->nb_tp.sz + nb->nb_ap.sz; if (sz > 1514) return -1; /* * The controller will not append pad bytes, * so we have to do this. */ if (sz < 60) { padding = (uint8_t) (60 - sz); sz = 60; } /* * Bengt Florin introduces polling mode for the transmitter. Be * aware, that this may introduce other problems. If a high * priority thread is waiting for the transmitter, it may hold * the CPU for more than 1.2 milliseconds in worst cases. */ while (NICINB(NIC_CR) & NIC_CR_TXP) NutThreadYield(); /* we don't want to be interrupted by NIC owerflow */ cbi(EIMSK, RTL_SIGNAL_IRQ); /* * Set remote dma byte count * and start address. */ NICOUTB(NIC_PG0_RBCR0, sz); NICOUTB(NIC_PG0_RBCR1, sz >> 8); NICOUTB(NIC_PG0_RSAR0, 0); NICOUTB(NIC_PG0_RSAR1, NIC_FIRST_TX_PAGE); /* * Peform the write. */ NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_RD1); /* * Transfer the Ethernet frame. */ NicWrite(nb->nb_dl.vp, nb->nb_dl.sz); NicWrite(nb->nb_nw.vp, nb->nb_nw.sz); NicWrite(nb->nb_tp.vp, nb->nb_tp.sz); NicWrite(nb->nb_ap.vp, nb->nb_ap.sz); /* * Add pad bytes. */ for (i = 0; i < padding; i++) NICOUTB(NIC_IOPORT, 0); /* * Complete remote dma. */ NicCompleteDma(); /* * Number of bytes to be transmitted. */ NICOUTB(NIC_PG0_TBCR0, (sz & 0xff)); NICOUTB(NIC_PG0_TBCR1, ((sz >> 8) & 0xff)); /* * First page of packet to be transmitted. */ NICOUTB(NIC_PG0_TPSR, NIC_FIRST_TX_PAGE); /* * Start transmission. */ NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_TXP | NIC_CR_RD2); sbi(EIMSK, RTL_SIGNAL_IRQ); return 0; } /*! * \brief Fetch the next packet out of the receive ring buffer. * * Nic interrupts must be disabled when calling this funtion. * * \return Pointer to an allocated ::NETBUF. If there is no * no data available, then the function returns a * null pointer. If the NIC's buffer seems to be * corrupted, a pointer to 0xFFFF is returned. */ static NETBUF *NicGetPacket(void) { NETBUF *nb = 0; struct nic_pkt_header hdr; uint16_t count; uint8_t *buf; uint8_t nextpg; uint8_t bnry; uint8_t curr; uint16_t i; uint8_t drop = 0; /* we don't want to be interrupted by NIC owerflow */ cbi(EIMSK, RTL_SIGNAL_IRQ); /* * Get the current page pointer. It points to the page where the NIC * will start saving the next incoming packet. */ NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_RD2 | NIC_CR_PS0); Delay16Cycles(); curr = NICINB(NIC_PG1_CURR); NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_RD2); /* * Get the pointer to the last page we read from. The following page * is the one where we start reading. If it's equal to the current * page pointer, then there's nothing to read. In this case we return * a null pointer. */ if ((bnry = NICINB(NIC_PG0_BNRY) + 1) >= NIC_STOP_PAGE) bnry = NIC_FIRST_RX_PAGE; if (bnry == curr) { sbi(EIMSK, RTL_SIGNAL_IRQ); return 0; } /* * Read the NIC specific packet header. */ NICOUTB(NIC_PG0_RBCR0, sizeof(struct nic_pkt_header)); NICOUTB(NIC_PG0_RBCR1, 0); NICOUTB(NIC_PG0_RSAR0, 0); NICOUTB(NIC_PG0_RSAR1, bnry); buf = (uint8_t *) & hdr; NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_RD0); Delay16Cycles(); for (i = 0; i < sizeof(struct nic_pkt_header); i++) *buf++ = NICINB(NIC_IOPORT); NicCompleteDma(); /* * Check packet length. Silently discard packets of illegal size. */ if (hdr.ph_size < 60 + sizeof(struct nic_pkt_header) || /* */ hdr.ph_size > 1514 + sizeof(struct nic_pkt_header)) { drop = 1; } /* * Calculate the page of the next packet. If it differs from the * pointer in the packet header, we return with errorcode. */ nextpg = bnry + (hdr.ph_size >> 8) + ((hdr.ph_size & 0xFF) != 0); if (nextpg >= NIC_STOP_PAGE) { nextpg -= NIC_STOP_PAGE; nextpg += NIC_FIRST_RX_PAGE; } if (nextpg != hdr.ph_nextpg) { uint8_t nextpg1 = nextpg + 1; if (nextpg1 >= NIC_STOP_PAGE) { nextpg1 -= NIC_STOP_PAGE; nextpg1 += NIC_FIRST_RX_PAGE; } if (nextpg1 != hdr.ph_nextpg) { sbi(EIMSK, RTL_SIGNAL_IRQ); return (NETBUF *) 0xFFFF; } nextpg = nextpg1; } /* * Check packet status. It should have set bit 0, but * even without this bit packets seem to be OK. */ if (!drop && ((hdr.ph_status & 0x0E) == 0)) { /* * Allocate a NETBUF. * Omit the fcs. */ count = hdr.ph_size - 4; if ((nb = NutNetBufAlloc(0, NBAF_DATALINK, count))) { /* * Set remote dma byte count and * start address. Don't read the * header again. */ NICOUTB(NIC_PG0_RBCR0, count); NICOUTB(NIC_PG0_RBCR1, count >> 8); NICOUTB(NIC_PG0_RSAR0, sizeof(struct nic_pkt_header)); NICOUTB(NIC_PG0_RSAR1, bnry); /* * Perform the read. */ NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_RD0); Delay16Cycles(); NicRead(nb->nb_dl.vp, count); NicCompleteDma(); } } /* * Set boundary register to the last page we read. * This also drops packets with errors */ if (--nextpg < NIC_FIRST_RX_PAGE) nextpg = NIC_STOP_PAGE - 1; NICOUTB(NIC_PG0_BNRY, nextpg); sbi(EIMSK, RTL_SIGNAL_IRQ); return nb; } /* * \brief Handle NIC overflows. * * When a receiver buffer overflow occurs, the NIC will defer any subsequent * action until properly restarted. * * This routine is called within interrupt context, which introduces a big * problem. It waits for the last transmission to finish, which may take * several milliseconds. Since Nut/OS 3.5, we do not support nested interrupts * on AVR systems anymore. So this routine may now increase interrupt * latency in an unacceptable way. The solution might be to handle overflows * in the receiver thread. * * In any case, this routines needs a major redesign. But it has been * tested in its current form to gracefully withstand ping floods. Thanks * to Bengt Florin for contributing his code, which provides much more * stability than its predecessor. */ static uint8_t NicOverflow(void) { uint8_t cr; uint8_t resend = 0; uint8_t curr; /* * Wait for any transmission in progress. Save the command register, * so we can later determine, if NIC transmitter has been interrupted. * or reception in progress. */ while (NICINB(NIC_CR) & NIC_CR_TXP); cr = NICINB(NIC_CR); /* * Get the current page pointer. It points to the page where the NIC * will start saving the next incoming packet. */ NICOUTB(NIC_CR, NIC_CR_STP | NIC_CR_RD2 | NIC_CR_PS0); curr = NICINB(NIC_PG1_CURR); NICOUTB(NIC_CR, NIC_CR_STP | NIC_CR_RD2); /* Clear remote byte count register. */ NICOUTB(NIC_PG0_RBCR0, 0); NICOUTB(NIC_PG0_RBCR1, 0); /* Check for any incomplete transmission. */ if ((cr & NIC_CR_TXP) && ((NICINB(NIC_PG0_ISR) & (NIC_ISR_PTX | NIC_ISR_TXE)) == 0)) { resend = 1; } /* Enter loopback mode and restart the NIC. */ NICOUTB(NIC_PG0_TCR, NIC_TCR_LB0); NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_RD2); /* * Discard all packets from the receiver buffer. Set boundary * register to the last page we read. */ if (--curr < NIC_FIRST_RX_PAGE) { curr = NIC_STOP_PAGE - 1; } NICOUTB(NIC_PG0_BNRY, curr); /* Switch from loopback to normal mode mode. */ NICOUTB(NIC_PG0_TCR, 0); /* Re-invoke any interrupted transmission. */ if (resend) { NICOUTB(NIC_CR, NIC_CR_STA | NIC_CR_TXP | NIC_CR_RD2); } /* Finally clear the overflow flag */ NICOUTB(NIC_PG0_ISR, NIC_ISR_OVW); return resend; } /* * \brief NIC interrupt entry. */ static void NicInterrupt(void *arg) { uint8_t isr; NICINFO *ni = (NICINFO *) ((NUTDEVICE *) arg)->dev_dcb; ni->ni_interrupts++; #ifdef RTL_IRQ_RISING_EDGE do { #endif isr = NICINB(NIC_PG0_ISR); NICOUTB(NIC_PG0_ISR, isr); /* * Recover from receive buffer overflow. This may take some * time, so we enable global interrupts but keep NIC * interrupts disabled. */ if (isr & NIC_ISR_OVW) { /* The AVR platform uses a dedicated interrupt stack, which * forbids interrupt nesting. */ #if !defined(__AVR__) cbi(EIMSK, RTL_SIGNAL_IRQ); sei(); #endif NicOverflow(); #if !defined(__AVR__) cli(); sbi(EIMSK, RTL_SIGNAL_IRQ); #endif ni->ni_rx_overruns++; } else { /* * If this is a transmit interrupt, then a packet has been sent. * So we can clear the transmitter busy flag and wake up the * transmitter thread. */ if (isr & NIC_ISR_TXE) ni->ni_tx_errors++; /* * If this is a receive interrupt, then wake up the receiver * thread. */ if (isr & NIC_ISR_PRX) NutEventPostFromIrq(&ni->ni_rx_rdy); if (isr & NIC_ISR_RXE) { ni->ni_rx_frame_errors += NICINB(NIC_PG0_CNTR0); ni->ni_rx_crc_errors += NICINB(NIC_PG0_CNTR1); ni->ni_rx_missed_errors += NICINB(NIC_PG0_CNTR2); } } #ifdef RTL_IRQ_RISING_EDGE /* Check that all unmasked interrupts are cleared before we * leave the ISR to assert the INT line goes back to low * and a new interrupt edge will be generated for following * interrupts. */ } while (bit_is_set(PINE, RTL_SIGNAL_IRQ)); #endif } /*! \fn NicRx(void *arg) * \brief NIC receiver thread. * * * It runs with high priority. */ THREAD(NicRx, arg) { NUTDEVICE *dev; IFNET *ifn; NICINFO *ni; NETBUF *nb; dev = arg; ifn = (IFNET *) dev->dev_icb; ni = (NICINFO *) dev->dev_dcb; NutThreadSetPriority(9); /* * This is a temporary hack. Due to a change in initialization, * we may not have got a MAC address yet. Wait until one has been * set. */ if ((ifn->if_mac[0] & ifn->if_mac[1] & ifn->if_mac[2]) == 0xFF) { while ((ifn->if_mac[0] & ifn->if_mac[1] & ifn->if_mac[2]) == 0xFF) NutSleep(125); cbi(EIMSK, RTL_SIGNAL_IRQ); NicStart(ifn->if_mac); sbi(EIMSK, RTL_SIGNAL_IRQ); } while (1) { NutEventWait(&ni->ni_rx_rdy, 0); /* * Fetch all packets from the NIC's internal * buffer and pass them to the registered handler. */ do { nb = NicGetPacket(); /* The sanity check may fail because the controller is too busy. restart the NIC. */ if ((uint16_t) nb == 0xFFFF) { NicStart(ifn->if_mac); ni->ni_rx_size_errors++; } else if (nb) { ni->ni_rx_packets++; (*ifn->if_recv) (dev, nb); } } while (nb); } } /*! * \brief Send Ethernet packet. * * \param dev Identifies the device to use. * \param nb Network buffer structure containing the packet to be sent. * The structure must have been allocated by a previous * call NutNetBufAlloc(). * * \return 0 on success, -1 in case of any errors. */ int NicOutput(NUTDEVICE * dev, NETBUF * nb) { int rc = -1; NICINFO *ni = (NICINFO *) dev->dev_dcb; if (NicPutPacket(nb) == 0) { ni->ni_tx_packets++; rc = 0; } return rc; } /*! * \brief Initialize Ethernet hardware. * * Resets RTL8019AS Ethernet controller, initializes all required * hardware registers and starts a background thread for incoming * Ethernet traffic. * * Applications should do not directly call this function. It is * automatically executed during during device registration by * NutRegisterDevice(). Note, that base address and interrupt number * passed to NutRegisterDevice() are silently ignored by this driver * for performance reasons. These values can be changed only by * using the Nut/OS Configurator to rebuild the system. * * If the network configuration hasn't been set by the application * before registering the specified device, this function will * call NutNetLoadConfig() to get the MAC address. * * \param dev Identifies the device to initialize. */ int NicInit(NUTDEVICE * dev) { IFNET *ifn; NICINFO *ni; /* * We need to know our MAC address. If no configuration is * available, load it now. */ if (confnet.cd_size == 0) NutNetLoadConfig(dev->dev_name); ifn = dev->dev_icb; memcpy(ifn->if_mac, confnet.cdn_mac, 6); ni = (NICINFO *) dev->dev_dcb; memset(ni, 0, sizeof(NICINFO)); /* * Start the receiver thread. */ NutThreadCreate("rxi5", NicRx, dev, (NUT_THREAD_NICRXSTACK * NUT_THREAD_STACK_MULT) + NUT_THREAD_STACK_ADD); NutSleep(WAIT500); /* * Register interrupt handler and enable interrupts. */ if (NutRegisterIrqHandler(&RTL_SIGNAL, NicInterrupt, dev)) return -1; cbi(EIMSK, RTL_SIGNAL_IRQ); #ifdef RTL_IRQ_RISING_EDGE /* Support of rising edge interrupts for HW w/o inverter gate */ RTL_RISING_EDGE_MODE(); #endif if (ifn->if_mac[0] | ifn->if_mac[1] | ifn->if_mac[2]) if (NicStart(ifn->if_mac)) return -1; sbi(EIMSK, RTL_SIGNAL_IRQ); return 0; } /*@}*/