/* * EMAC driver for NVCom-02TEM-3U evaluation kit * * Copyright (c) 2020 Elvees (support@elvees.com) * Author: Dmitry Evtushenko * */ //#define MULTICORE_DEBUG 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef CONFIG_MC30SF6 #include #else #include #endif #include #include #include #include #include "emac.h" #include "func.h" #include "irq.h" #include "ctl.h" #include "../common/debug.h" #include "../common/debug_tm.h" #define MC_MSG_DEFAULT (NETIF_MSG_DRV | NETIF_MSG_PROBE | NETIF_MSG_LINK) //#define USE_TEST_TX_MULTI_GEN //+EDN #define ETH_DMA_MAX_BUF_SIZE 1520 //- максимальный размер буфера DMA для ethernet (выровнен на 8 байт) #define MAX_HW_TX_FIFO_SIZE 4096 #define MAX_HW_RX_FIFO_SIZE 4096 #define DMA_MEM_FLAG GFP_DMA // GFP_KERNEL mc_emac_t *pemac = NULL; #ifdef USE_MULTI_MBUF multi_mbuf_t mmbuf_tx; multi_mbuf_t mmbuf_rx; #endif //USE_MULTI_MBUF extern u_int32_t mips_hpt_frequency; // DEBUG: bool en_print = false; // time profiling: #ifdef USE_PROFILER u64 tmprof_atm[TMPROF_ATM_SIZE]; void tmprof_clear(void) { memset( tmprof_atm, 0, sizeof(tmprof_atm)); } #endif //USE_PROFILER #ifdef USE_MULTI_MBUF ////////////////////////////////// multi_mbuf_t: //////////////////////////// bool mmbuf_init( multi_mbuf_t *pmb, u32 szmb) { memset( pmb, 0, sizeof(*pmb)); pmb->pbuf = dmam_alloc_coherent( &pemac->pplatdev->dev, szmb, &pmb->buf_phys, GFP_DMA); if( !pmb->pbuf) return false; pmb->sz_buf = szmb; pmb->pend = pmb->pbuf + szmb; mmbuf_reset( pmb); return true; } void mmbuf_destroy( multi_mbuf_t *pmb) { dmam_free_coherent( &pemac->pplatdev->dev, pmb->sz_buf, pmb->pbuf, pmb->buf_phys); memset( pmb, 0, sizeof(*pmb)); } bool mmbuf_hold_mem( multi_mbuf_t *pmb, u32 szmem, char **ppmem) { u32 szr, szl; char *phead = pmb->phead; //- может изменится на строне освобождающей память if( szmem & 0x7) szmem = (((szmem >> 3) + 1) << 3); if( (pmb->ptail == phead) && (pmb->ptail != pmb->pbuf)) { mmbuf_reset( pmb); phead = pmb->phead; } if( pmb->ptail >= phead) { szl = (phead - pmb->pbuf); szr = (pmb->pend - pmb->ptail); } else { szl = (phead - pmb->ptail); szr = 0; } if ( szmem <= szr) { *ppmem = pmb->ptail; pmb->ptail += szmem; } else if( szmem <= szl) { if( pmb->ptail >= phead) { *ppmem = pmb->pbuf; pmb->ptail = pmb->pbuf + szmem; } else { *ppmem = pmb->ptail; pmb->ptail += szmem; } } else return false; return true; } bool mmbuf_free_mem( multi_mbuf_t *pmb, char *ppmem, u32 szmem, char *pdatanext) { if( ppmem != pmb->phead) { PRINT( "mmbuf_free_mem: violation of the sequence of releasing items\n"); return false; } pmb->phead = pdatanext ? pdatanext : (pmb->phead + szmem); if( ((u32 )pmb->phead) & 0x7) pmb->phead = (char *)(((((u32 )pmb->phead) >> 3) + 1) << 3); return true; } #endif // !USE_MULTI_MBUF ////////////////////////////////// emac_dma_data_tx_t: //////////////////////////// static void *dma_data_tx_create( void *pv) { emac_dma_data_tx_t *pdd; #ifndef USE_MULTI_MBUF mc_emac_t *pe = (mc_emac_t *)pv; #endif // !USE_MULTI_MBUF if ((pdd = kcalloc( 1, sizeof(*pdd), GFP_KERNEL)) == NULL) { pr_info( "dma_data_tx_create: Could not allocate memory for emac_dma_data_tx_t\n"); return NULL; } #ifndef USE_MULTI_MBUF #if 0 if ((pdd->pbuf = kcalloc( ETH_DMA_MAX_BUF_SIZE, 1, GFP_DMA)) == NULL) { pr_info("dma_data_tx_create: Could not allocate memory for data buffer\n"); kfree( pdd); return NULL; } pdd->buf_phys = mips_virt_to_phys( (unsigned )&pdd->pbuf); #else pdd->pbuf = dmam_alloc_coherent( &pe->pplatdev->dev, ETH_DMA_MAX_BUF_SIZE, &pdd->buf_phys, DMA_MEM_FLAG); if( !pdd->pbuf) { pr_info("dma_data_tx_create: Could not allocate memory for data buffer\n"); kfree( pdd); return NULL; } #endif pdd->pdata = pdd->pbuf; //pr_info( " tx_buf: V=%Xh, Ph=%Xh\n", (u32 )pdd->pbuf, pdd->buf_phys); #endif // !USE_MULTI_MBUF return pdd; } static void dma_data_tx_delete( void *pi) { emac_dma_data_tx_t *pdd = (emac_dma_data_tx_t *)pi; #ifndef USE_MULTI_MBUF if( pdd->pbuf) { #if 0 kfree( pdd->pbuf); #else dmam_free_coherent( &pemac->pplatdev->dev, ETH_DMA_MAX_BUF_SIZE, pdd->pbuf, pdd->buf_phys); #endif } #endif // !USE_MULTI_MBUF kfree( pdd); } ////////////////////////////// emac_dma_data_rx_t: //////////////////////////// static void *dma_data_rx_create( void *pv) { emac_dma_data_rx_t *pdd; #ifndef USE_MULTI_MBUF mc_emac_t *pe = (mc_emac_t *)pv; #endif // !USE_MULTI_MBUF if ((pdd = kcalloc( 1, sizeof(*pdd), GFP_KERNEL)) == NULL) { pr_info( "dma_data_rx_create: Could not allocate memory for emac_dma_data_rx_s\n"); return NULL; } #ifndef USE_MULTI_MBUF #if 0 if ((pdd->pbuf = kcalloc( ETH_DMA_MAX_BUF_SIZE, 1, GFP_DMA)) == NULL) { pr_info("dma_data_rx_create: Could not allocate memory for data buffer\n"); kfree( pdd); return NULL; } pdd->buf_phys = mips_virt_to_phys( (unsigned )&pdd->pbuf); #else pdd->pbuf = dmam_alloc_coherent( &pe->pplatdev->dev, ETH_DMA_MAX_BUF_SIZE, &pdd->buf_phys, DMA_MEM_FLAG); if( !pdd->pbuf) { pr_info("dma_data_rx_create: Could not allocate memory for data buffer\n"); kfree( pdd); return NULL; } #endif #endif // !USE_MULTI_MBUF return pdd; } static void dma_data_rx_delete( void *pi) { emac_dma_data_tx_t *pdd = (emac_dma_data_tx_t *)pi; #ifndef USE_MULTI_MBUF if( pdd->pbuf) { #if 0 kfree( pdd->pbuf); #else dmam_free_coherent( &pemac->pplatdev->dev, ETH_DMA_MAX_BUF_SIZE, pdd->pbuf, pdd->buf_phys); #endif } #endif // !USE_MULTI_MBUF kfree( pdd); } ////////////////////////////////// EMAC driver: /////////////////////////////// // Запись регистра PHY: bool mc_phy_write( mc_emac_t *pe, u32 addr, u32 data) { u32 i, status; // Команда PHY _Wr( pe, RG_MD_CONTROL, F_OP_WRITE | F_DATA(data) | F_PHY(pe->phy) | F_REG(addr)); // Ожидаем завершения операции for( i=0; i<100000; ++i) { status = _Rd( pe, RG_MD_STATUS); if( !(status & F_MD_BUSY)) break; } /*PDEBUGG( "mc_phy_write(%d, 0x%02x, 0x%04x) ", pe->phy, addr, data); if( status & F_MD_BUSY) PDEBUG("TIMEOUT\n");*/ return (status & F_MD_BUSY || !(status & F_END_WRITE)) ? false : true; } // Чтение регистра PHY: bool mc_phy_read( mc_emac_t *pe, u32 addr, u32 *pval) { u32 status, i; *pval = 0; // Команда PHY _Wr( pe, RG_MD_CONTROL, F_OP_READ | F_PHY(pe->phy) | F_REG(addr)); // Ожидаем завершения операции for( i=0; (i < 100000); ++i) { status = _Rd( pe, RG_MD_STATUS); if( !(status & F_MD_BUSY)) break; } if( status & F_MD_BUSY || !(status & F_END_READ)) return false; /*PDEBUGG( "mc_phy_read(%d, 0x%02x) ", pe->phy, addr); if( status & F_MD_BUSY) PDEBUG( "TIMEOUT\n"); else PDEBUGG( "returned 0x%04x\n", status & F_DATA_MASK);*/ *pval = (status & F_DATA_MASK); return true; } // Установка MAC-адреса: int emac_set_mac_addr( struct net_device *pnd, void *addr) { mc_emac_t *pe = (mc_emac_t *)netdev_priv(pnd); //u8 psa[6] = {0xAA,0xAB,0xAC,0xAD,0xAE,0xAF}; #ifdef DEBUG_MULTICORE u8 *p = (u8*) addr; int i; for( i=0; (i < 8); i++) printk (KERN_DEBUG "addr[%d] = %02X\n", i, p[i]); #endif memcpy( pnd->dev_addr, addr, pnd->addr_len); //memcpy( pnd->dev_addr, psa, pnd->addr_len); _Wr( pe, RG_UCADDR_L, pnd->dev_addr[0] | (pnd->dev_addr[1] << 8) | (pnd->dev_addr[2] << 16)| (pnd->dev_addr[3] << 24)); _Wr( pe, RG_UCADDR_H, pnd->dev_addr[4] | (pnd->dev_addr[5] << 8)); PDEBUGG ("UCADDR=%02x:%08x\n", _Rd( pe, RG_UCADDR_H), _Rd( pe, RG_UCADDR_L)); return RC_EMAC_SUCCESS; } // Смена MTU: static int emac_change_mtu( struct net_device *pnd, int new_mtu) { mc_emac_t *pe = (mc_emac_t *)netdev_priv(pnd); _Wr( pe, RG_RX_FR_MAXSIZE, new_mtu + ETH_HEADER_SIZE + ETH_CRC_SIZE); pnd->mtu = new_mtu; return RC_EMAC_SUCCESS; } static void mc_phy_reset( mc_emac_t *pe) { int count; u32 ctl; for( count=100; (count > 0); --count) { if( mc_phy_write( pe, PHY_CTL, PHY_CTL_RST)) break; } if( count == 0) { printk( "mc_phy_reset: PHY_CTL_RST writing ERROR\n"); return; } for( count=10000; (count > 0); --count) { if( mc_phy_read(pe, PHY_CTL, &ctl) && !(ctl & PHY_CTL_RST)) break; } if( count == 0) printk( "mc_phy_reset: PHY reset failed\n"); } // TX: bool StartDma_MemToTxFifo( mc_emac_t *pe) { emac_dma_data_tx_t *pdat = pe->pdtx_mem; u32 sz_filled_tx_fifo; u32 csr; if( pe->stop_tx) return false; if( pdat == NULL) { //PRINT( "StartDma_MemToTxFifo: mem-item is absent\n"); return true; } if( pe->pdtx_dma_to_fifo) { PRINT( "StartDma_MemToTxFifo: previous dma_to_fifo item does not finished\n"); return false; } //PRINT( "MemToTxFifo: TxDMA_DMA_RUN=%Xh, len=%u\n", _Rd_TxDMA( pe, RG_DMA_RUN), pdat->len_data); if( !(_Rd_TxDMA( pe, RG_DMA_RUN) & F_DMA_RUN)) { sz_filled_tx_fifo = F_TXW(_Rd(pe, RG_STATUS_TX)) << 3; if( pdat->len_data <= (MAX_HW_TX_FIFO_SIZE - sz_filled_tx_fifo) ) { // В TX_FIFO есть место для данных из буфера DMA, инициируем перенос данных: csr = F_DMA_WN(0xF) | F_DMA_IPD | #if defined(CONFIG_NVCOM01M)||defined(CONFIG_MC30SF6) F_DMA_WCX( pdat->len_data - 1); #else F_DMA_WCX( ((pdat->len_data + 7) >> 3) - 1); #endif pe->pdtx_mem = (pe->pdtx_mem->p_next->state==EDDS_MEM) ? pe->pdtx_mem->p_next : NULL; pdat->state = EDDS_DMA_TO_FIFO; pe->pdtx_dma_to_fifo = pdat; _Wr_TxDMA( pe, RG_DMA_IR, mips_virt_to_phys( (unsigned)pdat->pdata)); _Wr_TxDMA( pe, RG_DMA_CSR, (csr | F_DMA_RUN)); //_Wr( pe, RG_CONTROL, _Rd( pe, RG_CONTROL) | F_EN_TX_DMA); // ожидаем прерывания emac_dma_tx_ih() ... return true; } //else // PRINT( "emac_tx_job: Insufficient TX_FIFO size %u for data %u\n", (MAX_HW_TX_FIFO_SIZE - sz_filled_tx_fifo), pdat->len_data); } //-end: if( !_Rd_TxDMA...) return true; } bool Start_TxFrmToPhy( mc_emac_t *pe) { emac_dma_data_tx_t *pdat = pe->pdtx_fifo; u32 sz_filled_tx_fifo; u32 r_status; if( pe->stop_tx) return false; if( (pdat==NULL) || (pdat->state != EDDS_FIFO)) { //PRINT( "Start_TxFrmToPhy: txfifo-item is absent\n"); return true; } if( pe->pdtx_fifo_phy) { PRINT( "Start_TxFrmToPhy: previous txfifo_to_phy item does not finished\n"); return false; } r_status = _Rd( pe, RG_STATUS_TX); // TODO: F_ONTRANSMIT | F_TX_BUSY - ??? if( !(r_status & F_ONTX_REQ)) { sz_filled_tx_fifo = (F_TXW(r_status) << 3); if( (pdat->len_data > sz_filled_tx_fifo)) { PRINT("emac_tx_job: ERROR: TX_FIFO data size(%u) < packet data size(%u)\n", sz_filled_tx_fifo, pdat->len_data); // TODO: придумать что-то на этот случай... pdat->len_data = sz_filled_tx_fifo; ++pe->pndev->stats.tx_errors; } pe->pdtx_fifo = (pe->pdtx_fifo->p_next->state==EDDS_FIFO) ? pe->pdtx_fifo->p_next : NULL; pdat->state = EDDS_FIFO_PHY; pe->pdtx_fifo_phy = pdat; // В TX_FIFO есть данные и контроллер не занят, инициируем передачу данных в PHY: // start TX EMAC: _Wr( pe, RG_TX_FRAME_CONTROL, F_DISENCAPFR | F_DISPAD | F_LENGTH( pdat->len_data) | F_TX_REQ); // ожидаем прерывания emac_frame_tx_ih() ... return true; } return true; } bool Fin_TxFrm( mc_emac_t *pe) { emac_dma_data_tx_t *pdat = pe->pdtx_phy; fifo_t *pff = &pe->ffTx; #ifdef USE_MULTI_MBUF char *pdatanext; #endif //USE_MULTI_MBUF if( pe->stop_tx) return false; if( (pdat==NULL) || (pdat->state != EDDS_PHY)) { PRINT( "Fin_TxFrm: phi-item is absent\n"); return false; } //PRINT( "emac_tx_job: state EDDS_PHY\n"); EPRINT(" PHY << %u\n", pdat->len_data); ++pe->pndev->stats.tx_packets; pe->pndev->stats.tx_bytes += pdat->len_data; /*if( pdat->pskb) { dev_kfree_skb( pdat->pskb); pdat->pskb = NULL; }*/ #ifdef USE_MULTI_MBUF pdatanext = ((pdat->p_next->len_data && pdat->p_next->pdata) ? pdat->p_next->pdata : NULL); mmbuf_free_mem( &mmbuf_tx, pdat->pdata, pdat->len_data, pdatanext); #endif //USE_MULTI_MBUF emac_dma_data_reset( pdat); if( pdat != (emac_dma_data_tx_t *)fifo_free_first_held( pff)) PRINT( "Fin_TxFrm: ERROR: Trying of using hold said TX data list from other thread!\n"); pe->pdtx_phy = NULL; if( pe->is_netif_stop_queue) { // будим или стартуем TX-очередь сетевого интерфейса: if( netif_queue_stopped( pe->pndev)) netif_wake_queue( pe->pndev); else netif_start_queue( pe->pndev); pe->is_netif_stop_queue = false; } else { //pr_info( "emac_frm_tx_ih: Start_TxFrmToPhy()\n"); if( Start_TxFrmToPhy( pe)) { //pr_info( "emac_frm_tx_ih: StartDma_MemToTxFifo()\n"); if( !StartDma_MemToTxFifo( pe)) return false; } else return false; } return true; } static netdev_tx_t emac_start_xmit( struct sk_buff *pskb, struct net_device *pndev) { mc_emac_t *pe = netdev_priv( pndev); fifo_t *pff = &pe->ffTx; emac_dma_data_tx_t *pdat; u32 len = 0; bool is_fifo_empty; //pr_info( "emac_start_xmit: tx %d B\n", pskb->len); EPRINT("NetIF >> %u\n", pskb->len); if( pe->stop_tx) { dev_kfree_skb( pskb); return NETDEV_TX_BUSY; } if( pskb->len < 4 || pskb->len > pndev->mtu + ETH_HEADER_SIZE + ETH_CRC_SIZE) { PDEBUG( "emac_start_xmit ERROR: pskb->len = %d, dev->mtu = %d\n", pskb->len, pndev->mtu); ++pndev->stats.tx_errors; dev_kfree_skb( pskb); return NETDEV_TX_OK; } if( (pdat = (emac_dma_data_tx_t *)fifo_get_first_free( pff)) == NULL) { // так быть не должно ! PRINT( "emac_start_xmit: Unexpected ERROR: DMA TX queue if full\n"); //!TODO: проверить, что станет с pskb !!! netif_stop_queue( pndev); dev_kfree_skb( pskb); pe->is_netif_stop_queue = true; return NETDEV_TX_BUSY; } if( pdat->p_next->p_next == (emac_dma_data_tx_t *)fifo_get_first_held( pff)) { // Очередь почти заполнена (остался последний свободный), останавливаем очередь драйвера: // останавливаем TX-очередь сетевого интерфейса: PRINT( "emac_start_xmit: !!! netif_stop_queue() !!!\n"); netif_stop_queue( pndev); pe->is_netif_stop_queue = true; //PRINT( "emac_start_xmit: DMA TX queue if full, stop, wait for interrupt\n; } len = pdat->len_data = pskb->len; if( len < 60) len = 60; #ifdef USE_MULTI_MBUF if( !mmbuf_hold_mem( &mmbuf_tx, len, &pdat->pdata)) { pr_info( " emac_start_xmit: mmbuf_hold_mem() -> 0\n"); netif_stop_queue( pndev); dev_kfree_skb( pskb); pe->is_netif_stop_queue = true; return NETDEV_TX_BUSY; } #endif // !USE_MULTI_MBUF //!TODO: добавить pskb в драйверную очередь отправки данных в TX_FIFO !!! memcpy( pdat->pdata, pskb->data, pskb->len); pdat->len_data = pskb->len; pdat->state = EDDS_MEM; dev_kfree_skb( pskb); if( pdat->len_data != len) { memset( &pdat->pdata[pdat->len_data], 0, (len - pdat->len_data)); pdat->len_data = len; } if( pe->pdtx_mem == NULL) pe->pdtx_mem = pdat; is_fifo_empty = fifo_get_first_held( pff) ? false : true; if( pdat != (emac_dma_data_tx_t *)fifo_hold_first_free( pff)) PRINT( "emac_start_xmit: ERROR: Trying of using free said TX data list from other thread!\n"); if( is_fifo_empty && F_TXW(_Rd( pe, RG_STATUS_TX))==0) { // если следы TX-работы в прерываниях не обнаружены, то запускаем ее: StartDma_MemToTxFifo( pe); } return NETDEV_TX_OK; } // RX: //-> true - был добавлен хотя бы один новый элемент в список и напрвлен на прием данных по DMA, false - нет bool ProcRxFifo( mc_emac_t *pe, bool permit_dma) { struct net_device *pndev = pe->pndev; fifo_t *pff = &pe->ffRx; u32 r_stat; emac_dma_data_rx_t *pdat; bool has_error; u32 frm_stat; u32 qnt_frm; u32 i, len; u32 cnt_held = 0; //- число новых занятых элементов в списке bool is_start_dma = false; if( pe->stop_rx) return false; r_stat = _Rd( pe, RG_STATUS_RX); _Wr( pe, RG_STATUS_RX, 0); //pr_info( ">>> emac_frm_rx_ih: STATUS_RX=%Xh\n", r_stat); if( r_stat & (F_STATUS_OVF | F_FIFO_OVF)) { // Есть переполнение PRINT("RX overflow! %u %u\n", pff->cntHeld, pff->cntFree); if( F_NUM_MISSED( r_stat)) pndev->stats.rx_over_errors += F_NUM_MISSED( r_stat); else ++pndev->stats.rx_over_errors; en_print = true; } qnt_frm = F_NUM_FR( r_stat); if( (r_stat & F_RX_DONE) || qnt_frm) { if( (pdat=(emac_dma_data_rx_t *)fifo_get_first_free( pff)) && qnt_frm ) { for( i=0; (i < qnt_frm); ++i) { if( pe->sign_stop_rx_job) { pe->sign_stop_rx_job = 2; break; } has_error = false; if( (pdat=(emac_dma_data_rx_t *)fifo_get_first_free( pff))==NULL ) { pe->is_full_ff_rx = true; break; } frm_stat = _Rd( pe, RG_RX_FRAME_STATUS_FIFO); len = F_LEN( frm_stat); if( !(frm_stat & F_OK)) { //PRINT( "ProcRxFifo: !F_OK, frm_stat=%#Xh\n", frm_stat); ++pndev->stats.rx_errors; if( frm_stat & F_LENGTH_ERROR) ++pndev->stats.rx_length_errors; if( frm_stat & (F_ALIGN_ERROR | F_FRAME_ERROR | F_TOO_LONG | F_TOO_SHORT | F_DRIBBLE_NIBBLE)) ++pndev->stats.rx_frame_errors; has_error = true; } if( !(frm_stat & F_LENGTH_ERROR) && (len < 4 || len > pndev->mtu + ETH_HEADER_SIZE + ETH_CRC_SIZE)) { // так не должно быть, если в RX_FRAME_CONTROL активируем Accept_TooShort & Discard_TooLong PRINT( "ProcRxFifo: bad length %d bytes, frm_stat=%#08x\n", len, frm_stat); ++pndev->stats.rx_length_errors; has_error = true; } if( has_error) PRINT( "ProcRxFifo: len=%u, err=%u !!!\n", len, has_error); if( len==0) { PRINT( "ProcRxFifo: len=%u, err=%u\n", len, has_error); continue; } if( !has_error) { if( frm_stat & (F_MCM | F_MCHT)) ++pndev->stats.multicast; } #ifdef USE_MULTI_MBUF if( !mmbuf_hold_mem( &mmbuf_rx, len, &pdat->pbuf)) break; #endif // !USE_MULTI_MBUF pdat->len_data = len; pdat->has_error = has_error; pdat->state = EDDS_FIFO; if( pdat != (emac_dma_data_rx_t *)fifo_hold_first_free( &pe->ffRx)) PRINT( "ProcRxFifo: ERROR: Trying of using free said RX data list from other thread!\n"); if( pe->pdrx_fifo == NULL) pe->pdrx_fifo = pdat; EPRINT(" PHY >> %u\n", pdat->len_data); ++cnt_held; if( cnt_held==1 && permit_dma) is_start_dma = StartDma_RxFifoToMem( pe); break; //- только так, иначе при переполнении RX_FIFO в начале следующих пакетов будут вставки данных от предыдущих пакетов в 6,6,4, ... байт } //-end: for( i=0; (i < qnt_frm); ++i) } //-end: if( pe->pdrx_dma_to_mem==NULL && ... else pe->is_full_ff_rx = true; } if( cnt_held==0 && permit_dma) is_start_dma = StartDma_RxFifoToMem( pe); return (is_start_dma ? true : false); } //-> true - DMA был заряжен, false - нет bool StartDma_RxFifoToMem( mc_emac_t *pe) { emac_dma_data_rx_t *pdat = pe->pdrx_fifo; u32 csr; if( pe->stop_rx) return false; if( pdat==NULL || pe->pdrx_dma_to_mem) return false; if( !pdat->has_error) { pdat->pdata = pdat->pbuf; pdat->has_error = false; } //-end: if( !has_error) else // has_error: { // ... осуществляем фиктивное выгребание некорректных данных: //TODO: проверить бываем ли тут ... pdat->pdata = pe->pbufrx; pdat->has_error = true; } // Инициируем отправку данных из RX_FIFO в память через DMA: pe->pdrx_fifo = ((pe->pdrx_fifo->p_next->state==EDDS_FIFO) ? pe->pdrx_fifo->p_next : NULL); pdat->state = EDDS_DMA_TO_MEM; pe->pdrx_dma_to_mem = pdat; csr = F_DMA_WN(15) | F_DMA_IPD | #if defined(CONFIG_NVCOM01M)||defined(CONFIG_MC30SF6) F_DMA_WCX( pdat->len_data - 1); #else F_DMA_WCX (((pdat->len_data + 7) >> 3) - 1); #endif _Wr_RxDMA( pe, RG_DMA_IR, mips_virt_to_phys( (unsigned)pdat->pbuf)); _Wr_RxDMA( pe, RG_DMA_CSR, (csr | F_DMA_RUN)); EPRINT("RX_FIFO >> %u\n", pdat->len_data); // Ожидаем прерывания drv_emac_dma_rx_ih(): return true; } int thread_emac_rx_job( void *pv) { struct net_device *pndev = (struct net_device *)pv; mc_emac_t *pe = netdev_priv( pndev); emac_dma_data_rx_t *pdat; #ifdef USE_MULTI_MBUF char *pdatanext; #endif //USE_MULTI_MBUF fifo_t *pff = &pe->ffRx; struct sk_buff *pskb; u32 len = 0; //long rc; while(1) { if( pe->stop_rx) { if( pe->stop_rx==1) pe->stop_rx = 2; //pr_info("emac_rx_job: wait_event_interruptible(): stop_rx=%u\n", pe->stop_rx); if( wait_event_interruptible( pe->wq_rxjob, pe->stop_rx==0 && !pe->is_param_changing)) { PRINT("thread_emac_rx_job: wait_event_interruptible(stop_rx) -> -ERESTARTSYS\n"); return -ERESTARTSYS; } continue; } while( (pdat = (emac_dma_data_rx_t *)fifo_get_first_held( pff)) ) { if( pe->sign_stop_rx_job) { pe->sign_stop_rx_job = 2; break; } if( pe->stop_rx) break; //PRINT("emac_rx_job: len=%u, state=%u, err=%u\n", pdat->len_data, pdat->state, pdat->has_error); if( (pdat->state == EDDS_FIFO) || (pdat->state == EDDS_DMA_TO_MEM) ) { break; } if( (pdat->state == EDDS_MEM) && !pdat->has_error) { #ifdef CONFIG_MC30SF6 /* TODO: проверить по документации: ///попробуем сделать выравнивание на 16 байт **** pskb = dev_alloc_skb((len + 16 + 0xF) & ~(0xF)); if (unlikely(pskb == NULL)) { printk(KERN_NOTICE "%s: Low memory, packet dropped.\n", dev->name); dev->stats.rx_dropped++; return 0; } // Выравниваем заголовок IP на байта. skb_reserve(pskb, 8);*/ #else // Размер пакета равен (len + 2) с выравниванием на 4 байта. // "+2", потому что мы выравниваем заголовок IP на 4 байта // (см. функцию skb_reserve() ниже) pskb = dev_alloc_skb( (pdat->len_data + 2 + 3) & ~3); if( pskb == NULL) { PRINT( "emac_rx_job: dev_alloc_skb: Low memory, packet dropped.\n"); ++pndev->stats.rx_dropped; goto l_util_data; } // Выравниваем заголовок IP на байта. skb_reserve( pskb, 2); #endif if( (pdat->pdata = skb_put( pskb, pdat->len_data))==NULL) { PRINT( "emac_rx_job: dev_alloc_skb: Low memory, packet dropped.\n"); ++pndev->stats.rx_dropped; goto l_util_data; } memcpy( pdat->pdata, pdat->pbuf, pdat->len_data); pskb->protocol = eth_type_trans( pskb, pndev); EPRINT( "NetIF << %u\n", pdat->len_data); if( netif_rx( pskb) == NET_RX_DROP) { ++pndev->stats.rx_dropped; PRINT("!!! emac_rx_job: netif_rx -> drop packet(%uB)\n", pdat->len_data); } else { ++pndev->stats.rx_packets; pndev->stats.rx_bytes += pdat->len_data; // Накопление статистики для построения гистограмм через /proc //pe->proc.rx_lengths [len / 100]++; } len = pdat->len_data; } //-end: if( (pdat->state == EDDS_MEM) && !pdat->has_error) l_util_data:; #ifdef USE_MULTI_MBUF pdatanext = ((pdat->p_next->len_data && pdat->p_next->pbuf) ? pdat->p_next->pbuf : NULL); mmbuf_free_mem( &mmbuf_rx, pdat->pbuf, pdat->len_data, pdatanext); #endif //USE_MULTI_MBUF emac_dma_data_rx_reset( pdat); if( pdat != (emac_dma_data_rx_t *)fifo_free_first_held( pff)) PRINT( "emac_rx_job: ERROR: Trying of using hold said RX data list from other thread!\n"); if( (fifo_get_first_held( pff)==NULL || F_NUM_FR( _Rd( pe, RG_STATUS_RX))==0)) { _Wr( pe, RG_CONTROL, _Rd( pe, RG_CONTROL) | F_IRQ_RX_DONE | F_IRQ_RX_OVF); } if( pe->is_full_ff_rx) pe->is_full_ff_rx = false; } //-end: while( (pdat = (emac_dma_data_rx_t *)fifo_get_first_held( pff)) ) if( pe->sign_stop_rx_job) { pe->sign_stop_rx_job = 2; break; } //PRINT("emac_rx_job: wait_event_interruptible():\n"); #if 1 if( wait_event_interruptible( pe->wq_rxjob, ((pdat = (emac_dma_data_rx_t *)fifo_get_first_held( pff)) && pdat->state!=EDDS_DMA_TO_MEM && pdat->state!=EDDS_FIFO) || pe->sign_stop_rx_job || pe->stop_rx) < 0) #else if( (rc=wait_event_interruptible_timeout( pe->wq_rxjob, ((pdat = (emac_dma_data_rx_t *)fifo_get_first_held( pff)) && pdat->state!=EDDS_DMA_TO_MEM && pdat->state!=EDDS_FIFO) || pe->sign_stop_rx_job || pe->stop_rx, msecs_to_jiffies(20000))) < 0) #endif { PRINT("emac_rx_job: wait_event_interruptible() -> -ERESTARTSYS\n"); return -ERESTARTSYS; } //PRINT("emac_rx_job: wait_event_interruptible() -> ffi=%Xh\n", (u32 )fifo_get_first_held( pff)); //pr_info("emac_rx_job: wait_event_interruptible() -> ffi=%Xh %u %u\n", // (u32 )fifo_get_first_held( pff), pe->sign_stop_rx_job, pe->stop_rx); } //-end: while(1) pe->p_task_rx_job = NULL; return 0; } int thread_emac_tx_job( void *pv) { struct net_device *pndev = (struct net_device *)pv; mc_emac_t *pe = netdev_priv( pndev); fifo_t *pff = &pe->ffTx; #ifndef USE_EXT_IRQ_PHY_LINK long rc = 0; #endif //USE_EXT_IRQ_PHY_LINK while(1) { if( pe->sign_stop_tx_job) { pe->sign_stop_tx_job = 2; break; } if( pe->stop_tx) { if( pe->stop_tx) pe->stop_tx = 2; //pr_info("thread_emac_tx_job: wait_event_interruptible(stop_tx)\n"); if( (rc=wait_event_interruptible_timeout( pe->wq_txjob, pe->sign_stop_tx_job || (pe->stop_tx==0 && !pe->is_param_changing), msecs_to_jiffies(1000))) ) { PRINT("thread_emac_tx_job: wait_event_interruptible(stop_tx) -> -ERESTARTSYS\n"); return -ERESTARTSYS; } if( rc==0 && pe->stop_tx && !pe->is_param_changing) emac_change_link( pe); continue; } //l_wait_data:; //pr_info( "emac_tx_job: fifo_get_first_held()\n"); if( fifo_get_first_held( pff)==NULL ) { //PRINT("emac_tx_job: wait_event_interruptible():\n"); //print_fifo_held_qnt( pctl, "job_NULL"); #ifdef USE_EXT_IRQ_PHY_LINK if( wait_event_interruptible( pe->wq_txjob, fifo_get_first_held( pff) || pe->sign_stop_tx_job || pe->stop_tx)) { //disable_irq( spw_tx_data_irq()); PRINT("thread_emac_tx_job: wait_event_interruptible() -> -ERESTARTSYS\n"); return -ERESTARTSYS; } #else // !USE_EXT_IRQ_PHY_LINK: if( (rc=wait_event_interruptible_timeout( pe->wq_txjob, fifo_get_first_held( pff) || pe->sign_stop_tx_job || pe->stop_tx, msecs_to_jiffies(1000))) < 0) //250 { //disable_irq( spw_tx_data_irq()); PRINT("thread_emac_tx_job: wait_event_interruptible() -> -ERESTARTSYS\n"); return -ERESTARTSYS; } //pr_info("emac_tx_job: wait_event_interruptible -> %i %u %u\n", // rc, pe->sign_stop_tx_job, pe->stop_tx); if( rc==0 && pe->stop_tx==0 && pe->stop_rx==0) emac_change_link( pe); #endif // !USE_EXT_IRQ_PHY_LINK //PRINT("emac_tx_job: wait_event_interruptible -> held=%Xh\n", (u32 )fifo_get_first_held( pff)); //pr_info("emac_tx_job: wait_event_interruptible -> held=%Xh %u %u\n", // (u32 )fifo_get_first_held( pff), pe->sign_stop_tx_job, pe->stop_tx); } } //-end: while(1) pe->p_task_tx_job = NULL; return 0; } //- end: thread_dma_tx_job /*void print_emac_regs( mc_emac_t *pe) { u32 rphy; pr_info( "emac_registers:\n"); pr_info( "RG_CONTROL=%Xh\n", _Rd( pe, RG_CONTROL)); pr_info( "RG_ADDR_L=%Xh\n", _Rd( pe, RG_ADDR_L)); pr_info( "RG_ADDR_H=%Xh\n", _Rd( pe, RG_ADDR_H)); pr_info( "RG_DADDR_L=%Xh\n", _Rd( pe, RG_DADDR_L)); pr_info( "RG_DADDR_H=%Xh\n", _Rd( pe, RG_DADDR_H)); pr_info( "RG_FCS_CLIENT=%Xh\n", _Rd( pe, RG_FCS_CLIENT)); pr_info( "RG_TYPE=%Xh\n", _Rd( pe, RG_TYPE)); pr_info( "RG_IFS_COLL_MODE=%Xh\n", _Rd( pe, RG_IFS_COLL_MODE)); pr_info( "RG_TX_FRAME_CONTROL=%Xh\n", _Rd( pe, RG_TX_FRAME_CONTROL)); pr_info( "RG_STATUS_TX=%Xh\n", _Rd( pe, RG_STATUS_TX)); pr_info( "RG_UCADDR_L=%Xh\n", _Rd( pe, RG_UCADDR_L)); pr_info( "RG_UCADDR_H=%Xh\n", _Rd( pe, RG_UCADDR_H)); pr_info( "RG_MCADDR_L=%Xh\n", _Rd( pe, RG_MCADDR_L)); pr_info( "RG_MCADDR_H=%Xh\n", _Rd( pe, RG_MCADDR_H)); pr_info( "RG_MCADDR_MASK_L=%Xh\n", _Rd( pe, RG_MCADDR_MASK_L)); pr_info( "RG_MCADDR_MASK_H=%Xh\n", _Rd( pe, RG_MCADDR_MASK_H)); pr_info( "RG_HASHT_L=%Xh\n", _Rd( pe, RG_HASHT_L)); pr_info( "RG_HASHT_H=%Xh\n", _Rd( pe, RG_HASHT_H)); pr_info( "RG_RX_FRAME_CONTROL=%Xh\n", _Rd( pe, RG_RX_FRAME_CONTROL)); pr_info( "RG_RX_FR_MAXSIZE=%Xh\n", _Rd( pe, RG_RX_FR_MAXSIZE)); pr_info( "RG_STATUS_RX=%Xh\n", _Rd( pe, RG_STATUS_RX)); pr_info( "RG_RX_FRAME_STATUS_FIFO=%Xh\n", _Rd( pe, RG_RX_FRAME_STATUS_FIFO)); pr_info( "RG_MD_CONTROL=%Xh\n", _Rd( pe, RG_MD_CONTROL)); pr_info( "RG_MD_STATUS=%Xh\n", _Rd( pe, RG_MD_STATUS)); pr_info( "RG_MD_MODE=%Xh\n", _Rd( pe, RG_MD_MODE)); pr_info( "RG_TX_TEST_CSR=%Xh\n", _Rd( pe, RG_TX_TEST_CSR)); pr_info( "RG_TX_FIFO=%Xh\n", _Rd( pe, RG_TX_FIFO)); pr_info( "RG_RX_TEST_CSR=%Xh\n", _Rd( pe, RG_RX_TEST_CSR)); pr_info( "RG_RX_FIFO=%Xh\n", _Rd( pe, RG_RX_FIFO)); pr_info( " IRQ_MSK0=%Xh, IRQ_REQ0=%Xh\n", MC_MASKR0, MC_QSTR0); if( mc_phy_read( pe, PHY_CTL, &rphy)) pr_info("PHY_CTL: PHY_CTL=%Xh\n", rphy); if( mc_phy_read( pe, PHY_STS, &rphy)) pr_info("PHY_STS: PHY_STS=%Xh\n", rphy); }*/ void emac_reinit_phy( mc_emac_t *pe) { // Сброс PHY mc_phy_reset( pe); #ifdef CONFIG_MC30SF6 mc_phy_write( pe, PHY_CTL, ((1 << 8) | PHY_CTL_ANEG_EN)); /// full duplex | autoneg enable // Автоподстройка (Auto-Negotiation) mc_phy_write( pe, PHY_ADVRT, PHY_ADVRT_CSMA | PHY_ADVRT_10 | PHY_ADVRT_10_FDX | PHY_ADVRT_100 | PHY_ADVRT_100_FDX); mc_phy_write( pe, PHY_CTL, ((1 << 8) | (1 << 7) | PHY_CTL_ANEG_EN | PHY_CTL_ANEG_RST)); /// full duplex | collision test | autoneg enable | autoneg restart #else mc_phy_write( pe, PHY_EXTCTL, PHY_EXTCTL_JABBER); // Автоподстройка (Auto-Negotiation) mc_phy_write( pe, PHY_ADVRT, PHY_ADVRT_CSMA | PHY_ADVRT_10_HDX | PHY_ADVRT_10_FDX | PHY_ADVRT_100_HDX | PHY_ADVRT_100_FDX); mc_phy_write( pe, PHY_CTL, PHY_CTL_ANEG_EN | PHY_CTL_ANEG_RST); #endif mc_phy_write( pe, PHY_ICS, (1 << 8) | (1 << 10)); } void reinit_emac( mc_emac_t *pe) { // Сброс приёмника и передатчика _Wr( pe, RG_CONTROL, F_CP_TX | F_RST_TX | F_CP_RX | F_RST_RX); udelay(10); PDEBUGG("MAC_CONTROL: 0x%08x\n", _Rd( pe, RG_CONTROL)); // Общие режимы _Wr( pe, RG_CONTROL, F_FULLD | // дуплексный режим F_EN_TX | // разрешение передачи F_EN_TX_DMA | // разрешение передающего DMА F_EN_RX | // разрешение приема F_IRQ_TX_DONE | // прерывание от передачи F_IRQ_RX_DONE | // прерывание по приёму F_IRQ_RX_OVF); // прерывание по переполнению PDEBUGG("MAC_CONTROL: 0x%08x\n", _Rd(pe, RG_CONTROL)); /* #endif */ // Режимы приёма _Wr( pe, RG_RX_FRAME_CONTROL, F_DIS_RCV_FCS | // не сохранять контрольную сумму F_ACC_TOOSHORT | // прием коротких кадров F_DIS_TOOLONG | // отбрасывание слишком длинных кадров F_DIS_FCSCHERR | // отбрасывание кадров с ошибкой CRC F_DIS_LENGTHERR); // отбрасывание кадров с ошибкой длины PDEBUGG( "RX_FRAME_CONTROL: 0x%08x\n", _Rd(pe, RG_RX_FRAME_CONTROL)); // Режимы передачи: // запрет формирования кадра в блоке передачи _Wr( pe, RG_TX_FRAME_CONTROL, F_DISENCAPFR); PDEBUGG( "TX_FRAME_CONTROL: 0x%08x\n", _Rd(pe, RG_TX_FRAME_CONTROL)); // Режимы обработки коллизии _Wr( pe, RG_IFS_COLL_MODE, F_ATTEMPT_NUM(15) | F_EN_CW | F_COLL_WIN(64) | F_JAMB(0xC3) | F_IFS(24)); #ifndef CONFIG_MC30SF6 // Тактовый сигнал MDC не должен превышать 2.5 МГц _Wr( pe, RG_MD_MODE, F_DIVIDER( mips_hpt_frequency / 2000000)); #endif /*pr_info( "RG_CONTROL=%Xh\n", _Rd( pe, RG_CONTROL)); pr_info( "RG_RX_FRAME_CONTROL=%Xh\n", _Rd( pe, RG_RX_FRAME_CONTROL)); pr_info( "RG_TX_FRAME_CONTROL=%Xh\n", _Rd( pe, RG_TX_FRAME_CONTROL)); pr_info( "RG_IFS_COLL_MODE=%Xh\n", _Rd( pe, RG_IFS_COLL_MODE));*/ } // Инициализация PHY static int emac_init_phy( mc_emac_t *pe, unsigned long base_addr) { unsigned id, retry = 0; u32 id1, id2; u32 rphy; while(1) { if( !mc_phy_read(pe, PHY_ID1, &id1)) pr_info( "emac_init_phy: PHY_ID1 reading ERROR\n"); if( !mc_phy_read(pe, PHY_ID2, &id2)) pr_info( "emac_init_phy: PHY_ID2 reading ERROR\n"); id = (id1 << 16) | id2; if( id != 0 && id != 0xffffffff) break; if( pe->phy > 0) --pe->phy; else { pe->phy = base_addr; ++retry; if( retry > 3) { printk("emac_init: no PHY detected\n"); return -ENODEV; } } } #ifdef CONFIG_MC30SF6 printk( "emac_init_phy: transceiver `%s' detected at address %d\n", ((id & PHY_ID_MASK) == PHY_ID2_NUMB) ? "LAN8710A" : "Unknown", pe->phy); #else printk( "emac_init_phy: transceiver `%s' detected at address %d\n", ((id & PHY_ID_MASK) == PHY_ID_KS8721BL) ? "KS8721" : "Unknown", pe->phy); #endif emac_reinit_phy( pe); reinit_emac( pe); if( mc_phy_read( pe, PHY_CTL, &rphy)) pr_info("PHY_CTL: PHY_CTL=%Xh\n", rphy); if( mc_phy_read( pe, PHY_STS, &rphy)) pr_info("PHY_STS: PHY_STS=%Xh\n", rphy); return RC_EMAC_SUCCESS; } bool emac_init_ff_tx( mc_emac_t *pe) { return fifo_init( &pe->ffTx, pe->tx_dma_fifo_size, sizeof(emac_dma_data_tx_t), &dma_data_tx_create, pe, &dma_data_tx_delete, FIFO_OPT__DOUBLE_LINK); } bool emac_init_ff_rx( mc_emac_t *pe) { return fifo_init( &pe->ffRx, pe->rx_dma_fifo_size, sizeof(emac_dma_data_rx_t), &dma_data_rx_create, pe, &dma_data_rx_delete, FIFO_OPT__DOUBLE_LINK); } // Деинициализация EMAC: static void emac_uninit( struct net_device *pndev) { mc_emac_t *pe = (mc_emac_t *)netdev_priv( pndev); //pr_info("*** emac_uninit\n"); if( pe->pbufrx) kfree( pe->pbufrx); emac_free_irqs( pe); fifo_free( &pe->ffTx); fifo_free( &pe->ffRx); #ifdef USE_MULTI_MBUF mmbuf_destroy( &mmbuf_tx); mmbuf_destroy( &mmbuf_rx); #endif //USE_MULTI_MBUF mutex_destroy( &pe->mtx_change_work); pe->is_netif_init = 0; } // Инициализация EMAC // static int emac_init( struct net_device *pndev) { mc_emac_t *pe = (mc_emac_t *)netdev_priv( pndev); int rc = RC_EMAC_ERROR; bool rcf; //pr_info("*** emac_init\n"); // *** Инициализируем основной объект EMAC: //memset( pe->proc, 0, sizeof(struct _proc_stat)); mutex_init( &pe->mtx_change_work); //init_waitqueue_head( &pe->wq_conn); init_waitqueue_head( &pe->wq_txjob); init_waitqueue_head( &pe->wq_rxjob); #ifdef USE_MULTI_MBUF //... if( !mmbuf_init( &mmbuf_tx, MAX_MMBUF_TX_SIZE)) { pr_info("emac_init: Could not allocate memory for multi_mbuf TX\n"); rc = -ENOMEM; goto l_err; } // pr_info( "emac_init: mmbuf_tx: sz=%u pbuf=%Xh %Xh\n", mmbuf_tx.sz_buf, (u32 )mmbuf_tx.pbuf, mmbuf_tx.buf_phys); if( !mmbuf_init( &mmbuf_rx, MAX_MMBUF_RX_SIZE)) { pr_info("emac_init: Could not allocate memory for multi_mbuf RX\n"); rc = -ENOMEM; goto l_err; } // pr_info( "emac_init: mmbuf_rx: sz=%u pbuf=%Xh %Xh\n", mmbuf_rx.sz_buf, (u32 )mmbuf_rx.pbuf, mmbuf_rx.buf_phys); #endif //USE_MULTI_MBUF rcf = fifo_init( &pe->ffTx, pe->tx_dma_fifo_size, sizeof(emac_dma_data_tx_t), &dma_data_tx_create, pe, &dma_data_tx_delete, FIFO_OPT__DOUBLE_LINK); if( !rcf) { pr_info("emac_init: Could create DMA TX data list\n"); rc = -ENOMEM; goto l_err; } rcf = fifo_init( &pe->ffRx, pe->rx_dma_fifo_size, sizeof(emac_dma_data_rx_t), &dma_data_rx_create, pe, &dma_data_rx_delete, FIFO_OPT__DOUBLE_LINK); if( !rcf) { pr_info("emac_init: Could create DMA RX data list\n"); rc = -ENOMEM; goto l_err; } if( emac_init_irqs( pe) != RC_EMAC_SUCCESS) { rc = RC_EMAC_ERROR; goto l_err; } if( (pe->pbufrx = kcalloc( 1, MAX_HW_RX_FIFO_SIZE, GFP_DMA)) == NULL) { pr_info("emac_init: Could not allocate memory for RX buffer\n"); rc = -ENOMEM; goto l_err; } // DMA: _Wr_TxDMA( pe, RG_DMA_CSR, (_Rd_TxDMA( pe, RG_DMA_CSR) | F_DMA_IPD)); _Wr_RxDMA( pe, RG_DMA_CSR, (_Rd_RxDMA( pe, RG_DMA_CSR) | F_DMA_IPD)); // *** HW initialization: //pr_info("emac_init: HW initialization\n"); // Включение тактовой частоты EMAC MC_CLKEN |= MC_CLKEN_EMAC; udelay(10); #ifdef CONFIG_MC30SF6 // Тактовый сигнал MDC не должен превышать 2.5 МГц _W(pe, RG_MD_MODE, F_DIVIDER( mips_hpt_frequency / 2000000)); #endif // Поиск адреса PHY pndev->base_addr = 31; if( (rc=emac_init_phy( pe, pndev->base_addr)) != RC_EMAC_SUCCESS) goto l_err; // *** network initialization: //pr_info("emac_init: network initialization\n"); // Свой адрес pndev->addr_assign_type = NET_ADDR_RANDOM; //random_ether_addr( pndev->perm_addr); { u8 sa[8] = {0xAA,0xAB,0xAC,0xAD,0xAE,0xAF,0,0}; memcpy( pndev->perm_addr, sa, sizeof(sa)); } emac_set_mac_addr( pndev, pndev->perm_addr); // Максимальный размер кадра _Wr( pe, RG_RX_FR_MAXSIZE, ETH_MTU + ETH_HEADER_SIZE + ETH_CRC_SIZE); pe->is_netif_init = 1; //pr_info("emac_init: END\n"); return RC_EMAC_SUCCESS; l_err:; emac_uninit( pndev); return rc; } void emac_start_rx( mc_emac_t *pe) { if( pe->stop_rx) { _Wr( pe, RG_CONTROL, _Rd( pe, RG_CONTROL) | (F_EN_RX | F_IRQ_RX_DONE | F_IRQ_RX_OVF | F_FULLD)); _Wr( pe, RG_RX_FRAME_CONTROL, F_DIS_RCV_FCS | F_ACC_TOOSHORT | F_DIS_TOOLONG | F_DIS_FCSCHERR | F_DIS_LENGTHERR); pe->stop_rx = 0; wake_up_interruptible( &pe->wq_rxjob); //- работу с нитью TX_job переводим в рабочий режим //schedule_timeout(1); } } void emac_stop_rx( mc_emac_t *pe) { int i, imax=5; if( !pe->stop_rx) { // останавливаем все очереди на передачу, нити, сбрасываем списки, RX_FIFO ... pe->stop_rx = 1; wake_up_interruptible( &pe->wq_rxjob); //- паркуем работу с нитью RX_job _Wr( pe, RG_CONTROL, (_Rd( pe, RG_CONTROL) & ~(F_EN_RX | F_IRQ_RX_DONE | F_IRQ_RX_OVF)) | F_CP_RX | F_RST_RX); //mc_phy_reset( pe); ??? for( i=0; (pe->stop_rx != 2) && (i < imax); ++i) schedule_timeout(1); if( pe->stop_rx != 2) pr_info("emac_stop_rx: Could not stop RX job thread\n"); fifo_reset( &pe->ffRx); pe->pdrx_fifo = NULL; pe->pdrx_dma_to_mem = NULL; pe->pdrx_mem = NULL; } } void emac_start_tx( mc_emac_t *pe) { if( pe->stop_tx) { _Wr( pe, RG_TX_FRAME_CONTROL, F_DISENCAPFR); _Wr( pe, RG_CONTROL, _Rd( pe, RG_CONTROL) | (F_EN_TX_DMA | F_EN_TX | F_IRQ_TX_DONE | F_FULLD)); netif_carrier_on( pe->pndev); if( netif_queue_stopped( pe->pndev)) netif_wake_queue( pe->pndev); else netif_start_queue( pe->pndev); pe->stop_tx = 0; wake_up_interruptible( &pe->wq_txjob); //- работу с нитью TX_job переводим в рабочий режим //schedule_timeout(1); } } void emac_stop_tx( mc_emac_t *pe) { int i, imax=5; if( !pe->stop_tx) { // останавливаем все очереди на передачу, нити, сбрасываем списки, TX_FIFO ... pe->stop_tx = 1; wake_up_interruptible( &pe->wq_txjob); //- паркуем работу с нитью TX_job _Wr(pe, RG_TX_FRAME_CONTROL, (_Rd( pe, RG_TX_FRAME_CONTROL) & ~(F_TX_REQ | F_LENGTH(0xFFFF))) | F_DISENCAPFR); _Wr( pe, RG_CONTROL, (_Rd( pe, RG_CONTROL) & ~(F_EN_TX_DMA | F_EN_TX | F_IRQ_TX_DONE)) | F_CP_TX | F_RST_TX); netif_stop_queue( pe->pndev); netif_carrier_off( pe->pndev); for( i=0; (pe->stop_tx != 2) && (i < imax); ++i) schedule_timeout(1); if( pe->stop_tx != 2) pr_info("emac_stop_tx: Could not stop TX job thread\n"); fifo_reset( &pe->ffTx); pe->pdtx_mem = NULL; pe->pdtx_dma_to_fifo = NULL; pe->pdtx_fifo = NULL; pe->pdtx_fifo_phy = NULL; pe->pdtx_phy = NULL; } } void emac_link_up( mc_emac_t *pe) { if( pe->stat_link_down) { mutex_lock( &pe->mtx_change_work); emac_start_tx( pe); pe->stat_link_down = 0; mutex_unlock( &pe->mtx_change_work); } } void emac_link_down( mc_emac_t *pe) { if( !pe->stat_link_down) { mutex_lock( &pe->mtx_change_work); emac_stop_tx( pe); pe->stat_link_down = 2; mutex_unlock( &pe->mtx_change_work); } } void emac_change_link( mc_emac_t *pe) { u32 phy_stat; if( !mc_phy_read( pe, PHY_STS, &phy_stat)) { pr_info( "emac_change_link: PHY_STS reading ERROR\n"); return; } //pr_info("change_link: PHY_STS=%Xh\n", phy_stat); if( (phy_stat & PHY_STS_LINK) && pe->stat_link_down==2) { emac_link_up( pe); pr_info("%s: link is up\n", pe->pndev->name); } else if( !(phy_stat & PHY_STS_LINK) && !pe->stat_link_down) { emac_link_down( pe); pr_info( "%s: link is down\n", pe->pndev->name); ++pe->pndev->stats.tx_carrier_errors; } } static int emac_open( struct net_device *pndev) { mc_emac_t *pe = netdev_priv(pndev); u32 stat; //pr_info("*** eth_open\n"); // *** EMAC: pe->sign_stop_tx_job = 0; pe->sign_stop_rx_job = 0; pe->is_netif_stop_queue = 0; fifo_reset( &pe->ffTx); fifo_reset( &pe->ffRx); pe->pdtx_mem = NULL; pe->pdtx_dma_to_fifo = NULL; pe->pdtx_fifo = NULL; pe->pdtx_fifo_phy = NULL; pe->pdtx_phy = NULL; pe->pdrx_dma_to_mem = NULL; if( (pe->p_task_tx_job = kthread_run( &thread_emac_tx_job, pndev, "emac_tx_job"))==NULL ) { if( IS_ERR( pe->p_task_tx_job)) { pr_info( "emac_open: Could not create 'emac_tx_job' thread\n"); return -ENOMEM; } } if( (pe->p_task_rx_job = kthread_run( &thread_emac_rx_job, pndev, "emac_rx_job"))==NULL ) { if( IS_ERR( pe->p_task_rx_job)) { pr_info( "emac_open: Could not create 'emac_rx_job' thread\n"); return -ENOMEM; } } if( !mc_phy_read( pe, PHY_STS, &stat)) { pr_info( "emac_open: PHY_STS reading ERROR\n"); return -EIO; } pe->stat_link_down = (stat & PHY_STS_LINK) ? 0 : 2; if( pe->stat_link_down == 0) { // *** netif: if( netif_queue_stopped( pndev)) netif_wake_queue( pndev); else netif_start_queue( pndev); #ifdef CONFIG_MULTICORE_ETH_NAPI napi_enable( &pe->napi); #endif _Wr( pe, RG_CONTROL, _Rd( pe, RG_CONTROL) | F_EN_TX | F_EN_TX_DMA | F_EN_RX); } emac_enable_irqs( pe, 1); pe->is_netif_open = 1; //print_emac_regs( pe); return RC_EMAC_SUCCESS; } static int emac_close( struct net_device *pndev) { mc_emac_t *pe = netdev_priv( pndev); int rc; //pr_info("*** eth_close\n"); emac_enable_irqs( pe, 0); _Wr( pe, RG_CONTROL, _Rd( pe, RG_CONTROL) & ~(F_EN_TX | F_EN_TX_DMA | F_EN_RX)); // *** netif: #ifdef CONFIG_MULTICORE_ETH_NAPI napi_disable( &pe->napi); #endif if( !netif_queue_stopped( pndev)) netif_stop_queue( pndev); pe->sign_stop_tx_job = 1; wake_up_interruptible( &pe->wq_txjob); pe->sign_stop_rx_job = 1; wake_up_interruptible( &pe->wq_rxjob); mdelay( 200); // *** EMAC: if( pe->sign_stop_tx_job != 2) { if( pe->p_task_tx_job) { //pr_info( "emac_close: forced TX job stop\n"); if( (rc = kthread_stop( pe->p_task_tx_job)) < 0) pr_info( "emac_close: kthread_stop ERROR(%u)\n", rc); } pe->p_task_tx_job = NULL; } if( pe->sign_stop_rx_job != 2) { if( pe->p_task_rx_job) { //pr_info( "emac_close: forced RX job stop\n"); if( (rc = kthread_stop( pe->p_task_rx_job)) < 0) pr_info( "emac_close: kthread_stop ERROR(%u)\n", rc); } pe->p_task_rx_job = NULL; } fifo_reset( &pe->ffTx); fifo_reset( &pe->ffRx); pe->is_netif_open = 0; return RC_EMAC_SUCCESS; } static const struct ethtool_ops mc_ethtool_ops = { .get_settings = etool_get_settings, .set_settings = etool_set_settings, .get_drvinfo = etool_get_drvinfo, .get_link = ethtool_op_get_link, .get_msglevel = etool_get_msglevel, .set_msglevel = etool_set_msglevel, .get_ringparam = etool_get_ringparam, .set_ringparam = etool_set_ringparam }; static const struct net_device_ops netdev_ops = { .ndo_init = emac_init, .ndo_uninit = emac_uninit, .ndo_open = emac_open, .ndo_stop = emac_close, .ndo_start_xmit = emac_start_xmit, //.ndo_tx_timeout = emac_timeout, //.ndo_set_multicast_list = emac_set_multicast_list, .ndo_change_mtu = emac_change_mtu, .ndo_set_mac_address = emac_set_mac_addr #ifdef CONFIG_NET_POLL_CONTROLLER //.ndo_poll_controller = eth_poll_controller, #endif }; //phy_ethtool_sset(); //mpc52xx_fec_probe(); //static __devinit int kshubin static int emac_probe( struct platform_device *pplatdev) { struct net_device *pndev; struct resource *pr; mc_emac_t *pe; int ret = RC_EMAC_SUCCESS; PDEBUG("*** drv_emac_probe\n"); //pr_info( "*** drv_emac_probe:\n"); if( (pndev = alloc_etherdev(sizeof(mc_emac_t))) == NULL) { printk( "%s: could not allocate device.\n", CARD_NAME); ret = -ENOMEM; goto err_get_res; } pndev->dma = (unsigned char)-1; pndev->netdev_ops = &netdev_ops; pndev->ethtool_ops = &mc_ethtool_ops; ether_setup( pndev); pndev->mtu = ETH_MTU; pe = netdev_priv( pndev); pemac = pe; memset( pe, 0, sizeof(*pe)); pe->tx_dma_fifo_size = EMAC_DFLT_TX_DSC_QNT; pe->rx_dma_fifo_size = EMAC_DFLT_RX_DSC_QNT; pe->pndev = pndev; pe->pplatdev = pplatdev; pr = platform_get_resource_byname( pplatdev, IORESOURCE_MEM, "regs"); if( pr == NULL) { pr_info( "mc_eth_probe: platform_get_resource_byname('regs') ERROR\n"); ret = -ENODEV; goto err_get_res; } pe->eth_regs = devm_ioremap_resource( &pplatdev->dev, pr); if( IS_ERR( pe->eth_regs)) { pr_info( "mc_eth_probe: devm_ioremap_resource('regs') ERROR\n"); ret = -ENOMEM; goto err_get_res; } pr = platform_get_resource_byname( pplatdev, IORESOURCE_MEM, "txdma"); if( pr == NULL) { pr_info( "mc_eth_probe: platform_get_resource_byname('txdma') ERROR\n"); ret = -ENODEV; goto err_get_res; } //pr_info( ">>> mc_eth_probe: dma_tx_regs = devm_ioremap_resource(txdma)\n"); pe->dma_tx_regs = devm_ioremap_resource( &pplatdev->dev, pr); if( IS_ERR( pe->dma_tx_regs)) { pr_info( "mc_eth_probe: devm_ioremap_resource('txdma') ERROR\n"); ret = -ENOMEM; goto err_get_res; } pr = platform_get_resource_byname( pplatdev, IORESOURCE_MEM, "rxdma"); if( pr == NULL) { pr_info( "mc_eth_probe: platform_get_resource_byname('rxdma') ERROR\n"); ret = -ENODEV; goto err_get_res; } //pr_info( ">>> mc_eth_probe: dma_rx_regs = devm_ioremap_resource(rxdma)\n"); pe->dma_rx_regs = devm_ioremap_resource( &pplatdev->dev, pr); if( IS_ERR( pe->dma_rx_regs)) { pr_info( "mc_eth_probe: devm_ioremap_resource('rxdma') ERROR\n"); ret = -ENOMEM; goto err_get_res; } PDEBUG( "Registers at: %p, RX_DMA: %p, TX_DMA: %p\n", pe->eth_regs, pe->dma_rx_regs, pe->dma_tx_regs); pe->irq_frm_rx = platform_get_irq_byname( pplatdev, "rxframe_irq"); if( pe->irq_frm_rx < 0) { ret = -EINVAL; goto err_get_res; } pe->irq_frm_tx = platform_get_irq_byname( pplatdev, "txframe_irq"); if( pe->irq_frm_tx < 0) { ret = -EINVAL; goto err_get_res; } pe->irq_dma_rx = platform_get_irq_byname( pplatdev, "rxdma_irq"); if( pe->irq_dma_rx < 0) { ret = -EINVAL; goto err_get_res; } pe->irq_dma_tx = platform_get_irq_byname( pplatdev, "txdma_irq"); if( pe->irq_dma_tx < 0) { ret = -EINVAL; goto err_get_res; } #ifdef USE_EXT_IRQ_PHY_LINK pe->irq_phy_link = platform_get_irq_byname( pplatdev, "phy_link_irq"); if( pe->irq_phy_link < 0) { pe->irq_phy_link = IRQ_EXT_LINK_DFLT; // ret = -EINVAL; } #endif //USE_EXT_IRQ_PHY_LINK pndev->irq = pe->irq_frm_rx; //- TODO ??? #ifdef CONFIG_MULTICORE_ETH_NAPI // Запуск NAPI netif_napi_add( ndev, &pe->napi, eth_poll, 64); #endif pe->msg_level = netif_msg_init( -1, MC_MSG_DEFAULT); if( (ret = register_netdev( pndev)) ) { ret = -ENXIO; goto err_register; } platform_set_drvdata( pplatdev, pndev); pr_info( "drv_emac_probe: %s found\n", CARD_NAME); { u32 rphy; if( mc_phy_read( pe, PHY_STS, &rphy)) pr_info("drv_emac_probe: PHY_STS: PHY_STS=%Xh\n", rphy); } return RC_EMAC_SUCCESS; err_register: #ifdef CONFIG_MULTICORE_ETH_NAPI netif_napi_del( &pe->napi); #endif free_netdev( pndev); err_get_res:; // devm_release_mem_region(dev, res->start, size); pr_info("*** mc_emac_probe: %s ERROR\n", CARD_NAME); return ret; } //static __devexit int kshubin static int emac_remove( struct platform_device *pplatdev) { struct net_device *ndev = platform_get_drvdata(pplatdev); emac_close(ndev); remove_proc_entry( PROC_FILENAME, NULL /* parent dir */); return RC_EMAC_SUCCESS; } static struct platform_driver mc_emac_driver = { .driver = { .name = CARD_NAME, .owner = THIS_MODULE, }, .probe = emac_probe, .remove = emac_remove, }; module_platform_driver( mc_emac_driver); #if 0 static int drv_emac_module_init(void) { pr_info( "\n*** drv_emac_module_init:\n"); /*memset( &emac, 0, sizeof(emac)); emac.tx_dma_fifo_size = EMAC_DFLT_TX_DSC_QNT; emac.rx_dma_fifo_size = EMAC_DFLT_RX_DSC_QNT;*/ return RC_EMAC_SUCCESS; } static void __exit drv_emac_module_exit( void) { pr_info( "*** drv_emac_module_exit:\n"); } module_init( drv_emac_module_init); module_exit( drv_emac_module_exit); #endif //0 MODULE_DESCRIPTION( "Multicore Ethernet Media Access Controller driver"); MODULE_AUTHOR( "Dmitry Evtushenko"); MODULE_LICENSE( "GPL");