if_pcnet.c

来自「开放源码实时操作系统源码.」· C语言 代码 · 共 1,329 行 · 第 1/3 页

C
1,329
字号

    // Restore controller state
    if (was_active) {
        // Unsuspend the device
        reg = get_reg(sc, PCNET_CSR_ECI);
        reg &= ~PCNET_CSR_ECI_SPND;
        put_reg(sc, PCNET_CSR_ECI, reg);
    }

    return res;
}

//
// This routine is called to see if it is possible to send another packet.
// It will return non-zero if a transmit is possible, zero otherwise.
//
static int
pcnet_can_send(struct eth_drv_sc *sc)
{
    struct pcnet_priv_data *cpd =
        (struct pcnet_priv_data *)sc->driver_private;
    cyg_uint16 stat;

    DEBUG_FUNCTION();

    stat = get_reg(sc, PCNET_ANR_PHYSTAT);
    if ((stat & PCNET_ANR_PHYSTAT_LINK) == 0) {
        return 0;                       // Link not connected
    }

    return (0 == cpd->txbusy);
}

//
// This routine is called to send data to the hardware.
static void 
pcnet_send(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len, 
            int total_len, unsigned long key)
{
    struct pcnet_priv_data *cpd = 
        (struct pcnet_priv_data *)sc->driver_private;
    int i, len, plen, ring_entry;

    cyg_uint8* sdata = NULL;
    cyg_uint8 *d, *buf, *txd;
    cyg_uint16 ints;
    cyg_uint32 b;

    DEBUG_FUNCTION();

    INCR_STAT( tx_count );

    cpd->txbusy = 1;
    cpd->txkey = key;

    // Find packet length
    plen = 0;
    for (i = 0;  i < sg_len;  i++)
        plen += sg_list[i].len;

    CYG_ASSERT( plen == total_len, "sg data length mismatch" );
    
    // Get next TX descriptor
    ring_entry = cpd->tx_ring_free;
    do {
        if (cpd->tx_ring_owned == cpd->tx_ring_cnt) {
            // Is this a dead end? Probably is.
#if DEBUG & 1
            db_printf("%s: Allocation failed! Retrying...\n", __FUNCTION__ );
#endif
            continue;
        }

        cpd->tx_ring_free++;
        cpd->tx_ring_owned++;
        if (cpd->tx_ring_free == cpd->tx_ring_cnt)
            cpd->tx_ring_free = 0;
    } while (0);

    txd = cpd->tx_ring + ring_entry*PCNET_TD_SIZE;
    buf = cpd->tx_buffers + ring_entry*_BUF_SIZE;
    CYG_ASSERT(0 == (_SU32(txd, PCNET_TD_PTR) & PCNET_TD_PTR_OWN),
               "TX descriptor not free");

#if DEBUG & 4
    db_printf("#####Tx descriptor 0x%08x buffer 0x%08x\n",
                txd, buf);
#endif

    // Put data into buffer
    d = buf;
    for (i = 0;  i < sg_len;  i++) {
        sdata = (cyg_uint8 *)sg_list[i].buf;
        len = sg_list[i].len;

        CYG_ASSERT( sdata, "No sg data pointer here" );
        while(len--)
            *d++ = *sdata++;
    }
    CYG_ASSERT( sdata, "No sg data pointer outside" );

#if DEBUG & 1
    db_printf("CSCR %04x\n", get_reg(sc, PCNET_CSR_CSCR));
#endif
    _SU16(txd, PCNET_TD_LEN) = (-plen);
    _SU16(txd, PCNET_TD_MISC) = 0;
    HAL_PCI_CPU_TO_BUS(buf, b);
    _SU32(txd, PCNET_TD_PTR) = ((b & PCNET_TD_PTR_MASK)
                                | PCNET_TD_PTR_OWN | PCNET_TD_PTR_STP | PCNET_TD_PTR_ENP);

#if DEBUG & 1
    db_printf("Last TX: LEN %04x MISC %04x PTR %08x\n", 
                _SU16(txd, PCNET_TD_LEN),
                _SU16(txd, PCNET_TD_MISC),
                _SU32(txd, PCNET_TD_PTR));
#endif

    // This delay seems to be necessary on some platforms 
    // (Malta 5kc for example).
    // Why it is needed is not clear, but removing it or
    // reducing it cause transmission failures in RedBoot (at least).
    CYGACC_CALL_IF_DELAY_US(100);
    

    // Set transmit demand
    ints = get_reg(sc, PCNET_CSR_CSCR);
    ints &= PCNET_CSR_CSCR_EV_MASK;
    ints |= PCNET_CSR_CSCR_TDMD;
    put_reg(sc, PCNET_CSR_CSCR, ints);

#if DEBUG & 1
    ints = get_reg(sc, PCNET_CSR_CSCR);
    db_printf("%s:END: ints at TX: 0x%04x\n", __FUNCTION__, ints);
#endif

    // This is another mystery delay like the one above. This one is
    // even stranger, since waiting here at the _end_ of the function
    // should have no effect.
    CYGACC_CALL_IF_DELAY_US(200);
}

static void
pcnet_TxEvent(struct eth_drv_sc *sc, int stat)
{
     struct pcnet_priv_data *cpd =
        (struct pcnet_priv_data *)sc->driver_private;
    int success = 1;
    cyg_uint8 *txd;
    cyg_uint16 ints;
    cyg_uint32 pkt_stat;

    DEBUG_FUNCTION();

    if (0 == cpd->tx_ring_owned) {
#if DEBUG & 1
        db_printf("%s: got TX completion when no outstanding packets\n", __FUNCTION__);
#endif
        return;
    }

    INCR_STAT( tx_complete );

    txd = cpd->tx_ring + cpd->tx_ring_alloc*PCNET_TD_SIZE;
    pkt_stat = _SU32(txd, PCNET_TD_PTR);
    if (pkt_stat & PCNET_TD_PTR_OWN) {
#if DEBUG & 1
        db_printf("%s: got TX completion when buffer is still owned\n", __FUNCTION__);
#endif
        // first dirty ring entry not freed - wtf?
    }

    if (pkt_stat & PCNET_TD_PTR_ERR) {
        // We had an error. Tell the stack.
        success = 0;
#if DEBUG & 1
        db_printf("%s: TX failure, retrying...\n", __FUNCTION__);
#endif
    }

    cpd->tx_ring_alloc++;
    if (cpd->tx_ring_alloc == cpd->tx_ring_cnt)
        cpd->tx_ring_alloc = 0;
    cpd->tx_ring_owned--;

#if FIXME
#ifdef KEEP_STATISTICS
    {
        cyg_uint16 reg;

        reg = get_reg( sc, PCNET_CSR_CSCR );
        
        // Covering each bit in turn...
        if ( reg & PCNET_STATUS_TX_UNRN   ) INCR_STAT( tx_underrun );
        //if ( reg & PCNET_STATUS_LINK_OK ) INCR_STAT(  );
        //if ( reg & PCNET_STATUS_CTR_ROL ) INCR_STAT(  );
        //if ( reg & PCNET_STATUS_EXC_DEF ) INCR_STAT(  );
        if ( reg & PCNET_STATUS_LOST_CARR ) INCR_STAT( tx_carrier_loss );
        if ( reg & PCNET_STATUS_LATCOL    ) INCR_STAT( tx_late_collisions );
        //if ( reg & PCNET_STATUS_WAKEUP  ) INCR_STAT(  );
        if ( reg & PCNET_STATUS_TX_DEFR   ) INCR_STAT( tx_deferred );
        //if ( reg & PCNET_STATUS_LTX_BRD ) INCR_STAT(  );
        if ( reg & PCNET_STATUS_SQET      ) INCR_STAT( tx_sqetesterrors );
        if ( reg & PCNET_STATUS_16COL     ) INCR_STAT( tx_max_collisions );
        //if ( reg & PCNET_STATUS_LTX_MULT) INCR_STAT(  );
        if ( reg & PCNET_STATUS_MUL_COL   ) INCR_STAT( tx_mult_collisions );
        if ( reg & PCNET_STATUS_SNGL_COL  ) INCR_STAT( tx_single_collisions );
        if ( reg & PCNET_STATUS_TX_SUC    ) INCR_STAT( tx_good );

        cpd->stats.tx_total_collisions = 
            cpd->stats.tx_late_collisions + 
            cpd->stats.tx_max_collisions + 
            cpd->stats.tx_mult_collisions + 
            cpd->stats.tx_single_collisions;

        // We do not need to look in the Counter Register (PCNET_COUNTER)
        // because it just mimics the info we already have above.
    }
#endif // KEEP_STATISTICS
#endif // FIXME

    // Ack the TX int which clears the packet from the TX completion
    // queue.
    ints = get_reg(sc, PCNET_CSR_CSCR);
    ints |= PCNET_CSR_CSCR_TINT;
    put_reg(sc, PCNET_CSR_CSCR, ints);

#if DEBUG & 4
    db_printf("#####Tx packet freed 0x%08x\n", txd );
#endif

    if ( cpd->txbusy ) {
        cpd->txbusy = 0;
        (sc->funs->eth_drv->tx_done)(sc, cpd->txkey, success);
    }
}


//
// This function is called when a packet has been received.  Its job is
// to prepare to unload the packet from the hardware.  Once the length of
// the packet is known, the upper layer of the driver can be told.  When
// the upper layer is ready to unload the packet, the internal function
// 'pcnet_recv' will be called to actually fetch it from the hardware.
//
static void
pcnet_RxEvent(struct eth_drv_sc *sc)
{
    struct pcnet_priv_data *cpd = 
        (struct pcnet_priv_data *)sc->driver_private;
    cyg_uint8 *rxd;
    cyg_uint32 rstat;
    cyg_uint16 ints, len;

    DEBUG_FUNCTION();

    ints = get_reg(sc, PCNET_CSR_CSCR);
#if DEBUG & 1
    db_printf("RxEvent - CSR: 0x%04x\n", ints);
#endif

    while (1) {
        // Get state of next (supposedly) full ring entry
        cpd->rxpacket = cpd->rx_ring_next;
        rxd = cpd->rx_ring + cpd->rxpacket*PCNET_RD_SIZE;
        rstat = _SU32(rxd, PCNET_RD_PTR);

        // Keep going until we hit an entry that is owned by the
        // controller.
        if (rstat & PCNET_RD_PTR_OWN) {
#if DEBUG & 1
            int i;
            for (i = 0; i < cpd->rx_ring_cnt; i++) {
                rxd = cpd->rx_ring + i*PCNET_RD_SIZE;
                rstat = _SU32(rxd, PCNET_RD_PTR);

                if (!(rstat & PCNET_RD_PTR_OWN)) {
                    int i;
                    cyg_uint32 rstat;
                    cyg_uint16 mlen, blen;
                    cyg_uint8* rxd;

                    db_printf("%s: Inconsistent RX state\n", __FUNCTION__);
                    for (i = 0; i < cpd->rx_ring_cnt; i++) {
                        rxd = cpd->rx_ring + i*PCNET_RD_SIZE;
                
                        rstat = _SU32(rxd, PCNET_RD_PTR);
                        blen = _SU16(rxd, PCNET_RD_BLEN);
                        mlen = _SU16(rxd, PCNET_RD_MLEN);
                        db_printf(" %02d: 0x%08x:0x%04x:0x%04x\n", i, rstat, blen, mlen);
                    }
                }
            }
#endif
            break;
        }

#if DEBUG & 4
        db_printf("#####Rx packet at index %d\n", cpd->rxpacket);
#endif

        // Increment counts
        INCR_STAT( rx_count );
        cpd->rx_ring_next++;
        if (cpd->rx_ring_next == cpd->rx_ring_cnt) cpd->rx_ring_next = 0;

        len = _SU16(rxd, PCNET_RD_MLEN);

#ifdef KEEP_STATISTICS
        //if ( rstat & PCNET_RD_PTR_FRAM ) INCR_STAT( rx_frame_errors );
        //if ( rstat & PCNET_RD_PTR_OFLO ) INCR_STAT(  );
        if ( rstat & PCNET_RD_PTR_CRC ) INCR_STAT( rx_crc_errors );
        //if ( rstat & PCNET_RD_PTR_BUFF ) INCR_STAT(  );
#endif // KEEP_STATISTICS

        if (0 == (rstat & PCNET_RD_PTR_ERR)) {
            // It's OK
            INCR_STAT( rx_good );

#if DEBUG & 1
            db_printf("RxEvent good rx - stat: 0x%08x, len: 0x%04x\n", rstat, len);
#endif
            // Check for bogusly short packets; can happen in promisc
            // mode: Asserted against and checked by upper layer
            // driver.
#ifdef CYGPKG_NET
            if ( len > sizeof( struct ether_header ) )
                // then it is acceptable; offer the data to the network stack
#endif
                (sc->funs->eth_drv->recv)(sc, len);
        } else {
            // Not OK for one reason or another...
#if DEBUG & 1
            db_printf("RxEvent - No RX bit: stat: 0x%08x, len: 0x%04x\n",
                        rstat, len);
#endif
        }

        // Free packet (clear all status flags, and set OWN)
        _SU32(rxd, PCNET_RD_PTR) &= PCNET_RD_PTR_MASK;
        _SU32(rxd, PCNET_RD_PTR) |= PCNET_RD_PTR_OWN;
    }

    // Ack RX interrupt set
    ints = get_reg(sc, PCNET_CSR_CSCR);
    ints &= PCNET_CSR_CSCR_EV_MASK;
    ints |= PCNET_CSR_CSCR_RINT;
    put_reg(sc, PCNET_CSR_CSCR, ints);
}

//
// This function is called as a result of the "eth_drv_recv()" call above.
// Its job is to actually fetch data for a packet from the hardware once
// memory buffers have been allocated for the packet.  Note that the buffers
// may come in pieces, using a scatter-gather list.  This allows for more
// efficient processing in the upper layers of the stack.
//
static void
pcnet_recv(struct eth_drv_sc *sc, struct eth_drv_sg *sg_list, int sg_len)
{
    struct pcnet_priv_data *cpd = 
        (struct pcnet_priv_data *)sc->driver_private;
    int i, mlen=0, plen;
    cyg_uint8 *data, *rxd, *buf;

    DEBUG_FUNCTION();

    rxd = cpd->rx_ring + cpd->rxpacket*PCNET_RD_SIZE;
    buf = cpd->rx_buffers + cpd->rxpacket*_BUF_SIZE;

    INCR_STAT( rx_deliver );

    plen = _SU16(rxd, PCNET_RD_MLEN);

    for (i = 0;  i < sg_len;  i++) {
        data = (cyg_uint8*)sg_list[i].buf;
        mlen = sg_list[i].len;

#if DEBUG & 1
        db_printf("%s : mlen %x, plen %x\n", __FUNCTION__, mlen, plen);
#endif
        if (data) {
            while (mlen > 0) {
                *data++ = *buf++;
                mlen--;
                plen--;
            }
        }
    }
}

static void
pcnet_poll(struct eth_drv_sc *sc)
{
    cyg_uint16 event;

//  DEBUG_FUNCTION();

    while (1) {
        // Get the (unmasked) requests
        event = get_reg(sc, PCNET_CSR_CSCR);
        if (!((PCNET_CSR_CSCR_ERR|PCNET_CSR_CSCR_INTR) & event))
            break;

        if (event & PCNET_CSR_CSCR_RINT) {
            pcnet_RxEvent(sc);
        }
        else if (event & PCNET_CSR_CSCR_TINT) {
            pcnet_TxEvent(sc, event);
        } 
        else if (event & PCNET_CSR_CSCR_MISS) {
#if DEBUG & 1
            int i;
            cyg_uint32 rstat;
            cyg_uint16 mlen, blen;
            cyg_uint8* rxd;
            struct pcnet_priv_data *cpd = 
                (struct pcnet_priv_data *)sc->driver_private;

            db_printf("%s: Ran out of RX buffers (%04x)\n", __FUNCTION__, event);
            for (i = 0; i < cpd->rx_ring_cnt; i++) {
                rxd = cpd->rx_ring + i*PCNET_TD_SIZE;
                
                rstat = _SU32(rxd, PCNET_RD_PTR);
                blen = _SU16(rxd, PCNET_RD_BLEN);
                mlen = _SU16(rxd, PCNET_RD_MLEN);
                db_printf(" %02d: 0x%08x:0x%04x:0x%04x\n", i, rstat, blen, mlen);
            }
#endif
            event &= PCNET_CSR_CSCR_EV_MASK;
            event |= PCNET_CSR_CSCR_MISS;
            put_reg(sc, PCNET_CSR_CSCR, event);
        }
        else {
#if DEBUG & 1
            db_printf("%s: Unknown interrupt: 0x%04x\n", __FUNCTION__, event);
#endif
            put_reg(sc, PCNET_CSR_CSCR, event);
        }
    }
}

// EOF if_pcnet.c

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?