netpoll per device txq
[powerpc.git] / net / core / netpoll.c
index 9cb7818..ac4e8b8 100644 (file)
 #define MAX_QUEUE_DEPTH (MAX_SKBS / 2)
 #define MAX_RETRIES 20000
 
-static DEFINE_SPINLOCK(skb_list_lock);
-static int nr_skbs;
-static struct sk_buff *skbs;
-
-static DEFINE_SPINLOCK(queue_lock);
-static int queue_depth;
-static struct sk_buff *queue_head, *queue_tail;
+static struct sk_buff_head skb_pool;
 
 static atomic_t trapped;
 
@@ -54,49 +48,29 @@ static atomic_t trapped;
                                sizeof(struct iphdr) + sizeof(struct ethhdr))
 
 static void zap_completion_queue(void);
+static void arp_reply(struct sk_buff *skb);
 
 static void queue_process(void *p)
 {
-       unsigned long flags;
+       struct netpoll_info *npinfo = p;
        struct sk_buff *skb;
 
-       while (queue_head) {
-               spin_lock_irqsave(&queue_lock, flags);
-
-               skb = queue_head;
-               queue_head = skb->next;
-               if (skb == queue_tail)
-                       queue_head = NULL;
-
-               queue_depth--;
-
-               spin_unlock_irqrestore(&queue_lock, flags);
-
+       while ((skb = skb_dequeue(&npinfo->txq)))
                dev_queue_xmit(skb);
-       }
-}
 
-static DECLARE_WORK(send_queue, queue_process, NULL);
+}
 
 void netpoll_queue(struct sk_buff *skb)
 {
-       unsigned long flags;
+       struct net_device *dev = skb->dev;
+       struct netpoll_info *npinfo = dev->npinfo;
 
-       if (queue_depth == MAX_QUEUE_DEPTH) {
-               __kfree_skb(skb);
-               return;
+       if (!npinfo)
+               kfree_skb(skb);
+       else {
+               skb_queue_tail(&npinfo->txq, skb);
+               schedule_work(&npinfo->tx_work);
        }
-
-       spin_lock_irqsave(&queue_lock, flags);
-       if (!queue_head)
-               queue_head = skb;
-       else
-               queue_tail->next = skb;
-       queue_tail = skb;
-       queue_depth++;
-       spin_unlock_irqrestore(&queue_lock, flags);
-
-       schedule_work(&send_queue);
 }
 
 static int checksum_udp(struct sk_buff *skb, struct udphdr *uh,
@@ -109,7 +83,7 @@ static int checksum_udp(struct sk_buff *skb, struct udphdr *uh,
 
        psum = csum_tcpudp_nofold(saddr, daddr, ulen, IPPROTO_UDP, 0);
 
-       if (skb->ip_summed == CHECKSUM_HW &&
+       if (skb->ip_summed == CHECKSUM_COMPLETE &&
            !(u16)csum_fold(csum_add(psum, skb->csum)))
                return 0;
 
@@ -153,6 +127,22 @@ static void poll_napi(struct netpoll *np)
        }
 }
 
+static void service_arp_queue(struct netpoll_info *npi)
+{
+       struct sk_buff *skb;
+
+       if (unlikely(!npi))
+               return;
+
+       skb = skb_dequeue(&npi->arp_tx);
+
+       while (skb != NULL) {
+               arp_reply(skb);
+               skb = skb_dequeue(&npi->arp_tx);
+       }
+       return;
+}
+
 void netpoll_poll(struct netpoll *np)
 {
        if(!np->dev || !netif_running(np->dev) || !np->dev->poll_controller)
@@ -163,6 +153,8 @@ void netpoll_poll(struct netpoll *np)
        if (np->dev->poll)
                poll_napi(np);
 
+       service_arp_queue(np->dev->npinfo);
+
        zap_completion_queue();
 }
 
@@ -171,17 +163,15 @@ static void refill_skbs(void)
        struct sk_buff *skb;
        unsigned long flags;
 
-       spin_lock_irqsave(&skb_list_lock, flags);
-       while (nr_skbs < MAX_SKBS) {
+       spin_lock_irqsave(&skb_pool.lock, flags);
+       while (skb_pool.qlen < MAX_SKBS) {
                skb = alloc_skb(MAX_SKB_SIZE, GFP_ATOMIC);
                if (!skb)
                        break;
 
-               skb->next = skbs;
-               skbs = skb;
-               nr_skbs++;
+               __skb_queue_tail(&skb_pool, skb);
        }
-       spin_unlock_irqrestore(&skb_list_lock, flags);
+       spin_unlock_irqrestore(&skb_pool.lock, flags);
 }
 
 static void zap_completion_queue(void)
@@ -210,38 +200,25 @@ static void zap_completion_queue(void)
        put_cpu_var(softnet_data);
 }
 
-static struct sk_buff * find_skb(struct netpoll *np, int len, int reserve)
+static struct sk_buff *find_skb(struct netpoll *np, int len, int reserve)
 {
-       int once = 1, count = 0;
-       unsigned long flags;
-       struct sk_buff *skb = NULL;
+       int count = 0;
+       struct sk_buff *skb;
 
        zap_completion_queue();
+       refill_skbs();
 repeat:
-       if (nr_skbs < MAX_SKBS)
-               refill_skbs();
 
        skb = alloc_skb(len, GFP_ATOMIC);
-
-       if (!skb) {
-               spin_lock_irqsave(&skb_list_lock, flags);
-               skb = skbs;
-               if (skb) {
-                       skbs = skb->next;
-                       skb->next = NULL;
-                       nr_skbs--;
-               }
-               spin_unlock_irqrestore(&skb_list_lock, flags);
-       }
+       if (!skb)
+               skb = skb_dequeue(&skb_pool);
 
        if(!skb) {
-               count++;
-               if (once && (count == 1000000)) {
-                       printk("out of netpoll skbs!\n");
-                       once = 0;
+               if (++count < 10) {
+                       netpoll_poll(np);
+                       goto repeat;
                }
-               netpoll_poll(np);
-               goto repeat;
+               return NULL;
        }
 
        atomic_set(&skb->users, 1);
@@ -279,14 +256,10 @@ static void netpoll_send_skb(struct netpoll *np, struct sk_buff *skb)
                 * network drivers do not expect to be called if the queue is
                 * stopped.
                 */
-               if (netif_queue_stopped(np->dev)) {
-                       netif_tx_unlock(np->dev);
-                       netpoll_poll(np);
-                       udelay(50);
-                       continue;
-               }
+               status = NETDEV_TX_BUSY;
+               if (!netif_queue_stopped(np->dev))
+                       status = np->dev->hard_start_xmit(skb, np->dev);
 
-               status = np->dev->hard_start_xmit(skb, np->dev);
                netif_tx_unlock(np->dev);
 
                /* success */
@@ -320,13 +293,19 @@ void netpoll_send_udp(struct netpoll *np, const char *msg, int len)
        memcpy(skb->data, msg, len);
        skb->len += len;
 
-       udph = (struct udphdr *) skb_push(skb, sizeof(*udph));
+       skb->h.uh = udph = (struct udphdr *) skb_push(skb, sizeof(*udph));
        udph->source = htons(np->local_port);
        udph->dest = htons(np->remote_port);
        udph->len = htons(udp_len);
        udph->check = 0;
+       udph->check = csum_tcpudp_magic(htonl(np->local_ip),
+                                       htonl(np->remote_ip),
+                                       udp_len, IPPROTO_UDP,
+                                       csum_partial((unsigned char *)udph, udp_len, 0));
+       if (udph->check == 0)
+               udph->check = -1;
 
-       iph = (struct iphdr *)skb_push(skb, sizeof(*iph));
+       skb->nh.iph = iph = (struct iphdr *)skb_push(skb, sizeof(*iph));
 
        /* iph->version = 4; iph->ihl = 5; */
        put_unaligned(0x45, (unsigned char *)iph);
@@ -342,8 +321,8 @@ void netpoll_send_udp(struct netpoll *np, const char *msg, int len)
        iph->check    = ip_fast_csum((unsigned char *)iph, iph->ihl);
 
        eth = (struct ethhdr *) skb_push(skb, ETH_HLEN);
-
-       eth->h_proto = htons(ETH_P_IP);
+       skb->mac.raw = skb->data;
+       skb->protocol = eth->h_proto = htons(ETH_P_IP);
        memcpy(eth->h_source, np->local_mac, 6);
        memcpy(eth->h_dest, np->remote_mac, 6);
 
@@ -446,7 +425,9 @@ int __netpoll_rx(struct sk_buff *skb)
        int proto, len, ulen;
        struct iphdr *iph;
        struct udphdr *uh;
-       struct netpoll *np = skb->dev->npinfo->rx_np;
+       struct netpoll_info *npi = skb->dev->npinfo;
+       struct netpoll *np = npi->rx_np;
+
 
        if (!np)
                goto out;
@@ -456,7 +437,7 @@ int __netpoll_rx(struct sk_buff *skb)
        /* check if netpoll clients need ARP */
        if (skb->protocol == __constant_htons(ETH_P_ARP) &&
            atomic_read(&trapped)) {
-               arp_reply(skb);
+               skb_queue_tail(&npi->arp_tx, skb);
                return 1;
        }
 
@@ -651,8 +632,15 @@ int netpoll_setup(struct netpoll *np)
                npinfo->poll_owner = -1;
                npinfo->tries = MAX_RETRIES;
                spin_lock_init(&npinfo->rx_lock);
-       } else
+               skb_queue_head_init(&npinfo->arp_tx);
+               skb_queue_head_init(&npinfo->txq);
+               INIT_WORK(&npinfo->tx_work, queue_process, npinfo);
+
+               atomic_set(&npinfo->refcnt, 1);
+       } else {
                npinfo = ndev->npinfo;
+               atomic_inc(&npinfo->refcnt);
+       }
 
        if (!ndev->poll_controller) {
                printk(KERN_ERR "%s: %s doesn't support polling, aborting.\n",
@@ -746,6 +734,12 @@ int netpoll_setup(struct netpoll *np)
        return -1;
 }
 
+static int __init netpoll_init(void) {
+       skb_queue_head_init(&skb_pool);
+       return 0;
+}
+core_initcall(netpoll_init);
+
 void netpoll_cleanup(struct netpoll *np)
 {
        struct netpoll_info *npinfo;
@@ -753,12 +747,24 @@ void netpoll_cleanup(struct netpoll *np)
 
        if (np->dev) {
                npinfo = np->dev->npinfo;
-               if (npinfo && npinfo->rx_np == np) {
-                       spin_lock_irqsave(&npinfo->rx_lock, flags);
-                       npinfo->rx_np = NULL;
-                       npinfo->rx_flags &= ~NETPOLL_RX_ENABLED;
-                       spin_unlock_irqrestore(&npinfo->rx_lock, flags);
+               if (npinfo) {
+                       if (npinfo->rx_np == np) {
+                               spin_lock_irqsave(&npinfo->rx_lock, flags);
+                               npinfo->rx_np = NULL;
+                               npinfo->rx_flags &= ~NETPOLL_RX_ENABLED;
+                               spin_unlock_irqrestore(&npinfo->rx_lock, flags);
+                       }
+
+                       np->dev->npinfo = NULL;
+                       if (atomic_dec_and_test(&npinfo->refcnt)) {
+                               skb_queue_purge(&npinfo->arp_tx);
+                               skb_queue_purge(&npinfo->txq);
+                               flush_scheduled_work();
+
+                               kfree(npinfo);
+                       }
                }
+
                dev_put(np->dev);
        }