[转载]KVM虚拟机代码揭秘——QEMU的PCI总线与设备(上)

最近研究了一下QEMU的虚拟PCI设备,打算虚拟一个PCI-PCI桥和一个PCI设备,设备挂在桥上,桥挂在pci主桥上。并且给设备固定映射一个IO基地址,但是发现还是件头疼的事情,经过几天的辛苦,终于算是有点收获,和大家分享一下,有什么问题希望大家支持,一起讨论,共同提高。

申明:本文主要针对x86架构进行说明。

1. PCI 结构简介

为了大家更加容易的理解后文,先来回顾一下PCI总线的基本内存结构。每一个PCI设备都对应一段内存空间,里面按照地址位置放置PCI设备的信息,包括厂家信息,bar信息,中断等等,也可以理解成一个数组,一些设备一出厂,相关的信息已经写在里面,我们这里模拟设备,所有这些所有的信息我们都要进行动态的读和写。在这里只列出了本文相关的数据够。

0x0                            0x04                             0x08                             0xc

0x00 |vendor ID    dev ID    | command                    |                                   |

0x10 |bar0 addr                  | bar1 addr                    |bar2 addr                     |bar3 addr

0x20 |bar4 addr                  | bar5 addr                    |                                  |

0x30 |                                |                                  | interrupt line                |

另外,我们可以在LInux中使用lspci -x 看到PCI设备的相关内存数据信息。

2. QEMU的PCI总线

QEMU在初始化硬件的时候,最开始的函数就是pc_init1。在这个函数里面会相继的初始化CPU,中断控制器,ISA总线,然后就要判断是否需要支持PCI,如果支持则调用i440fx_init初始化我们伟大的PCI总线。

i440fx_init函数主要参数就是之前初始化好的ISA总线以及中断控制器,返回值就是PCI总线,之后我们就可以将我们自己喜欢的设备统统挂载在这个上面,下面来简单分析一下这个函数:

dev = qdev_create(NULL, "i440FX-pcihost");             /*创建PCI主总线设备*/
    s = FROM_SYSBUS(I440FXState, sysbus_from_qdev(dev));
    b = pci_bus_new(&s->busdev.qdev, NULL, 0);           /*创建我们真正的PCI总线*/
    s->bus = b;
    qdev_init_nofail(dev);                                                 /*初始化主总线设备*/

d = pci_create_simple(b, 0, "i440FX");                        /*创建主桥*/
    *pi440fx_state = DO_UPCAST(PCII440FXState, dev, d);   
   
    piix3 = DO_UPCAST(PIIX3State, dev,                           /*创建ISA桥*/
                     pci_create_simple_multifunction(b, -1,true,"PIIX3"));   
    piix3->pic = pic;                                                            /*连接8259中断控制器,IOAPIC貌似也和在一起*/
    pci_bus_irqs(b, piix3_set_irq, pci_slot_get_pirq, piix3, 4);
    (*pi440fx_state)->piix3 = piix3;

经过上面的初始化我们就得到了系统的主PCI总线b了,接着就可以挂载我们的设备。

另外,在Linux里面我们可以使用lspci -t来看PCI总线的结构图。

3. QEMU的PCI-PCI桥

在QEMU中,所有的设备包括总线,桥,一般设备都对应一个设备结构,通过register函数将所有的设备链接起来,就像Linux的模块一样,在QEMU启动的时候会初始化所有的QEMU设备,而对于PCI设备来说,QEMU在初始化以后还会进行一次RESET,将所有的PCI bar上的地址清空,然后进行统一分配。

QEMU(x86)里面的PCI的默认PCI设都是挂载主总线上的,貌似没有看到PCI-PCI桥,而桥的作用一般也就是连接两个总线,然后进行终端和IO的映射。我在x86里面找了半天没结果,又为了省事,就把ppc架构里面的DEC桥强偷过来使用一下,嘎嘎,关键就是包含一下头文件,改一改x86下面的配置文件,将DEC强行的配置下去,这种i440FX加DEC的组合在真实设备上还真没见过。哈哈

有了现成的桥使用起来就很简单了,代码如下,一看就能看出来参数是之前的主PCI总线,返回子总线。

sub_bus= pci_dec_21154_init(pci_bus,-1);

有人会问如果自己要写一个PCI-PCI桥怎么办,其实也很简单,接来下来简单分析一下DEC桥的初始化过程:

PCIBus *pci_dec_21154_init(PCIBus *parent_bus, int devfn)
{
    PCIDevice *dev;
    PCIBridge *br;
    dev = pci_create_multifunction(parent_bus, devfn, false,    /*在主PCI专线上创建DEC桥设备*/
                                   "dec-21154-p2p-bridge"); 
    br = DO_UPCAST(PCIBridge, dev, dev);                            /*得到桥结构体,Linux的 upcast确实强大*/
    pci_bridge_map_irq(br, "DEC 21154 PCI-PCI bridge", dec_map_irq);
    qdev_init_nofail(&dev->qdev);                                        /*初始化DEC桥设备,在这里就会调用下面这个函数进行进一步的初始化*/                                    
    return pci_bridge_get_sec_bus(br);                                  /*返回桥另外一端的PCI总线指针*/
}

static int dec_21154_initfn(PCIDevice *dev)
{
    int rc;

/*这个函数是所有PCI-PCI桥的通用函数,就不具体展开描述了,它初始化PCI-PCI桥除了厂家设备名以外的其他的共通属性。比如class,另一端的PCI总线等等。所以我们在写自己的桥的时候也要调用这个函数来初始化桥属性和从桥上引出的另外一根总线。*/
    rc = pci_bridge_initfn(dev); 
    if (rc < 0) {
        return rc;
    }

/*初始化厂商ID和设备ID,这个是每个设备的标识*/

pci_config_set_vendor_id(dev->config, PCI_VENDOR_ID_DEC);
    pci_config_set_device_id(dev->config, PCI_DEVICE_ID_DEC_21154);
    return 0;
}

通过上面的几个关键步骤我们就能初始化一个我们自己的PCI桥设备。使用lspci -t 能够看到我们自己初始化桥的结构图。

4. QEMU的PCI设备

一般的PCI设备其实和桥很像,甚至更简单,关键区分桥和一般设备的地方就是class属性和bar地址。所谓落尽繁华才是本质,下面看一下一个标准的PCI设备结构是怎么样的。

static PCIDeviceInfo fpga_info={
     .qdev.name = "fpga",
     .qdev.size = sizeof(FPGAState),
     .init      = pci_fpga_init,                  /*PCI 设备注册初始化函数,这个和上面的类似,初始化PCI各种属性*/
};

static void fpga_register_devices(void)
{
      pci_qdev_register(&fpga_info);        /*注册设备结构*/
}
device_init(fpga_register_devices)         /*设备添加到QEMU设备列表*/
在上面的过程中,pci_fpga_init函数在之前的文章中描述过就不展开了,然而其中主要的一条就是给bar分配IO地址,调用函数如下:pci_register_bar(&s->dev,0,0x800,PCI_BASE_ADDRESS_SPACE_IO,fpga_ioport_map);

其中第一个参数是设备;第二个参数是bar的编号,每个PCI设备又5个bar,对应0-5,这个我们也可以在上面的PCI基本结构中看到这6个bar,这个也是后文中提到的6个region,我们这里设置第一个也就是0;第三个参数是分配的IO地址空间范围;第四个参数是表示IO类型是PIO而不是MMIO;最后一个参数是IO读写映射函数。

从这里我们会发现一个问题,这里并没有给设备分配IO空间的基地址,只有一个空间长度而已,这也进一步说明PCI设备在QEMU中一般是随机动态分配空间的,通过不断的updatemapping来不断更新IO空间的映射。

当PCI设备结构都构造好以后,就可以通过 pci_create_simple_multifunction(sub_bus, -1,true,"fpga"));  来挂载我们设备了,这里的sub_bus就是我们之前通过创建桥得到的子总线。

通过上文我们了解了QEMU基本PCI设备,并且能成功的添加一个PCI桥和一个设备了,但是遗留了一个问题就是如何给一个PCI设备的bar动态分配一个IO基地址呢?我们在接下面的文章进行进一步讨论。

文章来源:http://blog.csdn.net/yearn520/article/details/6576875

时间: 2024-11-07 11:55:34

[转载]KVM虚拟机代码揭秘——QEMU的PCI总线与设备(上)的相关文章

[转载]KVM虚拟机代码揭秘——QEMU的PCI总线与设备(下)

在上文中,我们在QEMU中已经成功的虚拟了一个PCI桥和一个PCI设备,接下来我们就来给他们分配固定的IO基地址. 要给PCI设备分配固定的IO基地址,那么就需要先了解PCI设备是如何刷新和分配IO基地址的. 1. PCI设备的重置与刷新 PCI在需要的时候,如第一次启动,IO重叠等就需要重置PCI设备,并且清空PCI bar上面的地址信息.主要调用函数pci_device_reset void pci_device_reset(PCIDevice *dev){    int r; ... ..

KVM虚拟机IO处理过程(一) ----Guest VM I/O 处理过程

虚拟化技术主要包含三部分内容:CPU虚拟化,内存虚拟化,设备虚拟化.本系列文章主要描述磁盘设备的虚拟化过程,包含了一个读操作的I/O请求如何从Guest Vm到其最终被处理的整个过程.本系列文章中引用到的linux内核代码版本为3.7.10,使用的虚拟化平台是KVM,qemu的版本是1.6.1. 用户程序想要访问IO设备需要调用操作系统提供的接口,即系统调用.当在用户程序中调用一个read操作时,系统先保存好read操作的参数,然后调用int 80命令(也可能是sysenter)进入内核空间,在

CentOS6.3下安装kvm和创建kvm虚拟机

一.安装kvm 1 在安装CentOS6.3时可以选择安装好kvm 2 如果未安装好kvm,请按照下列方式安装 [创建本地yum源] 挂载iso文件mount -o loop -t iso9660 CentOS-6.3-x86_64-bin-DVD1.iso /mnt 设置本地yum源在/etc/yum.repos.d/创建   "任意文件名.repo"    文件  vi 刚才新建的文件名编辑[localyum]任意名称 ,不能有空格.name= local yum  任意名称bas

KVM之初体验——QEMU安装虚拟机

QEMU简介 QEMU是一款开源的模拟器及虚拟机监管器(Virtual Machine Monitor,  VMM).QEMU主要提供两种功能给用户使用.一是作为用户态模拟器,利用动态代码翻译机制来执行不同于主机架构的代码.二是作为虚拟机监管器,模拟全 系统,利用其他VMM(Xen, KVM, etc)来使用硬件提供的虚拟化支持,创建接近于主机性能的虚拟机. 是x86架构且硬件支持虚拟化技术(如 intel VT 或 AMD-V)的Linux全虚拟化解决方案. 它包含一个为处理器提供底层虚拟化 

KVM虚拟机IO处理过程(二) ----QEMU/KVM I/O 处理过程

接着KVM虚拟机IO处理过程中Guest Vm IO处理过程(http://blog.csdn.net/dashulu/article/details/16820281),本篇文章主要描述IO从guest vm跳转到kvm和qemu后的处理过程. 首先回顾一下kvm的启动过程(http://blog.csdn.net/dashulu/article/details/17074675).qemu通过调用kvm提供的一系列接口来启动kvm. qemu的入口为vl.c中的main函数,main函数通过

[转载]kvm libvirt 虚拟机管理

http://www.2cto.com/os/201203/123128.html kvm虚拟机管理 一.环境 role         hostname    ip                  OS kvm_server   target      192.168.32.40/24    rhel6.0_x64 vir_guest1   node4       192.168.32.34/24    rhel5.5_i386 vir_guest2   node5       192.16

烂泥: KVM虚拟机Linux系统增加硬盘

本文首发于烂泥行天下. Linux虚拟机在使用过程中,硬盘空间不够使用.由于前期没有做LVM,所以只能手动添加新的硬盘. 给虚拟机添加硬盘有两种方法: 1.通过virsh attach-disk命令添加一块硬盘到系统中,即时生效,但系统重启后新硬盘会消失. 2.通过修改虚拟机配置文件进行添加,永久生效. 现在我来一一介绍方法一和方法二. 方法一.通过virsh attach-disk命令添加硬盘 首先看看在未添加新硬盘系统的分区情况. fdisk –l df –h 从上图我们可以看到目前系统中只

KVM虚拟化笔记(六)------kvm虚拟机的克隆

kvm虚拟机的克隆分为两种情况:kvm主机本机虚拟机直接克隆和通过复制配置文件与磁盘文件的虚拟机复制克隆.接下来我们一一进行测试: (一)kvm主机虚拟机的直接克隆 1,查看虚拟机的配置文件和磁盘文件: [[email protected] qemu]# cat /etc/libvirt/qemu/hadoop1.xml  <!-- WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE  OVERWRITT

KVM虚拟化笔记(四)------kvm虚拟机日常管理

一,查看KVM虚拟机配置文件及运行状态 1,KVM虚拟机默认的配置文件路径:/etc/libvirt/qemu/ 而autostart目录是配置kvm虚拟机开机自启动的目录. [[email protected] qemu]# cd /etc/libvirt/qemu [[email protected] qemu]# ll total 24 drwxr-xr-x 2 root root 4096 Feb  2 15:03 autostart      ###开机自启动目录   -rw-----