声明本文主要针对x86架构进行说明。
使用的qemu版本是:qemu-kvm-1.2.0-rc2
1)PCI结构简介
每个PCI设备都有一个配置空间和若干个地址空间,按照固定的顺序放置CPI的各种配置参数。关于详细的介绍可以在网上搜索相关的资料。
下面是在busybox下lspci -mk的输出内容说明:
- 00:00.0 "Class 0600" "8086" "1237" "1af4" "1100"
- 00:01.0 "Class 0601" "8086" "7000" "1af4" "1100"
- 00:01.1 "Class 0101" "8086" "7010" "1af4" "1100" "ata_piix"
- 00:01.3 "Class 0680" "8086" "7113" "1af4" "1100"
- 00:02.0 "Class 0300" "1013" "00b8" "1af4" "1100"
- 00:03.0 "Class 0200" "10ec" "8139" "1af4" "1100" "8139cp"
- 00:04.0 "Class 0604" "1011" "0026" "0000" "0000"
- 01:00.0 "Class 3542" "1234" "5678" "6872" "8952"
- 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设备的结构是怎样的:
- static TypeInfo mem_pci_info = {
- .name = "mem_pci",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(PCIMEMPCIState),
- .class_init = mem_pci_class_init, /// pci 设备的初始化函数
- };
- static void mem_pci_register_types(void)
- {
- type_register_static(&mem_pci_info); /// 注册设备结构
- }
在函数mem_pci_class_init里面为PCIDeviceClass的init数据成员赋值mem_pci_init
- static int mem_pci_init(PCIDevice *dev)
- {
- PCIMEMPCIState *pci = DO_UPCAST(PCIMEMPCIState, pci_dev, dev);
- MEMPCIState *s = &pci->state;
- pci->mem_pci_base = (uint32_t)malloc(PCI_MEM_SIZE);
- memory_region_init_io(&s->mem, &mem_pci_ops, pci, "mem-pci", PCI_MEM_SIZE); /// 注册一个MemoryRegion结构体,并分配一个 ///MemoryRegionOps数据成员,这样
- pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem); ///给pci设备注册一个bar类型是mem。
- return 0;
- }
由于本文需要让 linux内核的pci驱动 跟 qemu模拟的pci设备 之间实现通信,注意仅仅是实现数据流的传送。如果操作pci设备空间用qemu提供的api函数 cpu_inb ,那么会导致qemu把通信的数据当作操作设备的命令来执行。所以这里申请了一块内存并用mem_pci_base来指向申请的内存。
当以后需要对这块内存读写操作的时候就可以直接读写这块内存:
- static void mem_pci_write(void *opaque, target_phys_addr_t addr,
- uint64_t value, unsigned int size)
- {
- void *pci_mem_addr;
- int temp,region_size;
- byte buff[8];
- pci_mem_addr = ((PCIMEMPCIState *)opaque)->mem_pci_base;
- pci_mem_addr = ((char *)pci_mem_addr) + addr;
- switch (size) {
- case 1:
- sprintf(buff,"%02llx",value);
- sscanf(buff,"%x",&temp);
- *((byte*)pci_mem_addr) = (byte)temp;
- break;
- }
- }
- /*
- * QEMU memory pci emulation (PCI to ISA bridge)
- *
- */
- #include "pci.h"
- #include "pc.h"
- #include "i8254.h"
- #include "pcspk.h"
- #include "hw.h"
- #define MEM_PCI_VENDOR_ID 0x1234
- #define MEM_PCI_DEVICE_ID 0x5678
- #define MEM_PCI_REVISION_ID 0x73
- #define PCI_MEM_SIZE 0x00000010
- typedef struct MEMPCIState {
- MemoryRegion mem;
- } MEMPCIState;
- typedef struct PCIMEMPCIState {
- PCIDevice pci_dev;
- uint32_t mem_pci_base;
- MEMPCIState state;
- } PCIMEMPCIState;
- static const VMStateDescription vmstate_mem_pci = {
- .name = "mem_pci",
- .version_id = 0,
- .minimum_version_id = 0,
- .fields = (VMStateField[]) {
- VMSTATE_PCI_DEVICE(pci_dev, PCIMEMPCIState),
- VMSTATE_END_OF_LIST()
- },
- };
- typedef unsigned char byte;
- typedef unsigned short int uint16;
- typedef unsigned int uint32;
- static void mem_pci_write(void *opaque, target_phys_addr_t addr,
- uint64_t value, unsigned int size)
- {
- void *pci_mem_addr;
- int temp,region_size;
- byte buff[8];
- pci_mem_addr = ((PCIMEMPCIState *)opaque)->mem_pci_base;
- pci_mem_addr = ((char *)pci_mem_addr) + addr;
- region_size = (int)memory_region_size( &((PCIMEMPCIState *)opaque)->state.mem);
- if(addr > region_size)
- return ;
- fprintf(stderr,"%x\n",pci_mem_addr);
- switch (size) {
- case 1:
- sprintf(buff,"%02llx",value);
- sscanf(buff,"%x",&temp);
- *((byte*)pci_mem_addr) = (byte)temp;
- break;
- case 2:
- sprintf(buff,"%04llx",value);
- sscanf(buff,"%x",&temp);
- *((uint16*)pci_mem_addr)= (uint16)temp;
- break;
- case 4:
- sprintf(buff,"%08llx",value);
- sscanf(buff,"%x",&temp);
- *((uint32*)pci_mem_addr)= (uint32)temp;
- break;
- }
- fprintf(stderr,"%x\n",temp);
- }
- static uint64_t mem_pci_read(void *opaque, target_phys_addr_t addr,
- unsigned int size)
- {
- void *pci_mem_addr;
- int temp,region_size;
- byte buff[8];
- pci_mem_addr = ((PCIMEMPCIState *)opaque)->mem_pci_base;
- pci_mem_addr = ((char *)pci_mem_addr) + addr;
- region_size = memory_region_size(&((PCIMEMPCIState *)opaque)->state.mem);
- if(addr > region_size)
- return 0;
- switch (size) {
- case 1:
- temp = *((byte *)pci_mem_addr);
- return ((byte)temp);
- case 2:
- temp = *((uint16 *)pci_mem_addr);
- return ((uint16)temp);
- case 4:
- temp = *((uint32 *)pci_mem_addr);
- return ((uint32)temp);
- }
- //fprintf(stderr,"%d",temp);
- }
- static const MemoryRegionOps mem_pci_ops = {
- .read = mem_pci_read,
- .write = mem_pci_write,
- .endianness = DEVICE_LITTLE_ENDIAN,
- };
- static Property mem_pci_properties[] = {
- DEFINE_PROP_HEX32("membase", PCIMEMPCIState, mem_pci_base, 0xc0000000),
- DEFINE_PROP_END_OF_LIST()
- };
- static int mem_pci_init(PCIDevice *dev)
- {
- PCIMEMPCIState *pci = DO_UPCAST(PCIMEMPCIState, pci_dev, dev);
- MEMPCIState *s = &pci->state;
- pci->mem_pci_base = (uint32_t)malloc(PCI_MEM_SIZE);
- memory_region_init_io(&s->mem, &mem_pci_ops, pci, "mem-pci", PCI_MEM_SIZE);
- pci_register_bar(dev, 1, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem);
- return 0;
- }
- static void mem_pci_class_init(ObjectClass *klass, void *data)
- {
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
- DeviceClass *dc = DEVICE_CLASS(klass);
- k->init = mem_pci_init;
- k->vendor_id = MEM_PCI_VENDOR_ID;
- k->device_id = MEM_PCI_DEVICE_ID;
- k->revision = MEM_PCI_REVISION_ID;
- dc->vmsd = &vmstate_mem_pci;
- dc->props = mem_pci_properties;
- }
- static TypeInfo mem_pci_info = {
- .name = "mem_pci",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(PCIMEMPCIState),
- .class_init = mem_pci_class_init,
- };
- static void mem_pci_register_types(void)
- {
- type_register_static(&mem_pci_info);
- }
- type_init(mem_pci_register_types)
在文件hw/pc_piix.c 函数pc_init1里面增加创建设备的代码:
- pc_cmos_init(below_4g_mem_size, above_4g_mem_size, boot_device,
- floppy, idebus[0], idebus[1], rtc_state);
- pci_create_simple_multifunction(pci_bus, -1,true ,"mem_pci");
- if (pci_enabled && usb_enabled) {
- pci_create_simple(pci_bus, piix3_devfn + 2, "piix3-usb-uhci");
- }
把源文件mem_pci.c放在hw目录下,在文件hw/Makefile.objs 增加如下代码:
- hw-obj-y += mem_pci.o
然后编译。
linux内核一端需要有一个pci驱动来驱动这个我们模拟的pci设备,这里仅仅是一个简单的pci设备驱动,关于其框架不多说了,网上有很多教程。直接给出代码吧:
(注意对比这个驱动代码和上面的设备代码相同的地方)
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/pci.h>
- #include <linux/init.h>
- #define MEM_PCI_VENDOR_ID 0x1234
- #define MEM_PCI_DEVICE_ID 0x5678
- #define MEM_PCI_REVISION_ID 0x73
- typedef unsigned char byte;
- typedef unsigned short int uint16;
- typedef unsigned int uint32;
- static struct pci_device_id ids[] = {
- { PCI_DEVICE(MEM_PCI_VENDOR_ID, MEM_PCI_DEVICE_ID), },
- { 0, }
- };
- MODULE_DEVICE_TABLE(pci, ids);
- static unsigned char skel_get_revision(struct pci_dev *dev)
- {
- u8 revision;
- pci_read_config_byte(dev, PCI_REVISION_ID, &revision);
- return revision;
- }
- //return 0 means success
- static int probe(struct pci_dev *dev, const struct pci_device_id *id)
- {
- /* Do probing type stuff here.
- * Like calling request_region();
- */
- unsigned char revision_id;
- int bar ;
- if (skel_get_revision(dev) != MEM_PCI_REVISION_ID)
- return 1;
- pci_enable_device(dev);
- bar = 1;
- resource_size_t start = pci_resource_start(dev, bar);
- resource_size_t len = pci_resource_len(dev, bar);
- unsigned long flags = pci_resource_flags(dev, bar);
- void __iomem * addressio = pci_iomap(dev,bar,len);
- *(byte *)addressio = 0x57;
- iowrite8(0x89,addressio + 8);
- printk("%x\n",ioread8(addressio + 8));
- printk("%x\n",*(byte *)addressio);
- return 0;
- }
- static void remove(struct pci_dev *dev)
- {
- /* clean up any allocated resources and stuff here.
- * like call release_region();
- */
- pci_disable_device(dev);
- }
- static struct pci_driver pci_driver = {
- .name = "mem_pci",
- .id_table = ids,
- .probe = probe,
- .remove = remove,
- };
- static int __init mem_pci_init(void)
- {
- return pci_register_driver(&pci_driver);
- }
- static void __exit mem_pci_exit(void)
- {
- pci_unregister_driver(&pci_driver);
- }
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("gudujian");
- module_init(mem_pci_init);
- module_exit(mem_pci_exit);
上面的驱动程序在pci设备的首字节写了一个字符0x57.在第8个字节写了一个字符0x89.并读出来:
上面只是实现了一个简单的字节读写功能,有兴趣的可以参考我以前的文章 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