声明本文主要针对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
在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); /// 注册设备结构 }
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端pci设备 mem_pci.c 实现方式如下所示:
/* * 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)
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