diff options
| author | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2017-08-30 08:25:59 +0000 |
| commit | 8e4bff4cab0ddac6060645b0715210484d02ff40 (patch) | |
| tree | 3a5c3024371a04692a3a6d9974d001cdff8ebf84 /drivers/sipc/seth.c | |
Diffstat (limited to 'drivers/sipc/seth.c')
| -rw-r--r-- | drivers/sipc/seth.c | 1153 |
1 files changed, 1153 insertions, 0 deletions
diff --git a/drivers/sipc/seth.c b/drivers/sipc/seth.c new file mode 100644 index 00000000..e8e3d406 --- /dev/null +++ b/drivers/sipc/seth.c @@ -0,0 +1,1153 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/string.h> +#include <linux/delay.h> +#include <linux/errno.h> +#include <linux/interrupt.h> +#include <linux/init.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/skbuff.h> +#include <linux/wakelock.h> +#include <linux/workqueue.h> +#include <linux/ipv6.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/icmp.h> +#include <linux/icmpv6.h> +#include <asm/byteorder.h> +#include <linux/tty.h> +#include <linux/platform_device.h> +#include <linux/atomic.h> +#include <linux/sched.h> +#include <linux/kthread.h> +#ifdef CONFIG_DEBUG_FS +#include <linux/debugfs.h> +#include <linux/seq_file.h> +#endif + +#ifdef CONFIG_OF +#include <linux/of_device.h> +#endif + +#include <linux/sipc.h> +#include <linux/seth.h> + +#define seth_max(a,b) ((a) > (b) ? (a) : (b)) +#define seth_min(a,b) ((a) < (b) ? (a) : (b)) + +/* debugging macros */ +#define SETH_INFO(x...) pr_info("SETH: " x) +#define SETH_DEBUG(x...) pr_debug("SETH: " x) +#define SETH_ERR(x...) pr_err("SETH: Error: " x) + +/* defination of sblock length */ +#define SETH_BLOCK_SIZE (ETH_HLEN + ETH_DATA_LEN + NET_IP_ALIGN) + +/* device status */ +#define DEV_ON 1 +#define DEV_OFF 0 + +/* enable NAPI */ +#define SETH_NAPI + +/* tx pkt return value */ +#define SETH_TX_SUCCESS 0 +#define SETH_TX_NO_BLK -1 +#define SETH_TX_INVALID_BLK -2 + +/* Macro of Packet Field */ +#define SETH_IPV4_HLEN 20 +#define SETH_IPV6_HLEN 40 +#define SETH_ETH_HLEN ETH_HLEN +#define SETH_ETH_FRAME_LEN ETH_FRAME_LEN +#define SETH_IPV4_VERSION 0x04 +#define SETH_IPV6_VERSION 0x06 + +/* Macro of protocol type */ +#define SETH_PRO_TCP 6 +#define SETH_PRO_ICMP 1 + +/* Macro of ICMP type */ +#define SETH_ICMP_PING ICMP_ECHO +#define SETH_ICMP6_PING ICMPV6_ECHO_REQUEST + +#ifdef SETH_NAPI +#define SETH_NAPI_WEIGHT 64 +#define SETH_TX_WEIGHT 16 + +/* struct of data transfer statistics */ +struct seth_dtrans_stats { + uint32_t rx_pkt_max; + uint32_t rx_pkt_min; + uint32_t rx_sum; + uint32_t rx_cnt; + uint32_t rx_alloc_fails; + + uint32_t tx_pkt_max; + uint32_t tx_pkt_min; + uint32_t tx_sum; + uint32_t tx_cnt; + uint32_t tx_ping_cnt; + uint32_t tx_ack_cnt; +}; + +#endif /* end of SETH_NAPI*/ + +/* + * Device instance data. + */ +typedef struct SEth { + struct net_device_stats stats; /* net statistics */ + struct net_device* netdev; /* Linux net device */ + struct seth_init_data* pdata; /* platform data */ + int state; /* device state */ + int txstate; /* device txstate */ + +#ifdef SETH_NAPI + atomic_t rx_busy; + struct timer_list rx_timer; + + atomic_t txpending; /* seth tx resend count*/ + struct timer_list tx_timer; + struct napi_struct napi; /* napi instance */ + struct seth_dtrans_stats dt_stats; /* record data_transfer statistics*/ +#endif /* SETH_NAPI */ +} SEth; + +#ifdef CONFIG_DEBUG_FS +struct dentry *root = NULL; +uint32_t seth_print_seq = 0; +static int seth_debugfs_mknod(void *root, void * data); +#endif + +#ifdef SETH_NAPI +static void seth_rx_timer_handler(unsigned long data); + +static inline void seth_dt_stats_init(struct seth_dtrans_stats * stats) +{ + memset(stats, 0, sizeof(struct seth_dtrans_stats)); + stats->rx_pkt_min = 0xff; + stats->tx_pkt_min = 0xff; +} + +static inline void seth_rx_stats_update(struct seth_dtrans_stats *stats, uint32_t cnt) +{ + stats->rx_pkt_max = seth_max(stats->rx_pkt_max, cnt); + stats->rx_pkt_min = seth_min(stats->rx_pkt_min, cnt); + stats->rx_sum += cnt; + stats->rx_cnt++; +} + +static inline void seth_tx_stats_update(struct seth_dtrans_stats *stats, uint32_t cnt) +{ + stats->tx_pkt_max = seth_max(stats->tx_pkt_max, cnt); + stats->tx_pkt_min = seth_min(stats->tx_pkt_min, cnt); + stats->tx_sum += cnt; + stats->tx_cnt++; +} + +static inline int pkt_is_ping(void *data) +{ + uint8_t *idata = (uint8_t *)data; + uint8_t ver, protocol; + uint32_t ip_hlen; + int ret = 0; + + /* parse the version of IP protocol */ + ver = (idata[SETH_ETH_HLEN] & 0xf0) >> 4; + if (ver == SETH_IPV4_VERSION) { + struct iphdr * ip_hdr; + struct icmphdr *icmp_hdr; + + ip_hdr = (struct iphdr *)(&idata[SETH_ETH_HLEN]); + /* get length of ip header */ + ip_hlen = ip_hdr->ihl * 4; + /* get the protocol */ + protocol = ip_hdr->protocol; + icmp_hdr = (struct icmphdr *)(&idata[SETH_ETH_HLEN + ip_hlen]); + if (protocol == SETH_PRO_ICMP && icmp_hdr->type == SETH_ICMP_PING) { + ret = 1; + } + } else if (ver == SETH_IPV6_VERSION) { + struct ipv6hdr *ipv6_hdr; + struct icmp6hdr *icmp6_hdr; + + ipv6_hdr = (struct ipv6hdr *)(&idata[SETH_ETH_HLEN]); + ip_hlen = SETH_ETH_HLEN; + /* get the protocol */ + protocol = ipv6_hdr->nexthdr; + icmp6_hdr = (struct icmp6hdr *)(&idata[SETH_ETH_HLEN + ip_hlen]); + if (protocol == SETH_PRO_ICMP && icmp6_hdr->icmp6_type == SETH_ICMP6_PING) { + ret = 1; + } + } + + return ret; +} + +static inline int pkt_is_ack(void *data) +{ + uint8_t *idata = (uint8_t *)data; + uint8_t ver, protocol; + uint32_t ip_hlen; + struct tcphdr *tcp_hdr; + int ret = 0; + + /* parse the version of IP protocol */ + ver = (idata[SETH_ETH_HLEN] & 0xf0) >> 4; + if (ver == SETH_IPV4_VERSION) { + struct iphdr *ip_hdr; + + ip_hdr = (struct iphdr *)(&idata[SETH_ETH_HLEN]); + /*get length of ip header */ + ip_hlen = ip_hdr->ihl * 4; + /* get the protocol*/ + protocol = ip_hdr->protocol; + } else if (ver == SETH_IPV6_VERSION){ + struct ipv6hdr *ipv6_hdr; + + ipv6_hdr = (struct ipv6hdr *)(&idata[SETH_ETH_HLEN]); + ip_hlen = SETH_IPV6_HLEN; + /* get the protocol */ + protocol = ipv6_hdr->nexthdr; + } else { + return 0; + } + + tcp_hdr = (struct tcphdr *)(&idata[SETH_ETH_HLEN + ip_hlen]); + if (protocol == SETH_PRO_TCP && (tcp_hdr->ack || tcp_hdr->syn)) { + ret = 1; + } + + return ret; +} + +static inline void pkt_seq_print(void *data) +{ + uint8_t *idata = (uint8_t *)data; + uint8_t ver, proto; + uint32_t seq, ip_hlen; + static uint32_t nr = 0; + struct tcphdr *tcp_hdr; + + /* parse the version of IP protocol */ + ver = (idata[SETH_ETH_HLEN] & 0xf0) >> 4; + + if (ver == SETH_IPV4_VERSION) { + struct iphdr *ip_hdr; + + ip_hdr = (struct iphdr *)(&idata[SETH_ETH_HLEN]); + /* get length of ip header */ + ip_hlen = ip_hdr->ihl * 4; + /* get the protocol */ + proto = ip_hdr->protocol; + } else if (ver == SETH_IPV6_VERSION) { + struct ipv6hdr *ipv6_hdr; + + ipv6_hdr = (struct ipv6hdr *)(&idata[SETH_ETH_HLEN]); + ip_hlen = SETH_IPV6_HLEN; + /* get protocol */ + proto = ipv6_hdr->nexthdr; + } else { + return; + } + + /* print tcp seq */ + if (proto == SETH_PRO_TCP) { + tcp_hdr = (struct tcphdr*)(&idata[SETH_ETH_HLEN + ip_hlen]); + seq = ntohl(tcp_hdr->seq); + pr_err("SETH: rx pkt: no.%u, ipv%u, nseq = 0x%08x, hseq = 0x%08x\n", + nr++, proto, tcp_hdr->seq, seq); + } +} + +static int seth_rx_poll_handler(struct napi_struct * napi, int budget) +{ + struct SEth *seth = container_of(napi, struct SEth, napi); + struct sk_buff * skb; + struct seth_init_data *pdata; + struct sblock blk; + struct seth_dtrans_stats *dt_stats; + int skb_cnt, blk_ret, ret; + + if (!seth) { + SETH_ERR("seth_rx_poll_handler no seth device\n"); + return 0; + } + + pdata = seth->pdata; + dt_stats = &seth->dt_stats; + + blk_ret = 0; + skb_cnt = 0; + /* keep polling, until the sblock rx ring is empty */ + while ((budget - skb_cnt) && !blk_ret) { + blk_ret = sblock_receive(pdata->dst, pdata->channel, &blk, 0); + if (blk_ret) { + SETH_DEBUG("receive sblock error %d\n", blk_ret); + continue; + } + +#ifdef CONFIG_DEBUG_FS + /* print the sequence field of tcp packet for debug purpose*/ + if (seth_print_seq) { + pkt_seq_print((void *)(blk.addr + NET_IP_ALIGN)); + } +#endif + +#ifdef CONFIG_SETH_OPT + /* + * start from SHARKL, the first 2 bytes of sblock are reserved for optimization, + * so IP field has been aligned to 4 Bytes already + */ + skb = dev_alloc_skb (blk.length); + if (!skb) { + SETH_ERR ("failed to alloc skb!\n"); + seth->stats.rx_dropped++; + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR ("release sblock failed %d\n", ret); + } + dt_stats->rx_alloc_fails++; + continue; + } + + unalign_memcpy(skb->data, blk.addr, blk.length); + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR("release sblock error %d\n", ret); + } + + /* update skb poiter */ + skb_reserve(skb, NET_IP_ALIGN); + skb_put (skb, blk.length - NET_IP_ALIGN); +#else + skb = dev_alloc_skb (blk.length + NET_IP_ALIGN); + if (!skb) { + SETH_ERR ("alloc skbuff failed!\n"); + seth->stats.rx_dropped++; + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR ("release sblock failed %d\n", ret); + } + continue; + } + + skb_reserve(skb, NET_IP_ALIGN); + unalign_memcpy(skb->data, blk.addr, blk.length); + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR("release sblock error %d\n", ret); + } + skb_put (skb, blk.length); +#endif + + skb->protocol = eth_type_trans(skb, seth->netdev); + skb->ip_summed = CHECKSUM_NONE; + + /* update fifo rd_ptr */ + seth->stats.rx_bytes += skb->len; + seth->stats.rx_packets++; + +#ifdef CONFIG_SETH_GRO_DISABLE + netif_receive_skb(skb); +#else + napi_gro_receive(napi, skb); +#endif + /* update skb counter*/ + skb_cnt++; + } + + SETH_DEBUG("napi polling done, budget %d, skb cnt %d, cpu %d\n", + budget, skb_cnt, smp_processor_id()); + + /* update rx statistics */ + seth_rx_stats_update(dt_stats, skb_cnt); + + if (skb_cnt >= 0 && budget > skb_cnt) { + napi_complete(napi); + atomic_dec(&seth->rx_busy); + + /* to guarantee that any arrived sblock(s) can be processed even if there are no events issued by CP */ + if (sblock_get_arrived_count(pdata->dst, pdata->channel)) { + /* start a timer with 2 jiffies expries (20 ms) */ + seth->rx_timer.function = seth_rx_timer_handler; + seth->rx_timer.expires = jiffies + HZ/50; + seth->rx_timer.data = (unsigned long)seth; + mod_timer(&seth->rx_timer, seth->rx_timer.expires); + SETH_INFO("start rx_timer, jiffies %lu.\n", jiffies); + } + } + + return skb_cnt; +} + +#endif + +/* + * Tx_ready handler. + */ +static void +seth_tx_ready_handler (void* data) +{ + SEth* seth = (SEth*) data; + + SETH_DEBUG("seth_tx_ready_handler state %0x\n", seth->state); + if (seth->state != DEV_ON) { + seth->state = DEV_ON; + seth->txstate = DEV_ON; + if (!netif_carrier_ok (seth->netdev)) { + netif_carrier_on (seth->netdev); + } + } + else { + seth->state = DEV_OFF; + seth->txstate = DEV_OFF; + if (netif_carrier_ok (seth->netdev)) { + netif_carrier_off (seth->netdev); + } + } +} + +/* + * Tx_open handler. + */ +static void +seth_tx_open_handler (void* data) +{ + SEth* seth = (SEth*) data; + + SETH_DEBUG("seth_tx_open_handler state %0x\n", seth->state); + if (seth->state != DEV_ON) { + seth->state = DEV_ON; + seth->txstate = DEV_ON; + if (!netif_carrier_ok (seth->netdev)) { + netif_carrier_on (seth->netdev); + } + } +} + +/* + * Tx_close handler. + */ +static void +seth_tx_close_handler (void* data) +{ + SEth* seth = (SEth*) data; + + SETH_INFO("seth_tx_close_handler state %0x\n", seth->state); + if (seth->state != DEV_OFF) { + seth->state = DEV_OFF; + seth->txstate = DEV_OFF; + if (netif_carrier_ok (seth->netdev)) { + netif_carrier_off (seth->netdev); + } + } +} + +#ifdef SETH_NAPI +static void seth_rx_handler (void * data) +{ + SEth* seth = (SEth *)data; + + if (!seth) { + SETH_ERR("seth_rx_handler data is NULL.\n"); + return; + } + + /* if the poll handler has been done, trigger to schedule*/ + if (!atomic_cmpxchg(&seth->rx_busy, 0, 1)) { + /* update rx stats*/ + napi_schedule(&seth->napi); + /* trigger a NET_RX_SOFTIRQ softirq directly */ + raise_softirq(NET_RX_SOFTIRQ); + } +} + +static void seth_rx_timer_handler(unsigned long data) +{ + SETH_INFO("rx_timer expried, jiffies %lu.\n", jiffies); + seth_rx_handler((void *)data); +} + +#else +static void seth_rx_handler (void* data) +{ + SEth* seth = (SEth *)data; + struct seth_init_data *pdata = seth->pdata; + struct sblock blk; + struct sk_buff* skb; + uint32_t cnt; + int ret, sblkret; + + sblkret = 0; + cnt = 0; + while (!sblkret){ + sblkret = sblock_receive(pdata->dst, pdata->channel, &blk, 0); + if (sblkret) { + SETH_DEBUG("receive sblock error %d\n", sblkret); + break; + } + + /* if the seth state is off, drop the comming packet */ + if (seth->state != DEV_ON) { + SETH_ERR ("rx_handler the state of %s is off!\n", seth->netdev->name); + sblock_release(pdata->dst, pdata->channel, &blk); + continue; + } + +#ifdef CONFIG_SETH_OPT + /* + * start from SHARKL, the first 2 bytes of sblock are reserved for optimization, + * so IP field has been aligned to 4 Bytes already + */ + skb = dev_alloc_skb (blk.length); + if (!skb) { + SETH_ERR ("failed to alloc skb!\n"); + seth->stats.rx_dropped++; + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR ("release sblock failed %d\n", ret); + } + dt_stats->rx_alloc_fails++; + continue; + } + + unalign_memcpy(skb->data, blk.addr, blk.length); + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR("release sblock error %d\n", ret); + } + + /* update skb poiter */ + skb_reserve(skb, NET_IP_ALIGN); + skb_put (skb, blk.length - NET_IP_ALIGN); +#else + skb = dev_alloc_skb (blk.length + NET_IP_ALIGN); + if (!skb) { + SETH_ERR ("alloc skbuff failed!\n"); + seth->stats.rx_dropped++; + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR ("release sblock failed %d\n", ret); + } + continue; + } + + skb_reserve(skb, NET_IP_ALIGN); + unalign_memcpy(skb->data, blk.addr, blk.length); + ret = sblock_release(pdata->dst, pdata->channel, &blk); + if (ret) { + SETH_ERR("release sblock error %d\n", ret); + } + skb_put (skb, blk.length); +#endif + + skb->dev = seth->netdev; + skb->protocol = eth_type_trans (skb, seth->netdev); + skb->ip_summed = CHECKSUM_NONE; + + seth->stats.rx_packets++; + seth->stats.rx_bytes += skb->len; + + netif_rx (skb); + + seth->netdev->last_rx = jiffies; + } + + return; +} +#endif + + +/* + * Tx_close handler. + */ +static void +seth_tx_pre_handler (void* data) +{ + SEth* seth = (SEth*) data; + + if (seth->txstate != DEV_ON) { + seth->txstate = DEV_ON; + SETH_INFO ("seth_tx_ready_handler txstate %0x\n", seth->txstate ); + seth->netdev->trans_start = jiffies; + if (netif_queue_stopped(seth->netdev)) { + netif_wake_queue(seth->netdev); + } + } +} + +static void +seth_handler (int event, void* data) +{ + SEth *seth = (SEth *)data; + + switch(event) { + case SBLOCK_NOTIFY_GET: + SETH_DEBUG ("SBLOCK_NOTIFY_GET is received\n"); + seth_tx_pre_handler(seth); + break; + case SBLOCK_NOTIFY_RECV: + SETH_DEBUG ("SBLOCK_NOTIFY_RECV is received\n"); + del_timer(&seth->rx_timer); + seth_rx_handler(seth); + break; + case SBLOCK_NOTIFY_STATUS: + SETH_DEBUG ("SBLOCK_NOTIFY_STATUS is received\n"); + seth_tx_ready_handler(seth); + break; + case SBLOCK_NOTIFY_OPEN: + SETH_DEBUG ("SBLOCK_NOTIFY_OPEN is received\n"); + seth_tx_open_handler(seth); + break; + case SBLOCK_NOTIFY_CLOSE: + SETH_DEBUG ("SBLOCK_NOTIFY_CLOSE is received\n"); + seth_tx_close_handler(seth); + break; + default: + SETH_ERR ("Received event is invalid(event=%d)\n", event); + } +} + +static int +seth_tx_pkt(void* data, struct sk_buff* skb) +{ + struct sblock blk; + SEth* seth = netdev_priv (data); + struct seth_init_data *pdata = seth->pdata; + int ret; + + /* + * Get a free sblock. + */ + ret = sblock_get(pdata->dst, pdata->channel, &blk, 0); + if(ret) { + SETH_INFO("Get free sblock failed(%d), drop data!\n", ret); + seth->stats.tx_fifo_errors++; + return SETH_TX_NO_BLK; + } + + if(blk.length < (skb->len + NET_IP_ALIGN)) { + SETH_ERR("The size of sblock is so tiny!\n"); + /* recycle the unused sblock */ + sblock_put(pdata->dst, pdata->channel, &blk); + seth->stats.tx_fifo_errors++; + netif_wake_queue(seth->netdev); + return SETH_TX_INVALID_BLK; + } + +#ifdef CONFIG_SETH_OPT + /* padding 2 Bytes in the head */ + blk.length = skb->len + NET_IP_ALIGN; + unalign_memcpy(blk.addr + NET_IP_ALIGN, skb->data, skb->len); + /* copy the content into smem in mute */ + sblock_send_prepare(pdata->dst, pdata->channel, &blk); +#else + blk.length = skb->len; + unalign_memcpy(blk.addr, skb->data,skb->len); + /* copy the content into smem and trigger a smsg to the peer side */ + sblock_send(pdata->dst, pdata->channel, &blk); +#endif + /* update the statistics */ + seth->stats.tx_bytes += skb->len; + seth->stats.tx_packets++; + + dev_kfree_skb_any(skb); + +#ifdef CONFIG_SETH_OPT + atomic_inc(&seth->txpending); +#endif + + return SETH_TX_SUCCESS; +} + +static void seth_tx_flush(unsigned long data) +{ + int ret; + uint32_t cnt; + SEth* seth = netdev_priv((void *)data); + struct seth_init_data *pdata = seth->pdata; + + cnt = (uint32_t)atomic_read(&seth->txpending); + ret = sblock_send_finish(pdata->dst, pdata->channel); + seth_tx_stats_update(&seth->dt_stats, cnt); + if(ret) { + SETH_INFO("seth tx failed(%d)!\n", ret); + } else { + atomic_set(&seth->txpending, 0); + } +} + +/* + * Transmit interface + */ +static int +seth_start_xmit (struct sk_buff* skb, struct net_device* dev) +{ + SEth* seth = netdev_priv(dev); + struct seth_dtrans_stats *dt_stats; + int flag = 0, ret = 0; + int blk_cnt = 0; + + if (seth->state != DEV_ON) { + SETH_ERR ("xmit the state of %s is off\n", dev->name); + netif_carrier_off (dev); + seth->stats.tx_carrier_errors++; + dev_kfree_skb_any(skb); + return NETDEV_TX_OK; + } + + /* update tx statistics */ + dt_stats = &seth->dt_stats; + if (pkt_is_ping((void *)skb->data)) { + dt_stats->tx_ping_cnt++; + flag = 1; + } else if (pkt_is_ack((void *)skb->data)) { + dt_stats->tx_ack_cnt++; + flag = 1; + } + + ret = seth_tx_pkt(dev, skb); + if (SETH_TX_NO_BLK == ret) { + /* if there are no available sblks, enter flow control */ + seth->txstate = DEV_OFF; + netif_stop_queue(dev); + /* anyway, flush the stored sblks */ + seth_tx_flush((unsigned long)dev); + return NETDEV_TX_BUSY; + } else if (SETH_TX_INVALID_BLK == ret) { + return NETDEV_TX_OK; + } + + /* if there are no available sblock for subsequent skb, start flow control*/ + blk_cnt = sblock_get_free_count(seth->pdata->dst, seth->pdata->channel); + if (!blk_cnt) { + SETH_DEBUG("start flow control\n"); + seth->txstate = DEV_OFF; + netif_stop_queue(dev); + } + +#ifdef CONFIG_SETH_OPT + if ((atomic_read(&seth->txpending) >= SETH_TX_WEIGHT) || flag) { + del_timer(&seth->tx_timer); + seth_tx_flush((unsigned long)dev); + SETH_DEBUG ("seth_start_xmit:at once\n"); + } else if (atomic_read(&seth->txpending) == 1){ + /* start a timer with 1 jiffy expries (10 ms) */ + seth->tx_timer.function = seth_tx_flush; + seth->tx_timer.expires = jiffies + HZ/100; + seth->tx_timer.data = (unsigned long)dev; + mod_timer(&seth->tx_timer, seth->tx_timer.expires); + SETH_DEBUG ("seth_start_xmit:Timer\n"); + } +#endif + dev->trans_start = jiffies; + return NETDEV_TX_OK; +} + +/* + * Open interface + */ +static int seth_open (struct net_device *dev) +{ + SEth* seth = netdev_priv(dev); + struct seth_init_data *pdata; + struct sblock blk; + int ret = 0, num = 0; + + SETH_INFO("open seth!\n"); + + if (!seth) { + return -ENODEV; + } + + pdata = seth->pdata; + + /* clean the resident sblocks */ + while(!ret) { + ret = sblock_receive(pdata->dst, pdata->channel, &blk, 0); + if (!ret) { + sblock_release(pdata->dst, pdata->channel, &blk); + num++; + } + } + SETH_INFO("seth_open clean %d resident sblocks\n", num); + + /* Reset stats */ + memset(&seth->stats, 0, sizeof(seth->stats)); + + if (seth->state == DEV_ON && !netif_carrier_ok(seth->netdev)) { + SETH_INFO("seth_open netif_carrier_on\n"); + netif_carrier_on(seth->netdev); + } + +#ifdef SETH_NAPI + atomic_set(&seth->rx_busy, 0); + napi_enable(&seth->napi); + seth_dt_stats_init(&seth->dt_stats); +#endif + + seth->txstate = DEV_ON; + seth->state = DEV_ON; + + netif_start_queue(dev); + + return 0; +} + +/* + * Close interface + */ +static int seth_close (struct net_device *dev) +{ + SEth* seth = netdev_priv(dev); + + SETH_INFO("close seth!\n"); + + seth->txstate = DEV_OFF; + seth->state = DEV_OFF; + +#ifdef SETH_NAPI + napi_disable(&seth->napi); +#endif + + netif_stop_queue(dev); + + return 0; +} + +static struct net_device_stats * seth_get_stats(struct net_device *dev) +{ + SEth * seth = netdev_priv(dev); + return &(seth->stats); +} + +static void seth_tx_timeout(struct net_device *dev) +{ + SEth * seth = netdev_priv(dev); + + SETH_INFO ("seth_tx_timeout()\n"); + if (seth->txstate != DEV_ON) { + seth->txstate = DEV_ON; + dev->trans_start = jiffies; + netif_wake_queue(dev); + } +} + +static struct net_device_ops seth_ops = { + .ndo_open = seth_open, + .ndo_stop = seth_close, + .ndo_start_xmit = seth_start_xmit, + .ndo_get_stats = seth_get_stats, + .ndo_tx_timeout = seth_tx_timeout, +}; + +static int seth_parse_dt(struct seth_init_data **init, struct device *dev) +{ +#ifdef CONFIG_OF + struct seth_init_data *pdata = NULL; + struct device_node *np = dev->of_node; + int ret; + uint32_t data; + + pdata = kzalloc(sizeof(struct seth_init_data), GFP_KERNEL); + if (!pdata) { + return -ENOMEM; + } + + ret = of_property_read_string(np, "sprd,name", (const char**)&pdata->name); + if (ret) { + goto error; + } + + ret = of_property_read_u32(np, "sprd,dst", (uint32_t *)&data); + if (ret) { + goto error; + } + pdata->dst = (uint8_t)data; + + ret = of_property_read_u32(np, "sprd,channel", (uint32_t *)&data); + if (ret) { + goto error; + } + pdata->channel = (uint8_t)data; + + ret = of_property_read_u32(np, "sprd,blknum", (uint32_t *)&pdata->blocknum); + if (ret) { + goto error; + } + + *init = pdata; + return 0; +error: + kfree(pdata); + *init = NULL; + return ret; +#else + return -ENODEV; +#endif +} + +static inline void seth_destroy_pdata(struct seth_init_data **init) +{ +#ifdef CONFIG_OF + struct seth_init_data *pdata = *init; + + if (pdata) { + kfree(pdata); + } + *init = NULL; +#else + return; +#endif +} + +static int seth_probe(struct platform_device *pdev) +{ + struct seth_init_data *pdata = pdev->dev.platform_data; + struct net_device* netdev; + SEth* seth; + char ifname[IFNAMSIZ]; + int ret; + + if (pdev->dev.of_node && !pdata) { + ret = seth_parse_dt(&pdata, &pdev->dev); + if (ret) { + SETH_ERR( "failed to parse seth device tree, ret=%d\n", ret); + return ret; + } + } + SETH_INFO("after parse device tree, name=%s, dst=%u, channel=%u, blocknum=%u\n", + pdata->name, pdata->dst, pdata->channel, pdata->blocknum); + + if(pdata->name[0]) + strlcpy(ifname, pdata->name, IFNAMSIZ); + else + strcpy(ifname, "veth%d"); + + netdev = alloc_netdev (sizeof (SEth), ifname, ether_setup); + if (!netdev) { + seth_destroy_pdata(&pdata); + SETH_ERR ("alloc_netdev() failed.\n"); + return -ENOMEM; + } + seth = netdev_priv (netdev); + seth->pdata = pdata; + seth->netdev = netdev; + seth->state = DEV_OFF; + +#ifdef SETH_NAPI + atomic_set(&seth->rx_busy, 0); + atomic_set(&seth->txpending, 0); + + init_timer(&seth->rx_timer); + init_timer(&seth->tx_timer); + seth_dt_stats_init(&seth->dt_stats); +#endif + + netdev->netdev_ops = &seth_ops; + netdev->watchdog_timeo = 1*HZ; + netdev->irq = 0; + netdev->dma = 0; + + random_ether_addr(netdev->dev_addr); + +#ifdef SETH_NAPI + netif_napi_add(netdev, &seth->napi, seth_rx_poll_handler, SETH_NAPI_WEIGHT); +#endif + + ret = sblock_create(pdata->dst, pdata->channel, + pdata->blocknum, SETH_BLOCK_SIZE, + pdata->blocknum, SETH_BLOCK_SIZE); + if (ret) { + SETH_ERR ("create sblock failed (%d)\n", ret); +#ifdef SETH_NAPI + netif_napi_del(&seth->napi); +#endif + free_netdev(netdev); + seth_destroy_pdata(&pdata); + return ret; + } + + ret = sblock_register_notifier(pdata->dst, pdata->channel, seth_handler, seth); + if (ret) { + SETH_ERR ("regitster notifier failed (%d)\n", ret); +#ifdef SETH_NAPI + netif_napi_del(&seth->napi); +#endif + free_netdev(netdev); + sblock_destroy(pdata->dst, pdata->channel); + seth_destroy_pdata(&pdata); + return ret; + } + + /* register new Ethernet interface */ + if ((ret = register_netdev (netdev))) { + SETH_ERR ("register_netdev() failed (%d)\n", ret); +#ifdef SETH_NAPI + netif_napi_del(&seth->napi); +#endif + free_netdev(netdev); + sblock_destroy(pdata->dst, pdata->channel); + seth_destroy_pdata(&pdata); + return ret; + } + + /* set link as disconnected */ + netif_carrier_off (netdev); + + platform_set_drvdata(pdev, seth); +#ifdef CONFIG_DEBUG_FS + seth_debugfs_mknod(root, (void *)seth); +#endif + return 0; +} + +/* + * Cleanup Ethernet device driver. + */ +static int seth_remove (struct platform_device *pdev) +{ + struct SEth* seth = platform_get_drvdata(pdev); + struct seth_init_data *pdata = seth->pdata; + +#ifdef SETH_NAPI + netif_napi_del(&seth->napi); + + del_timer_sync(&seth->rx_timer); + del_timer_sync(&seth->tx_timer); +#endif + + sblock_destroy(pdata->dst, pdata->channel); + seth_destroy_pdata(&pdata); + unregister_netdev(seth->netdev); + free_netdev(seth->netdev); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id seth_match_table[] = { + { .compatible = "sprd,seth", }, + { }, +}; + +static struct platform_driver seth_driver = { + .probe = seth_probe, + .remove = seth_remove, + .driver = { + .owner = THIS_MODULE, + .name = "seth", + .of_match_table = seth_match_table, + }, +}; + +static int __init seth_init(void) +{ + return platform_driver_register(&seth_driver); +} + +static void __exit seth_exit(void) +{ + platform_driver_unregister(&seth_driver); +} + +module_init (seth_init); +module_exit (seth_exit); + +#ifdef CONFIG_DEBUG_FS +static int seth_debug_show(struct seq_file *m, void *v) +{ + struct SEth *seth = (struct SEth*)(m->private); + struct seth_dtrans_stats *stats; + struct seth_init_data *pdata; + + if(!seth) { + SETH_ERR("invalid data, seth is NULL\n"); + return -EINVAL; + } + + pdata = seth->pdata; + stats = &seth->dt_stats; + + seq_printf(m, "******************************************************************\n"); + seq_printf(m, "DEVICE: %s, state %d, NET_SKB_PAD %u\n", pdata->name, seth->state, NET_SKB_PAD); + seq_printf(m, "\nRX statistics:\n"); + seq_printf(m, "rx_pkt_max=%u, rx_pkt_min=%u, rx_sum=%u, rx_cnt=%u\n", + stats->rx_pkt_max, stats->rx_pkt_min, stats->rx_sum, stats->rx_cnt); + seq_printf(m, "rx_alloc_fails=%u, rx_busy=%d\n", stats->rx_alloc_fails, atomic_read(&seth->rx_busy)); + + seq_printf(m, "\nTX statistics:\n"); + seq_printf(m, "tx_pkt_max=%u, tx_pkt_min=%u, tx_sum=%u, tx_cnt=%u\n", + stats->tx_pkt_max, stats->tx_pkt_min, stats->tx_sum, stats->tx_cnt); + seq_printf(m, "tx_ping_cnt=%u, tx_ack_cnt=%u\n", + stats->tx_ping_cnt, stats->tx_ack_cnt); + seq_printf(m, "******************************************************************\n"); + return 0; +} + +static int seth_debug_open(struct inode *inode, struct file *file) +{ + return single_open(file, seth_debug_show, inode->i_private); +} + +static const struct file_operations seth_debug_fops = { + .open = seth_debug_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int seth_debugfs_mknod(void *root, void * data) +{ + struct SEth *seth = (struct SEth*) data; + struct seth_init_data *pdata; + + if (!seth) { + return -ENODEV; + } + pdata = seth->pdata; + + if (!root) + return -ENXIO; + + debugfs_create_file(pdata->name, S_IRUGO, (struct dentry *)root, data, &seth_debug_fops); + return 0; +} + +static int __init seth_debugfs_init(void) +{ + root = debugfs_create_dir("seth", NULL); + if (!root) { + SETH_ERR("failed to create seth debugfs dir\n"); + return -ENODEV; + } + + debugfs_create_u32("print_seq", S_IRUGO | S_IWUGO, root, &seth_print_seq); + + return 0; +} + +fs_initcall(seth_debugfs_init); +#endif + +MODULE_AUTHOR("Qiu Yi"); +MODULE_DESCRIPTION ("Spreadtrum Ethernet device driver"); +MODULE_LICENSE ("GPL"); + |
