增加: 10_RK3399_PCIe_Host驱动分析_设备枚举

This commit is contained in:
weidongshan
2022-01-06 17:53:06 +08:00
parent c57b907e71
commit 5cffe55441
41 changed files with 638 additions and 396 deletions

View File

@@ -1,4 +1,4 @@
## PCI驱动程序框架
## RK3399_PCIe_Host驱动分析_设备枚举
参考资料:
@@ -21,418 +21,356 @@
### 1. PCI驱动框架
### 1. PCIe控制器的资源
![image-20211227180031140](pic/10_PCI_PCIe/51_pci_driver_block.png)
上节视频我们分析了PCIe控制器驱动程序`pcie-rockchip.c`,它解析了设备树,得到了如下资源:
* 总线资源就是总线号从0到0x1f
* 内存资源CPU地址基地址为0xfa000000PCI地址基地址为0xfa000000大小为0x1e00000
* IO资源CPU地址基地址为0xfbe00000PCI地址基地址为0xfbe00000大小为0x100000
这3类资源记录在链表中
![image-20220106102119348](pic/10_PCI_PCIe/79_res.png)
### 2. Host驱动程序速览
解析设备树时,把资源记录在这个链表里:
怎么找到驱动?
* 在内核目录下根据芯片名字找到文件:`drivers\pci\host\pcie-rockchip.c`
* 看到如下代码:
```c
static const struct of_device_id rockchip_pcie_of_match[] = {
{ .compatible = "rockchip,rk3399-pcie", },
{}
};
```
* 在内核`arch/arm64/boot/dts`下搜:`grep "rockchip,rk3399-pcie" * -nr`
* 找到设备树文件:`arch/arm64/boot/dts/rk3399.dtsi`,代码如下:
```shell
pcie0: pcie@f8000000 {
compatible = "rockchip,rk3399-pcie";
#address-cells = <3>;
#size-cells = <2>;
aspm-no-l0s;
clocks = <&cru ACLK_PCIE>, <&cru ACLK_PERF_PCIE>,
<&cru PCLK_PCIE>, <&cru SCLK_PCIE_PM>;
clock-names = "aclk", "aclk-perf",
"hclk", "pm";
bus-range = <0x0 0x1f>;
max-link-speed = <1>;
linux,pci-domain = <0>;
msi-map = <0x0 &its 0x0 0x1000>;
interrupts = <GIC_SPI 49 IRQ_TYPE_LEVEL_HIGH 0>,
<GIC_SPI 50 IRQ_TYPE_LEVEL_HIGH 0>,
<GIC_SPI 51 IRQ_TYPE_LEVEL_HIGH 0>;
interrupt-names = "sys", "legacy", "client";
#interrupt-cells = <1>;
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0 0 0 1 &pcie0_intc 0>,
<0 0 0 2 &pcie0_intc 1>,
<0 0 0 3 &pcie0_intc 2>,
<0 0 0 4 &pcie0_intc 3>;
phys = <&pcie_phy>;
phy-names = "pcie-phy";
ranges = <0x83000000 0x0 0xfa000000 0x0 0xfa000000 0x0 0x1e00000
0x81000000 0x0 0xfbe00000 0x0 0xfbe00000 0x0 0x100000>;
reg = <0x0 0xf8000000 0x0 0x2000000>,
<0x0 0xfd000000 0x0 0x1000000>;
reg-names = "axi-base", "apb-base";
resets = <&cru SRST_PCIE_CORE>, <&cru SRST_PCIE_MGMT>,
<&cru SRST_PCIE_MGMT_STICKY>, <&cru SRST_PCIE_PIPE>,
<&cru SRST_PCIE_PM>, <&cru SRST_P_PCIE>,
<&cru SRST_A_PCIE>;
reset-names = "core", "mgmt", "mgmt-sticky", "pipe",
"pm", "pclk", "aclk";
status = "disabled";
pcie0_intc: interrupt-controller {
interrupt-controller;
#address-cells = <0>;
#interrupt-cells = <1>;
};
};
```
所谓`Host`就是PCIe控制器它的驱动做什么
* 解析设备树根据设备树确定寄存器地址、CPU空间地址、PCI空间地址、中断信息
* 记录资源CPU空间地址、PCI空间地址
* 初始化PCIe控制器本身建立CPU地址和PCI地址的映射
* 扫描识别当前PCIe控制器下面的PCIe设备
![image-20220106102303015](pic/10_PCI_PCIe/80_get_res.png)
驱动文件`drivers\pci\host\pcie-rockchip.c`中注册了一个platform_driver从它的probe函数开始分析
res链表中记录的资源最终会放到pci_bus->bridge->windows链表里如下图记录
![image-20220102114521662](pic/10_PCI_PCIe/69_rockchip_pcie_drvier.png)
![image-20220106162022018](pic/10_PCI_PCIe/81_rk3399_pci_res.png)
### 2. 设备配置空间
本节内容参考:`PCI_SPEV_V3_0.pdf`
使用PCI/PCIe的目的就是为了简单地访问它像读写内存一样读写PCI/PCIe设备。
提问:
* 使用哪些地址读写设备?
* 这些地址的范围有多大?
* 是像内存一样访问它还是像IO一样访问它
### 2. 解析设备树
每个PCI/PCIe设备都有配置空间就是一系列的寄存器对于普通的设备它的配置空间格式如下。
#### 2.1 设备树文件解析
里面有:
RK3399访问PCIe控制器时CPU地址空间可以分为
* 设备ID
* 厂家ID
* Class Code哪类设备存储设备显示设备等待
* 6个Base Address Register
* Client Register Set地址范围 0xFD000000~0xFD7FFFFF比如选择PCIe协议的版本(Gen1/Gen2)、电源控制等
* Core Register Set :地址范围 0xFD800000~0xFDFFFFFF所谓核心寄存器就是用来进行设置地址映射的寄存器等
* Region 00xF8000000~0xF9FFFFFF , 32MB用于访问外接的PCIe设备的配置空间
* Region 10xFA000000~0xFA0FFFFF1MB用于地址转换
* Region 20xFA100000~0xFA1FFFFF1MB用于地址转换
* ……
* Region 320xFBF00000~0xFBFFFFFF1MB用于地址转换
其中Region 0大小为32MBRegion1~31大小分别为1MB。
在设备树里都有体现(下列代码中,其他信息省略了)
* reg属性里的0xf8000000Region 0的地址
* reg属性里的0xfd000000PCIe控制器内部寄存器的地址
* Client Register Set地址范围 0xFD000000~0xFD7FFFFF
* Core Register Set :地址范围 0xFD800000~0xFDFFFFFF
* ranges属性里
* 第1个0xfa000000Region1~30的CPU地址空间首地址用于内存读写
* 第2个0xfa000000Region1~30的PCI地址空间首地址用于内存读写
* 第1个0xfbe00000Region31的CPU地址空间首地址用于IO读写
* 第2个0xfbe00000Region31的PCI地址空间首地址用于IO读写
* Region32呢在.c文件里用作"消息TLP"
```shell
pcie0: pcie@f8000000 {
compatible = "rockchip,rk3399-pcie";
#address-cells = <3>;
#size-cells = <2>;
ranges = <0x83000000 0x0 0xfa000000 0x0 0xfa000000 0x0 0x1e00000
0x81000000 0x0 0xfbe00000 0x0 0xfbe00000 0x0 0x100000>;
reg = <0x0 0xf8000000 0x0 0x2000000>,
<0x0 0xfd000000 0x0 0x1000000>;
reg-names = "axi-base", "apb-base";
};
```
![image-20211117140817516](pic/10_PCI_PCIe/28_typ0_configuration_space.png)
#### 2.2 设备树相关驱动程序分析
#### 2.1 设备信息
代码入口如下:
* Vendor ID厂家IDPCI SIG组织给每个厂家都分配了一个独一的ID
* Device ID厂家给自己的某类产品分配一个Device ID
* Revision ID厂家自定义的版本号可以认为是Device ID的延伸
* Header Type
* b[7]: 1-它是一个多功能设备("multi-function")0-它是单功能设备("single-function")
* b[6:0]: 00h-普通设备, 01h-桥设备这个取值也决定了配置空间中偏移地址10h开始处的含义
![image-20220106092929107](pic/10_PCI_PCIe/74_config_space_two_types.png)
![image-20220102114645953](pic/10_PCI_PCIe/70_rockchip_parse_dts.png)
* Class Code这是只读的寄存器它含有3个字节用来表明设备的功能它分为3部分
* 最高字节:表示"base class",用来表示它属于内存卡、显卡等待
* 中间字节:表示"sub-class",再细分一下类别
* 最低字节:用来表示寄存器级别的编程接口"Interface"
* 示例如下Base Class为01h时表示它是一个存储设备但是还可以继续使用sub-class、Interface细分
![image-20220106094233596](pic/10_PCI_PCIe/75_base_class_01h.png)
##### 2.2.1 Region0和寄存器地址
#### 2.2 基地址(Base Address)
![image-20220102120040167](pic/10_PCI_PCIe/71_region0_apb.png)
普通的PCI/PCIe设备有6个基地址寄存器简称为BAR
![image-20220106094822299](pic/10_PCI_PCIe/76_base_register_reg.png)
BAR用于
* 声明需要什么类型的空间内存、IO、32位地址、64位地址
* 声明需要的空间有多大
* 保存主控分配给它的PCI空间基地址
地址空间可以分为两类:内存(Memory)、IO
0xF8000000就是RK3399的Region0地址用于 ECAM[PCIe ECAM介绍](https://zhuanlan.zhihu.com/p/176988002)。
只写读写0xF8000000这段空间就可以只写读写PCIe设备的配置空间。
0xFD000000即使RK3399 PCIe控制器本身的寄存器基地址。
* 对于内存,写入什么值读出就是什么值,可以提前读取
* 对于IO它反应的是硬件当前的状态每个时刻读到的值不一定相同
Region0用与读写配置空间它对应的寄存器要设置用于产生对应的TLP函数调用关系如下:
BAR的格式如下:
* 用于内存空间
![image-20220106100241585](pic/10_PCI_PCIe/77_bar_for_mem.png)
* 用于IO空间
![image-20220106100318426](pic/10_PCI_PCIe/78_bar_for_io.png)
BAR怎么表示它想申请多大的空间以32位地址为例
* 软件往BAR写入0xFFFFFFFF
* 软件读BAR
* 读出的数值假设为0xFFF0,000?忽略最低的4位就得到0xFFF0,0000
* 这表示BAR中可以写入的"Base Address"只有最高的12位
* 也就表示了最低的20位是可以变化的范围所以这个空间大小为2^20=1M Byte
如果BAR表示它使用32位的地址那么BAR0~BAR5可以分别表示6个地址空间。
如果BAR表示它使用64位的地址那么BAR0和BAR1、BAR2和BAR3、BAR4和BAR5分别表示3个地址空间
* 低序号的BAR表示64位地址的低32位
* 高序号的地址表示64位地址的高32位。
### 3. 扫描设备的过程
#### 3.1 核心: 构造pci_dev
扫描PCIe总线对每一个PCIe桥、PCIe设备都构造出对应的pci_dev
* 填充pci_dev的各项成员比如VID、PID、Class等
* 分配地址空间、写入PCIe设备
pci_dev结构体如下
![image-20220106112538571](pic/10_PCI_PCIe/82_pci_dev.png)
对应pci_dev结构体里的设备信息读取PCI设备的配置空间即可获得。
对应pci_dev结构体里的资源本节课程先不分析irq。对于resource结构体每个成员对应一个BAR。
resource结构体如下要注意的是里面记录的start、end等是基于CPU角度看待的。也就是说如果记录的是内存地址、IO地址那么是CPU地址不是PCI地址。并且这些地址是物理地址要在软件中使用它们要先执行ioremap。
![image-20220106112818930](pic/10_PCI_PCIe/83_resource.png)
#### 3.2 代码分析
我们要找到这4个核心代码
* 分配pci_dev
* 读取PCIe设备的配置空间填充pci_dev中的设备信息
* 根据PCIe设备的BAR得知它想申请什么类型的地址、多大
* 分配地址写入BAR
关键代码分为两部分:
* 读信息、得知PCIe设备想申请多大的空间
```shell
rockchip_pcie_probe
bus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res);
pci_scan_root_bus_msi
pci_scan_child_bus
pci_scan_slot
dev = pci_scan_single_device(bus, devfn);
dev = pci_scan_device(bus, devfn);
struct pci_dev *dev;
dev = pci_alloc_dev(bus);
pci_setup_device
pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
pci_device_add(dev, bus);
```
* 分配空间
```c
rockchip_pcie_probe
err = rockchip_pcie_init_port(rockchip);
```
![image-20220102140141821](pic/10_PCI_PCIe/73_regio0_reg_set.png)
##### 2.2.2 确定CPU/PCI地址空间
在PCIe设备树里有一个属性`ranges`它里面含有多个range每个range描述了
* flags是内存还是IO
* PCIe地址
* CPU地址
* 长度
先提前说一下怎么解析这些range函数为`for_each_of_pci_range`,解析过程如下:
![image-20211224164144628](pic/10_PCI_PCIe/50_parse_range.png)
从probe函数开始分析完整的代码流程如下
```c
rockchip_pcie_probe
resource_size_t io_base;
LIST_HEAD(res); // 资源链表
// 解析设备树获得PCI host bridge的资源(CPU地址空间、PCI地址空间、大小)
err = of_pci_get_host_bridge_resources(dev->of_node, 0, 0xff, &res, &io_base);
// 解析 bus-range
// 设备树里: bus-range = <0x0 0x1f>;
// 解析得到: bus_range->start= 0 ,
// bus_range->end = 0x1f,
// bus_range->flags = IORESOURCE_BUS;
// 放入前面的链表"LIST_HEAD(res)"
err = of_pci_parse_bus_range(dev, bus_range);
pci_add_resource(resources, bus_range);
// 解析 ranges
// 设备树里:
// ranges = <0x83000000 0x0 0xfa000000 0x0 0xfa000000 0x0 0x1e00000
// 0x81000000 0x0 0xfbe00000 0x0 0xfbe00000 0x0 0x100000>;
of_pci_range_parser_init
parser->range = of_get_property(node, "ranges", &rlen);
for_each_of_pci_range(&parser, &range) {// 解析range
// 把range转换为resource
// 第0个range
// range->pci_space = 0x83000000,
// range->flags = IORESOURCE_MEM,
// range->pci_addr = 0xfa000000,
// range->cpu_addr = 0xfa000000,
// range->size = 0x1e00000,
// 转换得到第0个res
// res->flags = range->flags = IORESOURCE_MEM;
// res->start = range->cpu_addr = 0xfa000000;
// res->end = res->start + range->size - 1 = (0xfa000000+0x1e00000-1);
// ---------------------------------------------------------------
// 第1个range
// range->pci_space = 0x81000000,
// range->flags = IORESOURCE_IO,
// range->pci_addr = 0xfbe00000,
// range->cpu_addr = 0xfbe00000,
// range->size = 0x100000,
// 转换得到第1个res
// res->flags = range->flags = IORESOURCE_MEM;
// res->start = range->cpu_addr = 0xfbe00000;
// res->end = res->start + range->size - 1 = (0xfbe00000+0x100000-1);
err = of_pci_range_to_resource(&range, dev, res);
// 在链表中增加resource
// 第0个resource
// 注意第3个参数: offset = cpu_addr - pci_addr = 0xfa000000 - 0xfa000000 = 0
// 第1个resouce
// 注意第3个参数: offset = cpu_addr - pci_addr = 0xfbe00000 - 0xfbe00000 = 0
pci_add_resource_offset(resources, res, res->start - range.pci_addr);
}
/* Get the I/O and memory ranges from DT */
resource_list_for_each_entry(win, &res) {
rockchip->io_bus_addr = io->start - win->offset; // 0xfbe00000, cpu addr
rockchip->mem_bus_addr = mem->start - win->offset; // 0xfba00000, cpu addr
rockchip->root_bus_nr = win->res->start; // 0
}
bus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res);
pci_bus_add_devices(bus);
```
##### 2.1.2 建立CPU/PCI地址空间的映射
调用关系如下:
```c
rockchip_pcie_probe
err = rockchip_cfg_atu(rockchip);
/* MEM映射: Region1~30 */
for (reg_no = 0; reg_no < (rockchip->mem_size >> 20); reg_no++) {
err = rockchip_pcie_prog_ob_atu(rockchip, reg_no + 1,
AXI_WRAPPER_MEM_WRITE,
20 - 1,
rockchip->mem_bus_addr +
(reg_no << 20),
0);
if (err) {
dev_err(dev, "program RC mem outbound ATU failed\n");
return err;
}
}
/* IO映射: Region31 */
offset = rockchip->mem_size >> 20;
for (reg_no = 0; reg_no < (rockchip->io_size >> 20); reg_no++) {
err = rockchip_pcie_prog_ob_atu(rockchip,
reg_no + 1 + offset,
AXI_WRAPPER_IO_WRITE,
20 - 1,
rockchip->io_bus_addr +
(reg_no << 20),
0);
if (err) {
dev_err(dev, "program RC io outbound ATU failed\n");
return err;
}
}
/* 用于消息传输: Region32 */
rockchip_pcie_prog_ob_atu(rockchip, reg_no + 1 + offset,
AXI_WRAPPER_NOR_MSG,
20 - 1, 0, 0);
rockchip->msg_bus_addr = rockchip->mem_bus_addr +
((reg_no + offset) << 20);
```
MEM空间映射
```c
// rockchip->mem_bus_addr = 0xfa000000
// rockchip->mem_size = 0x1e00000
// 设置Region1、2、……30的映射关系
for (reg_no = 0; reg_no < (rockchip->mem_size >> 20); reg_no++) {
err = rockchip_pcie_prog_ob_atu(rockchip, reg_no + 1,
AXI_WRAPPER_MEM_WRITE,
20 - 1,
rockchip->mem_bus_addr +
(reg_no << 20),
0);
```
IO空间映射
```c
// rockchip->io_bus_addr = 0xfbe00000
// rockchip->io_size = 0x100000
// 设置Region31的映射关系
offset = rockchip->mem_size >> 20;
for (reg_no = 0; reg_no < (rockchip->io_size >> 20); reg_no++) {
err = rockchip_pcie_prog_ob_atu(rockchip,
reg_no + 1 + offset,
AXI_WRAPPER_IO_WRITE,
20 - 1,
rockchip->io_bus_addr +
(reg_no << 20),
0);
if (err) {
dev_err(dev, "program RC io outbound ATU failed\n");
return err;
}
}
```
Message空间映射
```c
/* Region32assign message regions */
rockchip_pcie_prog_ob_atu(rockchip, reg_no + 1 + offset,
AXI_WRAPPER_NOR_MSG,
20 - 1, 0, 0);
rockchip->msg_bus_addr = rockchip->mem_bus_addr +
((reg_no + offset) << 20);
```
任何一个Region都有对应的寄存器
![image-20211231114439910](pic/10_PCI_PCIe/61_region_regs.png)
所谓建立CPU和PCI地址空间的映射就是设置Region对应的寄存器都是使用函数`rockchip_pcie_prog_ob_atu`
![image-20220102135758143](pic/10_PCI_PCIe/72_rockchip_pcie_prog_ob_atu.png)
##### 2.1.3 配置
Region 0
```c
```
读写配置空间:
```c
busdev = PCIE_ECAM_ADDR(bus->number, PCI_SLOT(devfn),
PCI_FUNC(devfn), where);
```
#### 2.2 扫描总线过程
```c
rockchip_pcie_probe
bus = pci_scan_root_bus(&pdev->dev, 0, &rockchip_pcie_ops, rockchip, &res);
pci_scan_root_bus_msi
pci_scan_child_bus
pci_scan_slot
dev = pci_scan_single_device(bus, devfn);
dev = pci_scan_device(bus, devfn);
pci_setup_device
pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
pci_device_add(dev, bus);
pci_bus_size_bridges(bus);
pci_bus_assign_resources(bus);
list_for_each_entry(child, &bus->children, node)
pcie_bus_configure_settings(child);
pci_bus_add_devices(bus);
__pci_bus_assign_resources
pbus_assign_resources_sorted
/* pci_dev->resource[]里记录有想申请的资源的大小,
* 把这些资源按对齐的要求排序
* 比如资源A要求1K地址对齐资源B要求32地址对齐
* 那么资源A排在资源B前面, 优先分配资源A
*/
list_for_each_entry(dev, &bus->devices, bus_list)
__dev_sort_resources(dev, &head);
// 分配资源
__assign_resources_sorted
assign_requested_resources_sorted(head, &local_fail_head);
```
##### 3.2.1 分配pci_dev结构
![image-20220106113805402](pic/10_PCI_PCIe/84_alloc_pci_dev.png)
##### 3.2.2 读取设备信息
![image-20220106114243209](pic/10_PCI_PCIe/85_pci_setup_device.png)
在`pci_scan_device`函数中会先尝试读取VID、PID成功的话才会继续调用`pci_setup_device`
![image-20220106114505761](pic/10_PCI_PCIe/86_pci_scan_device.png)
在`pci_setup_device`内部,会继续读取其他信息:
![image-20220106114658658](pic/10_PCI_PCIe/87_pci_setup_device_code.png)
##### 3.2.3 读BAR
![image-20220106115246161](pic/10_PCI_PCIe/88_pci_read_bar.png)
![image-20220106115501793](pic/10_PCI_PCIe/89_pci_read_bar_code1.png)
`pci_read_bases`函数代码分析:
![image-20220106115858880](pic/10_PCI_PCIe/90_pci_read_bases.png)
`pci_read_bases`函数又会调用`__pci_read_base``__pci_read_base`只是读BAR算出想申请的空间的大小
* 读BAR保留原值
* 写0xFFFFFFFF到BAR
* 在读出来解析出所需要的地址空间大小记录在pci_dev->resource[ ]里
* pci_dev->resource[ ].start = 0;
* pci_dev->resource[ ].end = size - 1;
把前面讲过的贴出来,有助于理解代码:
BAR怎么表示它想申请多大的空间以32位地址为例
* 软件往BAR写入0xFFFFFFFF
* 软件读BAR
* 读出的数值假设为0xFFF0,000?忽略最低的4位就得到0xFFF0,0000
* 这表示BAR中可以写入的"Base Address"只有最高的12位
* 也就表示了最低的20位是可以变化的范围所以这个空间大小为2^20=1M Byte
以下是`__pci_read_bases`函数的代码分析。
* 得到大小(原始数据,需要进一步解析)比如下列代码中sz被赋值为0xFFF0,000?,需要进一步解析
![image-20220106151603798](pic/10_PCI_PCIe/91_get_size.png)
##### 3.2.4 分配地址空间
这部分代码的函数调用非常深我们抓住2个问题即可
* 从哪里分配得到地址空间?
* 在设备树里指明了CPU地址、PCI地址的对应关系这些作为"资源"记录在pci_bus里
* 读BAR时在pci_dev->resource[]里记录了它想申请空间的大小
* 分配得到的基地址要写入BAR
代码调用关系如下:
* 把要申请的资源, 按照对齐要求排序然后调用assign_requested_resources_sorted代码如下
```shell
/* 把要申请的资源, 按照对齐要求排序
* 然后调用assign_requested_resources_sorted
*/
rockchip_pcie_probe
pci_bus_size_bridges(bus);
pci_bus_assign_resources(bus);
__pci_bus_assign_resources
pbus_assign_resources_sorted
/* pci_dev->resource[]里记录有想申请的资源的大小,
* 把这些资源按对齐的要求排序
* 比如资源A要求1K地址对齐资源B要求32地址对齐
* 那么资源A排在资源B前面, 优先分配资源A
*/
list_for_each_entry(dev, &bus->devices, bus_list)
__dev_sort_resources(dev, &head);
// 分配资源
__assign_resources_sorted
assign_requested_resources_sorted(head, &local_fail_head);
```
* `assign_requested_resources_sorted`函数做两件事
* 分配地址空间
* 把这块空间对应的PCI地址写入PCIe设备的BAR
* 代码如下:
```shell
assign_requested_resources_sorted(head, &local_fail_head);
pci_assign_resource
ret = _pci_assign_resource(dev, resno, size, align);
// 分配地址空间
__pci_assign_resource
pci_bus_alloc_resource
pci_bus_alloc_from_region
/* Ok, try it out.. */
ret = allocate_resource(r, res, size, ...);
err = find_resource(root, new, size,...);
__find_resource
// 从资源链表中分配地址空间
// 设置pci_dev->resource[]
new->start = alloc.start;
new->end = alloc.end;
// 把对应的PCI地址写入BAR
pci_update_resource(dev, resno);
pci_std_update_resource
/* 把CPU地址转换为PCI地址: PCI地址 = CPU地址 - offset
* 写入BAR
*/
pcibios_resource_to_bus(dev->bus, &region, res);
new = region.start;
reg = PCI_BASE_ADDRESS_0 + 4 * resno;
pci_write_config_dword(dev, reg, new);
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB