設備模擬目的
我們好像不會幹一件事而毫無目的,就算不停刷微信朋友圈也是爲了打發你無聊的時間。
其實最裝B的回答是:設備模擬的目的就是模擬設備。這話是屁話,不過也能說明些什麼,確實是模擬設備,用軟件的方式提供硬件設備具備的功能。
對於和PC機交互的硬件設備,主要要幹兩件事,一是提供IRQ中斷,二是響應IO輸入輸出。IO包括PIO/MMIO/DMA等(DMA算不算IO?)
以i8254.c實現的pit爲例,主要提供了IRQ注入和PIO響應,見初始化函數pit_initfn:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
staticconstMemoryRegionOpspit_ioport_ops={
.read=pit_ioport_read,
.write=pit_ioport_write,
.impl={
.min_access_size=1,
.max_access_size=1,
},
.endianness=DEVICE_LITTLE_ENDIAN,
};
staticintpit_initfn(PITCommonState*pit)
{
PITChannelState*s;
s=&pit->channels[0];
/* the timer 0 is connected to an IRQ */
//這裏有個irq_timer,用於qemu_set_irq提供中斷注入
s->irq_timer=qemu_new_timer_ns(vm_clock,pit_irq_timer,s);
qdev_init_gpio_out(&pit->dev.qdev,&s->irq,1);
memory_region_init_io(&pit->ioports,&pit_ioport_ops,pit,"pit",4);
qdev_init_gpio_in(&pit->dev.qdev,pit_irq_control,1);
return0;
}
|
這裏的pit_ioport_ops,主要註冊GUEST操作系統讀寫PIO時候的回調函數。
模塊註冊
QEMU要模擬模塊那麼多,以程序員的喜好,至少得來一套管理這些模擬設備模塊的接口,以示設計良好。
QEMU將被模擬的模塊分爲了四類:
1
2
3
4
5
6
7
|
typedefenum{
MODULE_INIT_BLOCK,
MODULE_INIT_MACHINE,
MODULE_INIT_QAPI,
MODULE_INIT_QOM,
MODULE_INIT_MAX
}module_init_type;
|
-
BLOCK
比如磁盤IO就屬於BLOCK類型,e.g: block_init(bdrv_qcow2_init); block_init(iscsi_block_init); -
MACHINE
PC虛擬machine_init(pc_machine_init); XEN半虛擬化machine_init(xenpv_machine_init); MIPS虛擬machine_init(mips_machine_init); -
QAPI
QEMU GUEST AGENT模塊裏面會執行QAPI註冊的回調 -
QOM
QOM樹大枝多,兒孫滿堂,應該是這裏面最複雜的一個,我們重點介紹。
e.g:ObjectClass -> PCIDeviceClass //顯卡type_init(cirrus_vga_register_types),網卡 type_init(rtl8139_register_types) IDEDeviceClass //IDE硬盤或CD-ROM type_init(ide_register_types) ISADeviceClass //鼠標鍵盤type_init(i8042_register_types),RTC時鐘type_init(pit_register) SysBusDeviceClass //MMIO IDE(IDE設備直接連接CPU bus而不是連接IDE controller)type_init(mmio_ide_register_types) CPUClass -> X86CPUClass //X86 CPU架構 -> CRISCPUClass12345ObjectClass-> PCIDeviceClass //顯卡type_init(cirrus_vga_register_types),網卡type_init(rtl8139_register_types)IDEDeviceClass //IDE硬盤或CD-ROM type_init(ide_register_types)ISADeviceClass //鼠標鍵盤type_init(i8042_register_types),RTC時鐘type_init(pit_register)SysBusDeviceClass//MMIO IDE(IDE設備直接連接CPU bus而不是連接IDE controller)type_init(mmio_ide_register_types) CPUClass -> X86CPUClass //X86 CPU架構-> CRISCPUClass
註冊QOM設備的時候,使用QEMU提供的宏,type_init宏進行註冊:
1
2
3
4
5
|
#define type_init(function) module_init(function, MODULE_INIT_QOM)
#define module_init(function, type) \
staticvoid__attribute__((constructor))do_qemu_init_## function(void) { \
register_module_init(function,type);\
}
|
這和寫LINUX驅動類似,一般寫在一個模塊實現文件的最底部,以pit爲例,寫的是type_init(pit_register_types)展開後爲:
1
2
3
4
|
staticvoid__attribute__((constructor))do_qemu_init_pit_register_types(void)
{
register_module_init(pit_register_types,MODULE_INIT_QOM);
}
|
那麼,這個do_qemu_init_pit_register_types何時調用?
在gcc裏面,給函數加上__attribute__((destructor)),表示此函數需要在main開始前自動調用,測試調用順序是: 全局對象構造函數 -> __attribute__((constructor)) -> main -> 全局對象析構函數 -> __attribute__((destructor))。
調用register_module_init就是將pit_register_types回調函數插入util\module.c裏定義的init_type_list[MODULE_INIT_QOM]鏈表內。
1
2
3
4
5
6
7
8
9
|
voidregister_module_init(void(*fn)(void),module_init_typetype)
{
ModuleEntry*e;
ModuleTypeList*l;
e=g_malloc0(sizeof(*e));
e->init=fn;//init指針被設置爲fn
l=find_type(type);
QTAILQ_INSERT_TAIL(l,e,node);
}
|
通過下面main函數的部分代碼可以看出,模塊初始化順序是QOM->MACHINE->BLOCK,至於QAPI,在這個流程裏沒看到。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
voidmain()
{
module_call_init(MODULE_INIT_QOM);//初始化設備
qemu_add_opts//初始化默認選項
module_call_init(MODULE_INIT_MACHINE);//初始化機器類型
machine=find_default_machine();//這裏對machine賦值,下面還會通過參數更改machine
vtp_script_execute(g_qemu_start_hook_path,g_fairsched_string,TYPE_START);//開機啓動腳本的調用
深度分析啓動參數
bdrv_init_with_whitelist->bdrv_init->module_call_init(MODULE_INIT_BLOCK);//初始化BLOCK設備
machine->init(&args);//初始化machine
qemu_run_machine_init_done_notifiers();//初始化成功回調通知
qemu_system_reset(VMRESET_SILENT);//system reset 啓動運行
if(loadvm){
load_vmstate(loadvm);
}elseif(loadstate){
load_state_from_blockdev(loadstate);
}
resume_all_vcpus();
main_loop();//進入主循環
}
|
在main函數進來的時候,首先調用module_call_init(MODULE_INIT_QOM);
1
2
3
4
5
6
7
8
9
|
voidmodule_call_init(module_init_typetype)
{
ModuleTypeList*l;
ModuleEntry*e;
l=find_type(type);
QTAILQ_FOREACH(e,l,node){
e->init();//這裏,就是調用剛纔註冊的回調,例如,對於kvm-pit來說,調用的是pit_register
}
}
|
此module_call_init將依次調用註冊的回調,如PIT的pit_register_types:
1
2
3
4
5
6
7
8
9
10
11
|
staticconstTypeInfopit_info={
.name ="isa-pit", //做爲type_table的key
.parent ="pit-common", //父類型,這個比較重要,如果本TypeInfo沒有設置class_size,會根據parent獲取parent TypeImpl的class_size
.instance_size=sizeof(PITCommonState),//分配實例的大小
.class_init=pit_class_init, //初始化函數
};
staticvoidpit_register_types(void)
{
type_register_static(&pit_info);
}
|
pit_register_types又進一步調用type_register_static -> type_register -> type_register_internal,這個函數完成的功能其實只是在qom\object.c的type_table裏插入了一個HASH鍵值 對,以TypeInfo的name爲KEY,malloc了一個TypeInfo結構的超集TypeImpl爲VALUE,在以name爲KEY回溯 parent時需要TypeImpl,其實這個hash也可以做成一個tree。
QOM的Object模型
以pit爲例,通過回溯parent你可以看到,其定義TypeInfo最終形成一個繼承關係:
"isa-pit" -> "pit-common" -> "isa-device" -> "device" -> "object"
qom\object.c
1
2
3
4
5
6
|
staticTypeInfoobject_info={
.name="object",
.instance_size=sizeof(Object),
.instance_init=object_instance_init,
.abstract=true,
};
|
hw\qdev.c
1
2
3
4
5
6
7
8
9
10
11
|
staticconstTypeInfodevice_type_info={
.name="device",
.parent="object",
.instance_size=sizeof(DeviceState),
.instance_init=device_initfn,
.instance_finalize=device_finalize,
.class_base_init=device_class_base_init,
.class_init=device_class_init,
.abstract=true,
.class_size=sizeof(DeviceClass),
};
|
hw\isa-bus.c
1
2
3
4
5
6
7
8
|
staticconstTypeInfoisa_device_type_info={
.name="isa-device",
.parent="device",
.instance_size=sizeof(ISADevice),
.abstract=true,
.class_size=sizeof(ISADeviceClass),
.class_init=isa_device_class_init,
};
|
hw\i8254_common.c
1
2
3
4
5
6
7
8
|
staticconstTypeInfopit_common_type={
.name ="pit-common",
.parent ="isa-device",
.instance_size=sizeof(PITCommonState),
.class_size =sizeof(PITCommonClass),
.class_init =pit_common_class_init,
.abstract =true,
};
|
hw\i8254.c
1
2
3
4
5
6
|
staticconstTypeInfopit_info={
.name ="isa-pit",
.parent ="pit-common",
.instance_size=sizeof(PITCommonState),
.class_init =pit_class_initfn,
};
|
由於TypeInfo只是註冊時臨時使用,而TypeImpl是TypeInfo的超集,所以,這層關係也反應了TypeImpl的繼承關係。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
structTypeImpl
{
constchar*name;
size_tclass_size;
size_tinstance_size;
void(*class_init)(ObjectClass*klass,void*data);
void(*class_base_init)(ObjectClass*klass,void*data);
void(*class_finalize)(ObjectClass*klass,void*data);
void*class_data;
void(*instance_init)(Object*obj);
void(*instance_finalize)(Object*obj);
boolabstract;
constchar*parent;
TypeImpl*parent_type;
ObjectClass*class;
intnum_interfaces;
InterfaceImplinterfaces[MAX_INTERFACES];
};
|
Figure 1 TypeImpl圖解
打印查看TypeImpl屬性:
$13 = {name = 0x5555566e5e30 "mc146818rtc", class_size = 128, instance_size = 664, class_init = 0x55555579b790 , class_base_init = 0,
class_finalize = 0, class_data = 0x0, instance_init = 0, instance_finalize = 0, abstract = false, parent = 0x5555566e5e50 "isa-device",
parent_type = 0x5555566d8bd0, class = 0x555556a50e50, num_interfaces = 0, interfaces = {{typename = 0x0} }}
其主要包含如下部分:
- name/parent/parent_type 表示自己的,父親的KEY和TypeImpl指針。
- class/class_size/class_init/class_base_init/class_finalize/class_data 和ObjectClass聯繫,組成繼承關係。
- instance_init/instance_finalize和ObjectClass有裙帶關係的Object,共同完成繼承體系。
- num_interfaces/interfaces 用於管理接口。
Object和ObjectClass的關係
還是通過這條繼承鏈來看:
"isa-pit" -> "pit-common" -> "isa-device" -> "device" -> "object"
其中ObjectClass鏈的定義爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
structObjectClass
{
/*< private >*/
Typetype;
GSList*interfaces;
ObjectUnparent*unparent;
};
typedefstructDeviceClass{
/*< private >*/
ObjectClassparent_class;
/*< public >*/
constchar*fw_name;
constchar*desc;
Property*props;
intno_user;
/* callbacks */
void(*reset)(DeviceState*dev);
DeviceRealizerealize;
DeviceUnrealizeunrealize;
/* device state */
conststructVMStateDescription*vmsd;
/* Private to qdev / bus. */
qdev_initfninit;/* TODO remove, once users are converted to realize */
qdev_eventunplug;
qdev_eventexit;
constchar*bus_type;
}DeviceClass;
typedefstructISADeviceClass{
DeviceClassparent_class;
int(*init)(ISADevice*dev);
}ISADeviceClass;
typedefstructPITCommonClass{
ISADeviceClassparent_class;
int(*init)(PITCommonState*s);
void(*set_channel_gate)(PITCommonState*s,PITChannelState*sc,intval);
void(*get_channel_info)(PITCommonState*s,PITChannelState*sc,
PITChannelInfo*info);
void(*pre_save)(PITCommonState*s);
void(*post_load)(PITCommonState*s);
}PITCommonClass;
|
下層定義包含上層,很明顯的繼承模型,ObjectClass更像C++的CLASS,而Object鏈的定義爲:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
structObject
{
/*< private >*/
ObjectClass*class;
ObjectFree*free;
QTAILQ_HEAD(,ObjectProperty)properties;
uint32_tref;
Object*parent;
};
structDeviceState{
/*< private >*/
Objectparent_obj;
/*< public >*/
constchar*id;
boolrealized;
QemuOpts*opts;
inthotplugged;
BusState*parent_bus;
intnum_gpio_out;
qemu_irq*gpio_out;
intnum_gpio_in;
qemu_irq*gpio_in;
QLIST_HEAD(,BusState)child_bus;
intnum_child_bus;
intinstance_id_alias;
intalias_required_for_version;
};
structISADevice{
DeviceStateqdev;
uint32_tisairq[2];
intnirqs;
intioport_id;
};
typedefstructPITCommonState{
ISADevicedev;
MemoryRegionioports;
uint32_tiobase;
PITChannelStatechannels[3];
}PITCommonState;
|
有了ObjectClass爲什麼還要有個Object?從代碼看,ObjectClass只有一份實例,而Object是可以多個實例 的,Object引用ObjectClass獲得ObjectClass的特徵,但是同時又節約了初始化和存放ObjectClass的CPU和空間,相 同的ObjectClass可以被多個Object引用,例如scsi-disk.c裏面有"scsi-hd","scsi-cd","scsi- block","scsi-disk"四種Object共同引用了"scsi-device"。這裏可以想象成C++的虛繼承,ObjectClass是
virtual class而Object是class。其實兩者是可以柔和在一起的,Object也有對應的繼承關係,用來保存特定屬性。
Figure 2 ObjectClass和Object 關係
Object和ObjectClass的初始化
上面講的Object和ObjectClass主要是完成一個對象繼承模型,從代碼看QEMU的這個模型實現並不非常很優雅,封裝不夠徹底,就像你媽給你做了條褲子,卻沒有做褲腰帶,還得提着上路。
Object和ObjectClass的初始化方式並不一致,需要分別初始化,ObjectClass通常使用object_class_by_name 獲取,此函數會根據提供的KEY去查找TypeImpl並初始化ObjectClass指針;而Object的初始化是使用的object_new,通過 參數KEY查找TypeImpl然後malloc 實例。以qdev_try_create獲取"isa-pit"的Object DeviceState實例來說,其獲取DeviceState的函數如此定義:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
|
DeviceState*qdev_try_create(BusState*bus,constchar*type)
{
DeviceState*dev;
//這個type爲TypeInfo.name,例如"isa-pit"
if(object_class_by_name(type)==NULL){
returnNULL;
}
//type_initialize完成後,object_new用來實例化一個instance
dev=DEVICE(object_new(type));// = DEVICE(object_new_with_type(type_get_by_name(typename)))
if(!dev){
returnNULL;
}
if(!bus){
bus=sysbus_get_default();
}
qdev_set_parent_bus(dev,bus);
object_unref(OBJECT(dev));
returndev;
}
ObjectClass*object_class_by_name(constchar*typename)
{
//之前在type_register_static的時候,註冊了TypeInfo.name,例如"isa-pit"爲key的TypeImpl
TypeImpl*type=type_get_by_name(typename);
if(!type){
returnNULL;
}
type_initialize(type);//這裏面,初始化class,
returntype->class;
}
//其實這個函數更應該叫做new_TypeInfo_class()
staticvoidtype_initialize(TypeImpl*ti)
{
TypeImpl*parent;
if(ti->class){
return;
}
/*
type_class_get_size 首先獲取自己的class_size變量,如果沒有,再找parent類型所指的TypeImpl的class_size,直到找到爲止
比如"isa-pit"沒有設置class_size,那麼獲取的是"pit-common"的class_size, 而type_object_get_size也是類似
static const TypeInfo pit_common_type = {
.name = "pit-common",
.parent = "isa-device",
.instance_size = sizeof(PITCommonState),
.class_size = sizeof(PITCommonClass),
.class_init = pit_common_class_init,
.abstract = true,
};
*/
ti->class_size=type_class_get_size(ti);
ti->instance_size=type_object_get_size(ti);
ti->class=g_malloc0(ti->class_size);
parent=type_get_parent(ti);
if(parent){
//1,保證parent初始化了
type_initialize(parent);
GSList*e;
inti;
//2,將parent的class內容memcpy一份給自己的對應的parent區域
g_assert(parent->class_size<=ti->class_size);
memcpy(ti->class,parent->class,parent->class_size);
//3,將parent裏面的class的interfaces做一次深度複製,複製給自己
for(e=parent->class->interfaces;e;e=e->next){
ObjectClass*iface=e->data;
type_initialize_interface(ti,object_class_get_name(iface));
}
//4.如果本類型有自己的interfaces,初始化
for(i=0;inum_interfaces;i++){
TypeImpl*t=type_get_by_name(ti->interfaces[i].typename);
for(e=ti->class->interfaces;e;e=e->next){
TypeImpl*target_type=OBJECT_CLASS(e->data)->type;
if(type_is_ancestor(target_type,t)){
break;
}
}
if(e){
continue;
}
type_initialize_interface(ti,ti->interfaces[i].typename);
}
}
ti->class->type=ti;
while(parent){
if(parent->class_base_init){
//回溯回調parent的class_base_init函數
parent->class_base_init(ti->class,ti->class_data);
}
parent=type_get_parent(parent);
}
if(ti->class_init){
/*
如果本類設置了class_init,回調它,ti->class_data是一個void*的參數
比如"isa-pit"我們設置了pit_class_initfn
這個函數主要幹啥?主要填充class裏的其他該填充的地方。
malloc之後你總得調用構造函數吧,調用構造函數的第一句都是super(xxx)
這工作前面,2,3步驟已經做了,然後幹你自己的活。見pit_class_initfn定義
*/
ti->class_init(ti->class,ti->class_data);
}
}
|
上述代碼把object_class_by_name的流程說完了,再看看object_new(type) = object_new_with_type(type_get_by_name(typename))的流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
Object*object_new_with_type(Typetype)
{
Object*obj;
g_assert(type!=NULL);
type_initialize(type);
obj=g_malloc(type->instance_size);//這個instance_size是初始化TypeInfo的時候設置的sizeof(PITCommonState)
object_initialize_with_type(obj,type);
obj->free=g_free;
returnobj;
}
voidobject_initialize_with_type(void*data,TypeImpl*type)
{
Object*obj=data;
g_assert(type!=NULL);
type_initialize(type);
g_assert(type->instance_size>=sizeof(Object));
g_assert(type->abstract==false);
memset(obj,0,type->instance_size);
obj->class=type->class;//instace的類型通過class指針指定
object_ref(obj);
QTAILQ_INIT(&obj->properties);
object_init_with_type(obj,type);//深度遞歸調用TypeImpl及其parent的instance_init函數指針,相當於new instance的構造函數
}
|
qdev_try_create->object_class_by_name->type_initialize的調用流程,如果父 ObjectClass沒初始化,會初始化父ObjectClass,此時調用到父ObjectClass對應name的TypeImpl的 class_init函數,例如"pit-common"的class_init回調pit_common_class_init,此回調會設置 ISADeviceClass的init回調爲pit_init_common。
Object類型轉換
再解釋下Object裏常用的一個宏:OBJECT_CHECK,以ISA_DEVICE這段代碼爲例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
staticconstTypeInfomc146818rtc_info={
.name ="mc146818rtc",
.parent ="isa-device",
.instance_size=sizeof(RTCState),
.class_init =rtc_class_initfn,
};
ISADevice*isa_create(ISABus*bus,constchar*name)
{
DeviceState*dev;
if(!bus){
hw_error("Tried to create isa device %s with no isa bus present.",name);
}
dev=qdev_create(&bus->qbus,name);
returnISA_DEVICE(dev);//毫無疑問,"mc146818rtc"肯定可以轉換爲"isa-device",why?見mc146818rtc_info定義
}
ISADevice*dev=isa_create(bus,"mc146818rtc");
|
這裏的ISA_DEVICE體現了OBJECT的類型轉換功能,宏定義爲:
1
2
3
4
5
6
|
#define OBJECT(obj) \
((Object*)(obj))
#define OBJECT_CHECK(type, obj, name) \
((type*)object_dynamic_cast_assert(OBJECT(obj),(name)))
#define ISA_DEVICE(obj) \
OBJECT_CHECK(ISADevice,(obj),TYPE_ISA_DEVICE)
|
展開後爲:
1
|
#define ISA_DEVICE(dev) (ISADevice*)object_class_dynamic_cast(((Object *)dev)->class, "isa-device")
|
object.c裏面定義了object_class_dynamic_cast函數,其實此函數功能比較簡單,就是通過遍歷parent看當前class是否有一個祖先是typename,其定義如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
ObjectClass*object_class_dynamic_cast(ObjectClass*class,
constchar*typename)
{
TypeImpl*target_type=type_get_by_name(typename);//找到"isa-device"對應的TypeImpl*
TypeImpl*type=class->type;//本ObjectClass真實的TypeImpl*,其實這裏是"mc146818rtc"
ObjectClass*ret=NULL;
/*
(gdb) p *target_type
$31 = {name = 0x5555566d0b20 "isa-device", class_size = 128, instance_size = 160, class_init = 0x55555568ddd0 , class_base_init = 0,
class_finalize = 0, class_data = 0x0, instance_init = 0, instance_finalize = 0, abstract = true, parent = 0x5555566d0a40 "device", parent_type = 0x5555566da730,
class = 0x555556a14750, num_interfaces = 0, interfaces = {{typename = 0x0} }}
(gdb) p *type_interface
$33 = {name = 0x5555566e40b0 "interface", class_size = 32, instance_size = 0, class_init = 0, class_base_init = 0, class_finalize = 0, class_data = 0x0,
instance_init = 0, instance_finalize = 0, abstract = true, parent = 0x0, parent_type = 0x0, class = 0x0, num_interfaces = 0, interfaces = {{
typename = 0x0} }}
type_is_ancestor 用於判斷,"interface"是否是"isa-device"的祖先(判斷方法是遞歸遍歷"isa-device"的parent,比較是否 有"interface"),如果是,那麼要考慮interface的情況,這裏不是,且type->num_interfaces爲0
*/
if(type->num_interfaces&&type_is_ancestor(target_type,type_interface)){
intfound=0;
GSList*i;
for(i=class->interfaces;i;i=i->next){
ObjectClass*target_class=i->data;
if(type_is_ancestor(target_class->type,target_type)){
ret=target_class;
found++;
}
}
/* The match was ambiguous, don't allow a cast */
if(found>1){
ret=NULL;
}
}elseif(type_is_ancestor(type,target_type)){
/*
判斷type="mc146818rtc"的祖先是否是target_type="isa-device",
如果是,這裏表示子類class="mc146818rtc"能成功轉換爲父類typename="isa-device"所指的ObjectClass
*/
ret=class;
}
returnret;
}
|
GDB顯示其內部數據爲:
$11 = {class = 0x555556a50e50, free = 0x7ffff7424020 , properties = {tqh_first = 0x555556a17da0, tqh_last = 0x555556a50fd0}, ref = 1, parent = 0x0}
(gdb) p *obj->class //ObjectClass * class
$12 = {type = 0x5555566e5cb0, interfaces = 0x0, unparent = 0x5555556b1ac0 }
(gdb) p *obj->class->type //struct TypeImpl * type
$13 = {name = 0x5555566e5e30 "mc146818rtc", class_size = 128, instance_size = 664, class_init = 0x55555579b790 , class_base_init = 0,
class_finalize = 0, class_data = 0x0, instance_init = 0, instance_finalize = 0, abstract = false, parent = 0x5555566e5e50 "isa-device",
parent_type = 0x5555566d8bd0, class = 0x555556a50e50, num_interfaces = 0, interfaces = {{typename = 0x0} }}
QOM設備初始化
基於Object和ObjectClass實現的QOM設備,何時觸發他的初始化,以PIT爲例,將之前的Object和ObjectClass想象成C++,那麼PIT對應的PitCommonState定義應該類似如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
classPITCommonClass:publicISADeviceClass{
public:
virtualintinit(PITCommonState*s)=0;
};
classISADevice:publicDeviceState{
public:
intnirqs;
intioport_id;
};
classPITCommonState:publicISADevice,publicPITCommonClass{
intinit(PITCommonState*s);
};
|
看吧,QEMU繞了這麼大一個圈子,就想實現這樣一個結構,所以有的時候用C++還是有好處的(雖然本人生理週期現正處於不太喜歡C++時間)。
那麼,何處調用了new PITCommonState()?
這得從main函數開始看,main函數裏面,有machine->init(&args);函數調用,這是對註冊的machine的初始 化,而默認的machine是在pc_piix.c裏面pc_machine_init函數註冊的第一個machine,即:
1
2
3
4
5
6
7
8
9
10
11
|
staticQEMUMachinepc_i440fx_machine_v1_4={
.name="pc-i440fx-1.4",
.alias="pc",
.desc="Standard PC (i440FX + PIIX, 1996)",
.init=pc_init_pci,
.max_cpus=255,
.is_default=1,
.default_machine_opts=KVM_MACHINE_OPTIONS,
DEFAULT_MACHINE_OPTIONS,
};
qemu_register_machine(&pc_i440fx_machine_v1_4);
|
當main函數調用machine->init時,我的實驗環境默認情況其實就是調用的pc_i440fx_machine_v1_4的初始化回調pc_init_pci -> pc_init1,這個函數主要初始化相關PC硬件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
staticvoidpc_init1(MemoryRegion*system_memory,
MemoryRegion*system_io,
ram_addr_tram_size,
constchar*boot_device,
constchar*kernel_filename,
constchar*kernel_cmdline,
constchar*initrd_filename,
constchar*cpu_model,
intpci_enabled,
intkvmclock_enabled)
{
//CPU類型初始化-> cpu_x86_init -> mce_init/qemu_init_vcpu,初始化VCPU
pc_cpus_init(cpu_model);
//初始化acpi_tables
pc_acpi_init("acpi-dsdt.aml");
if(!xen_enabled()){
//ROM, BIOS, RAM相關初始化
fw_cfg=pc_memory_init(system_memory,
kernel_filename,kernel_cmdline,initrd_filename,
below_4g_mem_size,above_4g_mem_size,
rom_memory,&ram_memory);
}
//IRQ,初始化
//VGA初始化
pc_vga_init(isa_bus,pci_enabled?pci_bus:NULL);
/* init basic PC hardware */
pc_basic_device_init(isa_bus,gsi,&rtc_state,&floppy,xen_enabled());//這裏調用pit_init
//初始化網卡
pc_nic_init(isa_bus,pci_bus);
//初始化硬盤,音頻設備
//初始化cmos數據,比如設置cmos rtc時鐘,是否提供PS/2設備
pc_cmos_init(below_4g_mem_size,above_4g_mem_size,boot_device,
floppy,idebus[0],idebus[1],rtc_state);
//初始化USB
if(pci_enabled&&usb_enabled(false)){
pci_create_simple(pci_bus,piix3_devfn+2,"piix3-usb-uhci");
}
}
voidpc_basic_device_init(ISABus*isa_bus,qemu_irq*gsi,
ISADevice**rtc_state,
ISADevice**floppy,
boolno_vmport)
{
//初始化HPET
//初始化mc146818 rtc
//初始化i8042 PIT
pit=pit_init(isa_bus,0x40,pit_isa_irq,pit_alt_irq);
//初始化串口,並口
//初始化vmmouse ps2_mouse
}
|
接下來的流程是pit_init -> isa_create(bus, "isa-pit") -> qdev_create -> qdev_try_create,qdev_try_create的實現在前面已經講了,如上節所述,它分別使用 object_class_by_name和object_new來初始化ObjectClass和Object。
屬性設置
Object同ObjectClass的顯著區別就是Object提供了屬性的概念,以MC146818爲例,其定義時設置了"base_year"和"lost_tick_policy":
1
2
3
4
5
6
|
staticPropertymc146818rtc_properties[]={
DEFINE_PROP_INT32("base_year",RTCState,base_year,1980),
DEFINE_PROP_LOSTTICKPOLICY("lost_tick_policy",RTCState,
lost_tick_policy,LOST_TICK_DISCARD),
DEFINE_PROP_END_OF_LIST(),
};
|
但是用GDB一看:
$15 = {name = 0x555556a17df0 "type", type = 0x555556a17e10 "string", get = 0x555555727ae0 , set = 0,
release = 0x555555727980 , opaque = 0x555556a17d80, node = {tqe_next = 0x555556a17e50, tqe_prev = 0x555556a17af0}}
(gdb) p *obj->properties.tqh_first.node.tqe_next
$21 = {name = 0x555556a17ea0 "realized", type = 0x555556a17ec0 "bool", get = 0x5555557279c0 , set = 0x555555727a40 ,
release = 0x555555727940 , opaque = 0x555556a17e30, node = {tqe_next = 0x555556a17ee0, tqe_prev = 0x555556a17dd0}}
(gdb) p *obj->properties.tqh_first.node.tqe_next.node.tqe_next
$22 = {name = 0x555556a17f30 "base_year", type = 0x555556a1be10 "int32", get = 0x5555556af370 , set = 0x5555556afc60 , release = 0,
opaque = 0x555555d4e1a0, node = {tqe_next = 0x555556a50ee0, tqe_prev = 0x555556a17e80}}
(gdb) p *obj->properties.tqh_first.node.tqe_next.node.tqe_next.node.tqe_next
$23 = {name = 0x555556a1be30 "lost_tick_policy", type = 0x555556a1be50 "LostTickPolicy", get = 0x5555556af400 , set = 0x5555556af990 ,
release = 0, opaque = 0x555555d4e1c0, node = {tqe_next = 0x555556a50fa0, tqe_prev = 0x555556a17f10}}
(gdb) p *obj->properties.tqh_first.node.tqe_next.node.tqe_next.node.tqe_next.node.tqe_next
$24 = {name = 0x555556a50f80 "parent_bus", type = 0x55555680b1d0 "link", get = 0x555555729e50 ,
set = 0x55555572a320 , release = 0, opaque = 0x555556a17b30, node = {tqe_next = 0x0, tqe_prev = 0x555556a50f10}}
(gdb) p *obj->properties.tqh_first.node.tqe_next.node.tqe_next.node.tqe_next.node.tqe_next.node.tqe_next
Cannot access memory at address 0x0
事實上卻多了"type" "realized" "parent_bus",這些屬性都是動態添加的。
在"object"類型的,instance_init = object_instance_init回調處,添加了
1
|
object_property_add_str(obj,"type",qdev_get_type,NULL,NULL);
|
在"device"類型的instance_init = device_initfn回調處,添加了
1
2
3
4
|
object_property_add_bool(obj,"realized",
device_get_realized,device_set_realized,NULL);
bject_property_add_link(OBJECT(dev),"parent_bus",TYPE_BUS,
(Object**)&dev->parent_bus,NULL);
|
設置屬性的時候,調用類似qdev_prop_set_int32(&dev->qdev, "base_year", base_year);進行設置,這裏,注意第一個參數爲什麼是DeviceState* dev->qdev而不是ISADevice *dev?
因爲rtc_class_initfn裏初始化props是給 DeviceClass *dc初始化的,所以對應的應該是DeviceState而不是子類ISADevice。
設置屬性是如何實現的?
以"realized"的bool屬性設置爲例,調用順序爲object_property_set_bool -> object_property_set_qobject -> object_property_set,此函數定義:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
voidobject_property_set(Object*obj,Visitor*v,constchar*name,
Error**errp)
{
//obj還是"mc146818rtc"的實例,name爲"realized",object_property_find其實就是查找obj的properties鏈表裏是否存在名字爲name的屬性
ObjectProperty*prop=object_property_find(obj,name,errp);
if(prop==NULL){
return;
}
if(!prop->set){//如果存在,且沒有設置過set handler,錯誤
error_set(errp,QERR_PERMISSION_DENIED);
}else{//"realized"的set函數爲property_set_bool
prop->set(obj,v,prop->opaque,name,errp);
}
}
staticvoidproperty_set_bool(Object*obj,Visitor*v,void*opaque,
constchar*name,Error**errp)
{
BoolProperty*prop=opaque;
boolvalue;
Error*local_err=NULL;
visit_type_bool(v,&value,name,&local_err);
if(local_err){
error_propagate(errp,local_err);
return;
}
prop->set(obj,value,errp);//對於realized來說,其實就是調用device_set_realized
}
|
而CALLBACK=device_set_realized 又會調用CALLBACK=device_realize。
模塊PIO回調流程
註冊回調
以mc146818 rtc爲例,在rtc_initfn的時候,註冊回調的代碼如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
staticconstMemoryRegionOpscmos_ops={
.read=cmos_ioport_read,
.write=cmos_ioport_write,
.impl={
.min_access_size=1,
.max_access_size=1,
},
.endianness=DEVICE_LITTLE_ENDIAN,
};
voidisa_register_ioport(ISADevice*dev,MemoryRegion*io,uint16_tstart)
{
memory_region_add_subregion(isabus->address_space_io,start,io);
isa_init_ioport(dev,start);
}
memory_region_init_io(&s->io,&cmos_ops,s,"rtc",2);
isa_register_ioport(dev,&s->io,base);
|
其中s->io是MemoryRegion類型,MemoryRegion是可以像樹一樣,多級掛載,比如,現在將rtc的 MemoryRegion掛載在isabus的address_space_io這個MemoryRegion下,其start參數爲offset在整個 isabus->address_space_io MemoryRegion中的偏移,即0x70,那麼END呢?END在memory_region_init_io的時候已經存儲到 MemoryRegion的size裏面了。
再看看isabus內容,有個更深入的性感的認識:
$8 = {qbus = {obj = {class = 0x555556a14e70, free = 0x7ffff7424020 , properties = {tqh_first = 0x555556a14f60, tqh_last = 0x555556a1eab0}, ref = 4,
parent = 0x55555698d740}, parent = 0x55555698d740, name = 0x555556a14ff0 "isa.0", allow_hotplug = 0, max_index = 3, children = {tqh_first = 0x555556a50f30,
tqh_last = 0x555556a16450}, sibling = {le_next = 0x0, le_prev = 0x55555698d7b8}}, address_space_io = 0x555556708930, irqs = 0x55555699c4f0}
(gdb) p *isabus->address_space_io
$9 = {ops = 0x0, opaque = 0x0, parent = 0x0, size = {lo = 65536, hi = 0}, addr = 0, destructor = 0x5555557cb8e0 , ram_addr = 0,
subpage = false, terminates = false, readable = true, ram = false, readonly = false, enabled = true, rom_device = false, warning_printed = false,
flush_coalesced_mmio = false, alias = 0x0, alias_offset = 0, priority = 0, may_overlap = false, subregions = {tqh_first = 0x55555698de48,
tqh_last = 0x55555698bf60}, subregions_link = {tqe_next = 0x0, tqe_prev = 0x0}, coalesced = {tqh_first = 0x0, tqh_last = 0x5555567089b8},
name = 0x5555566f7220 "io", dirty_log_mask = 0 '\000', ioeventfd_nb = 0, ioeventfds = 0x0, updateaddr = 0, updateopaque = 0x0}
回調流程
QEMU通過kvm_cpu_exec -> kvm_vcpu_ioctl(cpu, KVM_RUN, 0) 執行GUEST機CODE,當GUEST遇到IO等操作需要退出,會先在KVM裏處理,KVM不能處理,kvm_vcpu_ioctl就返回,給QEMU 處理,QEMU根據返回的run->exit_reason進行分派,比如PROT READ 0x71操作,退出時其exit_reason爲KVM_EXIT_IO,kvm_handle_io裏,根據direction判斷是read還是 write,根據read的長度,判斷該回調哪個函數。比如0x71
read 1字節的時候,調用的是:
stb_p(ptr, cpu_inb(port));
stb_p是將第二個參數cpu_inb(port)的結果轉換爲一個字節大小賦值給第一個參數ptr所指內存。
cpu_inb (addr=addr@entry=113)
cpu_inb和cpu_inw和cpu_inl是一家人的三兄弟,長得極其神似,我們看cpu_inb,在他調用的ioport_read的時候,第一 個參數叫index=0,這是和他的兄弟cpu_inw, cpu_inl區別開來的關鍵特徵。本來這裏應該沒有index啥事的,但總有人偷懶不設置對應addr的handler,index就是對專門爲這種懶 人擦屁股,沒有handler的時候,給選擇一個默認handler,你大概也看明白了,就index的話,三兄弟的差別在於inb=0, inw=1, inl=2。
1
2
3
4
5
6
|
uint8_tcpu_inb(pio_addr_taddr)
{
uint8_tval;
val=ioport_read(0,addr);
returnval;
}
|
read 的address比較重要,例如rtc的0x71,index其實是用來選擇默認handler的,當在對應的ioport_read_table裏面沒有註冊函數的時候就根據index的值,分別選擇readb, readw, readl來做默認操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
staticuint32_tioport_read(intindex,uint32_taddress)
{
staticIOPortReadFunc*constdefault_func[3]={
default_ioport_readb,
default_ioport_readw,
default_ioport_readl
};
IOPortReadFunc*func=ioport_read_table[index][address];
if(!func)
func=default_func[index];
/*
func一般都是ioport_readb_thunk 關鍵在於ioport_opaque[address]這裏面存放的是不同端口的IORange*
這個ioport_opaque的每個值,都存儲的該端口對應的IORange*
當讀寫此端口的時候,就會找到之前註冊的IORange回調,比如mc146818的IORange爲
(gdb) p *(IORange *)ioport_opaque[0x71]
$22 = {ops = 0x7fca4a51c380, base = 112, len = 2}
(gdb) p *(IORangeOps*)0x7fca4a51c380
$25 = {read = 0x7fca49fe1190 , write = 0x7fca49fe1050 ,
destructor = 0x7fca49fdfcb0 }
由於x70,0x71都是readb,所以在mc146818設備的時候,這個func其實爲ioport_readb_thunk
(gdb) p ioport_read_table[0][0x70]
$67 = (IOPortReadFunc *) 0x5555557c6980
(gdb) p ioport_read_table[0][0x71]
$68 = (IOPortReadFunc *) 0x5555557c6980
*/
returnfunc(ioport_opaque[address],address);
}
|
ioport_readb_thunk (opaque=, addr=)
ioport_register裏面,註冊了對一個字節的ioport read handler爲ioport_readb_thunk,其實這個函數非常簡單
就是調用了ops->read的時候,將width設置爲1,和ioport_readw_thunk,ioport_readl_thunk之類的就差一個width的區別
爲什麼要搞這麼複雜?這是爲了64K的read空間設計的回調,因爲不同的offset位置,我們需要知道是應該調用readb還是readw還是readl。
1
2
3
4
5
6
7
8
9
10
|
staticIOPortReadFunc*ioport_read_table[3][64*1024]
staticuint32_tioport_readb_thunk(void*opaque,uint32_taddr)
{
IORange*ioport=opaque;
uint64_tdata;
//read is memory_region_iorange_read when input char to ps/2 keyboard
//比如,mc146818的時候,addr爲x71,ioport->base爲x71, ioport->len=2
ioport->ops->read(ioport,addr-ioport->base,1,&data);
returndata;
}
|
上面的回調爲memory_region_iorange_read (iorange=0x55555680b1a0, offset=1, width=1, data=0x7fffec1fdc00)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
staticconstMemoryRegionOpscmos_ops={
.read=cmos_ioport_read,
.write=cmos_ioport_write,
.impl={
.min_access_size=1,
.max_access_size=1,
},
.endianness=DEVICE_LITTLE_ENDIAN,
};
staticvoidmemory_region_iorange_read(IORange*iorange,
uint64_toffset,
unsignedwidth,
uint64_t*data)
{
MemoryRegionIORange*mrio
=container_of(iorange,MemoryRegionIORange,iorange);
/*
一段MemoryRegionIORange裏包含了IORange iorange和MemoryRegion* mr
(gdb) p *mrio
$58 = {iorange = {ops = 0x555555d16200, base = 0x70, len = 2}, mr = 0x555556a17b80, offset = 0}
(gdb) p *mrio->iorange.ops
$59 = {read = 0x5555557ccf50 , write = 0x5555557cce10 ,
destructor = 0x5555557cba70 }
(gdb) p *mrio->mr
$60 = {ops = 0x555555d14ba0, opaque = 0x555556a17ae0, parent = 0x555556708930, size = {lo = 2, hi = 0}, addr = 112,
destructor = 0x5555557cb910 , ram_addr = 18446744073709551615, subpage = false, terminates = true, readable = true, ram = false,
readonly = false, enabled = true, rom_device = false, warning_printed = false, flush_coalesced_mmio = false, alias = 0x0, alias_offset = 0, priority = 0,
may_overlap = false, subregions = {tqh_first = 0x0, tqh_last = 0x555556a17be8}, subregions_link = {tqe_next = 0x555556a50d80, tqe_prev = 0x555556a1c1f8},
coalesced = {tqh_first = 0x0, tqh_last = 0x555556a17c08}, name = 0x55555680b300 "rtc", dirty_log_mask = 0 '\000', ioeventfd_nb = 0, ioeventfds = 0x0,
updateaddr = 0, updateopaque = 0x0}
(gdb) p *mrio->mr->ops
$61 = {read = 0x55555579ea20 , write = 0x55555579e040 , endianness = DEVICE_LITTLE_ENDIAN, valid = {min_access_size = 0,
max_access_size = 0, unaligned = false, accepts = 0}, impl = {min_access_size = 1, max_access_size = 1, unaligned = false}, old_portio = 0x0, old_mmio = {
read = {0, 0, 0}, write = {0, 0, 0}}}
*/
MemoryRegion*mr=mrio->mr;
//如果mrio還有offset,要加上這個偏移,這個offset其實是當成地址來用的,比如,我認爲read 0x71應該在已有offset=1的基礎上加上x70,但是他沒加
offset+=mrio->offset;
if(mr->ops->old_portio){//對"mc146818rtc"已經沒有old_portio的CALLBACK了,跳過
constMemoryRegionPortio*mrp=find_portio(mr,offset-mrio->offset,
width,false);
*data=((uint64_t)1<<(width*8))-1;
if(mrp){
*data=mrp->read(mr->opaque,offset);
}elseif(width==2){
mrp=find_portio(mr,offset-mrio->offset,1,false);
assert(mrp);
*data=mrp->read(mr->opaque,offset)|
(mrp->read(mr->opaque,offset+1)<<8);
}
return;
}
*data=0;//這是read後的返回值存儲區域,提前清零
access_with_adjusted_size(offset,data,width,
mr->ops->impl.min_access_size,//這個min_access_size和max_access_size是在設置ops的時候定義的,見cmos_ops
mr->ops->impl.max_access_size,
memory_region_read_accessor,mr);
}
|
從參數看,這裏的access參數爲memory_region_read_accessor,而這個value參數,用來存放read的返回值。接下來進入
access_with_adjusted_size (addr=addr@entry=1, value=value@entry=0x7fffec1fdc00, size=1, access_size_min=, access_size_max=, access=access@entry=0x5555557cbf70 , opaque=opaque@entry=0x555556a17b80),其access參數非常重要,繼續回調的就是access。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
staticvoidaccess_with_adjusted_size(hwaddraddr,
uint64_t*value,
unsignedsize,
unsignedaccess_size_min,
unsignedaccess_size_max,
void(*access)(void*opaque,
hwaddraddr,
uint64_t*value,
unsignedsize,
unsignedshift,
uint64_tmask),
void*opaque)
{
uint64_taccess_mask;
unsignedaccess_size;
unsignedi;
if(!access_size_min){
access_size_min=1;
}
if(!access_size_max){
access_size_max=4;
}
access_size=MAX(MIN(size,access_size_max),access_size_min);//size其實在參數裏面已經指定了,但是爲了安全,要確保access_size區間爲[1,4]字節
access_mask=-1ULL>>(64-access_size*8);//作爲mask,對Read的幾個mask,確保結果大小爲預期大小
for(i=0;i<size;i+=access_size){ <="" div="" style="word-wrap: break-word;">
/*
最大返回結果其實只有sizeof(value) = 64bit,這裏的設計是,每次取一個字節的返回結果
但是access_size可以不爲bit,比如read 0x100,假設read範圍爲bit,就是x100-0x104,access_size可以爲,
這樣就分兩步走,第一步Read 0x100-0x102返回個字節的結果,存儲到value的低字節
第二步Read 0x103-0x104返回個字節的結果,存儲到value的高字節
最後返回的value就存儲了兩次的Read值,只佔用了bit,不會超過bit
*/
access(opaque,addr+i,value,access_size,i*8,access_mask);
}
}
|
上面的access_with_adjusted_size的access參數其實就是memory_region_read_accessor,可以通過GDB打印出來:
(gdb) p *mr
$52 = {ops = 0x555555d14ba0, opaque = 0x555556a17ae0, parent = 0x555556708930, size = {lo = 2, hi = 0}, addr = 112,
destructor = 0x5555557cb910 , ram_addr = 18446744073709551615, subpage = false, terminates = true, readable = true, ram = false,
readonly = false, enabled = true, rom_device = false, warning_printed = false, flush_coalesced_mmio = false, alias = 0x0, alias_offset = 0, priority = 0,
may_overlap = false, subregions = {tqh_first = 0x0, tqh_last = 0x555556a17be8}, subregions_link = {tqe_next = 0x555556a50d80, tqe_prev = 0x555556a1c1f8},
coalesced = {tqh_first = 0x0, tqh_last = 0x555556a17c08}, name = 0x55555680b300 "rtc", dirty_log_mask = 0 '\000', ioeventfd_nb = 0, ioeventfds = 0x0,
updateaddr = 0, updateopaque = 0x0}
(gdb) p *mr->ops
$53 = {read = 0x55555579ea20 , write = 0x55555579e040 , endianness = DEVICE_LITTLE_ENDIAN, valid = {min_access_size = 0,
max_access_size = 0, unaligned = false, accepts = 0}, impl = {min_access_size = 1, max_access_size = 1, unaligned = false}, old_portio = 0x0, old_mmio = {
read = {0, 0, 0}, write = {0, 0, 0}}}
繞了這麼多,memory_region_read_accessor裏的mr->ops->read終於到了我們註冊的函數,如 mc146818的cmos_ioport_read (opaque=0x555556a17ae0, addr=1, size=1)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
staticvoidmemory_region_read_accessor(void*opaque,
hwaddraddr,
uint64_t*value,
unsignedsize,
unsignedshift,
uint64_tmask)
{
MemoryRegion*mr=opaque;
uint64_ttmp;
if(mr->flush_coalesced_mmio){
qemu_flush_coalesced_mmio_buffer();
}
tmp=mr->ops->read(mr->opaque,addr,size);
*value|=(tmp&mask)<<shift; <="" div="" style="word-wrap: break-word;">
}
|