/**
* struct irq_desc - interrupt descriptor
* @irq_data: per irq and chip data passed down to chip functions
* @kstat_irqs: irq stats per cpu
* @handle_irq: highlevel irq-events handler
* @preflow_handler: handler called before the flow handler (currently used by sparc)
* @action: the irq action chain
* @status: status information
* @core_internal_state__do_not_mess_with_it: core internal status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple irq_set_irq_wake() callers
* @irq_count: stats field to detect stalled irqs
* @last_unhandled: aging timer for unhandled count
* @irqs_unhandled: stats field for spurious unhandled interrupts
* @lock: locking for SMP
* @affinity_hint: hint to user space for preferred irq affinity
* @affinity_notify: context for notification of affinity changes
* @pending_mask: pending rebalanced interrupts
* @threads_oneshot: bitfield to handle shared oneshot threads
* @threads_active: number of irqaction threads currently running
* @wait_for_threads: wait queue for sync_irq to wait for threaded handlers
* @dir: /proc/irq/ procfs entry
* @name: flow handler name for /proc/interrupts output
*/
struct irq_desc {
struct irq_data irq_data;
unsigned int __percpu *kstat_irqs;///* irq的统计信息,在proc中可查到 */
irq_flow_handler_t handle_irq;
/* 回调函数,当此中断产生中断时,会调用handle_irq,在handle_irq中进行遍历irqaction链表*/
/* handle_simple_irq 用于简单处理;
* handle_level_irq 用于电平触发中断的流控处理;
* handle_edge_irq 用于边沿触发中断的流控处理;
* handle_fasteoi_irq 用于需要响应eoi的中断控制器;
* handle_percpu_irq 用于只在单一cpu响应的中断;
* handle_nested_irq 用于处理使用线程的嵌套中断;
*/
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
irq_preflow_handler_t preflow_handler;
#endif
struct irqaction *action; /* IRQ action list */ unsigned int status_use_accessors;
unsigned int core_internal_state__do_not_mess_with_it;
unsigned int depth; /* nested irq disables */ /* 嵌套深度,中断线被激活显示0,如果为正数,表示被禁止次数 */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs *//* 此中断线上发生的中断次数 */
unsigned long last_unhandled; /* Aging timer for unhandled count */ /* 上次发生未处理中断时的jiffies值 */
unsigned int irqs_unhandled;/* 中断线上无法处理的中断次数,如果当第100000次中断发生时,有超过99900次是意外中断,系统会禁止这条中断线 */
raw_spinlock_t lock;
struct cpumask *percpu_enabled;
#ifdef CONFIG_SMP
const struct cpumask *affinity_hint; /* CPU亲和力关系,其实就是每个CPU是占一个bit长度,某CPU上置为1表明该CPU可以进行这个中断的处理 */
struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
cpumask_var_t pending_mask; /* 用于调整irq在各个cpu之间的平衡 */
#endif
#endif
unsigned long threads_oneshot;
atomic_t threads_active;
wait_queue_head_t wait_for_threads; /* 用于synchronize_irq(),等待该irq所有线程完成 */
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir; /* 指向与IRQn相关的/proc/irq/n目录的描述符 */
#endif
int parent_irq;
struct module *owner;
const char *name;/* 在/proc/interrupts所显示名称 */
} ____cacheline_internodealigned_in_smp;

中断的描述符如上所述。作为背景知识,可以理解下面的内容,本文讨论基于的内核版本信息如下:

uname -a
Linux localhost.localdomain 3.10.

我们知道,nvme的多队列,默认按照核数的多少来设置,目前nvme的队列有两种,admin队列,IO队列,两者都属于nvme_queue对象,submit queue,complete queue是一个nvme_queue对象的一个成员,其中submit queue在代码中会简写为sq,complete queue会简写成cq。两者是Queue Pair(QP),也就是submitqueue·completequeue和admin queue不是同一个级别的对象,对于admin队列来说,它也有自己的submitquque和completequeue,第一次看代码时往往容易混淆。

首先,我们来看一下nvme总共用的中断数。

# cat /proc/interrupts |grep nvme |wc -l
320

该系统上一共4块盘,80个核,就有320个中断,一个核对应一个队列,一个中断号。按道理IOqueue有80个,adminqueue也需要用中断,

那么中断数应该是81*4=324才对。

# cat /proc/interrupts |grep -i nvme[-]q0|awk '{print $1,$(NF-1),$NF}'
: nvme2q0, nvme2q1
: nvme3q0, nvme3q1
: nvme0q0, nvme0q1
: nvme1q0, nvme1q1

我们发现,nvme0q0 和 nvme0q1 是共享中断的。而其他的sq都是虽然带的参数也是共享,但是从实际情况看,是独占的。所以数量是320个。

nvme0q0 就是我们可爱的admin queue,从申请的角度看,我们可以看出来,一开始adminqueue申请,用的是裸命令,后面的ioqueues申请,利用的是admin queue的队列。

由于admin的queue是最先申请的,所以包括中断号也是单独申请的,nvme_configure_admin_queue 中,调用静态函数 queue_request_irq来初始化admin的队列的中断,而且它传入的参数是共享的,也就是不需要独占中断,IRQF_SHARED。admin的队列编号是0。

static int nvme_configure_admin_queue(struct nvme_dev *dev)
{
int result;
u32 aqa;
u64 cap = lo_hi_readq(dev->bar + NVME_REG_CAP);
struct nvme_queue *nvmeq; dev->subsystem = readl(dev->bar + NVME_REG_VS) >= NVME_VS(, , ) ?
NVME_CAP_NSSRC(cap) : ; if (dev->subsystem &&
(readl(dev->bar + NVME_REG_CSTS) & NVME_CSTS_NSSRO))
writel(NVME_CSTS_NSSRO, dev->bar + NVME_REG_CSTS); result = nvme_disable_ctrl(&dev->ctrl, cap);
if (result < )
return result; nvmeq = dev->queues[];//admin是第一个queue,队列编号肯定是0
if (!nvmeq) {//admin的queue,深度为256
nvmeq = nvme_alloc_queue(dev, , NVME_AQ_DEPTH);//
if (!nvmeq)
return -ENOMEM;
} aqa = nvmeq->q_depth - ;
aqa |= aqa << ;
//将sq_dma_addr 和 cq_dma_addr 分别系到bar 空间偏移为NVME_REG_ASQ和NVME_REG_ACQ,这个地址都是在nvme_alloc_queue 中申请的.
writel(aqa, dev->bar + NVME_REG_AQA);
lo_hi_writeq(nvmeq->sq_dma_addr, dev->bar + NVME_REG_ASQ);
lo_hi_writeq(nvmeq->cq_dma_addr, dev->bar + NVME_REG_ACQ); result = nvme_enable_ctrl(&dev->ctrl, cap);
if (result)
return result; nvmeq->cq_vector = ;
result = queue_request_irq(dev, nvmeq, nvmeq->irqname);//为admin队列申请中断,这个是nvme驱动最先申请的中断号
if (result) {
nvmeq->cq_vector = -;
return result;
} return result;
}

而ioquue,都是调用的nvme_pci_enable来完成中断的申请,

中断注册,也是在队列创建的时候完成,nvme_create_queue ,其中需要注意的是,admin的中断,会先注册,然后再取消注册,然后再注册一次。先注册的目的是为了借助这个中断来返回处理创建sq和cq等命令的结果。

nvme_create_io_queues---|nvme_alloc_queue----分配nvmeq结构体,并记录到dev->queues[]数组中,并分配submit queue 和complete queue命令所需要的空间。

---|nvme_create_queue---|adapter_alloc_cq----构建cmd,利用admin 的queue发送控制消息,分配sq相关信息

---|adapter_alloc_sq----这个是分配submitqueue队列的相关信息,与cq类似。

---|queue_request_irq---这个是申请中断

---|nvme_init_queue---初始化队列

下面,重点了解下queue_request_irq 的传入参数:

static int queue_request_irq(struct nvme_dev *dev, struct nvme_queue *nvmeq,
const char *name)
{
if (use_threaded_interrupts)//中断线程化使能,默认没有开启
return request_threaded_irq(dev->entry[nvmeq->cq_vector].vector,
nvme_irq_check, nvme_irq, IRQF_SHARED,
name, nvmeq);
return request_irq(dev->entry[nvmeq->cq_vector].vector, nvme_irq,
IRQF_SHARED, name, nvmeq);
} static int use_threaded_interrupts;默认就是0了。

也就是nvme驱动默认没有使能中断线程化功能。request_irq 是中断的申请接口了,定义在interrupt.h中,调用request_threaded_irq,其中第三个传入传入的是NULL

static inline int __must_check
request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags,
const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

request_threaded_irq定义在manager.c中,后面就是中断的通用流程了,我们主要针对传入的参数分析一下:

int request_threaded_irq(unsigned int irq, irq_handler_t handler,
irq_handler_t thread_fn, unsigned long irqflags,
const char *devname, void *dev_id)
{.....
......
action->handler = handler;---------就是我们的nvme_irq
    action->thread_fn = thread_fn;-----nvme中传入的是NULL
action->flags = irqflags;
action->name = devname;-------------nvmeq->irqname,中断名称,就是/proc/interrupts每行的最后那列,当中断共享的时候,会显示注册的多个名称。
action->dev_id = dev_id;------------nvmeq,作为对象,会在thread_fn 作为最后一个参数传回来
..... 
.....
}
static irqreturn_t nvme_irq(int irq, void *data)------处理nvme中断
{
irqreturn_t result;
struct nvme_queue *nvmeq = data;--------回调nvme_irq的时候,传入的data就是之前注册的时候传入的dev_id
spin_lock(&nvmeq->q_lock);-------------由于默认每个队列是在一个cpu上,所以这里自旋锁的消耗很少,是一种无锁设计的保护而已。
nvme_process_cq(nvmeq);
result = nvmeq->cqe_seen ? IRQ_HANDLED : IRQ_NONE;
nvmeq->cqe_seen = ;
spin_unlock(&nvmeq->q_lock);
return result;
}

去掉包裹函数,真正干活的就是nvme_process_cq 了,又看到了熟悉的head,tail标志,这个机制的描述在网上已经烂大街了,借用一下:

Head/Tail机制

Submission Queue使用Tail,Completion Queue使用Head,两者均由Host操作。处理完一个Command,Tail或Head加1,当大于Queue Depth时,则回到0。通过对比Head和Tail的值,就知道一个Queue中有多少未处理的Submission Command。下面的图摘自NVMe Spec,有兴趣的同学可以据此琢磨下Empty Queue和Full Queue的定义。

static int nvme_process_cq(struct nvme_queue *nvmeq)
{
u16 head, phase; head = nvmeq->cq_head;
phase = nvmeq->cq_phase; while (nvme_cqe_valid(nvmeq, head, phase)) {
struct nvme_completion cqe = nvmeq->cqes[head];
struct request *req; if (++head == nvmeq->q_depth) {
head = ;
phase = !phase;
} if (unlikely(cqe.command_id >= nvmeq->q_depth)) {
dev_warn(nvmeq->dev->ctrl.device,
"invalid id %d completed on queue %d\n",
cqe.command_id, le16_to_cpu(cqe.sq_id));
continue;
} /*
* AEN requests are special as they don't time out and can
* survive any kind of queue freeze and often don't respond to
* aborts. We don't even bother to allocate a struct request
* for them but rather special case them here.
*/
if (unlikely(nvmeq->qid == &&
cqe.command_id >= NVME_AQ_BLKMQ_DEPTH)) {
nvme_complete_async_event(&nvmeq->dev->ctrl,
cqe.status, &cqe.result);
continue;
} req = blk_mq_tag_to_rq(*nvmeq->tags, cqe.command_id);
nvme_req(req)->result = cqe.result;
blk_mq_complete_request(req, le16_to_cpu(cqe.status) >> );
} if (head == nvmeq->cq_head && phase == nvmeq->cq_phase)
return ; if (likely(nvmeq->cq_vector >= ))
writel(head, nvmeq->q_db + nvmeq->dev->db_stride);
nvmeq->cq_head = head;
nvmeq->cq_phase = phase; nvmeq->cqe_seen = ;
return ;
}

linux 中nvme 的中断申请及处理的更多相关文章

  1. 如何理解 Linux 中的 load averages

    原文:https://mp.weixin.qq.com/s?src=11&timestamp=1533697106&ver=1047&signature=poqrJFfcNAB ...

  2. 向linux内核中添加外部中断驱动模块

    本文主要介绍外部中断驱动模块的编写,包括:1.linux模块的框架及混杂设备的注册.卸载.操作函数集.2.中断的申请及释放.3.等待队列的使用.4.工作队列的使用.5.定时器的使用.6.向linux内 ...

  3. linux中断申请之request_threaded_irq

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=21977330&id=3755609 在linux里,中断处理分 ...

  4. linux中断申请之request_threaded_irq【转】

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=21977330&id=3755609 在linux里,中断处理分 ...

  5. linux中断申请之request_threaded_irq 【转】

    转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=21977330&id=3755609 在linux里,中断处理分 ...

  6. Linux就这个范儿 第15章 七种武器 linux 同步IO: sync、fsync与fdatasync Linux中的内存大页面huge page/large page David Cutler Linux读写内存数据的三种方式

    Linux就这个范儿 第15章 七种武器  linux 同步IO: sync.fsync与fdatasync   Linux中的内存大页面huge page/large page  David Cut ...

  7. Linux内核:关于中断你须要知道的

    1.中断处理程序与其它内核函数真正的差别在于,中断处理程序是被内核调用来对应中断的,而它们执行于中断上下文(原子上下文)中,在该上下文中执行的代码不可堵塞. 中断就是由硬件打断操作系统. 2.异常与中 ...

  8. 基於tiny4412的Linux內核移植 --- 实例学习中断背后的知识(1)

    作者:彭东林 邮箱:pengdonglin137@163.com QQ:405728433 平台 tiny4412 ADK Linux-4.9 概述 前面几篇博文列举了在有设备树的时候,gpio中断的 ...

  9. 蜕变成蝶~Linux设备驱动之中断与定时器

    “我叮咛你的 你说 不会遗忘 你告诉我的 我也全部珍藏 对于我们来说 记忆是飘不落的日子 永远不会发黄 相聚的时候 总是很短 期待的时候 总是很长 岁月的溪水边 捡拾起多少闪亮的诗行 如果你要想念我  ...

随机推荐

  1. 让git不再跟踪配置文件的变化

    我们经常会在配置文件里留下一些敏感信息 比如数据库链接字符串的用户名和密码 如果不提交配置文件到github或者其他源码管理网站 那么你的粉丝很可能就无法正确运行你的项目,就达不到开源的目的了 那么, ...

  2. 关于t00ls的挂机脚本

    0x00 前言 今天早上发现t00ls上有人发了个挂机脚本,二十四小时刷时间以及刷Tubi. 轻轻松松升级,坐收Tubi成富翁. 代码很简单,就是带上cookie每隔一段时间(比如60秒)去请求一下某 ...

  3. Linux常见命令(系统命令)

    1.查看主机名hostname 2.修改主机名(重启后无效)hostname hadoop 3.修改主机名(重启后永久生效)vi /etc/sysconfig/network[hostname=had ...

  4. node实现微信扫码群发消息《附上github代码》

    本篇文章就是为大家介绍一下我是如何用node去实现扫码群发功能,源代码地址在最后面 获取登录二维码 -> 扫码登录服务端           首先介绍一下主要流程,并附上关键代码 1.获取UUI ...

  5. CentOS环境下tomcat启动超级慢的解决方案

    在本地开发环境,应用正常启动. 在CentOS测试环境,应用启动速度也是正常的. 但是在阿里云的生产环境,tomcat启动超级慢,并且在最终打印出来以下内容: org.apache.catalina. ...

  6. 【ASP.NET Core】准备工作:在 Windows 10 上配置 Linux 子系统

    ASP.NET Core 其实比传统的 ASP.NET 要简单很多,而且也灵活很多,并且可以跨平台独立运行. 在 Windows 平台上,我们只要在安装 Visual Studio 的时候选择跨平台的 ...

  7. DAY5-小别-2018-1-15

    有两天没有写了,前天考完试出去浪了,惭愧自己没有学习:昨天,启程回家看完了循环内容的视频,晚上十点半火车到站,没抽出时间写了,还看了<黑客帝国>,有点小感触,人工智能的时代即将到来,我们该 ...

  8. 深度解剖dubbo源码

    -----------学习dubbo源码,能给你带来什么好处?----------- 1.提升SOA的微服务架构设计能力   通过读dubbo源码是一条非常不错的通往SOA架构设计之路,毕竟SOA的服 ...

  9. Java多线程同步问题:一个小Demo完全搞懂

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.一个简单的Demo引发的血案 关于线程同步问题我们从一个 ...

  10. bootstrap-table表格插件的使用案例

    近期刚刚结束一个项目,总结一下之前做的一个后台管理系统中用到的bootstrap-table表格插件,下面是我做的一个案例(展示主要代码部分): //请求服务数据时所传参数 function quer ...