在之前,我撰寫了三篇有關NVMe的文章 ,分別是"原理NVM Express - NVMe Submission Queue & Completion Queue (SQ & CQ)"、"原理NVM Express - Admin Command Set"和"原理NVM Express - NVM Command Set",只要有了這三篇的基本知識,我們就有足夠的能力可以去解析我們主機板上任何一個M.2 NVMe device的一些資訊和如何操作它。接下來這篇文章會使用x86系統並安裝Ubuntu OS,然後隨意安裝市面上的一款M.2 NVMe SSD到主機板上,透過Linux command line的方式來解析此M.2 NVMe SSD的能力和如何透過I/O command去讀寫namespace,並且提供一些在Linux中非常實用的CLI。
■ Parsing PCIe Part
首先透過lspci來展列所有系統上的pcie device(如圖1所示),bus 01/ dev 00 /func 0(01:00.0)Non-Volatile memory controller為我們的目標device
Figure 1. List of PCIe device
接下來我們可以透過lspci的指令來dump device的PCIe configuration space(如圖2),offset 10h-17h的地方為bar0,bar0所指向的memory address為存放NVMe register的空間,可以看到圖中其值為0x00000000a0500004。接下來可以查看這個device的capability list,PCIe的capability list從offset 34h開始,然後如資料結構的linked list的方法一路往下串聯,如圖3所示,[7:0]為Capability ID,主要表示當前所指到的是哪一個Capability ,[15:8]為Next Capability Pointer,主要表示下一個Capability的offset是多少。由圖2中可以看到34h的值為0x80,意思就是第一個capability list在offset 80h的地方,然後80h的值為Capability ID 0x10,0x10代表是PCI Express Capability List Register,然後下一個capability在offset d0h的位置。offset d0h的Capability ID值為0x11,0x11為MSI-X Capability,以此類推。
Figure 2. PCIe Configuration Space
Figure 3. PCIe Capability List Register
如果上述lspci所dump的binary方式不好看的話,其實有另外一種方式可以讓開發者可以更快地去得知每個register的狀態,指令為lspci -s 01:00.0 -vvvv(如圖4)。
Figure 4. PCIe Configuration Space in more verbose way
■ Dump NVMe Registers
如上面所提到的bar0為存放NVMe register的地方,接下來我們用Linux tool "devmem2"來dump NVMe registers(如圖5所示)。
Figure 5. PCIe Capability List Register
首先Offset 00h-07h的位置為Controller Capabilities register,其值為0x30400303FF,底下介紹此controller所support的能力有哪些:
- [15:0] Maximum Queue Entries Supported的值為0x3FF,代表這個controller可以支援queue的深度為何,此controller最多可以處理1024筆command。
- [16] Contiguous Queues Required值為1,表示host在create I/O SQ 和 CQ的時候,必須提供物理連續的memory。
- [17] Arbitration Mechanism Supported為1,表示此controller處理command的時候會以Weighted Round Robin的仲裁方式。
- [31:24] Timeout為0x40,表示建議host等待command完成的最大時間,單位為500ms,所以為0x40 x 500 = 32s。
- [36] NVM Subsystem Reset Supported為1,表示support NSSR reset。
- [44:37] Command Sets Supported為1,表示support NVM command set。
Offset 08h-0Bh為Version register,其值為0x10400,表示此controller相容於1.4的NVMe spec。
Offset 14h-17h為Controller Configuration,其值為0x460001,bit0為Enable bit,為1表示現在controller為應該要可以處理command的狀態,但還是得等到CSTS.RDY bit為1後host才可以開始提交command。bit [19:16] I/O Submission Queue Entry Size,表示SQ一個entry為多少bytes,0x6代表2^6 = 64 bytes per command entry。bit [23:20] I/O Completion Queue Entry Size表示CQ一個entry為多少bytes,0x4代表2^4= 16 bytes per completion entry。
Offset 1Ch-1Fh為Controller Status,bit [0] 用來表示controller現在可以開始接收command,主要會在host下CC.EN的時候才會轉變為1。
Offset 24h-27h為Admin Queue Attributes,其值為0x001F001F,表示Admin Queue的max entry為何,此controller的Admin SQ & CQ都為32個entries。
Offset 28h-2Fh為Admin Submission Queue Base Address,其值為0x3F204B000,表示host提交admin command的memory位置為何。
Offset 30h-37h為Admin Completion Queue Base Address,其值為0x3F204C000,表示controller回覆command completion的memory位置為何。
■ Dump Admin SQ(ASQ) base address
由於上述我們已得知Admin SQ(ASQ) base address位置且也知道ASQ一個entry為64 bytes,我們可以透過devmem2來dump ASQ的memory(如圖6),由圖中可以得知host提交到第一個entry的command為Opcode 02h,也就是Get Log Page command,其中NSID欄位為broadcast(0xFFFFFFFF),Log Page ID欄位為0x2,詳細的格式可以參考"原理NVM Express - Admin Command Set"這篇文章。 Figure 6. Admin Submission Queue Memory Space
■ Dump Admin CQ(ACQ) base address
Figure 7. Admin Completion Queue Memory Space
■ NVME CLI
在host OS之中,除了NVMe driver會提交command之外,還有一個tool提供開發者可以手動提交command,那就是NVMe CLI,由於我的系統是Ubuntu,可以透過sudo apt-get install nvme-cli來安裝。安裝完之後可以透過nvme --help得到此CLI的使用說明(如圖8)。
在使用nvme cli之前,我們可以透過ls /dev/nvme*來得知nvme的裝置名稱,如圖9所示,/dev/nvme0為controller,/dev/nvme0n1為namespace,如之前所撰寫的文章"原理NVM Express - NVM Command Set"有提到,namespace為LBA的集合,當namespace attach到controller之後,我們才能透過controller下I/O cmd來讀寫namespace,此範例為nvme0n1 attach到nvme0的情況。
Figure 9. List NVMe device
在之前所撰寫"原理NVM Express - Admin Command Set"文章裡有提到兩個很重要的data structure,分別是Identify Controller Data Structure (ICDS)和Identify Namespace Data Structure (INDS),ICDS主要是描述controller的一些資訊及能力,INDS主要是為了回報給host此namespace的一些資訊及能力。
ICDS可以使用nvme id-ctrl /dev/nvme0來對controller提交指令,其詳細內容如下,主要挑幾個欄位來解析:
- mdts欄位為6,表示Maximum Data Transfer Size為4096 x (2^6) = 262,144 bytes。
- oacs欄位為 0x17,可以得知此controller support Security Send和Security Receive、Format NVM、Firmware Commit and Firmware Image Download和Device Self-test command。
- npss欄位為4,表示power state有PS1~PS4。
- oncs欄位為0x5f,表示support Compare、Write Uncorrectable、Dataset Management和Write Zeroes command
root@i:~# nvme id-ctrl
/dev/nvme0
NVME Identify Controller: vid : 0x1987 ssvid : 0x1987 sn : TPBF2201280010102442 mn : T-FORCE TM8FPL500G fr : EJFM32.0 rab : 4 ieee : 6479a7 cmic : 0 mdts : 6 cntlid : 0 ver : 10400 rtd3r : 7a120 rtd3e : 4c4b40 oaes : 0x200 ctratt : 0x2 oacs : 0x17 acl : 3 aerl : 3 frmw : 0x12 lpa : 0x1e elpe : 62 npss : 4 avscc : 0x1 apsta : 0x1 wctemp : 357 cctemp : 361 mtfa : 100 hmpre : 16384 hmmin : 16384 tnvmcap : 0 unvmcap : 0 rpmbs : 0 edstt : 30 dsto : 0 fwug : 4 kas : 0 hctma : 0x1 mntmt : 273 mxtmt : 356 sanicap : 0xa0000002 hmminds : 1024 hmmaxd : 16 sqes : 0x66 cqes : 0x44 maxcmd : 256 nn : 1 oncs : 0x5f fuses : 0 fna : 0 vwc : 0x7 awun : 255 awupf : 0 nvscc : 1 acwu : 0 sgls : 0 subnqn :
nqn.2020-11.com.phison:nvme:PS5019:TPBF2201280010102442 ioccsz : 0 iorcsz : 0 icdoff : 0 ctrattr : 0 msdbd : 0 ps 0 : mp:5.00W operational enlat:0 exlat:0
rrt:0 rrl:0 rwt:0 rwl:0 idle_power:-
active_power:- ps 1 : mp:2.40W operational enlat:0 exlat:0
rrt:1 rrl:1 rwt:1 rwl:1 idle_power:-
active_power:- ps 2 : mp:1.92W operational enlat:0 exlat:0
rrt:2 rrl:2 rwt:2 rwl:2 idle_power:-
active_power:- ps 3 : mp:0.0700W non-operational enlat:1000
exlat:1000 rrt:3 rrl:3 rwt:3 rwl:3 idle_power:-
active_power:- ps 4 : mp:0.0050W non-operational
enlat:10000 exlat:40000 rrt:4 rrl:4 rwt:4 rwl:4 idle_power:-
active_power:-
|
INDS可以透過nvme id-ns /dev/nvme0 -n 0x1來對controller提交指令,其詳細內容如下,主要挑幾個欄位來解析:
- nsze欄位為0x3a386030,其值單位為sector,一個sector為512 bytes,所以其容量為0x3a386030 x 512 = 500,107,862,016 bytes = 500 GB
- nlbaf欄位為1,表示額外support 1種 LBA format,然後format資訊紀錄在最下面"lbaf 1"的位置,透過lbads:12可以得知此format的LBA為4096 bytes per sector。
- flbas欄位為0,表示目前使用的是LBA format 0。
root@i:~# nvme id-ns /dev/nvme0 -n 0x1 NVME Identify Namespace 1: nsze : 0x3a386030 ncap : 0x3a386030 nuse : 0x3a386030 nsfeat : 0 nlbaf : 1 flbas : 0 mc : 0 dpc : 0 dps : 0 nmic : 0 rescap : 0 fpi : 0 nawun : 0 nawupf : 0 nacwu : 0 nabsn : 0 nabo : 0 nabspf : 0 noiob : 0 nvmcap : 0 nguid : 00000000000000016479a7d9d02028bb eui64 : 6479a75dd02028bb lbaf 0 : ms:0 lbads:9 rp:0x1 (in use) lbaf 1 : ms:0 lbads:12 rp:0 |
然後透過"nvme smart-log /dev/nvme0 -o normal"來取得SMART / Health Information Log Page,其內容如下:
root@i:~# nvme smart-log /dev/nvme0 -o normal Smart Log for NVME device:nvme0 namespace-id:ffffffff critical_warning : 0 temperature : 36 C available_spare : 100% available_spare_threshold : 5% percentage_used : 0% data_units_read : 21,8955 data_units_written : 19,7982 host_read_commands : 155,2872 host_write_commands : 96,1765 controller_busy_time : 26 power_cycles : 19 power_on_hours : 1013 unsafe_shutdowns : 5 media_errors : 0 num_err_log_entries : 0 Warning Temperature Time : 0 Critical Composite Temperature Time : 0 Thermal Management T1 Trans Count : 0 Thermal Management T2 Trans Count : 0 Thermal Management T1 Total Time : 0 Thermal Management T2 Total Time : 0 |
■ NVME CLI - I/O Command
而nvme cli不只可以提交admin command,且也可以提交I/O command,如以下圖10範例,透過nvme cli對namespace nvme0n1下read command,SLBA(-s)從0x0開始,然後NLB(-c)為15,也就是16個sector,16個sector相當於8192 bytes(-z),"-d"則是將讀出的資料寫為任意檔名的檔案,由圖中可以看出目前的資料是沒有規則的。
接下來如圖11,我們透過write command來覆寫剛剛的SLBA和NLB所指定的LBA range,一樣指定namespace nvme0n1,SLBA 0x0、NLB 15和data size 8192 bytes,write command的傳入參數"-d"則是我們自己帶入想寫入的資料,所以我自己產生了一個全為0x5a的8k.bin的檔案,帶入write command之中,當回報Success代表8192 bytes的0x5a pattern已全數寫入nand之中,接著我們再透過read command將資料讀出來,則時候可以發現所指定的LBA range,資料已經全部變成0x5a。
由於Maximum Data Transfer Size(MDTS)為 262,144 bytes,因此我們可以透過command來測試此上限,如圖12所示,當我們上限設為262,144的時候,command可以正常執行,當超過262,144的時候,則會回報Invalid argument
Figure 12. MDTS limitation
您好
回覆刪除感謝大大分享好文,不過在文章中
[44:37] Command Sets Supported為1,表示support NVM command set。=> 是否有誤正確因該為[39:37]
我看了一下nvme spec 1.4c , 是bit [44:37]沒錯,請問你是看哪一個部分?
刪除大大您好
回覆刪除另外想請問dumpMem.sh 這一個執行檔,可以從哪裡調用,我安裝devmem2之後,好像也是不能像圖上這樣發送commands。
dumpMem.sh是我自己寫的shell script,只是用devmem2 去dump一段區間的memory而已,你需要可以提供信箱,我寄給你
刪除我的信箱如下,謝謝您的回覆。
刪除josh.lin70@gmail.com
大大你好, 請問你知道有甚麼tool可以手動切換D0~D3 State和查看L0~L1的當前狀態嗎?謝謝
回覆刪除D state的話可以用lspci -s xx:xx.x -vvvv 找出Power Management的capability offset,以下面
刪除為範例就是offset e0h的位置,然後+4(e4h)就是Power Management Control/Status Register,在bit[1:0]寫入你要的power state就可以了,寫入的方法用setpci -s xx:xx.x e4.b=3
至於L state好像沒辦法直接控制,好像也沒有register status可以看
範例:
Capabilities: [e0] Power Management version 3
Flags: PMEClk- DSI- D1- D2- AuxCurrent=0mA PME(D0+,D1-,D2-,D3hot+,D3cold+)
Status: D0 NoSoftRst+ PME-Enable- DSel=0 DScale=0 PME-
感謝您的回覆, 我在來試試看
刪除大大~請教一下, nvme cli可以設定CSTS的bit嗎?我查了一下查不到.
刪除CSTS想要寫的話可能要透過devmem2, bar0的位置就是指到nvme registers,上面例子是0xa0500000,CSTS在offset 0x1C-0x1F,所以指令就是$devmem2 0xa050001C w "data in hex"
刪除謝謝您的回覆, 另外可以跟您索取dumpMem.sh嗎?devmem2讀取起來沒有您寫的sh方便Orz
刪除如果可以的話麻煩您寄到wa741963@gmail.com
謝謝