声明本文主要针对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端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)
在文件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