Skip to content

Instantly share code, notes, and snippets.

@RKX1209
Created November 5, 2017 06:25
Show Gist options
  • Save RKX1209/2ee3e3b0c677106a18fb67c5bb6fcf6c to your computer and use it in GitHub Desktop.
Save RKX1209/2ee3e3b0c677106a18fb67c5bb6fcf6c to your computer and use it in GitHub Desktop.
QEMU internals4

QEMUのなかみ(QEMU internals) 4 10.ブロックデバイス qcow2などの各フォーマットは見たので、次はブロックデバイスのもう少し抽象的な機能を見る。 ブロックレイヤーはブロックジョブという機能があり、これは時間がかかるブロック処理をバックグラウンドで(i.e. VMを稼働させながら)実行する機能。主にLive snapshot, Image Streaming, Live commit, Image mirroringを実行する際使用される。 またゲストエージェント(WIP)を使ってゲストのファイルシステムをフリーズ(つまり状態を固定する)させたり、ゲストのアプリケーションにスナップショット取得の通知をしたりする事も考えられている。 ではまずLive commitを見てみよう。これはQMPでblock-commitを発行すると良い。(block-commitのHMPバージョンは何かしらの理由で無いらしいが、メンテナー自身も理由を忘れている https://lists.gnu.org/archive/html/qemu-block/2015-04/msg00015.html)

block-commitはQMPコマンドなため前回も少し触れたQAPIを使って定義されている。qapi-schema.jsonでqapi/block.jsonがincludeされ、さらにその中でqapi/block-core.jsonがincludeされている。このblock-core.jsonでblock-commitが定義されている。

(qmp-marshal.c)

|c| void qmp_marshal_block_commit(QDict *args, QObject **ret, Error **errp) { ... qmp_block_commit(arg.device, arg.has_base, arg.base, arg.has_top, arg.top, arg.has_backing_file, arg.backing_file, arg.has_speed, arg.speed, &err); ... } ||< (blockdev.c) |c| void qmp_block_commit(const char *device, bool has_base, const char *base, bool has_top, const char *top, bool has_backing_file, const char backing_file, bool has_speed, int64_t speed, Error **errp) { ... blk = blk_by_name(device); []

aio_context = blk_get_aio_context(blk); 
aio_context_acquire(aio_context);

bs = blk_bs(blk); [*]
...
/* default top_bs is the active layer */
top_bs = bs;

if (has_top && top) {
    if (strcmp(bs->filename, top) != 0) {
        top_bs = bdrv_find_backing_image(bs, top); [*]
    }
}

...
if (has_base && base) {
    base_bs = bdrv_find_backing_image(top_bs, base); [*]
} else {
    base_bs = bdrv_find_base(top_bs);
}
...
if (top_bs == bs) {
    commit_active_start(bs, base_bs, speed, on_error, block_job_cb,
                        bs, &local_err); [*]
} else {
    commit_start(bs, base_bs, top_bs, speed, on_error, block_job_cb, bs,
                 has_backing_file ? backing_file : NULL, &local_err);
}
...

} ||<

[],[]でlive commit対象のデバイス名からBlockDriverStateを取得している。[]ではactiveデバイス(現在VMが動作しているchainの一番最後のoverlay)とtopで指定されているoverlayが違う場合は、top_bsをそちらにする。要は陽にtopを指定した場合はそこからcommitを始めるが、デフォルトはactiveデバイスからcommitする。 次に[]でtop_bsのバッキングファイルを取得する。 [*]ではtop_bsがactiveデバイスの場合、commit_active_start(block/mirror.c)により

(block/mirror.c)

|c| void commit_active_start(BlockDriverState bs, BlockDriverState base, int64_t speed, BlockdevOnError on_error, BlockCompletionFunc cb, void opaque, Error **errp) { ... if (bdrv_reopen(base, bs->open_flags, errp)) { [] return; } length = bdrv_getlength(bs); [] base_length = bdrv_getlength(base); [] ... if (length > base_length) { ret = bdrv_truncate(base, length); [] ... }

mirror_start_job(bs, base, NULL, speed, 0, 0,
                 on_error, on_error, false, cb, opaque, &local_err,
                 &commit_active_job_driver, false, base); [*]

return;

} ||< []で [],[]でoverlayとバッキングファイルの長さを取得し、overlayの方が長ければ[]でバッキングファイルを伸長して大きさを合わせます。 そして[*]

(block.c)

|c| int bdrv_reopen_multiple(BlockReopenQueue *bs_queue, Error **errp) { int ret = -1; BlockReopenQueueEntry *bs_entry, *next; Error *local_err = NULL;

assert(bs_queue != NULL);

bdrv_drain_all(); [*]

QSIMPLEQ_FOREACH(bs_entry, bs_queue, entry) {
    if (bdrv_reopen_prepare(&bs_entry->state, bs_queue, &local_err)) { [*]
        error_propagate(errp, local_err);
        goto cleanup;
    }
    bs_entry->prepared = true;
}

/* If we reach this point, we have success and just need to apply the
 * changes
 */
QSIMPLEQ_FOREACH(bs_entry, bs_queue, entry) {
    bdrv_reopen_commit(&bs_entry->state); [*]
}

ret = 0;
...
return ret;

}

/* Reopen a single BlockDriverState with the specified flags. */ int bdrv_reopen(BlockDriverState *bs, int bdrv_flags, Error **errp) { int ret = -1; Error *local_err = NULL; BlockReopenQueue queue = bdrv_reopen_queue(NULL, bs, NULL, bdrv_flags); []

ret = bdrv_reopen_multiple(queue, &local_err); [*]
return ret;

} ||< []でblock jobのためのキューに要求を追加し、[]のbdrv_reopen_multipleでキューに従って実際にreopenする。このreopenの際、デバイスツリーの中で一つでもエラーが出た場合は全てのreopenを取り消す。 このように遅延評価を行っているのは、reopenする前にまだ反映されていない変更をきっちり反映してからreopenするためである。

(block.c)

|c| static BlockReopenQueue *bdrv_reopen_queue_child(BlockReopenQueue *bs_queue, BlockDriverState *bs, QDict *options, int flags, const BdrvChildRole *role, QDict *parent_options, int parent_flags) { assert(bs != NULL);

BlockReopenQueueEntry *bs_entry;
BdrvChild *child;
QDict *old_options, *explicit_options;

if (bs_queue == NULL) {
    bs_queue = g_new0(BlockReopenQueue, 1); [*]
    QSIMPLEQ_INIT(bs_queue);
}
...
QLIST_FOREACH(child, &bs->children, next) { 
    QDict *new_child_options;
    char *child_key_dot;

    /* reopen can only change the options of block devices that were
     * implicitly created and inherited options. For other (referenced)
     * block devices, a syntax like "backing.foo" results in an error. */
    if (child->bs->inherits_from != bs) {
        continue;
    }

    child_key_dot = g_strdup_printf("%s.", child->name);
    qdict_extract_subqdict(options, &new_child_options, child_key_dot);
    g_free(child_key_dot);

    bdrv_reopen_queue_child(bs_queue, child->bs, new_child_options, 0,
                            child->role, options, flags); [*]
}

bs_entry = g_new0(BlockReopenQueueEntry, 1);
QSIMPLEQ_INSERT_TAIL(bs_queue, bs_entry, entry); [*]

bs_entry->state.bs = bs;
bs_entry->state.options = options;
bs_entry->state.explicit_options = explicit_options;
bs_entry->state.flags = flags;

return bs_queue;

}

BlockReopenQueue *bdrv_reopen_queue(BlockReopenQueue bs_queue, BlockDriverState bs, QDict options, int flags) { return bdrv_reopen_queue_child(bs_queue, bs, options, flags, NULL, NULL, 0); } ||< []でキューがNULLの場合は新たにキューを作成する。また[]で指定されたBlockDriverStateの持つ子に対しても再帰的にReopenQueueに入れていく。最後に再帰の末端に来た場合[]でキューに要求を追加する。

さてではこれらキューを使って実際にreopenを行う処理を見ていく。 bdrv_reopen_multipleに戻って、まず[*]のbdrv_drain_allでreopen準備に入ったデバイスをまだ参照している要求(例えば途中でキャンセルされたAIOなど)を全て取り払う。

次に[*]で各エントリに対してbdrv_reopen_prepareを読んでいる。

(block.c)

|c| int bdrv_reopen_prepare(BDRVReopenState *reopen_state, BlockReopenQueue *queue, Error **errp) { int ret = -1; Error *local_err = NULL; BlockDriver *drv; QemuOpts *opts; const char *value;

assert(reopen_state != NULL);
assert(reopen_state->bs->drv != NULL);
drv = reopen_state->bs->drv;

/* Process generic block layer options */
opts = qemu_opts_create(&bdrv_runtime_opts, NULL, 0, &error_abort);
qemu_opts_absorb_qdict(opts, reopen_state->options, &local_err);
if (local_err) {
    error_propagate(errp, local_err);
    ret = -EINVAL;
    goto error;
}

update_flags_from_options(&reopen_state->flags, opts);

/* node-name and driver must be unchanged. Put them back into the QDict, so
 * that they are checked at the end of this function. */
value = qemu_opt_get(opts, "node-name");
if (value) {
    qdict_put(reopen_state->options, "node-name", qstring_from_str(value));
}

value = qemu_opt_get(opts, "driver");
if (value) {
    qdict_put(reopen_state->options, "driver", qstring_from_str(value));
}

/* if we are to stay read-only, do not allow permission change
 * to r/w */
if (!(reopen_state->bs->open_flags & BDRV_O_ALLOW_RDWR) &&
    reopen_state->flags & BDRV_O_RDWR) {
    error_setg(errp, "Node '%s' is read only",
               bdrv_get_device_or_node_name(reopen_state->bs));
    goto error;
}


ret = bdrv_flush(reopen_state->bs);
if (ret) {
    error_setg_errno(errp, -ret, "Error flushing drive");
    goto error;
}

if (drv->bdrv_reopen_prepare) {
    ret = drv->bdrv_reopen_prepare(reopen_state, queue, &local_err);
    if (ret) {
        if (local_err != NULL) {
            error_propagate(errp, local_err);
        } else {
            error_setg(errp, "failed while preparing to reopen image '%s'",
                       reopen_state->bs->filename);
        }
        goto error;
    }
} else {
    /* It is currently mandatory to have a bdrv_reopen_prepare()
     * handler for each supported drv. */
    error_setg(errp, "Block format '%s' used by node '%s' "
               "does not support reopening files", drv->format_name,
               bdrv_get_device_or_node_name(reopen_state->bs));
    ret = -1;
    goto error;
}

/* Options that are not handled are only okay if they are unchanged
 * compared to the old state. It is expected that some options are only
 * used for the initial open, but not reopen (e.g. filename) */
if (qdict_size(reopen_state->options)) {
    const QDictEntry *entry = qdict_first(reopen_state->options);

    do {
        QString *new_obj = qobject_to_qstring(entry->value);
        const char *new = qstring_get_str(new_obj);
        const char *old = qdict_get_try_str(reopen_state->bs->options,
                                            entry->key);

        if (!old || strcmp(new, old)) {
            error_setg(errp, "Cannot change the option '%s'", entry->key);
            ret = -EINVAL;
            goto error;
        }
    } while ((entry = qdict_next(reopen_state->options, entry)));
}

ret = 0;

error: qemu_opts_del(opts); return ret; } ||<

11.ACPI QEMUはACPIのエミュレーションも行っている。ACPIはチップセットのサウスブリッジPIIX4,もしくはICH9のエミュレーション内にて実装されている。今回はPIIX4の方を見る。

(hw/i386/pc_piix.c)

|c| /* PC hardware initialisation */ static void pc_init1(MachineState *machine, const char *host_type, const char pci_type) { pc_guest_info_init(); [] if (pcmc->pci_enabled && acpi_enabled) { DeviceState *piix4_pm; I2CBus *smbus;

    smi_irq = qemu_allocate_irq(pc_acpi_smi_interrupt, first_cpu, 0);
    /* TODO: Populate SPD eeprom data.  */
    smbus = piix4_pm_init(pci_bus, piix3_devfn + 3, 0xb100,
                          gsi[9], smi_irq,
                          pc_machine_is_smm_enabled(pcms),
                          &piix4_pm); [*]
    smbus_eeprom_init(smbus, 8, NULL, 0);

    object_property_add_link(OBJECT(machine), PC_MACHINE_ACPI_DEVICE_PROP,
                             TYPE_HOTPLUG_HANDLER,
                             (Object **)&pcms->acpi_dev,
                             object_property_allow_set_link,
                             OBJ_PROP_LINK_UNREF_ON_RELEASE, &error_abort);
    object_property_set_link(OBJECT(machine), OBJECT(piix4_pm),
                             PC_MACHINE_ACPI_DEVICE_PROP, &error_abort);
}

} ||<

(hw/acpi/piix4.c)

|c| I2CBus *piix4_pm_init(PCIBus *bus, int devfn, uint32_t smb_io_base, qemu_irq sci_irq, qemu_irq smi_irq, int smm_enabled, DeviceState **piix4_pm) { DeviceState *dev; PIIX4PMState *s;

dev = DEVICE(pci_create(bus, devfn, TYPE_PIIX4_PM));
qdev_prop_set_uint32(dev, "smb_io_base", smb_io_base);
if (piix4_pm) {
    *piix4_pm = dev;
}

s = PIIX4_PM(dev);
s->irq = sci_irq;
s->smi_irq = smi_irq;
s->smm_enabled = smm_enabled;
if (xen_enabled()) {
    s->use_acpi_pci_hotplug = false;
}

qdev_init_nofail(dev); [*]

return s->smb.smbus;

} ||<

part2でも書いたが[*]のqdev_init_nofailでdevのrealizedプロパティをtrueにします。 このrealizeはpiix4クラスの初期化関数piix4_pm_calss_init(hw/acpi/piix4.c)内でpiix4_pm_realizeに設定されています。

(hw/acpi/piix4.c)

|c| static void piix4_pm_realize(PCIDevice *dev, Error **errp) { PIIX4PMState *s = PIIX4_PM(dev); uint8_t pci_conf; ... / APM */ apm_init(dev, &s->apm, apm_ctrl_changed, s); ... pm_smbus_init(DEVICE(dev), &s->smb); memory_region_set_enabled(&s->smb.io, pci_conf[0xd2] & 1); memory_region_add_subregion(pci_address_space_io(dev), s->smb_io_base, &s->smb.io);

memory_region_init(&s->io, OBJECT(s), "piix4-pm", 64);
memory_region_set_enabled(&s->io, false);
memory_region_add_subregion(pci_address_space_io(dev),
                            0, &s->io);

acpi_pm_tmr_init(&s->ar, pm_tmr_timer, &s->io);
acpi_pm1_evt_init(&s->ar, pm_tmr_timer, &s->io);
acpi_pm1_cnt_init(&s->ar, &s->io, s->disable_s3, s->disable_s4, s->s4_val);
acpi_gpe_init(&s->ar, GPE_LEN);

s->powerdown_notifier.notify = piix4_pm_powerdown_req;
qemu_register_powerdown_notifier(&s->powerdown_notifier);

s->machine_ready.notify = piix4_pm_machine_ready;
qemu_add_machine_init_done_notifier(&s->machine_ready);
qemu_register_reset(piix4_reset, s);

piix4_acpi_system_hot_add_init(pci_address_space_io(dev), dev->bus, s); [*]

piix4_pm_add_propeties(s);

} ||<

[*]でホットプラグ処理の初期化を行います。 (hw/acpi/piix4.c)

|c| static void piix4_acpi_system_hot_add_init(MemoryRegion *parent, PCIBus bus, PIIX4PMState s) { memory_region_init_io(&s->io_gpe, OBJECT(s), &piix4_gpe_ops, s, "acpi-gpe0", GPE_LEN); [] memory_region_add_subregion(parent, GPE_BASE, &s->io_gpe); []

acpi_pcihp_init(OBJECT(s), &s->acpi_pci_hotplug, bus, parent,
                s->use_acpi_pci_hotplug);

acpi_cpu_hotplug_init(parent, OBJECT(s), &s->gpe_cpu,
                      PIIX4_CPU_HOTPLUG_IO_BASE);

if (s->acpi_memory_hotplug.is_enabled) {
    acpi_memory_hotplug_init(parent, OBJECT(s), &s->acpi_memory_hotplug); [*]
}

} ||< [],[]でACPIのGPE(General Purpose Event)というイベント通知のためのI/O空間が作成されています。これはACPIに関する各種イベント(ex. デバイスがプラグされた,など)をOSに通知するための空間です。イベント通知にはGPIOを通したGSI割り込みを使用します。

piix4_gpe_ops空間のread/writeにはそれぞれgpe_readb/gpe_writeb関数が設定されています。

(hw/acpi/piix4.c)

|c| static void gpe_writeb(void *opaque, hwaddr addr, uint64_t val, unsigned width) { PIIX4PMState *s = opaque;

acpi_gpe_ioport_writeb(&s->ar, addr, val); [*]
acpi_update_sci(&s->ar, s->irq); [*]

PIIX4_DPRINTF("gpe write %" HWADDR_PRIx " <== %" PRIu64 "\n", addr, val);

} ||< []でioportに実際の書き込みを行い、[]のacpi_update_sciでSCI割り込みを発生させます。

(hw/acpi/core.c)

|c| void acpi_update_sci(ACPIREGS *regs, qemu_irq irq) { int sci_level, pm1a_sts;

pm1a_sts = acpi_pm1_evt_get_sts(regs);

sci_level = ((pm1a_sts &
              regs->pm1.evt.en & ACPI_BITMASK_PM1_COMMON_ENABLED) != 0) ||
            ((regs->gpe.sts[0] & regs->gpe.en[0]) != 0);

qemu_set_irq(irq, sci_level); [*]

/* schedule a timer interruption if needed */
acpi_pm_tmr_update(regs,
                   (regs->pm1.evt.en & ACPI_BITMASK_TIMER_ENABLE) &&
                   !(pm1a_sts & ACPI_BITMASK_TIMER_STATUS));

} ||< [*]で実際に割り込みを発生させてイベントが発生した事を通知しています。

[*]のacpi_memory_hotplug_initがメモリホットプラグの初期化にあたります。

(hw/acpi/memory_hotplug.c)

|c| void acpi_memory_hotplug_init(MemoryRegion *as, Object *owner, MemHotplugState *state) { MachineState *machine = MACHINE(qdev_get_machine());

state->dev_count = machine->ram_slots;
if (!state->dev_count) {
    return;
}

state->devs = g_malloc0(sizeof(*state->devs) * state->dev_count);
memory_region_init_io(&state->io, owner, &acpi_memory_hotplug_ops, state,
                      "acpi-mem-hotplug", ACPI_MEMORY_HOTPLUG_IO_LEN); [*]
memory_region_add_subregion(as, ACPI_MEMORY_HOTPLUG_BASE, &state->io); [*]

} ||<

[]でメモリホットプラグが発生した際のイベント通知用I/Oを初期化しています。 acpi_memory_hotplug_opsのread/writeメソッドはacpi_memory_hotplug_read,writeに設定されています。ACPIでのプラグアンドプレイの扱いは、ACPI名前空間による各デバイス情報がDSDTブロックに保存されている。具体的にはACPI BIOSなどによって提供される、定義済みオブジェクト(ex. そのデバイスが使っているIRQやコンフィグ方法)がDSDTに含まれており、OSはこれを使ってデバイスのコンフィギュレーションを行える。このコンフィギュレーション情報は通常PCIバスなどが提供しているが、このような機構がないバスなどのデバイスの場合のみACPIのコンフィギュレーション情報を提供する。 さてではDSDT含むこれらのACPIテーブルはいつ初期化されているのでしょうか。 これはpc_init1にもどって[]のpc_guest_info_init(hw/i386/pc.c)完了後に呼ばれるpc_machine_done(hw/i386/pc.c)内のacpi_setup(hw/i386/acpi-build.c)で行われています。

(hw/i386/acpi-build.c)

|c| void acpi_setup(void) { PCMachineState *pcms = PC_MACHINE(qdev_get_machine()); PCMachineClass *pcmc = PC_MACHINE_GET_CLASS(pcms); AcpiBuildTables tables; AcpiBuildState *build_state;

build_state = g_malloc0(sizeof *build_state);

acpi_set_pci_info();

acpi_build_tables_init(&tables);
acpi_build(&tables, MACHINE(pcms)); [*]

/* Now expose it all to Guest */
build_state->table_mr = acpi_add_rom_blob(build_state, tables.table_data,
                                           ACPI_BUILD_TABLE_FILE,
                                           ACPI_BUILD_TABLE_MAX_SIZE); [*]
assert(build_state->table_mr != NULL);

build_state->linker_mr =
    acpi_add_rom_blob(build_state, tables.linker, "etc/table-loader", 0);

fw_cfg_add_file(pcms->fw_cfg, ACPI_BUILD_TPMLOG_FILE,
                tables.tcpalog->data, acpi_data_len(tables.tcpalog));

if (!pcmc->rsdp_in_ram) {
    uint32_t rsdp_size = acpi_data_len(tables.rsdp);

    build_state->rsdp = g_memdup(tables.rsdp->data, rsdp_size);
    fw_cfg_add_file_callback(pcms->fw_cfg, ACPI_BUILD_RSDP_FILE,
                             acpi_build_update, build_state,
                             build_state->rsdp, rsdp_size);
    build_state->rsdp_mr = NULL;
} else {
    build_state->rsdp = NULL;
    build_state->rsdp_mr = acpi_add_rom_blob(build_state, tables.rsdp,
                                              ACPI_BUILD_RSDP_FILE, 0);
}

qemu_register_reset(acpi_build_reset, build_state);
acpi_build_reset(build_state);
vmstate_register(NULL, 0, &vmstate_acpi_build, build_state);

} ||<

[*]のacpi_buildでACPIテーブルを初期化します。

(hw/i386/acpi-build.c)

|c| static void acpi_build(AcpiBuildTables *tables, MachineState *machine) {

acpi_get_pm_info(&pm);
acpi_get_misc_info(&misc);
acpi_get_pci_info(&pci);
acpi_get_slic_oem(&slic_oem);

table_offsets = g_array_new(false, true /* clear */,
                                    sizeof(uint32_t));
ACPI_BUILD_DPRINTF("init ACPI tables\n");

bios_linker_loader_alloc(tables->linker, ACPI_BUILD_TABLE_FILE,
                         64 /* Ensure FACS is aligned */,
                         false /* high memory */);

/*
 * FACS is pointed to by FADT.
 * We place it first since it's the only table that has alignment
 * requirements.
 */
facs = tables_blob->len;
build_facs(tables_blob, tables->linker);

/* DSDT is pointed to by FADT */
dsdt = tables_blob->len;
build_dsdt(tables_blob, tables->linker, &pm, &misc, &pci, machine); [*]
...

} ||<

[*]のbuild_dsdtでDSDTテーブルを初期化します。

(hw/i386/acpi-build.c)

|c| static void build_dsdt(GArray *table_data, GArray *linker, AcpiPmInfo *pm, AcpiMiscInfo *misc, PcPciInfo *pci, MachineState *machine) {

} ||<

DSDT内のメモリホットプラグのAMLによる記述は(hw/acpi/memory_hotplug_acpi_table.c)内で行われています。 AMLの記述は(hw/acpi/aml-build.c)のヘルパー関数を使用する。

(hw/acpi/aml-build.c)

|c| static Aml *aml_bundle(uint8_t op, AmlBlockFlags flags) { Aml var = aml_alloc(); var->op = op; var->block_flags = flags; return var; } ... / ACPI 1.0b: 16.2.5.1 Namespace Modifier Objects Encoding: DefScope */ Aml *aml_scope(const char name_format, ...) { va_list ap; Aml var = aml_bundle(0x10 / ScopeOp /, AML_PACKAGE); [] va_start(ap, name_format); build_append_namestringv(var->buf, name_format, ap); [] va_end(ap); return var; } ||<

[*]のaml_bundleは第一引数にオペコード、第二引数にオペコードの種類(引数をいくつとるとかバッファ形式だとか)を指定して命令を定義しています。ここではスコープ定義の命令を返しています。

(hw/acpi/memory_hotplug.c)

|c| static void acpi_memory_hotplug_write(void *opaque, hwaddr addr, uint64_t data, unsigned int size) { Aml *ifctx; Aml *method; Aml *pci_scope; Aml *mem_ctrl_dev;

/* scope for memory hotplug controller device node */
pci_scope = aml_scope("_SB.PCI0");
mem_ctrl_dev = aml_device(MEMORY_HOTPLUG_DEVICE);

... } ||< ここにはヘルパー関数を使ってAMLが記述されていますが、読みづらいのでゲストOS側からiasl -dでASLにディスアセンブルした結果を見ていきます。

|dsl| Scope (_SB.PCI0) { Device (MHPD) { Name (_HID, "PNP0A06" /* Generic Container Device */) // _HID: Hardware ID Name (_UID, "Memory hotplug resources") // _UID: Unique ID Method (_STA, 0, NotSerialized) // _STA: Status { If ((MDNR == Zero)) { Return (Zero) }

            Return (0x0B)
        }

        Mutex (MLCK, 0x00)
        Method (MSCN, 0, NotSerialized)
        {
            If ((MDNR == Zero))
            {
                Return (Zero)
            }

            Local0 = Zero
            Acquire (MLCK, 0xFFFF)
            While ((Local0 < MDNR))
            {
                MSEL = Local0
                If ((MINS == One))
                {
                    MTFY (Local0, One)
                    MINS = One
                }

                Local0 += One
            }

            Release (MLCK)
            Return (One)
        }

        Method (MRST, 1, NotSerialized)
        {
            Local0 = Zero
            Acquire (MLCK, 0xFFFF)
            MSEL = ToInteger (Arg0)
            If ((MES == One))
            {
                Local0 = 0x0F
            }

            Release (MLCK)
            Return (Local0)
        }

        Method (MCRS, 1, Serialized)
        {
            Acquire (MLCK, 0xFFFF)
            MSEL = ToInteger (Arg0)
            Name (MR64, ResourceTemplate ()
            {
                QWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
                    0x0000000000000000, // Granularity
                    0x0000000000000000, // Range Minimum
                    0xFFFFFFFFFFFFFFFE, // Range Maximum
                    0x0000000000000000, // Translation Offset
                    0xFFFFFFFFFFFFFFFF, // Length
                    ,, _Y01, AddressRangeMemory, TypeStatic)
            })
            CreateDWordField (MR64, \_SB.PCI0.MHPD.MCRS._Y01._MIN, MINL)  // _MIN: Minimum Base Address
            CreateDWordField (MR64, 0x12, MINH)
            CreateDWordField (MR64, \_SB.PCI0.MHPD.MCRS._Y01._LEN, LENL)  // _LEN: Length
            CreateDWordField (MR64, 0x2A, LENH)
            CreateDWordField (MR64, \_SB.PCI0.MHPD.MCRS._Y01._MAX, MAXL)  // _MAX: Maximum Base Address
            CreateDWordField (MR64, 0x1A, MAXH)
            MINH = MRBH /* External reference */
            MINL = MRBL /* External reference */
            LENH = MRLH /* External reference */
            LENL = MRLL /* External reference */
            MAXL = (MINL + LENL) /* \_SB_.PCI0.MHPD.MCRS.LENL */
            MAXH = (MINH + LENH) /* \_SB_.PCI0.MHPD.MCRS.LENH */
            If ((MAXL < MINL))
            {
                MAXH += One
            }

            If ((MAXL < One))
            {
                MAXH -= One
            }

            MAXL -= One
            If ((MAXH == Zero))
            {
                Name (MR32, ResourceTemplate ()
                {
                    DWordMemory (ResourceProducer, PosDecode, MinFixed, MaxFixed, Cacheable, ReadWrite,
                        0x00000000,         // Granularity
                        0x00000000,         // Range Minimum
                        0xFFFFFFFE,         // Range Maximum
                        0x00000000,         // Translation Offset
                        0xFFFFFFFF,         // Length
                        ,, _Y02, AddressRangeMemory, TypeStatic)
                })
                CreateDWordField (MR32, \_SB.PCI0.MHPD.MCRS._Y02._MIN, MIN)  // _MIN: Minimum Base Address
                CreateDWordField (MR32, \_SB.PCI0.MHPD.MCRS._Y02._MAX, MAX)  // _MAX: Maximum Base Address
                CreateDWordField (MR32, \_SB.PCI0.MHPD.MCRS._Y02._LEN, LEN)  // _LEN: Length
                MIN = MINL /* \_SB_.PCI0.MHPD.MCRS.MINL */
                MAX = MAXL /* \_SB_.PCI0.MHPD.MCRS.MAXL */
                LEN = LENL /* \_SB_.PCI0.MHPD.MCRS.LENL */
                Release (MLCK)
                Return (MR32) /* \_SB_.PCI0.MHPD.MCRS.MR32 */
            }

            Release (MLCK)
            Return (MR64) /* \_SB_.PCI0.MHPD.MCRS.MR64 */
        }

        Method (MPXM, 1, NotSerialized)
        {
            Acquire (MLCK, 0xFFFF)
            MSEL = ToInteger (Arg0)
            Local0 = MPX /* External reference */
            Release (MLCK)
            Return (Local0)
        }

        Method (MOST, 4, NotSerialized)
        {
            Acquire (MLCK, 0xFFFF)
            MSEL = ToInteger (Arg0)
            MOEV = Arg1
            MOSC = Arg2
            Release (MLCK)
        }
    }
}

||<

ではacpi_setupに戻って[*]のacpi_addrom_blobを見ていきます。

ではメモリホットプラグの一連の流れを追っていきます。QEMUでメモリホットプラグを行うには -m 1G,slots=3,maxmem=4G(メモリスロット3つ最大4GBまで)のように指定しておき、HMPコマンドで、 (qemu) object_add memory-backend-ram,id=mem1,size=1G (qemu) device_add pc-dimm,id=dimm1,memdev=mem1

とすればDIMMを一つスロットに追加できる。 このdevice_addの処理を見ていく。その3でも述べたがdevice_addはQMPのqmp_device_addからqdev_device_addを呼び出す。qdev_device_addでpc-dimmのドライバを検索しrealizeをtrueにする事でデバイスがプラグされた事を知らせる。 realizedプロパティが設定されるとdevice_set_realized(hw/core/qdev.c)が呼ばれる事は以前説明したが、この中で以下のような処理部分がある。

(hw/core/qdev.c)

|c| static void device_set_realized(Object obj, bool value, Error **errp) { hotplug_ctrl = qdev_get_hotplug_handler(dev); [] if (hotplug_ctrl) { hotplug_handler_plug(hotplug_ctrl, dev, &local_err); [] } } ||< []のqdev_get_hotplug_handlerはデバイスからホットプラグハンドラを取得するが、これはデバイスに登録されているわけでは""ない""。実際はこのデバイスが接続されているマザーボード(つまりMachineClass)のホットプラグハンドラを返す。 [*]のhotplug_handler_plug(hw/core/hotplug.c)でホットプラグハンドラを呼び出します。 マザーボードのホットプラグハンドラはpc_machine_class_init(pc.c)内でpc_machine_device_plug_cbに初期化されています。

(hw/i386/pc.c)

|c| static void pc_machine_device_plug_cb(HotplugHandler *hotplug_dev, DeviceState dev, Error **errp) { if (object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) { pc_dimm_plug(hotplug_dev, dev, errp); [] } else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) { pc_cpu_plug(hotplug_dev, dev, errp); } } ||<

DIMMモジュールがホットプラグされた場合は[*]でpc_dimm_plugを呼び出します。 (hw/i386/pc.c)

|c| static void pc_dimm_plug(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) {

pc_dimm_memory_plug(dev, &pcms->hotplug_memory, mr, align, &local_err); [*]

hhc = HOTPLUG_HANDLER_GET_CLASS(pcms->acpi_dev);
hhc->plug(HOTPLUG_HANDLER(pcms->acpi_dev), dev, &error_abort); [*]

} ||<

[]のpc_dimm_memory_plug(hw/mem/pc-dimm.c)はQEMU上にメモリを追加する処理(アドレス空間を増やすなど)で、[]のacpi_devのプラグ処理はpiix4のACPI実装を使用してプラグされた事をGPEで知らせます(何に?)

(hw/acpi/piix4.c)

|c| static void piix4_device_plug_cb(HotplugHandler *hotplug_dev, DeviceState *dev, Error **errp) { PIIX4PMState *s = PIIX4_PM(hotplug_dev);

if (s->acpi_memory_hotplug.is_enabled &&
    object_dynamic_cast(OBJECT(dev), TYPE_PC_DIMM)) {
    acpi_memory_plug_cb(&s->ar, s->irq, &s->acpi_memory_hotplug, dev, errp); [*]
} else if (object_dynamic_cast(OBJECT(dev), TYPE_PCI_DEVICE)) {
    acpi_pcihp_device_plug_cb(&s->ar, s->irq, &s->acpi_pci_hotplug, dev,
                              errp);
} else if (object_dynamic_cast(OBJECT(dev), TYPE_CPU)) {
    acpi_cpu_plug_cb(&s->ar, s->irq, &s->gpe_cpu, dev, errp);
} else {
    error_setg(errp, "acpi: device plug request for not supported device"
               " type: %s", object_get_typename(OBJECT(dev)));
}

} ||<

[*]のacpi_memory_plug_cbがメモリが挿入された際のACPI用コールバック関数です。

(hw/acpi/memory_hotplug.c)

|c| void acpi_memory_plug_cb(ACPIREGS *ar, qemu_irq irq, MemHotplugState *mem_st, DeviceState *dev, Error **errp) { MemStatus *mdev; DeviceClass *dc = DEVICE_GET_CLASS(dev);

mdev = acpi_memory_slot_status(mem_st, dev, errp);

mdev->dimm = dev;
mdev->is_enabled = true;
if (dev->hotplugged) {
    mdev->is_inserting = true;

    /* do ACPI magic */
    acpi_send_gpe_event(ar, irq, ACPI_MEMORY_HOTPLUG_STATUS); [*]
}

} ||<

(hw/acpi/core.c)

|c| void acpi_send_gpe_event(ACPIREGS ar, qemu_irq irq, AcpiGPEStatusBits status) { ar->gpe.sts[0] |= status; acpi_update_sci(ar, irq); [] } ||<

[*]でSCI割り込みを起こします。これは上でも述べたようにACPIのイベント通知はSCIを通して行われるからです。

12.ファームウェア QEMUは当然BIOS,UEFIといったファームウェア、およびファームウェアを格納するNVRAMもエミュレーションしています。BIOSにはオープンプロジェクトのSeaBIOSを(pc-bios/bios.bin)として利用しています。

ではファームウェアの初期化処理から見て行きましょう。 pc_init1内でメモリを初期化している部分を思い出してください。 (hw/i386/pc.c)

|c| void pc_memory_init(PCMachineState *pcms, MemoryRegion system_memory, MemoryRegion rom_memory, MemoryRegion **ram_memory) { linux_boot = (machine->kernel_filename != NULL); [] ... pc_system_firmware_init(rom_memory, !pcmc->pci_enabled); []

option_rom_mr = g_malloc(sizeof(*option_rom_mr));
memory_region_init_ram(option_rom_mr, NULL, "pc.rom", PC_ROM_SIZE,
                       &error_fatal);
vmstate_register_ram_global(option_rom_mr);
memory_region_add_subregion_overlap(rom_memory,
                                    PC_ROM_MIN_VGA,
                                    option_rom_mr,
                                    1);

fw_cfg = bochs_bios_init(&address_space_memory, pcms); [*]

rom_set_fw(fw_cfg); [*]
if (linux_boot) {
    load_linux(pcms, fw_cfg);
}

for (i = 0; i < nb_option_roms; i++) {
    rom_add_option(option_rom[i].name, option_rom[i].bootindex);
}
pcms->fw_cfg = fw_cfg;

} ||<

[*]のpc_system_firmware_initでファームウェアの初期化を行います。

(hw/i386/pc_sysfw.c)

|c| void pc_system_firmware_init(MemoryRegion *rom_memory, bool isapc_ram_fw) { DriveInfo *pflash_drv;

pflash_drv = drive_get(IF_PFLASH, 0, 0);

if (isapc_ram_fw || pflash_drv == NULL) {
    /* When a pflash drive is not found, use rom-mode */
    old_pc_system_rom_init(rom_memory, isapc_ram_fw); [*]
    return;
}
...

} ||<

[*]でROMの初期化を行います。コメントにもあるように-pflashオプションが指定されておらずROMを使用する場合です。

(hw/i386/pc_sysfw.c)

|c| static void old_pc_system_rom_init(MemoryRegion *rom_memory, bool isapc_ram_fw) { char *filename; MemoryRegion *bios, *isa_bios; int bios_size, isa_bios_size; int ret;

/* BIOS load */
if (bios_name == NULL) {
    bios_name = BIOS_FILENAME; [*]
}
filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name);
...
bios = g_malloc(sizeof(*bios));
memory_region_init_ram(bios, NULL, "pc.bios", bios_size, &error_fatal); [*]
vmstate_register_ram_global(bios);
if (!isapc_ram_fw) {
    memory_region_set_readonly(bios, true);
}
ret = rom_add_file_fixed(bios_name, (uint32_t)(-bios_size), -1); [*]
...
/* map the last 128KB of the BIOS in ISA space */
isa_bios_size = bios_size;
if (isa_bios_size > (128 * 1024)) {
    isa_bios_size = 128 * 1024;
}
isa_bios = g_malloc(sizeof(*isa_bios));
memory_region_init_alias(isa_bios, NULL, "isa-bios", bios,
                         bios_size - isa_bios_size, isa_bios_size);
memory_region_add_subregion_overlap(rom_memory,
                                    0x100000 - isa_bios_size,
                                    isa_bios,
                                    1);

/* map all the bios at the top of memory */
memory_region_add_subregion(rom_memory,
                            (uint32_t)(-bios_size),
                            bios); [*]

} ||< []でBIOSのファイル名をデフォルトのbios.binに指定し、[]でbios用のメモリリージョンを確保して[]で引数で渡されたrom_memory(ROM用のリージョン)にBIOS空間を追加しています。BIOSのロードは[]のrom_add_file_fixedです。 rom_add_file_fixed(file,addr,index)はrom_add_file(file,NULL,addr,index,false,NULL)を呼び出します。

(hw/core/loader.c)

|c| int rom_add_file(const char *file, const char *fw_dir, hwaddr addr, int32_t bootindex, bool option_rom, MemoryRegion *mr) { MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine()); Rom *rom; int rc, fd = -1; char devpath[100];

rom = g_malloc0(sizeof(*rom));
rom->name = g_strdup(file);
rom->path = qemu_find_file(QEMU_FILE_TYPE_BIOS, rom->name);
if (rom->path == NULL) {
    rom->path = g_strdup(file);
}

fd = open(rom->path, O_RDONLY | O_BINARY); [*]

rom->addr     = addr;
rom->romsize  = lseek(fd, 0, SEEK_END);

rom->datasize = rom->romsize;
rom->data     = g_malloc0(rom->datasize);
lseek(fd, 0, SEEK_SET);
rc = read(fd, rom->data, rom->datasize); [*]
...
close(fd);
rom_insert(rom); [*]
if (rom->fw_file && fw_cfg) {
    const char *basename;
    char fw_file_name[FW_CFG_MAX_FILE_PATH];
    void *data;

    basename = strrchr(rom->fw_file, '/');
    if (basename) {
        basename++;
    } else {
        basename = rom->fw_file;
    }
    snprintf(fw_file_name, sizeof(fw_file_name), "%s/%s", rom->fw_dir,
             basename);
    snprintf(devpath, sizeof(devpath), "/rom@%s", fw_file_name);

    if ((!option_rom || mc->option_rom_has_mr) && mc->rom_file_has_mr) {
        data = rom_set_mr(rom, OBJECT(fw_cfg), devpath);
    } else {
        data = rom->data;
    }

    fw_cfg_add_file(fw_cfg, fw_file_name, data, rom->romsize); [*]
} else {
    if (mr) {
        rom->mr = mr;
        snprintf(devpath, sizeof(devpath), "/rom@%s", file);
    } else {
        snprintf(devpath, sizeof(devpath), "/rom@" TARGET_FMT_plx, addr);
    }
}

add_boot_device_path(bootindex, NULL, devpath); [*]
return 0;

} ||< []でBIOSファイル(bios.bin)をopenし、[]でread,[*]のfw_cfg_add_fileでromsというROMのリスト(?)に挿入している。

(hw/nvram/fw_cfg.c)

|c| void fw_cfg_add_file_callback(FWCfgState *s, const char *filename, FWCfgReadCallback callback, void *callback_opaque, void *data, size_t len) { int i, index, count; size_t dsize; MachineClass *mc = MACHINE_GET_CLASS(qdev_get_machine()); int order = 0;

if (!s->files) {
    dsize = sizeof(uint32_t) + sizeof(FWCfgFile) * FW_CFG_FILE_SLOTS;
    s->files = g_malloc0(dsize);
    fw_cfg_add_bytes(s, FW_CFG_FILE_DIR, s->files, dsize);
}

count = be32_to_cpu(s->files->count);
assert(count < FW_CFG_FILE_SLOTS);

/* Find the insertion point. */
if (mc->legacy_fw_cfg_order) {
    /*
     * Sort by order. For files with the same order, we keep them
     * in the sequence in which they were added.
     */
    order = get_fw_cfg_order(s, filename);
    for (index = count;
         index > 0 && order < s->entry_order[index - 1];
         index--);
} else {
    /* Sort by file name. */
    for (index = count;
         index > 0 && strcmp(filename, s->files->f[index - 1].name) < 0;
         index--);
}

/*
 * Move all the entries from the index point and after down one
 * to create a slot for the new entry.  Because calculations are
 * being done with the index, make it so that "i" is the current
 * index and "i - 1" is the one being copied from, thus the
 * unusual start and end in the for statement.
 */
for (i = count + 1; i > index; i--) {
    s->files->f[i] = s->files->f[i - 1];
    s->files->f[i].select = cpu_to_be16(FW_CFG_FILE_FIRST + i);
    s->entries[0][FW_CFG_FILE_FIRST + i] =
        s->entries[0][FW_CFG_FILE_FIRST + i - 1];
    s->entry_order[i] = s->entry_order[i - 1];
}

memset(&s->files->f[index], 0, sizeof(FWCfgFile));
memset(&s->entries[0][FW_CFG_FILE_FIRST + index], 0, sizeof(FWCfgEntry));

pstrcpy(s->files->f[index].name, sizeof(s->files->f[index].name), filename);
for (i = 0; i <= count; i++) {
    if (i != index &&
        strcmp(s->files->f[index].name, s->files->f[i].name) == 0) {
        error_report("duplicate fw_cfg file name: %s",
                     s->files->f[index].name);
        exit(1);
    }
}

fw_cfg_add_bytes_read_callback(s, FW_CFG_FILE_FIRST + index,
                               callback, callback_opaque, data, len); [*]

s->files->f[index].size   = cpu_to_be32(len);
s->files->f[index].select = cpu_to_be16(FW_CFG_FILE_FIRST + index);
s->entry_order[index] = order;
trace_fw_cfg_add_file(s, index, s->files->f[index].name, len);

s->files->count = cpu_to_be32(count+1);

}

void fw_cfg_add_file(FWCfgState *s, const char *filename, void *data, size_t len) { fw_cfg_add_file_callback(s, filename, NULL, NULL, data, len); }

||<

[*]のfw_cfg_add_bytes_read_callbackでfw_cfgのkeyとarchをキーにしたc->entriesにファームウェアの実際の実体(data),長さ(len),コールバックなどを追加する。

|c| static void fw_cfg_add_bytes_read_callback(FWCfgState *s, uint16_t key, FWCfgReadCallback callback, void *callback_opaque, void *data, size_t len) { int arch = !!(key & FW_CFG_ARCH_LOCAL);

key &= FW_CFG_ENTRY_MASK;

assert(key < FW_CFG_MAX_ENTRY && len < UINT32_MAX);
assert(s->entries[arch][key].data == NULL); /* avoid key conflict */

s->entries[arch][key].data = data;
s->entries[arch][key].len = (uint32_t)len;
s->entries[arch][key].read_callback = callback;
s->entries[arch][key].callback_opaque = callback_opaque;

} ||<

最後に[]のadd_boot_device_pathで指定された起動順位(bootindex)でデバイスを登録する。ただしここではbootindexは-1のため削除を行い挿入は行わない(?) ではmemory_initに戻って[]のbochs_bios_initを見ていく。

(hw/i386/pc.c)

|c| static FWCfgState *bochs_bios_init(AddressSpace *as, PCMachineState *pcms) { FWCfgState *fw_cfg; uint64_t *numa_fw_cfg; int i, j;

fw_cfg = fw_cfg_init_io_dma(FW_CFG_IO_BASE, FW_CFG_IO_BASE + 4, as);

fw_cfg_add_i16(fw_cfg, FW_CFG_MAX_CPUS, (uint16_t)pcms->apic_id_limit);
fw_cfg_add_i64(fw_cfg, FW_CFG_RAM_SIZE, (uint64_t)ram_size);
fw_cfg_add_bytes(fw_cfg, FW_CFG_ACPI_TABLES,
                 acpi_tables, acpi_tables_len);
fw_cfg_add_i32(fw_cfg, FW_CFG_IRQ0_OVERRIDE, kvm_allows_irq0_override());

pc_build_smbios(fw_cfg);

fw_cfg_add_bytes(fw_cfg, FW_CFG_E820_TABLE,
                 &e820_reserve, sizeof(e820_reserve));
fw_cfg_add_file(fw_cfg, "etc/e820", e820_table,
                sizeof(struct e820_entry) * e820_entries);

fw_cfg_add_bytes(fw_cfg, FW_CFG_HPET, &hpet_cfg, sizeof(hpet_cfg));
numa_fw_cfg = g_new0(uint64_t, 1 + pcms->apic_id_limit + nb_numa_nodes);
numa_fw_cfg[0] = cpu_to_le64(nb_numa_nodes);
for (i = 0; i < max_cpus; i++) {
    unsigned int apic_id = x86_cpu_apic_id_from_index(i);
    assert(apic_id < pcms->apic_id_limit);
    for (j = 0; j < nb_numa_nodes; j++) {
        if (test_bit(i, numa_info[j].node_cpu)) {
            numa_fw_cfg[apic_id + 1] = cpu_to_le64(j);
            break;
        }
    }
}
for (i = 0; i < nb_numa_nodes; i++) {
    numa_fw_cfg[pcms->apic_id_limit + 1 + i] =
        cpu_to_le64(numa_info[i].node_mem);
}
fw_cfg_add_bytes(fw_cfg, FW_CFG_NUMA, numa_fw_cfg,
                 (1 + pcms->apic_id_limit + nb_numa_nodes) *
                 sizeof(*numa_fw_cfg));

return fw_cfg;

} ||<

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment