1. 程式人生 > 其它 >PCI匯流排驅動程式碼梳理(二)--配置空間訪問的設定

PCI匯流排驅動程式碼梳理(二)--配置空間訪問的設定

技術標籤:Linuxlinux

PCI匯流排驅動程式碼梳理(二)–配置空間訪問的設定

1.什麼是配置空間

PCI裝置有三個相互獨立的物理空間地址:儲存器地址空間、I/O地址空間、配置空間地址空間,而配置空間是一個PCI特有的物理空間。系統上電時BIOS檢測PCI匯流排,確定所有連線在PCI連線在PCI總線上的裝置以及它們的配置要求,並進行系統配置。所以PCI裝置必須實現配置空間,從而實現引數的自動配置。

2.對配置空間的訪問

x86架構中pci配置空間的訪問有4種方式:pci_bios、pci_conf1、pci_conf2、pci_mmcfg。最優的方式是mmcfg,這需要bios配置,把pci配置空間對映到cpu mem空間;pci_conf1、pci_conf2方式是通過io指標間接訪問的;pci_bios方式應該是呼叫bios提供的服務程序進行訪問。使用I/O訪問的方式只可以訪問配置空間的前256位元組,而使用mmcfg的方式則可以完全支援PCIE的擴充套件暫存器即4K位元組的配置空間。在linux初始化的時候,需要給驅動程式選擇一種最優的訪問方式。

3.PCI驅動中對配置空間的訪問的實現

注:基於4.4版本核心
系統在初始化PCI匯流排的時候,會設定好讀取配置空間的方法,讀取的方式就上述的兩大類(I/O埠訪問、MEM訪問),提供給上層的可用介面函式是read函式和write函式,系統初始化完成後會將實現好的read方法和write方法繫結至結構體pci_ops,我們首先來看兩段程式碼。

const struct pci_raw_ops *__read_mostly raw_pci_ops;       //pci裝置訪問
const struct pci_raw_ops *__read_mostly raw_pci_ext_ops;   //pcie裝置擴充套件暫存器訪問
int raw_pci_read(unsigned int domain, unsigned int bus, unsigned int devfn, int reg, int len, u32 *val) { //判定是否小於256位元組,如果在256範圍內則呼叫raw_pci_ops方法 if (domain == 0 && reg < 256 && raw_pci_ops) return raw_pci_ops->read(domain, bus, devfn, reg, len, val); if (raw_pci_ext_ops)
return raw_pci_ext_ops->read(domain, bus, devfn, reg, len, val); return -EINVAL; } int raw_pci_write(unsigned int domain, unsigned int bus, unsigned int devfn, int reg, int len, u32 val) { if (domain == 0 && reg < 256 && raw_pci_ops) return raw_pci_ops->write(domain, bus, devfn, reg, len, val); if (raw_pci_ext_ops) return raw_pci_ext_ops->write(domain, bus, devfn, reg, len, val); return -EINVAL; } static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value) { return raw_pci_read(pci_domain_nr(bus), bus->number, devfn, where, size, value); } static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value) { return raw_pci_write(pci_domain_nr(bus), bus->number, devfn, where, size, value); } struct pci_ops pci_root_ops = { .read = pci_read, .write = pci_write, };
struct pci_raw_ops *raw_pci_ops;

static int pci_read(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 *value)
{
	//老版本的不會區分,只實現一種方法
	return raw_pci_ops->read(0, bus->number, devfn, where, size, value);
}

static int pci_write(struct pci_bus *bus, unsigned int devfn, int where, int size, u32 value)
{
	return raw_pci_ops->write(0, bus->number, devfn, where, size, value);
}

struct pci_ops pci_root_ops = {
	.read = pci_read,
	.write = pci_write,
};

這兩段程式碼都是對read方法和write方法的實現,第一段程式碼來自於4.4.185版本的核心,第二段程式碼來自2.6.16版本的核心,我們可以發現在老版本的核心中對於I/O埠訪問、MEM訪問這兩種方式是相互獨立的,如果read方法和write方法綁定了pci_conf1和pci_conf2的方式就無法繫結pci_mmcfg的方式。但是在新版本的核心中在這裡做出了一個優化,在新版本中我們呼叫了read方法和write方法時它會去判斷你所訪問的地址是否屬於前256位元組,當訪問的地址屬於前256位元組的話呼叫raw_pci_ops中的讀寫方法(一般實現為pci_conf1),當訪問的地址超過前256位元組的話呼叫raw_pci_ext_ops中的讀寫方法(一般實現為pci_mmcfg),所以在老版本PCI的初始化時,我們只需填充好raw_pci_ops結構體,並考慮系統究竟使用哪種方法訪問配置空間(訪問方式唯一),而在新版本的PCI初始化中,我們雖然需要填充好raw_pci_ops和raw_pci_ext_ops兩個結構體,但是卻實現了兩種訪問方法的相容,我們不需要考慮究竟使用那種方式(因為兩個都支援)。

新舊核心在PCI讀寫這裡的實現稍有區別,所以我們需要注意,下面我們來看一下PCI配置空間讀寫函式的實現過程,與PCI讀寫相關的函式有如下幾個:
1.pci_direct_probe
2.pci_mmcfg_early_init
3.pci_direct_init
4.pci_mmcfg_late_init

3.1 pci_direct_probe

int __init pci_direct_probe(void)
{
	if ((pci_probe & PCI_PROBE_CONF1) == 0)         //此時pci_probe為系統的預設值
		goto type2;
	if (!request_region(0xCF8, 8, "PCI conf1"))     //申請i/o
		goto type2;

	if (pci_check_type1()) {
		raw_pci_ops = &pci_direct_conf1;           //conf1的繫結
		port_cf9_safe = true;
		return 1;
	}
	release_region(0xCF8, 8);

 type2:
	if ((pci_probe & PCI_PROBE_CONF2) == 0)
		return 0;
	if (!request_region(0xCF8, 4, "PCI conf2"))
		return 0;
	if (!request_region(0xC000, 0x1000, "PCI conf2"))
		goto fail2;

	if (pci_check_type2()) {
		raw_pci_ops = &pci_direct_conf2;
		port_cf9_safe = true;
		return 2;
	}

	release_region(0xC000, 0x1000);
 fail2:
	release_region(0xCF8, 4);
	return 0;
}

該函式通過pci_probe的值來確定訪問方法,該變數的具體數值由核心啟動時的傳參來確定,值的定義有以下幾種:

#define PCI_PROBE_BIOS		0x0001
#define PCI_PROBE_CONF1		0x0002   //I/O訪問配置空間
#define PCI_PROBE_CONF2		0x0004  
#define PCI_PROBE_MMCONF	0x0008   //記憶體訪問配置空間

但是從系統啟動的grub.cfg可知,bootloder在啟動核心時沒有傳入相應的引數,所以pci_probe使用預設值,即:

unsigned int pci_probe = PCI_PROBE_BIOS | PCI_PROBE_CONF1 | PCI_PROBE_CONF2 |
				PCI_PROBE_MMCONF;

最終該函式將raw_pci_ops結構體繫結為pci_direct_conf1方法,並返回一個型別碼1供後續函式使用。

3.2 pci_mmcfg_early_init

void __init pci_mmcfg_early_init(void)
{
	if (pci_probe & PCI_PROBE_MMCONF) {
		if (pci_mmcfg_check_hostbridge())    //檢查host主橋
			known_bridge = 1;
		else
			acpi_sfi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg);
		__pci_mmcfg_init(1);      //此函式完成mmcfg的繫結和pci_probe的改變

		set_apei_filter();
	}
}

該函式配置了raw_pci_ext_ops方式將其繫結為pci_mmcfg的方式並且同時也重新設定了pci_probe的值,通過新增列印除錯資訊,我們可以清楚的看到pci_probe前後的變化(對於raw_pci_ext_ops和pci_probe內容操作的函式實際為__pci_mmcfg_init(1)中的pci_mmcfg_arch_init()完成的)。
在這裡插入圖片描述
如上圖所示,在完成了pci_mmcfg_early_init函式後pci_probe的值變為0x08對PCI_PROBE_MMCONF模式

3.3 pci_direct_init

void __init pci_direct_init(int type)
{
	if (type == 0)
		return;
	printk(KERN_INFO "PCI: Using configuration type %d for base access\n",
		 type);
	if (type == 1) {
		raw_pci_ops = &pci_direct_conf1;
		if (raw_pci_ext_ops)     //在pci_mmcfg_early_init已經完成,所以直接返回
			return;
		if (!(pci_probe & PCI_HAS_IO_ECS))
			return;
		printk(KERN_INFO "PCI: Using configuration type 1 "
		       "for extended access\n");
		raw_pci_ext_ops = &pci_direct_conf1;
		return;
	}
	raw_pci_ops = &pci_direct_conf2;
}

該函式根據pci_direct_probe的返回值來對raw_pci_ops和raw_pci_ext_ops進行設定,由於raw_pci_ext_ops在pci_mmcfg_early_init()這個函式中已經設定完畢,所以在此無需進行設定,因此該函式直接返回。

3.4 上述函式總結

以上三個函式都是位於pci_arch_init()函式中,該函式的啟動等級為3,此函式就是設定整個PCI匯流排裝置配置空間的讀寫方法。
函式執行前:
pci_probe = 0xf(預設值)
raw_pci_ops = 空
raw_pci_ext_ops = 空
函式執行後:
pci_probe = 0x8
raw_pci_ops = pci_direct_conf1
raw_pci_ext_ops = pci_mmcfg

3.5 pci_mmcfg_late_init

void __init pci_mmcfg_late_init(void)
{
	/* MMCONFIG disabled */
	if ((pci_probe & PCI_PROBE_MMCONF) == 0)
		return;

	if (known_bridge)
		return;

	/* MMCONFIG hasn't been enabled yet, try again */
	if (pci_probe & PCI_PROBE_MASK & ~PCI_PROBE_MMCONF) {
		acpi_sfi_table_parse(ACPI_SIG_MCFG, pci_parse_mcfg);
		__pci_mmcfg_init(0);
	}
}

此函式的執行等級較後,它是pci_mmcfg的第二次配置,即如果前面pci_mmcfg配置異常則再次配置,此時pci_probe的值為0x8而raw_pci_ext_ops繫結pci_mmcfg,所以表示前面的配置成果,所以如果pci_mmcfg_early_init函式完成了配置那麼pci_mmcfg_late_init函式一般就直接返回。

4.總結

經過對上面四個函式的的分析,我們可以梳理出對於PCI裝置配置空間的訪問方法的實現,系統是如何完成的:
在這裡插入圖片描述
至此,在系統對PCi裝置進行列舉之前,系統會完成所有對於配置空間訪問方法的設定,以保證列舉過程的正常執行,我們需要注意的是在實際使用哪怕是列舉過程我們對於配置空間的訪問使用的函式是以下幾種:

 pci_read_config_byte(..)     //8
 pci_read_config_word(..)     //16
 pci_read_config_dword(..)    //32

 pci_write_config_byte(..)
 pci_write_config_word(..)
 pci_write_config_dword(..)

這些是系統為我們開出的訪問函式介面,這些函式本質上是呼叫pci_ops的兩個讀寫函式的(可以認為是核心在pci_ops的一個封裝),我們可以測試以下,分別在pci_read_config_dword和raw_pci_read增加答應資訊,我們可以得知pci_read_config_dword最終也就是呼叫raw_pci_read來完成工作:
在這裡插入圖片描述