1. 程式人生 > >Linux中nvme驅動詳解

Linux中nvme驅動詳解

NVMe離不開PCIe,NVMe SSD是PCIe的endpoint。PCIe是x86平臺上一種流行的bus匯流排,由於其Plug and Play的特性,目前很多外設都通過PCI Bus與Host通訊,甚至不少CPU的整合外設都通過PCI Bus連線,如APIC等。

  NVMe SSD在PCIe介面上使用新的標準協議NVMe,由大廠Intel推出並交由nvmexpress組織推廣,現在被全球大部分儲存企業採納

1.NVMe Command

NVMe Host(Server)和NVMe Controller(SSD)通過NVMe Command進行資訊互動。NVMe Spec中定義了NVMe Command的格式,佔用64位元組。

NVMe Command分為Admin Command和IO Command兩大類,前者主要是用於配置,後者用於資料傳輸。

  NVMe Command是Host與SSD Controller交流的基本單元,應用的I/O請求也要轉化成NVMe Command。

       詳見《NVM_Express_Revision》

2.PCI匯流排

在系統啟動時,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,將010802h放入pci_driver nvme_driver的id_table。之後當nvme_driver註冊到PCI Bus後,PCI Bus就知道這個驅動是給class code=010802h的裝置使用的。nvme_driver中有一個probe函式,nvme_probe(),這個函式才是真正載入裝置的處理函式。

#define PCI_CLASS_STORAGE_EXPRESS       0x010802

staticconststruct pci_device_id nvme_id_table[] = {

…….

{ PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_EXPRESS, 

0xffffff) },

……

};

3.單獨編譯NVME驅動

在老版本的原始碼中,可以在原始碼路徑drivers/block中,增加Makefile內容如下,進行編譯:

obj-$(CONFIG_BLK_DEV_NVME)      += nvme.o

nvme-objs := nvme-core.o nvme-scsi.o

PWD := $(shell pwd)

default:

        make -C /usr/src/kernels/3.10.0-327.x86_64/ M=$(PWD) modules

clean:

        rm rf *.o *.ko

然後直接make 即可生成nvme.ko檔案。

關於Makefile可以參考如下:

KERNELVER ?= $(shell uname -r)

KERNROOT = /lib/modules/$(KERNELVER)/build

nvme:

    $(MAKE) -C $(KERNROOT) M=`pwd`/drivers/block                                            

clean:

   $(MAKE) -C $(KERNROOT) M=`pwd`/drivers/block clean

主要就兩個檔案:nvme-core.cnvme-scsi.c

不過,最新的程式碼位於drivers/nvme/host,主要是core.cpci.c

4.註冊和初始化

我們知道首先是驅動需要註冊到PCI匯流排。那麼nvme_driver是如何註冊的呢?

當驅動被載入時就會呼叫nvme_init(drivers/nvme/host/pci.c)函式。在這個函式中,呼叫了kernel的函式pci_register_driver,註冊nvme_driver,其結構體如下。

staticstruct pci_driver nvme_driver = {

        .name           = "nvme",

        .id_table       = nvme_id_table,

        .probe          = nvme_probe,

        .remove         = nvme_remove,

        .shutdown       = nvme_shutdown,

        .driver         = {      

                .pm     = &nvme_dev_pm_ops,

        },              

        .sriov_configure = nvme_pci_sriov_configure,

        .err_handler    = &nvme_err_handler,

};

這樣PCI bus上就多了一個pci_driver nvme_driver。當讀到一個裝置的class code是010802h時,就會呼叫這個nvme_driver結構體的probe函式, 也就是說當裝置和驅動匹配了之後,驅動的probe函式就會被呼叫,來實現驅動的載入。

Probe函式主要完成四個工作:

1.對映裝置的bar空間到記憶體虛擬地址空間

2.設定admin queue;

3.新增nvme namespace裝置;

4.新增nvme Controller,提供ioctl介面。

       PCIe的Header空間和BAR空間是PCIe的關鍵特性。Header空間是PCIe裝置的通有屬性,所有的PCIe Spec功能和規範都在這裡實現;BAR空間則是裝置差異化的具體體現,BAR空間的定義決定了這個裝置是網絡卡,SSD還是虛擬裝置。BAR空間是Host和PCIe裝置進行資訊互動的重要介質,BAR空間的資料實際儲存在PCIe裝置上。Host這邊給PCIe裝置分配的地址資源,並不佔用Host的記憶體資源。當讀寫BAR空間時,都需要通過PCIe介面(通過PCI TLP訊息)進行實際的資料傳輸。

接著來看下nvme_driver結構體中的.probe函式nvme_probe。

staticint nvme_probe(struct pci_dev *pdev, conststruct pci_device_id *id)

{

int node, result = -ENOMEM;

struct nvme_dev *dev;    

unsignedlong quirks = id->driver_data;

        node = dev_to_node(&pdev->dev);

if (node == NUMA_NO_NODE)

                set_dev_node(&pdev->dev, first_memory_node);

        dev = kzalloc_node(sizeof(*dev), GFP_KERNEL, node);

if (!dev)       

return -ENOMEM;          

        dev->queues = kcalloc_node(num_possible_cpus() + 1,

sizeof(struct nvme_queue), GFP_KERNEL, node);

if (!dev->queues)

goto free;               

        dev->dev = get_device(&pdev->dev);

        pci_set_drvdata(pdev, dev);

        result = nvme_dev_map(dev);

if (result)

goto put_pci;            

        INIT_WORK(&dev->ctrl.reset_work, nvme_reset_work);

        INIT_WORK(&dev->remove_work, nvme_remove_dead_ctrl_work);

        mutex_init(&dev->shutdown_lock);

        init_completion(&dev->ioq_wait);

        result = nvme_setup_prp_pools(dev);

if (result)     

goto unmap;              

        quirks |= check_vendor_combination_bug(pdev);

        result = nvme_init_ctrl(&dev->ctrl, &pdev->dev, &nvme_pci_ctrl_ops,

                        quirks);                 

if (result)     

goto release_pools;      

        dev_info(dev->ctrl.device, "pci function %s\n", dev_name(&pdev->dev));

        nvme_reset_ctrl(&dev->ctrl);

return0;

release_pools:

        nvme_release_prp_pools(dev);

unmap:

        nvme_dev_unmap(dev);

put_pci:

        put_device(dev->dev);

free:

        kfree(dev->queues);

        kfree(dev);

return result;

}

       nvme_probe函式會通過nvme_dev_map函式(層層呼叫之後)對映裝置的bar空間到核心的虛擬地址空間當中, pci協議裡規定了pci裝置的配置空間裡有6個32位的bar暫存器,代表了pci裝置上的一段記憶體空間,可以通過writel, readl這類函式直接讀寫暫存器。

並分配裝置資料結構nvme_dev,佇列nvme_queue等,結構體如下。

structnvme_dev {

struct nvme_queue *queues;

struct blk_mq_tag_set tagset;

struct blk_mq_tag_set admin_tagset;

        u32 __iomem *dbs;

struct device *dev;

struct dma_pool *prp_page_pool;

struct dma_pool *prp_small_pool;

unsigned online_queues;

unsigned max_qid;

int q_depth;

        u32 db_stride;

void __iomem *bar;

unsignedlong bar_mapped_size;

struct work_struct remove_work;

struct mutex shutdown_lock;

bool subsystem;

void __iomem *cmb;

        pci_bus_addr_t cmb_bus_addr;

        u64 cmb_size;

        u32 cmbsz;

        u32 cmbloc;

struct nvme_ctrl ctrl;

struct completion ioq_wait;

/* shadow doorbell buffer support: */

        u32 *dbbuf_dbs;

        dma_addr_t dbbuf_dbs_dma_addr;

        u32 *dbbuf_eis;

        dma_addr_t dbbuf_eis_dma_addr;

/* host memory buffer support: */

        u64 host_mem_size;

        u32 nr_host_mem_descs;

        dma_addr_t host_mem_descs_dma;

struct nvme_host_mem_buf_desc *host_mem_descs;

void **host_mem_desc_bufs;

};

       每個裝置至少兩個佇列,一個是admin管理命令,一個是給I/O命令,這個佇列概念和之前介紹塊驅動中的磁碟佇列一個道理,只是那個驅動比較基礎,所以命令和IO並不區分佇列,具體結構體如下。

structnvme_queue {

struct device *q_dmadev;

struct nvme_dev *dev;

        spinlock_t q_lock;

struct nvme_command *sq_cmds;

struct nvme_command __iomem *sq_cmds_io;

volatilestruct nvme_completion *cqes;

struct blk_mq_tags **tags;

        dma_addr_t sq_dma_addr;

        dma_addr_t cq_dma_addr;

        u32 __iomem *q_db;

        u16 q_depth;

        s16 cq_vector;

        u16 sq_tail;

        u16 cq_head;

        u16 qid;

        u8 cq_phase;

        u8 cqe_seen;

        u32 *dbbuf_sq_db;

        u32 *dbbuf_cq_db;

        u32 *dbbuf_sq_ei;

        u32 *dbbuf_cq_ei;

};

繼續說nvme_probe函式,nvme_setup_prp_pools,主要是建立dma pool,後面可以通過dma函式從dma pool中獲得memory。主要是為了給4k128k的不同IO來做優化。

nvme_init_ctrl函式會建立NVMe控制器結構體,這樣在後後續probe階段時候用初始化過的結構,其傳入的操作函式集是nvme_pci_ctrl_ops

staticconststruct nvme_ctrl_ops nvme_pci_ctrl_ops = {

        .name                   = "pcie",

相關推薦

Linuxnvme驅動

NVMe離不開PCIe,NVMe SSD是PCIe的endpoint。PCIe是x86平臺上一種流行的bus匯流排,由於其Plug and Play的特性,目前很多外設都通過PCI Bus與Host通訊,甚至不少CPU的整合外設都通過PCI Bus連線,如APIC等。  NV

linuxexpect命令

linux運維expect介紹expect 是由Don Libes基於Tcl(Tool Command Language )語言開發的,主要應用於自動化交互式操作的場景,借助Expect處理交互的命令,可以將交互過程如:ssh登錄,ftp登錄等寫在一個腳本上,使之自動化完成。尤其適用於需要對多臺服務器執行相同

linuxat命令

at一次性計劃任務 at詳解 系統命令 at命令: 一:簡介: 計劃任務,在特定的時間執行某項工作,在特定的時間執行一次,需要安裝at服務,apt-get install at 二:時間定義: at允許使用一套相當復雜的指定時間的方法。● 能夠接受在當天的hh:mm(小時:分鐘)式的時間指定。假如

Linuxseq命令

seq命令可以輸出連續的數字,或固定間隔的數,或者是輸出指定格式的數字 例子: [[email protected] Desktop]$ seq 1 5 1 2 3 4 5 [[email protected] Desktop]$ seq 1 2 5 1 3

linuxapache服務4(企業級)ssl

ssl加密 yum install mod_ssl  -y      他是一個模組 yum install  crypto-utils  -y  加密 genkey www.westos.com

linuxapache服務3(企業級)cgi

對於cgi表單 mkdir -p /var/www/html/cgi semaneger fcontent -a -t httpd_sys_script_exec_t  '/var/www/html/cgi(/.*)?' restorecon -Rvvf  /va

linuxapache服務1(企業級)(http\cgi\php\ssl)

curl -I www.jd.com 檢視網站用的哪些服務 curl -I www.taobao.com firewall-config runtime 當前允許的狀態 permanent 永久允許的 html超文字標記語言 yum install  httpd  htt

linuxfork() 函式

fork入門知識 一個程序,包括程式碼、資料和分配給程序的資源。fork()函式通過系統呼叫建立一個與原來程序幾乎完全相同的程序,也就是兩個程序可以做完全相同的事,但如果初始引數或者傳入的變數不同,兩個程序也可以做不同的事。 一個程序呼叫fork()函式後,系統先給新的程序分配資源,例如儲存資料和程式碼的

輸入子系統------鍵盤按鍵驅動程式 13.Linux鍵盤按鍵驅動 ()

由上一節的輸入子系統的框架分析可知,其分三層:裝置驅動層,核心層,事件驅動層 我們在為某種裝置的編寫驅動層,只需要關心裝置驅動層,即如何驅動裝置並獲得硬體資料(如按下的按鍵資料),然後呼叫核心層提供的介面,核心層就會自動把資料提交給事件處理層。在輸入子系統中,事件驅動是標準的,適用於所有輸入類的。

linuxbtt工具

在之前的文章中介紹瞭如何使用blktrace 以及其工作原理和架構。我們知道blktrace 跟蹤塊裝置的統計資訊,每個CPU會有一個檔案儲存,然後通過blkparse可以將這些檔案整合成一個檔案來顯示。             不過blkparse顯示的檔案過於龐大,而通

Linuxdd命令

一、dd命令 dd:用指定大小的塊拷貝一個檔案,並在拷貝的同時進行指定的轉換。 注意:指定數字的地方若以下列字元結尾,則乘以相應的數字:b=512;c=1;k=1024;w=2 引數註釋: if=檔名:輸入檔名,預設為標準輸入。即指定原始檔。< i

6410 實現 linux 串列埠驅動

為了實現串列埠通訊,需要在嵌入式linux下編寫相應的驅動程式。在嵌入式系統中,串列埠被看做終端裝置tty。終端裝置是unix體系中一個非常重要的物件,內容非常複雜,它是整個unix人機互動的基礎,其地位並不亞於檔案系統在作業系統中的作用。筆者muge0913在此對uar

nvme 驅動 之1

按照老的套路,在分析一個driver時,我們首先看這個driver相關的kconfig及Makefile檔案,察看相關的原始碼檔案. 在開始閱讀一個driver,通常都是從module_initor syscall_init函式看起。 下面讓我們開始nvme的旅程吧。 首

Linux塊裝置驅動(一)

    請求佇列跟蹤等候的塊I/O請求,它儲存用於描述這個裝置能夠支援的請求的型別資訊、它們的最大大小、多少不同的段可進入一個請求、硬體扇區大小、對齊要求等引數,其結果是:如果請求佇列被配置正確了,它不會交給該裝置一個不能處理的請求。     請求佇列還實現一個插入介面,這個介面允許使用多個I/O排程器,I/

很好的linux下GPIO驅動文章

打算跟著友善之臂的《mini2440 linux移植開發指南》來做個LED驅動,雖然LED的原理簡單得不能再簡單了,但是要把kernel中針對於s3c24**的GPIO的一些資料結構,還有函式搞清楚也不是那麼輕鬆的事,所以本文主要簡單地說明下LED驅動中的相關資料結構以及

Linuxps命令

1. 執行(正在執行或在執行佇列中等待)   2. 中斷(休眠中, 受阻, 在等待某個條件的形成或接受到訊號)   3. 不可中斷(收到訊號不喚醒和不可執行, 程序必須等待直到有中斷髮生)   4. 僵死(程序已終止, 但程序描述符存在, 直到父程序呼叫wait4()系統呼叫後釋放)   5. 停止(程序收到

Linuxmake命令

原文地址:https://www.computerhope.com/unix/umake.htm About make make is a utility(實用的) for building and maintaining groups of programs 

Linuxvi命令

最近vi用的多,很多技巧不知道,備註一份, vi編輯器是所有Unix及Linux系統下標準的編輯器,它的強大不遜色於任何最新的文字編輯器,這裡只是簡單地介紹一下它的用法和一小部分指令。由於 對Unix及Linux系統的任何版本,vi編輯器是完全相同的,因此您可以在其他任

LinuxTFTP使用

FTP協議簡介 TFTP是用來下載遠端檔案的最簡單網路協議,它其於UDP協議而實現。 linux伺服器端tftp-server的配置 1、安裝tftp伺服器需要安裝xinetd(守護tftp)、tftp和tftp-server 3個軟體1)如果能上網,通過yum安裝

linuxawk命令

簡介awk是一個強大的文字分析工具,相對於grep的查詢,sed的編輯,awk在其對資料分析並生成報告時,顯得尤為強大。簡單來說awk就是把檔案逐行的讀入,以空格為預設分隔符將每行切片,切開的部分再進行各種分析處理。awk有3個不同版本: awk、nawk和gawk,未作特別