有时候需要监控硬件是否工作正常,如果已经有寄存器可以确认工作不正常,那这个时候可以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;
}
要使用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;
}