[PCIe] SR-IOV (單根虛擬化) 及linux驅動淺析(device的PF和VF及其驅動)

網上從服務器和虛擬化層面介紹SR-IOV應用的文章很多了。

本文重點從支持SR-IOV的設備(EP)及其驅動來討論。

對於SR-IOV的設備(EP)來說,無非就是一個device通過物理功能(PF)虛擬出關聯的若干個虛擬功能(VF)。

host的驅動通過SR-IOV Extended Capability識別並配置VF使能,加載VF驅動,使得只有一個物理端口的pcie設備在軟件層面

體現出多個pcie設備。在虛擬化應用上可以將虛擬功能VF分配給不同的客戶機進行使用。

 

q0, linux應用層面如何使用打開VF,並使用VF

sysfs下,支持SR-IOV的device提供sriov_numvfs來控制VF的打開和關閉.VF打開後一般需要加載單獨的VF驅動。

rom sysfs:

'nr_virtfn' is number of VFs to be enabled

echo 'nr_virtfn' > \ /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_numvfs

 關閉VF

echo  0 > \ /sys/bus/pci/devices/<DOMAIN:BUS:DEVICE.FUNCTION>/sriov_numvfs

實例

檢查、配置vf數量,因爲還沒配置,所以是0

[root@g1 ~]# cat /sys/class/infiniband/mlx5_1/device/mlx5_num_vfs
0

這裏我測試把OFED驅動中的vf數量配置成1

echo 1>  /sys/class/infiniband/mlx5_1/device/mlx5_num_vfs

在PCI總線上,用lspci可以看到ConnectX-6對應的設備出現了多次,其中Function 1是剛纔配置好的Virtual Function

[root@g1 ~]# lspci|grep Mellanox
04:00.0 Infiniband controller: Mellanox Technologies MT28908 Family [ConnectX-6]
04:00.1 Infiniband controller: Mellanox Technologies MT28908 Family [ConnectX-6 Virtual Functio

 

q1:VF的bdf number如何分配(vf打開後是如何被識別到的)

首先回顧一下Routing ID,Routing ID就是BDF number,即採用Bus Number、Device Number和Function Number來確定目標設備的位置的id。

SR-IOV Extended Capability中用FirstVF Offset和VF Stride來標記VF的Routing ID。VF的Routing ID是以PF的Routing ID值爲參考來計算的。

FirstVF Offset:第一個VF相對PF的Routing ID的偏移量

VF Stride: 相鄰VF之間的Routing ID的偏移量(步進值)

PF的Routing ID在PF枚舉之後就已經分配好了。PF的驅動程序通過配置SR-IOV Extended Capability,打開這個PF關聯的VF之後,通過FirstVF Offset和VF Stride就能計算出VF們的Routing ID

這個計算也非常簡單,按照BDF的位域規則累加就可以了,linux下pcie的device 和 function number是一起編碼的(就是devfn),計算代碼如下:

/* vf_bus = pf_bus + (pf_devfn + offset + stride * vf_id) >> 8 */
int pci_iov_virtfn_bus(struct pci_dev *dev, int vf_id)
{
	if (!dev->is_physfn)
		return -EINVAL;
	return dev->bus->number + ((dev->devfn + dev->sriov->offset +
				    dev->sriov->stride * vf_id) >> 8);
}
/* vf_devfn = (pf_devfn + offset + stride * vf_id) & 0xff */
int pci_iov_virtfn_devfn(struct pci_dev *dev, int vf_id)
{
	if (!dev->is_physfn)
		return -EINVAL;
	return (dev->devfn + dev->sriov->offset +
		dev->sriov->stride * vf_id) & 0xff;
}

 

q2:VF的BAR空間如何分配

VF的BAR[n]空間是通過PF的SR-IOV Capability中每個VF_BAR[n]來分配的,和VF Configuration Space的BAR無關。

SR-IOV Capability中VF_BAR[n]的介紹如下:

9.3.3.14 VF BAR0 (Offset 24h), VF BAR1 (Offset 28h), VF BAR2 (Offset 2Ch), VF BAR3 (Offset
30h), VF BAR4 (Offset 34h), VF BAR5 (Offset 38h)

These fields must define the VF’s Base Address Registers (BARs). These fields behave as normal PCI BARs, as described in Section 7.5.1 . They can be sized by writing all 1s and reading back the contents of the BARs as described in Section 7.5.1.2.1 , complying with the low order bits that define the BAR type fields.
These fields may have their attributes affected by the VF Resizable BAR Extended Capability (see Section 9.3.7.5 ) if it is implemented.
The amount of address space decoded by each BAR shall be an integral multiple of System Page Size.

這些字段必須定義VF的基地址寄存器(BARs)。這些字段的作用與正常的PCI BARs相似,如7.5.1節所述。它們可以通過寫入全1並按照第7.5.1.2.1節中描述的那樣讀入BAR的內容來調整大小,通過低BIT可以判斷BAR的類型和屬性。
如果實現了VF可調整大小的BAR擴展功能,這些字段的屬性可能會受到影響(參見9.3.7.5節)

Each VF BARn, when “sized” by writing 1s and reading back the contents, describes the amount of address space consumed and alignment required by a single Virtual Function, per BAR. When written with an actual address value, and VF Enable and VF MSE are Set, the BAR maps NumVFs BARs. In other words, the base address is the address of the first VF BARn associated with this PF and all subsequent VF BARn address ranges follow as described below.

每個VF BARn通過寫入1並讀取內容來“調整大小”時,每個VF BAR都描述了單個VF所消耗的地址空間量和所需的對齊方式。當使用實際的地址值寫入時,並且設置了VF Enable和VF MSE,則VF BAR映射了NumVFs個 BAR空間。換句話說,VF BAR的起始地址是與這個PF關聯的第一個VF BAR的起始地址,所有後續VF的 BAR地址空間則依次向後排列。


VF BARs shall only support 32-bit and 64-bit memory space. PCI I/O Space is not supported in VFs. Bit 0 of any
implemented VF BARx must be RO 0b except for a VF BARx used to map the upper 32 bits of a 64-bit memory VF BAR pair.
The alignment requirement and size read is for a single VF, but when VF Enable is Set and VF MSE is Set, the BAR contains the base address for all (NumVFs) VF BARn

VF BARs只能支持32位和64位內存空間映射。VFs中不支持PCI I/O空間。所有VF BARx的bit0必須是只讀的0值,除了用於映射64位內存VF BAR上32位的VF BARx。
對齊要求和大小讀取是針對單個VF的,但是當使能VF Enable和VF MSE時,該BAR實際上包含所有(NumVFs多個) VF BAR的BAR地址空間

簡單的總結一下:

1. PF 的VF_BAR[n]行爲上是和常規的BAR是一樣的(全寫1來確定大小...等等分配機制)

2. 但是PF 的VF_BAR 地址空間分配之後,代表的含義與PF自己的BAR不同。VF_BAR對應的是PF關聯的每個VF的BAR空間。

3. VF1的BAR空間完全與PF 的VF_BAR 地址空間相同,也就是PF 的VF_BAR[0-5]空間剛好就是對應第一個VF的bar[0-5]

4. 那後續的VFn的空間在哪呢,VFn的每一個BAR[n]空間都依次在VF1的bar[n]後依次排列(大小是相同的)

5. 也就是說,雖然VF_BAR只顯式的看到一份VF的BAR空間,但實際上有NumVFs份 BAR空間在每個VF_BAR後依次存在,然後對應就是每個VF的bar。(類似一個矩陣,PF有關聯的NumVFs個VF,每個VF有6個BAR。)

(這個講的有點繞了,看圖或者代碼會好理解一點)

下面這張圖展示了PF的BAR0, VF BAR0,和每個VF的BAR0空間的關係。其他的BARn也都是一樣的。

linux 驅動分配VF BAR空間的代碼如下:

PF驅動調用sriov_init的時候先按照SR-IOV Capability中每個VF_BAR[n] * TotalVF 來分配PCI空間的resource放在PF 的dev->resource[i + PCI_IOV_RESOURCES]中,這個時候VF還沒使能和枚舉。

static int sriov_init(struct pci_dev *dev, int pos)
{
	int i, bar64;
	int rc;
	int nres;
	u32 pgsz;
	u16 ctrl, total;
	struct pci_sriov *iov;
	struct resource *res;
	struct pci_dev *pdev;

...
	for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
		res = &dev->resource[i + PCI_IOV_RESOURCES];
		/*
		 * If it is already FIXED, don't change it, something
		 * (perhaps EA or header fixups) wants it this way.
		 */
		if (res->flags & IORESOURCE_PCI_FIXED)
			bar64 = (res->flags & IORESOURCE_MEM_64) ? 1 : 0;
		else
			bar64 = __pci_read_base(dev, pci_bar_unknown, res,
						pos + PCI_SRIOV_BAR + i * 4);
		if (!res->flags)
			continue;
		if (resource_size(res) & (PAGE_SIZE - 1)) {
			rc = -EIO;
			goto failed;
		}
		iov->barsz[i] = resource_size(res);
        /* 重點在這一行,驅動按照VF_BAR分配的空間又強行乘以VF的total數量  */
		res->end = res->start + resource_size(res) * total - 1;
		dev_info(&dev->dev, "VF(n) BAR%d space: %pR (contains BAR%d for %d VFs)\n",
			 i, res, i, total);
		i += bar64;
		nres++;
	}

...
}

然後在使能VF的時候sriov_enable->pci_iov_add_virtfn,再從PF之前申請到的dev->resource[i + PCI_IOV_RESOURCES]依次把對應的resource空間拿出去賦給每個VF 的dev->resource,然後再進行request_resource

int pci_iov_add_virtfn(struct pci_dev *dev, int id, int reset)
{
	int i;
	int rc = -ENOMEM;
	u64 size;
	char buf[VIRTFN_ID_LEN];
	struct pci_dev *virtfn;
	struct resource *res;
	struct pci_sriov *iov = dev->sriov;
	struct pci_bus *bus;

...

	for (i = 0; i < PCI_SRIOV_NUM_BARS; i++) {
		res = &dev->resource[i + PCI_IOV_RESOURCES];
		if (!res->parent)
			continue;
		virtfn->resource[i].name = pci_name(virtfn);
		virtfn->resource[i].flags = res->flags;
		size = pci_iov_resource_size(dev, i + PCI_IOV_RESOURCES);
		virtfn->resource[i].start = res->start + size * id;
		virtfn->resource[i].end = virtfn->resource[i].start + size - 1;
		rc = request_resource(res, &virtfn->resource[i]);
		BUG_ON(rc);
	}

...

	return rc;
}

q3:VF的Configuration Space有何含義

打開VF之後,通過VF的BDF number訪問VF的配置空間(和PF一樣的)。

VF作爲一個function也有自己Type 0 Configuration Space Header以及各種Capability存放在配置空間。

但是作爲一個虛擬function,VF配置空間中的很多字段都是沒用的,軟件上應該直接採用PF的值。

因爲配置空間主要是存放PCI的功能寄存器,而VF的電源,link,錯誤管理,BAR等其實完全是交給PF來管理的(VF和PF共享一個PCIe端口)。只有與VF獨立功能相關的纔會有VF單獨的實現(例如MSI-X中斷)。

 

例如:VF的Vendor ID和Device ID就是全F的無效值,軟件上VF的Vendor ID應該直接採用PF的值,

Device ID應該從PF配置空間中的VF Device ID字段來獲取。

pci_iov_add_virtfn中有這樣一行代碼:

virtfn->vendor = dev->vendor;
pci_read_config_word(dev, iov->pos + PCI_SRIOV_VF_DID, &virtfn->device);

其中dev就是 virtfn關聯的PF。

目前看來,配置空間涉及的VF獨立的功能有:

FLR(VF可以單獨進行復位)

MSI/MSI-X(VF應該單獨實現自己獨立的MSI/X capabilities,從而每個VF在系統中都有獨立的中斷向量,但VF無法支持INTx)

 

總結:VF通過共享PF的PCIe端口,節省了很多連接上的開銷。在獨立功能的支持方面,VF主要需要實現其獨立的BAR空間以及中斷資源,用於實現VF的獨立功能,以達到從PCIe層面的虛擬化。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章