#include "exec/address-spaces.h"
#include "sysemu/sysemu.h"
#include "qom/cpu.h"
+#include "hw/nvram/fw_cfg.h"
+#include "qemu/cutils.h"
/*****************************************************************************/
/* ICH9 LPC PCI to ISA bridge */
cpu_interrupt(first_cpu, CPU_INTERRUPT_SMI);
}
-void ich9_generate_nmi(void)
-{
- cpu_interrupt(first_cpu, CPU_INTERRUPT_NMI);
-}
-
static int ich9_lpc_sci_irq(ICH9LPCState *lpc)
{
switch (lpc->d.config[ICH9_LPC_ACPI_CTRL] &
}
}
+static void smi_features_ok_callback(void *opaque)
+{
+ ICH9LPCState *lpc = opaque;
+ uint64_t guest_features;
+
+ if (lpc->smi_features_ok) {
+ /* negotiation already complete, features locked */
+ return;
+ }
+
+ memcpy(&guest_features, lpc->smi_guest_features_le, sizeof guest_features);
+ le64_to_cpus(&guest_features);
+ if (guest_features & ~lpc->smi_host_features) {
+ /* guest requests invalid features, leave @features_ok at zero */
+ return;
+ }
+
+ /* valid feature subset requested, lock it down, report success */
+ lpc->smi_negotiated_features = guest_features;
+ lpc->smi_features_ok = 1;
+}
+
void ich9_lpc_pm_init(PCIDevice *lpc_pci, bool smm_enabled)
{
ICH9LPCState *lpc = ICH9_LPC_DEVICE(lpc_pci);
qemu_irq sci_irq;
+ FWCfgState *fw_cfg = fw_cfg_find();
sci_irq = qemu_allocate_irq(ich9_set_sci, lpc, 0);
ich9_pm_init(lpc_pci, &lpc->pm, smm_enabled, sci_irq);
+
+ if (lpc->smi_host_features && fw_cfg) {
+ uint64_t host_features_le;
+
+ host_features_le = cpu_to_le64(lpc->smi_host_features);
+ memcpy(lpc->smi_host_features_le, &host_features_le,
+ sizeof host_features_le);
+ fw_cfg_add_file(fw_cfg, "etc/smi/supported-features",
+ lpc->smi_host_features_le,
+ sizeof lpc->smi_host_features_le);
+
+ /* The other two guest-visible fields are cleared on device reset, we
+ * just link them into fw_cfg here.
+ */
+ fw_cfg_add_file_callback(fw_cfg, "etc/smi/requested-features",
+ NULL, NULL,
+ lpc->smi_guest_features_le,
+ sizeof lpc->smi_guest_features_le,
+ false);
+ fw_cfg_add_file_callback(fw_cfg, "etc/smi/features-ok",
+ smi_features_ok_callback, lpc,
+ &lpc->smi_features_ok,
+ sizeof lpc->smi_features_ok,
+ true);
+ }
+
ich9_lpc_reset(&lpc->d.qdev);
}
/* SMI_EN = PMBASE + 30. SMI control and enable register */
if (lpc->pm.smi_en & ICH9_PMIO_SMI_EN_APMC_EN) {
- cpu_interrupt(current_cpu, CPU_INTERRUPT_SMI);
+ if (lpc->smi_negotiated_features &
+ (UINT64_C(1) << ICH9_LPC_SMI_F_BROADCAST_BIT)) {
+ CPUState *cs;
+ CPU_FOREACH(cs) {
+ cpu_interrupt(cs, CPU_INTERRUPT_SMI);
+ }
+ } else {
+ cpu_interrupt(current_cpu, CPU_INTERRUPT_SMI);
+ }
}
}
lpc->sci_level = 0;
lpc->rst_cnt = 0;
+
+ memset(lpc->smi_guest_features_le, 0, sizeof lpc->smi_guest_features_le);
+ lpc->smi_features_ok = 0;
+ lpc->smi_negotiated_features = 0;
}
/* root complex register block is mapped into memory space */
}
};
+static bool ich9_smi_feat_needed(void *opaque)
+{
+ ICH9LPCState *lpc = opaque;
+
+ return !buffer_is_zero(lpc->smi_guest_features_le,
+ sizeof lpc->smi_guest_features_le) ||
+ lpc->smi_features_ok;
+}
+
+static const VMStateDescription vmstate_ich9_smi_feat = {
+ .name = "ICH9LPC/smi_feat",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ich9_smi_feat_needed,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8_ARRAY(smi_guest_features_le, ICH9LPCState,
+ sizeof(uint64_t)),
+ VMSTATE_UINT8(smi_features_ok, ICH9LPCState),
+ VMSTATE_UINT64(smi_negotiated_features, ICH9LPCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static const VMStateDescription vmstate_ich9_lpc = {
.name = "ICH9LPC",
.version_id = 1,
},
.subsections = (const VMStateDescription*[]) {
&vmstate_ich9_rst_cnt,
+ &vmstate_ich9_smi_feat,
NULL
}
};
static Property ich9_lpc_properties[] = {
DEFINE_PROP_BOOL("noreboot", ICH9LPCState, pin_strap.spkr_hi, true),
+ DEFINE_PROP_BIT64("x-smi-broadcast", ICH9LPCState, smi_host_features,
+ ICH9_LPC_SMI_F_BROADCAST_BIT, true),
DEFINE_PROP_END_OF_LIST(),
};
* Reason: part of ICH9 southbridge, needs to be wired up by
* pc_q35_init()
*/
- dc->cannot_instantiate_with_device_add_yet = true;
+ dc->user_creatable = false;
hc->plug = ich9_pm_device_plug_cb;
hc->unplug_request = ich9_pm_device_unplug_request_cb;
hc->unplug = ich9_pm_device_unplug_cb;