NVME Driver分析之nvme_submit_sync_cmd

前面在分支创建 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;
}



  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`nvme_submit_user_cmd()` 函数是 NVMe 驱动中用于向 NVMe 设备提交用户命令的函数。该函数的实现如下: ```c int nvme_submit_user_cmd(struct request_queue *q, struct nvme_command *cmd, void __user *ubuf, void __user *meta, unsigned timeout) { ... struct nvme_ns *ns = q->queuedata; ... struct nvme_user_io io = { .opcode = cmd->common.opcode, .flags = cmd->common.flags, .control = cpu_to_le16((timeout ? NVME_IO_FLAGS_PRACT : 0) | NVME_IO_FLAGS_CQ_UPDATE | NVME_IO_FLAGS_SGL_METABUF), .metadata = (__u64)meta, .addr = (__u64)ubuf, .slba = cpu_to_le64(cmd->rw.slba), .nlb = cpu_to_le16(cmd->rw.nblocks), .dsmgmt = cpu_to_le16(cmd->rw.dsmgmt), .reftag = cpu_to_le16(cmd->rw.reftag), .apptag = cpu_to_le16(cmd->rw.apptag), .appmask = cpu_to_le16(cmd->rw.appmask), }; ... ret = nvme_submit_user_cmd_hw(q, ns, &io, &cmd->common, timeout); ... return ret; } ``` 该函数的主要作用是将用户命令转换为 `nvme_user_io` 结构体,并调用 `nvme_submit_user_cmd_hw()` 函数将该命令提交给 NVMe 设备。下面是对该函数的参数及关键代码进行分析: - `q`:请求队列指针,用于指定 NVMe 设备所在的请求队列。 - `cmd`:NVMe 命令结构体指针,包含了要提交的 NVMe 写入命令的相关信息。 - `ubuf`:用户数据缓冲区的指针,该缓冲区包含了要写入存储介质的数据。 - `meta`:元数据缓冲区的指针,该缓冲区用于存储 NVMe 设备返回的写入操作结果。 - `timeout`:命令超时时间,以毫秒为单位。 该函数首先从请求队列中获取 NVMe 命名空间指针 `ns`,然后将用户命令转换为 `nvme_user_io` 结构体,并设置了一些命令的控制标志位。接着,该函数调用 `nvme_submit_user_cmd_hw()` 函数将命令提交给 NVMe 设备。 在 `nvme_submit_user_cmd_hw()` 函数中,NVMe 驱动会将 `nvme_user_io` 结构体中的数据转换为 NVMe 命令数据结构,并将该命令放入命令队列中。然后,NVMe 驱动会等待命令完成,并将命令的执行结果存储到元数据缓冲区中。最后,驱动程序会更新命令队列和完成队列的指针,并返回命令的执行状态。 在 NVMe 驱动中,`nvme_submit_user_cmd()` 函数是将用户命令提交给 NVMe 设备的入口函数,它的实现非常简单,主要是将用户命令转换为 NVMe 命令,并调用硬件相关的函数将命令提交给 NVMe 设备。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值