Про pcie в линукс

Мои познания о том как работает pcie на примере инициализации видеокарты.

Пример типичного лога инициализации pcie при загрузке ядра linux (в данном случае для платформы 5040ds be qoriq от freescale).

/pcie@ffe200000: PCICSRBAR @ 0xdf000000 
/pcie@ffe200000: Setup 64-bit PCI DMA window 
/pcie@ffe200000: DMA window size is 0xdf000000 
Found FSL PCI host bridge at 0x0000000ffe201000. Firmware bus number: 0->0 
PCI host bridge /pcie@ffe201000  ranges: 
 MEM 0x0000000c20000000..0x0000000c3fffffff -> 0x00000000e0000000 
  IO 0x0000000ff8010000..0x0000000ff801ffff -> 0x0000000000000000 
/pcie@ffe201000: PCICSRBAR @ 0xdf000000 
/pcie@ffe201000: Setup 64-bit PCI DMA window 
/pcie@ffe201000: DMA window size is 0xdf000000 
software IO TLB [mem 0x0bdce000-0x0fdce000] (64MB) mapped at [c00000000bdce000-c00000000fdcdfff] 
PCI: Probing PCI hardware 
fsl-pci ffe200000.pcie: PCI host bridge to bus 0000:00 
pci_bus 0000:00: root bus resource [io  0x10000-0x1ffff] (bus address [0x0000-0xffff]) 
pci_bus 0000:00: root bus resource [mem 0xc00000000-0xc1fffffff] (bus address [0xe0000000-0xffffffff]) 
pci_bus 0000:00: root bus resource [bus 00-01] 
pci_bus 0000:00: busn_res: [bus 00-01] end is updated to ff 
pci 0000:00:00.0: [1957:0450] type 01 class 0x0b2000 
pci 0000:00:00.0: ignoring class 0x0b2000 (doesn't match header type 01) 
pci 0000:00:00.0: supports D1 D2 
pci 0000:00:00.0: PME# supported from D0 D1 D2 D3hot D3cold 
pci 0000:01:00.0: [10de:0141] type 00 class 0x030000 
pci 0000:01:00.0: reg 10: [mem 0xc00000000-0xc00ffffff] 
pci 0000:01:00.0: reg 14: [mem 0xc10000000-0xc1fffffff 64bit pref] 
pci 0000:01:00.0: reg 1c: [mem 0x1000ff000000-0x1000ffffffff 64bit] 
pci 0000:01:00.0: reg 30: [mem 0x00000000-0x0001ffff pref] 
pci 0000:01:00.0: disabling ASPM on pre-1.1 PCIe device.  You can enable it with 'pcie_aspm=force' 
pci 0000:00:00.0: PCI bridge to [bus 01-ff] 
pci 0000:00:00.0:   bridge window [io  0x11000-0x11fff] 
pci 0000:00:00.0:   bridge window [mem 0xc00000000-0xc1fffffff] 
pci_bus 0000:01: busn_res: [bus 01-ff] end is updated to 01 
pci_bus 0000:00: busn_res: [bus 00-ff] end is updated to 01 
fsl-pci ffe201000.pcie: PCI host bridge to bus 0001:00 
pci_bus 0001:00: root bus resource [io  0x21000-0x30fff] (bus address [0x0000-0xffff]) 
pci_bus 0001:00: root bus resource [mem 0xc20000000-0xc3fffffff] (bus address [0xe0000000-0xffffffff]) 
pci_bus 0001:00: root bus resource [bus 00] 
pci_bus 0001:00: busn_res: [bus 00] end is updated to ff 
pci 0001:00:00.0: [1957:0450] type 01 class 0x0b2000 
pci 0001:00:00.0: ignoring class 0x0b2000 (doesn't match header type 01) 
pci 0001:00:00.0: supports D1 D2 
pci 0001:00:00.0: PME# supported from D0 D1 D2 D3hot D3cold 
pci 0001:00:00.0: bridge configuration invalid ([bus 00-00]), reconfiguring 
pci 0001:00:00.0: PCI bridge to [bus 01-ff] 
pci 0001:00:00.0:   bridge window [io  0x21000-0x21fff] 
pci 0001:00:00.0:   bridge window [mem 0x00000000-0x000fffff] 
pci 0001:00:00.0:   bridge window [mem 0x00000000-0x000fffff 64bit pref] 
pci_bus 0001:01: busn_res: [bus 01-ff] end is updated to 01 
pci_bus 0001:00: busn_res: [bus 00-ff] end is updated to 01 
PCI: Cannot allocate resource region 3 of device 0000:01:00.0, will remap 
PCI 0000:00 Cannot reserve Legacy IO [io  0x10000-0x10fff] 
PCI 0001:00 Cannot reserve Legacy IO [io  0x21000-0x21fff] 
pci 0000:00:00.0: BAR 9: can't assign mem pref (size 0x100000) 
pci 0000:01:00.0: BAR 3: assigned [mem 0xc01000000-0xc01ffffff 64bit] 
pci 0000:01:00.0: BAR 6: assigned [mem 0xc02000000-0xc0201ffff pref] 
pci 0000:00:00.0: PCI bridge to [bus 01] 
pci 0000:00:00.0:   bridge window [io  0x10000-0x1ffff] 
pci 0000:00:00.0:   bridge window [mem 0xc00000000-0xc1fffffff] 
pci 0001:00:00.0: PCI bridge to [bus 01] 
pci 0001:00:00.0:   bridge window [io  0x21000-0x30fff] 
pci 0001:00:00.0:   bridge window [mem 0xc20000000-0xc3fffffff] 
pci_bus 0000:00: resource 4 [io  0x10000-0x1ffff] 
pci_bus 0000:00: resource 5 [mem 0xc00000000-0xc1fffffff] 
pci_bus 0000:01: resource 0 [io  0x10000-0x1ffff] 
pci_bus 0000:01: resource 1 [mem 0xc00000000-0xc1fffffff] 
pci_bus 0001:00: resource 4 [io  0x21000-0x30fff] 
pci_bus 0001:00: resource 5 [mem 0xc20000000-0xc3fffffff] 
pci_bus 0001:01: resource 0 [io  0x21000-0x30fff] 
pci_bus 0001:01: resource 1 [mem 0xc20000000-0xc3fffffff]

В этом логе важными местом является вывод с момента обнаружения видеокарты:

pci 0000:01:00.0: [10de:0141] type 00 class 0x030000 

После этого идет алгоритм инициализации BAR регистров pcie-устройства. Очень классная дока по этой теме по ссылке: resources.in fosecinstitute.com/system-address-map-initialization-in-x86x64-architecture-part-1-pci-based-systems/

В юбуте алгоритм инициализации (описанный в доке по ссылке) например выглядит вот так:

в файле corenet_ds.h юбута есть вот такие дефайны:

define CONFIG_SYS_PCIE1_MEM_SIZE	0x20000000	/* 512M */
#define CONFIG_SYS_PCIE1_IO_VIRT	0xf8000000
#define CONFIG_SYS_PCIE1_IO_BUS		0x00000000

В файле pci_auto.c:

	pci_hose_read_config_word(hose, dev, PCI_COMMAND, &cmdstat);
	cmdstat = (cmdstat & ~(PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) | PCI_COMMAND_MASTER;

	for (bar = PCI_BASE_ADDRESS_0;
		bar < PCI_BASE_ADDRESS_0 + (bars_num * 4); bar += 4) {


		/* Tickle the BAR and get the response */
#ifndef CONFIG_PCI_ENUM_ONLY
		pci_hose_write_config_dword(hose, dev, bar, 0xffffffff);
		debugPrintAllBarValue(__FUNCTION__,__LINE__,hose);
#endif
		pci_hose_read_config_dword(hose, dev, bar, &bar_response);

		/* If BAR is not implemented go to the next BAR */
		if (!bar_response)
			continue;

1. записывается значение 0xffffffff в регистр bar

2. считывает значение из этого же регистра.

В ядре этот же алгоритм находится в функции __pci_read_base (файл probe.c) и выглядит как то так:

      pci_read_config_dword(dev, pos, &l); 
        pci_write_config_dword(dev, pos, l | mask); 
        pci_read_config_dword(dev, pos, &sz); 
        pci_write_config_dword(dev, pos, l); 

        /* 
         * All bits set in sz means the device isn't working properly. 
         * If the BAR isn't implemented, all bits must be 0.  If it's a 
         * memory BAR or a ROM, bit 0 must be clear; if it's an io BAR, bit 
         * 1 must be clear. 
         */ 
        if (!sz || sz == 0xffffffff) 
                goto fail; 

В результате этого алгоритма распознается сколько памяти нужно отразить в адресном пространстве cpu для конкретного pci-устройтва. При этом очень важной частью в логе являются значения напрямую связанные с содержимым регистров BAR:

pci 0000:01:00.0: reg 10: [mem 0xc00000000-0xc00ffffff] 
pci 0000:01:00.0: reg 14: [mem 0xc10000000-0xc1fffffff 64bit pref] 
pci 0000:01:00.0: reg 1c: [mem 0x1000ff000000-0x1000ffffffff 64bit] 
pci 0000:01:00.0: reg 30: [mem 0x00000000-0x0001ffff pref] 

Данные значения адресов для разных платформ куда будет подключено это pcie устройство может отличаться, но они всегда будут отражать размер памяти который должен быть выделен в адресном пространстве для данного устройства. Так например BAR1(reg 14) в данном случае содержит информацию о 256мбт:

0xc1fffffff−0xc10000000 +1 = 0x10000000;
0x10000000 / 0x400 / 0x400 = 256МБт

Также нужно обращать на сообщения напрямую связанные с инициализацией BAR

pci 0000:00:00.0: BAR 9: can't assign mem pref (size 0x100000) 
pci 0000:01:00.0: BAR 3: assigned [mem 0xc01000000-0xc01ffffff 64bit] 
pci 0000:01:00.0: BAR 6: assigned [mem 0xc02000000-0xc0201ffff pref]

И на сообщения в момент инициализации видеокарты, поскольку там тоже есть информация о необходимом объеме памяти. Так, например, у меня была ситуация что в юбуте были заданы размеры памяти для pcie устройства 128Мib а требовалось 512Mib и по этой причине видео не поднималось в ядре т.е. не создавалось устройство /dev/fb0:

nouveau  [     PFB][0000:01:00.0] RAM type: DDR2
nouveau  [     PFB][0000:01:00.0] RAM size: 512 MiB
nouveau  [     PFB][0000:01:00.0]    ZCOMP: 378880 tags
[TTM] Zone  kernel: Available graphics memory: 4063564 kiB
[TTM] Zone   dma32: Available graphics memory: 2097152 kiB
[TTM] Initializing pool allocator
[TTM] Initializing DMA pool allocator
nouveau  [     DRM] VRAM: 508 MiB
nouveau  [     DRM] GART: 512 MiB
nouveau  [     DRM] BIT BIOS found
nouveau  [     DRM] Bios version 05.43.02.88
nouveau  [     DRM] TMDS table version 1.1
nouveau  [     DRM] DCB version 3.0
nouveau  [     DRM] DCB outp 00: 01000300 00000028
nouveau  [     DRM] DCB outp 01: 04011320 00000028

Причем в dts файле ядра задавался необходимый размер памяти для видеокарты, но файл dts всеголишь декларирует и не производит каких либо настроек. Все настройки осуществляет uboot, выполняющий роль биоса на данной платформе.

Вставлю сюда без комментарив фрагмент из файла p5040ds.dts где видно каким образом можно проинформировать ядро сколько памяти выделено, но как написал выше эти данные ядро принимает и начинает с ними работать, но аппаратную перестройку при этом не осуществляет(это должен сделать uboot)):

        pcie@ffe200000 {
                reg = <0xf 0xfe200000 0x0 0x1000>;
/*              ranges = <0x2000000   0x0       0xe0000000 0xc         0x0       0x0
              0x20000000  0x1000000 0x0        0x0         0xf       0xf8000000
              0x0         0x10000>;
*/
                        
                ranges = <0x2000000   0x0       0xe0000000 0xc         0x0       0x0
              0x40000000  0x1000000 0x0        0x0         0xf       0xf8000000
              0x0         0x10000>;
                
                                                           
                
                compatible = "fsl,p5040-pcie", "fsl,qoriq-pcie-v2.4", "fsl,qoriq-pcie";
                device_type = "pci";
                #size-cells = <0x2>;
                #address-cells = <0x3>;
                bus-range = <0x0 0xff>;
                clock-frequency = <0x1fca055>;
                interrupts = <0x10 0x2 0x1 0xf>;
                fsl,iommu-parent = <0x3c>;
                  
                pcie@0 {
/*                      ranges = <0x2000000 0x0         0xe0000000  0x2000000 0x0 0xe0000000
                0x0       0x20000000  0x1000000   0x0       0x0 0x1000000
                  0x0       0x0         0x0         0x10000>;
*/
                        ranges = <0x2000000 0x0         0xe0000000  0x2000000 0x0 0xe0000000
                0x0       0x40000000  0x1000000   0x0       0x0 0x1000000
                  0x0       0x0         0x0         0x10000>;