| HISTORY: |
| February 16/2002 -- revision 0.2.1: |
| COR typo corrected |
| February 10/2002 -- revision 0.2: |
| some spell checking ;-> |
| January 12/2002 -- revision 0.1 |
| This is still work in progress so may change. |
| To keep up to date please watch this space. |
| |
| Introduction to NAPI |
| ==================== |
| |
| NAPI is a proven (www.cyberus.ca/~hadi/usenix-paper.tgz) technique |
| to improve network performance on Linux. For more details please |
| read that paper. |
| NAPI provides a "inherent mitigation" which is bound by system capacity |
| as can be seen from the following data collected by Robert on Gigabit |
| ethernet (e1000): |
| |
| Psize Ipps Tput Rxint Txint Done Ndone |
| --------------------------------------------------------------- |
| 60 890000 409362 17 27622 7 6823 |
| 128 758150 464364 21 9301 10 7738 |
| 256 445632 774646 42 15507 21 12906 |
| 512 232666 994445 241292 19147 241192 1062 |
| 1024 119061 1000003 872519 19258 872511 0 |
| 1440 85193 1000003 946576 19505 946569 0 |
| |
| |
| Legend: |
| "Ipps" stands for input packets per second. |
| "Tput" == packets out of total 1M that made it out. |
| "txint" == transmit completion interrupts seen |
| "Done" == The number of times that the poll() managed to pull all |
| packets out of the rx ring. Note from this that the lower the |
| load the more we could clean up the rxring |
| "Ndone" == is the converse of "Done". Note again, that the higher |
| the load the more times we couldnt clean up the rxring. |
| |
| Observe that: |
| when the NIC receives 890Kpackets/sec only 17 rx interrupts are generated. |
| The system cant handle the processing at 1 interrupt/packet at that load level. |
| At lower rates on the other hand, rx interrupts go up and therefore the |
| interrupt/packet ratio goes up (as observable from that table). So there is |
| possibility that under low enough input, you get one poll call for each |
| input packet caused by a single interrupt each time. And if the system |
| cant handle interrupt per packet ratio of 1, then it will just have to |
| chug along .... |
| |
| |
| 0) Prerequisites: |
| ================== |
| A driver MAY continue using the old 2.4 technique for interfacing |
| to the network stack and not benefit from the NAPI changes. |
| NAPI additions to the kernel do not break backward compatibility. |
| NAPI, however, requires the following features to be available: |
| |
| A) DMA ring or enough RAM to store packets in software devices. |
| |
| B) Ability to turn off interrupts or maybe events that send packets up |
| the stack. |
| |
| NAPI processes packet events in what is known as dev->poll() method. |
| Typically, only packet receive events are processed in dev->poll(). |
| The rest of the events MAY be processed by the regular interrupt handler |
| to reduce processing latency (justified also because there are not that |
| many of them). |
| Note, however, NAPI does not enforce that dev->poll() only processes |
| receive events. |
| Tests with the tulip driver indicated slightly increased latency if |
| all of the interrupt handler is moved to dev->poll(). Also MII handling |
| gets a little trickier. |
| The example used in this document is to move the receive processing only |
| to dev->poll(); this is shown with the patch for the tulip driver. |
| For an example of code that moves all the interrupt driver to |
| dev->poll() look at the ported e1000 code. |
| |
| There are caveats that might force you to go with moving everything to |
| dev->poll(). Different NICs work differently depending on their status/event |
| acknowledgement setup. |
| There are two types of event register ACK mechanisms. |
| I) what is known as Clear-on-read (COR). |
| when you read the status/event register, it clears everything! |
| The natsemi and sunbmac NICs are known to do this. |
| In this case your only choice is to move all to dev->poll() |
| |
| II) Clear-on-write (COW) |
| i) you clear the status by writing a 1 in the bit-location you want. |
| These are the majority of the NICs and work the best with NAPI. |
| Put only receive events in dev->poll(); leave the rest in |
| the old interrupt handler. |
| ii) whatever you write in the status register clears every thing ;-> |
| Cant seem to find any supported by Linux which do this. If |
| someone knows such a chip email us please. |
| Move all to dev->poll() |
| |
| C) Ability to detect new work correctly. |
| NAPI works by shutting down event interrupts when theres work and |
| turning them on when theres none. |
| New packets might show up in the small window while interrupts were being |
| re-enabled (refer to appendix 2). A packet might sneak in during the period |
| we are enabling interrupts. We only get to know about such a packet when the |
| next new packet arrives and generates an interrupt. |
| Essentially, there is a small window of opportunity for a race condition |
| which for clarity we'll refer to as the "rotting packet". |
| |
| This is a very important topic and appendix 2 is dedicated for more |
| discussion. |
| |
| Locking rules and environmental guarantees |
| ========================================== |
| |
| -Guarantee: Only one CPU at any time can call dev->poll(); this is because |
| only one CPU can pick the initial interrupt and hence the initial |
| netif_rx_schedule(dev); |
| - The core layer invokes devices to send packets in a round robin format. |
| This implies receive is totaly lockless because of the guarantee only that |
| one CPU is executing it. |
| - contention can only be the result of some other CPU accessing the rx |
| ring. This happens only in close() and suspend() (when these methods |
| try to clean the rx ring); |
| ****guarantee: driver authors need not worry about this; synchronization |
| is taken care for them by the top net layer. |
| -local interrupts are enabled (if you dont move all to dev->poll()). For |
| example link/MII and txcomplete continue functioning just same old way. |
| This improves the latency of processing these events. It is also assumed that |
| the receive interrupt is the largest cause of noise. Note this might not |
| always be true. |
| [according to Manfred Spraul, the winbond insists on sending one |
| txmitcomplete interrupt for each packet (although this can be mitigated)]. |
| For these broken drivers, move all to dev->poll(). |
| |
| For the rest of this text, we'll assume that dev->poll() only |
| processes receive events. |
| |
| new methods introduce by NAPI |
| ============================= |
| |
| a) netif_rx_schedule(dev) |
| Called by an IRQ handler to schedule a poll for device |
| |
| b) netif_rx_schedule_prep(dev) |
| puts the device in a state which allows for it to be added to the |
| CPU polling list if it is up and running. You can look at this as |
| the first half of netif_rx_schedule(dev) above; the second half |
| being c) below. |
| |
| c) __netif_rx_schedule(dev) |
| Add device to the poll list for this CPU; assuming that _prep above |
| has already been called and returned 1. |
| |
| d) netif_rx_reschedule(dev, undo) |
| Called to reschedule polling for device specifically for some |
| deficient hardware. Read Appendix 2 for more details. |
| |
| e) netif_rx_complete(dev) |
| |
| Remove interface from the CPU poll list: it must be in the poll list |
| on current cpu. This primitive is called by dev->poll(), when |
| it completes its work. The device cannot be out of poll list at this |
| call, if it is then clearly it is a BUG(). You'll know ;-> |
| |
| All these above nethods are used below. So keep reading for clarity. |
| |
| Device driver changes to be made when porting NAPI |
| ================================================== |
| |
| Below we describe what kind of changes are required for NAPI to work. |
| |
| 1) introduction of dev->poll() method |
| ===================================== |
| |
| This is the method that is invoked by the network core when it requests |
| for new packets from the driver. A driver is allowed to send upto |
| dev->quota packets by the current CPU before yielding to the network |
| subsystem (so other devices can also get opportunity to send to the stack). |
| |
| dev->poll() prototype looks as follows: |
| int my_poll(struct net_device *dev, int *budget) |
| |
| budget is the remaining number of packets the network subsystem on the |
| current CPU can send up the stack before yielding to other system tasks. |
| *Each driver is responsible for decrementing budget by the total number of |
| packets sent. |
| Total number of packets cannot exceed dev->quota. |
| |
| dev->poll() method is invoked by the top layer, the driver just sends if it |
| can to the stack the packet quantity requested. |
| |
| more on dev->poll() below after the interrupt changes are explained. |
| |
| 2) registering dev->poll() method |
| =================================== |
| |
| dev->poll should be set in the dev->probe() method. |
| e.g: |
| dev->open = my_open; |
| . |
| . |
| /* two new additions */ |
| /* first register my poll method */ |
| dev->poll = my_poll; |
| /* next register my weight/quanta; can be overridden in /proc */ |
| dev->weight = 16; |
| . |
| . |
| dev->stop = my_close; |
| |
| |
| |
| 3) scheduling dev->poll() |
| ============================= |
| This involves modifying the interrupt handler and the code |
| path which takes the packet off the NIC and sends them to the |
| stack. |
| |
| it's important at this point to introduce the classical D Becker |
| interrupt processor: |
| |
| ------------------ |
| static irqreturn_t |
| netdevice_interrupt(int irq, void *dev_id, struct pt_regs *regs) |
| { |
| |
| struct net_device *dev = (struct net_device *)dev_instance; |
| struct my_private *tp = (struct my_private *)dev->priv; |
| |
| int work_count = my_work_count; |
| status = read_interrupt_status_reg(); |
| if (status == 0) |
| return IRQ_NONE; /* Shared IRQ: not us */ |
| if (status == 0xffff) |
| return IRQ_HANDLED; /* Hot unplug */ |
| if (status & error) |
| do_some_error_handling() |
| |
| do { |
| acknowledge_ints_ASAP(); |
| |
| if (status & link_interrupt) { |
| spin_lock(&tp->link_lock); |
| do_some_link_stat_stuff(); |
| spin_lock(&tp->link_lock); |
| } |
| |
| if (status & rx_interrupt) { |
| receive_packets(dev); |
| } |
| |
| if (status & rx_nobufs) { |
| make_rx_buffs_avail(); |
| } |
| |
| if (status & tx_related) { |
| spin_lock(&tp->lock); |
| tx_ring_free(dev); |
| if (tx_died) |
| restart_tx(); |
| spin_unlock(&tp->lock); |
| } |
| |
| status = read_interrupt_status_reg(); |
| |
| } while (!(status & error) || more_work_to_be_done); |
| return IRQ_HANDLED; |
| } |
| |
| ---------------------------------------------------------------------- |
| |
| We now change this to what is shown below to NAPI-enable it: |
| |
| ---------------------------------------------------------------------- |
| static irqreturn_t |
| netdevice_interrupt(int irq, void *dev_id, struct pt_regs *regs) |
| { |
| struct net_device *dev = (struct net_device *)dev_instance; |
| struct my_private *tp = (struct my_private *)dev->priv; |
| |
| status = read_interrupt_status_reg(); |
| if (status == 0) |
| return IRQ_NONE; /* Shared IRQ: not us */ |
| if (status == 0xffff) |
| return IRQ_HANDLED; /* Hot unplug */ |
| if (status & error) |
| do_some_error_handling(); |
| |
| do { |
| /************************ start note *********************************/ |
| acknowledge_ints_ASAP(); // dont ack rx and rxnobuff here |
| /************************ end note *********************************/ |
| |
| if (status & link_interrupt) { |
| spin_lock(&tp->link_lock); |
| do_some_link_stat_stuff(); |
| spin_unlock(&tp->link_lock); |
| } |
| /************************ start note *********************************/ |
| if (status & rx_interrupt || (status & rx_nobuffs)) { |
| if (netif_rx_schedule_prep(dev)) { |
| |
| /* disable interrupts caused |
| * by arriving packets */ |
| disable_rx_and_rxnobuff_ints(); |
| /* tell system we have work to be done. */ |
| __netif_rx_schedule(dev); |
| } else { |
| printk("driver bug! interrupt while in poll\n"); |
| /* FIX by disabling interrupts */ |
| disable_rx_and_rxnobuff_ints(); |
| } |
| } |
| /************************ end note note *********************************/ |
| |
| if (status & tx_related) { |
| spin_lock(&tp->lock); |
| tx_ring_free(dev); |
| |
| if (tx_died) |
| restart_tx(); |
| spin_unlock(&tp->lock); |
| } |
| |
| status = read_interrupt_status_reg(); |
| |
| /************************ start note *********************************/ |
| } while (!(status & error) || more_work_to_be_done(status)); |
| /************************ end note note *********************************/ |
| return IRQ_HANDLED; |
| } |
| |
| --------------------------------------------------------------------- |
| |
| |
| We note several things from above: |
| |
| I) Any interrupt source which is caused by arriving packets is now |
| turned off when it occurs. Depending on the hardware, there could be |
| several reasons that arriving packets would cause interrupts; these are the |
| interrupt sources we wish to avoid. The two common ones are a) a packet |
| arriving (rxint) b) a packet arriving and finding no DMA buffers available |
| (rxnobuff) . |
| This means also acknowledge_ints_ASAP() will not clear the status |
| register for those two items above; clearing is done in the place where |
| proper work is done within NAPI; at the poll() and refill_rx_ring() |
| discussed further below. |
| netif_rx_schedule_prep() returns 1 if device is in running state and |
| gets successfully added to the core poll list. If we get a zero value |
| we can _almost_ assume are already added to the list (instead of not running. |
| Logic based on the fact that you shouldn't get interrupt if not running) |
| We rectify this by disabling rx and rxnobuf interrupts. |
| |
| II) that receive_packets(dev) and make_rx_buffs_avail() may have disappeared. |
| These functionalities are still around actually...... |
| |
| infact, receive_packets(dev) is very close to my_poll() and |
| make_rx_buffs_avail() is invoked from my_poll() |
| |
| 4) converting receive_packets() to dev->poll() |
| =============================================== |
| |
| We need to convert the classical D Becker receive_packets(dev) to my_poll() |
| |
| First the typical receive_packets() below: |
| ------------------------------------------------------------------- |
| |
| /* this is called by interrupt handler */ |
| static void receive_packets (struct net_device *dev) |
| { |
| |
| struct my_private *tp = (struct my_private *)dev->priv; |
| rx_ring = tp->rx_ring; |
| cur_rx = tp->cur_rx; |
| int entry = cur_rx % RX_RING_SIZE; |
| int received = 0; |
| int rx_work_limit = tp->dirty_rx + RX_RING_SIZE - tp->cur_rx; |
| |
| while (rx_ring_not_empty) { |
| u32 rx_status; |
| unsigned int rx_size; |
| unsigned int pkt_size; |
| struct sk_buff *skb; |
| /* read size+status of next frame from DMA ring buffer */ |
| /* the number 16 and 4 are just examples */ |
| rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset)); |
| rx_size = rx_status >> 16; |
| pkt_size = rx_size - 4; |
| |
| /* process errors */ |
| if ((rx_size > (MAX_ETH_FRAME_SIZE+4)) || |
| (!(rx_status & RxStatusOK))) { |
| netdrv_rx_err (rx_status, dev, tp, ioaddr); |
| return; |
| } |
| |
| if (--rx_work_limit < 0) |
| break; |
| |
| /* grab a skb */ |
| skb = dev_alloc_skb (pkt_size + 2); |
| if (skb) { |
| . |
| . |
| netif_rx (skb); |
| . |
| . |
| } else { /* OOM */ |
| /*seems very driver specific ... some just pass |
| whatever is on the ring already. */ |
| } |
| |
| /* move to the next skb on the ring */ |
| entry = (++tp->cur_rx) % RX_RING_SIZE; |
| received++ ; |
| |
| } |
| |
| /* store current ring pointer state */ |
| tp->cur_rx = cur_rx; |
| |
| /* Refill the Rx ring buffers if they are needed */ |
| refill_rx_ring(); |
| . |
| . |
| |
| } |
| ------------------------------------------------------------------- |
| We change it to a new one below; note the additional parameter in |
| the call. |
| |
| ------------------------------------------------------------------- |
| |
| /* this is called by the network core */ |
| static int my_poll (struct net_device *dev, int *budget) |
| { |
| |
| struct my_private *tp = (struct my_private *)dev->priv; |
| rx_ring = tp->rx_ring; |
| cur_rx = tp->cur_rx; |
| int entry = cur_rx % RX_BUF_LEN; |
| /* maximum packets to send to the stack */ |
| /************************ note note *********************************/ |
| int rx_work_limit = dev->quota; |
| |
| /************************ end note note *********************************/ |
| do { // outer beginning loop starts here |
| |
| clear_rx_status_register_bit(); |
| |
| while (rx_ring_not_empty) { |
| u32 rx_status; |
| unsigned int rx_size; |
| unsigned int pkt_size; |
| struct sk_buff *skb; |
| /* read size+status of next frame from DMA ring buffer */ |
| /* the number 16 and 4 are just examples */ |
| rx_status = le32_to_cpu (*(u32 *) (rx_ring + ring_offset)); |
| rx_size = rx_status >> 16; |
| pkt_size = rx_size - 4; |
| |
| /* process errors */ |
| if ((rx_size > (MAX_ETH_FRAME_SIZE+4)) || |
| (!(rx_status & RxStatusOK))) { |
| netdrv_rx_err (rx_status, dev, tp, ioaddr); |
| return 1; |
| } |
| |
| /************************ note note *********************************/ |
| if (--rx_work_limit < 0) { /* we got packets, but no quota */ |
| /* store current ring pointer state */ |
| tp->cur_rx = cur_rx; |
| |
| /* Refill the Rx ring buffers if they are needed */ |
| refill_rx_ring(dev); |
| goto not_done; |
| } |
| /********************** end note **********************************/ |
| |
| /* grab a skb */ |
| skb = dev_alloc_skb (pkt_size + 2); |
| if (skb) { |
| . |
| . |
| /************************ note note *********************************/ |
| netif_receive_skb (skb); |
| /********************** end note **********************************/ |
| . |
| . |
| } else { /* OOM */ |
| /*seems very driver specific ... common is just pass |
| whatever is on the ring already. */ |
| } |
| |
| /* move to the next skb on the ring */ |
| entry = (++tp->cur_rx) % RX_RING_SIZE; |
| received++ ; |
| |
| } |
| |
| /* store current ring pointer state */ |
| tp->cur_rx = cur_rx; |
| |
| /* Refill the Rx ring buffers if they are needed */ |
| refill_rx_ring(dev); |
| |
| /* no packets on ring; but new ones can arrive since we last |
| checked */ |
| status = read_interrupt_status_reg(); |
| if (rx status is not set) { |
| /* If something arrives in this narrow window, |
| an interrupt will be generated */ |
| goto done; |
| } |
| /* done! at least thats what it looks like ;-> |
| if new packets came in after our last check on status bits |
| they'll be caught by the while check and we go back and clear them |
| since we havent exceeded our quota */ |
| } while (rx_status_is_set); |
| |
| done: |
| |
| /************************ note note *********************************/ |
| dev->quota -= received; |
| *budget -= received; |
| |
| /* If RX ring is not full we are out of memory. */ |
| if (tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL) |
| goto oom; |
| |
| /* we are happy/done, no more packets on ring; put us back |
| to where we can start processing interrupts again */ |
| netif_rx_complete(dev); |
| enable_rx_and_rxnobuf_ints(); |
| |
| /* The last op happens after poll completion. Which means the following: |
| * 1. it can race with disabling irqs in irq handler (which are done to |
| * schedule polls) |
| * 2. it can race with dis/enabling irqs in other poll threads |
| * 3. if an irq raised after the begining of the outer beginning |
| * loop(marked in the code above), it will be immediately |
| * triggered here. |
| * |
| * Summarizing: the logic may results in some redundant irqs both |
| * due to races in masking and due to too late acking of already |
| * processed irqs. The good news: no events are ever lost. |
| */ |
| |
| return 0; /* done */ |
| |
| not_done: |
| if (tp->cur_rx - tp->dirty_rx > RX_RING_SIZE/2 || |
| tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL) |
| refill_rx_ring(dev); |
| |
| if (!received) { |
| printk("received==0\n"); |
| received = 1; |
| } |
| dev->quota -= received; |
| *budget -= received; |
| return 1; /* not_done */ |
| |
| oom: |
| /* Start timer, stop polling, but do not enable rx interrupts. */ |
| start_poll_timer(dev); |
| return 0; /* we'll take it from here so tell core "done"*/ |
| |
| /************************ End note note *********************************/ |
| } |
| ------------------------------------------------------------------- |
| |
| From above we note that: |
| 0) rx_work_limit = dev->quota |
| 1) refill_rx_ring() is in charge of clearing the bit for rxnobuff when |
| it does the work. |
| 2) We have a done and not_done state. |
| 3) instead of netif_rx() we call netif_receive_skb() to pass the skb. |
| 4) we have a new way of handling oom condition |
| 5) A new outer for (;;) loop has been added. This serves the purpose of |
| ensuring that if a new packet has come in, after we are all set and done, |
| and we have not exceeded our quota that we continue sending packets up. |
| |
| |
| ----------------------------------------------------------- |
| Poll timer code will need to do the following: |
| |
| a) |
| |
| if (tp->cur_rx - tp->dirty_rx > RX_RING_SIZE/2 || |
| tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL) |
| refill_rx_ring(dev); |
| |
| /* If RX ring is not full we are still out of memory. |
| Restart the timer again. Else we re-add ourselves |
| to the master poll list. |
| */ |
| |
| if (tp->rx_buffers[tp->dirty_rx % RX_RING_SIZE].skb == NULL) |
| restart_timer(); |
| |
| else netif_rx_schedule(dev); /* we are back on the poll list */ |
| |
| 5) dev->close() and dev->suspend() issues |
| ========================================== |
| The driver writter neednt worry about this. The top net layer takes |
| care of it. |
| |
| 6) Adding new Stats to /proc |
| ============================= |
| In order to debug some of the new features, we introduce new stats |
| that need to be collected. |
| TODO: Fill this later. |
| |
| APPENDIX 1: discussion on using ethernet HW FC |
| ============================================== |
| Most chips with FC only send a pause packet when they run out of Rx buffers. |
| Since packets are pulled off the DMA ring by a softirq in NAPI, |
| if the system is slow in grabbing them and we have a high input |
| rate (faster than the system's capacity to remove packets), then theoretically |
| there will only be one rx interrupt for all packets during a given packetstorm. |
| Under low load, we might have a single interrupt per packet. |
| FC should be programmed to apply in the case when the system cant pull out |
| packets fast enough i.e send a pause only when you run out of rx buffers. |
| Note FC in itself is a good solution but we have found it to not be |
| much of a commodity feature (both in NICs and switches) and hence falls |
| under the same category as using NIC based mitigation. Also experiments |
| indicate that its much harder to resolve the resource allocation |
| issue (aka lazy receiving that NAPI offers) and hence quantify its usefullness |
| proved harder. In any case, FC works even better with NAPI but is not |
| necessary. |
| |
| |
| APPENDIX 2: the "rotting packet" race-window avoidance scheme |
| ============================================================= |
| |
| There are two types of associations seen here |
| |
| 1) status/int which honors level triggered IRQ |
| |
| If a status bit for receive or rxnobuff is set and the corresponding |
| interrupt-enable bit is not on, then no interrupts will be generated. However, |
| as soon as the "interrupt-enable" bit is unmasked, an immediate interrupt is |
| generated. [assuming the status bit was not turned off]. |
| Generally the concept of level triggered IRQs in association with a status and |
| interrupt-enable CSR register set is used to avoid the race. |
| |
| If we take the example of the tulip: |
| "pending work" is indicated by the status bit(CSR5 in tulip). |
| the corresponding interrupt bit (CSR7 in tulip) might be turned off (but |
| the CSR5 will continue to be turned on with new packet arrivals even if |
| we clear it the first time) |
| Very important is the fact that if we turn on the interrupt bit on when |
| status is set that an immediate irq is triggered. |
| |
| If we cleared the rx ring and proclaimed there was "no more work |
| to be done" and then went on to do a few other things; then when we enable |
| interrupts, there is a possibility that a new packet might sneak in during |
| this phase. It helps to look at the pseudo code for the tulip poll |
| routine: |
| |
| -------------------------- |
| do { |
| ACK; |
| while (ring_is_not_empty()) { |
| work-work-work |
| if quota is exceeded: exit, no touching irq status/mask |
| } |
| /* No packets, but new can arrive while we are doing this*/ |
| CSR5 := read |
| if (CSR5 is not set) { |
| /* If something arrives in this narrow window here, |
| * where the comments are ;-> irq will be generated */ |
| unmask irqs; |
| exit poll; |
| } |
| } while (rx_status_is_set); |
| ------------------------ |
| |
| CSR5 bit of interest is only the rx status. |
| If you look at the last if statement: |
| you just finished grabbing all the packets from the rx ring .. you check if |
| status bit says theres more packets just in ... it says none; you then |
| enable rx interrupts again; if a new packet just came in during this check, |
| we are counting that CSR5 will be set in that small window of opportunity |
| and that by re-enabling interrupts, we would actually triger an interrupt |
| to register the new packet for processing. |
| |
| [The above description nay be very verbose, if you have better wording |
| that will make this more understandable, please suggest it.] |
| |
| 2) non-capable hardware |
| |
| These do not generally respect level triggered IRQs. Normally, |
| irqs may be lost while being masked and the only way to leave poll is to do |
| a double check for new input after netif_rx_complete() is invoked |
| and re-enable polling (after seeing this new input). |
| |
| Sample code: |
| |
| --------- |
| . |
| . |
| restart_poll: |
| while (ring_is_not_empty()) { |
| work-work-work |
| if quota is exceeded: exit, not touching irq status/mask |
| } |
| . |
| . |
| . |
| enable_rx_interrupts() |
| netif_rx_complete(dev); |
| if (ring_has_new_packet() && netif_rx_reschedule(dev, received)) { |
| disable_rx_and_rxnobufs() |
| goto restart_poll |
| } while (rx_status_is_set); |
| --------- |
| |
| Basically netif_rx_complete() removes us from the poll list, but because a |
| new packet which will never be caught due to the possibility of a race |
| might come in, we attempt to re-add ourselves to the poll list. |
| |
| |
| |
| |
| APPENDIX 3: Scheduling issues. |
| ============================== |
| As seen NAPI moves processing to softirq level. Linux uses the ksoftirqd as the |
| general solution to schedule softirq's to run before next interrupt and by putting |
| them under scheduler control. Also this prevents consecutive softirq's from |
| monopolize the CPU. This also have the effect that the priority of ksoftirq needs |
| to be considered when running very CPU-intensive applications and networking to |
| get the proper balance of softirq/user balance. Increasing ksoftirq priority to 0 |
| (eventually more) is reported cure problems with low network performance at high |
| CPU load. |
| |
| Most used processes in a GIGE router: |
| USER PID %CPU %MEM SIZE RSS TTY STAT START TIME COMMAND |
| root 3 0.2 0.0 0 0 ? RWN Aug 15 602:00 (ksoftirqd_CPU0) |
| root 232 0.0 7.9 41400 40884 ? S Aug 15 74:12 gated |
| |
| -------------------------------------------------------------------- |
| |
| relevant sites: |
| ================== |
| ftp://robur.slu.se/pub/Linux/net-development/NAPI/ |
| |
| |
| -------------------------------------------------------------------- |
| TODO: Write net-skeleton.c driver. |
| ------------------------------------------------------------- |
| |
| Authors: |
| ======== |
| Alexey Kuznetsov <kuznet@ms2.inr.ac.ru> |
| Jamal Hadi Salim <hadi@cyberus.ca> |
| Robert Olsson <Robert.Olsson@data.slu.se> |
| |
| Acknowledgements: |
| ================ |
| People who made this document better: |
| |
| Lennert Buytenhek <buytenh@gnu.org> |
| Andrew Morton <akpm@zip.com.au> |
| Manfred Spraul <manfred@colorfullife.com> |
| Donald Becker <becker@scyld.com> |
| Jeff Garzik <jgarzik@pobox.com> |