diff mbox

[V2,1/2] ARM: PL061: Clear PL061 device state after reset

Message ID 1454359775-25959-1-git-send-email-wei@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Wei Huang Feb. 1, 2016, 8:49 p.m. UTC
Current QEMU doesn't clear PL061 state after reset. This causes a
weird issue with guest reboot via GPIO. Here is the device state
description with two reboot requests:

  (PL061State fields)           data   old_in_data   istate
VM boot                         0      0             0
After 1st ACPI reboot request   8      8             8
After VM PL061 driver ACK       8      8             0
After VM reboot                 8      8             0
------------------------------------------------------------
2nd ACPI reboot request         8

In the second reboot request above, because old_in_data field is 8,
QEMU decides that there is a pending edge IRQ already (see
pl061_update()) in input; so it doesn't raise up IRQ again. As a result
the second reboot request is lost. The correct way is to clear PL061
device state after reset.

NOTE: The reset state is found from the following documentation:
 - PL061 Technical Reference Manual
 - Stellaris LM3S8962 Microcontroller Data Sheet
 - Stellaris LM3S5P31 Microcontroller Data Sheet

Signed-off-by: Wei Huang <wei@redhat.com>
---
 hw/gpio/pl061.c | 32 ++++++++++++++++++++++++++++++--
 1 file changed, 30 insertions(+), 2 deletions(-)

Comments

Shannon Zhao Feb. 3, 2016, 12:46 p.m. UTC | #1
Hi Wei,

This still has a problem as I said before.

If we execute "virsh reboot xxx" during VM booting(i.e. before the PL061
driver loaded), then after VM booting, the VM will not have any reaction
to the "virsh reboot xxx".

On 2016/2/2 4:49, Wei Huang wrote:
> Current QEMU doesn't clear PL061 state after reset. This causes a
> weird issue with guest reboot via GPIO. Here is the device state
> description with two reboot requests:
> 
>   (PL061State fields)           data   old_in_data   istate
> VM boot                         0      0             0
> After 1st ACPI reboot request   8      8             8
> After VM PL061 driver ACK       8      8             0
> After VM reboot                 8      8             0
> ------------------------------------------------------------
> 2nd ACPI reboot request         8
> 
> In the second reboot request above, because old_in_data field is 8,
> QEMU decides that there is a pending edge IRQ already (see
> pl061_update()) in input; so it doesn't raise up IRQ again. As a result
> the second reboot request is lost. The correct way is to clear PL061
> device state after reset.
> 
> NOTE: The reset state is found from the following documentation:
>  - PL061 Technical Reference Manual
>  - Stellaris LM3S8962 Microcontroller Data Sheet
>  - Stellaris LM3S5P31 Microcontroller Data Sheet
> 
> Signed-off-by: Wei Huang <wei@redhat.com>
> ---
>  hw/gpio/pl061.c | 32 ++++++++++++++++++++++++++++++--
>  1 file changed, 30 insertions(+), 2 deletions(-)
> 
> diff --git a/hw/gpio/pl061.c b/hw/gpio/pl061.c
> index e5a696e..342a70d 100644
> --- a/hw/gpio/pl061.c
> +++ b/hw/gpio/pl061.c
> @@ -284,8 +284,35 @@ static void pl061_write(void *opaque, hwaddr offset,
>  
>  static void pl061_reset(PL061State *s)
>  {
> -  s->locked = 1;
> -  s->cr = 0xff;
> +    /* reset values from PL061 TRM, Stellaris LM3S5P31 & LM3S8962 Data Sheet */
> +    s->data = 0;
> +    s->old_out_data = 0;
> +    s->old_in_data = 0;
> +    s->dir = 0;
> +    s->isense = 0;
> +    s->ibe = 0;
> +    s->iev = 0;
> +    s->im = 0;
> +    s->istate = 0;
> +    s->afsel = 0;
> +    s->dr2r = 0xff;
> +    s->dr4r = 0;
> +    s->dr8r = 0;
> +    s->odr = 0;
> +    s->pur = 0;
> +    s->pdr = 0;
> +    s->slr = 0;
> +    s->den = 0;
> +    s->locked = 1;
> +    s->cr = 0xff;
> +    s->amsel = 0;
> +}
> +
> +static void pl061_state_reset(DeviceState *dev)
> +{
> +    PL061State *s = PL061(dev);
> +
> +    pl061_reset(s);
>  }
>  
>  static void pl061_set_irq(void * opaque, int irq, int level)
> @@ -343,6 +370,7 @@ static void pl061_class_init(ObjectClass *klass, void *data)
>  
>      k->init = pl061_initfn;
>      dc->vmsd = &vmstate_pl061;
> +    dc->reset = &pl061_state_reset;
>  }
>  
>  static const TypeInfo pl061_info = {
>
Peter Maydell Feb. 16, 2016, 2:35 p.m. UTC | #2
On 1 February 2016 at 20:49, Wei Huang <wei@redhat.com> wrote:
> Current QEMU doesn't clear PL061 state after reset. This causes a
> weird issue with guest reboot via GPIO. Here is the device state
> description with two reboot requests:
>
>   (PL061State fields)           data   old_in_data   istate
> VM boot                         0      0             0
> After 1st ACPI reboot request   8      8             8
> After VM PL061 driver ACK       8      8             0
> After VM reboot                 8      8             0
> ------------------------------------------------------------
> 2nd ACPI reboot request         8
>
> In the second reboot request above, because old_in_data field is 8,
> QEMU decides that there is a pending edge IRQ already (see
> pl061_update()) in input; so it doesn't raise up IRQ again. As a result
> the second reboot request is lost. The correct way is to clear PL061
> device state after reset.
>
> NOTE: The reset state is found from the following documentation:
>  - PL061 Technical Reference Manual
>  - Stellaris LM3S8962 Microcontroller Data Sheet
>  - Stellaris LM3S5P31 Microcontroller Data Sheet
>
> Signed-off-by: Wei Huang <wei@redhat.com>
> ---
>  hw/gpio/pl061.c | 32 ++++++++++++++++++++++++++++++--
>  1 file changed, 30 insertions(+), 2 deletions(-)
>
> diff --git a/hw/gpio/pl061.c b/hw/gpio/pl061.c
> index e5a696e..342a70d 100644
> --- a/hw/gpio/pl061.c
> +++ b/hw/gpio/pl061.c
> @@ -284,8 +284,35 @@ static void pl061_write(void *opaque, hwaddr offset,
>
>  static void pl061_reset(PL061State *s)
>  {
> -  s->locked = 1;
> -  s->cr = 0xff;
> +    /* reset values from PL061 TRM, Stellaris LM3S5P31 & LM3S8962 Data Sheet */
> +    s->data = 0;
> +    s->old_out_data = 0;
> +    s->old_in_data = 0;
> +    s->dir = 0;
> +    s->isense = 0;
> +    s->ibe = 0;
> +    s->iev = 0;
> +    s->im = 0;
> +    s->istate = 0;
> +    s->afsel = 0;
> +    s->dr2r = 0xff;
> +    s->dr4r = 0;
> +    s->dr8r = 0;
> +    s->odr = 0;
> +    s->pur = 0;
> +    s->pdr = 0;
> +    s->slr = 0;
> +    s->den = 0;
> +    s->locked = 1;
> +    s->cr = 0xff;
> +    s->amsel = 0;
> +}

These reset values are all OK...

> +
> +static void pl061_state_reset(DeviceState *dev)
> +{
> +    PL061State *s = PL061(dev);
> +
> +    pl061_reset(s);
>  }

...but you don't need to have this wrapper function.
You can just do the reset in a function called pl061_reset()
with the function signature we need for dc->reset.
The only place that currently calls the existing pl061_reset()
is the device's init function, and you can delete that call
because the Device framework automatically calls the dc->reset
function after device initialization.

thanks
-- PMM
Peter Maydell Feb. 16, 2016, 2:39 p.m. UTC | #3
On 16 February 2016 at 14:35, Peter Maydell <peter.maydell@linaro.org> wrote:
> On 1 February 2016 at 20:49, Wei Huang <wei@redhat.com> wrote:
>> Current QEMU doesn't clear PL061 state after reset. This causes a
>> weird issue with guest reboot via GPIO. Here is the device state
>> description with two reboot requests:

>
> These reset values are all OK...
>
>> +
>> +static void pl061_state_reset(DeviceState *dev)
>> +{
>> +    PL061State *s = PL061(dev);
>> +
>> +    pl061_reset(s);
>>  }
>
> ...but you don't need to have this wrapper function.
> You can just do the reset in a function called pl061_reset()
> with the function signature we need for dc->reset.
> The only place that currently calls the existing pl061_reset()
> is the device's init function, and you can delete that call
> because the Device framework automatically calls the dc->reset
> function after device initialization.

I know this patch doesn't (by itself) fix the issues with guest
reboot, but I think it is worth having anyway because not resetting
the PL061 state is a genuine bug. Can you do a v3 and resend, please?

PS: please could you include a cover letter email next time round,
since this is a multi patch series?

Side note: half our "PL061" behaviour is actually specific
to the TI variant in the Luminary, and for our plain old PL061
we ought to restrict access to the registers that are Stellaris
only. But that's a different bug and not a very major one.

thanks
-- PMM
Wei Huang Feb. 17, 2016, 5:34 p.m. UTC | #4
On 02/16/2016 08:39 AM, Peter Maydell wrote:
> On 16 February 2016 at 14:35, Peter Maydell <peter.maydell@linaro.org> wrote:
>> On 1 February 2016 at 20:49, Wei Huang <wei@redhat.com> wrote:
>>> Current QEMU doesn't clear PL061 state after reset. This causes a
>>> weird issue with guest reboot via GPIO. Here is the device state
>>> description with two reboot requests:
> 
>>
>> These reset values are all OK...
>>
>>> +
>>> +static void pl061_state_reset(DeviceState *dev)
>>> +{
>>> +    PL061State *s = PL061(dev);
>>> +
>>> +    pl061_reset(s);
>>>  }
>>
>> ...but you don't need to have this wrapper function.
>> You can just do the reset in a function called pl061_reset()
>> with the function signature we need for dc->reset.
>> The only place that currently calls the existing pl061_reset()
>> is the device's init function, and you can delete that call
>> because the Device framework automatically calls the dc->reset
>> function after device initialization.
> 
> I know this patch doesn't (by itself) fix the issues with guest
> reboot, but I think it is worth having anyway because not resetting
> the PL061 state is a genuine bug. Can you do a v3 and resend, please?
> 
> PS: please could you include a cover letter email next time round,
> since this is a multi patch series?

Done, please review.

> 
> Side note: half our "PL061" behaviour is actually specific
> to the TI variant in the Luminary, and for our plain old PL061
> we ought to restrict access to the registers that are Stellaris
> only. But that's a different bug and not a very major one.

Thanks for your suggestion. I was trying to fix it. The plan was to add
a new field rsvd_addr in "struct PL061State". Then in pl061_read() and
pl061_write(), we can check offset against [rsvd_addr, 0xfcc] (ignored
if inside).

While I was working on it, I realized that this is a benign issue. It is
true that PL061 device can access Luminary registers in the reserved
memory area. However QEMU doesn't use these Luminary registers anywhere
else other than pl061_read() and pl061_write(). It basically passes the
read/write requests through. I don't see a malicious driver can damage
device state. Thoughts?

Thanks,
-Wei

> 
> thanks
> -- PMM
>
Peter Maydell Feb. 17, 2016, 5:53 p.m. UTC | #5
On 17 February 2016 at 17:34, Wei Huang <wei@redhat.com> wrote:
> On 02/16/2016 08:39 AM, Peter Maydell wrote:
>> Side note: half our "PL061" behaviour is actually specific
>> to the TI variant in the Luminary, and for our plain old PL061
>> we ought to restrict access to the registers that are Stellaris
>> only. But that's a different bug and not a very major one.
>
> Thanks for your suggestion. I was trying to fix it. The plan was to add
> a new field rsvd_addr in "struct PL061State". Then in pl061_read() and
> pl061_write(), we can check offset against [rsvd_addr, 0xfcc] (ignored
> if inside).
>
> While I was working on it, I realized that this is a benign issue. It is
> true that PL061 device can access Luminary registers in the reserved
> memory area. However QEMU doesn't use these Luminary registers anywhere
> else other than pl061_read() and pl061_write(). It basically passes the
> read/write requests through. I don't see a malicious driver can damage
> device state. Thoughts?

It's not a "malicious guest can do bad things" bug, it's a "modelled
hardware doesn't behave like the real thing" bug. A non-Luminary PL061
should act like the hardware, which means that the registers that don't
exist should be RAZ/WI (and should log guest-errors if the guest tries
to access them), the same way we do in the "default" case of the
case statements for other reserved registers.

thanks
-- PMM
diff mbox

Patch

diff --git a/hw/gpio/pl061.c b/hw/gpio/pl061.c
index e5a696e..342a70d 100644
--- a/hw/gpio/pl061.c
+++ b/hw/gpio/pl061.c
@@ -284,8 +284,35 @@  static void pl061_write(void *opaque, hwaddr offset,
 
 static void pl061_reset(PL061State *s)
 {
-  s->locked = 1;
-  s->cr = 0xff;
+    /* reset values from PL061 TRM, Stellaris LM3S5P31 & LM3S8962 Data Sheet */
+    s->data = 0;
+    s->old_out_data = 0;
+    s->old_in_data = 0;
+    s->dir = 0;
+    s->isense = 0;
+    s->ibe = 0;
+    s->iev = 0;
+    s->im = 0;
+    s->istate = 0;
+    s->afsel = 0;
+    s->dr2r = 0xff;
+    s->dr4r = 0;
+    s->dr8r = 0;
+    s->odr = 0;
+    s->pur = 0;
+    s->pdr = 0;
+    s->slr = 0;
+    s->den = 0;
+    s->locked = 1;
+    s->cr = 0xff;
+    s->amsel = 0;
+}
+
+static void pl061_state_reset(DeviceState *dev)
+{
+    PL061State *s = PL061(dev);
+
+    pl061_reset(s);
 }
 
 static void pl061_set_irq(void * opaque, int irq, int level)
@@ -343,6 +370,7 @@  static void pl061_class_init(ObjectClass *klass, void *data)
 
     k->init = pl061_initfn;
     dc->vmsd = &vmstate_pl061;
+    dc->reset = &pl061_state_reset;
 }
 
 static const TypeInfo pl061_info = {