NVMe驱动解析-注册设备

33 篇文章 15 订阅

讲NVMe离不开PCIe,PCIe是x86平台上一种流行的bus总线,由于其Plug and Play的特性,目前很多外设都通过PCI Bus与Host通信,甚至不少CPU的集成外设都通过PCI Bus连接,如APIC等。下图是x86服务器上常用的至强CPU给外设提供的PCIe接口。


NVMe SSD作为PCIe的endpoint,是如何被系统识别为NVMe SSD并加载上的呢?

在系统启动时,BIOS会枚举整个PCI的总线,之后将扫描到的设备通过ACPI tables传给操作系统。当操作系统加载时,PCI Bus驱动则会根据此信息读取各个PCI设备的Header Config空间,从class code寄存器获得一个特征值。

class code就是PCI bus用来选择哪个驱动加载设备的唯一根据。NVMe Spec定义的class code是010802h。NVMe SSD内部的Controller PCIe Header中class code都会设置成010802h。

只要在驱动中指定class code为010802h则由该驱动加载设备即可。nvme驱动中,将010802h放入pci_drivernvme_driver的id_table,之后当nvme_driver注册到PCI Bus后,PCI Bus就知道这个驱动是给class code=010802h的设备使用的。这里还有一个地方值得注意,nvme_driver中还有一个probe函数,nvme_probe(),这个函数才是真正加载设备的处理函数。

2051 /* Move to pci_ids.h later */
2052<strong> #define PCI_CLASS_STORAGE_EXPRESS       0x010802</strong>
2053 
2054 static DEFINE_PCI_DEVICE_TABLE(nvme_id_table) = {
2055         { PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 0xffffff) },
2056         { 0, }
2057 };
2058 MODULE_DEVICE_TABLE(pci, nvme_id_table);
2059 
2060 static struct pci_driver nvme_driver = {
2061         .name           = "nvme",
2062         .id_table       = nvme_id_table,
2063        <strong> .probe          = nvme_probe,</strong>
2064         .remove         = nvme_remove,
2065         .suspend        = nvme_suspend,
2066         .resume         = nvme_resume,
2067         .err_handler    = &nvme_err_handler,
2068 };

那么,nvme_driver是如何注册到PCI Bus的呢?如下是nvme驱动的初始化函数,当这个驱动被加载时就会调用nvme_init函数(如使用modprobe nvme)时。在这个函数中,就调用了kernel提供的函数pci_register_driver,注册上面提到的nvme_driver 。执行这个函数之后,PCI bus上就多了一个pci_driver nvme_driver。当读到一个设备的class code是010802h时,就会调用这个nvme_driver结构体的probe函数。

2070 static int __init nvme_init(void)
2071 {
2072         int result;
2073 
2074         nvme_thread = kthread_run(nvme_kthread, NULL, "nvme");
2075         if (IS_ERR(nvme_thread))
2076                 return PTR_ERR(nvme_thread);
2077 
2078         result = register_blkdev(nvme_major, "nvme");
2079         if (result < 0)
2080                 goto kill_kthread;
2081         else if (result > 0)
2082                 nvme_major = result;
2083 
2084         <strong>result = pci_register_driver(&nvme_driver);</strong>
2085         if (result)
2086                 goto unregister_blkdev;
2087         return 0;
2088 
2089  unregister_blkdev:
2090         unregister_blkdev(nvme_major, "nvme");
2091  kill_kthread:
2092         kthread_stop(nvme_thread);
2093         return result;
2094 }

nvme_driver 的probe函数nvme_probe做了些什么呢?这个函数做了很多事情,这里只介绍其中比较重要的部分。第一,设置映射设备的bar空间到内核的虚拟地址空间当中,通过调用ioremap函数,将Controller的nvme寄存器映射到内核后,可以通过writel, readl这类函数直接读写寄存器。
第二,设置admin queue,admin queue设置之后,才能发送nvme admin Command。
第三,添加nvme namespace设备,即/dev/nvme#n#,这样就可以对设备进行读写操作了。
第四,添加nvme Controller设备,即/dev/nvme#,提供ioctl接口。这样userspace就可以通过ioctl系统调用发送nvme admin command。

1924 static int nvme_probe(struct pci_dev *pdev, const struct pci_device_id *id)
1925 {
1926         int bars, result = -ENOMEM;
1927         struct nvme_dev *dev;
1928 
1929         dev = kzalloc(sizeof(*dev), GFP_KERNEL);
1930         if (!dev)
1931                 return -ENOMEM;
1932         dev->entry = kcalloc(num_possible_cpus(), sizeof(*dev->entry),
1933                                                                 GFP_KERNEL);
1934         if (!dev->entry)
1935                 goto free;
1936         dev->queues = kcalloc(num_possible_cpus() + 1, sizeof(void *),
1937                                                                 GFP_KERNEL);
1938         if (!dev->queues)
1939                 goto free;
1940 
1941         if (pci_enable_device_mem(pdev))
1942                 goto free;
1943         pci_set_master(pdev);
1944         bars = pci_select_bars(pdev, IORESOURCE_MEM);
1945         if (pci_request_selected_regions(pdev, bars, "nvme"))
1946                 goto disable;
1947 
1948         INIT_LIST_HEAD(&dev->namespaces);
1949         dev->pci_dev = pdev;
1950         pci_set_drvdata(pdev, dev);
1951 
1952         if (!dma_set_mask(&pdev->dev, DMA_BIT_MASK(64)))
1953                 dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(64));
1954         else if (!dma_set_mask(&pdev->dev, DMA_BIT_MASK(32)))
1955                 dma_set_coherent_mask(&pdev->dev, DMA_BIT_MASK(32));
1956         else
1957                 goto disable;
1958 
1959         result = nvme_set_instance(dev);
1960         if (result)
1961                 goto disable;
1962 
1963         dev->entry[0].vector = pdev->irq;
1964 
1965         result = nvme_setup_prp_pools(dev);
1966         if (result)
1967                 goto disable_msix;
1968 
1969         <strong>dev->bar = ioremap(pci_resource_start(pdev, 0), 8192);</strong>
1970         if (!dev->bar) {
1971                 result = -ENOMEM;
1972                 goto disable_msix;
1973         }
1974 
1975         <strong>result = nvme_configure_admin_queue(dev);</strong>
1976         if (result)
1977                 goto unmap;
1978         dev->queue_count++;
1979 
1980         spin_lock(&dev_list_lock);
1981         list_add(&dev->node, &dev_list);
1982         spin_unlock(&dev_list_lock);
1983 
1984         <strong>result = nvme_dev_add(dev);</strong>
1985         if (result)
1986                 goto delete;
1987 
1988         scnprintf(dev->name, sizeof(dev->name), "nvme%d", dev->instance);
1989         dev->miscdev.minor = MISC_DYNAMIC_MINOR;
1990         dev->miscdev.parent = &pdev->dev;
1991         dev->miscdev.name = dev->name;
1992         dev->miscdev.fops = &nvme_dev_fops;
1993        <strong> result = misc_register(&dev->miscdev);</strong>
1994         if (result)
1995                 goto remove;
1996 
1997         kref_init(&dev->kref);
1998         return 0;

这篇文字就先介绍到这里,下一篇文章将对probe函数进行更细致的解析。

张元元是Memblaze SSD事业部应用工程师,研究方向涉及PCIe SSD在VSAN、Docker等环境中的应用及优化。对于服务器虚拟化、NVMe驱动的实现、Linux内核及容器技术有深入的研究。本系列文章为张元元对于NVMe驱动及相关技术的全面解读,更多张元元的文章请关注他的微信公众号:yuan_memblaze


  • 4
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值