这里记录static int nvme_dev_add(struct nvme_dev *dev)函数。
在调用完这个函数以后,就可以在dev目录下看到响应的设备了。
static int nvme_dev_add(struct nvme_dev *dev)
{
int res;
unsigned nn, i;
struct nvme_ns *ns;
struct nvme_id_ctrl *ctrl;
struct nvme_id_ns *id_ns;
void *mem;
dma_addr_t dma_addr;
/*
该字段表示控制器支持的最小hostmemory页面大小。最小内存页大小是(2 ^ (12 + MPSMIN))。
主机不能配置内存页大小inCC。小于此值的MPS。
*/
int shift = NVME_CAP_MPSMIN(readq(&dev->bar->cap)) + 12;
mem = dma_alloc_coherent(&dev->pci_dev->dev, 8192, &dma_addr, GFP_KERNEL);
if (!mem)
return -ENOMEM;
res = nvme_identify(dev, 0, 1, dma_addr);//cns为1是struct nvme_id_ctrl结构
if (res) {
res = -EIO;
goto out;
}
ctrl = mem;
nn = le32_to_cpup(&ctrl->nn); //Number of namespaces
dev->oncs = le16_to_cpup(&ctrl->oncs);//option nvm command support
//下面这几个后面可以与scsi命令转nvme,或者上层获取nvme相关的版本信息
memcpy(dev->serial, ctrl->sn, sizeof(ctrl->sn));//serial number
memcpy(dev->model, ctrl->mn, sizeof(ctrl->mn)); //model number
memcpy(dev->firmware_rev, ctrl->fr, sizeof(ctrl->fr));//firmware revision
/*
2^n,以MPSMIN为单位, 最大数据传输长度,0表示没有限制
比如mdts是1,以2^n表示值是2(即2 * 4096/),假设mpsmin是0,即支持的最小内存页为4096,
那么max_hw_sectors = 1 << (1 + 12 - 9) = 1 << 4 = 16
16 * 512(扇区的大小) = 2 * 4096 刚好是对应的。
*/
if (ctrl->mdts)
dev->max_hw_sectors = 1 << (ctrl->mdts + shift - 9);
/*
这个不知道描述的是啥?
*/
if ((dev->pci_dev->vendor == PCI_VENDOR_ID_INTEL) && (dev->pci_dev->device == 0x0953) && ctrl->vs[3])
dev->stripe_size = 1 << (ctrl->vs[3] + shift);
id_ns = mem;
for (i = 1; i <= nn; i++) { //number of namespaces
res = nvme_identify(dev, i, 0, dma_addr); //cns为0是struct nvme_id_ns结构
if (res)
continue;
/*
Namespace Capacity (NCAP):
该字段表示在任何时间点命名空间中可以分配的最大逻辑块数量。
逻辑块的数量取决于格式化的LBA大小。在格式化命名空间之前,此字段是未定义的。
该字段用于精简配置,报告一个小于等于Namespace Size的值。备用LBAs不作为该字段的一部分报告。
当使用Write或Write uncorrectable命令写入逻辑块时,会分配逻辑块。
可以使用数据集管理、清理或写零命令释放逻辑块
*/
if (id_ns->ncap == 0)
continue;
res = nvme_get_features(dev, NVME_FEAT_LBA_RANGE, i, dma_addr + 4096, NULL);
if (res)
memset(mem + 4096, 0, 4096);
ns = nvme_alloc_ns(dev, i, mem, mem + 4096);
if (ns)
list_add_tail(&ns->list, &dev->namespaces);//namespace 用一个链表表示
}
//根据namespace来增加disk,函数调用完毕,就可以在dev目录下看到相应的设备节点了
list_for_each_entry(ns, &dev->namespaces, list)
add_disk(ns->disk); //这个函数相信大家都比较熟悉了
res = 0;
out:
dma_free_coherent(&dev->pci_dev->dev, 8192, mem, dma_addr);
return res;
}
nvme_alloc_ns
static struct nvme_ns *nvme_alloc_ns(struct nvme_dev *dev, unsigned nsid,
struct nvme_id_ns *id, struct nvme_lba_range_type *rt)
{
struct nvme_ns *ns;
struct gendisk *disk;
int lbaf;
if (rt->attributes & NVME_LBART_ATTRIB_HIDE)//值是0或者1,其它是非法值
return NULL;
ns = kzalloc(sizeof(*ns), GFP_KERNEL);
if (!ns)
return NULL;
//申请队列结构体
ns->queue = blk_alloc_queue(GFP_KERNEL);
if (!ns->queue)
goto out_free_ns;
ns->queue->queue_flags = QUEUE_FLAG_DEFAULT;
queue_flag_set_unlocked(QUEUE_FLAG_NOMERGES, ns->queue);//不需要上层进行io合并操作
queue_flag_set_unlocked(QUEUE_FLAG_NONROT, ns->queue);//随机访问设备
//nvme_make_request发起命令的回调函数
blk_queue_make_request(ns->queue, nvme_make_request);
ns->dev = dev;
ns->queue->queuedata = ns;
disk = alloc_disk(NVME_MINORS); //申请struct gendisk结构
if (!disk)
goto out_free_queue;
ns->ns_id = nsid; //namespace id
ns->disk = disk;
lbaf = id->flbas & 0xf;//取0-3bit
ns->lba_shift = id->lbaf[lbaf].ds; //得到LBA的大小,逻辑块的大小,2^n表示
ns->ms = le16_to_cpu(id->lbaf[lbaf].ms);
//设置队列的逻辑块大小,以字节位单位
blk_queue_logical_block_size(ns->queue, 1 << ns->lba_shift);
/*
将控制器的限制告诉内核(单次io的大小限制)
*/
if (dev->max_hw_sectors)
blk_queue_max_hw_sectors(ns->queue, dev->max_hw_sectors);//max_segment_size
disk->major = nvme_major;
disk->minors = NVME_MINORS;
disk->first_minor = NVME_MINORS * nvme_get_ns_idx();
disk->fops = &nvme_fops;//块设备操作
disk->private_data = ns;
disk->queue = ns->queue;//块设备相关的队列
disk->driverfs_dev = &dev->pci_dev->dev;
//磁盘名称,这里利用到了instance和nsid的值
sprintf(disk->disk_name, "nvme%dn%d", dev->instance, nsid);
/*
设置磁盘容量,以扇区为单位
Namespace Size (NSZE):
该字段表示logicalblocks中命名空间的总大小。
大小为n的命名空间由LBA 0到(n - 1)个LBA组成,逻辑块的数量根据格式化后的LBA大小确定。
在格式化命名空间之前,此字段是未定义的。
*/
set_capacity(disk, le64_to_cpup(&id->nsze) << (ns->lba_shift - 9));
/*
位3如果设置为“1”,则控制器支持写零命令。如果设置为“0”,则该控制器不支持写零命令。
位2如果设置为“1”,则控制器支持数据集管理命令。如果清除为“0”,则控制器不支持数据集管理命令。
第1位如果设置为“1”,则控制器支持Write Uncorrectable命令。如果清除为“0”,则该控制器不支持Write Uncorrectable命令。
位0如果设置为“1”,则控制器支持比较命令。如果清除为“0”,则控制器不支持比较命令。
*/
if (dev->oncs & NVME_CTRL_ONCS_DSM) //0100
nvme_config_discard(ns);
return ns;
out_free_queue:
blk_cleanup_queue(ns->queue);
out_free_ns:
kfree(ns);
return NULL;
}
这里看到了块设备编程当中比较主要的函数,比如:
blk_queue_make_request
blk_cleanup_queue
set_capacity
alloc_disk
add_disk
nvme_fops
static int nvme_ioctl(struct block_device *bdev, fmode_t mode, unsigned int cmd, unsigned long arg)
{
struct nvme_ns *ns = bdev->bd_disk->private_data;
switch (cmd) {
case NVME_IOCTL_ID:
force_successful_syscall_return();
return ns->ns_id;
case NVME_IOCTL_ADMIN_CMD: //admin命令
return nvme_user_admin_cmd(ns->dev, (void __user *)arg);
case NVME_IOCTL_SUBMIT_IO: //io命令
return nvme_submit_io(ns, (void __user *)arg);
case SG_GET_VERSION_NUM:
return nvme_sg_get_version_num((void __user *)arg);
case SG_IO: //scsi-nvme
return nvme_sg_io(ns, (void __user *)arg);
default:
return -ENOTTY;
}
}
static const struct block_device_operations nvme_fops = {
.owner = THIS_MODULE,
.ioctl = nvme_ioctl,
.compat_ioctl = nvme_ioctl,
};