NVMe驱动解析-DMA传输
DMA技術(shù)是一項比較古老的技術(shù),大部分的處理器都附帶這個功能。通過DMA引擎,在CPU不用參與的情況下,數(shù)據(jù)就能夠從一個地址傳輸?shù)搅硪粋€地址。這在進行大量數(shù)據(jù)搬移的情況下,能夠大大降低CPU的使用率。
PCIe有個寄存器位Bus Master Enable。這個bit置1后,PCIe設(shè)備就可以向Host發(fā)送DMA Read Memory和DMA Write Memory請求了。當Host收到請求后,根據(jù)請求中包含的內(nèi)存地址,通過DMA引擎對該地址進行讀寫操作,再通過TLP發(fā)送或者接收數(shù)據(jù)。
當Host的driver需要跟PCIe設(shè)備傳輸數(shù)據(jù)的時候,只需要告訴PCIe設(shè)備存放數(shù)據(jù)的地址就可以了,下面將介紹NVMe是如何使用DMA傳輸NVMe Command的。
先回顧下之前文章提到的內(nèi)容,一是NVMe Command占用64個字節(jié),二是NVMe的PCIe BAR空間被映射到虛擬內(nèi)存空間(其中包括用來通知NVMe SSD Controller讀取Command的Doorbell寄存器)。另外,提一下NVMe數(shù)據(jù)傳輸?shù)姆绞?#xff0c;NVMe的數(shù)據(jù)傳輸都是通過NVMe Command,而NVMe Command則存放在NVMe Queue中,NVMe Queue一般按照下圖方法配置。
NVMe Command的DMA地址分配
NVMe驅(qū)動中分配NVMe queue的函數(shù)nvme_alloc_queue(),其中用來存放Completion Command( nvmeq->cqes)和Submit Command ( nvmeq->sq_cmds )的地址都是通過內(nèi)核函數(shù)dma_alloc_coherent()分配的。這里有必要介紹下,DMA傳輸?shù)刂繁仨毷俏锢磉B續(xù)的,通過dma_alloc_coherent()分配的內(nèi)存能夠滿足這個要求,而kmalloc()則不能。dma_alloc_coherent()的第二個參數(shù)是指定分配的空間,Submit Command 指定的是SQ_SIZE(depth),意思是分配depth個Submit Command的連續(xù)空間,所以一個Queue只能放depth個Command。第三個參數(shù)是存放實際的DMA地址,這個地址就是需要告訴PCIe設(shè)備的;與其對應(yīng)的是函數(shù)的返回值nvmeq->sq_cmds ,這個值是DMA地址轉(zhuǎn)換成內(nèi)核線程空間的地址值,驅(qū)動會向這個地址寫數(shù)據(jù)。那么整個過程是這樣:驅(qū)動獲得地址nvmeq->sq_cmds ,當上層傳入Command后,將Command寫入nvmeq->sq_cmds[i*64Bytes](i表示第n個Command,n不大于depth),然后通過Doorbell告訴SSD Controller 這個i值,之后Controller通過i就可以算出要取得數(shù)據(jù)的DMA地址了(nvmeq->cq_dma_addr[i*64Bytes])。
1023 static struct nvme_queue *nvme_alloc_queue(struct nvme_dev *dev, int qid, 1024 int depth, int vector) 1025 { 1026 struct device *dmadev = &dev->pci_dev->dev; 1027 unsigned extra = DIV_ROUND_UP(depth, 8) + (depth * 1028 sizeof(struct nvme_cmd_info)); 1029 struct nvme_queue *nvmeq = kzalloc(sizeof(*nvmeq) + extra, GFP_KERNEL); 1030 if (!nvmeq) 1031 return NULL; 1032 1033 nvmeq->cqes = dma_alloc_coherent(dmadev, CQ_SIZE(depth), 1034 &nvmeq->cq_dma_addr, GFP_KERNEL); 1035 if (!nvmeq->cqes) 1036 goto free_nvmeq; 1037 memset((void *)nvmeq->cqes, 0, CQ_SIZE(depth)); 1038 1039 nvmeq->sq_cmds = dma_alloc_coherent(dmadev, SQ_SIZE(depth), 1040 &nvmeq->sq_dma_addr, GFP_KERNEL); 1041 if (!nvmeq->sq_cmds) 1042 goto free_cqdma; 1043 1044 nvmeq->q_dmadev = dmadev; 1045 nvmeq->dev = dev; 1046 spin_lock_init(&nvmeq->q_lock); 1047 nvmeq->cq_head = 0; 1048 nvmeq->cq_phase = 1; 1049 init_waitqueue_head(&nvmeq->sq_full); 1050 init_waitqueue_entry(&nvmeq->sq_cong_wait, nvme_thread); 1051 bio_list_init(&nvmeq->sq_cong); 1052 nvmeq->q_db = &dev->dbs[qid << (dev->db_stride + 1)]; 1053 nvmeq->q_depth = depth; 1054 nvmeq->cq_vector = vector; 1055 1056 return nvmeq; 1057 1058 free_cqdma: 1059 dma_free_coherent(dmadev, CQ_SIZE(depth), (void *)nvmeq->cqes, 1060 nvmeq->cq_dma_addr); 1061 free_nvmeq: 1062 kfree(nvmeq); 1063 return NULL; 1064 }其實,NVMe并不是完全按照上面說的那樣,而是使用一種tail, head來表示。Submit Queue中用tail來表示最后一個入隊的Command index,而CompletionQueue中用head表示。這樣通過比較tail和head就可以知道隊列中哪些地址是有Command的。
Host如何告訴SSD Controller DMA地址
Controller需要知道DMA基地址后,才能算出某個index對應(yīng)的Command地址。那么,Host是怎么告訴Controller這個地址的呢?
NVMe協(xié)議規(guī)定了一個create_sq的Admin Command,Host就是通過向Controller發(fā)送這個命令告訴的,其中prp1的值就是前面講到的nvmeq->sq_dma_addr。Controller收到這個命令后,存下prp1的值即可。同理,competition queue也有一個Admin Command為create_cq。
866 static int adapter_alloc_cq(struct nvme_dev *dev, u16 qid, 867 struct nvme_queue *nvmeq) 868 { 869 int status; 870 struct nvme_command c; 871 int flags = NVME_QUEUE_PHYS_CONTIG | NVME_CQ_IRQ_ENABLED; 872 873 memset(&c, 0, sizeof(c)); 874 c.create_cq.opcode = nvme_admin_create_cq; 875 c.create_cq.prp1 = cpu_to_le64(nvmeq->cq_dma_addr); 876 c.create_cq.cqid = cpu_to_le16(qid); 877 c.create_cq.qsize = cpu_to_le16(nvmeq->q_depth - 1); 878 c.create_cq.cq_flags = cpu_to_le16(flags); 879 c.create_cq.irq_vector = cpu_to_le16(nvmeq->cq_vector); 880 881 status = nvme_submit_admin_cmd(dev, &c, NULL); 882 if (status) 883 return -EIO; 884 return 0; 885 } 886 887 static int adapter_alloc_sq(struct nvme_dev *dev, u16 qid, 888 struct nvme_queue *nvmeq) 889 { 890 int status; 891 struct nvme_command c; 892 int flags = NVME_QUEUE_PHYS_CONTIG | NVME_SQ_PRIO_MEDIUM; 893 894 memset(&c, 0, sizeof(c)); 895 c.create_sq.opcode = nvme_admin_create_sq; 896 c.create_sq.prp1 = cpu_to_le64(nvmeq->sq_dma_addr); 897 c.create_sq.sqid = cpu_to_le16(qid); 898 c.create_sq.qsize = cpu_to_le16(nvmeq->q_depth - 1); 899 c.create_sq.sq_flags = cpu_to_le16(flags); 900 c.create_sq.cqid = cpu_to_le16(qid); 901 902 status = nvme_submit_admin_cmd(dev, &c, NULL); 903 if (status) 904 return -EIO; 905 return 0; 906 } 907但是,還有一個問題,這個Admin Command是怎么傳過去的呢?還是要看NVMe Spec。之前提到的NVMe的BAR空間中就有這么兩個寄存器,它們用來存儲Admin Queue 的 Command DMA基地址。 如下,在創(chuàng)建Admin Queue的時候就向Controller寫入DMA地址:
1155 static int nvme_configure_admin_queue(struct nvme_dev *dev) 1156 {... 1182 writeq(nvmeq->sq_dma_addr, &dev->bar->asq); 1183 writeq(nvmeq->cq_dma_addr, &dev->bar->acq); 1184 writel(dev->ctrl_config, &dev->bar->cc);... 1200 }
總結(jié)
這篇文章主要講解了NVMe 通過DMA傳輸NVMe Command的機制,DMA并不是一項新技術(shù),在InfiniBand中也使用。NVMe的優(yōu)勢其實是DMA加上Multi-Queue,并且繞過了Linux Kernel龐大的Block層,下一篇文章將著重介紹NVMe是如何響應(yīng)I/O Request。
本文作者
張元元是Memblaze SSD事業(yè)部應(yīng)用工程師,研究方向涉及PCIe SSD在VSAN、Docker等環(huán)境中的應(yīng)用及優(yōu)化。對于服務(wù)器虛擬化、NVMe驅(qū)動的實現(xiàn)、Linux內(nèi)核及容器技術(shù)有深入的研究。本系列文章為張元元對于NVMe驅(qū)動及相關(guān)技術(shù)的全面解讀,更多張元元的文章請關(guān)注他的微信公眾號:yuan_memblaze
總結(jié)
以上是生活随笔為你收集整理的NVMe驱动解析-DMA传输的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: JAVA命令符找不到符号_[转]Java
- 下一篇: 记事本安卓软件代码设计_用轻量级工具 N