讲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 #define PCI_CLASS_STORAGE_EXPRESS 0x010802
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 .probe = nvme_probe,
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 result = pci_register_driver(&nvme_driver);
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 dev->bar = ioremap(pci_resource_start(pdev, 0), 8192);
1970 if (!dev->bar) {
1971 result = -ENOMEM;
1972 goto disable_msix;
1973 }
1974
1975 result = nvme_configure_admin_queue(dev);
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 result = nvme_dev_add(dev);
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 result = misc_register(&dev->miscdev);
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