netdev: dev_watchdog timer(結合stmmac 分析)

分析netdev看門狗定時器

 

1. dev_watchdog()作爲定時器回調函數會被週期執行

在dev_watchdog()中,如果 if (netif_xmit_stopped(txq) && time_after(jiffies, (trans_start + dev->watchdog_timeo))) 成立,執行some_queue_timedout = 1, 然後便調用ndo_tx_timeout。

ndo_tx_timeout函數便是網卡發送異常(數據發不出去等)時的超時處理函數。它會調用 stmmac_tx_timeout重新啓動transmission。

if (some_queue_timedout) {
    WARN_ONCE(1, KERN_INFO "NETDEV WATCHDOG: %s (%s): transmit queue %u timed out\n",
           dev->name, netdev_drivername(dev), i);
    dev->netdev_ops->ndo_tx_timeout(dev);
}

 

2. dev_watchdog()執行ndo_tx_timeout的條件

if (netif_xmit_stopped(txq) && time_after(jiffies, (trans_start + dev->watchdog_timeo)))

 

2.1 time_after(jiffies, (trans_start + dev->watchdog_timeo))

即queue發送超時

1)dev->watchdog_timeo爲定時週期

2)在 __netdev_start_xmit中,如果 ndo_start_xmit返回 NETDEV_TX_OK(這並不代表數據已經通過DMA發送出去),便調用 txq_trans_update更新txq->trans_start。在dev_watchdog中,會將txq->trans_start賦值給trans_start。

 

2.2 netif_xmit_stopped(txq)

queue已經停止發送

static inline bool netif_xmit_stopped(const struct netdev_queue *dev_queue)
{
    return dev_queue->state & QUEUE_STATE_ANY_XOFF;
}

#define QUEUE_STATE_ANY_XOFF    (QUEUE_STATE_DRV_XOFF | QUEUE_STATE_STACK_XOFF)
#define QUEUE_STATE_DRV_XOFF    (1 << __QUEUE_STATE_DRV_XOFF)
#define QUEUE_STATE_STACK_XOFF    (1 << __QUEUE_STATE_STACK_XOFF)

1)__QUEUE_STATE_STACK_XOFF

清除__QUEUE_STATE_STACK_XOFF:

如果DMA發送完成,在中斷服務程序 stmmac_dma_interrupt() ->  stmmac_poll() ->  stmmac_tx_clean() ->  netdev_tx_completed_queue() ->  test_and_clear_bit(__QUEUE_STATE_STACK_XOFF, &dev_queue->state)  清除

 

設置__QUEUE_STATE_STACK_XOFF:

發送時,在stmmac_xmit() -> netdev_tx_sent_queue() -> set_bit(__QUEUE_STATE_STACK_XOFF, &dev_queue->state) 設置

 

所以如果dma中斷沒有被觸發,或是觸發了但是發送沒有完成,則netif_xmit_stopped()返回1

static inline void netdev_tx_sent_queue(struct netdev_queue *dev_queue,
                    unsigned int bytes)
{
#ifdef CONFIG_BQL
    dql_queued(&dev_queue->dql, bytes);
    if (likely(dql_avail(&dev_queue->dql) >= 0))
        return;
    set_bit(__QUEUE_STATE_STACK_XOFF, &dev_queue->state);
    /* check again in case another CPU has just made room avail */
    if (unlikely(dql_avail(&dev_queue->dql) >= 0))
        clear_bit(__QUEUE_STATE_STACK_XOFF, &dev_queue->state);
#endif
}


static inline void netdev_tx_reset_queue(struct netdev_queue *q)
{
#ifdef CONFIG_BQL
    clear_bit(__QUEUE_STATE_STACK_XOFF, &q->state);
    dql_reset(&q->dql);
#endif
}


static inline void netdev_tx_completed_queue(struct netdev_queue *dev_queue,
                         unsigned int pkts, unsigned int bytes)
{
#ifdef CONFIG_BQL
    if (unlikely(!bytes))
        return;


    dql_completed(&dev_queue->dql, bytes);

    if (dql_avail(&dev_queue->dql) < 0)
        return;

    if (test_and_clear_bit(__QUEUE_STATE_STACK_XOFF, &dev_queue->state))
        netif_schedule_queue(dev_queue);
#endif
}

2) __QUEUE_STATE_DRV_XOFF

主要針對網卡queue,當queue滿或不足以進行下一個傳輸時,會調用netif_tx_stop_queue通知上層停止發送。當queue恢復時,調用netif_tx_wake_queue通知上層重新開始傳輸。

 

清除__QUEUE_STATE_DRV_XOFF:

stmmac_open -> stmmac_start_all_queues -> netif_tx_start_queue -> clear_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state)

stmmac_dma_interrupt -> stmmac_poll -> stmmac_tx_clean -> netif_tx_wake_queue

 

設置__QUEUE_STATE_DRV_XOFF:

static __always_inline void netif_tx_start_queue(struct netdev_queue *dev_queue)
{
    clear_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);
}


void netif_tx_wake_queue(struct netdev_queue *dev_queue)
{
    if (test_and_clear_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state)) {
        struct Qdisc *q;
        rcu_read_lock();
        q = rcu_dereference(dev_queue->qdisc);
        __netif_schedule(q);
        rcu_read_unlock();
    }
}


static __always_inline void netif_tx_stop_queue(struct netdev_queue *dev_queue)
{
    set_bit(__QUEUE_STATE_DRV_XOFF, &dev_queue->state);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章