前面在分支创建 I/O Completion Queue 命令时我们提到了,Admin command的提交,使用了nvme_submit_admin_cmd函数,该函数其实是调用了nvme_submit_sync_cmd(dev, 0, cmd, result, ADMIN_TIMEOUT),即提交了一个同步的命令。
static int nvme_submit_sync_cmd(struct nvme_dev *dev, int q_idx, struct nvme_command *cmd, u32 *result, unsigned timeout)
{
int cmdid, ret;
struct sync_cmd_info cmdinfo;
struct nvme_queue *nvmeq;
nvmeq = lock_nvmeq(dev, q_idx);
if (!nvmeq)
return -ENODEV;
cmdinfo.task = current;
cmdinfo.status = -EINTR;
cmdid = alloc_cmdid(nvmeq, &cmdinfo, sync_completion, timeout); //给cmd分配一个cmd id,通过在一个bitmap(还记得第一节分析init时提到的cmdid_data吗,它就是这个bitmap)中从头向后找,找到第一个非0的位置,就将这个位置号作为cmd id分配给这个cmd。。在alloc_cmdid的时候还会把completion的回调函数(
sync_completion)以及回调函数的(cmdinfo)放入nvmeq->cmdinfo中,当controller处理完命令以后,host在被唤醒或者检测到这个命令处理完以后,就会执行这个回调函数,处理completion queue。
if (cmdid < 0) {
unlock_nvmeq(nvmeq);
return cmdid;
}
cmd->common.command_id = cmdid; //把获取的cmd id赋值要提交的cmd的command_id
set_current_state(TASK_KILLABLE);
ret = nvme_submit_cmd(nvmeq, cmd); //把command拷贝到queue中去同时按一下doorbell寄存器门铃,nvmeq->q_db = &dev->dbs[qid * 2 * dev->db_stride]
if (ret) {//如果提交失败
free_cmdid(nvmeq, cmdid, NULL);//把cmdid清理掉
unlock_nvmeq(nvmeq);
set_current_state(TASK_RUNNING);
return ret;//返回error code
}
unlock_nvmeq(nvmeq);
schedule_timeout(timeout);//等待controller执行完,执行一次cpu调度
if (cmdinfo.status == -EINTR)
nvmeq = lock_nvmeq(dev, q_idx);
if (nvmeq) {//如果queue还存在,则说明timeout超时了
nvme_abort_command(nvmeq, cmdid);//aborted掉这个cmd
unlock_nvmeq(nvmeq);
}
return -EINTR;
}
if (result)
*result = cmdinfo.result;
return cmdinfo.status;
}
static int nvme_submit_cmd(struct nvme_queue *nvmeq, struct nvme_command *cmd)
{
unsigned long flags;
u16 tail;
spin_lock_irqsave(&nvmeq->q_lock, flags);
if (nvmeq->q_suspended) { //如果是suspend状态,说明这个queue没有init OK,所以要返回error信息
spin_unlock_irqrestore(&nvmeq->q_lock, flags);
return -EBUSY;
}
tail = nvmeq->sq_tail; //找到submission queue的队尾
memcpy(&nvmeq->sq_cmds[tail], cmd, sizeof(*cmd)); //把要提交的命令拷贝到循环队列的队尾中去
if (++tail == nvmeq->q_depth) //如果循环队列到头了,则队尾重新归0
tail = 0;
writel(tail, nvmeq->q_db); //按门铃!!!
nvmeq->sq_tail = tail; //更新队尾的位置
spin_unlock_irqrestore(&nvmeq->q_lock, flags);
return 0;
}
static void *free_cmdid(struct nvme_queue *nvmeq, int cmdid,
nvme_completion_fn *fn)
{
void *ctx;
struct nvme_cmd_info *info = nvme_cmd_info(nvmeq);
if (cmdid >= nvmeq->q_depth || !info[cmdid].fn) {
if (fn)
*fn = special_completion;
return CMD_CTX_INVALID;
}
if (fn)
*fn = info[cmdid].fn;
ctx = info[cmdid].ctx;
info[cmdid].fn = special_completion;
info[cmdid].ctx = CMD_CTX_COMPLETED;
clear_bit(cmdid, nvmeq->cmdid_data); //将该cmdid对应的bitmap位清零
wake_up(&nvmeq->sq_full); //唤醒等待获取cmdid的进程,同时唤醒process_cq的处理进程
return ctx;
}