在qemu中增加pci设备并用linux驱动验证

时间:2022-08-03 17:54:34

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

使用的qemu版本是:qemu-kvm-1.2.0-rc2


1)PCI结构简介

每个PCI设备都有一个配置空间和若干个地址空间,按照固定的顺序放置CPI的各种配置参数。关于详细的介绍可以在网上搜索相关的资料。

下面是在busybox下lspci -mk的输出内容说明:

[plain]  view plain copy print ? 在qemu中增加pci设备并用linux驱动验证 在qemu中增加pci设备并用linux驱动验证
  1. 00:00.0 "Class 0600" "8086" "1237" "1af4" "1100"  
  2. 00:01.0 "Class 0601" "8086" "7000" "1af4" "1100"  
  3. 00:01.1 "Class 0101" "8086" "7010" "1af4" "1100" "ata_piix"  
  4. 00:01.3 "Class 0680" "8086" "7113" "1af4" "1100"  
  5. 00:02.0 "Class 0300" "1013" "00b8" "1af4" "1100"  
  6. 00:03.0 "Class 0200" "10ec" "8139" "1af4" "1100" "8139cp"  
  7. 00:04.0 "Class 0604" "1011" "0026" "0000" "0000"  
  8. 01:00.0 "Class 3542" "1234"         "5678"      "6872"          "8952"  
  9.     class_id    vendor_id   device_id   subsystem_vendor_id subsystem_id  

2)qemu的桥

在qemu中桥,总线,设备都会对应一个设备结构。最开始的初始化硬件的函数是pc_init1,在这里调用函数i440fx_init创建一个pci_bus,并且和isa_bus关联起来,(qemu模拟的还是pci-isa桥),然后再基于pci_bus创建一系列的设备。


3)pci设备创建

先看下一个pci设备的结构是怎样的:

  1. static TypeInfo mem_pci_info = {  
  2.     .name = "mem_pci",  
  3.     .parent = TYPE_PCI_DEVICE,  
  4.     .instance_size = sizeof(PCIMEMPCIState),  
  5.     .class_init = mem_pci_class_init,          /// pci 设备的初始化函数  
  6. };  

  1. static void mem_pci_register_types(void)  
  2. {  
  3.     type_register_static(&mem_pci_info);          /// 注册设备结构  
  4. }  

在函数mem_pci_class_init里面为PCIDeviceClass的init数据成员赋值mem_pci_init

  1. static int mem_pci_init(PCIDevice *dev)  
  2. {  
  3.     PCIMEMPCIState *pci = DO_UPCAST(PCIMEMPCIState, pci_dev, dev);  
  4.     MEMPCIState *s = &pci->state;  
  5.   
  6.     pci->mem_pci_base    = (uint32_t)malloc(PCI_MEM_SIZE);                            
  7.     memory_region_init_io(&s->mem, &mem_pci_ops, pci, "mem-pci", PCI_MEM_SIZE);        /// 注册一个MemoryRegion结构体,并分配一个                                                                                   ///MemoryRegionOps数据成员,这样  
  8.     pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem);                  ///给pci设备注册一个bar类型是mem。  
  9.   
  10.     return 0;  
  11. }  

由于本文需要让 linux内核的pci驱动 跟  qemu模拟的pci设备  之间实现通信,注意仅仅是实现数据流的传送。如果操作pci设备空间用qemu提供的api函数 cpu_inb ,那么会导致qemu把通信的数据当作操作设备的命令来执行。所以这里申请了一块内存并用mem_pci_base来指向申请的内存。

当以后需要对这块内存读写操作的时候就可以直接读写这块内存:

  1. static void mem_pci_write(void *opaque, target_phys_addr_t addr,  
  2.                              uint64_t value, unsigned int size)  
  3. {  
  4.         void    *pci_mem_addr;  
  5.         int     temp,region_size;  
  6.     byte    buff[8];  
  7.   
  8.     pci_mem_addr    = ((PCIMEMPCIState *)opaque)->mem_pci_base;  
  9.     pci_mem_addr    = ((char *)pci_mem_addr) + addr;  
  10.   
  11.     switch (size) {  
  12.             case 1:  
  13.             sprintf(buff,"%02llx",value);  
  14.             sscanf(buff,"%x",&temp);  
  15.             *((byte*)pci_mem_addr)  = (byte)temp;  
  16.             break;  
  17.     }  
  18. }  
具体的qemu端pci设备 mem_pci.c 实现方式如下所示:

  1. /* 
  2.  * QEMU memory pci emulation (PCI to ISA bridge) 
  3.  * 
  4.  */  
  5.   
  6. #include "pci.h"  
  7. #include "pc.h"  
  8. #include "i8254.h"  
  9. #include "pcspk.h"  
  10. #include "hw.h"  
  11.   
  12.   
  13. #define MEM_PCI_VENDOR_ID   0x1234  
  14. #define MEM_PCI_DEVICE_ID   0x5678  
  15. #define MEM_PCI_REVISION_ID 0x73  
  16.   
  17. #define PCI_MEM_SIZE        0x00000010  
  18.   
  19. typedef struct MEMPCIState {  
  20.     MemoryRegion mem;  
  21. } MEMPCIState;  
  22.   
  23. typedef struct PCIMEMPCIState {  
  24.     PCIDevice pci_dev;  
  25.     uint32_t mem_pci_base;  
  26.     MEMPCIState state;  
  27. } PCIMEMPCIState;  
  28.   
  29. static const VMStateDescription vmstate_mem_pci = {  
  30.     .name = "mem_pci",  
  31.     .version_id = 0,  
  32.     .minimum_version_id = 0,  
  33.     .fields = (VMStateField[]) {  
  34.         VMSTATE_PCI_DEVICE(pci_dev, PCIMEMPCIState),  
  35.         VMSTATE_END_OF_LIST()  
  36.     },  
  37. };  
  38.   
  39.   
  40. typedef unsigned char  byte;  
  41. typedef unsigned short int uint16;  
  42. typedef unsigned int    uint32;  
  43.   
  44.   
  45. static void mem_pci_write(void *opaque, target_phys_addr_t addr,  
  46.                              uint64_t value, unsigned int size)  
  47. {  
  48.         void    *pci_mem_addr;  
  49.         int     temp,region_size;  
  50.     byte    buff[8];  
  51.       
  52.     pci_mem_addr    = ((PCIMEMPCIState *)opaque)->mem_pci_base;  
  53.     pci_mem_addr    = ((char *)pci_mem_addr) + addr;  
  54.     region_size = (int)memory_region_size( &((PCIMEMPCIState *)opaque)->state.mem);  
  55.   
  56.     if(addr > region_size)  
  57.         return ;  
  58.     fprintf(stderr,"%x\n",pci_mem_addr);  
  59.     switch (size) {  
  60.             case 1:  
  61.             sprintf(buff,"%02llx",value);  
  62.             sscanf(buff,"%x",&temp);  
  63.             *((byte*)pci_mem_addr)  = (byte)temp;  
  64.             break;  
  65.             case 2:  
  66.             sprintf(buff,"%04llx",value);  
  67.             sscanf(buff,"%x",&temp);  
  68.             *((uint16*)pci_mem_addr)= (uint16)temp;  
  69.             break;  
  70.             case 4:  
  71.             sprintf(buff,"%08llx",value);  
  72.             sscanf(buff,"%x",&temp);                  
  73.             *((uint32*)pci_mem_addr)= (uint32)temp;  
  74.             break;  
  75.     }  
  76.     fprintf(stderr,"%x\n",temp);  
  77. }  
  78.   
  79. static uint64_t mem_pci_read(void *opaque, target_phys_addr_t addr,  
  80.                                 unsigned int size)  
  81. {  
  82.     void    *pci_mem_addr;  
  83.     int     temp,region_size;  
  84.     byte    buff[8];  
  85.     pci_mem_addr    = ((PCIMEMPCIState *)opaque)->mem_pci_base;  
  86.     pci_mem_addr    = ((char *)pci_mem_addr) + addr;  
  87.     region_size = memory_region_size(&((PCIMEMPCIState *)opaque)->state.mem);      
  88.   
  89.     if(addr > region_size)  
  90.         return 0;  
  91.     switch (size) {  
  92.         case 1:  
  93.             temp = *((byte *)pci_mem_addr);  
  94.             return ((byte)temp);  
  95.         case 2:  
  96.             temp = *((uint16 *)pci_mem_addr);  
  97.             return ((uint16)temp);  
  98.         case 4:  
  99.             temp = *((uint32 *)pci_mem_addr);  
  100.             return ((uint32)temp);  
  101.     }  
  102.         //fprintf(stderr,"%d",temp);  
  103. }  
  104.   
  105. static const MemoryRegionOps mem_pci_ops = {  
  106.     .read = mem_pci_read,  
  107.     .write = mem_pci_write,  
  108.     .endianness = DEVICE_LITTLE_ENDIAN,  
  109. };  
  110.   
  111.   
  112. static Property mem_pci_properties[] = {  
  113.     DEFINE_PROP_HEX32("membase", PCIMEMPCIState, mem_pci_base, 0xc0000000),  
  114.     DEFINE_PROP_END_OF_LIST()  
  115. };  
  116.   
  117.   
  118. static int mem_pci_init(PCIDevice *dev)  
  119. {  
  120.     PCIMEMPCIState *pci = DO_UPCAST(PCIMEMPCIState, pci_dev, dev);  
  121.     MEMPCIState *s = &pci->state;  
  122.   
  123.     pci->mem_pci_base    = (uint32_t)malloc(PCI_MEM_SIZE);  
  124.     memory_region_init_io(&s->mem, &mem_pci_ops, pci, "mem-pci", PCI_MEM_SIZE);  
  125.     pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem);  
  126.   
  127.     return 0;  
  128. }  
  129.   
  130. static void mem_pci_class_init(ObjectClass *klass, void *data)  
  131. {  
  132.     PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);  
  133.     DeviceClass *dc = DEVICE_CLASS(klass);  
  134.   
  135.     k->init = mem_pci_init;  
  136.     k->vendor_id = MEM_PCI_VENDOR_ID;  
  137.     k->device_id = MEM_PCI_DEVICE_ID;  
  138.     k->revision  = MEM_PCI_REVISION_ID;  
  139.   
  140.     dc->vmsd = &vmstate_mem_pci;  
  141.     dc->props = mem_pci_properties;  
  142. }  
  143.   
  144. static TypeInfo mem_pci_info = {  
  145.     .name = "mem_pci",  
  146.     .parent = TYPE_PCI_DEVICE,  
  147.     .instance_size = sizeof(PCIMEMPCIState),  
  148.     .class_init = mem_pci_class_init,  
  149. };  
  150.   
  151. static void mem_pci_register_types(void)  
  152. {  
  153.     type_register_static(&mem_pci_info);  
  154. }  
  155.   
  156. type_init(mem_pci_register_types)  

在文件hw/pc_piix.c 函数pc_init1里面增加创建设备的代码:

  1. pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device,  
  2.              floppy, idebus[0], idebus[1], rtc_state);  
  3.   
  4. pci_create_simple_multifunction(pci_bus, -1,true ,"mem_pci");  
  5.   
  6. if (pci_enabled && usb_enabled) {  
  7.     pci_create_simple(pci_bus, piix3_devfn + 2, "piix3-usb-uhci");  
  8. }  


把源文件mem_pci.c放在hw目录下,在文件hw/Makefile.objs 增加如下代码:

[plain]  view plain copy print ? 在qemu中增加pci设备并用linux驱动验证 在qemu中增加pci设备并用linux驱动验证
  1. hw-obj-y                  += mem_pci.o  

然后编译。


linux内核一端需要有一个pci驱动来驱动这个我们模拟的pci设备,这里仅仅是一个简单的pci设备驱动,关于其框架不多说了,网上有很多教程。直接给出代码吧:

(注意对比这个驱动代码和上面的设备代码相同的地方)

  1. #include <linux/kernel.h>  
  2. #include <linux/module.h>  
  3. #include <linux/pci.h>  
  4. #include <linux/init.h>  
  5.   
  6.   
  7. #define MEM_PCI_VENDOR_ID       0x1234  
  8. #define MEM_PCI_DEVICE_ID       0x5678        
  9. #define MEM_PCI_REVISION_ID     0x73  
  10.   
  11. typedef unsigned char  byte;  
  12. typedef unsigned short int uint16;  
  13. typedef unsigned int    uint32;  
  14.   
  15. static struct pci_device_id ids[] = {  
  16.     { PCI_DEVICE(MEM_PCI_VENDOR_ID, MEM_PCI_DEVICE_ID), },  
  17.     { 0, }  
  18. };  
  19. MODULE_DEVICE_TABLE(pci, ids);  
  20.   
  21. static unsigned char skel_get_revision(struct pci_dev *dev)  
  22. {  
  23.     u8 revision;  
  24.   
  25.     pci_read_config_byte(dev, PCI_REVISION_ID, &revision);  
  26.     return revision;  
  27. }  
  28.   
  29.   
  30. //return 0 means success  
  31. static int probe(struct pci_dev *dev, const struct pci_device_id *id)  
  32. {  
  33.         /* Do probing type stuff here.   
  34.          * Like calling request_region(); 
  35.          */   
  36.     unsigned char revision_id;  
  37.     int bar ;  
  38.   
  39.     if (skel_get_revision(dev) != MEM_PCI_REVISION_ID)  
  40.         return 1;  
  41.   
  42.         pci_enable_device(dev);  
  43.   
  44.     bar = 1;  
  45.     resource_size_t start = pci_resource_start(dev, bar);  
  46.     resource_size_t len = pci_resource_len(dev, bar);  
  47.     unsigned long flags = pci_resource_flags(dev, bar);  
  48.   
  49.     void __iomem * addressio = pci_iomap(dev,bar,len);  
  50.     *(byte *)addressio = 0x57;  
  51.     iowrite8(0x89,addressio + 8);  
  52.     printk("%x\n",ioread8(addressio + 8));  
  53.     printk("%x\n",*(byte *)addressio);  
  54.   
  55.     return 0;  
  56. }  
  57.   
  58. static void remove(struct pci_dev *dev)  
  59. {  
  60.     /* clean up any allocated resources and stuff here. 
  61.     * like call release_region(); 
  62.     */  
  63.     pci_disable_device(dev);  
  64. }  
  65.   
  66. static struct pci_driver pci_driver = {  
  67.     .name = "mem_pci",  
  68.     .id_table = ids,  
  69.     .probe = probe,  
  70.     .remove = remove,  
  71. };  
  72.   
  73. static int __init mem_pci_init(void)  
  74. {  
  75.     return pci_register_driver(&pci_driver);  
  76. }  
  77.   
  78. static void __exit mem_pci_exit(void)  
  79. {  
  80.     pci_unregister_driver(&pci_driver);  
  81. }  
  82.   
  83. MODULE_LICENSE("GPL");  
  84. MODULE_AUTHOR("gudujian");    
  85.   
  86. module_init(mem_pci_init);  
  87. module_exit(mem_pci_exit);  

上面的驱动程序在pci设备的首字节写了一个字符0x57.在第8个字节写了一个字符0x89.并读出来:

在qemu中增加pci设备并用linux驱动验证

上面只是实现了一个简单的字节读写功能,有兴趣的可以参考我以前的文章 http://blog.csdn.net/xsckernel/article/details/8159568 把pci驱动实现成一个字符驱动。


本文部分参考文章:

http://blog.csdn.net/yearn520/article/details/6576875

http://blog.csdn.net/yearn520/article/details/6577988