QEMU QDEV代码分析
Qemu Qdev设备原理,代码实现相关内容整理。
Qdev主要为了解决之前qemu没有统一的设备模型,导致设备配置方式混乱。
另外Qdev实现了guest设备的模拟,以及将向guest暴露host设备。
bus和device构成了一个设备树,设备树的根为sysBus。
- 原有的设备配置方式,设备类型不同,配置方式各异
-drive if=TYPE,index=IDX,bus=BUS,unit=UNIT,HOST-OPTS. . .
-usbdevice disk:format=FMT:FILENAME
-serial CHARDEV
-parallel CHARDEV
-usbdevice serial:vendorid=VID,productid=PRID,CHARDEV
-usbdevice NAME
-virtioconsole CHARDEV
-net nic,vlan=VLAN,macaddr=MACADDR,model=MODEL,name=ID,addr=STR,vectors=V
-usbdevice net:vlan=VLAN,macaddr=MACADDR,name=ID,addr=STR,vectors=V
-vga VGA
-soundhw C1,...
-watchdog NAME
-pcidevice host=ADDR,dma=none,id=ID
-usbdevice host:auto:BUS.ADDR:VID:PRID
- 设备树的例子
(qemu) info qtree
bus: main-system-bus
type System
dev: hpet, id ""
gpio-in 2
gpio-out 1
timers = 3
msi = off
irq 32
mmio 00000000fed00000/0000000000000400
dev: kvm-ioapic, id ""
gpio-in 24
gsi_base = 0
irq 0
mmio 00000000fec00000/0000000000001000
dev: i440FX-pcihost, id ""
irq 0
bus: pci.0
type PCI
dev: virtio-balloon-pci, id ""
indirect_desc = on
event_idx = on
class = 0xff
addr = 04.0
romfile =
rombar = 1
multifunction = off
command_serr_enable = on
class Class 00ff, addr 00:04.0, pci id 1af4:1002 (sub 1af4:0005)
bar 0: i/o at 0xc040 [0xc05f]
dev: PIIX4_PM, id ""
smb_io_base = 45312
disable_s3 = 0
disable_s4 = 0
s4_val = 2
addr = 01.3
romfile =
rombar = 1
multifunction = off
command_serr_enable = on
class Bridge, addr 00:01.3, pci id 8086:7113 (sub 1af4:1100)
bus: i2c
type i2c-bus
dev: smbus-eeprom, id ""
address = 87
dev: smbus-eeprom, id ""
address = 86
dev: smbus-eeprom, id ""
address = 85
dev: smbus-eeprom, id ""
address = 84
dev: smbus-eeprom, id ""
address = 83
dev: smbus-eeprom, id ""
address = 82
dev: smbus-eeprom, id ""
address = 81
dev: smbus-eeprom, id ""
address = 80
dev: piix3-ide, id ""
addr = 01.1
romfile =
rombar = 1
multifunction = off
command_serr_enable = on
class IDE controller, addr 00:01.1, pci id 8086:7010 (sub 1af4:1100)
bar 4: i/o at 0xc060 [0xc06f]
bus: ide.1
type IDE
dev: ide-cd, id ""
drive = ide1-cd0
logical_block_size = 512
physical_block_size = 512
min_io_size = 0
opt_io_size = 0
bootindex = -1
discard_granularity = 0
ver = "1.3.50"
wwn = 0x0
serial = "QM00003"
model =
unit = 0
bus: ide.0
type IDE
dev: ide-hd, id ""
drive = ide0-hd0
logical_block_size = 512
physical_block_size = 512
min_io_size = 0
opt_io_size = 0
bootindex = -1
discard_granularity = 0
ver = "1.3.50"
wwn = 0x0
serial = "QM00001"
model =
cyls = 16383
heads = 16
secs = 63
bios-chs-trans = lba
unit = 0
dev: e1000, id ""
mac = 52:54:00:12:34:56
vlan = 0
netdev = hub0port0
bootindex = -1
addr = 03.0
romfile = "pxe-e1000.rom"
rombar = 1
multifunction = off
command_serr_enable = on
class Ethernet controller, addr 00:03.0, pci id 8086:100e (sub 1af4:1100)
bar 0: mem at 0xfeba0000 [0xfebbffff]
bar 1: i/o at 0xc000 [0xc03f]
bar 6: mem at 0xffffffffffffffff [0x1fffe]
dev: cirrus-vga, id ""
vgamem_mb = 8
addr = 02.0
romfile = "vgabios-cirrus.bin"
rombar = 1
multifunction = off
command_serr_enable = on
class VGA controller, addr 00:02.0, pci id 1013:00b8 (sub 1af4:1100)
bar 0: mem at 0xfc000000 [0xfdffffff]
bar 1: mem at 0xfebf0000 [0xfebf0fff]
bar 6: mem at 0xffffffffffffffff [0xfffe]
dev: PIIX3, id ""
addr = 01.0
romfile =
rombar = 1
multifunction = on
command_serr_enable = on
class ISA bridge, addr 00:01.0, pci id 8086:7000 (sub 1af4:1100)
bus: isa.0
type ISA
dev: isa-fdc, id ""
iobase = 0x3f0
irq = 6
dma = 2
driveA = floppy0
driveB =
bootindexA = -1
bootindexB = -1
check_media_rate = on
isa irq 6
dev: port92, id ""
dev: vmmouse, id ""
dev: vmport, id ""
dev: i8042, id ""
isa irqs 1,12
dev: isa-parallel, id ""
index = 0
iobase = 0x378
irq = 7
chardev = parallel0
isa irq 7
dev: isa-serial, id ""
index = 0
iobase = 0x3f8
irq = 4
chardev = serial0
wakeup = 0
isa irq 4
dev: isa-pcspk, id ""
iobase = 0x61
dev: kvm-pit, id ""
gpio-in 1
iobase = 0x40
lost_tick_policy = delay
dev: mc146818rtc, id ""
base_year = 0
lost_tick_policy = discard
dev: kvm-i8259, id ""
iobase = 0xa0
elcr_addr = 0x4d1
elcr_mask = 0xde
master = off
dev: kvm-i8259, id ""
iobase = 0x20
elcr_addr = 0x4d0
elcr_mask = 0xf8
master = on
dev: i440FX, id ""
addr = 00.0
romfile =
rombar = 1
multifunction = off
command_serr_enable = on
class Host bridge, addr 00:00.0, pci id 8086:1237 (sub 1af4:1100)
bus: membus.pv
type dimmbus
bus: membus.0
type dimmbus
dev: dimm, id "dimm1"
start = 1207959552
size = 1.000G
node = 0
populated = on
dev: dimm, id "dimm0"
start = 134217728
size = 1.000G
node = 0
populated = on
dev: fw_cfg, id ""
ctl_iobase = 0x510
data_iobase = 0x511
irq 0
mmio ffffffffffffffff/0000000000000002
mmio ffffffffffffffff/0000000000000001
dev: pc-sysfw, id ""
rom_only = 1
irq 0
dev: kvmclock, id ""
irq 0
dev: kvm-apic, id ""
id = 3
vapic = on
irq 0
mmio ffffffffffffffff/0000000000100000
dev: kvm-apic, id ""
id = 2
vapic = on
irq 0
mmio ffffffffffffffff/0000000000100000
dev: kvm-apic, id ""
id = 1
vapic = on
irq 0
mmio ffffffffffffffff/0000000000100000
dev: kvmvapic, id ""
irq 0
dev: kvm-apic, id ""
id = 0
vapic = on
irq 0
mmio 00000000fee00000/0000000000100000
(qemu)
QDEV设备用法
- 配置device
通过“-device"指定 - 热插
通过device_add命令添加 - 热拔
通过device_del命令添加 - 读取配置文件
通过"-readconfig"命令
基本概念
- device
记录device的状态。设备只能有1个parent。一个device向下可以有0到多个bus。 device连接两个bus,并使之协做。device不知道上位bus是哪个device所暴露的。 device可以是设备树的叶子节点,这种情况最简单,他们通常只是将消息传递给bus或者的设备(网络,块,字符设备子系统)。或者为bus或者祖父设备提供服务。 通常device含有bus属性和device属性。 Host设备的副本,不属于设备树。 - bus 记录bus的状态。bus并不总是对应于物理世界存在的设备。每个child bus可以关联多个设备。bus对于用户来说是不可见的,bus连接两个device,使之进行协做。bus不知道上位device。
bus和device的层次结构
bus和device处于两个同等的继承树下,BusState和DeviceState。同一bus下的设备拥有同样的超类。 因此,每个bus会定义BusState的子类和DeviceState的抽象子类。然后设备添加他们的实体子类到DeviceState树下。(如下图所示)
BusState
PCIBus
ISABus
i2c_bus
DeviceState
PCIState /* bus common superclass */
LSIState /* device-specific class */
...
ISADevice
IB700State
ISASerialState
...
i2c_slave
WM8750State
...
- bus类(如i2c_bus)通常没太多可说的,主要包含一些私有成员在设备创建时使用。 比如分配给device的IRQ,某些时候甚至没有。比如SysBus重用BusState。
- bus超类(如i2c_slave)通常包含地址和他们所含的中断线。
- 设备子类包含设备所特有的配置信息(比如所连的快设备和字符设备)和寄存器。
元信息及层次结构
如BusInfo,I2CSlaveInfo,DeviceInfo,主要用于存储类信息(属性和虚函数)。元信息的层次结构 模仿了上述BusState和DeviceState。
BusState <=> BusInfo
PCIBus -> struct BusInfo pci_bus_info = ...
ISABus -> struct BusInfo isa_bus_info = ...
i2c_bus -> struct BusInfo i2c_bus_info = ...
DeviceState <=> DeviceInfo
PCIState <=> PCIDeviceInfo
LSIState -> static PCIDeviceInfo lsi_info = ...
...
ISADevice <=> ISADeviceInfo
IB700State -> static ISADeviceInfo wdt_ib700_info = ...
ISASerialState -> static ISADeviceInfo serial_isa_info = ...
...
i2c_slave <=> I2CSlaveInfo
WM8750State -> static I2CSlaveInfo wm8750_info = ...
...
注册Qdev设备
我们一般使用“-device”设置虚拟机的设备。注册设备名主要通过qdev_register函数。根据 Bus不同初始化过程也不相同,bus会为相关的设备进行初步的检查和共通的初始设置。
如图,bus的提供的设备共通注册过程:
void i2c_register_slave(I2CSlaveInfo *info)
{
assert(info->qdev.size >= sizeof(i2c_slave));
info->qdev.init = i2c_slave_qdev_init;
info->qdev.bus_info = &i2c_bus_info;
qdev_register(&info->qdev);
}
如图,设备这么进行注册:
static void wm8750_register_devices(void)
{
i2c_register_slave(&wm8750_info);
}
如图,wm8750_register_devices 在虚拟机初始化时调用。
device_init(wm8750_register_devices)
代码原理、解读
以memory hotplug为例解释qdev。 dimm相关定义了两个TypeInfo, TYPE_DIMM(Device类型)和 TYPE_DIMM_BUS(Bus类型)。
TypeInfo结构如下。其中class_init主要用于类的虚函数的初始化。instance_init主要用于 实例对象成员的初始化。
327 struct TypeInfo
328 {
329 const char *name;
330 const char *parent;
331
332 size_t instance_size;
333 void (*instance_init)(Object *obj);
334 void (*instance_finalize)(Object *obj);
335
336 bool abstract;
337 size_t class_size;
338
339 void (*class_init)(ObjectClass *klass, void *data);
340 void (*class_base_init)(ObjectClass *klass, void *data);
341 void (*class_finalize)(ObjectClass *klass, void *data);
342 void *class_data;
343
344 InterfaceInfo *interfaces;
345 };
继承BusState的DimmBus类。dimm_hotplug提供了设备hotplug的操作,dimm_revert提供了 对固件寄存器的操作。 通过dimm_bus_hotplug进行函数成员的初始化。具体函数实现与北桥芯片类型相关。
74 typedef struct DimmBus {
75 BusState qbus;
76 DeviceState *dimm_hotplug_qdev;
77 dimm_hotplug_fn dimm_hotplug;
78 DimmConfiglist dimmconfig_list;
79 dimm_hotplug_fn dimm_revert;
80 QTAILQ_HEAD(Dimmlist, DimmDevice) dimmlist;
81 QTAILQ_HEAD(dimm_hp_result_head, dimm_hp_result) dimm_hp_result_queue;
82 QLIST_ENTRY(DimmBus) next;
83 } DimmBus;
继承DeviceState的DimmDevice类,添加了dimm设备特有的信息。
45 struct DimmDevice {
46 DeviceState qdev;
47 uint32_t idx; /* index in memory hotplug register/bitmap */
48 ram_addr_t start; /* starting physical address */
49 ram_addr_t size;
50 uint32_t node; /* numa node proximity */
51 uint32_t populated; /* 1 means device has been hotplugged. Default is 0. */
52 MemoryRegion *mr; /* MemoryRegion for this slot. !NULL only if populated */
53 dimm_hp_pending_code pending; /* pending hot operation for this dimm */
54 QTAILQ_ENTRY(DimmDevice) nextdimm;
55 };
参考资料
- “qdev for programmers writeup”
http://lists.nongnu.org/archive/html/qemu-devel/2011-07/msg00842.html