diff mbox

rtc: introduce nmi disable bit handler for cmos

Message ID 33183CC9F5247A488A2544077AF19020B02B5191@SZXEMA503-MBS.china.huawei.com
State New
Headers show

Commit Message

Gonglei (Arei) Dec. 16, 2015, 10:28 a.m. UTC
> 

> On 15/12/2015 19:53, Radim Krcmar wrote:

> > 2015-12-15 05:43-0500, Paolo Bonzini:

> >>> Hi Paolo,

> >>>

> >>> /* for KVM_GET/SET_VCPU_EVENTS */

> >>> struct kvm_vcpu_events {

> >>>  ...

> >>> struct {

> >>> 		__u8 injected;

> >>> 		__u8 pending;

> >>> 		__u8 masked;

> >>> 		__u8 pad;

> >>> 	} nmi;

> >>>  ...

> >>>

> >>> I found that the nmi.masked property does these enable or disable NMI

> jobs.

> >>> So, I think we don't need to add a new bit. Right?

> >>

> >> nmi.masked says whether the CPU is accepting the NMIs, and is cleared

> >> by the next IRET instruction.  This is a different thing; it probably

> >> shouldn't affect NMI IPIs, and it definitely should remain set until

> >> cleared via the RTC.  So it should be something like

> >>

> >>     _u8 external_nmi_disabled;

> >>

> >> or similar.

> >>

> >> *However* I found this in the ICH9 datasheet:

> >>

> >>     The ICH9's I/O APIC can only send interrupts due to interrupts which

> >>     do not include SMI, NMI or INIT. This means that in IA-32/Intel ® 64

> >>     based platforms, Front Side Bus interrupt message format delivery

> modes

> >>     010 (SMI/PMI), 100 (NMI), and 101 (INIT) as indicated in this section,

> >>     must not be used and is not supported.

> >>

> >> In theory the PIIX4 could deliver such messages, but perhaps we could

> >> disable them in the KVM IOAPIC.  If we do this, there is no need for

> >> a change to struct kvm_vcpu_events, because all external NMI sources

> >> will be in userspace.

> >>

> >> Radim, what do you think?

> >

> > I looked at the 440fx, piix, and 82083aa(ioapic) datasheets and the

> > NMI_EN bit doesn't seem to be propagated into the IOAPIC.

> > The IOAPIC datasheet doesn't mention a thing about NMI masking and

> > PIIX4 generates NMI on SERR# or IOCHK# so it seems that the NMI_EN

> > feature only changes the behavior of those two ...

> >

> > I think it's best to do nothing in KVM.

> 

> Then Gonglei's patch (apart from the issues that Eduardo pointed out) is fine.

> 

I'll move the global nmi_disabled into RTCState, then I have to add a global RTCState
Variable so that other C files can use the rtc_state->external_nmi_disabled.



And one more thing is we need to keep the property in vmstate for supporting live
Migration.

Am I right?  Thanks!


Regards,
-Gonglei

Comments

Paolo Bonzini Dec. 16, 2015, 12:14 p.m. UTC | #1
On 16/12/2015 11:28, Gonglei (Arei) wrote:
> I'll move the global nmi_disabled into RTCState, then I have to add a global RTCState
> Variable so that other C files can use the rtc_state->external_nmi_disabled.

Hmm, I think it should be done differently.  This is a layering
violation, the NMI_EN is essentially a pin (qemu_irq) between the ISA
bridges and the RTC.  The NMI "button" is also a component of the ISA
bridge; you should not need to touch anything except the RTC and the ISA
bridges, in particular not the APICs.

First, you need to add a qemu_irq argument to rtc_init. The RTC can
raise/lower the IRQ on writes to port 0x70.

Second, make the ISA bridges implement NMIState, where the
implementation of NMIState is similar to inject_nmi in hw/core/nmi.c:

    CPU_FOREACH(cs) {
	X86CPU *cpu = X86_CPU(cs);

	if (!cpu->apic_state) {
	    cpu_interrupt(cs, CPU_INTERRUPT_NMI);
	} else {
	    apic_deliver_nmi(cpu->apic_state);
	}
    }

Third, the ISA bridges (hw/isa/piix4.c and hw/isa/lpc_ich9.c) need to
export a qemu_irq for nmi_en IRQ (e.g. using qdev_init_gpio_in_named),
and you should modify the ISA bridge's implementation of NMIState to
latch the NMI if you send one while NMIs are disabled.  The nmi_en IRQ
can also trigger an NMI when nmi_en is enabled and an NMI was latched.
The nmi_en status and NMI latch status must be migrated in a new
subsection of the ISA bridges.

Fourth, the PC machines should use qdev_get_gpio_in_named to retrieve
the qemu_irq from the ISA bridges, and pass it to pc_basic_device_init.

I may have messed up some steps, but this is the basic idea.

Paolo
Gonglei (Arei) Dec. 17, 2015, 7:17 a.m. UTC | #2
Hello Paolo,

> 

> 

> On 16/12/2015 11:28, Gonglei (Arei) wrote:

> > I'll move the global nmi_disabled into RTCState, then I have to add a global

> RTCState

> > Variable so that other C files can use the rtc_state->external_nmi_disabled.

> 

> Hmm, I think it should be done differently.  This is a layering

> violation, the NMI_EN is essentially a pin (qemu_irq) between the ISA

> bridges and the RTC.  The NMI "button" is also a component of the ISA


So, you mean the NMI_EN can only control NMI injection came from ISA bridge?
What's this NMI "button" mean? 

> bridge; you should not need to touch anything except the RTC and the ISA

> bridges, in particular not the APICs.

> 

Currently, the qmp command "inject-nmi" doesn't pass ISA bridge. How
do we address this situation?

> First, you need to add a qemu_irq argument to rtc_init. The RTC can

> raise/lower the IRQ on writes to port 0x70.

> 

> Second, make the ISA bridges implement NMIState, where the

> implementation of NMIState is similar to inject_nmi in hw/core/nmi.c:

> 

>     CPU_FOREACH(cs) {

> 	X86CPU *cpu = X86_CPU(cs);

> 

> 	if (!cpu->apic_state) {

> 	    cpu_interrupt(cs, CPU_INTERRUPT_NMI);

> 	} else {

> 	    apic_deliver_nmi(cpu->apic_state);

> 	}

>     }

> 

> Third, the ISA bridges (hw/isa/piix4.c and hw/isa/lpc_ich9.c) need to


We don't use hw/isa/piix4.c but hw/pci-host/piix.c in x86 target. Right?

> export a qemu_irq for nmi_en IRQ (e.g. using qdev_init_gpio_in_named),

> and you should modify the ISA bridge's implementation of NMIState to

> latch the NMI if you send one while NMIs are disabled.  The nmi_en IRQ

> can also trigger an NMI when nmi_en is enabled and an NMI was latched.


Sorry, I'm a bit confused. The nmi_en can trigger an NMI? Isn't a flag bit which
can enable/disable the NMI switch?

> The nmi_en status and NMI latch status must be migrated in a new

> subsection of the ISA bridges.

> 

> Fourth, the PC machines should use qdev_get_gpio_in_named to retrieve

> the qemu_irq from the ISA bridges, and pass it to pc_basic_device_init.

> 

> I may have messed up some steps, but this is the basic idea.

> 

> Paolo


Thanks,
-Gonglei
Paolo Bonzini Dec. 17, 2015, 8:37 a.m. UTC | #3
On 17/12/2015 08:17, Gonglei (Arei) wrote:
>> On 16/12/2015 11:28, Gonglei (Arei) wrote:
>>> I'll move the global nmi_disabled into RTCState, then I have to add a global
>> RTCState
>>> Variable so that other C files can use the rtc_state->external_nmi_disabled.
>>
>> Hmm, I think it should be done differently.  This is a layering
>> violation, the NMI_EN is essentially a pin (qemu_irq) between the ISA
>> bridges and the RTC.  The NMI "button" is also a component of the ISA
> 
> So, you mean the NMI_EN can only control NMI injection came from ISA bridge?
> What's this NMI "button" mean? 

The NMI command in the monitor is a "virtual NMI button".

>> bridge; you should not need to touch anything except the RTC and the ISA
>> bridges, in particular not the APICs.
>>
> Currently, the qmp command "inject-nmi" doesn't pass ISA bridge. How
> do we address this situation?

That's step two below: make the ISA bridges implement NMIState.

>> First, you need to add a qemu_irq argument to rtc_init. The RTC can
>> raise/lower the IRQ on writes to port 0x70.
>>
>> Second, make the ISA bridges implement NMIState, where the
>> implementation of NMIState is similar to inject_nmi in hw/core/nmi.c:
>>
>>     CPU_FOREACH(cs) {
>> 	X86CPU *cpu = X86_CPU(cs);
>>
>> 	if (!cpu->apic_state) {
>> 	    cpu_interrupt(cs, CPU_INTERRUPT_NMI);
>> 	} else {
>> 	    apic_deliver_nmi(cpu->apic_state);
>> 	}
>>     }
>>
>> Third, the ISA bridges (hw/isa/piix4.c and hw/isa/lpc_ich9.c) need to
> 
> We don't use hw/isa/piix4.c but hw/pci-host/piix.c in x86 target. Right?

Right, I said I had certainly messed up something. :)

>> export a qemu_irq for nmi_en IRQ (e.g. using qdev_init_gpio_in_named),
>> and you should modify the ISA bridge's implementation of NMIState to
>> latch the NMI if you send one while NMIs are disabled.  The nmi_en IRQ
>> can also trigger an NMI when nmi_en is enabled and an NMI was latched.
> 
> Sorry, I'm a bit confused. The nmi_en can trigger an NMI? Isn't a flag bit which
> can enable/disable the NMI switch?

Suppose an NMI was injected with the monitor while nmi_en was disabled.
 The NMI is then latched, and triggered when you enable NMIs again with
nmi_en.

Thanks,

Paolo
Gonglei (Arei) Dec. 17, 2015, 9:04 a.m. UTC | #4
> 

> From: Paolo Bonzini [mailto:paolo.bonzini@gmail.com] On Behalf Of Paolo

> Bonzini

> Sent: Thursday, December 17, 2015 4:37 PM

> 

> 

> On 17/12/2015 08:17, Gonglei (Arei) wrote:

> >> On 16/12/2015 11:28, Gonglei (Arei) wrote:

> >>> I'll move the global nmi_disabled into RTCState, then I have to add

> >>> a global

> >> RTCState

> >>> Variable so that other C files can use the

> rtc_state->external_nmi_disabled.

> >>

> >> Hmm, I think it should be done differently.  This is a layering

> >> violation, the NMI_EN is essentially a pin (qemu_irq) between the ISA

> >> bridges and the RTC.  The NMI "button" is also a component of the ISA

> >

> > So, you mean the NMI_EN can only control NMI injection came from ISA

> bridge?

> > What's this NMI "button" mean?

> 

> The NMI command in the monitor is a "virtual NMI button".

> 

Okay, I see. 

> >> bridge; you should not need to touch anything except the RTC and the

> >> ISA bridges, in particular not the APICs.

> >>

> > Currently, the qmp command "inject-nmi" doesn't pass ISA bridge. How

> > do we address this situation?

> 

> That's step two below: make the ISA bridges implement NMIState.

> 

Yes, It's more reasonable.

> >> First, you need to add a qemu_irq argument to rtc_init. The RTC can

> >> raise/lower the IRQ on writes to port 0x70.

> >>

> >> Second, make the ISA bridges implement NMIState, where the

> >> implementation of NMIState is similar to inject_nmi in hw/core/nmi.c:

> >>

> >>     CPU_FOREACH(cs) {

> >> 	X86CPU *cpu = X86_CPU(cs);

> >>

> >> 	if (!cpu->apic_state) {

> >> 	    cpu_interrupt(cs, CPU_INTERRUPT_NMI);

> >> 	} else {

> >> 	    apic_deliver_nmi(cpu->apic_state);

> >> 	}

> >>     }

> >>

> >> Third, the ISA bridges (hw/isa/piix4.c and hw/isa/lpc_ich9.c) need to

> >

> > We don't use hw/isa/piix4.c but hw/pci-host/piix.c in x86 target. Right?

> 

> Right, I said I had certainly messed up something. :)

> 

> >> export a qemu_irq for nmi_en IRQ (e.g. using

> >> qdev_init_gpio_in_named), and you should modify the ISA bridge's

> >> implementation of NMIState to latch the NMI if you send one while

> >> NMIs are disabled.  The nmi_en IRQ can also trigger an NMI when nmi_en

> is enabled and an NMI was latched.

> >

> > Sorry, I'm a bit confused. The nmi_en can trigger an NMI? Isn't a flag

> > bit which can enable/disable the NMI switch?

> 

> Suppose an NMI was injected with the monitor while nmi_en was disabled.

>  The NMI is then latched, and triggered when you enable NMIs again with

> nmi_en.

> 

It's clear. I'll try to do this as your suggestion. Thank you so much!

Regards,
-Gonglei
diff mbox

Patch

diff --git a/hw/timer/mc146818rtc.c b/hw/timer/mc146818rtc.c
index e770e74..c019ec0 100644
--- a/hw/timer/mc146818rtc.c
+++ b/hw/timer/mc146818rtc.c
@@ -67,6 +67,7 @@  typedef struct RTCState {
     MemoryRegion io;
     uint8_t cmos_data[128];
     uint8_t cmos_index;
+    bool external_nmi_disabled;
     int32_t base_year;
     uint64_t base_rtc;
     uint64_t last_update;
@@ -89,6 +90,8 @@  typedef struct RTCState {
     QLIST_ENTRY(RTCState) link;
 } RTCState;
 
+static RTCState *rtc_global_state;
+
 static void rtc_set_time(RTCState *s);
 static void rtc_update_time(RTCState *s);
 static void rtc_set_cmos(RTCState *s, const struct tm *tm);
@@ -383,6 +386,11 @@  static void rtc_update_timer(void *opaque)
     check_update_timer(s);
 }
 
+bool external_nmi_is_disabled()
+{
+    return rtc_global_state->external_nmi_disabled;
+}
+
 static void cmos_ioport_write(void *opaque, hwaddr addr,
                               uint64_t data, unsigned size)
 {
@@ -397,9 +405,9 @@  static void cmos_ioport_write(void *opaque, hwaddr addr,
          * by applications, BIOS, and even the operating system since it is
          * used to block NMI assertions when sensitive code is executing.
          */
-        nmi_disabled = !!(data & NMI_DISABLE_BIT);
+        s->external_nmi_disabled = !!(data & NMI_DISABLE_BIT);
         CMOS_DPRINTF("cmos: nmi_disabled=%s\n",
-                     nmi_disabled ? "true" : "false");
+                     s->external_nmi_disabled ? "true" : "false");
         s->cmos_index = data & 0x7f;
     } else {
         CMOS_DPRINTF("cmos: write index=0x%02x val=0x%02" PRIx64 "\n",
@@ -930,6 +938,8 @@  ISADevice *rtc_init(ISABus *bus, int base_year, qemu_irq intercept_irq)
     }
     QLIST_INSERT_HEAD(&rtc_devices, s, link);
 
+    rtc_global_state = s;
+
     return isadev;
 }
 
diff --git a/include/hw/timer/mc146818rtc.h b/include/hw/timer/mc146818rtc.h
index eaf6497..761eba2 100644
--- a/include/hw/timer/mc146818rtc.h
+++ b/include/hw/timer/mc146818rtc.h
@@ -9,5 +9,6 @@ 
 ISADevice *rtc_init(ISABus *bus, int base_year, qemu_irq intercept_irq);
 void rtc_set_memory(ISADevice *dev, int addr, int val);
 int rtc_get_memory(ISADevice *dev, int addr);
+bool external_nmi_is_disabled();
 
 #endif /* !MC146818RTC_H */