X540のMSI-X設定

実際のデバイスMSI-Xの設定の確認をしてみます. 今手元にX540があるので,それで実際に確認してみます.

文献

X540の仕様は以下から入手できます.

7.3章にX540におけるMSI-Xについて書いてあります.また,9.3章にPCIeのConfiguration Spaceの構造が書いてあります.

Configuration Space及びMSI-X Tableの確認

lspci -vvvとすると,デバイスのConfiguration Spaceの情報を表示してくれます(Capabilityを表示するには,管理者権限が必要です).

% sudo lspci -vvv -s 04:00.0
04:00.0 Ethernet controller: Intel Corporation Ethernet Controller 10-Gigabit X540-AT2 (rev 01)
        Subsystem: Intel Corporation Ethernet Converged Network Adapter X540-T2
        Control: I/O- Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR- FastB2B- DisINTx+
        Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
        Latency: 0, Cache Line Size: 64 bytes
        Interrupt: pin B routed to IRQ 17
        Region 0: Memory at f0200000 (64-bit, prefetchable) [size=2M]
        Region 4: Memory at f0404000 (64-bit, prefetchable) [size=16K]
        Expansion ROM at f0480000 [disabled] [size=512K]
        Capabilities: [40] 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=1 PME-
        Capabilities: [50] MSI: Enable- Count=1/1 Maskable+ 64bit+
                Address: 0000000000000000  Data: 0000
                Masking: 00000000  Pending: 00000000
        Capabilities: [70] MSI-X: Enable+ Count=64 Masked-
                Vector table: BAR=4 offset=00000000
                PBA: BAR=4 offset=00002000
... (省略) ...

この結果より,X540はMSIMSI-Xともに対応しており,またMSI-Xが有効(Enable+)になっていることが分かります.Count=64なので,デバイスには64個の割り込みが登録されています.またMSI-X TableはBAR 4のoffset 0から,PBAはBAR 4 のoffset 02000hからあることが分かります.BAR 4のアドレスは,Region 4: Memory at f0404000のアドレスになります.

lspci -xxx を利用すると,configuration spaceのダンプが見れるので,こちらも一応確認してみます.

% sudo lspci -xxx -s 04:00.0
04:00.0 Ethernet controller: Intel Corporation Ethernet Controller 10-Gigabit X540-AT2 (rev 01)
00: 86 80 28 15 06 04 10 00 01 00 00 02 10 00 80 00
10: 0c 00 20 f0 00 00 00 00 00 00 00 00 00 00 00 00
20: 0c 40 40 f0 00 00 00 00 00 00 00 00 86 80 01 00
30: 00 00 88 f7 40 00 00 00 00 00 00 00 0a 02 00 00
40: 01 50 23 48 00 20 00 00 00 00 00 00 00 00 00 00
50: 05 70 80 01 00 00 00 00 00 00 00 00 00 00 00 00
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
70: 11 a0 3f 80 04 00 00 00 04 20 00 00 00 00 00 00
80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
a0: 10 00 02 00 c2 8c 00 10 2f 28 09 00 82 fc 42 10
b0: 00 00 82 10 00 00 00 00 00 00 00 00 00 00 00 00
c0: 00 00 00 00 1f 00 00 00 00 00 00 00 00 00 00 00
d0: 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

まず,Configuration Spaceの0x34がCapability Pointerで,その部分は40です(ちなみに,Configuration Spaceはリトルエンディアンです).そこで,40h番地を見てみるとCapability IDが01h, Next Cap Ptrが50hです.Capability ID 01hはMSIでもMSI-Xでもないので,次に50h番地を見てみると,Capability IDが05h, Next Cap Ptrが70hです.Capability IDが05hなので,ここがMSIのCapabilityに相当します.また,次の70h番地を見てみると,Capability IDが11hで,ここがMSI-XのCapabilityです.この領域をよくみると,MSI-X Table BIRとMSI-X PBA BIRが4で,またTable Offsetが0,PBA Offsetが2000hであることが確認できます.

PCIバイスのBAR領域にアクセスするには,/sys/bus/pci/devices/<bus>:<device>:<num>:.<function num>/resource<BAR番号> のファイルをmmapすればアクセスすることができます.そのためのソフトウェアとして,pcimemがあります.pcimemを利用してX540のBAR4の領域にアクセスしてみると以下のようになりました.

% for j in `seq 0 1 64`; do; for i in `seq $((($j+1)*16-4)) -4 $(($j*16))`; sudo ./pcimem  /sys/bus/pci/devices/0000:04:00.0/resource4 $i w | tail -n1 | cut -d':' -f 2 | tr '\n' ' '; echo ; done
 0x0  0x41A2  0x0  0xFEE8000C
 0x0  0x41B2  0x0  0xFEE0400C
 0x0  0x41C2  0x0  0xFEE4000C
 0x0  0x41D2  0x0  0xFEE8000C
 0x0  0x41E2  0x0  0xFEE4000C
 0x0  0x4123  0x0  0xFEE1000C
 0x0  0x4143  0x0  0xFEE4000C
 0x0  0x4153  0x0  0xFEE1000C
 0x0  0x4163  0x0  0xFEEFF00C
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0x1  0x0  0x0  0x0
 0xDEADBEAF  0xDEADBEAF  0xDEADBEAF  0xDEADBEAF

これは各行につきMSI-X Tableのエントリ一つで,左からControl Vector, Message Data Register, Message Upper Address, Message Addressです.まず,今回Configuration Spaceで設定されたエントリ数は64でしたが,実際には9つのエントリしか有効になっていないことが分かります(Control Vector=1の場合,その割り込みはマスクされる).また,65番目以降のエントリにアクセスすると0xDEADBEAFが返ってきます(これは仕様です).ixgbeのドライバでは基本的にコア数分だけキューを用意して,割り込みはキュー間で分散させるよう設定するのだと思います(このあたり,また後で詳しく調べたい).

Message Address Registerを見てみると,下位4bitがC0であることから,RH=1, DM=1であることが分かります.Data Registerの値は下位8bitの割り込みベクタ番号のみが違い,割り込みベクタはそれぞれA2h, B2h, C2h, D2h, E2h, 23h, 43h, 53h, 63hとなっています.Local APICでは割り込みベクタの上位4bitで割り込みの優先度が決まりますが,それぞれの割り込みで同じ優先度にならないように割り込みベクタを割り振っているようです.

割り込みベクタ番号と,linuxカーネル内で利用されるirq番号の対応はvector_irq という名のstruct irq_desc*の配列に入っています.以下のようなカーネルモジュールでベクタ番号とirq番号の確認ができます(vector_irqEXPORT_SYMBOL()されていないので,kallsyms_lookup_nameを使う必要があります).

int hello_init(){
    vector_irq_t* vector_irq = (vector_irq_t*)kallsyms_lookup_name("vector_irq");
    int vector_num[] = {0xA2, 0xB2, 0xC2, 0xD2, 0xE2, 0x23, 0x43, 0x53, 0x63};
    int i = 0;
    for (i = 0; i < 9; i++) {
        struct irq_desc* desc = __this_cpu_read((*vector_irq)[vector_num[i]]);
        if (IS_ERR_OR_NULL(desc)){
            continue;
        }
        struct irq_data* data = irq_desc_get_irq_data(desc);
        pr_info("%x : %d\n", vector_num[i], data->irq);
    }

    return 0;
}

実行結果:

% sudo insmod hello5.ko && sudo rmmod hello5 && dmesg | tail -n9
[91213.998593] a2 : 33
[91213.999377] b2 : 34
[91214.000155] c2 : 35
[91214.000933] d2 : 36
[91214.001710] e2 : 37
[91214.002555] 23 : 38
[91214.003359] 43 : 39
[91214.004136] 53 : 40
[91234.077288] 63 : 41

これより,各割り込みはこの環境ではirq番号33-41に対応していることが分かります.

割り込み状況の確認

/proc/interruptsで割り込み状況を確認してみます.

% cat /proc/interrupts
           CPU0       CPU1       CPU2       CPU3       CPU4       CPU5       CPU6       CPU7
...
 33:         52       1158          0          0          0          0          0      44844   PCI-MSI 2097152-edge      enp4s0f0-TxRx-0
 34:          1         45      45965          0          0          0          0          0   PCI-MSI 2097153-edge      enp4s0f0-TxRx-1
 35:          1          0         45          0          0          0      46211          0   PCI-MSI 2097154-edge      enp4s0f0-TxRx-2
 36:          1          0          0         45          0          0          0      45965   PCI-MSI 2097155-edge      enp4s0f0-TxRx-3
 37:          0       1150          0          1         45          0      44872          0   PCI-MSI 2097156-edge      enp4s0f0-TxRx-4
 38:          0       1145          0          0      44816         51          0          0   PCI-MSI 2097157-edge      enp4s0f0-TxRx-5
 39:          1          0          0          0          0          0      46066          0   PCI-MSI 2097158-edge      enp4s0f0-TxRx-6
 40:          0          0          0          0      45969          0          0         45   PCI-MSI 2097159-edge      enp4s0f0-TxRx-7
 41:          1          0          0          0          0          0          0          0   PCI-MSI 2097160-edge      enp4s0f0
...

irqのsmp_affinityを確認すると,以下のようになっています.

% for i in `seq 33 1 41`; cat /proc/irq/$i/smp_affinity
80
04
40
80
40
10
40
10
08

この値をよく見てみると,ベクタ番号41を除き,smp_affinityの値はMSI Address Registerの12-19bit目と一致していることが分かります.また,それぞれのビットが立っている位置とCPU番号を対応付けて /proc/interrupts を見てみると,そのビットが立っているCPUに割り込みが多く入っていることが分かります.ということは,今現在MSI-Xのlogical apic IDはflat modeで運用されていると予想されます.flat modeかどうかはLocal APICDestination Fromat Register (DFR)の値によります.また,Logical APIC IDはLogical Destination Register (LDR)に格納されています.LDRは0fee000d0h, FDRは0fee000e0h 番地にmemory-mappedされています.

以下のようなカーネルモジュールを書いて確認してみます(CPUはi7 3770Kです).

int hello_init(){
    void *addr = ioremap(0xfee000D0, 32);
    pr_info("LDR,DFR : %08x, %08x \n", readl(addr), readl(addr+16));
    iounmap(addr);
    return 0;
}

実行結果:

% for i in `seq 0 1 7`; sudo taskset -c $i insmod hello.ko && dmesg | tail -n1 && sudo rmmod hello
[81324.591351] LDR,DFR : 01000000, ffffffff
[81324.651546] LDR,DFR : 02000000, ffffffff
[81324.702683] LDR,DFR : 04000000, ffffffff
[81324.758692] LDR,DFR : 08000000, ffffffff
[81324.802557] LDR,DFR : 10000000, ffffffff
[81324.855086] LDR,DFR : 20000000, ffffffff
[81324.905689] LDR,DFR : 40000000, ffffffff
[81324.945385] LDR,DFR : 80000000, ffffffff

DFRの31-28bit目が全て1なので,これはfalt modelです.また,各CPUコアごとに異なるビットがLogical APIC IDとして振られていることが分かります.この結果は/proc/interrupts の結果と矛盾しません(なんでデフォルトでMSI-Xの各エントリが各コアに対応していないのかはよく分かりませんが..).

smp_affintyを変化させた場合

smp_affinityを変更して各キューに一つのCPUが対応するようにしてみます.

% for i in `seq 0 1 7`;  echo "obase=16; $((1<<i))" | bc | sudo tee /proc/irq/$((33+i))/smp_affinity

ちゃんと変更されちるかsmp_affinityを確認してみます.

% for i in `seq 0 1 8`; cat /proc/irq/$((33+i))/smp_affinity
01
02
04
08
10
20
40
80

で,これで割り込みがコアごとに分散されるかと思ったのですが,しばらく/proc/interrupts観測を続けても特に変化なし..

おかしいと思ってMSI-X Tableを確認してみると,何も変わっていませんでした.

% for j in `seq 0 1 8`; do; for i in `seq $((($j+1)*16-4)) -4 $(($j*16))`; sudo ./pcimem  /sys/bus/pci/devices/0000:04:00.1/resource4 $i w | tail -n1 | cut -d':' -f 2 | tr '\n' ' '; echo ; done
 0x0  0x4183  0x0  0xFEE1000C
 0x0  0x4193  0x0  0xFEE4000C
 0x0  0x41A3  0x0  0xFEE0400C
 0x0  0x41B3  0x0  0xFEE0800C
 0x0  0x41C3  0x0  0xFEE1000C
 0x0  0x41D3  0x0  0xFEE4000C
 0x0  0x41E3  0x0  0xFEE0400C
 0x0  0x4124  0x0  0xFEE8000C
 0x0  0x4144  0x0  0xFEEFF00C

何が原因なのか分かりませんが,仕方ないので直接MSI-X TableのAddress RegisterのDestination Fieldを書き換えてみます.

% for i in `seq 0 1 7`; sudo ./pcimem /sys/bus/pci/devices/0000:04:00.1/resource4 $((i*16)) w "0xFEE"`echo "$((1<<i))"| awk '{printf "%02x",$1}'`"00C"

以下のように書き換わりました.

% for j in `seq 0 1 8`; do; for i in `seq $((($j+1)*16-4)) -4 $(($j*16))`; sudo ./pcimem  /sys/bus/pci/devices/0000:04:00.1/resource4 $i w | tail -n1 | cut -d':' -f 2 | tr '\n' ' '; echo ; done
 0x0  0x4183  0x0  0xFEE0100C
 0x0  0x4193  0x0  0xFEE0200C
 0x0  0x41A3  0x0  0xFEE0400C
 0x0  0x41B3  0x0  0xFEE0800C
 0x0  0x41C3  0x0  0xFEE1000C
 0x0  0x41D3  0x0  0xFEE2000C
 0x0  0x41E3  0x0  0xFEE4000C
 0x0  0x4124  0x0  0xFEE8000C
 0x0  0x4144  0x0  0xFEEFF00C

これで,割り込みがコアごとに分散されるはずです. しばらくpingを動かしてみると,以下のようになりました.

 33:       1130       1158          0          0          0          0          0      45552   PCI-MSI 2097152-edge      enp4s0f0-TxRx-0
 34:          1       1102      46673          0          0          0          0          0   PCI-MSI 2097153-edge      enp4s0f0-TxRx-1
 35:          1          0       1109          0          0          0      46923          0   PCI-MSI 2097154-edge      enp4s0f0-TxRx-2
 36:          1          0          0       1102          0          0          0      46673   PCI-MSI 2097155-edge      enp4s0f0-TxRx-3
 37:          0       1150          0          1       1805          0      45580          0   PCI-MSI 2097156-edge      enp4s0f0-TxRx-4
 38:          0       1145          0          0      45524       1108          0          0   PCI-MSI 2097157-edge      enp4s0f0-TxRx-5
 39:          1          0          0          0          0          0      47832          0   PCI-MSI 2097158-edge      enp4s0f0-TxRx-6
 40:          0          0          0          0      46677          0          0       1102   PCI-MSI 2097159-edge      enp4s0f0-TxRx-7
 41:          1          0          0          0          0          0          0          0   PCI-MSI 2097160-edge      enp4s0f0

数分後:

 33:       1343       1158          0          0          0          0          0      45552   PCI-MSI 2097152-edge      enp4s0f0-TxRx-0
 34:          1       1314      46673          0          0          0          0          0   PCI-MSI 2097153-edge      enp4s0f0-TxRx-1
 35:          1          0       1323          0          0          0      46923          0   PCI-MSI 2097154-edge      enp4s0f0-TxRx-2
 36:          1          0          0       1314          0          0          0      46673   PCI-MSI 2097155-edge      enp4s0f0-TxRx-3
 37:          0       1150          0          1       2017          0      45580          0   PCI-MSI 2097156-edge      enp4s0f0-TxRx-4
 38:          0       1145          0          0      45524       1320          0          0   PCI-MSI 2097157-edge      enp4s0f0-TxRx-5
 39:          1          0          0          0          0          0      48044          0   PCI-MSI 2097158-edge      enp4s0f0-TxRx-6
 40:          0          0          0          0      46677          0          0       1314   PCI-MSI 2097159-edge      enp4s0f0-TxRx-7
 41:          1          0          0          0          0          0          0          0   PCI-MSI 2097160-edge      enp4s0f0

この二つの差分をとると,以下のようになります.

33: 213        0        0        0        0        0        0        0
34:   0      212        0        0        0        0        0        0
35:   0        0      214        0        0        0        0        0
36:   0        0        0      212        0        0        0        0
37:   0        0        0        0      212        0        0        0
38:   0        0        0        0        0      212        0        0
39:   0        0        0        0        0        0      212        0
40:   0        0        0        0        0        0        0        212

目標通り,割り込みが分散されていることが確認できます.

smp_affinityを変更してもAddress Registerが書き変わらないのはixgbeドライバの 問題でしょうか.要調査ですね..