由nvme想到的通过周期性timer检查硬件controller的状态

来源:互联网 发布:东北财经大学网络教学 编辑:程序博客网 时间:2024/05/21 06:25
有时候需要监控硬件是否工作正常,如果已经有寄存器可以确认工作不正常,那这个时候可以reset 这个hw。例如nvme需要周期性的检测nvme controller是否工作正常,就是用这种方法做的的,详细code如下:
要使用timer周期性的检测硬件状态,首先在自身的结构图中加上struct timer_list。
例如:
struct nvme_dev {
    struct nvme_queue **queues;
    struct blk_mq_tag_set tagset;
    struct blk_mq_tag_set admin_tagset;
    u32 __iomem *dbs;
    struct device *dev;
    struct dma_pool *prp_page_pool;
    struct dma_pool *prp_small_pool;
    unsigned queue_count;
    unsigned online_queues;
    unsigned max_qid;
    int q_depth;
    u32 db_stride;
    void __iomem *bar;
    struct work_struct reset_work;
    struct work_struct remove_work;
    struct timer_list watchdog_timer;
    struct mutex shutdown_lock;
    bool subsystem;
    void __iomem *cmb;
    dma_addr_t cmb_dma_addr;
    u64 cmb_size;
    u32 cmbsz;
    u32 cmbloc;
    struct nvme_ctrl ctrl;
    struct completion ioq_wait;
};

其次在nvme_probe中调用setup_timer 来设置callback函数
setup_timer(&dev->watchdog_timer, nvme_watchdog_timer,(unsigned long)dev);
其次调用mod_timer 这个timer就可以开始工作了,这里表示每1HZ 检查一次
mod_timer(&dev->watchdog_timer, round_jiffies(jiffies + HZ));
然后每隔1s就会调用nvme_watchdog_timer
static void nvme_watchdog_timer(unsigned long data)
{
    struct nvme_dev *dev = (struct nvme_dev *)data;
    u32 csts = readl(dev->bar + NVME_REG_CSTS);

    /* Skip controllers under certain specific conditions. */
    if (nvme_should_reset(dev, csts)) {
        if (!nvme_reset(dev))
            dev_warn(dev->dev,
                "Failed status: 0x%x, reset controller.\n",
                csts);
        return;
    }

    mod_timer(&dev->watchdog_timer, round_jiffies(jiffies + HZ));
}
在nvme_watchdog_timer 将data强转成nvme_dev,这个data是在调用setup_timer 时候设置的第三个参数。如果通过nvme_should_reset 检测nvme controller 需要reset的话,就调用nvme_reset来reset。如果不需要重启的话,就继续调用mod_timer来修改timer来做下一次的检测.
如果nvme调用nvme_dev_disable 来关掉nvme controller的时候,需要调用del_timer_sync来删掉这个时间
    del_timer_sync(&dev->watchdog_timer);

从round_jiffies的实现可以看到这个函数是绑定到当前的cpu的。
unsigned long round_jiffies(unsigned long j)
{
    return round_jiffies_common(j, raw_smp_processor_id(), false);
}
而round_jiffies_common中的第三个参数false是说1.3s的话,就用1s。例如
static unsigned long round_jiffies_common(unsigned long j, int cpu,
        bool force_up)
{
    int rem;
    unsigned long original = j;

    /*
     * We don't want all cpus firing their timers at once hitting the
     * same lock or cachelines, so we skew each extra cpu with an extra
     * 3 jiffies. This 3 jiffies came originally from the mm/ code which
     * already did this.
     * The skew is done by adding 3*cpunr, then round, then subtract this
     * extra offset again.
     */
    j += cpu * 3;

    rem = j % HZ;

    /*
     * If the target jiffie is just after a whole second (which can happen
     * due to delays of the timer irq, long irq off times etc etc) then
     * we should round down to the whole second, not up. Use 1/4th second
     * as cutoff for this rounding as an extreme upper bound for this.
     * But never round down if @force_up is set.
     */
    if (rem < HZ/4 && !force_up) /* round down */
        j = j - rem;
    else /* round up */
        j = j - rem + HZ;

    /* now that we have rounded, subtract the extra skew again */
    j -= cpu * 3;

    /*
     * Make sure j is still in the future. Otherwise return the
     * unmodified value.
     */
    return time_is_after_jiffies(j) ? j : original;
}




原创粉丝点击