@@ -0,0 +1,1341 @@+/*+*FaradayFTMAC10010/100Ethernet+*+*(C)Copyright2009-2011FaradayTechnology+*Po-YuChuang<ratbert@faraday-tech.com>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,writetotheFreeSoftware+*Foundation,Inc.,675MassAve,Cambridge,MA02139,USA.+*/++#include<linux/dma-mapping.h>+#include<linux/etherdevice.h>+#include<linux/ethtool.h>+#include<linux/init.h>+#include<linux/io.h>+#include<linux/kernel.h>+#include<linux/mii.h>+#include<linux/module.h>+#include<linux/moduleparam.h>+#include<linux/netdevice.h>+#include<linux/platform_device.h>++#include"ftmac100.h"++#define USE_NAPI++#define DRV_NAME "ftmac100"+#define DRV_VERSION "0.1"++#define RX_QUEUE_ENTRIES 128 /* must be power of 2 */+#define TX_QUEUE_ENTRIES 16 /* must be power of 2 */++#define MAX_PKT_SIZE 1518+#define RX_BUF_SIZE 2044 /* must be smaller than 0x7ff */++/******************************************************************************+*priveatedata+*****************************************************************************/+structftmac100_descs{+structftmac100_rxdesrxdes[RX_QUEUE_ENTRIES];+structftmac100_txdestxdes[TX_QUEUE_ENTRIES];+};++structftmac100{+structresource*res;+void*base;+intirq;++structftmac100_descs*descs;+dma_addr_tdescs_dma_addr;++unsignedintrx_pointer;+unsignedinttx_clean_pointer;+unsignedinttx_pointer;+unsignedinttx_pending;++spinlock_thw_lock;+spinlock_trx_lock;+spinlock_ttx_lock;++structnet_device*netdev;+structdevice*dev;+#ifdef USE_NAPI+structnapi_structnapi;+#endif++structnet_device_statsstats;++structmii_if_infomii;+};++/******************************************************************************+*internalfunctions(hardwareregisteraccess)+*****************************************************************************/+#define INT_MASK_RX_DISABLED (FTMAC100_INT_XPKT_OK | \+FTMAC100_INT_XPKT_LOST|\+FTMAC100_INT_RPKT_LOST|\+FTMAC100_INT_AHB_ERR|\+FTMAC100_INT_PHYSTS_CHG)++#define INT_MASK_ALL_ENABLED (INT_MASK_RX_DISABLED | \+FTMAC100_INT_RPKT_FINISH|\+FTMAC100_INT_NORXBUF)++#define INT_MASK_ALL_DISABLED 0++#ifdef USE_NAPI+staticinlinevoidftmac100_disable_rxint(structftmac100*priv)+{+unsignedlongflags;++spin_lock_irqsave(&priv->hw_lock,flags);+iowrite32(INT_MASK_RX_DISABLED,priv->base+FTMAC100_OFFSET_IMR);+spin_unlock_irqrestore(&priv->hw_lock,flags);+}+#endif++staticinlinevoidftmac100_enable_all_int(structftmac100*priv)+{+unsignedlongflags;++spin_lock_irqsave(&priv->hw_lock,flags);+iowrite32(INT_MASK_ALL_ENABLED,priv->base+FTMAC100_OFFSET_IMR);+spin_unlock_irqrestore(&priv->hw_lock,flags);+}++staticinlinevoidftmac100_disable_all_int(structftmac100*priv)+{+unsignedlongflags;++spin_lock_irqsave(&priv->hw_lock,flags);+iowrite32(INT_MASK_ALL_DISABLED,priv->base+FTMAC100_OFFSET_IMR);+spin_unlock_irqrestore(&priv->hw_lock,flags);+}++staticinlinevoidftmac100_set_receive_ring_base(structftmac100*priv,+dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_RXR_BADR);+}++staticinlinevoidftmac100_set_transmit_ring_base(structftmac100*priv,+dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_TXR_BADR);+}++staticinlinevoidftmac100_txdma_start_polling(structftmac100*priv)+{+iowrite32(1,priv->base+FTMAC100_OFFSET_TXPD);+}++staticintftmac100_reset(structftmac100*priv)+{+structdevice*dev=&priv->netdev->dev;+unsignedlongflags;+inti;++/* NOTE: reset clears all registers */++spin_lock_irqsave(&priv->hw_lock,flags);+iowrite32(FTMAC100_MACCR_SW_RST,priv->base+FTMAC100_OFFSET_MACCR);+spin_unlock_irqrestore(&priv->hw_lock,flags);++for(i=0;i<5;i++){+intmaccr;++spin_lock_irqsave(&priv->hw_lock,flags);+maccr=ioread32(priv->base+FTMAC100_OFFSET_MACCR);+spin_unlock_irqrestore(&priv->hw_lock,flags);+if(!(maccr&FTMAC100_MACCR_SW_RST)){+/*+*FTMAC100_MACCR_SW_RSTcleareddoesnotindicate+*thathardwareresetcompleted(whatthef*ck).+*Westillneedtowaitforawhile.+*/+usleep_range(500,1000);+return0;+}++usleep_range(1000,10000);+}++dev_err(dev,"software reset failed\n");+return-EIO;+}++staticvoidftmac100_set_mac(structftmac100*priv,constunsignedchar*mac)+{+unsignedintmaddr=mac[0]<<8|mac[1];+unsignedintladdr=mac[2]<<24|mac[3]<<16|mac[4]<<8|mac[5];++iowrite32(maddr,priv->base+FTMAC100_OFFSET_MAC_MADR);+iowrite32(laddr,priv->base+FTMAC100_OFFSET_MAC_LADR);+}++staticintftmac100_start_hw(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+unsignedlongflags;+intmaccr;++if(ftmac100_reset(priv))+return-EIO;++/* setup ring buffer base registers */++spin_lock_irqsave(&priv->hw_lock,flags);+ftmac100_set_receive_ring_base(priv,+priv->descs_dma_addr+offsetof(structftmac100_descs,rxdes));+ftmac100_set_transmit_ring_base(priv,+priv->descs_dma_addr+offsetof(structftmac100_descs,txdes));++iowrite32(FTMAC100_APTC_RXPOLL_CNT(1),+priv->base+FTMAC100_OFFSET_APTC);++ftmac100_set_mac(priv,netdev->dev_addr);++maccr=FTMAC100_MACCR_XMT_EN|+FTMAC100_MACCR_RCV_EN|+FTMAC100_MACCR_XDMA_EN|+FTMAC100_MACCR_RDMA_EN|+FTMAC100_MACCR_CRC_APD|+FTMAC100_MACCR_FULLDUP|+FTMAC100_MACCR_RX_RUNT|+FTMAC100_MACCR_RX_BROADPKT;++iowrite32(maccr,priv->base+FTMAC100_OFFSET_MACCR);+spin_unlock_irqrestore(&priv->hw_lock,flags);++return0;+}++staticvoidftmac100_stop_hw(structftmac100*priv)+{+unsignedlongflags;++spin_lock_irqsave(&priv->hw_lock,flags);+iowrite32(0,priv->base+FTMAC100_OFFSET_MACCR);+spin_unlock_irqrestore(&priv->hw_lock,flags);+}++/******************************************************************************+*internalfunctions(receivedescriptor)+*****************************************************************************/+staticinlineintftmac100_rxdes_first_segment(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_FRS;+}++staticinlineintftmac100_rxdes_last_segment(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_LRS;+}++staticinlineintftmac100_rxdes_owned_by_dma(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RXDMA_OWN;+}++staticinlinevoidftmac100_rxdes_set_dma_own(structftmac100_rxdes*rxdes)+{+/* clear status bits */+rxdes->rxdes0=FTMAC100_RXDES0_RXDMA_OWN;+}++staticinlineintftmac100_rxdes_rx_error(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RX_ERR;+}++staticinlineintftmac100_rxdes_crc_error(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_CRC_ERR;+}++staticinlineintftmac100_rxdes_frame_too_long(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_FTL;+}++staticinlineintftmac100_rxdes_runt(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RUNT;+}++staticinlineintftmac100_rxdes_odd_nibble(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RX_ODD_NB;+}++staticinlineunsignedintftmac100_rxdes_frame_length(+structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RFL;+}++staticinlineintftmac100_rxdes_multicast(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_MULTICAST;+}++staticinlinevoidftmac100_rxdes_set_buffer_size(structftmac100_rxdes*rxdes,+unsignedintsize)+{+rxdes->rxdes1=(rxdes->rxdes1&FTMAC100_RXDES1_EDORR)|+FTMAC100_RXDES1_RXBUF_SIZE(size);+}++staticinlinevoidftmac100_rxdes_set_end_of_ring(structftmac100_rxdes*rxdes)+{+rxdes->rxdes1|=FTMAC100_RXDES1_EDORR;+}++staticinlinevoidftmac100_rxdes_set_dma_addr(structftmac100_rxdes*rxdes,+dma_addr_taddr)+{+rxdes->rxdes2=addr;+}++staticinlinedma_addr_tftmac100_rxdes_get_dma_addr(+structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes2;+}++/* rxdes3 is not used by hardware, we use it to keep track of buffer */+staticinlinevoidftmac100_rxdes_set_va(structftmac100_rxdes*rxdes,+void*addr)+{+rxdes->rxdes3=(unsignedint)addr;+}++staticinlinevoid*ftmac100_rxdes_get_va(structftmac100_rxdes*rxdes)+{+return(void*)rxdes->rxdes3;+}++/******************************************************************************+*internalfunctions(receive)+*****************************************************************************/+staticinlineintftmac100_next_rx_pointer(intpointer)+{+return(pointer+1)&(RX_QUEUE_ENTRIES-1);+}++staticinlinevoidftmac100_rx_pointer_advance(structftmac100*priv)+{+priv->rx_pointer=ftmac100_next_rx_pointer(priv->rx_pointer);+}++staticinlinestructftmac100_rxdes*ftmac100_current_rxdes(+structftmac100*priv)+{+return&priv->descs->rxdes[priv->rx_pointer];+}++staticstructftmac100_rxdes*ftmac100_rx_locate_first_segment(+structftmac100*priv)+{+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);++while(!ftmac100_rxdes_owned_by_dma(rxdes)){+if(ftmac100_rxdes_first_segment(rxdes))+returnrxdes;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}++returnNULL;+}++staticintftmac100_rx_packet_error(structftmac100*priv,+structftmac100_rxdes*rxdes)+{+structdevice*dev=&priv->netdev->dev;+interror=0;++if(unlikely(ftmac100_rxdes_rx_error(rxdes))){+if(printk_ratelimit())+dev_info(dev,"rx err\n");++priv->stats.rx_errors++;+error=1;+}++if(unlikely(ftmac100_rxdes_crc_error(rxdes))){+if(printk_ratelimit())+dev_info(dev,"rx crc err\n");++priv->stats.rx_crc_errors++;+error=1;+}++if(unlikely(ftmac100_rxdes_frame_too_long(rxdes))){+if(printk_ratelimit())+dev_info(dev,"rx frame too long\n");++priv->stats.rx_length_errors++;+error=1;+}++if(unlikely(ftmac100_rxdes_runt(rxdes))){+if(printk_ratelimit())+dev_info(dev,"rx runt\n");++priv->stats.rx_length_errors++;+error=1;+}++if(unlikely(ftmac100_rxdes_odd_nibble(rxdes))){+if(printk_ratelimit())+dev_info(dev,"rx odd nibble\n");++priv->stats.rx_length_errors++;+error=1;+}++returnerror;+}++staticvoidftmac100_rx_drop_packet(structftmac100*priv)+{+structdevice*dev=&priv->netdev->dev;+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);+intdone=0;++if(printk_ratelimit())+dev_dbg(dev,"drop packet %p\n",rxdes);++do{+if(ftmac100_rxdes_last_segment(rxdes))+done=1;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}while(!done&&!ftmac100_rxdes_owned_by_dma(rxdes));++priv->stats.rx_dropped++;+}++staticintftmac100_rx_packet(structftmac100*priv,int*processed)+{+structnet_device*netdev=priv->netdev;+structdevice*dev=&netdev->dev;+unsignedlongflags;+structftmac100_rxdes*rxdes;+structsk_buff*skb;+intlength;+intcopied=0;+intdone=0;++spin_lock_irqsave(&priv->rx_lock,flags);+rxdes=ftmac100_rx_locate_first_segment(priv);+spin_unlock_irqrestore(&priv->rx_lock,flags);+if(!rxdes)+return0;++if(unlikely(ftmac100_rx_packet_error(priv,rxdes))){+spin_lock_irqsave(&priv->rx_lock,flags);+ftmac100_rx_drop_packet(priv);+spin_unlock_irqrestore(&priv->rx_lock,flags);+return1;+}++/* start processing */++length=ftmac100_rxdes_frame_length(rxdes);++skb=dev_alloc_skb(length+NET_IP_ALIGN);+if(unlikely(!skb)){+if(printk_ratelimit())+dev_err(dev,"rx skb alloc failed\n");++spin_lock_irqsave(&priv->rx_lock,flags);+ftmac100_rx_drop_packet(priv);+spin_unlock_irqrestore(&priv->rx_lock,flags);+return1;+}++if(unlikely(ftmac100_rxdes_multicast(rxdes)))+priv->stats.multicast++;++skb_reserve(skb,NET_IP_ALIGN);++do{+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*buf=ftmac100_rxdes_get_va(rxdes);+intsize;++size=min(length-copied,RX_BUF_SIZE);++dma_sync_single_for_cpu(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);+memcpy(skb_put(skb,size),buf,size);++copied+=size;++if(ftmac100_rxdes_last_segment(rxdes))+done=1;++dma_sync_single_for_device(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);++spin_lock_irqsave(&priv->rx_lock,flags);++ftmac100_rxdes_set_dma_own(rxdes);++ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);++spin_unlock_irqrestore(&priv->rx_lock,flags);+}while(!done&&copied<length);++skb->protocol=eth_type_trans(skb,netdev);++/* push packet to protocol stack */++#ifdef USE_NAPI+netif_receive_skb(skb);+#else+netif_rx(skb);+#endif++netdev->last_rx=jiffies;++priv->stats.rx_packets++;+priv->stats.rx_bytes+=skb->len;++(*processed)++;++return1;+}++/******************************************************************************+*internalfunctions(transmitdescriptor)+*****************************************************************************/+staticinlinevoidftmac100_txdes_reset(structftmac100_txdes*txdes)+{+/* clear all except end of ring bit */+txdes->txdes0=0;+txdes->txdes1&=FTMAC100_TXDES1_EDOTR;+txdes->txdes2=0;+txdes->txdes3=0;+}++staticinlineintftmac100_txdes_owned_by_dma(structftmac100_txdes*txdes)+{+returntxdes->txdes0&FTMAC100_TXDES0_TXDMA_OWN;+}++staticinlinevoidftmac100_txdes_set_dma_own(structftmac100_txdes*txdes)+{+txdes->txdes0|=FTMAC100_TXDES0_TXDMA_OWN;+}++staticinlineintftmac100_txdes_excessive_collision(+structftmac100_txdes*txdes)+{+returntxdes->txdes0&FTMAC100_TXDES0_TXPKT_EXSCOL;+}++staticinlineintftmac100_txdes_late_collision(structftmac100_txdes*txdes)+{+returntxdes->txdes0&FTMAC100_TXDES0_TXPKT_LATECOL;+}++staticinlinevoidftmac100_txdes_set_end_of_ring(structftmac100_txdes*txdes)+{+txdes->txdes1|=FTMAC100_TXDES1_EDOTR;+}++staticinlinevoidftmac100_txdes_set_first_segment(+structftmac100_txdes*txdes)+{+txdes->txdes1|=FTMAC100_TXDES1_FTS;+}++staticinlinevoidftmac100_txdes_set_last_segment(structftmac100_txdes*txdes)+{+txdes->txdes1|=FTMAC100_TXDES1_LTS;+}++staticinlinevoidftmac100_txdes_set_txint(structftmac100_txdes*txdes)+{+txdes->txdes1|=FTMAC100_TXDES1_TXIC;+}++staticinlinevoidftmac100_txdes_set_buffer_size(structftmac100_txdes*txdes,+unsignedintlen)+{+txdes->txdes1|=FTMAC100_TXDES1_TXBUF_SIZE(len);+}++staticinlinevoidftmac100_txdes_set_dma_addr(structftmac100_txdes*txdes,+dma_addr_taddr)+{+txdes->txdes2=addr;+}++staticinlinedma_addr_tftmac100_txdes_get_dma_addr(+structftmac100_txdes*txdes)+{+returntxdes->txdes2;+}++/* txdes3 is not used by hardware, we use it to keep track of socket buffer */+staticinlinevoidftmac100_txdes_set_skb(structftmac100_txdes*txdes,+structsk_buff*skb)+{+txdes->txdes3=(unsignedint)skb;+}++staticinlinestructsk_buff*ftmac100_txdes_get_skb(+structftmac100_txdes*txdes)+{+return(structsk_buff*)txdes->txdes3;+}++/******************************************************************************+*internalfunctions(transmit)+*****************************************************************************/+staticinlineintftmac100_next_tx_pointer(intpointer)+{+return(pointer+1)&(TX_QUEUE_ENTRIES-1);+}++staticinlinevoidftmac100_tx_pointer_advance(structftmac100*priv)+{+priv->tx_pointer=ftmac100_next_tx_pointer(priv->tx_pointer);+}++staticinlinevoidftmac100_tx_clean_pointer_advance(structftmac100*priv)+{+priv->tx_clean_pointer=+ftmac100_next_tx_pointer(priv->tx_clean_pointer);+}++staticinlinestructftmac100_txdes*ftmac100_current_txdes(+structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_pointer];+}++staticinlinestructftmac100_txdes*ftmac100_current_clean_txdes(+structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_clean_pointer];+}++staticintftmac100_tx_complete_packet(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+structftmac100_txdes*txdes;+structsk_buff*skb;+dma_addr_tmap;++if(priv->tx_pending==0)+return0;++txdes=ftmac100_current_clean_txdes(priv);++if(ftmac100_txdes_owned_by_dma(txdes))+return0;++skb=ftmac100_txdes_get_skb(txdes);+map=ftmac100_txdes_get_dma_addr(txdes);++if(unlikely(ftmac100_txdes_excessive_collision(txdes)||+ftmac100_txdes_late_collision(txdes))){+/*+*packettransmittedtoethernetlostduetolatecollision+*orexcessivecollision+*/+priv->stats.tx_aborted_errors++;+}else{+priv->stats.tx_packets++;+priv->stats.tx_bytes+=skb->len;+}++dma_unmap_single(priv->dev,map,skb_headlen(skb),DMA_TO_DEVICE);++dev_kfree_skb_irq(skb);++ftmac100_txdes_reset(txdes);++ftmac100_tx_clean_pointer_advance(priv);++priv->tx_pending--;+netif_wake_queue(netdev);++return1;+}++staticvoidftmac100_tx_complete(structftmac100*priv)+{+unsignedlongflags;++spin_lock_irqsave(&priv->tx_lock,flags);+while(ftmac100_tx_complete_packet(priv))+;+spin_unlock_irqrestore(&priv->tx_lock,flags);+}++staticintftmac100_xmit(structftmac100*priv,structsk_buff*skb,+dma_addr_tmap)+{+structnet_device*netdev=priv->netdev;+structdevice*dev=&netdev->dev;+structftmac100_txdes*txdes;+unsignedintlen=(skb->len<ETH_ZLEN)?ETH_ZLEN:skb->len;+unsignedlongflags;++txdes=ftmac100_current_txdes(priv);+ftmac100_tx_pointer_advance(priv);++/* setup TX descriptor */++spin_lock_irqsave(&priv->tx_lock,flags);+ftmac100_txdes_set_skb(txdes,skb);+ftmac100_txdes_set_dma_addr(txdes,map);++ftmac100_txdes_set_first_segment(txdes);+ftmac100_txdes_set_last_segment(txdes);+ftmac100_txdes_set_txint(txdes);+ftmac100_txdes_set_buffer_size(txdes,len);++priv->tx_pending++;+if(priv->tx_pending==TX_QUEUE_ENTRIES){+if(printk_ratelimit())+dev_info(dev,"tx queue full\n");++netif_stop_queue(netdev);+}++/* start transmit */++wmb();+ftmac100_txdes_set_dma_own(txdes);+spin_unlock_irqrestore(&priv->tx_lock,flags);++spin_lock_irqsave(&priv->hw_lock,flags);+ftmac100_txdma_start_polling(priv);+spin_unlock_irqrestore(&priv->hw_lock,flags);+netdev->trans_start=jiffies;++returnNETDEV_TX_OK;+}++/******************************************************************************+*internalfunctions(buffer)+*****************************************************************************/+staticvoidftmac100_free_buffers(structftmac100*priv)+{+inti;++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*page=ftmac100_rxdes_get_va(rxdes);++if(d)+dma_unmap_single(priv->dev,d,PAGE_SIZE,+DMA_FROM_DEVICE);++if(page!=NULL)+free_page((unsignedlong)page);+}++for(i=0;i<TX_QUEUE_ENTRIES;i++){+structftmac100_txdes*txdes=&priv->descs->txdes[i];+structsk_buff*skb=ftmac100_txdes_get_skb(txdes);++if(skb){+dma_addr_tmap;++map=ftmac100_txdes_get_dma_addr(txdes);+dma_unmap_single(priv->dev,map,skb_headlen(skb),+DMA_TO_DEVICE);+dev_kfree_skb(skb);+}+}++dma_free_coherent(priv->dev,sizeof(structftmac100_descs),+priv->descs,priv->descs_dma_addr);+}++staticintftmac100_alloc_buffers(structftmac100*priv)+{+inti;++priv->descs=dma_alloc_coherent(priv->dev,+sizeof(structftmac100_descs),&priv->descs_dma_addr,+GFP_KERNEL|GFP_DMA);+if(priv->descs==NULL)+return-ENOMEM;++memset(priv->descs,0,sizeof(structftmac100_descs));++/* initialize RX ring */++ftmac100_rxdes_set_end_of_ring(+&priv->descs->rxdes[RX_QUEUE_ENTRIES-1]);++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+void*page;+dma_addr_td;++page=(void*)__get_free_page(GFP_KERNEL|GFP_DMA);+if(page==NULL)+gotoerr;++d=dma_map_single(priv->dev,page,PAGE_SIZE,+DMA_FROM_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,d))){+free_page((unsignedlong)page);+gotoerr;+}++/*+*Thehardwareenforcesasub-2Kmaximumpacketsize,sowe+*puttwobuffersoneveryhardwarepage.+*/+ftmac100_rxdes_set_va(rxdes,page);+ftmac100_rxdes_set_va(rxdes+1,page+PAGE_SIZE/2);++ftmac100_rxdes_set_dma_addr(rxdes,d);+ftmac100_rxdes_set_dma_addr(rxdes+1,d+PAGE_SIZE/2);++ftmac100_rxdes_set_buffer_size(rxdes,RX_BUF_SIZE);+ftmac100_rxdes_set_buffer_size(rxdes+1,RX_BUF_SIZE);++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rxdes_set_dma_own(rxdes+1);+}++/* initialize TX ring */++ftmac100_txdes_set_end_of_ring(+&priv->descs->txdes[TX_QUEUE_ENTRIES-1]);+return0;++err:+ftmac100_free_buffers(priv);+return-ENOMEM;+}++/******************************************************************************+*structmii_if_infofunctions+*****************************************************************************/+staticintftmac100_mdio_read(structnet_device*netdev,intphy_id,intreg)+{+structftmac100*priv=netdev_priv(netdev);+structdevice*dev=&netdev->dev;+unsignedlongflags;+intphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIRD;++spin_lock_irqsave(&priv->hw_lock,flags);+iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);+spin_unlock_irqrestore(&priv->hw_lock,flags);++for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIRD)==0)+returnphycr&FTMAC100_PHYCR_MIIRDATA;++usleep_range(100,1000);+}++dev_err(dev,"mdio read timed out\n");+return0xffff;+}++staticvoidftmac100_mdio_write(structnet_device*netdev,intphy_id,intreg,+intdata)+{+structftmac100*priv=netdev_priv(netdev);+structdevice*dev=&netdev->dev;+unsignedlongflags;+intphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIWR;++data=FTMAC100_PHYWDATA_MIIWDATA(data);++spin_lock_irqsave(&priv->hw_lock,flags);+iowrite32(data,priv->base+FTMAC100_OFFSET_PHYWDATA);+iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);+spin_unlock_irqrestore(&priv->hw_lock,flags);++for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIWR)==0)+return;++usleep_range(100,1000);+}++dev_err(dev,"mdio write timed out\n");+}++/******************************************************************************+*structethtool_opsfunctions+*****************************************************************************/+staticvoidftmac100_get_drvinfo(structnet_device*netdev,+structethtool_drvinfo*info)+{+strcpy(info->driver,DRV_NAME);+strcpy(info->version,DRV_VERSION);+strcpy(info->bus_info,dev_name(&netdev->dev));+}++staticintftmac100_get_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_gset(&priv->mii,cmd);+}++staticintftmac100_set_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_sset(&priv->mii,cmd);+}++staticintftmac100_nway_reset(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_nway_restart(&priv->mii);+}++staticu32ftmac100_get_link(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_link_ok(&priv->mii);+}++staticstructethtool_opsftmac100_ethtool_ops={+.set_settings=ftmac100_set_settings,+.get_settings=ftmac100_get_settings,+.get_drvinfo=ftmac100_get_drvinfo,+.nway_reset=ftmac100_nway_reset,+.get_link=ftmac100_get_link,+};++/******************************************************************************+*interrupthandler+*****************************************************************************/+staticirqreturn_tftmac100_interrupt(intirq,void*dev_id)+{+structnet_device*netdev=dev_id;+structdevice*dev=&netdev->dev;+structftmac100*priv=netdev_priv(netdev);+unsignedlongflags;+unsignedintstatus;+unsignedintimr;++spin_lock_irqsave(&priv->hw_lock,flags);+status=ioread32(priv->base+FTMAC100_OFFSET_ISR);+imr=ioread32(priv->base+FTMAC100_OFFSET_IMR);+spin_unlock_irqrestore(&priv->hw_lock,flags);++status&=imr;+if(status&(FTMAC100_INT_RPKT_FINISH|FTMAC100_INT_NORXBUF)){+/*+*FTMAC100_INT_RPKT_FINISH:+*RXDMAhasreceivedpacketsintoRXbuffersuccessfully+*+*FTMAC100_INT_NORXBUF:+*RXbufferunavailable+*/+#ifdef USE_NAPI+/* Disable interrupts for polling */+ftmac100_disable_rxint(priv);++napi_schedule(&priv->napi);+#else+intrx=0;++while(ftmac100_rx_packet(priv,&rx))+;+#endif+}++if(status&FTMAC100_INT_NORXBUF){+/* RX buffer unavailable */+if(printk_ratelimit())+dev_info(dev,"INT_NORXBUF\n");++priv->stats.rx_over_errors++;+}++if(status&(FTMAC100_INT_XPKT_OK|FTMAC100_INT_XPKT_LOST)){+/*+*FTMAC100_INT_XPKT_OK:+*packettransmittedtoethernetsuccessfully+*+*FTMAC100_INT_XPKT_LOST:+*packettransmittedtoethernetlostduetolate+*collisionorexcessivecollision+*/+ftmac100_tx_complete(priv);+}++if(status&FTMAC100_INT_RPKT_LOST){+/* received packet lost due to RX FIFO full */+if(printk_ratelimit())+dev_info(dev,"INT_RPKT_LOST\n");++priv->stats.rx_fifo_errors++;+}++if(status&FTMAC100_INT_AHB_ERR){+/* AHB error */+if(printk_ratelimit())+dev_info(dev,"INT_AHB_ERR\n");++/* do nothing */+}++if(status&FTMAC100_INT_PHYSTS_CHG){+/* PHY link status change */+if(printk_ratelimit())+dev_info(dev,"INT_PHYSTS_CHG\n");++mii_check_link(&priv->mii);+}++returnIRQ_HANDLED;+}++/******************************************************************************+*structnapi_structfunctions+*****************************************************************************/+#ifdef USE_NAPI+staticintftmac100_poll(structnapi_struct*napi,intbudget)+{+structftmac100*priv=container_of(napi,structftmac100,napi);+intretry;+intrx=0;++do{+retry=ftmac100_rx_packet(priv,&rx);+}while(retry&&rx<budget);++if(!retry||rx<budget){+/* stop polling */+napi_complete(napi);+ftmac100_enable_all_int(priv);+}++returnrx;+}+#endif++/******************************************************************************+*structnet_device_opsfunctions+*****************************************************************************/+staticintftmac100_open(structnet_device*netdev)+{+structdevice*dev=&netdev->dev;+structftmac100*priv=netdev_priv(netdev);+interr;++err=ftmac100_alloc_buffers(priv);+if(err){+dev_err(dev,"failed to allocate buffers\n");+gotoerr_alloc;+}++err=request_irq(priv->irq,ftmac100_interrupt,0,netdev->name,+netdev);+if(err){+dev_err(dev,"failed to request irq %d\n",priv->irq);+gotoerr_irq;+}++priv->rx_pointer=0;+priv->tx_clean_pointer=0;+priv->tx_pointer=0;+spin_lock_init(&priv->hw_lock);+spin_lock_init(&priv->rx_lock);+spin_lock_init(&priv->tx_lock);+priv->tx_pending=0;++err=ftmac100_start_hw(priv);+if(err)+gotoerr_hw;++#ifdef USE_NAPI+napi_enable(&priv->napi);+#endif+netif_start_queue(netdev);++ftmac100_enable_all_int(priv);++return0;++err_hw:+free_irq(priv->irq,netdev);+err_irq:+ftmac100_free_buffers(priv);+err_alloc:+returnerr;+}++staticintftmac100_stop(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);++ftmac100_disable_all_int(priv);+netif_stop_queue(netdev);+#ifdef USE_NAPI+napi_disable(&priv->napi);+#endif+ftmac100_stop_hw(priv);+free_irq(priv->irq,netdev);+ftmac100_free_buffers(priv);++return0;+}++staticintftmac100_hard_start_xmit(structsk_buff*skb,+structnet_device*netdev)+{+structdevice*dev=&netdev->dev;+structftmac100*priv=netdev_priv(netdev);+dma_addr_tmap;++if(unlikely(skb->len>MAX_PKT_SIZE)){+if(printk_ratelimit())+dev_dbg(dev,"tx packet too big\n");++priv->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++map=dma_map_single(priv->dev,skb->data,skb_headlen(skb),+DMA_TO_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,map))){+/* drop packet */+if(printk_ratelimit())+dev_err(dev,"map socket buffer failed\n");++priv->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++returnftmac100_xmit(priv,skb,map);+}++staticstructnet_device_stats*ftmac100_get_stats(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+return&priv->stats;+}++/* optional */+staticintftmac100_do_ioctl(structnet_device*netdev,structifreq*ifr,+intcmd)+{+structftmac100*priv=netdev_priv(netdev);+structmii_ioctl_data*data=if_mii(ifr);++returngeneric_mii_ioctl(&priv->mii,data,cmd,NULL);+}++staticstructnet_device_opsftmac100_netdev_ops={+.ndo_open=ftmac100_open,+.ndo_stop=ftmac100_stop,+.ndo_start_xmit=ftmac100_hard_start_xmit,+.ndo_set_mac_address=eth_mac_addr,+.ndo_validate_addr=eth_validate_addr,+.ndo_get_stats=ftmac100_get_stats,+.ndo_do_ioctl=ftmac100_do_ioctl,+};++/******************************************************************************+*structplatform_driverfunctions+*****************************************************************************/+staticintftmac100_remove(structplatform_device*pdev)+{+structnet_device*netdev;+structftmac100*priv;++netdev=platform_get_drvdata(pdev);+if(netdev==NULL)+return0;++platform_set_drvdata(pdev,NULL);++priv=netdev_priv(netdev);++unregister_netdev(netdev);++if(priv->base!=NULL)+iounmap(priv->base);++if(priv->res!=NULL)+release_resource(priv->res);++free_netdev(netdev);++return0;+}++staticintftmac100_probe(structplatform_device*pdev)+{+structresource*res;+intirq;+structnet_device*netdev;+structftmac100*priv;+interr;++if(!pdev)+return-ENODEV;++res=platform_get_resource(pdev,IORESOURCE_MEM,0);+if(!res)+return-ENXIO;++irq=platform_get_irq(pdev,0);+if(irq<0)+returnirq;++/* setup net_device */++netdev=alloc_etherdev(sizeof(structftmac100));+if(netdev==NULL){+err=-ENOMEM;+gotoerr_out;+}++SET_NETDEV_DEV(netdev,&pdev->dev);+SET_ETHTOOL_OPS(netdev,&ftmac100_ethtool_ops);+netdev->netdev_ops=&ftmac100_netdev_ops;++platform_set_drvdata(pdev,netdev);++/* setup private data */++priv=netdev_priv(netdev);+priv->netdev=netdev;+priv->dev=&pdev->dev;++#ifdef USE_NAPI+/* initialize NAPI */+netif_napi_add(netdev,&priv->napi,ftmac100_poll,64);+#endif++/* map io memory */++priv->res=request_mem_region(res->start,res->end-res->start,+dev_name(&pdev->dev));+if(priv->res==NULL){+dev_err(&pdev->dev,"Could not reserve memory region\n");+err=-ENOMEM;+gotoerr_out;+}++priv->base=ioremap(res->start,res->end-res->start);+if(priv->base==NULL){+dev_err(&pdev->dev,"Failed to ioremap ethernet registers\n");+err=-EIO;+gotoerr_out;+}++priv->irq=irq;++/* initialize struct mii_if_info */++priv->mii.phy_id=0;+priv->mii.phy_id_mask=0x1f;+priv->mii.reg_num_mask=0x1f;+priv->mii.dev=netdev;+priv->mii.mdio_read=ftmac100_mdio_read;+priv->mii.mdio_write=ftmac100_mdio_write;++/* register network device */++err=register_netdev(netdev);+if(err){+dev_err(&pdev->dev,"Failed to register netdev\n");+gotoerr_out;+}++dev_info(&netdev->dev,"irq %d, mapped at %p\n",priv->irq,priv->base);++if(is_zero_ether_addr(netdev->dev_addr)){+random_ether_addr(netdev->dev_addr);+dev_info(&netdev->dev,"generated random MAC address "+"%.2x:%.2x:%.2x:%.2x:%.2x:%.2x.\n",+netdev->dev_addr[0],netdev->dev_addr[1],+netdev->dev_addr[2],netdev->dev_addr[3],+netdev->dev_addr[4],netdev->dev_addr[5]);+}++return0;++err_out:+ftmac100_remove(pdev);+returnerr;+}++staticstructplatform_driverftmac100_driver={+.probe=ftmac100_probe,+.remove=ftmac100_remove,+.driver={+.name=DRV_NAME,+.owner=THIS_MODULE,+},+};++/******************************************************************************+*initialization/finalization+*****************************************************************************/+staticint__initftmac100_init(void)+{+printk(KERN_INFO"Loading "DRV_NAME": version "DRV_VERSION" ...\n");+returnplatform_driver_register(&ftmac100_driver);+}++staticvoid__exitftmac100_exit(void)+{+platform_driver_unregister(&ftmac100_driver);+}++module_init(ftmac100_init);+module_exit(ftmac100_exit);++MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");+MODULE_DESCRIPTION("FTMAC100 driver");+MODULE_LICENSE("GPL");
From: Ben Hutchings <hidden> Date: 2011-01-13 14:03:14
On Thu, 2011-01-13 at 19:49 +0800, Po-Yu Chuang wrote:
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC including
Faraday A320 and Andes AG101.
There is a net_device_stats structure in the net_device structure; you
should normally use that instead of adding another one.
[...]
quoted hunk
+static int ftmac100_reset(struct ftmac100 *priv)+{+ struct device *dev = &priv->netdev->dev;+ unsigned long flags;+ int i;++ /* NOTE: reset clears all registers */++ spin_lock_irqsave(&priv->hw_lock, flags);+ iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);+ spin_unlock_irqrestore(&priv->hw_lock, flags);++ for (i = 0; i < 5; i++) {+ int maccr;++ spin_lock_irqsave(&priv->hw_lock, flags);+ maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);+ spin_unlock_irqrestore(&priv->hw_lock, flags);+ if (!(maccr & FTMAC100_MACCR_SW_RST)) {+ /*+ * FTMAC100_MACCR_SW_RST cleared does not indicate+ * that hardware reset completed (what the f*ck).+ * We still need to wait for a while.+ */+ usleep_range(500, 1000);
Sleeping here means this must be running in process context. But you
used spin_lock_irqsave() above which implies you're not sure what the
context is. One of these must be wrong.
I wonder whether hw_lock is even needed; you seem to acquire and release
it around each PIO (read or write). This should be unnecessary since
each PIO is atomic.
[...]
quoted hunk
+static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)+{+ struct net_device *netdev = priv->netdev;+ struct device *dev = &netdev->dev;+ unsigned long flags;+ struct ftmac100_rxdes *rxdes;+ struct sk_buff *skb;+ int length;+ int copied = 0;+ int done = 0;++ spin_lock_irqsave(&priv->rx_lock, flags);+ rxdes = ftmac100_rx_locate_first_segment(priv);+ spin_unlock_irqrestore(&priv->rx_lock, flags);+ if (!rxdes)+ return 0;++ if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {+ spin_lock_irqsave(&priv->rx_lock, flags);+ ftmac100_rx_drop_packet(priv);+ spin_unlock_irqrestore(&priv->rx_lock, flags);
I think you can also get rid of rx_lock; it's only used in the RX data
path which is already serialised by NAPI.
[...]
Don't set trans_start; the networking core takes care of it now.
[...]
quoted hunk
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)+{+ int i;++ priv->descs = dma_alloc_coherent(priv->dev,+ sizeof(struct ftmac100_descs), &priv->descs_dma_addr,+ GFP_KERNEL | GFP_DMA);
On x86, GFP_DMA means the memory must be within the ISA DMA range
(< 16 MB). I don't know quite what it means on ARM but it may not be
what you want.
[...]
quoted hunk
+/******************************************************************************+ * interrupt handler+ *****************************************************************************/+static irqreturn_t ftmac100_interrupt(int irq, void *dev_id)+{+ struct net_device *netdev = dev_id;+ struct device *dev = &netdev->dev;+ struct ftmac100 *priv = netdev_priv(netdev);+ unsigned long flags;+ unsigned int status;+ unsigned int imr;++ spin_lock_irqsave(&priv->hw_lock, flags);+ status = ioread32(priv->base + FTMAC100_OFFSET_ISR);+ imr = ioread32(priv->base + FTMAC100_OFFSET_IMR);+ spin_unlock_irqrestore(&priv->hw_lock, flags);++ status &= imr;+ if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {+ /*+ * FTMAC100_INT_RPKT_FINISH:+ * RX DMA has received packets into RX buffer successfully+ *+ * FTMAC100_INT_NORXBUF:+ * RX buffer unavailable+ */+#ifdef USE_NAPI+ /* Disable interrupts for polling */+ ftmac100_disable_rxint(priv);++ napi_schedule(&priv->napi);+#else+ int rx = 0;++ while (ftmac100_rx_packet(priv, &rx))+ ;+#endif+ }++ if (status & FTMAC100_INT_NORXBUF) {+ /* RX buffer unavailable */+ if (printk_ratelimit())+ dev_info(dev, "INT_NORXBUF\n");++ priv->stats.rx_over_errors++;+ }++ if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {+ /*+ * FTMAC100_INT_XPKT_OK:+ * packet transmitted to ethernet successfully+ *+ * FTMAC100_INT_XPKT_LOST:+ * packet transmitted to ethernet lost due to late+ * collision or excessive collision+ */+ ftmac100_tx_complete(priv);+ }
TX completions should also be handled through NAPI if possible.
[...]
[...]
There should be a netif_napi_del() before this.
A general comment: please use netdev_err(), netdev_info() etc. for
logging. This ensures that both the platform device address and the
network device name appears in the log messages.
Ben.
--
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.
From: Eric Dumazet <hidden> Date: 2011-01-13 14:22:46
Le jeudi 13 janvier 2011 à 19:49 +0800, Po-Yu Chuang a écrit :
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1341 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 +++++++
4 files changed, 1531 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ftmac100.c
create mode 100644 drivers/net/ftmac100.h
Hi
1) please use netdev_alloc_skb_ip_align() instead of dev_alloc_skb() +
skb_reserve(skb, NET_IP_ALIGN);
2) Dont include a "struct net_device_stats stats" in struct ftmac100,
you can use the one included in struct net_device
(You can then remove ftmac100_get_stats())
3) Dont "netdev->last_rx = jiffies;" : This is not driver job.
Ditto for trans_start
4) You must comment why wmb() is needed in ftmac100_xmit()
5) Why isnt NAPI used too for TX completions ?
BTW, I am not sure we want a define. All new drivers must use NAPI.
6) Use is_valid_ether_addr() instead of is_zero_ether_addr() in
ftmac100_probe()
7) "static struct ethtool_ops ftmac100_ethtool_ops" should be const :
"static const struct ethtool_ops ftmac100_ethtool_ops"
Ditto for :
"static struct net_device_ops ftmac100_netdev_ops"
8) Why an interrupt handler (ftmac100_interrupt()) needs to block
interrupts with spin_lock_irqsave(&priv->hw_lock, flags); ?
9) Instead of dev_info(&netdev->dev ...) , please consider netdev_info()
From: Joe Perches <joe@perches.com> Date: 2011-01-13 15:40:00
On Thu, 2011-01-13 at 19:49 +0800, Po-Yu Chuang wrote:
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC including
Faraday A320 and Andes AG101.
A couple of trivial comments not already mentioned by others.
@@ -2014,6 +2014,15 @@ config BCM63XX_ENET This driver supports the ethernet MACs in the Broadcom 63xx MIPS chipset family (BCM63XX).+config FTMAC100+ tristate "Faraday FTMAC100 10/100 Ethernet support"+ depends on ARM+ select MII+ help+ This driver supports the FTMAC100 Ethernet controller from+ Faraday. It is used on Faraday A320, Andes AG101, AG101P+ and some other ARM/NDS32 SoC's.+
ARM specific net drivers are for now in drivers/net/arm/
Perhaps it's better to locate these files there?
+static int ftmac100_rx_packet_error(struct ftmac100 *priv,+ struct ftmac100_rxdes *rxdes)+{+ struct device *dev = &priv->netdev->dev;+ int error = 0;++ if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {+ if (printk_ratelimit())+ dev_info(dev, "rx err\n");
There are many printk_ratelimit() tests.
It's better to use net_ratelimit() or a local ratelimit_state
so there's less possible suppression of other subsystem
messages.
From: Andres Salomon <hidden> Date: 2011-01-13 16:30:10
On Thu, 13 Jan 2011 15:22:39 +0100
Eric Dumazet [off-list ref] wrote:
Le jeudi 13 janvier 2011 à 19:49 +0800, Po-Yu Chuang a écrit :
quoted
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1341
++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 +++++++ 4 files changed, 1531
insertions(+), 0 deletions(-) create mode 100644
drivers/net/ftmac100.c create mode 100644 drivers/net/ftmac100.h
Hi
[...]
9) Instead of dev_info(&netdev->dev ...) , please consider
netdev_info()
No one else mentioned it, so I'll add:
Don't explicitly inline functions unless they're in a header, or you
have a really good reason (and that reason should probably be described
in a comment). Otherwise, just leave off the 'inline' keyword; the
compiler is smart enough to decide whether a function should be inlined
or not.
Dear Ben,
On Thu, Jan 13, 2011 at 10:03 PM, Ben Hutchings
[off-list ref] wrote:
On Thu, 2011-01-13 at 19:49 +0800, Po-Yu Chuang wrote:
quoted
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC including
Faraday A320 and Andes AG101.
[...]
quoted
+#define USE_NAPI
All new network drivers should use NAPI only, so please remove the
definition of USE_NAPI and change the conditional code to use NAPI
always.
OK, fixed.
[...]
quoted
+ struct net_device_stats stats;
There is a net_device_stats structure in the net_device structure; you
should normally use that instead of adding another one.
OK, fixed.
[...]
quoted
+static int ftmac100_reset(struct ftmac100 *priv)+{+ struct device *dev = &priv->netdev->dev;+ unsigned long flags;+ int i;++ /* NOTE: reset clears all registers */++ spin_lock_irqsave(&priv->hw_lock, flags);+ iowrite32(FTMAC100_MACCR_SW_RST, priv->base + FTMAC100_OFFSET_MACCR);+ spin_unlock_irqrestore(&priv->hw_lock, flags);++ for (i = 0; i < 5; i++) {+ int maccr;++ spin_lock_irqsave(&priv->hw_lock, flags);+ maccr = ioread32(priv->base + FTMAC100_OFFSET_MACCR);+ spin_unlock_irqrestore(&priv->hw_lock, flags);+ if (!(maccr & FTMAC100_MACCR_SW_RST)) {+ /*+ * FTMAC100_MACCR_SW_RST cleared does not indicate+ * that hardware reset completed (what the f*ck).+ * We still need to wait for a while.+ */+ usleep_range(500, 1000);
Sleeping here means this must be running in process context. But you
used spin_lock_irqsave() above which implies you're not sure what the
context is. One of these must be wrong.
I wonder whether hw_lock is even needed; you seem to acquire and release
it around each PIO (read or write). This should be unnecessary since
each PIO is atomic.
OK, fixed.
I think you can also get rid of rx_lock; it's only used in the RX data
path which is already serialised by NAPI.
OK, fixed.
[...]
quoted
+ netdev->last_rx = jiffies;
Don't set last_rx; the networking core takes care of it now.
On x86, GFP_DMA means the memory must be within the ISA DMA range
(< 16 MB). I don't know quite what it means on ARM but it may not be
what you want.
On ARM, all 4G address space can be used by DMA (at least for all the
hardwares I have ever used). All memory pages are in DMA zone AFAIK.
I put GFP_DMA here just to be safe if there were any constraints.
By checking drivers in drivers/net/arm/, ep93xx_eth.c also uses this flag,
so I guess this is acceptable?
[...]
quoted
+ if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {+ /*+ * FTMAC100_INT_XPKT_OK:+ * packet transmitted to ethernet successfully+ *+ * FTMAC100_INT_XPKT_LOST:+ * packet transmitted to ethernet lost due to late+ * collision or excessive collision+ */+ ftmac100_tx_complete(priv);+ }
TX completions should also be handled through NAPI if possible.
These locks should be initialised in your probe function.
OK, fixed.
[...]
quoted
+ unregister_netdev(netdev);
There should be a netif_napi_del() before this.
OK, fixed.
A general comment: please use netdev_err(), netdev_info() etc. for
logging. This ensures that both the platform device address and the
network device name appears in the log messages.
OK, fixed.
Thanks a lot for your detailed review. I'll submit a new version ASAP.
Thanks,
Po-Yu Chuang
Dear Joe,
On Thu, Jan 13, 2011 at 11:39 PM, Joe Perches [off-list ref] wrote:
On Thu, 2011-01-13 at 19:49 +0800, Po-Yu Chuang wrote:
quoted
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC including
Faraday A320 and Andes AG101.
A couple of trivial comments not already mentioned by others.
This driver supports the ethernet MACs in the Broadcom 63xx
MIPS chipset family (BCM63XX).
+config FTMAC100
+ tristate "Faraday FTMAC100 10/100 Ethernet support"
+ depends on ARM
+ select MII
+ help
+ This driver supports the FTMAC100 Ethernet controller from
+ Faraday. It is used on Faraday A320, Andes AG101, AG101P
+ and some other ARM/NDS32 SoC's.
+
ARM specific net drivers are for now in drivers/net/arm/
Perhaps it's better to locate these files there?
This controller is used by not only ARM-base platforms, but also
NDS32-base ones.
NDS32 is an ISA designed by Andes tech. Although it is not yet in the mainline,
they plan to push their stuff to mainline and are working on that.
So... I don't know if it is better to put my driver to driver/net/arm/.
Should I leave my driver at driver/net/ or put it in drvier/net/arm/
and let Andes tech guys
move it out of driver/net/arm/ when they use it later?
quoted
+ if (unlikely(ftmac100_rxdes_rx_error(rxdes))) {+ if (printk_ratelimit())+ dev_info(dev, "rx err\n");
There are many printk_ratelimit() tests.
It's better to use net_ratelimit() or a local ratelimit_state
so there's less possible suppression of other subsystem
messages.
OK, fixed.
Thanks a lot for your absolutely non-trivial review. :-)
I'll submit a new version ASAP.
Thanks,
Po-Yu Chuang
Dear Andres,
On Fri, Jan 14, 2011 at 12:29 AM, Andres Salomon [off-list ref] wrote:
On Thu, 13 Jan 2011 15:22:39 +0100
Eric Dumazet [off-list ref] wrote:
quoted
Le jeudi 13 janvier 2011 à 19:49 +0800, Po-Yu Chuang a écrit :
No one else mentioned it, so I'll add:
Don't explicitly inline functions unless they're in a header, or you
have a really good reason (and that reason should probably be described
in a comment). Otherwise, just leave off the 'inline' keyword; the
compiler is smart enough to decide whether a function should be inlined
or not.
Dear Eric,
On Thu, Jan 13, 2011 at 10:22 PM, Eric Dumazet [off-list ref] wrote:
Le jeudi 13 janvier 2011 à 19:49 +0800, Po-Yu Chuang a écrit :
quoted
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
Hi
1) please use netdev_alloc_skb_ip_align() instead of dev_alloc_skb() +
skb_reserve(skb, NET_IP_ALIGN);
OK, fixed.
2) Dont include a "struct net_device_stats stats" in struct ftmac100,
you can use the one included in struct net_device
(You can then remove ftmac100_get_stats())
OK, fixed.
3) Dont "netdev->last_rx = jiffies;" : This is not driver job.
Ditto for trans_start
OK, fixed.
4) You must comment why wmb() is needed in ftmac100_xmit()
OK, comment added.
5) Why isnt NAPI used too for TX completions ?
BTW, I am not sure we want a define. All new drivers must use NAPI.
I'll study how to do that
6) Use is_valid_ether_addr() instead of is_zero_ether_addr() in
ftmac100_probe()
OK, fixed.
7) "static struct ethtool_ops ftmac100_ethtool_ops" should be const :
"static const struct ethtool_ops ftmac100_ethtool_ops"
Ditto for :
"static struct net_device_ops ftmac100_netdev_ops"
OK, both fixed.
8) Why an interrupt handler (ftmac100_interrupt()) needs to block
interrupts with spin_lock_irqsave(&priv->hw_lock, flags); ?
Fixed. Not a problem anymore since hw_lock is removed now.
9) Instead of dev_info(&netdev->dev ...) , please consider netdev_info()
OK, fixed.
Thanks a lot for your detailed review. I'll submit a new version ASAP.
Thanks,
Po-Yu Chuang
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts
As I said in the previous mail, this controller is used not only on ARM,
so I still leave it in drivers/net/. If there is any suggestion, please
let me know. Thanks
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1223 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 +++++++
4 files changed, 1413 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ftmac100.c
create mode 100644 drivers/net/ftmac100.h
@@ -0,0 +1,1223 @@+/*+*FaradayFTMAC10010/100Ethernet+*+*(C)Copyright2009-2011FaradayTechnology+*Po-YuChuang<ratbert@faraday-tech.com>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,writetotheFreeSoftware+*Foundation,Inc.,675MassAve,Cambridge,MA02139,USA.+*/++#include<linux/dma-mapping.h>+#include<linux/etherdevice.h>+#include<linux/ethtool.h>+#include<linux/init.h>+#include<linux/io.h>+#include<linux/kernel.h>+#include<linux/mii.h>+#include<linux/module.h>+#include<linux/moduleparam.h>+#include<linux/netdevice.h>+#include<linux/platform_device.h>++#include"ftmac100.h"++#define DRV_NAME "ftmac100"+#define DRV_VERSION "0.1"++#define RX_QUEUE_ENTRIES 128 /* must be power of 2 */+#define TX_QUEUE_ENTRIES 16 /* must be power of 2 */++#define MAX_PKT_SIZE 1518+#define RX_BUF_SIZE 2044 /* must be smaller than 0x7ff */++/******************************************************************************+*priveatedata+*****************************************************************************/+structftmac100_descs{+structftmac100_rxdesrxdes[RX_QUEUE_ENTRIES];+structftmac100_txdestxdes[TX_QUEUE_ENTRIES];+};++structftmac100{+structresource*res;+void*base;+intirq;++structftmac100_descs*descs;+dma_addr_tdescs_dma_addr;++unsignedintrx_pointer;+unsignedinttx_clean_pointer;+unsignedinttx_pointer;+unsignedinttx_pending;++spinlock_ttx_lock;++structnet_device*netdev;+structdevice*dev;+structnapi_structnapi;++structmii_if_infomii;+};++/******************************************************************************+*internalfunctions(hardwareregisteraccess)+*****************************************************************************/+staticvoidftmac100_enable_all_int(structftmac100*priv)+{+unsignedintimr;++imr=FTMAC100_INT_RPKT_FINISH|FTMAC100_INT_NORXBUF+|FTMAC100_INT_XPKT_OK|FTMAC100_INT_XPKT_LOST+|FTMAC100_INT_RPKT_LOST|FTMAC100_INT_AHB_ERR+|FTMAC100_INT_PHYSTS_CHG;+iowrite32(imr,priv->base+FTMAC100_OFFSET_IMR);+}++staticvoidftmac100_disable_all_int(structftmac100*priv)+{+iowrite32(0,priv->base+FTMAC100_OFFSET_IMR);+}++staticvoidftmac100_set_receive_ring_base(structftmac100*priv,+dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_RXR_BADR);+}++staticvoidftmac100_set_transmit_ring_base(structftmac100*priv,+dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_TXR_BADR);+}++staticvoidftmac100_txdma_start_polling(structftmac100*priv)+{+iowrite32(1,priv->base+FTMAC100_OFFSET_TXPD);+}++staticintftmac100_reset(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+inti;++/* NOTE: reset clears all registers */+iowrite32(FTMAC100_MACCR_SW_RST,priv->base+FTMAC100_OFFSET_MACCR);++for(i=0;i<5;i++){+intmaccr;++maccr=ioread32(priv->base+FTMAC100_OFFSET_MACCR);+if(!(maccr&FTMAC100_MACCR_SW_RST)){+/*+*FTMAC100_MACCR_SW_RSTcleareddoesnotindicate+*thathardwareresetcompleted(whatthef*ck).+*Westillneedtowaitforawhile.+*/+usleep_range(500,1000);+return0;+}++usleep_range(1000,10000);+}++netdev_err(netdev,"software reset failed\n");+return-EIO;+}++staticvoidftmac100_set_mac(structftmac100*priv,constunsignedchar*mac)+{+unsignedintmaddr=mac[0]<<8|mac[1];+unsignedintladdr=mac[2]<<24|mac[3]<<16|mac[4]<<8|mac[5];++iowrite32(maddr,priv->base+FTMAC100_OFFSET_MAC_MADR);+iowrite32(laddr,priv->base+FTMAC100_OFFSET_MAC_LADR);+}++staticintftmac100_start_hw(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+intmaccr;++if(ftmac100_reset(priv))+return-EIO;++/* setup ring buffer base registers */++ftmac100_set_receive_ring_base(priv,+priv->descs_dma_addr+offsetof(structftmac100_descs,rxdes));+ftmac100_set_transmit_ring_base(priv,+priv->descs_dma_addr+offsetof(structftmac100_descs,txdes));++iowrite32(FTMAC100_APTC_RXPOLL_CNT(1),+priv->base+FTMAC100_OFFSET_APTC);++ftmac100_set_mac(priv,netdev->dev_addr);++maccr=FTMAC100_MACCR_XMT_EN|+FTMAC100_MACCR_RCV_EN|+FTMAC100_MACCR_XDMA_EN|+FTMAC100_MACCR_RDMA_EN|+FTMAC100_MACCR_CRC_APD|+FTMAC100_MACCR_FULLDUP|+FTMAC100_MACCR_RX_RUNT|+FTMAC100_MACCR_RX_BROADPKT;++iowrite32(maccr,priv->base+FTMAC100_OFFSET_MACCR);+return0;+}++staticvoidftmac100_stop_hw(structftmac100*priv)+{+iowrite32(0,priv->base+FTMAC100_OFFSET_MACCR);+}++/******************************************************************************+*internalfunctions(receivedescriptor)+*****************************************************************************/+staticintftmac100_rxdes_first_segment(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_FRS;+}++staticintftmac100_rxdes_last_segment(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_LRS;+}++staticintftmac100_rxdes_owned_by_dma(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RXDMA_OWN;+}++staticvoidftmac100_rxdes_set_dma_own(structftmac100_rxdes*rxdes)+{+/* clear status bits */+rxdes->rxdes0=FTMAC100_RXDES0_RXDMA_OWN;+}++staticintftmac100_rxdes_rx_error(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RX_ERR;+}++staticintftmac100_rxdes_crc_error(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_CRC_ERR;+}++staticintftmac100_rxdes_frame_too_long(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_FTL;+}++staticintftmac100_rxdes_runt(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RUNT;+}++staticintftmac100_rxdes_odd_nibble(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RX_ODD_NB;+}++staticunsignedintftmac100_rxdes_frame_length(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_RFL;+}++staticintftmac100_rxdes_multicast(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&FTMAC100_RXDES0_MULTICAST;+}++staticvoidftmac100_rxdes_set_buffer_size(structftmac100_rxdes*rxdes,+unsignedintsize)+{+rxdes->rxdes1=(rxdes->rxdes1&FTMAC100_RXDES1_EDORR)|+FTMAC100_RXDES1_RXBUF_SIZE(size);+}++staticvoidftmac100_rxdes_set_end_of_ring(structftmac100_rxdes*rxdes)+{+rxdes->rxdes1|=FTMAC100_RXDES1_EDORR;+}++staticvoidftmac100_rxdes_set_dma_addr(structftmac100_rxdes*rxdes,+dma_addr_taddr)+{+rxdes->rxdes2=addr;+}++staticdma_addr_tftmac100_rxdes_get_dma_addr(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes2;+}++/* rxdes3 is not used by hardware, we use it to keep track of buffer */+staticvoidftmac100_rxdes_set_va(structftmac100_rxdes*rxdes,void*addr)+{+rxdes->rxdes3=(unsignedint)addr;+}++staticvoid*ftmac100_rxdes_get_va(structftmac100_rxdes*rxdes)+{+return(void*)rxdes->rxdes3;+}++/******************************************************************************+*internalfunctions(receive)+*****************************************************************************/+staticintftmac100_next_rx_pointer(intpointer)+{+return(pointer+1)&(RX_QUEUE_ENTRIES-1);+}++staticvoidftmac100_rx_pointer_advance(structftmac100*priv)+{+priv->rx_pointer=ftmac100_next_rx_pointer(priv->rx_pointer);+}++staticstructftmac100_rxdes*ftmac100_current_rxdes(structftmac100*priv)+{+return&priv->descs->rxdes[priv->rx_pointer];+}++staticstructftmac100_rxdes*ftmac100_rx_locate_first_segment(+structftmac100*priv)+{+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);++while(!ftmac100_rxdes_owned_by_dma(rxdes)){+if(ftmac100_rxdes_first_segment(rxdes))+returnrxdes;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}++returnNULL;+}++staticintftmac100_rx_packet_error(structftmac100*priv,+structftmac100_rxdes*rxdes)+{+structnet_device*netdev=priv->netdev;+interror=0;++if(unlikely(ftmac100_rxdes_rx_error(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx err\n");++netdev->stats.rx_errors++;+error=1;+}++if(unlikely(ftmac100_rxdes_crc_error(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx crc err\n");++netdev->stats.rx_crc_errors++;+error=1;+}++if(unlikely(ftmac100_rxdes_frame_too_long(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx frame too long\n");++netdev->stats.rx_length_errors++;+error=1;+}++if(unlikely(ftmac100_rxdes_runt(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx runt\n");++netdev->stats.rx_length_errors++;+error=1;+}++if(unlikely(ftmac100_rxdes_odd_nibble(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx odd nibble\n");++netdev->stats.rx_length_errors++;+error=1;+}++returnerror;+}++staticvoidftmac100_rx_drop_packet(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);+intdone=0;++if(net_ratelimit())+netdev_dbg(netdev,"drop packet %p\n",rxdes);++do{+if(ftmac100_rxdes_last_segment(rxdes))+done=1;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}while(!done&&!ftmac100_rxdes_owned_by_dma(rxdes));++netdev->stats.rx_dropped++;+}++staticintftmac100_rx_packet(structftmac100*priv,int*processed)+{+structnet_device*netdev=priv->netdev;+structftmac100_rxdes*rxdes;+structsk_buff*skb;+intlength;+intcopied=0;+intdone=0;++rxdes=ftmac100_rx_locate_first_segment(priv);+if(!rxdes)+return0;++length=ftmac100_rxdes_frame_length(rxdes);++netdev->stats.rx_packets++;+netdev->stats.rx_bytes+=length;++if(unlikely(ftmac100_rx_packet_error(priv,rxdes))){+ftmac100_rx_drop_packet(priv);+return1;+}++/* start processing */+skb=netdev_alloc_skb_ip_align(netdev,length);+if(unlikely(!skb)){+if(net_ratelimit())+netdev_err(netdev,"rx skb alloc failed\n");++ftmac100_rx_drop_packet(priv);+return1;+}++if(unlikely(ftmac100_rxdes_multicast(rxdes)))+netdev->stats.multicast++;++do{+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*buf=ftmac100_rxdes_get_va(rxdes);+intsize;++size=min(length-copied,RX_BUF_SIZE);++dma_sync_single_for_cpu(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);+memcpy(skb_put(skb,size),buf,size);++copied+=size;++if(ftmac100_rxdes_last_segment(rxdes))+done=1;++dma_sync_single_for_device(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);++ftmac100_rxdes_set_dma_own(rxdes);++ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}while(!done&&copied<length);++skb->protocol=eth_type_trans(skb,netdev);++/* push packet to protocol stack */+netif_receive_skb(skb);++(*processed)++;+return1;+}++/******************************************************************************+*internalfunctions(transmitdescriptor)+*****************************************************************************/+staticvoidftmac100_txdes_reset(structftmac100_txdes*txdes)+{+/* clear all except end of ring bit */+txdes->txdes0=0;+txdes->txdes1&=FTMAC100_TXDES1_EDOTR;+txdes->txdes2=0;+txdes->txdes3=0;+}++staticintftmac100_txdes_owned_by_dma(structftmac100_txdes*txdes)+{+returntxdes->txdes0&FTMAC100_TXDES0_TXDMA_OWN;+}++staticvoidftmac100_txdes_set_dma_own(structftmac100_txdes*txdes)+{+/*+*Makesuredmaownbitwillnotbesetbeforeanyother+*descriptorfiels.+*/+wmb();++txdes->txdes0|=FTMAC100_TXDES0_TXDMA_OWN;+}++staticintftmac100_txdes_excessive_collision(structftmac100_txdes*txdes)+{+returntxdes->txdes0&FTMAC100_TXDES0_TXPKT_EXSCOL;+}++staticintftmac100_txdes_late_collision(structftmac100_txdes*txdes)+{+returntxdes->txdes0&FTMAC100_TXDES0_TXPKT_LATECOL;+}++staticvoidftmac100_txdes_set_end_of_ring(structftmac100_txdes*txdes)+{+txdes->txdes1|=FTMAC100_TXDES1_EDOTR;+}++staticvoidftmac100_txdes_set_first_segment(structftmac100_txdes*txdes)+{+txdes->txdes1|=FTMAC100_TXDES1_FTS;+}++staticvoidftmac100_txdes_set_last_segment(structftmac100_txdes*txdes)+{+txdes->txdes1|=FTMAC100_TXDES1_LTS;+}++staticvoidftmac100_txdes_set_txint(structftmac100_txdes*txdes)+{+txdes->txdes1|=FTMAC100_TXDES1_TXIC;+}++staticvoidftmac100_txdes_set_buffer_size(structftmac100_txdes*txdes,+unsignedintlen)+{+txdes->txdes1|=FTMAC100_TXDES1_TXBUF_SIZE(len);+}++staticvoidftmac100_txdes_set_dma_addr(structftmac100_txdes*txdes,+dma_addr_taddr)+{+txdes->txdes2=addr;+}++staticdma_addr_tftmac100_txdes_get_dma_addr(structftmac100_txdes*txdes)+{+returntxdes->txdes2;+}++/* txdes3 is not used by hardware, we use it to keep track of socket buffer */+staticvoidftmac100_txdes_set_skb(structftmac100_txdes*txdes,+structsk_buff*skb)+{+txdes->txdes3=(unsignedint)skb;+}++staticstructsk_buff*ftmac100_txdes_get_skb(structftmac100_txdes*txdes)+{+return(structsk_buff*)txdes->txdes3;+}++/******************************************************************************+*internalfunctions(transmit)+*****************************************************************************/+staticintftmac100_next_tx_pointer(intpointer)+{+return(pointer+1)&(TX_QUEUE_ENTRIES-1);+}++staticvoidftmac100_tx_pointer_advance(structftmac100*priv)+{+priv->tx_pointer=ftmac100_next_tx_pointer(priv->tx_pointer);+}++staticvoidftmac100_tx_clean_pointer_advance(structftmac100*priv)+{+priv->tx_clean_pointer=+ftmac100_next_tx_pointer(priv->tx_clean_pointer);+}++staticstructftmac100_txdes*ftmac100_current_txdes(structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_pointer];+}++staticstructftmac100_txdes*ftmac100_current_clean_txdes(+structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_clean_pointer];+}++staticintftmac100_tx_complete_packet(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+structftmac100_txdes*txdes;+structsk_buff*skb;+dma_addr_tmap;++if(priv->tx_pending==0)+return0;++txdes=ftmac100_current_clean_txdes(priv);++if(ftmac100_txdes_owned_by_dma(txdes))+return0;++skb=ftmac100_txdes_get_skb(txdes);+map=ftmac100_txdes_get_dma_addr(txdes);++if(unlikely(ftmac100_txdes_excessive_collision(txdes)||+ftmac100_txdes_late_collision(txdes))){+/*+*packettransmittedtoethernetlostduetolatecollision+*orexcessivecollision+*/+netdev->stats.tx_aborted_errors++;+}else{+netdev->stats.tx_packets++;+netdev->stats.tx_bytes+=skb->len;+}++dma_unmap_single(priv->dev,map,skb_headlen(skb),DMA_TO_DEVICE);++dev_kfree_skb_irq(skb);++ftmac100_txdes_reset(txdes);++ftmac100_tx_clean_pointer_advance(priv);++priv->tx_pending--;+netif_wake_queue(netdev);++return1;+}++staticvoidftmac100_tx_complete(structftmac100*priv)+{+unsignedlongflags;++spin_lock_irqsave(&priv->tx_lock,flags);+while(ftmac100_tx_complete_packet(priv))+;+spin_unlock_irqrestore(&priv->tx_lock,flags);+}++staticintftmac100_xmit(structftmac100*priv,structsk_buff*skb,+dma_addr_tmap)+{+structnet_device*netdev=priv->netdev;+structftmac100_txdes*txdes;+unsignedintlen=(skb->len<ETH_ZLEN)?ETH_ZLEN:skb->len;+unsignedlongflags;++txdes=ftmac100_current_txdes(priv);+ftmac100_tx_pointer_advance(priv);++/* setup TX descriptor */++spin_lock_irqsave(&priv->tx_lock,flags);+ftmac100_txdes_set_skb(txdes,skb);+ftmac100_txdes_set_dma_addr(txdes,map);++ftmac100_txdes_set_first_segment(txdes);+ftmac100_txdes_set_last_segment(txdes);+ftmac100_txdes_set_txint(txdes);+ftmac100_txdes_set_buffer_size(txdes,len);++priv->tx_pending++;+if(priv->tx_pending==TX_QUEUE_ENTRIES){+if(net_ratelimit())+netdev_info(netdev,"tx queue full\n");++netif_stop_queue(netdev);+}++/* start transmit */+ftmac100_txdes_set_dma_own(txdes);+spin_unlock_irqrestore(&priv->tx_lock,flags);++ftmac100_txdma_start_polling(priv);++returnNETDEV_TX_OK;+}++/******************************************************************************+*internalfunctions(buffer)+*****************************************************************************/+staticvoidftmac100_free_buffers(structftmac100*priv)+{+inti;++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*page=ftmac100_rxdes_get_va(rxdes);++if(d)+dma_unmap_single(priv->dev,d,PAGE_SIZE,+DMA_FROM_DEVICE);++if(page!=NULL)+free_page((unsignedlong)page);+}++for(i=0;i<TX_QUEUE_ENTRIES;i++){+structftmac100_txdes*txdes=&priv->descs->txdes[i];+structsk_buff*skb=ftmac100_txdes_get_skb(txdes);++if(skb){+dma_addr_tmap;++map=ftmac100_txdes_get_dma_addr(txdes);+dma_unmap_single(priv->dev,map,skb_headlen(skb),+DMA_TO_DEVICE);+dev_kfree_skb(skb);+}+}++dma_free_coherent(priv->dev,sizeof(structftmac100_descs),+priv->descs,priv->descs_dma_addr);+}++staticintftmac100_alloc_buffers(structftmac100*priv)+{+inti;++priv->descs=dma_alloc_coherent(priv->dev,+sizeof(structftmac100_descs),&priv->descs_dma_addr,+GFP_KERNEL|GFP_DMA);+if(priv->descs==NULL)+return-ENOMEM;++memset(priv->descs,0,sizeof(structftmac100_descs));++/* initialize RX ring */++ftmac100_rxdes_set_end_of_ring(+&priv->descs->rxdes[RX_QUEUE_ENTRIES-1]);++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+void*page;+dma_addr_td;++page=(void*)__get_free_page(GFP_KERNEL|GFP_DMA);+if(page==NULL)+gotoerr;++d=dma_map_single(priv->dev,page,PAGE_SIZE,+DMA_FROM_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,d))){+free_page((unsignedlong)page);+gotoerr;+}++/*+*Thehardwareenforcesasub-2Kmaximumpacketsize,sowe+*puttwobuffersoneveryhardwarepage.+*/+ftmac100_rxdes_set_va(rxdes,page);+ftmac100_rxdes_set_va(rxdes+1,page+PAGE_SIZE/2);++ftmac100_rxdes_set_dma_addr(rxdes,d);+ftmac100_rxdes_set_dma_addr(rxdes+1,d+PAGE_SIZE/2);++ftmac100_rxdes_set_buffer_size(rxdes,RX_BUF_SIZE);+ftmac100_rxdes_set_buffer_size(rxdes+1,RX_BUF_SIZE);++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rxdes_set_dma_own(rxdes+1);+}++/* initialize TX ring */++ftmac100_txdes_set_end_of_ring(+&priv->descs->txdes[TX_QUEUE_ENTRIES-1]);+return0;++err:+ftmac100_free_buffers(priv);+return-ENOMEM;+}++/******************************************************************************+*structmii_if_infofunctions+*****************************************************************************/+staticintftmac100_mdio_read(structnet_device*netdev,intphy_id,intreg)+{+structftmac100*priv=netdev_priv(netdev);+intphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIRD;++iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);+for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIRD)==0)+returnphycr&FTMAC100_PHYCR_MIIRDATA;++usleep_range(100,1000);+}++netdev_err(netdev,"mdio read timed out\n");+return0xffff;+}++staticvoidftmac100_mdio_write(structnet_device*netdev,intphy_id,intreg,+intdata)+{+structftmac100*priv=netdev_priv(netdev);+intphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIWR;++data=FTMAC100_PHYWDATA_MIIWDATA(data);++iowrite32(data,priv->base+FTMAC100_OFFSET_PHYWDATA);+iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);++for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIWR)==0)+return;++usleep_range(100,1000);+}++netdev_err(netdev,"mdio write timed out\n");+}++/******************************************************************************+*structethtool_opsfunctions+*****************************************************************************/+staticvoidftmac100_get_drvinfo(structnet_device*netdev,+structethtool_drvinfo*info)+{+strcpy(info->driver,DRV_NAME);+strcpy(info->version,DRV_VERSION);+strcpy(info->bus_info,dev_name(&netdev->dev));+}++staticintftmac100_get_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_gset(&priv->mii,cmd);+}++staticintftmac100_set_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_sset(&priv->mii,cmd);+}++staticintftmac100_nway_reset(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_nway_restart(&priv->mii);+}++staticu32ftmac100_get_link(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_link_ok(&priv->mii);+}++staticconststructethtool_opsftmac100_ethtool_ops={+.set_settings=ftmac100_set_settings,+.get_settings=ftmac100_get_settings,+.get_drvinfo=ftmac100_get_drvinfo,+.nway_reset=ftmac100_nway_reset,+.get_link=ftmac100_get_link,+};++/******************************************************************************+*interrupthandler+*****************************************************************************/+staticirqreturn_tftmac100_interrupt(intirq,void*dev_id)+{+structnet_device*netdev=dev_id;+structftmac100*priv=netdev_priv(netdev);++if(likely(netif_running(netdev))){+/* Disable interrupts for polling */+ftmac100_disable_all_int(priv);+napi_schedule(&priv->napi);+}++returnIRQ_HANDLED;+}++/******************************************************************************+*structnapi_structfunctions+*****************************************************************************/+staticintftmac100_poll(structnapi_struct*napi,intbudget)+{+structftmac100*priv=container_of(napi,structftmac100,napi);+structnet_device*netdev=priv->netdev;+unsignedintstatus;+intcompleted=1;+intrx=0;++status=ioread32(priv->base+FTMAC100_OFFSET_ISR);++if(status&(FTMAC100_INT_RPKT_FINISH|FTMAC100_INT_NORXBUF)){+/*+*FTMAC100_INT_RPKT_FINISH:+*RXDMAhasreceivedpacketsintoRXbuffersuccessfully+*+*FTMAC100_INT_NORXBUF:+*RXbufferunavailable+*/+intretry;++do{+retry=ftmac100_rx_packet(priv,&rx);+}while(retry&&rx<budget);++if(retry&&rx==budget)+completed=0;+}++if(status&FTMAC100_INT_NORXBUF){+/* RX buffer unavailable */+if(net_ratelimit())+netdev_info(netdev,"INT_NORXBUF\n");++netdev->stats.rx_over_errors++;+}++if(status&(FTMAC100_INT_XPKT_OK|FTMAC100_INT_XPKT_LOST)){+/*+*FTMAC100_INT_XPKT_OK:+*packettransmittedtoethernetsuccessfully+*+*FTMAC100_INT_XPKT_LOST:+*packettransmittedtoethernetlostduetolate+*collisionorexcessivecollision+*/+ftmac100_tx_complete(priv);+}++if(status&FTMAC100_INT_RPKT_LOST){+/* received packet lost due to RX FIFO full */+if(net_ratelimit())+netdev_info(netdev,"INT_RPKT_LOST\n");++netdev->stats.rx_fifo_errors++;+}++if(status&FTMAC100_INT_AHB_ERR){+/* AHB error */+if(net_ratelimit())+netdev_info(netdev,"INT_AHB_ERR\n");++/* do nothing */+}++if(status&FTMAC100_INT_PHYSTS_CHG){+/* PHY link status change */+if(net_ratelimit())+netdev_info(netdev,"INT_PHYSTS_CHG\n");++mii_check_link(&priv->mii);+}++if(completed){+/* stop polling */+napi_complete(napi);+ftmac100_enable_all_int(priv);+}++returnrx;+}++/******************************************************************************+*structnet_device_opsfunctions+*****************************************************************************/+staticintftmac100_open(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+interr;++err=ftmac100_alloc_buffers(priv);+if(err){+netdev_err(netdev,"failed to allocate buffers\n");+gotoerr_alloc;+}++err=request_irq(priv->irq,ftmac100_interrupt,0,netdev->name,+netdev);+if(err){+netdev_err(netdev,"failed to request irq %d\n",priv->irq);+gotoerr_irq;+}++priv->rx_pointer=0;+priv->tx_clean_pointer=0;+priv->tx_pointer=0;+priv->tx_pending=0;++err=ftmac100_start_hw(priv);+if(err)+gotoerr_hw;++napi_enable(&priv->napi);+netif_start_queue(netdev);++ftmac100_enable_all_int(priv);+return0;++err_hw:+free_irq(priv->irq,netdev);+err_irq:+ftmac100_free_buffers(priv);+err_alloc:+returnerr;+}++staticintftmac100_stop(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);++ftmac100_disable_all_int(priv);+netif_stop_queue(netdev);+napi_disable(&priv->napi);+ftmac100_stop_hw(priv);+free_irq(priv->irq,netdev);+ftmac100_free_buffers(priv);++return0;+}++staticintftmac100_hard_start_xmit(structsk_buff*skb,+structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+dma_addr_tmap;++if(unlikely(skb->len>MAX_PKT_SIZE)){+if(net_ratelimit())+netdev_dbg(netdev,"tx packet too big\n");++netdev->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++map=dma_map_single(priv->dev,skb->data,skb_headlen(skb),+DMA_TO_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,map))){+/* drop packet */+if(net_ratelimit())+netdev_err(netdev,"map socket buffer failed\n");++netdev->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++returnftmac100_xmit(priv,skb,map);+}++/* optional */+staticintftmac100_do_ioctl(structnet_device*netdev,structifreq*ifr,+intcmd)+{+structftmac100*priv=netdev_priv(netdev);+structmii_ioctl_data*data=if_mii(ifr);++returngeneric_mii_ioctl(&priv->mii,data,cmd,NULL);+}++staticconststructnet_device_opsftmac100_netdev_ops={+.ndo_open=ftmac100_open,+.ndo_stop=ftmac100_stop,+.ndo_start_xmit=ftmac100_hard_start_xmit,+.ndo_set_mac_address=eth_mac_addr,+.ndo_validate_addr=eth_validate_addr,+.ndo_do_ioctl=ftmac100_do_ioctl,+};++/******************************************************************************+*structplatform_driverfunctions+*****************************************************************************/+staticintftmac100_remove(structplatform_device*pdev)+{+structnet_device*netdev;+structftmac100*priv;++netdev=platform_get_drvdata(pdev);+if(netdev==NULL)+return0;++platform_set_drvdata(pdev,NULL);++priv=netdev_priv(netdev);++netif_napi_del(&priv->napi);+unregister_netdev(netdev);++if(priv->base!=NULL)+iounmap(priv->base);++if(priv->res!=NULL)+release_resource(priv->res);++free_netdev(netdev);+return0;+}++staticintftmac100_probe(structplatform_device*pdev)+{+structresource*res;+intirq;+structnet_device*netdev;+structftmac100*priv;+interr;++if(!pdev)+return-ENODEV;++res=platform_get_resource(pdev,IORESOURCE_MEM,0);+if(!res)+return-ENXIO;++irq=platform_get_irq(pdev,0);+if(irq<0)+returnirq;++/* setup net_device */++netdev=alloc_etherdev(sizeof(structftmac100));+if(netdev==NULL){+err=-ENOMEM;+gotoerr_out;+}++SET_NETDEV_DEV(netdev,&pdev->dev);+SET_ETHTOOL_OPS(netdev,&ftmac100_ethtool_ops);+netdev->netdev_ops=&ftmac100_netdev_ops;++platform_set_drvdata(pdev,netdev);++/* setup private data */++priv=netdev_priv(netdev);+priv->netdev=netdev;+priv->dev=&pdev->dev;++spin_lock_init(&priv->tx_lock);++/* initialize NAPI */+netif_napi_add(netdev,&priv->napi,ftmac100_poll,64);++/* map io memory */+priv->res=request_mem_region(res->start,res->end-res->start,+dev_name(&pdev->dev));+if(priv->res==NULL){+dev_err(&pdev->dev,"Could not reserve memory region\n");+err=-ENOMEM;+gotoerr_out;+}++priv->base=ioremap(res->start,res->end-res->start);+if(priv->base==NULL){+dev_err(&pdev->dev,"Failed to ioremap ethernet registers\n");+err=-EIO;+gotoerr_out;+}++priv->irq=irq;++/* initialize struct mii_if_info */++priv->mii.phy_id=0;+priv->mii.phy_id_mask=0x1f;+priv->mii.reg_num_mask=0x1f;+priv->mii.dev=netdev;+priv->mii.mdio_read=ftmac100_mdio_read;+priv->mii.mdio_write=ftmac100_mdio_write;++/* register network device */++err=register_netdev(netdev);+if(err){+dev_err(&pdev->dev,"Failed to register netdev\n");+gotoerr_out;+}++netdev_info(netdev,"irq %d, mapped at %p\n",priv->irq,priv->base);++if(!is_valid_ether_addr(netdev->dev_addr)){+random_ether_addr(netdev->dev_addr);+netdev_info(netdev,"generated random MAC address %pM\n",+netdev->dev_addr);+}++return0;++err_out:+ftmac100_remove(pdev);+returnerr;+}++staticstructplatform_driverftmac100_driver={+.probe=ftmac100_probe,+.remove=ftmac100_remove,+.driver={+.name=DRV_NAME,+.owner=THIS_MODULE,+},+};++/******************************************************************************+*initialization/finalization+*****************************************************************************/+staticint__initftmac100_init(void)+{+printk(KERN_INFO"Loading "DRV_NAME": version "DRV_VERSION" ...\n");+returnplatform_driver_register(&ftmac100_driver);+}++staticvoid__exitftmac100_exit(void)+{+platform_driver_unregister(&ftmac100_driver);+}++module_init(ftmac100_init);+module_exit(ftmac100_exit);++MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");+MODULE_DESCRIPTION("FTMAC100 driver");+MODULE_LICENSE("GPL");
From: Joe Perches <joe@perches.com> Date: 2011-01-17 17:19:53
On Mon, 2011-01-17 at 17:21 +0800, Po-Yu Chuang wrote:
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Hi again.
Signed-off-by: Po-Yu Chuang <redacted>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts
This looks very clean, thanks for doing the rework.
Now the the really trivial...
+static int ftmac100_rx_packet_error(struct ftmac100 *priv,
+ struct ftmac100_rxdes *rxdes)
[]
quoted hunk
+ if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {+ if (net_ratelimit())+ netdev_info(netdev, "rx frame too long\n");++ netdev->stats.rx_length_errors++;+ error = 1;+ }++ if (unlikely(ftmac100_rxdes_runt(rxdes))) {
else if ?
quoted hunk
+static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)+{+ struct net_device *netdev = priv->netdev;+ struct ftmac100_rxdes *rxdes;+ struct sk_buff *skb;+ int length;+ int copied = 0;+ int done = 0;
You could use bool/true/false here for copied and done
and all the other uses of an int for a logical bool.
quoted hunk
+static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)+{+ /*+ * Make sure dma own bit will not be set before any other+ * descriptor fiels.
field/fields
quoted hunk
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)+{+ struct ftmac100 *priv = netdev_priv(netdev);+ int phycr;+ int i;++ phycr = FTMAC100_PHYCR_PHYAD(phy_id) |+ FTMAC100_PHYCR_REGAD(reg) |+ FTMAC100_PHYCR_MIIRD;++ iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);+ for (i = 0; i < 10; i++) {+ phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);++ if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)+ return phycr & FTMAC100_PHYCR_MIIRDATA;++ usleep_range(100, 1000);+ }++ netdev_err(netdev, "mdio read timed out\n");+ return 0xffff;
0xffff is a rather odd return, perhaps a #define?
quoted hunk
+/******************************************************************************+ * initialization / finalization+ *****************************************************************************/+static int __init ftmac100_init(void)+{+ printk(KERN_INFO "Loading " DRV_NAME ": version " DRV_VERSION " ...\n");
You could use
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
before any #include and
pr_info("Loading version " DRV_VERSION " ...\n");
One last comment on split long line indentation style
and long function declarations.
There's no required style so you can use what you are
most comfortable doing.
Most of drivers/net uses an alignment to open parenthesis
using maximal tabs and minimal necessary spaces instead of
an extra tabstop.
Like:
static int some_long_function(type var1, type var2...
type varN)
and
some_long_function(var1, var2, ...
varN);
not
static int some_long_function(type var1, type var2...
type varN)
and
some_long_function(var1, var2, ...
varN);
From: Eric Dumazet <hidden> Date: 2011-01-17 17:33:30
Le lundi 17 janvier 2011 à 17:21 +0800, Po-Yu Chuang a écrit :
quoted hunk
+static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)+{+ struct net_device *netdev = priv->netdev;+ struct ftmac100_rxdes *rxdes;+ struct sk_buff *skb;+ int length;+ int copied = 0;+ int done = 0;++ rxdes = ftmac100_rx_locate_first_segment(priv);+ if (!rxdes)+ return 0;++ length = ftmac100_rxdes_frame_length(rxdes);++ netdev->stats.rx_packets++;+ netdev->stats.rx_bytes += length;++ if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {+ ftmac100_rx_drop_packet(priv);+ return 1;+ }++ /* start processing */+ skb = netdev_alloc_skb_ip_align(netdev, length);+ if (unlikely(!skb)) {+ if (net_ratelimit())+ netdev_err(netdev, "rx skb alloc failed\n");++ ftmac100_rx_drop_packet(priv);+ return 1;+ }+
Please dont increase rx_packets/rx_bytes before the
netdev_alloc_skb_ip_align().
In case of mem allocation failure, it would be better not pretending we
handled a packet.
drivers/net/r8169.c for example does the rx_packets/rx_bytes only if
packet is delivered to upper stack.
I wonder if its not too expensive to read/modify/write txdes->txdes1
Maybe you should use a temporary u32 var and perform one final write on
txdes->txdes1 (with the set_dma_own)
quoted hunk
+ priv->tx_pending++;+ if (priv->tx_pending == TX_QUEUE_ENTRIES) {+ if (net_ratelimit())+ netdev_info(netdev, "tx queue full\n");++ netif_stop_queue(netdev);+ }++ /* start transmit */+ ftmac100_txdes_set_dma_own(txdes);
txdes->txdes1 = txdes1;
BTW, shouldnt you use cpu_to_be32() or cpu_to_le32(), if this driver is
multi platform ?
From: Ben Hutchings <hidden> Date: 2011-01-17 18:58:46
On Mon, 2011-01-17 at 18:29 +0100, Eric Dumazet wrote:
Le lundi 17 janvier 2011 à 17:21 +0800, Po-Yu Chuang a écrit :
quoted
+static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)+{+ struct net_device *netdev = priv->netdev;+ struct ftmac100_rxdes *rxdes;+ struct sk_buff *skb;+ int length;+ int copied = 0;+ int done = 0;++ rxdes = ftmac100_rx_locate_first_segment(priv);+ if (!rxdes)+ return 0;++ length = ftmac100_rxdes_frame_length(rxdes);++ netdev->stats.rx_packets++;+ netdev->stats.rx_bytes += length;++ if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {+ ftmac100_rx_drop_packet(priv);+ return 1;+ }++ /* start processing */+ skb = netdev_alloc_skb_ip_align(netdev, length);+ if (unlikely(!skb)) {+ if (net_ratelimit())+ netdev_err(netdev, "rx skb alloc failed\n");++ ftmac100_rx_drop_packet(priv);+ return 1;+ }+
Please dont increase rx_packets/rx_bytes before the
netdev_alloc_skb_ip_align().
In case of mem allocation failure, it would be better not pretending we
handled a packet.
drivers/net/r8169.c for example does the rx_packets/rx_bytes only if
packet is delivered to upper stack.
That's news to me. I specifically advised Po-Yu Chuang to increment
these earlier because my understanding is that all packets/bytes should
be counted. And drivers which use hardware MAC stats will generally do
that, so I really don't think it makes sense to make other drivers
different deliberately.
Ben.
--
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.
From: Eric Dumazet <hidden> Date: 2011-01-17 20:47:03
Le lundi 17 janvier 2011 à 18:58 +0000, Ben Hutchings a écrit :
On Mon, 2011-01-17 at 18:29 +0100, Eric Dumazet wrote:
quoted
Le lundi 17 janvier 2011 à 17:21 +0800, Po-Yu Chuang a écrit :
quoted
+static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)+{+ struct net_device *netdev = priv->netdev;+ struct ftmac100_rxdes *rxdes;+ struct sk_buff *skb;+ int length;+ int copied = 0;+ int done = 0;++ rxdes = ftmac100_rx_locate_first_segment(priv);+ if (!rxdes)+ return 0;++ length = ftmac100_rxdes_frame_length(rxdes);++ netdev->stats.rx_packets++;+ netdev->stats.rx_bytes += length;++ if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {+ ftmac100_rx_drop_packet(priv);+ return 1;+ }++ /* start processing */+ skb = netdev_alloc_skb_ip_align(netdev, length);+ if (unlikely(!skb)) {+ if (net_ratelimit())+ netdev_err(netdev, "rx skb alloc failed\n");++ ftmac100_rx_drop_packet(priv);+ return 1;+ }+
Please dont increase rx_packets/rx_bytes before the
netdev_alloc_skb_ip_align().
In case of mem allocation failure, it would be better not pretending we
handled a packet.
drivers/net/r8169.c for example does the rx_packets/rx_bytes only if
packet is delivered to upper stack.
That's news to me. I specifically advised Po-Yu Chuang to increment
these earlier because my understanding is that all packets/bytes should
be counted. And drivers which use hardware MAC stats will generally do
that, so I really don't think it makes sense to make other drivers
different deliberately.
I see, but when one frame is dropped because of RX ring buffer
under/overflow we dont account for the lost packet/bytes.
Thats probably not very important, but would be good if all drivers
behave the same.
Dear Eric,
On Tue, Jan 18, 2011 at 2:21 AM, Eric Dumazet [off-list ref] wrote:
BTW, shouldnt you use cpu_to_be32() or cpu_to_le32(), if this driver is
multi platform ?
This reminds me another thing. Should I use u32 instead of unsigned int for all
hardware related variables (registers, descriptors) ?
Not quite sure about these cross-platform issues.
best regards,
Po-Yu Chuang
I wonder if its not too expensive to read/modify/write txdes->txdes1
Maybe you should use a temporary u32 var and perform one final write on
txdes->txdes1 (with the set_dma_own)
That's OK, the compiler combines these bits updates together and write once
since txdes is not volatile. (I checked the object file)
It's not expensive.
BTW, DMA_OWN bit is at txdes0
quoted
+ priv->tx_pending++;+ if (priv->tx_pending == TX_QUEUE_ENTRIES) {+ if (net_ratelimit())+ netdev_info(netdev, "tx queue full\n");++ netif_stop_queue(netdev);+ }++ /* start transmit */+ ftmac100_txdes_set_dma_own(txdes);
txdes->txdes1 = txdes1;
BTW, shouldnt you use cpu_to_be32() or cpu_to_le32(), if this driver is
multi platform ?
OK, but I am not sure if I use them the right way.
Please check my v3 patch later.
Thanks,
Po-Yu Chuang
+static int ftmac100_rx_packet_error(struct ftmac100 *priv,
+ struct ftmac100_rxdes *rxdes)
[]
quoted
+ if (unlikely(ftmac100_rxdes_frame_too_long(rxdes))) {+ if (net_ratelimit())+ netdev_info(netdev, "rx frame too long\n");++ netdev->stats.rx_length_errors++;+ error = 1;+ }++ if (unlikely(ftmac100_rxdes_runt(rxdes))) {
else if ?
OK, fixed.
quoted
+static int ftmac100_rx_packet(struct ftmac100 *priv, int *processed)+{+ struct net_device *netdev = priv->netdev;+ struct ftmac100_rxdes *rxdes;+ struct sk_buff *skb;+ int length;+ int copied = 0;+ int done = 0;
You could use bool/true/false here for copied and done
and all the other uses of an int for a logical bool.
OK, fixed.
quoted
+static void ftmac100_txdes_set_dma_own(struct ftmac100_txdes *txdes)+{+ /*+ * Make sure dma own bit will not be set before any other+ * descriptor fiels.
field/fields
Fixed.
quoted
+static int ftmac100_mdio_read(struct net_device *netdev, int phy_id, int reg)+{+ struct ftmac100 *priv = netdev_priv(netdev);+ int phycr;+ int i;++ phycr = FTMAC100_PHYCR_PHYAD(phy_id) |+ FTMAC100_PHYCR_REGAD(reg) |+ FTMAC100_PHYCR_MIIRD;++ iowrite32(phycr, priv->base + FTMAC100_OFFSET_PHYCR);+ for (i = 0; i < 10; i++) {+ phycr = ioread32(priv->base + FTMAC100_OFFSET_PHYCR);++ if ((phycr & FTMAC100_PHYCR_MIIRD) == 0)+ return phycr & FTMAC100_PHYCR_MIIRDATA;++ usleep_range(100, 1000);+ }++ netdev_err(netdev, "mdio read timed out\n");+ return 0xffff;
0xffff is a rather odd return, perhaps a #define?
After a little digging in drivers/net/mii.c, it seems that mii lib does not
check return value if it is error. So I guess I should return 0 if error.
quoted
+/******************************************************************************+ * initialization / finalization+ *****************************************************************************/+static int __init ftmac100_init(void)+{+ printk(KERN_INFO "Loading " DRV_NAME ": version " DRV_VERSION " ...\n");
You could use
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
before any #include and
pr_info("Loading version " DRV_VERSION " ...\n");
OK
One last comment on split long line indentation style
and long function declarations.
There's no required style so you can use what you are
most comfortable doing.
Most of drivers/net uses an alignment to open parenthesis
using maximal tabs and minimal necessary spaces instead of
an extra tabstop.
Like:
static int some_long_function(type var1, type var2...
type varN)
and
some_long_function(var1, var2, ...
varN);
not
static int some_long_function(type var1, type var2...
type varN)
and
some_long_function(var1, var2, ...
varN);
Well, TBH, I don't like this style because if I changed the
function name, the indentation might need to be adjusted.
Even worse, I got an infeasible case :-(
static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
struct ftmac100 *priv)
I know my function names are quite long, but I like them to be descriptive.
Do you really insist on it?
Thanks,
Po-Yu Chuang
From: Ben Hutchings <hidden> Date: 2011-01-19 12:46:49
On Wed, 2011-01-19 at 17:40 +0800, Po-Yu Chuang wrote:
[...]
Well, TBH, I don't like this style because if I changed the
function name, the indentation might need to be adjusted.
Even worse, I got an infeasible case :-(
static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
struct ftmac100 *priv)
I know my function names are quite long, but I like them to be descriptive.
Do you really insist on it?
You can break after the return type:
static struct ftmac100_rxdes *
ftmac100_rx_locate_first_segment(struct ftmac100 *priv)
Ben.
--
Ben Hutchings, Senior Software Engineer, Solarflare Communications
Not speaking for my employer; that's the marketing department's job.
They asked us to note that Solarflare product names are trademarked.
From: Joe Perches <joe@perches.com> Date: 2011-01-19 16:41:33
On Wed, 2011-01-19 at 17:40 +0800, Po-Yu Chuang wrote:
On Tue, Jan 18, 2011 at 1:19 AM, Joe Perches [off-list ref] wrote:
quoted
on split long line indentation style
and long function declarations.
[]
quoted
Most of drivers/net uses an alignment to open parenthesis
using maximal tabs and minimal necessary spaces instead of
an extra tabstop.
[]
Well, TBH, I don't like this style because if I changed the
function name, the indentation might need to be adjusted.
No worries. That could happen using either style.
There's no required style so you can use what you are
most comfortable doing. It's not a big deal at all.
Even worse, I got an infeasible case :-(
static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
struct ftmac100 *priv)
I know my function names are quite long, but I like them to be descriptive.
Do you really insist on it?
Here's a common alternative style for this case:
static struct ftmac100_rxdes *
ftmac100_rx_locate_first_segment(struct ftmac100 *priv)
Dear Joe,
On Thu, Jan 20, 2011 at 12:41 AM, Joe Perches [off-list ref] wrote:
On Wed, 2011-01-19 at 17:40 +0800, Po-Yu Chuang wrote:
quoted
Well, TBH, I don't like this style because if I changed the
function name, the indentation might need to be adjusted.
No worries. That could happen using either style.
There's no required style so you can use what you are
most comfortable doing. It's not a big deal at all.
quoted
Even worse, I got an infeasible case :-(
static struct ftmac100_rxdes *ftmac100_rx_locate_first_segment(
struct ftmac100 *priv)
I know my function names are quite long, but I like them to be descriptive.
Do you really insist on it?
Here's a common alternative style for this case:
static struct ftmac100_rxdes *
ftmac100_rx_locate_first_segment(struct ftmac100 *priv)
OK, I see.
One more question: how to deal with this? Add a local variable for the
2nd argument?
ftmac100_set_receive_ring_base(priv,
priv->descs_dma_addr + offsetof(struct ftmac100_descs, rxdes));
best regards,
Po-Yu Chuang
From: Joe Perches <joe@perches.com> Date: 2011-01-20 08:46:38
On Thu, 2011-01-20 at 13:30 +0800, Po-Yu Chuang wrote:
One more question: how to deal with this? Add a local variable for the
2nd argument?
ftmac100_set_receive_ring_base(priv,
priv->descs_dma_addr + offsetof(struct ftmac100_descs, rxdes));
That's one way, another is:
ftmac100_set_receive_ring_base(priv,
priv->descs_dma_addr +
offsetof(struct ftmac100_descs, rxdes));
Another would be to change set_receive_ring_base to
just pass priv and determine the dma address there:
static void ftmac100_set_receive_ring_base(struct ftmac100 *priv)
{
dma_addr_t addr;
addr = priv->descs_dma_addr + offsetof(struct ftmac100_descs, rxdes);
iowrite32(addr, priv->base + FTMAC100_OFFSET_RXR_BADR);
}
...
ftmac100_set_receive_ring_base(priv);
Another is not to be overly adherent to 80 columns.
Pick one that suits you.
You chose to use a lot of single use, single line
functions with descriptive names that use iowrite32 or
return some flag. I probably would have just used
iowrite32 or tested the flag directly, but that's your
choice and it's perfectly fine.
There are a lot of coding choices that are readable and
good. There isn't and shouldn't be some mandate for some
specific code appearance before inclusion or acceptance.
checkpatch is just a style guide. Ignore it and ignore
me when you feel it's appropriate. I won't mind.
cheers, Joe
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts
v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1243 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 +++++++
4 files changed, 1433 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ftmac100.c
create mode 100644 drivers/net/ftmac100.h
@@ -0,0 +1,1243 @@+/*+*FaradayFTMAC10010/100Ethernet+*+*(C)Copyright2009-2011FaradayTechnology+*Po-YuChuang<ratbert@faraday-tech.com>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,writetotheFreeSoftware+*Foundation,Inc.,675MassAve,Cambridge,MA02139,USA.+*/++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/dma-mapping.h>+#include<linux/etherdevice.h>+#include<linux/ethtool.h>+#include<linux/init.h>+#include<linux/io.h>+#include<linux/kernel.h>+#include<linux/mii.h>+#include<linux/module.h>+#include<linux/moduleparam.h>+#include<linux/netdevice.h>+#include<linux/platform_device.h>++#include"ftmac100.h"++#define DRV_NAME "ftmac100"+#define DRV_VERSION "0.2"++#define RX_QUEUE_ENTRIES 128 /* must be power of 2 */+#define TX_QUEUE_ENTRIES 16 /* must be power of 2 */++#define MAX_PKT_SIZE 1518+#define RX_BUF_SIZE 2044 /* must be smaller than 0x7ff */++/******************************************************************************+*privatedata+*****************************************************************************/+structftmac100_descs{+structftmac100_rxdesrxdes[RX_QUEUE_ENTRIES];+structftmac100_txdestxdes[TX_QUEUE_ENTRIES];+};++structftmac100{+structresource*res;+void*base;+intirq;++structftmac100_descs*descs;+dma_addr_tdescs_dma_addr;++unsignedintrx_pointer;+unsignedinttx_clean_pointer;+unsignedinttx_pointer;+unsignedinttx_pending;++spinlock_ttx_lock;++structnet_device*netdev;+structdevice*dev;+structnapi_structnapi;++structmii_if_infomii;+};++/******************************************************************************+*internalfunctions(hardwareregisteraccess)+*****************************************************************************/+#define INT_MASK_ALL_ENABLED (FTMAC100_INT_RPKT_FINISH | \+FTMAC100_INT_NORXBUF|\+FTMAC100_INT_XPKT_OK|\+FTMAC100_INT_XPKT_LOST|\+FTMAC100_INT_RPKT_LOST|\+FTMAC100_INT_AHB_ERR|\+FTMAC100_INT_PHYSTS_CHG)++staticvoidftmac100_enable_all_int(structftmac100*priv)+{+iowrite32(INT_MASK_ALL_ENABLED,priv->base+FTMAC100_OFFSET_IMR);+}++staticvoidftmac100_disable_all_int(structftmac100*priv)+{+iowrite32(0,priv->base+FTMAC100_OFFSET_IMR);+}++staticvoidftmac100_set_rx_ring_base(structftmac100*priv,dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_RXR_BADR);+}++staticvoidftmac100_set_tx_ring_base(structftmac100*priv,dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_TXR_BADR);+}++staticvoidftmac100_txdma_start_polling(structftmac100*priv)+{+iowrite32(1,priv->base+FTMAC100_OFFSET_TXPD);+}++staticintftmac100_reset(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+inti;++/* NOTE: reset clears all registers */+iowrite32(FTMAC100_MACCR_SW_RST,priv->base+FTMAC100_OFFSET_MACCR);++for(i=0;i<5;i++){+unsignedintmaccr;++maccr=ioread32(priv->base+FTMAC100_OFFSET_MACCR);+if(!(maccr&FTMAC100_MACCR_SW_RST)){+/*+*FTMAC100_MACCR_SW_RSTcleareddoesnotindicate+*thathardwareresetcompleted(whatthef*ck).+*Westillneedtowaitforawhile.+*/+usleep_range(500,1000);+return0;+}++usleep_range(1000,10000);+}++netdev_err(netdev,"software reset failed\n");+return-EIO;+}++staticvoidftmac100_set_mac(structftmac100*priv,constunsignedchar*mac)+{+unsignedintmaddr=mac[0]<<8|mac[1];+unsignedintladdr=mac[2]<<24|mac[3]<<16|mac[4]<<8|mac[5];++iowrite32(maddr,priv->base+FTMAC100_OFFSET_MAC_MADR);+iowrite32(laddr,priv->base+FTMAC100_OFFSET_MAC_LADR);+}++#define MACCR_ENABLE_ALL (FTMAC100_MACCR_XMT_EN | \+FTMAC100_MACCR_RCV_EN|\+FTMAC100_MACCR_XDMA_EN|\+FTMAC100_MACCR_RDMA_EN|\+FTMAC100_MACCR_CRC_APD|\+FTMAC100_MACCR_FULLDUP|\+FTMAC100_MACCR_RX_RUNT|\+FTMAC100_MACCR_RX_BROADPKT)++staticintftmac100_start_hw(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;++if(ftmac100_reset(priv))+return-EIO;++/* setup ring buffer base registers */++ftmac100_set_rx_ring_base(priv,+priv->descs_dma_addr++offsetof(structftmac100_descs,rxdes));+ftmac100_set_tx_ring_base(priv,+priv->descs_dma_addr++offsetof(structftmac100_descs,txdes));++iowrite32(FTMAC100_APTC_RXPOLL_CNT(1),priv->base+FTMAC100_OFFSET_APTC);++ftmac100_set_mac(priv,netdev->dev_addr);++iowrite32(MACCR_ENABLE_ALL,priv->base+FTMAC100_OFFSET_MACCR);+return0;+}++staticvoidftmac100_stop_hw(structftmac100*priv)+{+iowrite32(0,priv->base+FTMAC100_OFFSET_MACCR);+}++/******************************************************************************+*internalfunctions(receivedescriptor)+*****************************************************************************/+staticboolftmac100_rxdes_first_segment(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_FRS;+}++staticboolftmac100_rxdes_last_segment(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_LRS;+}++staticboolftmac100_rxdes_owned_by_dma(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_RXDMA_OWN;+}++staticvoidftmac100_rxdes_set_dma_own(structftmac100_rxdes*rxdes)+{+/* clear status bits */+rxdes->rxdes0=cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);+}++staticboolftmac100_rxdes_rx_error(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_RX_ERR;+}++staticboolftmac100_rxdes_crc_error(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_CRC_ERR;+}++staticboolftmac100_rxdes_frame_too_long(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_FTL;+}++staticboolftmac100_rxdes_runt(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_RUNT;+}++staticboolftmac100_rxdes_odd_nibble(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_RX_ODD_NB;+}++staticunsignedintftmac100_rxdes_frame_length(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_RFL;+}++staticboolftmac100_rxdes_multicast(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes0)&FTMAC100_RXDES0_MULTICAST;+}++staticvoidftmac100_rxdes_set_buffer_size(structftmac100_rxdes*rxdes,+unsignedintsize)+{+unsignedinttmp=le32_to_cpu(rxdes->rxdes1);++tmp&=FTMAC100_RXDES1_EDORR;+tmp|=FTMAC100_RXDES1_RXBUF_SIZE(size);+rxdes->rxdes1=cpu_to_le32(tmp);+}++staticvoidftmac100_rxdes_set_end_of_ring(structftmac100_rxdes*rxdes)+{+unsignedinttmp=le32_to_cpu(rxdes->rxdes1);++tmp|=FTMAC100_RXDES1_EDORR;+rxdes->rxdes1=cpu_to_le32(tmp);+}++staticvoidftmac100_rxdes_set_dma_addr(structftmac100_rxdes*rxdes,+dma_addr_taddr)+{+rxdes->rxdes2=cpu_to_le32(addr);+}++staticdma_addr_tftmac100_rxdes_get_dma_addr(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes2);+}++/* rxdes3 is not used by hardware, we use it to keep track of buffer */+staticvoidftmac100_rxdes_set_va(structftmac100_rxdes*rxdes,void*addr)+{+rxdes->rxdes3=cpu_to_le32(addr);+}++staticvoid*ftmac100_rxdes_get_va(structftmac100_rxdes*rxdes)+{+return(void*)le32_to_cpu(rxdes->rxdes3);+}++/******************************************************************************+*internalfunctions(receive)+*****************************************************************************/+staticintftmac100_next_rx_pointer(intpointer)+{+return(pointer+1)&(RX_QUEUE_ENTRIES-1);+}++staticvoidftmac100_rx_pointer_advance(structftmac100*priv)+{+priv->rx_pointer=ftmac100_next_rx_pointer(priv->rx_pointer);+}++staticstructftmac100_rxdes*ftmac100_current_rxdes(structftmac100*priv)+{+return&priv->descs->rxdes[priv->rx_pointer];+}++staticstructftmac100_rxdes*+ftmac100_rx_locate_first_segment(structftmac100*priv)+{+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);++while(!ftmac100_rxdes_owned_by_dma(rxdes)){+if(ftmac100_rxdes_first_segment(rxdes))+returnrxdes;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}++returnNULL;+}++staticboolftmac100_rx_packet_error(structftmac100*priv,+structftmac100_rxdes*rxdes)+{+structnet_device*netdev=priv->netdev;+boolerror=false;++if(unlikely(ftmac100_rxdes_rx_error(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx err\n");++netdev->stats.rx_errors++;+error=true;+}++if(unlikely(ftmac100_rxdes_crc_error(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx crc err\n");++netdev->stats.rx_crc_errors++;+error=true;+}++if(unlikely(ftmac100_rxdes_frame_too_long(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx frame too long\n");++netdev->stats.rx_length_errors++;+error=true;+}elseif(unlikely(ftmac100_rxdes_runt(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx runt\n");++netdev->stats.rx_length_errors++;+error=true;+}elseif(unlikely(ftmac100_rxdes_odd_nibble(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx odd nibble\n");++netdev->stats.rx_length_errors++;+error=true;+}++returnerror;+}++staticvoidftmac100_rx_drop_packet(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);+booldone=false;++if(net_ratelimit())+netdev_dbg(netdev,"drop packet %p\n",rxdes);++do{+if(ftmac100_rxdes_last_segment(rxdes))+done=true;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}while(!done&&!ftmac100_rxdes_owned_by_dma(rxdes));++netdev->stats.rx_dropped++;+}++staticboolftmac100_rx_packet(structftmac100*priv,int*processed)+{+structnet_device*netdev=priv->netdev;+structftmac100_rxdes*rxdes;+structsk_buff*skb;+intlength;+boolcopied=false;+booldone=false;++rxdes=ftmac100_rx_locate_first_segment(priv);+if(!rxdes)+returnfalse;++if(unlikely(ftmac100_rx_packet_error(priv,rxdes))){+ftmac100_rx_drop_packet(priv);+returntrue;+}++/* start processing */++length=ftmac100_rxdes_frame_length(rxdes);++skb=netdev_alloc_skb_ip_align(netdev,length);+if(unlikely(!skb)){+if(net_ratelimit())+netdev_err(netdev,"rx skb alloc failed\n");++ftmac100_rx_drop_packet(priv);+returntrue;+}++if(unlikely(ftmac100_rxdes_multicast(rxdes)))+netdev->stats.multicast++;++do{+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*buf=ftmac100_rxdes_get_va(rxdes);+intsize;++size=min(length-copied,RX_BUF_SIZE);++dma_sync_single_for_cpu(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);+memcpy(skb_put(skb,size),buf,size);++copied+=size;++if(ftmac100_rxdes_last_segment(rxdes))+done=true;++dma_sync_single_for_device(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);++ftmac100_rxdes_set_dma_own(rxdes);++ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}while(!done&&copied<length);++skb->protocol=eth_type_trans(skb,netdev);++/* push packet to protocol stack */+netif_receive_skb(skb);++netdev->stats.rx_packets++;+netdev->stats.rx_bytes+=skb->len;++(*processed)++;++returntrue;+}++/******************************************************************************+*internalfunctions(transmitdescriptor)+*****************************************************************************/+staticvoidftmac100_txdes_reset(structftmac100_txdes*txdes)+{+/* clear all except end of ring bit */+txdes->txdes0=0;+txdes->txdes1&=FTMAC100_TXDES1_EDOTR;+txdes->txdes2=0;+txdes->txdes3=0;+}++staticboolftmac100_txdes_owned_by_dma(structftmac100_txdes*txdes)+{+returnle32_to_cpu(txdes->txdes0)&FTMAC100_TXDES0_TXDMA_OWN;+}++staticvoidftmac100_txdes_set_dma_own(structftmac100_txdes*txdes)+{+unsignedinttmp=le32_to_cpu(txdes->txdes0);++tmp|=FTMAC100_TXDES0_TXDMA_OWN;++/*+*Makesuredmaownbitwillnotbesetbeforeanyother+*descriptorfields.+*/+wmb();+txdes->txdes0=cpu_to_le32(tmp);+}++staticboolftmac100_txdes_excessive_collision(structftmac100_txdes*txdes)+{+returnle32_to_cpu(txdes->txdes0)&FTMAC100_TXDES0_TXPKT_EXSCOL;+}++staticboolftmac100_txdes_late_collision(structftmac100_txdes*txdes)+{+returnle32_to_cpu(txdes->txdes0)&FTMAC100_TXDES0_TXPKT_LATECOL;+}++staticvoidftmac100_txdes_set_end_of_ring(structftmac100_txdes*txdes)+{+unsignedinttmp=le32_to_cpu(txdes->txdes1);++tmp|=FTMAC100_TXDES1_EDOTR;+txdes->txdes1=cpu_to_le32(tmp);+}++staticvoidftmac100_txdes_set_first_segment(structftmac100_txdes*txdes)+{+unsignedinttmp=le32_to_cpu(txdes->txdes1);++tmp|=FTMAC100_TXDES1_FTS;+txdes->txdes1=cpu_to_le32(tmp);+}++staticvoidftmac100_txdes_set_last_segment(structftmac100_txdes*txdes)+{+unsignedinttmp=le32_to_cpu(txdes->txdes1);++tmp|=FTMAC100_TXDES1_LTS;+txdes->txdes1=cpu_to_le32(tmp);+}++staticvoidftmac100_txdes_set_txint(structftmac100_txdes*txdes)+{+unsignedinttmp=le32_to_cpu(txdes->txdes1);++tmp|=FTMAC100_TXDES1_TXIC;+txdes->txdes1=cpu_to_le32(tmp);+}++staticvoidftmac100_txdes_set_buffer_size(structftmac100_txdes*txdes,+unsignedintlen)+{+unsignedinttmp=le32_to_cpu(txdes->txdes1);++tmp|=FTMAC100_TXDES1_TXBUF_SIZE(len);+txdes->txdes1=cpu_to_le32(tmp);+}++staticvoidftmac100_txdes_set_dma_addr(structftmac100_txdes*txdes,+dma_addr_taddr)+{+txdes->txdes2=cpu_to_le32(addr);+}++staticdma_addr_tftmac100_txdes_get_dma_addr(structftmac100_txdes*txdes)+{+returnle32_to_cpu(txdes->txdes2);+}++/* txdes3 is not used by hardware, we use it to keep track of socket buffer */+staticvoidftmac100_txdes_set_skb(structftmac100_txdes*txdes,+structsk_buff*skb)+{+txdes->txdes3=cpu_to_le32(skb);+}++staticstructsk_buff*ftmac100_txdes_get_skb(structftmac100_txdes*txdes)+{+return(structsk_buff*)le32_to_cpu(txdes->txdes3);+}++/******************************************************************************+*internalfunctions(transmit)+*****************************************************************************/+staticintftmac100_next_tx_pointer(intpointer)+{+return(pointer+1)&(TX_QUEUE_ENTRIES-1);+}++staticvoidftmac100_tx_pointer_advance(structftmac100*priv)+{+priv->tx_pointer=ftmac100_next_tx_pointer(priv->tx_pointer);+}++staticvoidftmac100_tx_clean_pointer_advance(structftmac100*priv)+{+priv->tx_clean_pointer=ftmac100_next_tx_pointer(priv->tx_clean_pointer);+}++staticstructftmac100_txdes*ftmac100_current_txdes(structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_pointer];+}++staticstructftmac100_txdes*+ftmac100_current_clean_txdes(structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_clean_pointer];+}++staticboolftmac100_tx_complete_packet(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+structftmac100_txdes*txdes;+structsk_buff*skb;+dma_addr_tmap;++if(priv->tx_pending==0)+returnfalse;++txdes=ftmac100_current_clean_txdes(priv);++if(ftmac100_txdes_owned_by_dma(txdes))+returnfalse;++skb=ftmac100_txdes_get_skb(txdes);+map=ftmac100_txdes_get_dma_addr(txdes);++if(unlikely(ftmac100_txdes_excessive_collision(txdes)||+ftmac100_txdes_late_collision(txdes))){+/*+*packettransmittedtoethernetlostduetolatecollision+*orexcessivecollision+*/+netdev->stats.tx_aborted_errors++;+}else{+netdev->stats.tx_packets++;+netdev->stats.tx_bytes+=skb->len;+}++dma_unmap_single(priv->dev,map,skb_headlen(skb),DMA_TO_DEVICE);++dev_kfree_skb_irq(skb);++ftmac100_txdes_reset(txdes);++ftmac100_tx_clean_pointer_advance(priv);++priv->tx_pending--;+netif_wake_queue(netdev);++returntrue;+}++staticvoidftmac100_tx_complete(structftmac100*priv)+{+unsignedlongflags;++spin_lock_irqsave(&priv->tx_lock,flags);+while(ftmac100_tx_complete_packet(priv))+;+spin_unlock_irqrestore(&priv->tx_lock,flags);+}++staticintftmac100_xmit(structftmac100*priv,structsk_buff*skb,+dma_addr_tmap)+{+structnet_device*netdev=priv->netdev;+structftmac100_txdes*txdes;+unsignedintlen=(skb->len<ETH_ZLEN)?ETH_ZLEN:skb->len;+unsignedlongflags;++txdes=ftmac100_current_txdes(priv);+ftmac100_tx_pointer_advance(priv);++/* setup TX descriptor */++spin_lock_irqsave(&priv->tx_lock,flags);+ftmac100_txdes_set_skb(txdes,skb);+ftmac100_txdes_set_dma_addr(txdes,map);++ftmac100_txdes_set_first_segment(txdes);+ftmac100_txdes_set_last_segment(txdes);+ftmac100_txdes_set_txint(txdes);+ftmac100_txdes_set_buffer_size(txdes,len);++priv->tx_pending++;+if(priv->tx_pending==TX_QUEUE_ENTRIES){+if(net_ratelimit())+netdev_info(netdev,"tx queue full\n");++netif_stop_queue(netdev);+}++/* start transmit */+ftmac100_txdes_set_dma_own(txdes);+spin_unlock_irqrestore(&priv->tx_lock,flags);++ftmac100_txdma_start_polling(priv);++returnNETDEV_TX_OK;+}++/******************************************************************************+*internalfunctions(buffer)+*****************************************************************************/+staticvoidftmac100_free_buffers(structftmac100*priv)+{+inti;++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*page=ftmac100_rxdes_get_va(rxdes);++if(d)+dma_unmap_single(priv->dev,d,PAGE_SIZE,+DMA_FROM_DEVICE);++if(page!=NULL)+free_page((unsignedlong)page);+}++for(i=0;i<TX_QUEUE_ENTRIES;i++){+structftmac100_txdes*txdes=&priv->descs->txdes[i];+structsk_buff*skb=ftmac100_txdes_get_skb(txdes);++if(skb){+dma_addr_tmap;++map=ftmac100_txdes_get_dma_addr(txdes);+dma_unmap_single(priv->dev,map,skb_headlen(skb),+DMA_TO_DEVICE);+dev_kfree_skb(skb);+}+}++dma_free_coherent(priv->dev,sizeof(structftmac100_descs),+priv->descs,priv->descs_dma_addr);+}++staticintftmac100_alloc_buffers(structftmac100*priv)+{+inti;++priv->descs=dma_alloc_coherent(priv->dev,+sizeof(structftmac100_descs),+&priv->descs_dma_addr,+GFP_KERNEL|GFP_DMA);+if(priv->descs==NULL)+return-ENOMEM;++memset(priv->descs,0,sizeof(structftmac100_descs));++/* initialize RX ring */++ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES-1]);++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+void*page;+dma_addr_td;++page=(void*)__get_free_page(GFP_KERNEL|GFP_DMA);+if(page==NULL)+gotoerr;++d=dma_map_single(priv->dev,page,PAGE_SIZE,DMA_FROM_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,d))){+free_page((unsignedlong)page);+gotoerr;+}++/*+*Thehardwareenforcesasub-2Kmaximumpacketsize,sowe+*puttwobuffersoneveryhardwarepage.+*/+ftmac100_rxdes_set_va(rxdes,page);+ftmac100_rxdes_set_va(rxdes+1,page+PAGE_SIZE/2);++ftmac100_rxdes_set_dma_addr(rxdes,d);+ftmac100_rxdes_set_dma_addr(rxdes+1,d+PAGE_SIZE/2);++ftmac100_rxdes_set_buffer_size(rxdes,RX_BUF_SIZE);+ftmac100_rxdes_set_buffer_size(rxdes+1,RX_BUF_SIZE);++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rxdes_set_dma_own(rxdes+1);+}++/* initialize TX ring */++ftmac100_txdes_set_end_of_ring(&priv->descs->txdes[TX_QUEUE_ENTRIES-1]);+return0;++err:+ftmac100_free_buffers(priv);+return-ENOMEM;+}++/******************************************************************************+*structmii_if_infofunctions+*****************************************************************************/+staticintftmac100_mdio_read(structnet_device*netdev,intphy_id,intreg)+{+structftmac100*priv=netdev_priv(netdev);+unsignedintphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIRD;++iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);+for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIRD)==0)+returnphycr&FTMAC100_PHYCR_MIIRDATA;++usleep_range(100,1000);+}++netdev_err(netdev,"mdio read timed out\n");+return0;+}++staticvoidftmac100_mdio_write(structnet_device*netdev,intphy_id,intreg,+intdata)+{+structftmac100*priv=netdev_priv(netdev);+unsignedintphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIWR;++data=FTMAC100_PHYWDATA_MIIWDATA(data);++iowrite32(data,priv->base+FTMAC100_OFFSET_PHYWDATA);+iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);++for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIWR)==0)+return;++usleep_range(100,1000);+}++netdev_err(netdev,"mdio write timed out\n");+}++/******************************************************************************+*structethtool_opsfunctions+*****************************************************************************/+staticvoidftmac100_get_drvinfo(structnet_device*netdev,+structethtool_drvinfo*info)+{+strcpy(info->driver,DRV_NAME);+strcpy(info->version,DRV_VERSION);+strcpy(info->bus_info,dev_name(&netdev->dev));+}++staticintftmac100_get_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_gset(&priv->mii,cmd);+}++staticintftmac100_set_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_sset(&priv->mii,cmd);+}++staticintftmac100_nway_reset(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_nway_restart(&priv->mii);+}++staticu32ftmac100_get_link(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_link_ok(&priv->mii);+}++staticconststructethtool_opsftmac100_ethtool_ops={+.set_settings=ftmac100_set_settings,+.get_settings=ftmac100_get_settings,+.get_drvinfo=ftmac100_get_drvinfo,+.nway_reset=ftmac100_nway_reset,+.get_link=ftmac100_get_link,+};++/******************************************************************************+*interrupthandler+*****************************************************************************/+staticirqreturn_tftmac100_interrupt(intirq,void*dev_id)+{+structnet_device*netdev=dev_id;+structftmac100*priv=netdev_priv(netdev);++if(likely(netif_running(netdev))){+/* Disable interrupts for polling */+ftmac100_disable_all_int(priv);+napi_schedule(&priv->napi);+}++returnIRQ_HANDLED;+}++/******************************************************************************+*structnapi_structfunctions+*****************************************************************************/+staticintftmac100_poll(structnapi_struct*napi,intbudget)+{+structftmac100*priv=container_of(napi,structftmac100,napi);+structnet_device*netdev=priv->netdev;+unsignedintstatus;+boolcompleted=true;+intrx=0;++status=ioread32(priv->base+FTMAC100_OFFSET_ISR);++if(status&(FTMAC100_INT_RPKT_FINISH|FTMAC100_INT_NORXBUF)){+/*+*FTMAC100_INT_RPKT_FINISH:+*RXDMAhasreceivedpacketsintoRXbuffersuccessfully+*+*FTMAC100_INT_NORXBUF:+*RXbufferunavailable+*/+boolretry;++do{+retry=ftmac100_rx_packet(priv,&rx);+}while(retry&&rx<budget);++if(retry&&rx==budget)+completed=false;+}++if(status&FTMAC100_INT_NORXBUF){+/* RX buffer unavailable */+if(net_ratelimit())+netdev_info(netdev,"INT_NORXBUF\n");++netdev->stats.rx_over_errors++;+}++if(status&(FTMAC100_INT_XPKT_OK|FTMAC100_INT_XPKT_LOST)){+/*+*FTMAC100_INT_XPKT_OK:+*packettransmittedtoethernetsuccessfully+*+*FTMAC100_INT_XPKT_LOST:+*packettransmittedtoethernetlostduetolate+*collisionorexcessivecollision+*/+ftmac100_tx_complete(priv);+}++if(status&FTMAC100_INT_RPKT_LOST){+/* received packet lost due to RX FIFO full */+if(net_ratelimit())+netdev_info(netdev,"INT_RPKT_LOST\n");++netdev->stats.rx_fifo_errors++;+}++if(status&FTMAC100_INT_AHB_ERR){+/* AHB error */+if(net_ratelimit())+netdev_info(netdev,"INT_AHB_ERR\n");++/* do nothing */+}++if(status&FTMAC100_INT_PHYSTS_CHG){+/* PHY link status change */+if(net_ratelimit())+netdev_info(netdev,"INT_PHYSTS_CHG\n");++mii_check_link(&priv->mii);+}++if(completed){+/* stop polling */+napi_complete(napi);+ftmac100_enable_all_int(priv);+}++returnrx;+}++/******************************************************************************+*structnet_device_opsfunctions+*****************************************************************************/+staticintftmac100_open(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+interr;++err=ftmac100_alloc_buffers(priv);+if(err){+netdev_err(netdev,"failed to allocate buffers\n");+gotoerr_alloc;+}++err=request_irq(priv->irq,ftmac100_interrupt,0,netdev->name,+netdev);+if(err){+netdev_err(netdev,"failed to request irq %d\n",priv->irq);+gotoerr_irq;+}++priv->rx_pointer=0;+priv->tx_clean_pointer=0;+priv->tx_pointer=0;+priv->tx_pending=0;++err=ftmac100_start_hw(priv);+if(err)+gotoerr_hw;++napi_enable(&priv->napi);+netif_start_queue(netdev);++ftmac100_enable_all_int(priv);+return0;++err_hw:+free_irq(priv->irq,netdev);+err_irq:+ftmac100_free_buffers(priv);+err_alloc:+returnerr;+}++staticintftmac100_stop(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);++ftmac100_disable_all_int(priv);+netif_stop_queue(netdev);+napi_disable(&priv->napi);+ftmac100_stop_hw(priv);+free_irq(priv->irq,netdev);+ftmac100_free_buffers(priv);++return0;+}++staticintftmac100_hard_start_xmit(structsk_buff*skb,+structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+dma_addr_tmap;++if(unlikely(skb->len>MAX_PKT_SIZE)){+if(net_ratelimit())+netdev_dbg(netdev,"tx packet too big\n");++netdev->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++map=dma_map_single(priv->dev,skb->data,skb_headlen(skb),DMA_TO_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,map))){+/* drop packet */+if(net_ratelimit())+netdev_err(netdev,"map socket buffer failed\n");++netdev->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++returnftmac100_xmit(priv,skb,map);+}++/* optional */+staticintftmac100_do_ioctl(structnet_device*netdev,structifreq*ifr,+intcmd)+{+structftmac100*priv=netdev_priv(netdev);+structmii_ioctl_data*data=if_mii(ifr);++returngeneric_mii_ioctl(&priv->mii,data,cmd,NULL);+}++staticconststructnet_device_opsftmac100_netdev_ops={+.ndo_open=ftmac100_open,+.ndo_stop=ftmac100_stop,+.ndo_start_xmit=ftmac100_hard_start_xmit,+.ndo_set_mac_address=eth_mac_addr,+.ndo_validate_addr=eth_validate_addr,+.ndo_do_ioctl=ftmac100_do_ioctl,+};++/******************************************************************************+*structplatform_driverfunctions+*****************************************************************************/+staticintftmac100_remove(structplatform_device*pdev)+{+structnet_device*netdev;+structftmac100*priv;++netdev=platform_get_drvdata(pdev);+if(netdev==NULL)+return0;++platform_set_drvdata(pdev,NULL);++priv=netdev_priv(netdev);++netif_napi_del(&priv->napi);+unregister_netdev(netdev);++if(priv->base!=NULL)+iounmap(priv->base);++if(priv->res!=NULL)+release_resource(priv->res);++free_netdev(netdev);+return0;+}++staticintftmac100_probe(structplatform_device*pdev)+{+structresource*res;+intirq;+structnet_device*netdev;+structftmac100*priv;+interr;++if(!pdev)+return-ENODEV;++res=platform_get_resource(pdev,IORESOURCE_MEM,0);+if(!res)+return-ENXIO;++irq=platform_get_irq(pdev,0);+if(irq<0)+returnirq;++/* setup net_device */++netdev=alloc_etherdev(sizeof(structftmac100));+if(netdev==NULL){+err=-ENOMEM;+gotoerr_out;+}++SET_NETDEV_DEV(netdev,&pdev->dev);+SET_ETHTOOL_OPS(netdev,&ftmac100_ethtool_ops);+netdev->netdev_ops=&ftmac100_netdev_ops;++platform_set_drvdata(pdev,netdev);++/* setup private data */++priv=netdev_priv(netdev);+priv->netdev=netdev;+priv->dev=&pdev->dev;++spin_lock_init(&priv->tx_lock);++/* initialize NAPI */+netif_napi_add(netdev,&priv->napi,ftmac100_poll,64);++/* map io memory */+priv->res=request_mem_region(res->start,res->end-res->start,+dev_name(&pdev->dev));+if(priv->res==NULL){+dev_err(&pdev->dev,"Could not reserve memory region\n");+err=-ENOMEM;+gotoerr_out;+}++priv->base=ioremap(res->start,res->end-res->start);+if(priv->base==NULL){+dev_err(&pdev->dev,"Failed to ioremap ethernet registers\n");+err=-EIO;+gotoerr_out;+}++priv->irq=irq;++/* initialize struct mii_if_info */++priv->mii.phy_id=0;+priv->mii.phy_id_mask=0x1f;+priv->mii.reg_num_mask=0x1f;+priv->mii.dev=netdev;+priv->mii.mdio_read=ftmac100_mdio_read;+priv->mii.mdio_write=ftmac100_mdio_write;++/* register network device */++err=register_netdev(netdev);+if(err){+dev_err(&pdev->dev,"Failed to register netdev\n");+gotoerr_out;+}++netdev_info(netdev,"irq %d, mapped at %p\n",priv->irq,priv->base);++if(!is_valid_ether_addr(netdev->dev_addr)){+random_ether_addr(netdev->dev_addr);+netdev_info(netdev,"generated random MAC address %pM\n",+netdev->dev_addr);+}++return0;++err_out:+ftmac100_remove(pdev);+returnerr;+}++staticstructplatform_driverftmac100_driver={+.probe=ftmac100_probe,+.remove=ftmac100_remove,+.driver={+.name=DRV_NAME,+.owner=THIS_MODULE,+},+};++/******************************************************************************+*initialization/finalization+*****************************************************************************/+staticint__initftmac100_init(void)+{+pr_info("Loading version "DRV_VERSION" ...\n");+returnplatform_driver_register(&ftmac100_driver);+}++staticvoid__exitftmac100_exit(void)+{+platform_driver_unregister(&ftmac100_driver);+}++module_init(ftmac100_init);+module_exit(ftmac100_exit);++MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");+MODULE_DESCRIPTION("FTMAC100 driver");+MODULE_LICENSE("GPL");
Hmm, after call to netif_receive_skb(skb), you are not allowed to access
skb anymore (maybe it was freed)
netdev->stats.rx_packets++;
netdev->stats.rx_bytes += skb->len;
netif_receive_skb(skb);
From: Eric Dumazet <hidden> Date: 2011-01-20 15:41:51
Le jeudi 20 janvier 2011 à 23:30 +0800, Po-Yu Chuang a écrit :
quoted hunk
+static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)+{+ struct net_device *netdev = priv->netdev;+ struct ftmac100_txdes *txdes;+ struct sk_buff *skb;+ dma_addr_t map;++ if (priv->tx_pending == 0)+ return false;++ txdes = ftmac100_current_clean_txdes(priv);++ if (ftmac100_txdes_owned_by_dma(txdes))+ return false;++ skb = ftmac100_txdes_get_skb(txdes);+ map = ftmac100_txdes_get_dma_addr(txdes);++ if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||+ ftmac100_txdes_late_collision(txdes))) {+ /*+ * packet transmitted to ethernet lost due to late collision+ * or excessive collision+ */+ netdev->stats.tx_aborted_errors++;+ } else {+ netdev->stats.tx_packets++;+ netdev->stats.tx_bytes += skb->len;+ }++ dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);++ dev_kfree_skb_irq(skb);++ ftmac100_txdes_reset(txdes);++ ftmac100_tx_clean_pointer_advance(priv);++ priv->tx_pending--;+ netif_wake_queue(netdev);++ return true;+}++static void ftmac100_tx_complete(struct ftmac100 *priv)+{+ unsigned long flags;++ spin_lock_irqsave(&priv->tx_lock, flags);+ while (ftmac100_tx_complete_packet(priv))+ ;+ spin_unlock_irqrestore(&priv->tx_lock, flags);+}+
I dont understand why you still block hard IRQS, after full NAPI
conversion.
Now you run from NAPI, and softirq handler, are you sure you still need
to block hard IRQ and tx_lock ?
It seems to me ftmac100_xmit() could only block softirqs (but they
already are blocked by caller), so you could use spin_lock() from
ftmac100_xmit()
Hmm, after call to netif_receive_skb(skb), you are not allowed to access
skb anymore (maybe it was freed)
netdev->stats.rx_packets++;
netdev->stats.rx_bytes += skb->len;
netif_receive_skb(skb);
Dear Eric,
On Thu, Jan 20, 2011 at 11:41 PM, Eric Dumazet [off-list ref] wrote:
Le jeudi 20 janvier 2011 à 23:30 +0800, Po-Yu Chuang a écrit :
quoted
+static bool ftmac100_tx_complete_packet(struct ftmac100 *priv)+{+ struct net_device *netdev = priv->netdev;+ struct ftmac100_txdes *txdes;+ struct sk_buff *skb;+ dma_addr_t map;++ if (priv->tx_pending == 0)+ return false;++ txdes = ftmac100_current_clean_txdes(priv);++ if (ftmac100_txdes_owned_by_dma(txdes))+ return false;++ skb = ftmac100_txdes_get_skb(txdes);+ map = ftmac100_txdes_get_dma_addr(txdes);++ if (unlikely(ftmac100_txdes_excessive_collision(txdes) ||+ ftmac100_txdes_late_collision(txdes))) {+ /*+ * packet transmitted to ethernet lost due to late collision+ * or excessive collision+ */+ netdev->stats.tx_aborted_errors++;+ } else {+ netdev->stats.tx_packets++;+ netdev->stats.tx_bytes += skb->len;+ }++ dma_unmap_single(priv->dev, map, skb_headlen(skb), DMA_TO_DEVICE);++ dev_kfree_skb_irq(skb);++ ftmac100_txdes_reset(txdes);++ ftmac100_tx_clean_pointer_advance(priv);++ priv->tx_pending--;+ netif_wake_queue(netdev);++ return true;+}++static void ftmac100_tx_complete(struct ftmac100 *priv)+{+ unsigned long flags;++ spin_lock_irqsave(&priv->tx_lock, flags);+ while (ftmac100_tx_complete_packet(priv))+ ;+ spin_unlock_irqrestore(&priv->tx_lock, flags);+}+
I dont understand why you still block hard IRQS, after full NAPI
conversion.
Now you run from NAPI, and softirq handler, are you sure you still need
to block hard IRQ and tx_lock ?
It seems to me ftmac100_xmit() could only block softirqs (but they
already are blocked by caller), so you could use spin_lock() from
ftmac100_xmit()
I was not quite clear about when to use what kinds of locking.
After your explanation, now I understand.
I will submit v4 tomorrow.
really appreciate,
Po-Yu Chuang
+/******************************************************************************+ * struct napi_struct functions+ *****************************************************************************/+static int ftmac100_poll(struct napi_struct *napi, int budget)+{+ struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);+ struct net_device *netdev = priv->netdev;+ unsigned int status;+ bool completed = true;+ int rx = 0;++ status = ioread32(priv->base + FTMAC100_OFFSET_ISR);++ if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {+ /*+ * FTMAC100_INT_RPKT_FINISH:+ * RX DMA has received packets into RX buffer successfully+ *+ * FTMAC100_INT_NORXBUF:+ * RX buffer unavailable+ */+ bool retry;++ do {+ retry = ftmac100_rx_packet(priv, &rx);+ } while (retry && rx < budget);++ if (retry && rx == budget)+ completed = false;
Is it useful to retry the NORXBUF case?
quoted hunk
+ }++ if (status & FTMAC100_INT_NORXBUF) {+ /* RX buffer unavailable */+ if (net_ratelimit())+ netdev_info(netdev, "INT_NORXBUF\n");++ netdev->stats.rx_over_errors++;+ }
Perhaps this "if (status & FTMAC100_INT_NORXBUF)" block should be
moved into the test block above it before the retry?
quoted hunk
++ if (status & (FTMAC100_INT_XPKT_OK | FTMAC100_INT_XPKT_LOST)) {+ /*+ * FTMAC100_INT_XPKT_OK:+ * packet transmitted to ethernet successfully+ *+ * FTMAC100_INT_XPKT_LOST:+ * packet transmitted to ethernet lost due to late+ * collision or excessive collision+ */+ ftmac100_tx_complete(priv);+ }++ if (status & FTMAC100_INT_RPKT_LOST) {+ /* received packet lost due to RX FIFO full */+ if (net_ratelimit())+ netdev_info(netdev, "INT_RPKT_LOST\n");++ netdev->stats.rx_fifo_errors++;+ }++ if (status & FTMAC100_INT_AHB_ERR) {+ /* AHB error */+ if (net_ratelimit())+ netdev_info(netdev, "INT_AHB_ERR\n");++ /* do nothing */+ }++ if (status & FTMAC100_INT_PHYSTS_CHG) {+ /* PHY link status change */+ if (net_ratelimit())+ netdev_info(netdev, "INT_PHYSTS_CHG\n");++ mii_check_link(&priv->mii);+ }++ if (completed) {+ /* stop polling */+ napi_complete(napi);+ ftmac100_enable_all_int(priv);+ }++ return rx;+}
It's possible to miss multiple states because of the ratelimit.
If multiple ISR status bits are possible, it might be better to
combine all netdev_info uses into a single call.
Something like:
if ((status & (FTMAC100_INT_NORXBUF | FTMAC100_INT_RPKT_LOST |
FTMAC100_INT_AHB_ERR | FTMAC100_INT_PHYSTS_CHG)) &&
net_ratelimit())
netdev_info(netdev, "ISR status: %x%s%s%s%s\n",
status & FTMAC100_INT_NORXBUF ? ": INT_NORXBUF" : "",
status & FTMAC100_INT_RPKT_LOST ? ": INT_RPKT_LOST" : "",
status & FTMAC100_INT_AHB_ERR ? ": INT_AHB_ERR" : "",
status & FTMAC100_INT_PHYSTS_CHG ? " : INT_PHYSTS_CHG" : "");
[...]
You can change these and similar functions to use:
rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_LRS)
This will be subject to constant folding by compiler and produce
better code for big-endian arches.
Best Regards,
Michał Mirosław
[...]
You can change these and similar functions to use:
rxdes->rxdes0 & cpu_to_le32(FTMAC100_RXDES0_LRS)
This will be subject to constant folding by compiler and produce
better code for big-endian arches.
Fixed. It looks much better now.
Thanks a lot.
best regards,
Po-Yu Chuang
+/******************************************************************************+ * struct napi_struct functions+ *****************************************************************************/+static int ftmac100_poll(struct napi_struct *napi, int budget)+{+ struct ftmac100 *priv = container_of(napi, struct ftmac100, napi);+ struct net_device *netdev = priv->netdev;+ unsigned int status;+ bool completed = true;+ int rx = 0;++ status = ioread32(priv->base + FTMAC100_OFFSET_ISR);++ if (status & (FTMAC100_INT_RPKT_FINISH | FTMAC100_INT_NORXBUF)) {+ /*+ * FTMAC100_INT_RPKT_FINISH:+ * RX DMA has received packets into RX buffer successfully+ *+ * FTMAC100_INT_NORXBUF:+ * RX buffer unavailable+ */+ bool retry;++ do {+ retry = ftmac100_rx_packet(priv, &rx);+ } while (retry && rx < budget);++ if (retry && rx == budget)+ completed = false;
Is it useful to retry the NORXBUF case?
The idea is that if I miss packet finished interrupts (then rx buffers used up),
I should retrieve the received packets ASAP to free buffers to HW.
Maybe this is really unnecessary.
I am not quite sure, but I'll do your advice now.
quoted
+ }++ if (status & FTMAC100_INT_NORXBUF) {+ /* RX buffer unavailable */+ if (net_ratelimit())+ netdev_info(netdev, "INT_NORXBUF\n");++ netdev->stats.rx_over_errors++;+ }
Perhaps this "if (status & FTMAC100_INT_NORXBUF)" block should be
moved into the test block above it before the retry?
Since status is not changed in the function, it does not matter where
the test is.
But I agree that it is better to handle error cases earlier.
It's possible to miss multiple states because of the ratelimit.
If multiple ISR status bits are possible, it might be better to
combine all netdev_info uses into a single call.
Something like:
if ((status & (FTMAC100_INT_NORXBUF | FTMAC100_INT_RPKT_LOST |
FTMAC100_INT_AHB_ERR | FTMAC100_INT_PHYSTS_CHG)) &&
net_ratelimit())
netdev_info(netdev, "ISR status: %x%s%s%s%s\n",
status & FTMAC100_INT_NORXBUF ? ": INT_NORXBUF" : "",
status & FTMAC100_INT_RPKT_LOST ? ": INT_RPKT_LOST" : "",
status & FTMAC100_INT_AHB_ERR ? ": INT_AHB_ERR" : "",
status & FTMAC100_INT_PHYSTS_CHG ? " : INT_PHYSTS_CHG" : "");
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts
v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix
v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1207 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 +++++++
4 files changed, 1397 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ftmac100.c
create mode 100644 drivers/net/ftmac100.h
@@ -0,0 +1,1207 @@+/*+*FaradayFTMAC10010/100Ethernet+*+*(C)Copyright2009-2011FaradayTechnology+*Po-YuChuang<ratbert@faraday-tech.com>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,writetotheFreeSoftware+*Foundation,Inc.,675MassAve,Cambridge,MA02139,USA.+*/++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/dma-mapping.h>+#include<linux/etherdevice.h>+#include<linux/ethtool.h>+#include<linux/init.h>+#include<linux/io.h>+#include<linux/kernel.h>+#include<linux/mii.h>+#include<linux/module.h>+#include<linux/moduleparam.h>+#include<linux/netdevice.h>+#include<linux/platform_device.h>++#include"ftmac100.h"++#define DRV_NAME "ftmac100"+#define DRV_VERSION "0.2"++#define RX_QUEUE_ENTRIES 128 /* must be power of 2 */+#define TX_QUEUE_ENTRIES 16 /* must be power of 2 */++#define MAX_PKT_SIZE 1518+#define RX_BUF_SIZE 2044 /* must be smaller than 0x7ff */++/******************************************************************************+*privatedata+*****************************************************************************/+structftmac100_descs{+structftmac100_rxdesrxdes[RX_QUEUE_ENTRIES];+structftmac100_txdestxdes[TX_QUEUE_ENTRIES];+};++structftmac100{+structresource*res;+void*base;+intirq;++structftmac100_descs*descs;+dma_addr_tdescs_dma_addr;++unsignedintrx_pointer;+unsignedinttx_clean_pointer;+unsignedinttx_pointer;+unsignedinttx_pending;++spinlock_ttx_lock;++structnet_device*netdev;+structdevice*dev;+structnapi_structnapi;++structmii_if_infomii;+};++/******************************************************************************+*internalfunctions(hardwareregisteraccess)+*****************************************************************************/+#define INT_MASK_ALL_ENABLED (FTMAC100_INT_RPKT_FINISH | \+FTMAC100_INT_NORXBUF|\+FTMAC100_INT_XPKT_OK|\+FTMAC100_INT_XPKT_LOST|\+FTMAC100_INT_RPKT_LOST|\+FTMAC100_INT_AHB_ERR|\+FTMAC100_INT_PHYSTS_CHG)++staticvoidftmac100_enable_all_int(structftmac100*priv)+{+iowrite32(INT_MASK_ALL_ENABLED,priv->base+FTMAC100_OFFSET_IMR);+}++staticvoidftmac100_disable_all_int(structftmac100*priv)+{+iowrite32(0,priv->base+FTMAC100_OFFSET_IMR);+}++staticvoidftmac100_set_rx_ring_base(structftmac100*priv,dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_RXR_BADR);+}++staticvoidftmac100_set_tx_ring_base(structftmac100*priv,dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_TXR_BADR);+}++staticvoidftmac100_txdma_start_polling(structftmac100*priv)+{+iowrite32(1,priv->base+FTMAC100_OFFSET_TXPD);+}++staticintftmac100_reset(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+inti;++/* NOTE: reset clears all registers */+iowrite32(FTMAC100_MACCR_SW_RST,priv->base+FTMAC100_OFFSET_MACCR);++for(i=0;i<5;i++){+unsignedintmaccr;++maccr=ioread32(priv->base+FTMAC100_OFFSET_MACCR);+if(!(maccr&FTMAC100_MACCR_SW_RST)){+/*+*FTMAC100_MACCR_SW_RSTcleareddoesnotindicate+*thathardwareresetcompleted(whatthef*ck).+*Westillneedtowaitforawhile.+*/+usleep_range(500,1000);+return0;+}++usleep_range(1000,10000);+}++netdev_err(netdev,"software reset failed\n");+return-EIO;+}++staticvoidftmac100_set_mac(structftmac100*priv,constunsignedchar*mac)+{+unsignedintmaddr=mac[0]<<8|mac[1];+unsignedintladdr=mac[2]<<24|mac[3]<<16|mac[4]<<8|mac[5];++iowrite32(maddr,priv->base+FTMAC100_OFFSET_MAC_MADR);+iowrite32(laddr,priv->base+FTMAC100_OFFSET_MAC_LADR);+}++#define MACCR_ENABLE_ALL (FTMAC100_MACCR_XMT_EN | \+FTMAC100_MACCR_RCV_EN|\+FTMAC100_MACCR_XDMA_EN|\+FTMAC100_MACCR_RDMA_EN|\+FTMAC100_MACCR_CRC_APD|\+FTMAC100_MACCR_FULLDUP|\+FTMAC100_MACCR_RX_RUNT|\+FTMAC100_MACCR_RX_BROADPKT)++staticintftmac100_start_hw(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;++if(ftmac100_reset(priv))+return-EIO;++/* setup ring buffer base registers */++ftmac100_set_rx_ring_base(priv,+priv->descs_dma_addr++offsetof(structftmac100_descs,rxdes));+ftmac100_set_tx_ring_base(priv,+priv->descs_dma_addr++offsetof(structftmac100_descs,txdes));++iowrite32(FTMAC100_APTC_RXPOLL_CNT(1),priv->base+FTMAC100_OFFSET_APTC);++ftmac100_set_mac(priv,netdev->dev_addr);++iowrite32(MACCR_ENABLE_ALL,priv->base+FTMAC100_OFFSET_MACCR);+return0;+}++staticvoidftmac100_stop_hw(structftmac100*priv)+{+iowrite32(0,priv->base+FTMAC100_OFFSET_MACCR);+}++/******************************************************************************+*internalfunctions(receivedescriptor)+*****************************************************************************/+staticboolftmac100_rxdes_first_segment(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_FRS);+}++staticboolftmac100_rxdes_last_segment(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_LRS);+}++staticboolftmac100_rxdes_owned_by_dma(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);+}++staticvoidftmac100_rxdes_set_dma_own(structftmac100_rxdes*rxdes)+{+/* clear status bits */+rxdes->rxdes0=cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);+}++staticboolftmac100_rxdes_rx_error(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RX_ERR);+}++staticboolftmac100_rxdes_crc_error(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_CRC_ERR);+}++staticboolftmac100_rxdes_frame_too_long(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_FTL);+}++staticboolftmac100_rxdes_runt(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RUNT);+}++staticboolftmac100_rxdes_odd_nibble(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RX_ODD_NB);+}++staticunsignedintftmac100_rxdes_frame_length(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RFL);+}++staticboolftmac100_rxdes_multicast(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_MULTICAST);+}++staticvoidftmac100_rxdes_set_buffer_size(structftmac100_rxdes*rxdes,+unsignedintsize)+{+rxdes->rxdes1&=cpu_to_le32(FTMAC100_RXDES1_EDORR);+rxdes->rxdes1|=cpu_to_le32(FTMAC100_RXDES1_RXBUF_SIZE(size));+}++staticvoidftmac100_rxdes_set_end_of_ring(structftmac100_rxdes*rxdes)+{+rxdes->rxdes1|=cpu_to_le32(FTMAC100_RXDES1_EDORR);+}++staticvoidftmac100_rxdes_set_dma_addr(structftmac100_rxdes*rxdes,+dma_addr_taddr)+{+rxdes->rxdes2=cpu_to_le32(addr);+}++staticdma_addr_tftmac100_rxdes_get_dma_addr(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes2);+}++/* rxdes3 is not used by hardware, we use it to keep track of buffer */+staticvoidftmac100_rxdes_set_va(structftmac100_rxdes*rxdes,void*addr)+{+rxdes->rxdes3=cpu_to_le32(addr);+}++staticvoid*ftmac100_rxdes_get_va(structftmac100_rxdes*rxdes)+{+return(void*)le32_to_cpu(rxdes->rxdes3);+}++/******************************************************************************+*internalfunctions(receive)+*****************************************************************************/+staticintftmac100_next_rx_pointer(intpointer)+{+return(pointer+1)&(RX_QUEUE_ENTRIES-1);+}++staticvoidftmac100_rx_pointer_advance(structftmac100*priv)+{+priv->rx_pointer=ftmac100_next_rx_pointer(priv->rx_pointer);+}++staticstructftmac100_rxdes*ftmac100_current_rxdes(structftmac100*priv)+{+return&priv->descs->rxdes[priv->rx_pointer];+}++staticstructftmac100_rxdes*+ftmac100_rx_locate_first_segment(structftmac100*priv)+{+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);++while(!ftmac100_rxdes_owned_by_dma(rxdes)){+if(ftmac100_rxdes_first_segment(rxdes))+returnrxdes;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}++returnNULL;+}++staticboolftmac100_rx_packet_error(structftmac100*priv,+structftmac100_rxdes*rxdes)+{+structnet_device*netdev=priv->netdev;+boolerror=false;++if(unlikely(ftmac100_rxdes_rx_error(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx err\n");++netdev->stats.rx_errors++;+error=true;+}++if(unlikely(ftmac100_rxdes_crc_error(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx crc err\n");++netdev->stats.rx_crc_errors++;+error=true;+}++if(unlikely(ftmac100_rxdes_frame_too_long(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx frame too long\n");++netdev->stats.rx_length_errors++;+error=true;+}elseif(unlikely(ftmac100_rxdes_runt(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx runt\n");++netdev->stats.rx_length_errors++;+error=true;+}elseif(unlikely(ftmac100_rxdes_odd_nibble(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx odd nibble\n");++netdev->stats.rx_length_errors++;+error=true;+}++returnerror;+}++staticvoidftmac100_rx_drop_packet(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);+booldone=false;++if(net_ratelimit())+netdev_dbg(netdev,"drop packet %p\n",rxdes);++do{+if(ftmac100_rxdes_last_segment(rxdes))+done=true;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}while(!done&&!ftmac100_rxdes_owned_by_dma(rxdes));++netdev->stats.rx_dropped++;+}++staticboolftmac100_rx_packet(structftmac100*priv,int*processed)+{+structnet_device*netdev=priv->netdev;+structftmac100_rxdes*rxdes;+structsk_buff*skb;+intlength;+boolcopied=false;+booldone=false;++rxdes=ftmac100_rx_locate_first_segment(priv);+if(!rxdes)+returnfalse;++if(unlikely(ftmac100_rx_packet_error(priv,rxdes))){+ftmac100_rx_drop_packet(priv);+returntrue;+}++/* start processing */++length=ftmac100_rxdes_frame_length(rxdes);++skb=netdev_alloc_skb_ip_align(netdev,length);+if(unlikely(!skb)){+if(net_ratelimit())+netdev_err(netdev,"rx skb alloc failed\n");++ftmac100_rx_drop_packet(priv);+returntrue;+}++if(unlikely(ftmac100_rxdes_multicast(rxdes)))+netdev->stats.multicast++;++do{+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*buf=ftmac100_rxdes_get_va(rxdes);+intsize;++size=min(length-copied,RX_BUF_SIZE);++dma_sync_single_for_cpu(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);+memcpy(skb_put(skb,size),buf,size);++copied+=size;++if(ftmac100_rxdes_last_segment(rxdes))+done=true;++dma_sync_single_for_device(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);++ftmac100_rxdes_set_dma_own(rxdes);++ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}while(!done&&copied<length);++skb->protocol=eth_type_trans(skb,netdev);++netdev->stats.rx_packets++;+netdev->stats.rx_bytes+=skb->len;++/* push packet to protocol stack */+netif_receive_skb(skb);++(*processed)++;+returntrue;+}++/******************************************************************************+*internalfunctions(transmitdescriptor)+*****************************************************************************/+staticvoidftmac100_txdes_reset(structftmac100_txdes*txdes)+{+/* clear all except end of ring bit */+txdes->txdes0=0;+txdes->txdes1&=FTMAC100_TXDES1_EDOTR;+txdes->txdes2=0;+txdes->txdes3=0;+}++staticboolftmac100_txdes_owned_by_dma(structftmac100_txdes*txdes)+{+returntxdes->txdes0&cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);+}++staticvoidftmac100_txdes_set_dma_own(structftmac100_txdes*txdes)+{+/*+*Makesuredmaownbitwillnotbesetbeforeanyother+*descriptorfields.+*/+wmb();+txdes->txdes0|=cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);+}++staticboolftmac100_txdes_excessive_collision(structftmac100_txdes*txdes)+{+returntxdes->txdes0&cpu_to_le32(FTMAC100_TXDES0_TXPKT_EXSCOL);+}++staticboolftmac100_txdes_late_collision(structftmac100_txdes*txdes)+{+returntxdes->txdes0&cpu_to_le32(FTMAC100_TXDES0_TXPKT_LATECOL);+}++staticvoidftmac100_txdes_set_end_of_ring(structftmac100_txdes*txdes)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_EDOTR);+}++staticvoidftmac100_txdes_set_first_segment(structftmac100_txdes*txdes)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_FTS);+}++staticvoidftmac100_txdes_set_last_segment(structftmac100_txdes*txdes)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_LTS);+}++staticvoidftmac100_txdes_set_txint(structftmac100_txdes*txdes)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_TXIC);+}++staticvoidftmac100_txdes_set_buffer_size(structftmac100_txdes*txdes,+unsignedintlen)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_TXBUF_SIZE(len));+}++staticvoidftmac100_txdes_set_dma_addr(structftmac100_txdes*txdes,+dma_addr_taddr)+{+txdes->txdes2=cpu_to_le32(addr);+}++staticdma_addr_tftmac100_txdes_get_dma_addr(structftmac100_txdes*txdes)+{+returnle32_to_cpu(txdes->txdes2);+}++/* txdes3 is not used by hardware, we use it to keep track of socket buffer */+staticvoidftmac100_txdes_set_skb(structftmac100_txdes*txdes,+structsk_buff*skb)+{+txdes->txdes3=cpu_to_le32(skb);+}++staticstructsk_buff*ftmac100_txdes_get_skb(structftmac100_txdes*txdes)+{+return(structsk_buff*)le32_to_cpu(txdes->txdes3);+}++/******************************************************************************+*internalfunctions(transmit)+*****************************************************************************/+staticintftmac100_next_tx_pointer(intpointer)+{+return(pointer+1)&(TX_QUEUE_ENTRIES-1);+}++staticvoidftmac100_tx_pointer_advance(structftmac100*priv)+{+priv->tx_pointer=ftmac100_next_tx_pointer(priv->tx_pointer);+}++staticvoidftmac100_tx_clean_pointer_advance(structftmac100*priv)+{+priv->tx_clean_pointer=ftmac100_next_tx_pointer(priv->tx_clean_pointer);+}++staticstructftmac100_txdes*ftmac100_current_txdes(structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_pointer];+}++staticstructftmac100_txdes*+ftmac100_current_clean_txdes(structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_clean_pointer];+}++staticboolftmac100_tx_complete_packet(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+structftmac100_txdes*txdes;+structsk_buff*skb;+dma_addr_tmap;++if(priv->tx_pending==0)+returnfalse;++txdes=ftmac100_current_clean_txdes(priv);++if(ftmac100_txdes_owned_by_dma(txdes))+returnfalse;++skb=ftmac100_txdes_get_skb(txdes);+map=ftmac100_txdes_get_dma_addr(txdes);++if(unlikely(ftmac100_txdes_excessive_collision(txdes)||+ftmac100_txdes_late_collision(txdes))){+/*+*packettransmittedtoethernetlostduetolatecollision+*orexcessivecollision+*/+netdev->stats.tx_aborted_errors++;+}else{+netdev->stats.tx_packets++;+netdev->stats.tx_bytes+=skb->len;+}++dma_unmap_single(priv->dev,map,skb_headlen(skb),DMA_TO_DEVICE);++dev_kfree_skb_irq(skb);++ftmac100_txdes_reset(txdes);++ftmac100_tx_clean_pointer_advance(priv);++priv->tx_pending--;+netif_wake_queue(netdev);++returntrue;+}++staticvoidftmac100_tx_complete(structftmac100*priv)+{+spin_lock(&priv->tx_lock);+while(ftmac100_tx_complete_packet(priv))+;+spin_unlock(&priv->tx_lock);+}++staticintftmac100_xmit(structftmac100*priv,structsk_buff*skb,+dma_addr_tmap)+{+structnet_device*netdev=priv->netdev;+structftmac100_txdes*txdes;+unsignedintlen=(skb->len<ETH_ZLEN)?ETH_ZLEN:skb->len;++txdes=ftmac100_current_txdes(priv);+ftmac100_tx_pointer_advance(priv);++/* setup TX descriptor */++spin_lock(&priv->tx_lock);+ftmac100_txdes_set_skb(txdes,skb);+ftmac100_txdes_set_dma_addr(txdes,map);++ftmac100_txdes_set_first_segment(txdes);+ftmac100_txdes_set_last_segment(txdes);+ftmac100_txdes_set_txint(txdes);+ftmac100_txdes_set_buffer_size(txdes,len);++priv->tx_pending++;+if(priv->tx_pending==TX_QUEUE_ENTRIES){+if(net_ratelimit())+netdev_info(netdev,"tx queue full\n");++netif_stop_queue(netdev);+}++/* start transmit */+ftmac100_txdes_set_dma_own(txdes);+spin_unlock(&priv->tx_lock);++ftmac100_txdma_start_polling(priv);++returnNETDEV_TX_OK;+}++/******************************************************************************+*internalfunctions(buffer)+*****************************************************************************/+staticvoidftmac100_free_buffers(structftmac100*priv)+{+inti;++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*page=ftmac100_rxdes_get_va(rxdes);++if(d)+dma_unmap_single(priv->dev,d,PAGE_SIZE,+DMA_FROM_DEVICE);++if(page!=NULL)+free_page((unsignedlong)page);+}++for(i=0;i<TX_QUEUE_ENTRIES;i++){+structftmac100_txdes*txdes=&priv->descs->txdes[i];+structsk_buff*skb=ftmac100_txdes_get_skb(txdes);++if(skb){+dma_addr_tmap;++map=ftmac100_txdes_get_dma_addr(txdes);+dma_unmap_single(priv->dev,map,skb_headlen(skb),+DMA_TO_DEVICE);+dev_kfree_skb(skb);+}+}++dma_free_coherent(priv->dev,sizeof(structftmac100_descs),+priv->descs,priv->descs_dma_addr);+}++staticintftmac100_alloc_buffers(structftmac100*priv)+{+inti;++priv->descs=dma_alloc_coherent(priv->dev,+sizeof(structftmac100_descs),+&priv->descs_dma_addr,+GFP_KERNEL|GFP_DMA);+if(priv->descs==NULL)+return-ENOMEM;++memset(priv->descs,0,sizeof(structftmac100_descs));++/* initialize RX ring */++ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES-1]);++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+void*page;+dma_addr_td;++page=(void*)__get_free_page(GFP_KERNEL|GFP_DMA);+if(page==NULL)+gotoerr;++d=dma_map_single(priv->dev,page,PAGE_SIZE,DMA_FROM_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,d))){+free_page((unsignedlong)page);+gotoerr;+}++/*+*Thehardwareenforcesasub-2Kmaximumpacketsize,sowe+*puttwobuffersoneveryhardwarepage.+*/+ftmac100_rxdes_set_va(rxdes,page);+ftmac100_rxdes_set_va(rxdes+1,page+PAGE_SIZE/2);++ftmac100_rxdes_set_dma_addr(rxdes,d);+ftmac100_rxdes_set_dma_addr(rxdes+1,d+PAGE_SIZE/2);++ftmac100_rxdes_set_buffer_size(rxdes,RX_BUF_SIZE);+ftmac100_rxdes_set_buffer_size(rxdes+1,RX_BUF_SIZE);++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rxdes_set_dma_own(rxdes+1);+}++/* initialize TX ring */++ftmac100_txdes_set_end_of_ring(&priv->descs->txdes[TX_QUEUE_ENTRIES-1]);+return0;++err:+ftmac100_free_buffers(priv);+return-ENOMEM;+}++/******************************************************************************+*structmii_if_infofunctions+*****************************************************************************/+staticintftmac100_mdio_read(structnet_device*netdev,intphy_id,intreg)+{+structftmac100*priv=netdev_priv(netdev);+unsignedintphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIRD;++iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);+for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIRD)==0)+returnphycr&FTMAC100_PHYCR_MIIRDATA;++usleep_range(100,1000);+}++netdev_err(netdev,"mdio read timed out\n");+return0;+}++staticvoidftmac100_mdio_write(structnet_device*netdev,intphy_id,intreg,+intdata)+{+structftmac100*priv=netdev_priv(netdev);+unsignedintphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIWR;++data=FTMAC100_PHYWDATA_MIIWDATA(data);++iowrite32(data,priv->base+FTMAC100_OFFSET_PHYWDATA);+iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);++for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIWR)==0)+return;++usleep_range(100,1000);+}++netdev_err(netdev,"mdio write timed out\n");+}++/******************************************************************************+*structethtool_opsfunctions+*****************************************************************************/+staticvoidftmac100_get_drvinfo(structnet_device*netdev,+structethtool_drvinfo*info)+{+strcpy(info->driver,DRV_NAME);+strcpy(info->version,DRV_VERSION);+strcpy(info->bus_info,dev_name(&netdev->dev));+}++staticintftmac100_get_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_gset(&priv->mii,cmd);+}++staticintftmac100_set_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_sset(&priv->mii,cmd);+}++staticintftmac100_nway_reset(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_nway_restart(&priv->mii);+}++staticu32ftmac100_get_link(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_link_ok(&priv->mii);+}++staticconststructethtool_opsftmac100_ethtool_ops={+.set_settings=ftmac100_set_settings,+.get_settings=ftmac100_get_settings,+.get_drvinfo=ftmac100_get_drvinfo,+.nway_reset=ftmac100_nway_reset,+.get_link=ftmac100_get_link,+};++/******************************************************************************+*interrupthandler+*****************************************************************************/+staticirqreturn_tftmac100_interrupt(intirq,void*dev_id)+{+structnet_device*netdev=dev_id;+structftmac100*priv=netdev_priv(netdev);++if(likely(netif_running(netdev))){+/* Disable interrupts for polling */+ftmac100_disable_all_int(priv);+napi_schedule(&priv->napi);+}++returnIRQ_HANDLED;+}++/******************************************************************************+*structnapi_structfunctions+*****************************************************************************/+staticintftmac100_poll(structnapi_struct*napi,intbudget)+{+structftmac100*priv=container_of(napi,structftmac100,napi);+structnet_device*netdev=priv->netdev;+unsignedintstatus;+boolcompleted=true;+intrx=0;++status=ioread32(priv->base+FTMAC100_OFFSET_ISR);++if(status&(FTMAC100_INT_RPKT_FINISH|FTMAC100_INT_NORXBUF)){+/*+*FTMAC100_INT_RPKT_FINISH:+*RXDMAhasreceivedpacketsintoRXbuffersuccessfully+*+*FTMAC100_INT_NORXBUF:+*RXbufferunavailable+*/+boolretry;++do{+retry=ftmac100_rx_packet(priv,&rx);+}while(retry&&rx<budget);++if(retry&&rx==budget)+completed=false;+}++if(status&(FTMAC100_INT_XPKT_OK|FTMAC100_INT_XPKT_LOST)){+/*+*FTMAC100_INT_XPKT_OK:+*packettransmittedtoethernetsuccessfully+*+*FTMAC100_INT_XPKT_LOST:+*packettransmittedtoethernetlostduetolate+*collisionorexcessivecollision+*/+ftmac100_tx_complete(priv);+}++if(status&(FTMAC100_INT_NORXBUF|FTMAC100_INT_RPKT_LOST|+FTMAC100_INT_AHB_ERR|FTMAC100_INT_PHYSTS_CHG)){+if(net_ratelimit())+netdev_info(netdev,"[ISR] = 0x%x: %s%s%s%s\n",status,+status&FTMAC100_INT_NORXBUF?"NORXBUF ":"",+status&FTMAC100_INT_RPKT_LOST?"RPKT_LOST ":"",+status&FTMAC100_INT_AHB_ERR?"AHB_ERR ":"",+status&FTMAC100_INT_PHYSTS_CHG?"PHYSTS_CHG":"");++if(status&FTMAC100_INT_NORXBUF){+/* RX buffer unavailable */+netdev->stats.rx_over_errors++;+}++if(status&FTMAC100_INT_RPKT_LOST){+/* received packet lost due to RX FIFO full */+netdev->stats.rx_fifo_errors++;+}++if(status&FTMAC100_INT_PHYSTS_CHG){+/* PHY link status change */+mii_check_link(&priv->mii);+}+}++if(completed){+/* stop polling */+napi_complete(napi);+ftmac100_enable_all_int(priv);+}++returnrx;+}++/******************************************************************************+*structnet_device_opsfunctions+*****************************************************************************/+staticintftmac100_open(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+interr;++err=ftmac100_alloc_buffers(priv);+if(err){+netdev_err(netdev,"failed to allocate buffers\n");+gotoerr_alloc;+}++err=request_irq(priv->irq,ftmac100_interrupt,0,netdev->name,+netdev);+if(err){+netdev_err(netdev,"failed to request irq %d\n",priv->irq);+gotoerr_irq;+}++priv->rx_pointer=0;+priv->tx_clean_pointer=0;+priv->tx_pointer=0;+priv->tx_pending=0;++err=ftmac100_start_hw(priv);+if(err)+gotoerr_hw;++napi_enable(&priv->napi);+netif_start_queue(netdev);++ftmac100_enable_all_int(priv);+return0;++err_hw:+free_irq(priv->irq,netdev);+err_irq:+ftmac100_free_buffers(priv);+err_alloc:+returnerr;+}++staticintftmac100_stop(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);++ftmac100_disable_all_int(priv);+netif_stop_queue(netdev);+napi_disable(&priv->napi);+ftmac100_stop_hw(priv);+free_irq(priv->irq,netdev);+ftmac100_free_buffers(priv);++return0;+}++staticintftmac100_hard_start_xmit(structsk_buff*skb,+structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+dma_addr_tmap;++if(unlikely(skb->len>MAX_PKT_SIZE)){+if(net_ratelimit())+netdev_dbg(netdev,"tx packet too big\n");++netdev->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++map=dma_map_single(priv->dev,skb->data,skb_headlen(skb),DMA_TO_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,map))){+/* drop packet */+if(net_ratelimit())+netdev_err(netdev,"map socket buffer failed\n");++netdev->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++returnftmac100_xmit(priv,skb,map);+}++/* optional */+staticintftmac100_do_ioctl(structnet_device*netdev,structifreq*ifr,+intcmd)+{+structftmac100*priv=netdev_priv(netdev);+structmii_ioctl_data*data=if_mii(ifr);++returngeneric_mii_ioctl(&priv->mii,data,cmd,NULL);+}++staticconststructnet_device_opsftmac100_netdev_ops={+.ndo_open=ftmac100_open,+.ndo_stop=ftmac100_stop,+.ndo_start_xmit=ftmac100_hard_start_xmit,+.ndo_set_mac_address=eth_mac_addr,+.ndo_validate_addr=eth_validate_addr,+.ndo_do_ioctl=ftmac100_do_ioctl,+};++/******************************************************************************+*structplatform_driverfunctions+*****************************************************************************/+staticintftmac100_remove(structplatform_device*pdev)+{+structnet_device*netdev;+structftmac100*priv;++netdev=platform_get_drvdata(pdev);+if(netdev==NULL)+return0;++platform_set_drvdata(pdev,NULL);++priv=netdev_priv(netdev);++netif_napi_del(&priv->napi);+unregister_netdev(netdev);++if(priv->base!=NULL)+iounmap(priv->base);++if(priv->res!=NULL)+release_resource(priv->res);++free_netdev(netdev);+return0;+}++staticintftmac100_probe(structplatform_device*pdev)+{+structresource*res;+intirq;+structnet_device*netdev;+structftmac100*priv;+interr;++if(!pdev)+return-ENODEV;++res=platform_get_resource(pdev,IORESOURCE_MEM,0);+if(!res)+return-ENXIO;++irq=platform_get_irq(pdev,0);+if(irq<0)+returnirq;++/* setup net_device */++netdev=alloc_etherdev(sizeof(structftmac100));+if(netdev==NULL){+err=-ENOMEM;+gotoerr_out;+}++SET_NETDEV_DEV(netdev,&pdev->dev);+SET_ETHTOOL_OPS(netdev,&ftmac100_ethtool_ops);+netdev->netdev_ops=&ftmac100_netdev_ops;++platform_set_drvdata(pdev,netdev);++/* setup private data */++priv=netdev_priv(netdev);+priv->netdev=netdev;+priv->dev=&pdev->dev;++spin_lock_init(&priv->tx_lock);++/* initialize NAPI */+netif_napi_add(netdev,&priv->napi,ftmac100_poll,64);++/* map io memory */+priv->res=request_mem_region(res->start,resource_size(res),+dev_name(&pdev->dev));+if(priv->res==NULL){+dev_err(&pdev->dev,"Could not reserve memory region\n");+err=-ENOMEM;+gotoerr_out;+}++priv->base=ioremap(res->start,res->end-res->start);+if(priv->base==NULL){+dev_err(&pdev->dev,"Failed to ioremap ethernet registers\n");+err=-EIO;+gotoerr_out;+}++priv->irq=irq;++/* initialize struct mii_if_info */++priv->mii.phy_id=0;+priv->mii.phy_id_mask=0x1f;+priv->mii.reg_num_mask=0x1f;+priv->mii.dev=netdev;+priv->mii.mdio_read=ftmac100_mdio_read;+priv->mii.mdio_write=ftmac100_mdio_write;++/* register network device */++err=register_netdev(netdev);+if(err){+dev_err(&pdev->dev,"Failed to register netdev\n");+gotoerr_out;+}++netdev_info(netdev,"irq %d, mapped at %p\n",priv->irq,priv->base);++if(!is_valid_ether_addr(netdev->dev_addr)){+random_ether_addr(netdev->dev_addr);+netdev_info(netdev,"generated random MAC address %pM\n",+netdev->dev_addr);+}++return0;++err_out:+ftmac100_remove(pdev);+returnerr;+}++staticstructplatform_driverftmac100_driver={+.probe=ftmac100_probe,+.remove=ftmac100_remove,+.driver={+.name=DRV_NAME,+.owner=THIS_MODULE,+},+};++/******************************************************************************+*initialization/finalization+*****************************************************************************/+staticint__initftmac100_init(void)+{+pr_info("Loading version "DRV_VERSION" ...\n");+returnplatform_driver_register(&ftmac100_driver);+}++staticvoid__exitftmac100_exit(void)+{+platform_driver_unregister(&ftmac100_driver);+}++module_init(ftmac100_init);+module_exit(ftmac100_exit);++MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");+MODULE_DESCRIPTION("FTMAC100 driver");+MODULE_LICENSE("GPL");
From: Eric Dumazet <hidden> Date: 2011-01-21 09:08:18
Le vendredi 21 janvier 2011 à 15:55 +0800, Po-Yu Chuang a écrit :
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
From: Michał Mirosław <hidden> Date: 2011-01-21 12:26:29
2011/1/21 Po-Yu Chuang [off-list ref]:
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
[...]
quoted hunk
+static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)+{+ /* clear all except end of ring bit */+ txdes->txdes0 = 0;+ txdes->txdes1 &= FTMAC100_TXDES1_EDOTR;+ txdes->txdes2 = 0;+ txdes->txdes3 = 0;+}
This also probably needs cpu_to_le32().
[...]
quoted hunk
+static void ftmac100_free_buffers(struct ftmac100 *priv)+{+ int i;++ for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {+ struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];+ dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);+ void *page = ftmac100_rxdes_get_va(rxdes);++ if (d)+ dma_unmap_single(priv->dev, d, PAGE_SIZE,+ DMA_FROM_DEVICE);++ if (page != NULL)+ free_page((unsigned long)page);+ }+
[...]
quoted hunk
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)+{+ int i;++ priv->descs = dma_alloc_coherent(priv->dev,+ sizeof(struct ftmac100_descs),+ &priv->descs_dma_addr,+ GFP_KERNEL | GFP_DMA);+ if (priv->descs == NULL)+ return -ENOMEM;++ memset(priv->descs, 0, sizeof(struct ftmac100_descs));++ /* initialize RX ring */++ ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);++ for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {+ struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];+ void *page;+ dma_addr_t d;++ page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);+ if (page == NULL)+ goto err;++ d = dma_map_single(priv->dev, page, PAGE_SIZE, DMA_FROM_DEVICE);+ if (unlikely(dma_mapping_error(priv->dev, d))) {+ free_page((unsigned long)page);+ goto err;+ }++ /*+ * The hardware enforces a sub-2K maximum packet size, so we+ * put two buffers on every hardware page.+ */+ ftmac100_rxdes_set_va(rxdes, page);+ ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);++ ftmac100_rxdes_set_dma_addr(rxdes, d);+ ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);++ ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);+ ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);++ ftmac100_rxdes_set_dma_own(rxdes);+ ftmac100_rxdes_set_dma_own(rxdes + 1);+ }
[...]
Did you test this? This looks like it will result in double free after
packet RX, as you are giving the same page (referenced once) to two
distinct RX descriptors, that may be assigned different packets.
Since your not implementing any RX offloads, you might just allocate
fresh skb's with alloc_skb() and store skb pointer in rxdes3. Since
hardware doesn't touch it, you can skip cpu_to_le32()/le32_to_cpu()
there (leave a comment, though).
Unless this needs to work for ISA devices, you should drop GFP_DMA
allocation flag.
Best Regards,
Michał Mirosław
Dear Michał,
2011/1/21 Michał Mirosław [off-list ref]:
2011/1/21 Po-Yu Chuang [off-list ref]:
quoted
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
[...]
quoted
+static void ftmac100_txdes_reset(struct ftmac100_txdes *txdes)+{+ /* clear all except end of ring bit */+ txdes->txdes0 = 0;+ txdes->txdes1 &= FTMAC100_TXDES1_EDOTR;+ txdes->txdes2 = 0;+ txdes->txdes3 = 0;+}
This also probably needs cpu_to_le32().
Ah, I missed that. Fixed.
[...]
quoted
+static void ftmac100_free_buffers(struct ftmac100 *priv)+{+ int i;++ for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {+ struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];+ dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);+ void *page = ftmac100_rxdes_get_va(rxdes);++ if (d)+ dma_unmap_single(priv->dev, d, PAGE_SIZE,+ DMA_FROM_DEVICE);++ if (page != NULL)+ free_page((unsigned long)page);+ }+
[...]
quoted
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)+{+ int i;++ priv->descs = dma_alloc_coherent(priv->dev,+ sizeof(struct ftmac100_descs),+ &priv->descs_dma_addr,+ GFP_KERNEL | GFP_DMA);+ if (priv->descs == NULL)+ return -ENOMEM;++ memset(priv->descs, 0, sizeof(struct ftmac100_descs));++ /* initialize RX ring */++ ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);++ for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {+ struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];+ void *page;+ dma_addr_t d;++ page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);+ if (page == NULL)+ goto err;++ d = dma_map_single(priv->dev, page, PAGE_SIZE, DMA_FROM_DEVICE);+ if (unlikely(dma_mapping_error(priv->dev, d))) {+ free_page((unsigned long)page);+ goto err;+ }++ /*+ * The hardware enforces a sub-2K maximum packet size, so we+ * put two buffers on every hardware page.+ */+ ftmac100_rxdes_set_va(rxdes, page);+ ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);++ ftmac100_rxdes_set_dma_addr(rxdes, d);+ ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);++ ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);+ ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);++ ftmac100_rxdes_set_dma_own(rxdes);+ ftmac100_rxdes_set_dma_own(rxdes + 1);+ }
[...]
Did you test this? This looks like it will result in double free after
packet RX, as you are giving the same page (referenced once) to two
distinct RX descriptors, that may be assigned different packets.
Yes, this is tested.
Since your not implementing any RX offloads, you might just allocate
fresh skb's with alloc_skb() and store skb pointer in rxdes3. Since
rxdes3 does not store virtual address of an skb.
It stores the address of the buffer allocated while open() and freed
only when stop().
The data in that buffer will be memcpy()ed to an skb allocated in
ftmac100_rx_packet().
No double free happens.
hardware doesn't touch it, you can skip cpu_to_le32()/le32_to_cpu()
there (leave a comment, though).
Agree. Thanks.
Unless this needs to work for ISA devices, you should drop GFP_DMA
allocation flag.
Ben mentioned about this in the previous mail. I thought that it is OK
to keep GFP_DMA
on ARM platform, but since you point out this flag is for ISA, I will drop it.
best regards,
Po-Yu Chuang
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts
v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix
v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()
v5:
use dev_kfree_skb() in ftmac100_tx_complete_packet()
cpu_to_le32/le32_to_cpu usage fix
drop GFP_DMA
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1212 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 +++++++
4 files changed, 1402 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ftmac100.c
create mode 100644 drivers/net/ftmac100.h
@@ -0,0 +1,1212 @@+/*+*FaradayFTMAC10010/100Ethernet+*+*(C)Copyright2009-2011FaradayTechnology+*Po-YuChuang<ratbert@faraday-tech.com>+*+*Thisprogramisfreesoftware;youcanredistributeitand/ormodify+*itunderthetermsoftheGNUGeneralPublicLicenseaspublishedby+*theFreeSoftwareFoundation;eitherversion2oftheLicense,or+*(atyouroption)anylaterversion.+*+*Thisprogramisdistributedinthehopethatitwillbeuseful,+*butWITHOUTANYWARRANTY;withouteventheimpliedwarrantyof+*MERCHANTABILITYorFITNESSFORAPARTICULARPURPOSE.Seethe+*GNUGeneralPublicLicenseformoredetails.+*+*YoushouldhavereceivedacopyoftheGNUGeneralPublicLicense+*alongwiththisprogram;ifnot,writetotheFreeSoftware+*Foundation,Inc.,675MassAve,Cambridge,MA02139,USA.+*/++#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt++#include<linux/dma-mapping.h>+#include<linux/etherdevice.h>+#include<linux/ethtool.h>+#include<linux/init.h>+#include<linux/io.h>+#include<linux/kernel.h>+#include<linux/mii.h>+#include<linux/module.h>+#include<linux/moduleparam.h>+#include<linux/netdevice.h>+#include<linux/platform_device.h>++#include"ftmac100.h"++#define DRV_NAME "ftmac100"+#define DRV_VERSION "0.2"++#define RX_QUEUE_ENTRIES 128 /* must be power of 2 */+#define TX_QUEUE_ENTRIES 16 /* must be power of 2 */++#define MAX_PKT_SIZE 1518+#define RX_BUF_SIZE 2044 /* must be smaller than 0x7ff */++/******************************************************************************+*privatedata+*****************************************************************************/+structftmac100_descs{+structftmac100_rxdesrxdes[RX_QUEUE_ENTRIES];+structftmac100_txdestxdes[TX_QUEUE_ENTRIES];+};++structftmac100{+structresource*res;+void*base;+intirq;++structftmac100_descs*descs;+dma_addr_tdescs_dma_addr;++unsignedintrx_pointer;+unsignedinttx_clean_pointer;+unsignedinttx_pointer;+unsignedinttx_pending;++spinlock_ttx_lock;++structnet_device*netdev;+structdevice*dev;+structnapi_structnapi;++structmii_if_infomii;+};++/******************************************************************************+*internalfunctions(hardwareregisteraccess)+*****************************************************************************/+#define INT_MASK_ALL_ENABLED (FTMAC100_INT_RPKT_FINISH | \+FTMAC100_INT_NORXBUF|\+FTMAC100_INT_XPKT_OK|\+FTMAC100_INT_XPKT_LOST|\+FTMAC100_INT_RPKT_LOST|\+FTMAC100_INT_AHB_ERR|\+FTMAC100_INT_PHYSTS_CHG)++staticvoidftmac100_enable_all_int(structftmac100*priv)+{+iowrite32(INT_MASK_ALL_ENABLED,priv->base+FTMAC100_OFFSET_IMR);+}++staticvoidftmac100_disable_all_int(structftmac100*priv)+{+iowrite32(0,priv->base+FTMAC100_OFFSET_IMR);+}++staticvoidftmac100_set_rx_ring_base(structftmac100*priv,dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_RXR_BADR);+}++staticvoidftmac100_set_tx_ring_base(structftmac100*priv,dma_addr_taddr)+{+iowrite32(addr,priv->base+FTMAC100_OFFSET_TXR_BADR);+}++staticvoidftmac100_txdma_start_polling(structftmac100*priv)+{+iowrite32(1,priv->base+FTMAC100_OFFSET_TXPD);+}++staticintftmac100_reset(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+inti;++/* NOTE: reset clears all registers */+iowrite32(FTMAC100_MACCR_SW_RST,priv->base+FTMAC100_OFFSET_MACCR);++for(i=0;i<5;i++){+unsignedintmaccr;++maccr=ioread32(priv->base+FTMAC100_OFFSET_MACCR);+if(!(maccr&FTMAC100_MACCR_SW_RST)){+/*+*FTMAC100_MACCR_SW_RSTcleareddoesnotindicate+*thathardwareresetcompleted(whatthef*ck).+*Westillneedtowaitforawhile.+*/+usleep_range(500,1000);+return0;+}++usleep_range(1000,10000);+}++netdev_err(netdev,"software reset failed\n");+return-EIO;+}++staticvoidftmac100_set_mac(structftmac100*priv,constunsignedchar*mac)+{+unsignedintmaddr=mac[0]<<8|mac[1];+unsignedintladdr=mac[2]<<24|mac[3]<<16|mac[4]<<8|mac[5];++iowrite32(maddr,priv->base+FTMAC100_OFFSET_MAC_MADR);+iowrite32(laddr,priv->base+FTMAC100_OFFSET_MAC_LADR);+}++#define MACCR_ENABLE_ALL (FTMAC100_MACCR_XMT_EN | \+FTMAC100_MACCR_RCV_EN|\+FTMAC100_MACCR_XDMA_EN|\+FTMAC100_MACCR_RDMA_EN|\+FTMAC100_MACCR_CRC_APD|\+FTMAC100_MACCR_FULLDUP|\+FTMAC100_MACCR_RX_RUNT|\+FTMAC100_MACCR_RX_BROADPKT)++staticintftmac100_start_hw(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;++if(ftmac100_reset(priv))+return-EIO;++/* setup ring buffer base registers */++ftmac100_set_rx_ring_base(priv,+priv->descs_dma_addr++offsetof(structftmac100_descs,rxdes));+ftmac100_set_tx_ring_base(priv,+priv->descs_dma_addr++offsetof(structftmac100_descs,txdes));++iowrite32(FTMAC100_APTC_RXPOLL_CNT(1),priv->base+FTMAC100_OFFSET_APTC);++ftmac100_set_mac(priv,netdev->dev_addr);++iowrite32(MACCR_ENABLE_ALL,priv->base+FTMAC100_OFFSET_MACCR);+return0;+}++staticvoidftmac100_stop_hw(structftmac100*priv)+{+iowrite32(0,priv->base+FTMAC100_OFFSET_MACCR);+}++/******************************************************************************+*internalfunctions(receivedescriptor)+*****************************************************************************/+staticboolftmac100_rxdes_first_segment(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_FRS);+}++staticboolftmac100_rxdes_last_segment(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_LRS);+}++staticboolftmac100_rxdes_owned_by_dma(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);+}++staticvoidftmac100_rxdes_set_dma_own(structftmac100_rxdes*rxdes)+{+/* clear status bits */+rxdes->rxdes0=cpu_to_le32(FTMAC100_RXDES0_RXDMA_OWN);+}++staticboolftmac100_rxdes_rx_error(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RX_ERR);+}++staticboolftmac100_rxdes_crc_error(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_CRC_ERR);+}++staticboolftmac100_rxdes_frame_too_long(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_FTL);+}++staticboolftmac100_rxdes_runt(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RUNT);+}++staticboolftmac100_rxdes_odd_nibble(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RX_ODD_NB);+}++staticunsignedintftmac100_rxdes_frame_length(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_RFL);+}++staticboolftmac100_rxdes_multicast(structftmac100_rxdes*rxdes)+{+returnrxdes->rxdes0&cpu_to_le32(FTMAC100_RXDES0_MULTICAST);+}++staticvoidftmac100_rxdes_set_buffer_size(structftmac100_rxdes*rxdes,+unsignedintsize)+{+rxdes->rxdes1&=cpu_to_le32(FTMAC100_RXDES1_EDORR);+rxdes->rxdes1|=cpu_to_le32(FTMAC100_RXDES1_RXBUF_SIZE(size));+}++staticvoidftmac100_rxdes_set_end_of_ring(structftmac100_rxdes*rxdes)+{+rxdes->rxdes1|=cpu_to_le32(FTMAC100_RXDES1_EDORR);+}++staticvoidftmac100_rxdes_set_dma_addr(structftmac100_rxdes*rxdes,+dma_addr_taddr)+{+rxdes->rxdes2=cpu_to_le32(addr);+}++staticdma_addr_tftmac100_rxdes_get_dma_addr(structftmac100_rxdes*rxdes)+{+returnle32_to_cpu(rxdes->rxdes2);+}++/*+*rxdes3isnotusedbyhardware.Weuseittokeeptrackofbuffer.+*Sincehardwaredoesnottouchit,wecanskipcpu_to_le32()/le32_to_cpu().+*/+staticvoidftmac100_rxdes_set_va(structftmac100_rxdes*rxdes,void*addr)+{+rxdes->rxdes3=(unsignedint)addr;+}++staticvoid*ftmac100_rxdes_get_va(structftmac100_rxdes*rxdes)+{+return(void*)rxdes->rxdes3;+}++/******************************************************************************+*internalfunctions(receive)+*****************************************************************************/+staticintftmac100_next_rx_pointer(intpointer)+{+return(pointer+1)&(RX_QUEUE_ENTRIES-1);+}++staticvoidftmac100_rx_pointer_advance(structftmac100*priv)+{+priv->rx_pointer=ftmac100_next_rx_pointer(priv->rx_pointer);+}++staticstructftmac100_rxdes*ftmac100_current_rxdes(structftmac100*priv)+{+return&priv->descs->rxdes[priv->rx_pointer];+}++staticstructftmac100_rxdes*+ftmac100_rx_locate_first_segment(structftmac100*priv)+{+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);++while(!ftmac100_rxdes_owned_by_dma(rxdes)){+if(ftmac100_rxdes_first_segment(rxdes))+returnrxdes;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}++returnNULL;+}++staticboolftmac100_rx_packet_error(structftmac100*priv,+structftmac100_rxdes*rxdes)+{+structnet_device*netdev=priv->netdev;+boolerror=false;++if(unlikely(ftmac100_rxdes_rx_error(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx err\n");++netdev->stats.rx_errors++;+error=true;+}++if(unlikely(ftmac100_rxdes_crc_error(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx crc err\n");++netdev->stats.rx_crc_errors++;+error=true;+}++if(unlikely(ftmac100_rxdes_frame_too_long(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx frame too long\n");++netdev->stats.rx_length_errors++;+error=true;+}elseif(unlikely(ftmac100_rxdes_runt(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx runt\n");++netdev->stats.rx_length_errors++;+error=true;+}elseif(unlikely(ftmac100_rxdes_odd_nibble(rxdes))){+if(net_ratelimit())+netdev_info(netdev,"rx odd nibble\n");++netdev->stats.rx_length_errors++;+error=true;+}++returnerror;+}++staticvoidftmac100_rx_drop_packet(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+structftmac100_rxdes*rxdes=ftmac100_current_rxdes(priv);+booldone=false;++if(net_ratelimit())+netdev_dbg(netdev,"drop packet %p\n",rxdes);++do{+if(ftmac100_rxdes_last_segment(rxdes))+done=true;++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}while(!done&&!ftmac100_rxdes_owned_by_dma(rxdes));++netdev->stats.rx_dropped++;+}++staticboolftmac100_rx_packet(structftmac100*priv,int*processed)+{+structnet_device*netdev=priv->netdev;+structftmac100_rxdes*rxdes;+structsk_buff*skb;+intlength;+boolcopied=false;+booldone=false;++rxdes=ftmac100_rx_locate_first_segment(priv);+if(!rxdes)+returnfalse;++if(unlikely(ftmac100_rx_packet_error(priv,rxdes))){+ftmac100_rx_drop_packet(priv);+returntrue;+}++/* start processing */++length=ftmac100_rxdes_frame_length(rxdes);++skb=netdev_alloc_skb_ip_align(netdev,length);+if(unlikely(!skb)){+if(net_ratelimit())+netdev_err(netdev,"rx skb alloc failed\n");++ftmac100_rx_drop_packet(priv);+returntrue;+}++if(unlikely(ftmac100_rxdes_multicast(rxdes)))+netdev->stats.multicast++;++do{+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*buf=ftmac100_rxdes_get_va(rxdes);+intsize;++size=min(length-copied,RX_BUF_SIZE);++dma_sync_single_for_cpu(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);+memcpy(skb_put(skb,size),buf,size);++copied+=size;++if(ftmac100_rxdes_last_segment(rxdes))+done=true;++dma_sync_single_for_device(priv->dev,d,RX_BUF_SIZE,+DMA_FROM_DEVICE);++ftmac100_rxdes_set_dma_own(rxdes);++ftmac100_rx_pointer_advance(priv);+rxdes=ftmac100_current_rxdes(priv);+}while(!done&&copied<length);++skb->protocol=eth_type_trans(skb,netdev);++netdev->stats.rx_packets++;+netdev->stats.rx_bytes+=skb->len;++/* push packet to protocol stack */+netif_receive_skb(skb);++(*processed)++;+returntrue;+}++/******************************************************************************+*internalfunctions(transmitdescriptor)+*****************************************************************************/+staticvoidftmac100_txdes_reset(structftmac100_txdes*txdes)+{+/* clear all except end of ring bit */+txdes->txdes0=0;+txdes->txdes1&=cpu_to_le32(FTMAC100_TXDES1_EDOTR);+txdes->txdes2=0;+txdes->txdes3=0;+}++staticboolftmac100_txdes_owned_by_dma(structftmac100_txdes*txdes)+{+returntxdes->txdes0&cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);+}++staticvoidftmac100_txdes_set_dma_own(structftmac100_txdes*txdes)+{+/*+*Makesuredmaownbitwillnotbesetbeforeanyother+*descriptorfields.+*/+wmb();+txdes->txdes0|=cpu_to_le32(FTMAC100_TXDES0_TXDMA_OWN);+}++staticboolftmac100_txdes_excessive_collision(structftmac100_txdes*txdes)+{+returntxdes->txdes0&cpu_to_le32(FTMAC100_TXDES0_TXPKT_EXSCOL);+}++staticboolftmac100_txdes_late_collision(structftmac100_txdes*txdes)+{+returntxdes->txdes0&cpu_to_le32(FTMAC100_TXDES0_TXPKT_LATECOL);+}++staticvoidftmac100_txdes_set_end_of_ring(structftmac100_txdes*txdes)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_EDOTR);+}++staticvoidftmac100_txdes_set_first_segment(structftmac100_txdes*txdes)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_FTS);+}++staticvoidftmac100_txdes_set_last_segment(structftmac100_txdes*txdes)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_LTS);+}++staticvoidftmac100_txdes_set_txint(structftmac100_txdes*txdes)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_TXIC);+}++staticvoidftmac100_txdes_set_buffer_size(structftmac100_txdes*txdes,+unsignedintlen)+{+txdes->txdes1|=cpu_to_le32(FTMAC100_TXDES1_TXBUF_SIZE(len));+}++staticvoidftmac100_txdes_set_dma_addr(structftmac100_txdes*txdes,+dma_addr_taddr)+{+txdes->txdes2=cpu_to_le32(addr);+}++staticdma_addr_tftmac100_txdes_get_dma_addr(structftmac100_txdes*txdes)+{+returnle32_to_cpu(txdes->txdes2);+}++/*+*txdes3isnotusedbyhardware.Weuseittokeeptrackofsocketbuffer.+*Sincehardwaredoesnottouchit,wecanskipcpu_to_le32()/le32_to_cpu().+*/+staticvoidftmac100_txdes_set_skb(structftmac100_txdes*txdes,+structsk_buff*skb)+{+txdes->txdes3=(unsignedint)skb;+}++staticstructsk_buff*ftmac100_txdes_get_skb(structftmac100_txdes*txdes)+{+return(structsk_buff*)txdes->txdes3;+}++/******************************************************************************+*internalfunctions(transmit)+*****************************************************************************/+staticintftmac100_next_tx_pointer(intpointer)+{+return(pointer+1)&(TX_QUEUE_ENTRIES-1);+}++staticvoidftmac100_tx_pointer_advance(structftmac100*priv)+{+priv->tx_pointer=ftmac100_next_tx_pointer(priv->tx_pointer);+}++staticvoidftmac100_tx_clean_pointer_advance(structftmac100*priv)+{+priv->tx_clean_pointer=ftmac100_next_tx_pointer(priv->tx_clean_pointer);+}++staticstructftmac100_txdes*ftmac100_current_txdes(structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_pointer];+}++staticstructftmac100_txdes*+ftmac100_current_clean_txdes(structftmac100*priv)+{+return&priv->descs->txdes[priv->tx_clean_pointer];+}++staticboolftmac100_tx_complete_packet(structftmac100*priv)+{+structnet_device*netdev=priv->netdev;+structftmac100_txdes*txdes;+structsk_buff*skb;+dma_addr_tmap;++if(priv->tx_pending==0)+returnfalse;++txdes=ftmac100_current_clean_txdes(priv);++if(ftmac100_txdes_owned_by_dma(txdes))+returnfalse;++skb=ftmac100_txdes_get_skb(txdes);+map=ftmac100_txdes_get_dma_addr(txdes);++if(unlikely(ftmac100_txdes_excessive_collision(txdes)||+ftmac100_txdes_late_collision(txdes))){+/*+*packettransmittedtoethernetlostduetolatecollision+*orexcessivecollision+*/+netdev->stats.tx_aborted_errors++;+}else{+netdev->stats.tx_packets++;+netdev->stats.tx_bytes+=skb->len;+}++dma_unmap_single(priv->dev,map,skb_headlen(skb),DMA_TO_DEVICE);++dev_kfree_skb(skb);++ftmac100_txdes_reset(txdes);++ftmac100_tx_clean_pointer_advance(priv);++priv->tx_pending--;+netif_wake_queue(netdev);++returntrue;+}++staticvoidftmac100_tx_complete(structftmac100*priv)+{+spin_lock(&priv->tx_lock);+while(ftmac100_tx_complete_packet(priv))+;+spin_unlock(&priv->tx_lock);+}++staticintftmac100_xmit(structftmac100*priv,structsk_buff*skb,+dma_addr_tmap)+{+structnet_device*netdev=priv->netdev;+structftmac100_txdes*txdes;+unsignedintlen=(skb->len<ETH_ZLEN)?ETH_ZLEN:skb->len;++txdes=ftmac100_current_txdes(priv);+ftmac100_tx_pointer_advance(priv);++/* setup TX descriptor */++spin_lock(&priv->tx_lock);+ftmac100_txdes_set_skb(txdes,skb);+ftmac100_txdes_set_dma_addr(txdes,map);++ftmac100_txdes_set_first_segment(txdes);+ftmac100_txdes_set_last_segment(txdes);+ftmac100_txdes_set_txint(txdes);+ftmac100_txdes_set_buffer_size(txdes,len);++priv->tx_pending++;+if(priv->tx_pending==TX_QUEUE_ENTRIES){+if(net_ratelimit())+netdev_info(netdev,"tx queue full\n");++netif_stop_queue(netdev);+}++/* start transmit */+ftmac100_txdes_set_dma_own(txdes);+spin_unlock(&priv->tx_lock);++ftmac100_txdma_start_polling(priv);++returnNETDEV_TX_OK;+}++/******************************************************************************+*internalfunctions(buffer)+*****************************************************************************/+staticvoidftmac100_free_buffers(structftmac100*priv)+{+inti;++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+dma_addr_td=ftmac100_rxdes_get_dma_addr(rxdes);+void*page=ftmac100_rxdes_get_va(rxdes);++if(d)+dma_unmap_single(priv->dev,d,PAGE_SIZE,+DMA_FROM_DEVICE);++if(page!=NULL)+free_page((unsignedlong)page);+}++for(i=0;i<TX_QUEUE_ENTRIES;i++){+structftmac100_txdes*txdes=&priv->descs->txdes[i];+structsk_buff*skb=ftmac100_txdes_get_skb(txdes);++if(skb){+dma_addr_tmap;++map=ftmac100_txdes_get_dma_addr(txdes);+dma_unmap_single(priv->dev,map,skb_headlen(skb),+DMA_TO_DEVICE);+dev_kfree_skb(skb);+}+}++dma_free_coherent(priv->dev,sizeof(structftmac100_descs),+priv->descs,priv->descs_dma_addr);+}++staticintftmac100_alloc_buffers(structftmac100*priv)+{+inti;++priv->descs=dma_alloc_coherent(priv->dev,+sizeof(structftmac100_descs),+&priv->descs_dma_addr,GFP_KERNEL);+if(priv->descs==NULL)+return-ENOMEM;++memset(priv->descs,0,sizeof(structftmac100_descs));++/* initialize RX ring */++ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES-1]);++for(i=0;i<RX_QUEUE_ENTRIES;i+=2){+structftmac100_rxdes*rxdes=&priv->descs->rxdes[i];+void*page;+dma_addr_td;++page=(void*)__get_free_page(GFP_KERNEL);+if(page==NULL)+gotoerr;++d=dma_map_single(priv->dev,page,PAGE_SIZE,DMA_FROM_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,d))){+free_page((unsignedlong)page);+gotoerr;+}++/*+*Thehardwareenforcesasub-2Kmaximumpacketsize,sowe+*puttwobuffersoneveryhardwarepage.+*/+ftmac100_rxdes_set_va(rxdes,page);+ftmac100_rxdes_set_va(rxdes+1,page+PAGE_SIZE/2);++ftmac100_rxdes_set_dma_addr(rxdes,d);+ftmac100_rxdes_set_dma_addr(rxdes+1,d+PAGE_SIZE/2);++ftmac100_rxdes_set_buffer_size(rxdes,RX_BUF_SIZE);+ftmac100_rxdes_set_buffer_size(rxdes+1,RX_BUF_SIZE);++ftmac100_rxdes_set_dma_own(rxdes);+ftmac100_rxdes_set_dma_own(rxdes+1);+}++/* initialize TX ring */++ftmac100_txdes_set_end_of_ring(&priv->descs->txdes[TX_QUEUE_ENTRIES-1]);+return0;++err:+ftmac100_free_buffers(priv);+return-ENOMEM;+}++/******************************************************************************+*structmii_if_infofunctions+*****************************************************************************/+staticintftmac100_mdio_read(structnet_device*netdev,intphy_id,intreg)+{+structftmac100*priv=netdev_priv(netdev);+unsignedintphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIRD;++iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);+for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIRD)==0)+returnphycr&FTMAC100_PHYCR_MIIRDATA;++usleep_range(100,1000);+}++netdev_err(netdev,"mdio read timed out\n");+return0;+}++staticvoidftmac100_mdio_write(structnet_device*netdev,intphy_id,intreg,+intdata)+{+structftmac100*priv=netdev_priv(netdev);+unsignedintphycr;+inti;++phycr=FTMAC100_PHYCR_PHYAD(phy_id)|+FTMAC100_PHYCR_REGAD(reg)|+FTMAC100_PHYCR_MIIWR;++data=FTMAC100_PHYWDATA_MIIWDATA(data);++iowrite32(data,priv->base+FTMAC100_OFFSET_PHYWDATA);+iowrite32(phycr,priv->base+FTMAC100_OFFSET_PHYCR);++for(i=0;i<10;i++){+phycr=ioread32(priv->base+FTMAC100_OFFSET_PHYCR);++if((phycr&FTMAC100_PHYCR_MIIWR)==0)+return;++usleep_range(100,1000);+}++netdev_err(netdev,"mdio write timed out\n");+}++/******************************************************************************+*structethtool_opsfunctions+*****************************************************************************/+staticvoidftmac100_get_drvinfo(structnet_device*netdev,+structethtool_drvinfo*info)+{+strcpy(info->driver,DRV_NAME);+strcpy(info->version,DRV_VERSION);+strcpy(info->bus_info,dev_name(&netdev->dev));+}++staticintftmac100_get_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_gset(&priv->mii,cmd);+}++staticintftmac100_set_settings(structnet_device*netdev,+structethtool_cmd*cmd)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_ethtool_sset(&priv->mii,cmd);+}++staticintftmac100_nway_reset(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_nway_restart(&priv->mii);+}++staticu32ftmac100_get_link(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+returnmii_link_ok(&priv->mii);+}++staticconststructethtool_opsftmac100_ethtool_ops={+.set_settings=ftmac100_set_settings,+.get_settings=ftmac100_get_settings,+.get_drvinfo=ftmac100_get_drvinfo,+.nway_reset=ftmac100_nway_reset,+.get_link=ftmac100_get_link,+};++/******************************************************************************+*interrupthandler+*****************************************************************************/+staticirqreturn_tftmac100_interrupt(intirq,void*dev_id)+{+structnet_device*netdev=dev_id;+structftmac100*priv=netdev_priv(netdev);++if(likely(netif_running(netdev))){+/* Disable interrupts for polling */+ftmac100_disable_all_int(priv);+napi_schedule(&priv->napi);+}++returnIRQ_HANDLED;+}++/******************************************************************************+*structnapi_structfunctions+*****************************************************************************/+staticintftmac100_poll(structnapi_struct*napi,intbudget)+{+structftmac100*priv=container_of(napi,structftmac100,napi);+structnet_device*netdev=priv->netdev;+unsignedintstatus;+boolcompleted=true;+intrx=0;++status=ioread32(priv->base+FTMAC100_OFFSET_ISR);++if(status&(FTMAC100_INT_RPKT_FINISH|FTMAC100_INT_NORXBUF)){+/*+*FTMAC100_INT_RPKT_FINISH:+*RXDMAhasreceivedpacketsintoRXbuffersuccessfully+*+*FTMAC100_INT_NORXBUF:+*RXbufferunavailable+*/+boolretry;++do{+retry=ftmac100_rx_packet(priv,&rx);+}while(retry&&rx<budget);++if(retry&&rx==budget)+completed=false;+}++if(status&(FTMAC100_INT_XPKT_OK|FTMAC100_INT_XPKT_LOST)){+/*+*FTMAC100_INT_XPKT_OK:+*packettransmittedtoethernetsuccessfully+*+*FTMAC100_INT_XPKT_LOST:+*packettransmittedtoethernetlostduetolate+*collisionorexcessivecollision+*/+ftmac100_tx_complete(priv);+}++if(status&(FTMAC100_INT_NORXBUF|FTMAC100_INT_RPKT_LOST|+FTMAC100_INT_AHB_ERR|FTMAC100_INT_PHYSTS_CHG)){+if(net_ratelimit())+netdev_info(netdev,"[ISR] = 0x%x: %s%s%s%s\n",status,+status&FTMAC100_INT_NORXBUF?"NORXBUF ":"",+status&FTMAC100_INT_RPKT_LOST?"RPKT_LOST ":"",+status&FTMAC100_INT_AHB_ERR?"AHB_ERR ":"",+status&FTMAC100_INT_PHYSTS_CHG?"PHYSTS_CHG":"");++if(status&FTMAC100_INT_NORXBUF){+/* RX buffer unavailable */+netdev->stats.rx_over_errors++;+}++if(status&FTMAC100_INT_RPKT_LOST){+/* received packet lost due to RX FIFO full */+netdev->stats.rx_fifo_errors++;+}++if(status&FTMAC100_INT_PHYSTS_CHG){+/* PHY link status change */+mii_check_link(&priv->mii);+}+}++if(completed){+/* stop polling */+napi_complete(napi);+ftmac100_enable_all_int(priv);+}++returnrx;+}++/******************************************************************************+*structnet_device_opsfunctions+*****************************************************************************/+staticintftmac100_open(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+interr;++err=ftmac100_alloc_buffers(priv);+if(err){+netdev_err(netdev,"failed to allocate buffers\n");+gotoerr_alloc;+}++err=request_irq(priv->irq,ftmac100_interrupt,0,netdev->name,+netdev);+if(err){+netdev_err(netdev,"failed to request irq %d\n",priv->irq);+gotoerr_irq;+}++priv->rx_pointer=0;+priv->tx_clean_pointer=0;+priv->tx_pointer=0;+priv->tx_pending=0;++err=ftmac100_start_hw(priv);+if(err)+gotoerr_hw;++napi_enable(&priv->napi);+netif_start_queue(netdev);++ftmac100_enable_all_int(priv);+return0;++err_hw:+free_irq(priv->irq,netdev);+err_irq:+ftmac100_free_buffers(priv);+err_alloc:+returnerr;+}++staticintftmac100_stop(structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);++ftmac100_disable_all_int(priv);+netif_stop_queue(netdev);+napi_disable(&priv->napi);+ftmac100_stop_hw(priv);+free_irq(priv->irq,netdev);+ftmac100_free_buffers(priv);++return0;+}++staticintftmac100_hard_start_xmit(structsk_buff*skb,+structnet_device*netdev)+{+structftmac100*priv=netdev_priv(netdev);+dma_addr_tmap;++if(unlikely(skb->len>MAX_PKT_SIZE)){+if(net_ratelimit())+netdev_dbg(netdev,"tx packet too big\n");++netdev->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++map=dma_map_single(priv->dev,skb->data,skb_headlen(skb),DMA_TO_DEVICE);+if(unlikely(dma_mapping_error(priv->dev,map))){+/* drop packet */+if(net_ratelimit())+netdev_err(netdev,"map socket buffer failed\n");++netdev->stats.tx_dropped++;+dev_kfree_skb(skb);+returnNETDEV_TX_OK;+}++returnftmac100_xmit(priv,skb,map);+}++/* optional */+staticintftmac100_do_ioctl(structnet_device*netdev,structifreq*ifr,+intcmd)+{+structftmac100*priv=netdev_priv(netdev);+structmii_ioctl_data*data=if_mii(ifr);++returngeneric_mii_ioctl(&priv->mii,data,cmd,NULL);+}++staticconststructnet_device_opsftmac100_netdev_ops={+.ndo_open=ftmac100_open,+.ndo_stop=ftmac100_stop,+.ndo_start_xmit=ftmac100_hard_start_xmit,+.ndo_set_mac_address=eth_mac_addr,+.ndo_validate_addr=eth_validate_addr,+.ndo_do_ioctl=ftmac100_do_ioctl,+};++/******************************************************************************+*structplatform_driverfunctions+*****************************************************************************/+staticintftmac100_remove(structplatform_device*pdev)+{+structnet_device*netdev;+structftmac100*priv;++netdev=platform_get_drvdata(pdev);+if(netdev==NULL)+return0;++platform_set_drvdata(pdev,NULL);++priv=netdev_priv(netdev);++netif_napi_del(&priv->napi);+unregister_netdev(netdev);++if(priv->base!=NULL)+iounmap(priv->base);++if(priv->res!=NULL)+release_resource(priv->res);++free_netdev(netdev);+return0;+}++staticintftmac100_probe(structplatform_device*pdev)+{+structresource*res;+intirq;+structnet_device*netdev;+structftmac100*priv;+interr;++if(!pdev)+return-ENODEV;++res=platform_get_resource(pdev,IORESOURCE_MEM,0);+if(!res)+return-ENXIO;++irq=platform_get_irq(pdev,0);+if(irq<0)+returnirq;++/* setup net_device */++netdev=alloc_etherdev(sizeof(structftmac100));+if(netdev==NULL){+err=-ENOMEM;+gotoerr_out;+}++SET_NETDEV_DEV(netdev,&pdev->dev);+SET_ETHTOOL_OPS(netdev,&ftmac100_ethtool_ops);+netdev->netdev_ops=&ftmac100_netdev_ops;++platform_set_drvdata(pdev,netdev);++/* setup private data */++priv=netdev_priv(netdev);+priv->netdev=netdev;+priv->dev=&pdev->dev;++spin_lock_init(&priv->tx_lock);++/* initialize NAPI */+netif_napi_add(netdev,&priv->napi,ftmac100_poll,64);++/* map io memory */+priv->res=request_mem_region(res->start,resource_size(res),+dev_name(&pdev->dev));+if(priv->res==NULL){+dev_err(&pdev->dev,"Could not reserve memory region\n");+err=-ENOMEM;+gotoerr_out;+}++priv->base=ioremap(res->start,res->end-res->start);+if(priv->base==NULL){+dev_err(&pdev->dev,"Failed to ioremap ethernet registers\n");+err=-EIO;+gotoerr_out;+}++priv->irq=irq;++/* initialize struct mii_if_info */++priv->mii.phy_id=0;+priv->mii.phy_id_mask=0x1f;+priv->mii.reg_num_mask=0x1f;+priv->mii.dev=netdev;+priv->mii.mdio_read=ftmac100_mdio_read;+priv->mii.mdio_write=ftmac100_mdio_write;++/* register network device */++err=register_netdev(netdev);+if(err){+dev_err(&pdev->dev,"Failed to register netdev\n");+gotoerr_out;+}++netdev_info(netdev,"irq %d, mapped at %p\n",priv->irq,priv->base);++if(!is_valid_ether_addr(netdev->dev_addr)){+random_ether_addr(netdev->dev_addr);+netdev_info(netdev,"generated random MAC address %pM\n",+netdev->dev_addr);+}++return0;++err_out:+ftmac100_remove(pdev);+returnerr;+}++staticstructplatform_driverftmac100_driver={+.probe=ftmac100_probe,+.remove=ftmac100_remove,+.driver={+.name=DRV_NAME,+.owner=THIS_MODULE,+},+};++/******************************************************************************+*initialization/finalization+*****************************************************************************/+staticint__initftmac100_init(void)+{+pr_info("Loading version "DRV_VERSION" ...\n");+returnplatform_driver_register(&ftmac100_driver);+}++staticvoid__exitftmac100_exit(void)+{+platform_driver_unregister(&ftmac100_driver);+}++module_init(ftmac100_init);+module_exit(ftmac100_exit);++MODULE_AUTHOR("Po-Yu Chuang <ratbert@faraday-tech.com>");+MODULE_DESCRIPTION("FTMAC100 driver");+MODULE_LICENSE("GPL");
Hmm, I guess you didnt tested your driver with a pktgen flood ;)
This 'netdev_info(netdev, "tx queue full\n");' is not necessary, since
its a pretty normal condition for a driver (to fill its TX ring buffer)
From: Michał Mirosław <hidden> Date: 2011-01-24 20:22:33
W dniu 24 stycznia 2011 09:26 użytkownik Po-Yu Chuang
[off-list ref] napisał:
2011/1/21 Michał Mirosław [off-list ref]:
quoted
2011/1/21 Po-Yu Chuang [off-list ref]:
quoted
+static void ftmac100_free_buffers(struct ftmac100 *priv)+{+ int i;++ for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {+ struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];+ dma_addr_t d = ftmac100_rxdes_get_dma_addr(rxdes);+ void *page = ftmac100_rxdes_get_va(rxdes);++ if (d)+ dma_unmap_single(priv->dev, d, PAGE_SIZE,+ DMA_FROM_DEVICE);++ if (page != NULL)+ free_page((unsigned long)page);+ }+
[...]
quoted
+static int ftmac100_alloc_buffers(struct ftmac100 *priv)+{+ int i;++ priv->descs = dma_alloc_coherent(priv->dev,+ sizeof(struct ftmac100_descs),+ &priv->descs_dma_addr,+ GFP_KERNEL | GFP_DMA);+ if (priv->descs == NULL)+ return -ENOMEM;++ memset(priv->descs, 0, sizeof(struct ftmac100_descs));++ /* initialize RX ring */++ ftmac100_rxdes_set_end_of_ring(&priv->descs->rxdes[RX_QUEUE_ENTRIES - 1]);++ for (i = 0; i < RX_QUEUE_ENTRIES; i += 2) {+ struct ftmac100_rxdes *rxdes = &priv->descs->rxdes[i];+ void *page;+ dma_addr_t d;++ page = (void *)__get_free_page(GFP_KERNEL | GFP_DMA);+ if (page == NULL)+ goto err;++ d = dma_map_single(priv->dev, page, PAGE_SIZE, DMA_FROM_DEVICE);+ if (unlikely(dma_mapping_error(priv->dev, d))) {+ free_page((unsigned long)page);+ goto err;+ }++ /*+ * The hardware enforces a sub-2K maximum packet size, so we+ * put two buffers on every hardware page.+ */+ ftmac100_rxdes_set_va(rxdes, page);+ ftmac100_rxdes_set_va(rxdes + 1, page + PAGE_SIZE / 2);++ ftmac100_rxdes_set_dma_addr(rxdes, d);+ ftmac100_rxdes_set_dma_addr(rxdes + 1, d + PAGE_SIZE / 2);++ ftmac100_rxdes_set_buffer_size(rxdes, RX_BUF_SIZE);+ ftmac100_rxdes_set_buffer_size(rxdes + 1, RX_BUF_SIZE);++ ftmac100_rxdes_set_dma_own(rxdes);+ ftmac100_rxdes_set_dma_own(rxdes + 1);+ }
[...]
Did you test this? This looks like it will result in double free after
packet RX, as you are giving the same page (referenced once) to two
distinct RX descriptors, that may be assigned different packets.
Yes, this is tested.
quoted
Since your not implementing any RX offloads, you might just allocate
fresh skb's with alloc_skb() and store skb pointer in rxdes3. Since
rxdes3 does not store virtual address of an skb.
It stores the address of the buffer allocated while open() and freed
only when stop().
The data in that buffer will be memcpy()ed to an skb allocated in
ftmac100_rx_packet().
No double free happens.
Ah, I blindly assumed that you're just appending the buffers to the
skb (using skb_fill_page_desc() and friends). Since you have to mark
descriptors for the device anyway, it might be faster to allocate new
skbs and map those as rx buffers (changing the descriptor's buffer
address after every RX) instead of keeping static buffer and copying
every time. (For small packets it wastes lot of memory, though - so
the right choice depends on the expected workload.)
Best Regards,
Michał Mirosław
Hmm, I guess you didnt tested your driver with a pktgen flood ;)
This 'netdev_info(netdev, "tx queue full\n");' is not necessary, since
its a pretty normal condition for a driver (to fill its TX ring buffer)
Errr... No, I didn't test it with pktgen. I tested it with normal NFS usages.
I'll try it.
best regards,
Po-Yu Chuang
Dear Michał,
2011/1/25 Michał Mirosław [off-list ref]:
W dniu 24 stycznia 2011 09:26 użytkownik Po-Yu Chuang
[off-list ref] napisał:
quoted
2011/1/21 Michał Mirosław [off-list ref]:
quoted
Did you test this? This looks like it will result in double free after
packet RX, as you are giving the same page (referenced once) to two
distinct RX descriptors, that may be assigned different packets.
Yes, this is tested.
quoted
Since your not implementing any RX offloads, you might just allocate
fresh skb's with alloc_skb() and store skb pointer in rxdes3. Since
rxdes3 does not store virtual address of an skb.
It stores the address of the buffer allocated while open() and freed
only when stop().
The data in that buffer will be memcpy()ed to an skb allocated in
ftmac100_rx_packet().
No double free happens.
Ah, I blindly assumed that you're just appending the buffers to the
skb (using skb_fill_page_desc() and friends). Since you have to mark
descriptors for the device anyway, it might be faster to allocate new
skbs and map those as rx buffers (changing the descriptor's buffer
address after every RX) instead of keeping static buffer and copying
every time. (For small packets it wastes lot of memory, though - so
the right choice depends on the expected workload.)
The reason I chose to use memcpy rx buffer to skb is that I didn't know
how to deal with multi-segment packets if I preallocated skb for each
rx descriptor. This function seems to be what I need.
Let me rework this. Thanks.
best regards,
Po-Yu Chuang
Dear Michał
2011/1/25 Po-Yu Chuang [off-list ref]:
2011/1/25 Michał Mirosław [off-list ref]:
quoted
Ah, I blindly assumed that you're just appending the buffers to the
skb (using skb_fill_page_desc() and friends). Since you have to mark
descriptors for the device anyway, it might be faster to allocate new
skbs and map those as rx buffers (changing the descriptor's buffer
address after every RX) instead of keeping static buffer and copying
every time. (For small packets it wastes lot of memory, though - so
the right choice depends on the expected workload.)
The reason I chose to use memcpy rx buffer to skb is that I didn't know
how to deal with multi-segment packets if I preallocated skb for each
rx descriptor. This function seems to be what I need.
Let me rework this. Thanks.
After looking at some drivers using skb_fill_page_desc(), I am still
confused.
It seems that this function is mainly for jumbo packet and the
beginning of data of received packet should still be in skb->data.
Scheme 1)
If I simply allocate a page for each rx ring entry, I still need to allocate
an skb and copy at least packet header in first page to skb->data. Then
add the page of rest of payload to skb by skb_fill_page_desc().
Scheme 2)
If I simply allocate an skb for each rx ring entry, zero copy can be easily
achieved if the packet contains only one segment (one rx ring entry).
However, if the received packet has more than one segment, I guess I
might need to allocate a bigger skb and copy data of all segments
(skb->data) to the new skb?
The problem is, in most cases, the received packets are one-segment only.
(TBH, I don't know when will multi-segment packets appear.)
If all packets are one-segment, I can just allocate an skb for each rx ring
enry and achieve zero-copy. However, since there might be multi-segment
packets, I need to deal with them.
How can I do?
best regards,
Po-Yu Chuang
If I simply allocate a page for each rx ring entry, I still need to allocate
an skb and copy at least packet header in first page to skb->data. Then
add the page of rest of payload to skb by skb_fill_page_desc().
You should attach the pages, the use __pskb_pull_tail() to bring in the
headers to the linear skb->data area.
See drivers/net/niu.c:niu_process_rx_pkt().
If I simply allocate a page for each rx ring entry, I still need to allocate
an skb and copy at least packet header in first page to skb->data. Then
add the page of rest of payload to skb by skb_fill_page_desc().
You should attach the pages, the use __pskb_pull_tail() to bring in the
headers to the linear skb->data area.
See drivers/net/niu.c:niu_process_rx_pkt().
I tried two ways to implement zero-copy.
One is to preallocate skb big enough for any rx packet and use the skb
as rx buffer.
The other is use page as rx buffer, use skb_fill_page_desc() to add a
data page to
skb and then pull only header to skb by __pskb_pull_tail() as you suggested.
Two implementations are slower than the original memcpy version.
(benchmarked with iperf)
I guess the problem is because a HW restriction that the rx buffer must be
64 bits aligned. Since I cannot make rx buffer starts at offset 2 bytes, the
IP header, TCP header and data are not 4 bytes aligned. The performance
drops drastically.
Therefore, I will submit later a v6 which is still using memcpy().
best regards,
Po-Yu Chuang
I guess the problem is because a HW restriction that the rx buffer must be
64 bits aligned. Since I cannot make rx buffer starts at offset 2 bytes, the
IP header, TCP header and data are not 4 bytes aligned. The performance
drops drastically.
I cannot believe that after 20 years of commodity ethernet networking
chips were first designed, people are still designing hardware that
doesn't do this right.
Just emit garbage bytes into the sub-word alignment padding if the chip
wants to word align it's DMA writes.
Even the 15 year old Dec Tulip chips do this properly.
I guess the problem is because a HW restriction that the rx buffer must be
64 bits aligned. Since I cannot make rx buffer starts at offset 2 bytes, the
IP header, TCP header and data are not 4 bytes aligned. The performance
drops drastically.
I cannot believe that after 20 years of commodity ethernet networking
chips were first designed, people are still designing hardware that
doesn't do this right.
Ha ha...
Well, this restriction was removed in the later IPs of our company. :-p
Just emit garbage bytes into the sub-word alignment padding if the chip
wants to word align it's DMA writes.
Not sure what do you mean. The problem is that HW does not accept a
base address of RX buffer which is not 8 bytes aligned.
Even the 15 year old Dec Tulip chips do this properly.
I guess the problem is because a HW restriction that the rx buffer must be
64 bits aligned. Since I cannot make rx buffer starts at offset 2 bytes, the
IP header, TCP header and data are not 4 bytes aligned. The performance
drops drastically.
I cannot believe that after 20 years of commodity ethernet networking
chips were first designed, people are still designing hardware that
doesn't do this right.
Ha ha...
Well, this restriction was removed in the later IPs of our company. :-p
quoted
Just emit garbage bytes into the sub-word alignment padding if the chip
wants to word align it's DMA writes.
Not sure what do you mean. The problem is that HW does not accept a
base address of RX buffer which is not 8 bytes aligned.
I still dont understand the problem, maybe you should post your work as
RFC
Why not using for the first part the skb buffer (eventually copy the
first 128 bytes to get aligned IP/TCP header), and add frags for
following parts ?
From: Po-Yu Chuang <redacted>
*** NOTE ***
This patch is only for reference. Its performance is worse than v5.
Please do not apply this.
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts
v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix
v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()
v5:
use dev_kfree_skb() in ftmac100_tx_complete_packet()
cpu_to_le32/le32_to_cpu usage fix
drop GFP_DMA
ref0:
cpu_to_le32/le32_to_cpu usage fix
remove "tx queue full" message
reduce critical section protected by tx_lock
add check of MAX_PKT_SIZE and RX_BUF_SIZE
add __exit to ftmac100_remove()
zero copy - use skb_fill_page_desc() and __pskb_pull_tail().
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1196 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 ++++++++
4 files changed, 1386 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ftmac100.c
create mode 100644 drivers/net/ftmac100.h
From: Eric Dumazet <hidden> Date: 2011-02-24 17:39:47
Le jeudi 24 février 2011 à 17:29 +0800, Po-Yu Chuang a écrit :
From: Po-Yu Chuang <redacted>
quoted hunk
++static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)+{+ struct net_device *netdev = priv->netdev;+ struct ftmac100_rxdes *rxdes;+ struct sk_buff *skb;+ struct page *page;+ dma_addr_t map;+ int length;++ rxdes = ftmac100_rx_locate_first_segment(priv);+ if (!rxdes)+ return false;++ if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {+ ftmac100_rx_drop_packet(priv);+ return true;+ }++ /*+ * It is impossible to get multi-segment packets+ * because we always provide big enough receive buffers.+ */+ if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))+ BUG();++ /* start processing */+ skb = netdev_alloc_skb_ip_align(netdev, ETH_HLEN);
Oh I see... You should allocate a bigger head (say... 128 bytes)
And copy in it up to 128 bytes of first part... this to avoid upper
stack to reallocate skb head (because IP/TCP processing need to get
their headers in skb head)
From: Eric Dumazet <hidden> Date: 2011-02-24 17:52:42
Le jeudi 24 février 2011 à 18:39 +0100, Eric Dumazet a écrit :
Le jeudi 24 février 2011 à 17:29 +0800, Po-Yu Chuang a écrit :
quoted
From: Po-Yu Chuang <redacted>
quoted
++static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)+{+ struct net_device *netdev = priv->netdev;+ struct ftmac100_rxdes *rxdes;+ struct sk_buff *skb;+ struct page *page;+ dma_addr_t map;+ int length;++ rxdes = ftmac100_rx_locate_first_segment(priv);+ if (!rxdes)+ return false;++ if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {+ ftmac100_rx_drop_packet(priv);+ return true;+ }++ /*+ * It is impossible to get multi-segment packets+ * because we always provide big enough receive buffers.+ */+ if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))+ BUG();++ /* start processing */+ skb = netdev_alloc_skb_ip_align(netdev, ETH_HLEN);
Oh I see... You should allocate a bigger head (say... 128 bytes)
And copy in it up to 128 bytes of first part... this to avoid upper
stack to reallocate skb head (because IP/TCP processing need to get
their headers in skb head)
Take a look at drivers/net/niu.c :
#define RX_SKB_ALLOC_SIZE 128 + NET_IP_ALIGN
static int niu_process_rx_pkt(...)
{
...
skb = netdev_alloc_skb(np->dev, RX_SKB_ALLOC_SIZE);
...
while (1) {
...
niu_rx_skb_append(skb, page, off, append_size);
}
}
Hi Eric,
On Fri, Feb 25, 2011 at 1:48 AM, Eric Dumazet [off-list ref] wrote:
Le jeudi 24 février 2011 à 18:39 +0100, Eric Dumazet a écrit :
quoted
Le jeudi 24 février 2011 à 17:29 +0800, Po-Yu Chuang a écrit :
quoted
From: Po-Yu Chuang <redacted>
+
+static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+ struct net_device *netdev = priv->netdev;
+ struct ftmac100_rxdes *rxdes;
+ struct sk_buff *skb;
+ struct page *page;
+ dma_addr_t map;
+ int length;
+
+ rxdes = ftmac100_rx_locate_first_segment(priv);
+ if (!rxdes)
+ return false;
+
+ if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+ ftmac100_rx_drop_packet(priv);
+ return true;
+ }
+
+ /*
+ * It is impossible to get multi-segment packets
+ * because we always provide big enough receive buffers.
+ */
+ if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))
+ BUG();
+
+ /* start processing */
+ skb = netdev_alloc_skb_ip_align(netdev, ETH_HLEN);
Oh I see... You should allocate a bigger head (say... 128 bytes)
And copy in it up to 128 bytes of first part... this to avoid upper
stack to reallocate skb head (because IP/TCP processing need to get
their headers in skb head)
Take a look at drivers/net/niu.c :
#define RX_SKB_ALLOC_SIZE 128 + NET_IP_ALIGN
static int niu_process_rx_pkt(...)
{
...
skb = netdev_alloc_skb(np->dev, RX_SKB_ALLOC_SIZE);
...
while (1) {
...
niu_rx_skb_append(skb, page, off, append_size);
}
}
Oh I got it.
I will try this and redo the benchmarking.
Thanks,
Po-Yu Chuang
Hi Eric,
On Fri, Feb 25, 2011 at 10:32 AM, Po-Yu Chuang [off-list ref] wrote:
On Fri, Feb 25, 2011 at 1:48 AM, Eric Dumazet [off-list ref] wrote:
quoted
Le jeudi 24 février 2011 à 18:39 +0100, Eric Dumazet a écrit :
quoted
Le jeudi 24 février 2011 à 17:29 +0800, Po-Yu Chuang a écrit :
quoted
From: Po-Yu Chuang <redacted>
+
+static bool ftmac100_rx_packet(struct ftmac100 *priv, int *processed)
+{
+ struct net_device *netdev = priv->netdev;
+ struct ftmac100_rxdes *rxdes;
+ struct sk_buff *skb;
+ struct page *page;
+ dma_addr_t map;
+ int length;
+
+ rxdes = ftmac100_rx_locate_first_segment(priv);
+ if (!rxdes)
+ return false;
+
+ if (unlikely(ftmac100_rx_packet_error(priv, rxdes))) {
+ ftmac100_rx_drop_packet(priv);
+ return true;
+ }
+
+ /*
+ * It is impossible to get multi-segment packets
+ * because we always provide big enough receive buffers.
+ */
+ if (unlikely(!ftmac100_rxdes_last_segment(rxdes)))
+ BUG();
+
+ /* start processing */
+ skb = netdev_alloc_skb_ip_align(netdev, ETH_HLEN);
Oh I see... You should allocate a bigger head (say... 128 bytes)
And copy in it up to 128 bytes of first part... this to avoid upper
stack to reallocate skb head (because IP/TCP processing need to get
their headers in skb head)
Take a look at drivers/net/niu.c :
#define RX_SKB_ALLOC_SIZE 128 + NET_IP_ALIGN
static int niu_process_rx_pkt(...)
{
...
skb = netdev_alloc_skb(np->dev, RX_SKB_ALLOC_SIZE);
...
while (1) {
...
niu_rx_skb_append(skb, page, off, append_size);
}
}
Oh I got it.
I will try this and redo the benchmarking.
It's a little faster than v5 now. Thanks.
I will submit the current version later.
One more question just curious, why 128 bytes?
best regards,
Po-Yu Chuang
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts
v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix
v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()
v5:
use dev_kfree_skb() in ftmac100_tx_complete_packet()
cpu_to_le32/le32_to_cpu usage fix
drop GFP_DMA
v6:
cpu_to_le32/le32_to_cpu usage fix
remove "tx queue full" message
reduce critical section protected by tx_lock
add check of MAX_PKT_SIZE and RX_BUF_SIZE
add __exit to ftmac100_remove()
simplify ftmac100_rx_packet()
zero copy - use skb_fill_page_desc() and __pskb_pull_tail().
pull more data to skb head to include tcp/ip header
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1198 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 ++++++++
4 files changed, 1388 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ftmac100.c
create mode 100644 drivers/net/ftmac100.h
From: Eric Dumazet <hidden> Date: 2011-02-25 11:40:49
Le vendredi 25 février 2011 à 17:57 +0800, Po-Yu Chuang a écrit :
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
It seems fine to me, but I have somes questions
1) On V5, the receive function ftmac100_rx_packet() was able to process
several segments per skb. On V6 you process one frag only.
Isnt this NIC able to handle large MTU (say... 9000) ?
2) ftmac100_alloc_rx_page() is called and allocate a full page for a
rxdes.
128*4K -> 512 Kbytes of memory for RX ring
In V5, you were using half pages only, so 256 kbytes of memory.
If you look at other drivers (NIU, BENET), they are able to use exactly
128*2 kbytes (for a rxring of 128 slots, and 2Kbytes per slot)
Hi Eric,
On Fri, Feb 25, 2011 at 7:40 PM, Eric Dumazet [off-list ref] wrote:
Le vendredi 25 février 2011 à 17:57 +0800, Po-Yu Chuang a écrit :
quoted
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
It seems fine to me, but I have somes questions
1) On V5, the receive function ftmac100_rx_packet() was able to process
several segments per skb. On V6 you process one frag only.
Isnt this NIC able to handle large MTU (say... 9000) ?
No, it isn't. Its max supported packet size is 0x7ff (2047 bytes).
I wasn't sure when will get multi-segment packet. After confirmed with
HW designer two weeks ago, he told me that only if the rx buffer described
by the first descriptor is not big enough, another rx buffer will be used.
That means, since every rx buffer is large enough, it is impossible to
get a multi-segment packets.
So I simplified ftmac100_rx_packet().
2) ftmac100_alloc_rx_page() is called and allocate a full page for a
rxdes.
128*4K -> 512 Kbytes of memory for RX ring
In V5, you were using half pages only, so 256 kbytes of memory.
If you look at other drivers (NIU, BENET), they are able to use exactly
128*2 kbytes (for a rxring of 128 slots, and 2Kbytes per slot)
Both NIU and BENET refill rx ring after all packets receive work done,
but I allocate rx buffer right after a packet is received. I think it
is difficult
to use a page for two rx buffers in my driver now. Can we just keep it
the way it is? I can study how to achieve that after current driver is accepted.
best regards,
Po-Yu Chuang
From: Eric Dumazet <hidden> Date: 2011-03-01 05:26:51
Le mardi 01 mars 2011 à 13:20 +0800, Po-Yu Chuang a écrit :
Hi Eric,
On Fri, Feb 25, 2011 at 7:40 PM, Eric Dumazet [off-list ref] wrote:
Both NIU and BENET refill rx ring after all packets receive work done,
but I allocate rx buffer right after a packet is received. I think it
is difficult
to use a page for two rx buffers in my driver now. Can we just keep it
the way it is? I can study how to achieve that after current driver is accepted.
No problem, you are the author, I am only a reviewer and ask questions
after all ;)
Thanks !
From: Eric Dumazet <hidden> Date: 2011-03-01 05:45:42
Le mardi 01 mars 2011 à 13:20 +0800, Po-Yu Chuang a écrit :
That means, since every rx buffer is large enough, it is impossible to
get a multi-segment packets.
So I simplified ftmac100_rx_packet().
Signed-off-by: Eric Dumazet <redacted>
I am only wondering then if not using fragments would be faster then
(eventually doing copybreaks for small frames like tg3)
But if you plan to get new hw soon, you have the infrastructure...
From: Eric Dumazet <hidden> Date: 2011-03-01 05:53:33
Le mardi 01 mars 2011 à 13:45 +0800, Po-Yu Chuang a écrit :
Hi Eric,
On Sat, Feb 26, 2011 at 2:47 AM, Eric Dumazet [off-list ref] wrote:
quoted
A compromise would be to use 128 for the allocation, but only copy 64
bytes.
I will use this way and submit v7 later.
BTW, even with IPV6, the first 64 bytes contain "RPS" header
We changed NET_SKB_PAD a while back and added this comment in
include/linux/skbuff.h
NET_IP_ALIGN(2) + ethernet_header(14) + IP_header(20/40) + ports(8)
From: Eric Dumazet <hidden> Date: 2011-03-01 05:59:54
Le mardi 01 mars 2011 à 13:51 +0800, Po-Yu Chuang a écrit :
On Tue, Mar 1, 2011 at 1:45 PM, Eric Dumazet [off-list ref] wrote:
quoted
I am only wondering then if not using fragments would be faster then
(eventually doing copybreaks for small frames like tg3)
Although not many circumstances are tested.
iperf shows that it is a little faster to use fragments than memcpy, so...
This might be a side effect of skb->truesize being smaller with
fragments than "regular packets" and socket backlog congestion.
If a full page was really accounted for in skb->truesize, instead of
used length, performance might be the same or lower :(
- skb->truesize += length;
+ skb->truesize += PAGE_SIZE;
This has nothing to do with your driver, but a core implementation
detail.
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts
v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix
v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()
v5:
use dev_kfree_skb() in ftmac100_tx_complete_packet()
cpu_to_le32/le32_to_cpu usage fix
drop GFP_DMA
v6:
cpu_to_le32/le32_to_cpu usage fix
remove "tx queue full" message
reduce critical section protected by tx_lock
add check of MAX_PKT_SIZE and RX_BUF_SIZE
add __exit to ftmac100_remove()
simplify ftmac100_rx_packet()
zero copy - use skb_fill_page_desc() and __pskb_pull_tail().
pull more data to skb head to include tcp/ip header
v7:
allocate 128 bytes skb and pull 64 bytes only
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1196 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 ++++++++
4 files changed, 1386 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ftmac100.c
create mode 100644 drivers/net/ftmac100.h
From: Eric Dumazet <hidden> Date: 2011-03-01 07:27:08
Le mardi 01 mars 2011 à 14:48 +0800, Po-Yu Chuang a écrit :
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>
---
v2:
always use NAPI
do not use our own net_device_stats structure
don't set trans_start and last_rx
stats.rx_packets and stats.rx_bytes include dropped packets
add missed netif_napi_del()
initialize spinlocks in probe function
remove rx_lock and hw_lock
use netdev_[err/info/dbg] instead of dev_* ones
use netdev_alloc_skb_ip_align()
remove ftmac100_get_stats()
use is_valid_ether_addr() instead of is_zero_ether_addr()
add const to ftmac100_ethtool_ops and ftmac100_netdev_ops
use net_ratelimit() instead of printk_ratelimit()
no explicit inline
use %pM to print MAC address
add comment before wmb
use napi poll() to handle all interrupts
v3:
undo "stats.rx_packets and stats.rx_bytes include dropped packets"
ftmac100_mdio_read() returns 0 if error
fix comment typos
use pr_fmt and pr_info
define INT_MASK_ALL_ENABLED
define MACCR_ENABLE_ALL
do not count length error many times
use bool/true/false
use cpu_to_le32/le32_to_cpu to access descriptors
indent style fix
v4:
should not access skb after netif_receive_skb()
use resource_size()
better way to use cpu_to_le32/le32_to_cpu
use spin_lock() for tx_lock
combine all netdev_info() together in ftmac100_poll()
v5:
use dev_kfree_skb() in ftmac100_tx_complete_packet()
cpu_to_le32/le32_to_cpu usage fix
drop GFP_DMA
v6:
cpu_to_le32/le32_to_cpu usage fix
remove "tx queue full" message
reduce critical section protected by tx_lock
add check of MAX_PKT_SIZE and RX_BUF_SIZE
add __exit to ftmac100_remove()
simplify ftmac100_rx_packet()
zero copy - use skb_fill_page_desc() and __pskb_pull_tail().
pull more data to skb head to include tcp/ip header
v7:
allocate 128 bytes skb and pull 64 bytes only
drivers/net/Kconfig | 9 +
drivers/net/Makefile | 1 +
drivers/net/ftmac100.c | 1196 ++++++++++++++++++++++++++++++++++++++++++++++++
drivers/net/ftmac100.h | 180 ++++++++
4 files changed, 1386 insertions(+), 0 deletions(-)
create mode 100644 drivers/net/ftmac100.c
create mode 100644 drivers/net/ftmac100.h
From: David Miller <davem@davemloft.net> Date: 2011-03-03 20:18:55
From: Eric Dumazet <redacted>
Date: Tue, 01 Mar 2011 08:27:00 +0100
Le mardi 01 mars 2011 à 14:48 +0800, Po-Yu Chuang a écrit :
quoted
From: Po-Yu Chuang <redacted>
FTMAC100 Ethernet Media Access Controller supports 10/100 Mbps and
MII. This driver has been working on some ARM/NDS32 SoC's including
Faraday A320 and Andes AG101.
Signed-off-by: Po-Yu Chuang <redacted>