设为首页 收藏本站
查看: 1211|回复: 0

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

[复制链接]

尚未签到

发表于 2015-4-11 08:13:34 | 显示全部楼层 |阅读模式
  在上文中,我们在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;
  ... ...
  ... ...
  dev->config[PCI_CACHE_LINE_SIZE] = 0x0;
    dev->config[PCI_INTERRUPT_LINE] = 0x0;
    for (r = 0; r < PCI_NUM_REGIONS; ++r) {    /*遍历所有的region,这个的region就是bar,清空region里面的IO地址*/
        PCIIORegion *region = &dev->io_regions[r];
        if (!region->size) {
            continue;
        }
  if (!(region->type & PCI_BASE_ADDRESS_SPACE_IO) &&
            region->type & PCI_BASE_ADDRESS_MEM_TYPE_64) {
            pci_set_quad(dev->config + pci_bar(dev, r), region->type);
        } else {
  /*用type将bar上所有的数据都覆盖,之前分配的IO基地址也没了*/
            pci_set_long(dev->config + pci_bar(dev, r), region->type);
  /*刷新设备*/
  pci_update_mappings(dev);
        }
    }
  /*刷新IO地址,更新IO读写映射*/
    pci_update_mappings(dev);
}
  
  刷新IO地址函数展开如下:
  static void pci_update_mappings(PCIDevice *d)
{
    PCIIORegion *r;
    int i;
    pcibus_t new_addr, filtered_size;
  for(i = 0; i < PCI_NUM_REGIONS; i++) {
        r = &d->io_regions;
  /* 如果没有注册region,那么不进行任何操作*/
        if (!r->size)
            continue;
  /* 得到设备bar上存储的基地址 */
  new_addr = pci_bar_address(d, i, r->type, r->size);
  /* bridge filtering */
        filtered_size = r->size;
  /* 如果分配了bar地址,那么比较设备地址与父桥的地址,看是否匹配*/
        if (new_addr != PCI_BAR_UNMAPPED) {
            pci_bridge_filter(d, &new_addr, &filtered_size, r->type);
        }
  /* 如果得到的新地址没有改变,大小也没变,那么不更新IO重映射,否则将IO读写进行重新映射。*/
        if (new_addr == r->addr && filtered_size == r->filtered_size)
            continue;
  /* 调用IO读写映射函数 */
       ... ...
  ... ...
  
    }
}
  得到设备bar上存储的基地址的函数展开如下:
  static pcibus_t pci_bar_address(PCIDevice *d, int reg, uint8_t type, pcibus_t size)
{
    pcibus_t new_addr, last_addr;
  /*获得region里基地址的偏移位置*/
    int bar = pci_bar(d, reg);
  /*检查PCI设备IO是否分配,分配以后command应该置1*/
    uint16_t cmd = pci_get_word(d->config + PCI_COMMAND);
  if (type & PCI_BASE_ADDRESS_SPACE_IO) {
  /*如果没有设置type或者没有分配IO那么直接返回地址未映射,将基地址重新置成-1*/
        if (!(cmd & PCI_COMMAND_IO)) {
            return PCI_BAR_UNMAPPED;           
  }
  /*将地址进行对齐,大小范围内清0,这个不是很好解释,因为前面我们这个size是制定为2的N此方的,所以减1就尾数全为1,取反为清0*/
        new_addr = pci_get_long(d->config + bar) & ~(size - 1);
  /*得到region结束地址*/
        last_addr = new_addr + size - 1;
        /* NOTE: we have only 64K ioports on PC */
  /*检查地址是否合法*/
        if (last_addr  UINT16_MAX) {
            return PCI_BAR_UNMAPPED;
        }
  /*返回新地址*/
        return new_addr;
    }
  ... ...
  ... ...
  }
  从这里可以看出,要保证地址不被清空,只要保证之前有基地址,而且合法,所以,只要reset不清空地址,那么在这里只要地址合法,就不会清楚映射好的地址。
  当刷新得到新地址以后就进行与父桥的地址匹配,函数展开如下:
  static void pci_bridge_filter(PCIDevice *d, pcibus_t *addr, pcibus_t *size, uint8_t type)
{
  ... ...
  ... ...
  /*取桥与设备基地址的最大值作为设备基地址,取桥与设备结束的最小值作为设备的结束地址,如果这个地址合法,那么保证设备在桥地址的范围内*/
     base = MAX(base, pci_bridge_get_base(br, type));
     limit = MIN(limit, pci_bridge_get_limit(br, type));
    /*如果取得地址不匹配,说明设备不在桥的范围内,而且无法截断,将设备地址设置成无效,重新匹配*/
  if (base > limit) {
        goto no_map;
    }
  /*匹配成功*/
    *addr = base;
    *size = limit - base + 1;
    return;
no_map:
    *addr = PCI_BAR_UNMAPPED;
    *size = 0;
}
  从这个函数可以看出来,设备的地址分配是受桥的地址分配约束的,只要桥的地址分配了,设备的地址只能分配在桥的范围内,否则就会被置为无效,然后重新分配,一直到分配在桥的范围内为止。所以只要固定了桥的地址,自然就固定了设备的地址。
  
  所以只需要初始化桥的地址,并且在reset的时候跳过桥的基地址重置,就能实现设备和桥地址的固定。添加的函数和代码如下:
  添加桥的初始地址,因为桥的地址固定写在bar3上,通过写20可以将基地址固定在0x2000上,同时还需要写命令位,置1.
  static int dec_21154_initfn(PCIDevice *dev)
{
  ... ...
  ... ...
  pci_set_word(dev->config + PCI_BASE_ADDRESS_3,0x2020);
     pci_set_word(dev->config + PCI_COMMAND,0x1);
  void pci_device_reset(PCIDevice *dev)
  
  return 1;
}
  在重置桥里面过滤我们的桥,通过dev的名字可以识别我们自己定义的设备,如果是我们的设备就不重置,直接进行更新IO映射。
  void pci_device_reset(PCIDevice *dev)
{
  if(strcmp(dev->name,"dec_name")==0){
          pci_update_mappings(dev);
          return   
  }
  ... ...
  ... ...
  }
  通过上面的步骤就能实现一般的IO基地址固定,我们可以在Linux中使用 cat /proc/ioports 命令来查看当前PCI设备的IO映射地址关系。
  
2. 直接重写config_write函数。
  我用这种方法测试过几种操作系统,不同系统的PCI设备初始化可能会有区别,有些不能够自适应分配IO基地址设备的,那么我们就需要强行overide PCI配置读写函数。
  
  在QEMU中,每一个PCI设备都要注册一个读写配置函数,用来提供给操作系统读写PCI设备的内存信息,通过读写这两个函数,就能实现对PCI设备IO基地址进行设置,而我们的IO基地址之所以会动态的变化,也就是因为这个函数将新的IO基地址写到了我们虚拟的PCI设备的bar里面,造成我们自己设置的基地址被覆盖。如果我们不重写它,就使用系统默认的配置函数,不改变重写的数值,如果我们有些特殊的需求,如强行给PCI内存赋值,就可以重写这个函数,虽然有些暴力,但是确实可行。
  这样做我们需要修改之前定义的设备结构体。在结构体里面增添.config_write和.config_read。并且在write里面强行的把基地址写成我们想固定的地址。
  
  static PCIDeviceInfo fpga_info={
     .qdev.name = "fpga",
     .qdev.size = sizeof(FPGAState),
     .init      = pci_fpga_init,
  .config_write = fpga_config_write,
  .config_read = fpga_config_read,
};
  void fpga_write_config(PCIDevice *d, uint32_t addr, uint32_t val, int l)
  {
  /*如果是bar0 则是0x10,这个必须根据我们分配的bar不同而变化*/
  if(addr = 0x10) pci_default_write_config(d,addr,0x20,l);
  else pci_default_write_config(d,addr,val,l);
  }  
  
  同样的方法我们也可以用在桥里面,将桥的IO基地址固定,然而桥的PCI桥地址的基地址是放在bar3上的,所以判断起来要判断1d,如:
  if(addr==1d)   pci_bridge_write_config(d,addr,0x20,l);
  else   pci_bridge_write_config(d,addr,val,l);
  
  这样我们就强行的将两者的IO基地址固定了,这个我在操作系统上测试通过了,并且KVM IO拦截运行正常。
总结
  通过上面两种改写就能够确保模拟出来的PCI总线设备和桥固定在我们想要的IO空间段,不用系统随机的分配。这样做可以满足我们一些特殊化得需求,如某些板子的某些设备是固定IO地址的,而相应的操作系统不是通过class和subclass,vendor,device ID这些来读取设备,而是通过固定IO来访问设备的就能起到作用。对一些固定的操作系统有更强的兼容性。另外也在一定的程度上帮助我们更深入的理解了PCI设备,理解了硬件与操作系统的IO交互。
  
  文章出处:http://blog.iyunv.com/yearn520/article/details/6577988

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-55874-1-1.html 上篇帖子: centos6 kvm网卡桥接 下篇帖子: CloudStack 4.1.0 安装详解 – 2、KVM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表