diff mbox series

[1/2] xen/mm: fold PGC_broken into PGC_state bits

Message ID 20200319212150.2651419-1-dwmw2@infradead.org (mailing list archive)
State New, archived
Headers show
Series Handle | expand

Commit Message

David Woodhouse March 19, 2020, 9:21 p.m. UTC
From: David Woodhouse <dwmw@amazon.co.uk>

Only PGC_state_offlining and PGC_state_offlined are valid in conjunction
with PGC_broken. The other two states (free and inuse) were never valid
for a broken page.

By folding PGC_broken in, we can have three bits for PGC_state which
allows up to 8 states, of which 6 are currently used and 2 are available
for new use cases.

Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
---
 xen/arch/x86/domctl.c    |  2 +-
 xen/common/page_alloc.c  | 66 ++++++++++++++++++++++------------------
 xen/include/asm-arm/mm.h | 38 +++++++++++++++--------
 xen/include/asm-x86/mm.h | 36 ++++++++++++++++------
 4 files changed, 89 insertions(+), 53 deletions(-)

Comments

Paul Durrant March 20, 2020, 1:17 p.m. UTC | #1
> -----Original Message-----
> From: Xen-devel <xen-devel-bounces@lists.xenproject.org> On Behalf Of David Woodhouse
> Sent: 19 March 2020 21:22
> To: xen-devel@lists.xenproject.org
> Cc: Stefano Stabellini <sstabellini@kernel.org>; Julien Grall <julien@xen.org>; Wei Liu <wl@xen.org>;
> Andrew Cooper <andrew.cooper3@citrix.com>; Ian Jackson <ian.jackson@eu.citrix.com>; George Dunlap
> <george.dunlap@citrix.com>; hongyxia@amazon.com; Jan Beulich <jbeulich@suse.com>; Volodymyr Babchuk
> <Volodymyr_Babchuk@epam.com>; Roger Pau Monné <roger.pau@citrix.com>
> Subject: [Xen-devel] [PATCH 1/2] xen/mm: fold PGC_broken into PGC_state bits
> 
> From: David Woodhouse <dwmw@amazon.co.uk>
> 
> Only PGC_state_offlining and PGC_state_offlined are valid in conjunction
> with PGC_broken. The other two states (free and inuse) were never valid
> for a broken page.
> 
> By folding PGC_broken in, we can have three bits for PGC_state which
> allows up to 8 states, of which 6 are currently used and 2 are available
> for new use cases.
> 
> Signed-off-by: David Woodhouse <dwmw@amazon.co.uk>
> ---
>  xen/arch/x86/domctl.c    |  2 +-
>  xen/common/page_alloc.c  | 66 ++++++++++++++++++++++------------------
>  xen/include/asm-arm/mm.h | 38 +++++++++++++++--------
>  xen/include/asm-x86/mm.h | 36 ++++++++++++++++------
>  4 files changed, 89 insertions(+), 53 deletions(-)
> 
> diff --git a/xen/arch/x86/domctl.c b/xen/arch/x86/domctl.c
> index ed86762fa6..a411f64afa 100644
> --- a/xen/arch/x86/domctl.c
> +++ b/xen/arch/x86/domctl.c
> @@ -422,7 +422,7 @@ long arch_do_domctl(
>                  if ( page->u.inuse.type_info & PGT_pinned )
>                      type |= XEN_DOMCTL_PFINFO_LPINTAB;
> 
> -                if ( page->count_info & PGC_broken )
> +                if ( page_is_broken(page) )
>                      type = XEN_DOMCTL_PFINFO_BROKEN;
>              }
> 
> diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c
> index 76d37226df..8d72a64f4e 100644
> --- a/xen/common/page_alloc.c
> +++ b/xen/common/page_alloc.c
> @@ -1093,7 +1093,7 @@ static int reserve_offlined_page(struct page_info *head)
>          struct page_info *pg;
>          int next_order;
> 
> -        if ( page_state_is(cur_head, offlined) )
> +        if ( page_is_offlined(cur_head) )
>          {
>              cur_head++;
>              if ( first_dirty != INVALID_DIRTY_IDX && first_dirty )
> @@ -1113,7 +1113,7 @@ static int reserve_offlined_page(struct page_info *head)
>              for ( i = (1 << cur_order), pg = cur_head + (1 << cur_order );
>                    i < (1 << next_order);
>                    i++, pg++ )
> -                if ( page_state_is(pg, offlined) )
> +                if ( page_is_offlined(pg) )
>                      break;
>              if ( i == ( 1 << next_order) )
>              {
> @@ -1145,16 +1145,20 @@ static int reserve_offlined_page(struct page_info *head)
> 
>      for ( cur_head = head; cur_head < head + ( 1UL << head_order); cur_head++ )
>      {
> -        if ( !page_state_is(cur_head, offlined) )
> +        struct page_list_head *list;
> +
> +        if ( page_state_is(cur_head, offlined) )
> +            list = &page_offlined_list;
> +        else if (page_state_is(cur_head, broken) )
> +            list = &page_broken_list;
> +        else
>              continue;
> 
>          avail[node][zone]--;
>          total_avail_pages--;
>          ASSERT(total_avail_pages >= 0);
> 
> -        page_list_add_tail(cur_head,
> -                           test_bit(_PGC_broken, &cur_head->count_info) ?
> -                           &page_broken_list : &page_offlined_list);
> +        page_list_add_tail(cur_head, list);
> 
>          count++;
>      }
> @@ -1404,13 +1408,16 @@ static void free_heap_pages(
>          switch ( pg[i].count_info & PGC_state )
>          {
>          case PGC_state_inuse:
> -            BUG_ON(pg[i].count_info & PGC_broken);
>              pg[i].count_info = PGC_state_free;
>              break;
> 
>          case PGC_state_offlining:
> -            pg[i].count_info = (pg[i].count_info & PGC_broken) |
> -                               PGC_state_offlined;
> +            pg[i].count_info = PGC_state_offlined;
> +            tainted = 1;
> +            break;
> +
> +        case PGC_state_broken_offlining:
> +            pg[i].count_info = PGC_state_broken;
>              tainted = 1;
>              break;
> 
> @@ -1527,16 +1534,16 @@ static unsigned long mark_page_offline(struct page_info *pg, int broken)
>      do {
>          nx = x = y;
> 
> -        if ( ((x & PGC_state) != PGC_state_offlined) &&
> -             ((x & PGC_state) != PGC_state_offlining) )
> -        {
> -            nx &= ~PGC_state;
> -            nx |= (((x & PGC_state) == PGC_state_free)
> -                   ? PGC_state_offlined : PGC_state_offlining);
> -        }
> +        nx &= ~PGC_state;
> 
> -        if ( broken )
> -            nx |= PGC_broken;
> +        /* If it was already broken, it stays broken */
> +        if ( pgc_is_broken(x) )
> +            broken = 1;
> +
> +        if ( pgc_is_offlined(x) || pgc_is(x, free) )
> +            nx |= broken ? PGC_state_broken : PGC_state_offlined;
> +        else
> +            nx |= broken ? PGC_state_broken_offlining : PGC_state_offlining;
> 
>          if ( x == nx )
>              break;
> @@ -1609,7 +1616,7 @@ int offline_page(mfn_t mfn, int broken, uint32_t *status)
>       * need to prevent malicious guest access the broken page again.
>       * Under such case, hypervisor shutdown guest, preventing recursive mce.
>       */
> -    if ( (pg->count_info & PGC_broken) && (owner = page_get_owner(pg)) )
> +    if ( page_is_broken(pg) && (owner = page_get_owner(pg)) )
>      {
>          *status = PG_OFFLINE_AGAIN;
>          domain_crash(owner);
> @@ -1620,7 +1627,7 @@ int offline_page(mfn_t mfn, int broken, uint32_t *status)
> 
>      old_info = mark_page_offline(pg, broken);
> 
> -    if ( page_state_is(pg, offlined) )
> +    if ( page_is_offlined(pg) )
>      {
>          reserve_heap_page(pg);
> 
> @@ -1699,19 +1706,18 @@ unsigned int online_page(mfn_t mfn, uint32_t *status)
>      do {
>          ret = *status = 0;
> 
> -        if ( y & PGC_broken )
> +        if ( pgc_is_broken(y) )
>          {
>              ret = -EINVAL;
> -            *status = PG_ONLINE_FAILED |PG_ONLINE_BROKEN;
> +            *status = PG_ONLINE_FAILED | PG_ONLINE_BROKEN;

Whitespace fix. Ought to be called out in the commit comment.

>              break;
>          }
> -
> -        if ( (y & PGC_state) == PGC_state_offlined )
> +        else if ( pgc_is(y, offlined) )
>          {
>              page_list_del(pg, &page_offlined_list);
>              *status = PG_ONLINE_ONLINED;
>          }
> -        else if ( (y & PGC_state) == PGC_state_offlining )
> +        else if ( pgc_is(y, offlining) )
>          {
>              *status = PG_ONLINE_ONLINED;
>          }
> @@ -1726,7 +1732,7 @@ unsigned int online_page(mfn_t mfn, uint32_t *status)
> 
>      spin_unlock(&heap_lock);
> 
> -    if ( (y & PGC_state) == PGC_state_offlined )
> +    if ( pgc_is(y, offlined) )
>          free_heap_pages(pg, 0, false);
> 
>      return ret;
> @@ -1747,11 +1753,11 @@ int query_page_offline(mfn_t mfn, uint32_t *status)
> 
>      pg = mfn_to_page(mfn);
> 
> -    if ( page_state_is(pg, offlining) )
> +    if ( page_is_offlining(pg) )
>          *status |= PG_OFFLINE_STATUS_OFFLINE_PENDING;
> -    if ( pg->count_info & PGC_broken )
> +    if ( page_is_broken(pg) )
>          *status |= PG_OFFLINE_STATUS_BROKEN;
> -    if ( page_state_is(pg, offlined) )
> +    if ( page_is_offlined(pg) )
>          *status |= PG_OFFLINE_STATUS_OFFLINED;
> 
>      spin_unlock(&heap_lock);
> @@ -2519,7 +2525,7 @@ __initcall(pagealloc_keyhandler_init);
> 
>  void scrub_one_page(struct page_info *pg)
>  {
> -    if ( unlikely(pg->count_info & PGC_broken) )
> +    if ( unlikely(page_is_broken(pg)) )
>          return;
> 
>  #ifndef NDEBUG
> diff --git a/xen/include/asm-arm/mm.h b/xen/include/asm-arm/mm.h
> index 7df91280bc..a877791d1c 100644
> --- a/xen/include/asm-arm/mm.h
> +++ b/xen/include/asm-arm/mm.h
> @@ -108,21 +108,35 @@ struct page_info
>    /* Page is Xen heap? */
>  #define _PGC_xen_heap     PG_shift(2)
>  #define PGC_xen_heap      PG_mask(1, 2)
> -/* ... */
> -/* Page is broken? */
> -#define _PGC_broken       PG_shift(7)
> -#define PGC_broken        PG_mask(1, 7)
> - /* Mutually-exclusive page states: { inuse, offlining, offlined, free }. */
> -#define PGC_state         PG_mask(3, 9)
> -#define PGC_state_inuse   PG_mask(0, 9)
> -#define PGC_state_offlining PG_mask(1, 9)
> -#define PGC_state_offlined PG_mask(2, 9)
> -#define PGC_state_free    PG_mask(3, 9)
> -#define page_state_is(pg, st) (((pg)->count_info&PGC_state) == PGC_state_##st)
> + /*
> +  * Mutually-exclusive page states:
> +  * { inuse, offlining, offlined, free, broken_offlining, broken }
> +  */
> +#define PGC_state                  PG_mask(7, 9)
> +#define PGC_state_inuse            PG_mask(0, 9)
> +#define PGC_state_offlining        PG_mask(1, 9)
> +#define PGC_state_offlined         PG_mask(2, 9)
> +#define PGC_state_free             PG_mask(3, 9)
> +#define PGC_state_broken_offlining PG_mask(4, 9) /* Broken and offlining */
> +#define PGC_state_broken           PG_mask(5, 9) /* Broken and offlined */
> +
> +#define pgc_is(pgc, st)            (((pgc) & PGC_state) == PGC_state_##st)
> +#define page_state_is(pg, st)       pgc_is((pg)->count_info, st)
> +
> +#define pgc_is_broken(pgc)         (pgc_is(pgc, broken) || \
> +                                    pgc_is(pgc, broken_offlining))
> +#define pgc_is_offlined(pgc)       (pgc_is(pgc, offlined) || \
> +                                    pgc_is(pgc, broken))
> +#define pgc_is_offlining(pgc)      (pgc_is(pgc, offlining) || \
> +                                    pgc_is(pgc, broken_offlining))
> +
> +#define page_is_broken(pg)         (pgc_is_broken((pg)->count_info))
> +#define page_is_offlined(pg)       (pgc_is_broken((pg)->count_info))
> +#define page_is_offlining(pg)      (pgc_is_broken((pg)->count_info))
> +
>  /* Page is not reference counted */
>  #define _PGC_extra        PG_shift(10)
>  #define PGC_extra         PG_mask(1, 10)
> -

Extraneous whitespace change.

>  /* Count of references to this frame. */
>  #define PGC_count_width   PG_shift(10)
>  #define PGC_count_mask    ((1UL<<PGC_count_width)-1)
> diff --git a/xen/include/asm-x86/mm.h b/xen/include/asm-x86/mm.h
> index a06b2fb81f..1203f1b179 100644
> --- a/xen/include/asm-x86/mm.h
> +++ b/xen/include/asm-x86/mm.h
> @@ -67,16 +67,32 @@
>   /* 3-bit PAT/PCD/PWT cache-attribute hint. */
>  #define PGC_cacheattr_base PG_shift(6)
>  #define PGC_cacheattr_mask PG_mask(7, 6)
> - /* Page is broken? */
> -#define _PGC_broken       PG_shift(7)
> -#define PGC_broken        PG_mask(1, 7)
> - /* Mutually-exclusive page states: { inuse, offlining, offlined, free }. */
> -#define PGC_state         PG_mask(3, 9)
> -#define PGC_state_inuse   PG_mask(0, 9)
> -#define PGC_state_offlining PG_mask(1, 9)
> -#define PGC_state_offlined PG_mask(2, 9)
> -#define PGC_state_free    PG_mask(3, 9)
> -#define page_state_is(pg, st) (((pg)->count_info&PGC_state) == PGC_state_##st)
> + /*
> +  * Mutually-exclusive page states:
> +  * { inuse, offlining, offlined, free, broken_offlining, broken }
> +  */
> +#define PGC_state                  PG_mask(7, 9)
> +#define PGC_state_inuse            PG_mask(0, 9)
> +#define PGC_state_offlining        PG_mask(1, 9)
> +#define PGC_state_offlined         PG_mask(2, 9)
> +#define PGC_state_free             PG_mask(3, 9)
> +#define PGC_state_broken_offlining PG_mask(4, 9) /* Broken and offlining */
> +#define PGC_state_broken           PG_mask(5, 9) /* Broken and offlined */
> +
> +#define pgc_is(pgc, st)            (((pgc) & PGC_state) == PGC_state_##st)

Maybe pgc_state_is() for consistency? Might also draw attention to the difference between e.g.:

pgc_is(pgc, offlined) and pgc_is_offlined(pgc) 

> +#define page_state_is(pg, st)       pgc_is((pg)->count_info, st)

Indentation looks wrong.

^^ Same for the arm code.

  Paul

> +
> +#define pgc_is_broken(pgc)         (pgc_is(pgc, broken) || \
> +                                    pgc_is(pgc, broken_offlining))
> +#define pgc_is_offlined(pgc)       (pgc_is(pgc, offlined) || \
> +                                    pgc_is(pgc, broken))
> +#define pgc_is_offlining(pgc)      (pgc_is(pgc, offlining) || \
> +                                    pgc_is(pgc, broken_offlining))
> +
> +#define page_is_broken(pg)         (pgc_is_broken((pg)->count_info))
> +#define page_is_offlined(pg)       (pgc_is_broken((pg)->count_info))
> +#define page_is_offlining(pg)      (pgc_is_broken((pg)->count_info))
> +
>  /* Page is not reference counted */
>  #define _PGC_extra        PG_shift(10)
>  #define PGC_extra         PG_mask(1, 10)
> --
> 2.21.0
> 
> 
> _______________________________________________
> Xen-devel mailing list
> Xen-devel@lists.xenproject.org
> https://lists.xenproject.org/mailman/listinfo/xen-devel
Jan Beulich March 31, 2020, noon UTC | #2
On 19.03.2020 22:21, David Woodhouse wrote:
> --- a/xen/arch/x86/domctl.c
> +++ b/xen/arch/x86/domctl.c
> @@ -422,7 +422,7 @@ long arch_do_domctl(
>                  if ( page->u.inuse.type_info & PGT_pinned )
>                      type |= XEN_DOMCTL_PFINFO_LPINTAB;
>  
> -                if ( page->count_info & PGC_broken )
> +                if ( page_is_broken(page) )
>                      type = XEN_DOMCTL_PFINFO_BROKEN;

Coming back to an earlier request of mine: There are no locks being
held here afaics, so with

#define page_is_broken(pg)         (pgc_is_broken((pg)->count_info))

and

#define pgc_is_broken(pgc)         (pgc_is(pgc, broken) || \
                                    pgc_is(pgc, broken_offlining))

there's a chance that the page gets transitioned from
broken_offlining to broken (by another CPU) between these two
checks, resulting in wrong returned state. Either the latter macro
uses an intermediate variable and ACCESS_ONCE() or, as suggested
before, enumerators get arranged such that the check can be done
(e.g. using binary masking operations) with a single evaluation of
pgc.

This may or may not also be an issue for the other two pgc_is_*(),
but I think at least for symmetry they should then follow suit. At
the very least all three macros need to be immune to uses like
page_is_offlined(pg++) or similar argument expressions with side
effects.

> @@ -1699,19 +1706,18 @@ unsigned int online_page(mfn_t mfn, uint32_t *status)
>      do {
>          ret = *status = 0;
>  
> -        if ( y & PGC_broken )
> +        if ( pgc_is_broken(y) )
>          {
>              ret = -EINVAL;
> -            *status = PG_ONLINE_FAILED |PG_ONLINE_BROKEN;
> +            *status = PG_ONLINE_FAILED | PG_ONLINE_BROKEN;
>              break;
>          }
> -
> -        if ( (y & PGC_state) == PGC_state_offlined )
> +        else if ( pgc_is(y, offlined) )

At the risk of getting flamed again: Even if it was a matter of
taste in new code whether to use "else" in a case like this one,
this isn't new code, and it is in no way necessary to change what
we have for the purpose of this patch. I.e. without even having
to resort to the question of whether personal taste decisions are
to be accepted, this simply falls under "no unrelated /
unnecessary changes please". (FAOD this includes the deletion of
the blank line then as well.)

> --- a/xen/include/asm-x86/mm.h
> +++ b/xen/include/asm-x86/mm.h
> @@ -67,16 +67,32 @@
>   /* 3-bit PAT/PCD/PWT cache-attribute hint. */
>  #define PGC_cacheattr_base PG_shift(6)
>  #define PGC_cacheattr_mask PG_mask(7, 6)
> - /* Page is broken? */
> -#define _PGC_broken       PG_shift(7)
> -#define PGC_broken        PG_mask(1, 7)
> - /* Mutually-exclusive page states: { inuse, offlining, offlined, free }. */
> -#define PGC_state         PG_mask(3, 9)
> -#define PGC_state_inuse   PG_mask(0, 9)
> -#define PGC_state_offlining PG_mask(1, 9)
> -#define PGC_state_offlined PG_mask(2, 9)
> -#define PGC_state_free    PG_mask(3, 9)
> -#define page_state_is(pg, st) (((pg)->count_info&PGC_state) == PGC_state_##st)
> + /*
> +  * Mutually-exclusive page states:
> +  * { inuse, offlining, offlined, free, broken_offlining, broken }
> +  */
> +#define PGC_state                  PG_mask(7, 9)
> +#define PGC_state_inuse            PG_mask(0, 9)
> +#define PGC_state_offlining        PG_mask(1, 9)
> +#define PGC_state_offlined         PG_mask(2, 9)
> +#define PGC_state_free             PG_mask(3, 9)
> +#define PGC_state_broken_offlining PG_mask(4, 9) /* Broken and offlining */
> +#define PGC_state_broken           PG_mask(5, 9) /* Broken and offlined */
> +
> +#define pgc_is(pgc, st)            (((pgc) & PGC_state) == PGC_state_##st)
> +#define page_state_is(pg, st)       pgc_is((pg)->count_info, st)
> +
> +#define pgc_is_broken(pgc)         (pgc_is(pgc, broken) || \
> +                                    pgc_is(pgc, broken_offlining))
> +#define pgc_is_offlined(pgc)       (pgc_is(pgc, offlined) || \
> +                                    pgc_is(pgc, broken))
> +#define pgc_is_offlining(pgc)      (pgc_is(pgc, offlining) || \
> +                                    pgc_is(pgc, broken_offlining))
> +
> +#define page_is_broken(pg)         (pgc_is_broken((pg)->count_info))
> +#define page_is_offlined(pg)       (pgc_is_broken((pg)->count_info))
> +#define page_is_offlining(pg)      (pgc_is_broken((pg)->count_info))

Copy-and-paste mistake (rhs is the same for all three; same for Arm)?
Also there's no need here for the outer pairs of parentheses.

Also, for the next version, may I ask that you number versions in
the subject's tag and that you provide a brief description of
changes from the previous version (if any, but there ought to be
some in a series for there to be a point to send out)? Thanks.

Jan
diff mbox series

Patch

diff --git a/xen/arch/x86/domctl.c b/xen/arch/x86/domctl.c
index ed86762fa6..a411f64afa 100644
--- a/xen/arch/x86/domctl.c
+++ b/xen/arch/x86/domctl.c
@@ -422,7 +422,7 @@  long arch_do_domctl(
                 if ( page->u.inuse.type_info & PGT_pinned )
                     type |= XEN_DOMCTL_PFINFO_LPINTAB;
 
-                if ( page->count_info & PGC_broken )
+                if ( page_is_broken(page) )
                     type = XEN_DOMCTL_PFINFO_BROKEN;
             }
 
diff --git a/xen/common/page_alloc.c b/xen/common/page_alloc.c
index 76d37226df..8d72a64f4e 100644
--- a/xen/common/page_alloc.c
+++ b/xen/common/page_alloc.c
@@ -1093,7 +1093,7 @@  static int reserve_offlined_page(struct page_info *head)
         struct page_info *pg;
         int next_order;
 
-        if ( page_state_is(cur_head, offlined) )
+        if ( page_is_offlined(cur_head) )
         {
             cur_head++;
             if ( first_dirty != INVALID_DIRTY_IDX && first_dirty )
@@ -1113,7 +1113,7 @@  static int reserve_offlined_page(struct page_info *head)
             for ( i = (1 << cur_order), pg = cur_head + (1 << cur_order );
                   i < (1 << next_order);
                   i++, pg++ )
-                if ( page_state_is(pg, offlined) )
+                if ( page_is_offlined(pg) )
                     break;
             if ( i == ( 1 << next_order) )
             {
@@ -1145,16 +1145,20 @@  static int reserve_offlined_page(struct page_info *head)
 
     for ( cur_head = head; cur_head < head + ( 1UL << head_order); cur_head++ )
     {
-        if ( !page_state_is(cur_head, offlined) )
+        struct page_list_head *list;
+
+        if ( page_state_is(cur_head, offlined) )
+            list = &page_offlined_list;
+        else if (page_state_is(cur_head, broken) )
+            list = &page_broken_list;
+        else
             continue;
 
         avail[node][zone]--;
         total_avail_pages--;
         ASSERT(total_avail_pages >= 0);
 
-        page_list_add_tail(cur_head,
-                           test_bit(_PGC_broken, &cur_head->count_info) ?
-                           &page_broken_list : &page_offlined_list);
+        page_list_add_tail(cur_head, list);
 
         count++;
     }
@@ -1404,13 +1408,16 @@  static void free_heap_pages(
         switch ( pg[i].count_info & PGC_state )
         {
         case PGC_state_inuse:
-            BUG_ON(pg[i].count_info & PGC_broken);
             pg[i].count_info = PGC_state_free;
             break;
 
         case PGC_state_offlining:
-            pg[i].count_info = (pg[i].count_info & PGC_broken) |
-                               PGC_state_offlined;
+            pg[i].count_info = PGC_state_offlined;
+            tainted = 1;
+            break;
+
+        case PGC_state_broken_offlining:
+            pg[i].count_info = PGC_state_broken;
             tainted = 1;
             break;
 
@@ -1527,16 +1534,16 @@  static unsigned long mark_page_offline(struct page_info *pg, int broken)
     do {
         nx = x = y;
 
-        if ( ((x & PGC_state) != PGC_state_offlined) &&
-             ((x & PGC_state) != PGC_state_offlining) )
-        {
-            nx &= ~PGC_state;
-            nx |= (((x & PGC_state) == PGC_state_free)
-                   ? PGC_state_offlined : PGC_state_offlining);
-        }
+        nx &= ~PGC_state;
 
-        if ( broken )
-            nx |= PGC_broken;
+        /* If it was already broken, it stays broken */
+        if ( pgc_is_broken(x) )
+            broken = 1;
+
+        if ( pgc_is_offlined(x) || pgc_is(x, free) )
+            nx |= broken ? PGC_state_broken : PGC_state_offlined;
+        else
+            nx |= broken ? PGC_state_broken_offlining : PGC_state_offlining;
 
         if ( x == nx )
             break;
@@ -1609,7 +1616,7 @@  int offline_page(mfn_t mfn, int broken, uint32_t *status)
      * need to prevent malicious guest access the broken page again.
      * Under such case, hypervisor shutdown guest, preventing recursive mce.
      */
-    if ( (pg->count_info & PGC_broken) && (owner = page_get_owner(pg)) )
+    if ( page_is_broken(pg) && (owner = page_get_owner(pg)) )
     {
         *status = PG_OFFLINE_AGAIN;
         domain_crash(owner);
@@ -1620,7 +1627,7 @@  int offline_page(mfn_t mfn, int broken, uint32_t *status)
 
     old_info = mark_page_offline(pg, broken);
 
-    if ( page_state_is(pg, offlined) )
+    if ( page_is_offlined(pg) )
     {
         reserve_heap_page(pg);
 
@@ -1699,19 +1706,18 @@  unsigned int online_page(mfn_t mfn, uint32_t *status)
     do {
         ret = *status = 0;
 
-        if ( y & PGC_broken )
+        if ( pgc_is_broken(y) )
         {
             ret = -EINVAL;
-            *status = PG_ONLINE_FAILED |PG_ONLINE_BROKEN;
+            *status = PG_ONLINE_FAILED | PG_ONLINE_BROKEN;
             break;
         }
-
-        if ( (y & PGC_state) == PGC_state_offlined )
+        else if ( pgc_is(y, offlined) )
         {
             page_list_del(pg, &page_offlined_list);
             *status = PG_ONLINE_ONLINED;
         }
-        else if ( (y & PGC_state) == PGC_state_offlining )
+        else if ( pgc_is(y, offlining) )
         {
             *status = PG_ONLINE_ONLINED;
         }
@@ -1726,7 +1732,7 @@  unsigned int online_page(mfn_t mfn, uint32_t *status)
 
     spin_unlock(&heap_lock);
 
-    if ( (y & PGC_state) == PGC_state_offlined )
+    if ( pgc_is(y, offlined) )
         free_heap_pages(pg, 0, false);
 
     return ret;
@@ -1747,11 +1753,11 @@  int query_page_offline(mfn_t mfn, uint32_t *status)
 
     pg = mfn_to_page(mfn);
 
-    if ( page_state_is(pg, offlining) )
+    if ( page_is_offlining(pg) )
         *status |= PG_OFFLINE_STATUS_OFFLINE_PENDING;
-    if ( pg->count_info & PGC_broken )
+    if ( page_is_broken(pg) )
         *status |= PG_OFFLINE_STATUS_BROKEN;
-    if ( page_state_is(pg, offlined) )
+    if ( page_is_offlined(pg) )
         *status |= PG_OFFLINE_STATUS_OFFLINED;
 
     spin_unlock(&heap_lock);
@@ -2519,7 +2525,7 @@  __initcall(pagealloc_keyhandler_init);
 
 void scrub_one_page(struct page_info *pg)
 {
-    if ( unlikely(pg->count_info & PGC_broken) )
+    if ( unlikely(page_is_broken(pg)) )
         return;
 
 #ifndef NDEBUG
diff --git a/xen/include/asm-arm/mm.h b/xen/include/asm-arm/mm.h
index 7df91280bc..a877791d1c 100644
--- a/xen/include/asm-arm/mm.h
+++ b/xen/include/asm-arm/mm.h
@@ -108,21 +108,35 @@  struct page_info
   /* Page is Xen heap? */
 #define _PGC_xen_heap     PG_shift(2)
 #define PGC_xen_heap      PG_mask(1, 2)
-/* ... */
-/* Page is broken? */
-#define _PGC_broken       PG_shift(7)
-#define PGC_broken        PG_mask(1, 7)
- /* Mutually-exclusive page states: { inuse, offlining, offlined, free }. */
-#define PGC_state         PG_mask(3, 9)
-#define PGC_state_inuse   PG_mask(0, 9)
-#define PGC_state_offlining PG_mask(1, 9)
-#define PGC_state_offlined PG_mask(2, 9)
-#define PGC_state_free    PG_mask(3, 9)
-#define page_state_is(pg, st) (((pg)->count_info&PGC_state) == PGC_state_##st)
+ /*
+  * Mutually-exclusive page states:
+  * { inuse, offlining, offlined, free, broken_offlining, broken }
+  */
+#define PGC_state                  PG_mask(7, 9)
+#define PGC_state_inuse            PG_mask(0, 9)
+#define PGC_state_offlining        PG_mask(1, 9)
+#define PGC_state_offlined         PG_mask(2, 9)
+#define PGC_state_free             PG_mask(3, 9)
+#define PGC_state_broken_offlining PG_mask(4, 9) /* Broken and offlining */
+#define PGC_state_broken           PG_mask(5, 9) /* Broken and offlined */
+
+#define pgc_is(pgc, st)            (((pgc) & PGC_state) == PGC_state_##st)
+#define page_state_is(pg, st)       pgc_is((pg)->count_info, st)
+
+#define pgc_is_broken(pgc)         (pgc_is(pgc, broken) || \
+                                    pgc_is(pgc, broken_offlining))
+#define pgc_is_offlined(pgc)       (pgc_is(pgc, offlined) || \
+                                    pgc_is(pgc, broken))
+#define pgc_is_offlining(pgc)      (pgc_is(pgc, offlining) || \
+                                    pgc_is(pgc, broken_offlining))
+
+#define page_is_broken(pg)         (pgc_is_broken((pg)->count_info))
+#define page_is_offlined(pg)       (pgc_is_broken((pg)->count_info))
+#define page_is_offlining(pg)      (pgc_is_broken((pg)->count_info))
+
 /* Page is not reference counted */
 #define _PGC_extra        PG_shift(10)
 #define PGC_extra         PG_mask(1, 10)
-
 /* Count of references to this frame. */
 #define PGC_count_width   PG_shift(10)
 #define PGC_count_mask    ((1UL<<PGC_count_width)-1)
diff --git a/xen/include/asm-x86/mm.h b/xen/include/asm-x86/mm.h
index a06b2fb81f..1203f1b179 100644
--- a/xen/include/asm-x86/mm.h
+++ b/xen/include/asm-x86/mm.h
@@ -67,16 +67,32 @@ 
  /* 3-bit PAT/PCD/PWT cache-attribute hint. */
 #define PGC_cacheattr_base PG_shift(6)
 #define PGC_cacheattr_mask PG_mask(7, 6)
- /* Page is broken? */
-#define _PGC_broken       PG_shift(7)
-#define PGC_broken        PG_mask(1, 7)
- /* Mutually-exclusive page states: { inuse, offlining, offlined, free }. */
-#define PGC_state         PG_mask(3, 9)
-#define PGC_state_inuse   PG_mask(0, 9)
-#define PGC_state_offlining PG_mask(1, 9)
-#define PGC_state_offlined PG_mask(2, 9)
-#define PGC_state_free    PG_mask(3, 9)
-#define page_state_is(pg, st) (((pg)->count_info&PGC_state) == PGC_state_##st)
+ /*
+  * Mutually-exclusive page states:
+  * { inuse, offlining, offlined, free, broken_offlining, broken }
+  */
+#define PGC_state                  PG_mask(7, 9)
+#define PGC_state_inuse            PG_mask(0, 9)
+#define PGC_state_offlining        PG_mask(1, 9)
+#define PGC_state_offlined         PG_mask(2, 9)
+#define PGC_state_free             PG_mask(3, 9)
+#define PGC_state_broken_offlining PG_mask(4, 9) /* Broken and offlining */
+#define PGC_state_broken           PG_mask(5, 9) /* Broken and offlined */
+
+#define pgc_is(pgc, st)            (((pgc) & PGC_state) == PGC_state_##st)
+#define page_state_is(pg, st)       pgc_is((pg)->count_info, st)
+
+#define pgc_is_broken(pgc)         (pgc_is(pgc, broken) || \
+                                    pgc_is(pgc, broken_offlining))
+#define pgc_is_offlined(pgc)       (pgc_is(pgc, offlined) || \
+                                    pgc_is(pgc, broken))
+#define pgc_is_offlining(pgc)      (pgc_is(pgc, offlining) || \
+                                    pgc_is(pgc, broken_offlining))
+
+#define page_is_broken(pg)         (pgc_is_broken((pg)->count_info))
+#define page_is_offlined(pg)       (pgc_is_broken((pg)->count_info))
+#define page_is_offlining(pg)      (pgc_is_broken((pg)->count_info))
+
 /* Page is not reference counted */
 #define _PGC_extra        PG_shift(10)
 #define PGC_extra         PG_mask(1, 10)