diff mbox series

[v11,7/7] microcode: reject late ucode loading if any core is parked

Message ID 1569506015-26938-8-git-send-email-chao.gao@intel.com (mailing list archive)
State New, archived
Headers show
Series improve late microcode loading | expand

Commit Message

Chao Gao Sept. 26, 2019, 1:53 p.m. UTC
If a core with all of its thread being parked, late ucode loading
which currently only loads ucode on online threads would lead to
differing ucode revisions in the system. In general, keeping ucode
revision consistent would be less error-prone. To this end, if there
is a parked thread doesn't have an online sibling thread, late ucode
loading is rejected.

Two threads are on the same core or computing unit iff they have
the same phys_proc_id and cpu_core_id/compute_unit_id. Based on
phys_proc_id and cpu_core_id/compute_unit_id, an unique core id
is generated for each thread. And use a bitmap to reduce the
number of comparison.

Signed-off-by: Chao Gao <chao.gao@intel.com>
---
Alternatively, we can mask the thread id off apicid and use it
as the unique core id. It needs to introduce new field in cpuinfo_x86
to record the mask for thread id. So I don't take this way.
---
 xen/arch/x86/microcode.c        | 75 +++++++++++++++++++++++++++++++++++++++++
 xen/include/asm-x86/processor.h |  1 +
 2 files changed, 76 insertions(+)

Comments

Jan Beulich Sept. 27, 2019, 11:19 a.m. UTC | #1
On 26.09.2019 15:53, Chao Gao wrote:
> If a core with all of its thread being parked, late ucode loading
> which currently only loads ucode on online threads would lead to
> differing ucode revisions in the system. In general, keeping ucode
> revision consistent would be less error-prone. To this end, if there
> is a parked thread doesn't have an online sibling thread, late ucode
> loading is rejected.
> 
> Two threads are on the same core or computing unit iff they have
> the same phys_proc_id and cpu_core_id/compute_unit_id. Based on
> phys_proc_id and cpu_core_id/compute_unit_id, an unique core id
> is generated for each thread. And use a bitmap to reduce the
> number of comparison.
> 
> Signed-off-by: Chao Gao <chao.gao@intel.com>
> ---
> Alternatively, we can mask the thread id off apicid and use it
> as the unique core id. It needs to introduce new field in cpuinfo_x86
> to record the mask for thread id. So I don't take this way.

It feels a little odd that you introduce a "custom" ID, but it
should be fine without going this alternative route. (You
wouldn't need a new field though, I think, as we've got the
x86_num_siblings one already.)

What I continue to be unconvinced of is for the chosen approach
to be better than briefly unparking a thread on each core, as
previously suggested.

> --- a/xen/arch/x86/microcode.c
> +++ b/xen/arch/x86/microcode.c
> @@ -573,6 +573,64 @@ static int do_microcode_update(void *patch)
>      return ret;
>  }
>  
> +static unsigned int unique_core_id(unsigned int cpu, unsigned int socket_shift)
> +{
> +    unsigned int core_id = cpu_to_cu(cpu);
> +
> +    if ( core_id == INVALID_CUID )
> +        core_id = cpu_to_core(cpu);
> +
> +    return (cpu_to_socket(cpu) << socket_shift) + core_id;
> +}
> +
> +static int has_parked_core(void)
> +{
> +    int ret = 0;

I don't think you need the initializer here.

> +    if ( park_offline_cpus )

    if ( !park_offline_cpus )
        return 0;

would allow one level less of indentation of the main part of
the function body.

> +    {
> +        unsigned int cpu, max_bits, core_width;
> +        unsigned int max_sockets = 1, max_cores = 1;
> +        struct cpuinfo_x86 *c = cpu_data;
> +        unsigned long *bitmap;
> +
> +        for_each_present_cpu(cpu)
> +        {
> +            if ( x86_cpu_to_apicid[cpu] == BAD_APICID )
> +                continue;
> +
> +            /* Note that cpu_to_socket() get an ID starting from 0. */
> +            if ( cpu_to_socket(cpu) + 1 > max_sockets )

Instead of "+ 1", why not >= ?

> +                max_sockets = cpu_to_socket(cpu) + 1;
> +
> +            if ( c[cpu].x86_max_cores > max_cores )
> +                max_cores = c[cpu].x86_max_cores;

What guarantees .x86_max_cores to be valid? Onlining a hot-added
CPU is a two step process afaict, XENPF_cpu_hotadd followed by
XENPF_cpu_online. In between the CPU would be marked present
(and cpu_add() would also have filled x86_cpu_to_apicid[cpu]),
but cpu_data[cpu] wouldn't have been filled yet afaict. This
also makes the results of the cpu_to_*() unreliable that you use
in unique_core_id().

However, if we assume sufficient similarity between CPU
packages (as you've done elsewhere in this series iirc), this
may not be an actual problem. But it wants mentioning in a code
comment, I think. Plus at the very least you depend on the used
cpu_data[] fields to not contain unduly large values (and hence
you e.g. depend on cpu_data[] not gaining an initializer,
setting the three fields of interest to their INVALID_* values,
as currently done by identify_cpu()).

> +        }
> +
> +        core_width = fls(max_cores);
> +        max_bits = max_sockets << core_width;
> +        bitmap = xzalloc_array(unsigned long, BITS_TO_LONGS(max_bits));
> +        if ( !bitmap )
> +            return -ENOMEM;
> +
> +        for_each_present_cpu(cpu)
> +        {
> +            if ( cpu_online(cpu) || x86_cpu_to_apicid[cpu] == BAD_APICID )
> +                continue;
> +
> +            __set_bit(unique_core_id(cpu, core_width), bitmap);
> +        }
> +
> +        for_each_online_cpu(cpu)
> +            __clear_bit(unique_core_id(cpu, core_width), bitmap);
> +
> +        ret = (find_first_bit(bitmap, max_bits) < max_bits);

I think bitmap_empty() would be a cheaper operation for the purpose
you have, especially if further up you rounded up max_bits to a
multiple of BITS_PER_LONG.

Jan
Chao Gao Sept. 30, 2019, 6:43 a.m. UTC | #2
On Fri, Sep 27, 2019 at 01:19:16PM +0200, Jan Beulich wrote:
>On 26.09.2019 15:53, Chao Gao wrote:
>> If a core with all of its thread being parked, late ucode loading
>> which currently only loads ucode on online threads would lead to
>> differing ucode revisions in the system. In general, keeping ucode
>> revision consistent would be less error-prone. To this end, if there
>> is a parked thread doesn't have an online sibling thread, late ucode
>> loading is rejected.
>> 
>> Two threads are on the same core or computing unit iff they have
>> the same phys_proc_id and cpu_core_id/compute_unit_id. Based on
>> phys_proc_id and cpu_core_id/compute_unit_id, an unique core id
>> is generated for each thread. And use a bitmap to reduce the
>> number of comparison.
>> 
>> Signed-off-by: Chao Gao <chao.gao@intel.com>
>> ---
>> Alternatively, we can mask the thread id off apicid and use it
>> as the unique core id. It needs to introduce new field in cpuinfo_x86
>> to record the mask for thread id. So I don't take this way.
>
>It feels a little odd that you introduce a "custom" ID, but it
>should be fine without going this alternative route. (You
>wouldn't need a new field though, I think, as we've got the
>x86_num_siblings one already.)
>
>What I continue to be unconvinced of is for the chosen approach
>to be better than briefly unparking a thread on each core, as
>previously suggested.

It isn't so easy to go the same way as set_cx_pminfo().

1. NMI handler on parked threads is changed to a nop. To load ucode in
NMI handler, we have to switch back to normal NMI handler in
default_idle(). But it conflicts with what the comments in play_dead()
implies: it is not safe to call normal NMI handler after
cpu_exit_clear().

2. A precondition of unparking a thread on each core, we need to find
out exactly all parked cores and wake up one thread of each of them.
Then in theory, what this patch does is only part of unparking a thread
on each core.

I don't mean they are hard to address. But we need to take care of them.
Given that, IMO, this patch is much straightforward.

>
>> --- a/xen/arch/x86/microcode.c
>> +++ b/xen/arch/x86/microcode.c
>> @@ -573,6 +573,64 @@ static int do_microcode_update(void *patch)
>>      return ret;
>>  }
>>  
>> +static unsigned int unique_core_id(unsigned int cpu, unsigned int socket_shift)
>> +{
>> +    unsigned int core_id = cpu_to_cu(cpu);
>> +
>> +    if ( core_id == INVALID_CUID )
>> +        core_id = cpu_to_core(cpu);
>> +
>> +    return (cpu_to_socket(cpu) << socket_shift) + core_id;
>> +}
>> +
>> +static int has_parked_core(void)
>> +{
>> +    int ret = 0;
>
>I don't think you need the initializer here.
>
>> +    if ( park_offline_cpus )
>
>    if ( !park_offline_cpus )
>        return 0;
>
>would allow one level less of indentation of the main part of
>the function body.
>
>> +    {
>> +        unsigned int cpu, max_bits, core_width;
>> +        unsigned int max_sockets = 1, max_cores = 1;
>> +        struct cpuinfo_x86 *c = cpu_data;
>> +        unsigned long *bitmap;
>
+
>> +        for_each_present_cpu(cpu)
>> +        {
>> +            if ( x86_cpu_to_apicid[cpu] == BAD_APICID )
>> +                continue;
>> +
>> +            /* Note that cpu_to_socket() get an ID starting from 0. */
>> +            if ( cpu_to_socket(cpu) + 1 > max_sockets )
>
>Instead of "+ 1", why not >= ?
>
>> +                max_sockets = cpu_to_socket(cpu) + 1;
>> +
>> +            if ( c[cpu].x86_max_cores > max_cores )
>> +                max_cores = c[cpu].x86_max_cores;
>
>What guarantees .x86_max_cores to be valid? Onlining a hot-added
>CPU is a two step process afaict, XENPF_cpu_hotadd followed by
>XENPF_cpu_online. In between the CPU would be marked present
>(and cpu_add() would also have filled x86_cpu_to_apicid[cpu]),
>but cpu_data[cpu] wouldn't have been filled yet afaict. This
>also makes the results of the cpu_to_*() unreliable that you use
>in unique_core_id().

Indeed. I agree.

>
>However, if we assume sufficient similarity between CPU
>packages (as you've done elsewhere in this series iirc), this

Yes.

>may not be an actual problem. But it wants mentioning in a code
>comment, I think. Plus at the very least you depend on the used
>cpu_data[] fields to not contain unduly large values (and hence
>you e.g. depend on cpu_data[] not gaining an initializer,
>setting the three fields of interest to their INVALID_* values,
>as currently done by identify_cpu()).

Can we skip those threads whose socket ID is invalid and initialize
the three fields in cpu_add()?
Or maintain a bitmap for parked threads to help distinguish them from
real offlined threads, and go through parked threads here?

Thanks
Chao
Jan Beulich Sept. 30, 2019, 8:12 a.m. UTC | #3
On 30.09.2019 08:43, Chao Gao wrote:
> On Fri, Sep 27, 2019 at 01:19:16PM +0200, Jan Beulich wrote:
>> What I continue to be unconvinced of is for the chosen approach
>> to be better than briefly unparking a thread on each core, as
>> previously suggested.
> 
> It isn't so easy to go the same way as set_cx_pminfo().
> 
> 1. NMI handler on parked threads is changed to a nop. To load ucode in
> NMI handler, we have to switch back to normal NMI handler in
> default_idle(). But it conflicts with what the comments in play_dead()
> implies: it is not safe to call normal NMI handler after
> cpu_exit_clear().

Right - pointing at the Cx handling for reference was perhaps not
the best choice. Here we'd need to truly online a core, remembering
to offline it again right after the ucode update.

> 2. A precondition of unparking a thread on each core, we need to find
> out exactly all parked cores and wake up one thread of each of them.
> Then in theory, what this patch does is only part of unparking a thread
> on each core.

Possibly, but you've suggested a possibly better alternative further
down.

>> may not be an actual problem. But it wants mentioning in a code
>> comment, I think. Plus at the very least you depend on the used
>> cpu_data[] fields to not contain unduly large values (and hence
>> you e.g. depend on cpu_data[] not gaining an initializer,
>> setting the three fields of interest to their INVALID_* values,
>> as currently done by identify_cpu()).
> 
> Can we skip those threads whose socket ID is invalid and initialize
> the three fields in cpu_add()?

What would you initialize them to in cpu_add()? You don't know
their values yet, do you?

> Or maintain a bitmap for parked threads to help distinguish them from
> real offlined threads, and go through parked threads here?

I think this is the better approach in the long run. I've been at
least considering addition of such a bitmap for other reasons as well.

Jan
diff mbox series

Patch

diff --git a/xen/arch/x86/microcode.c b/xen/arch/x86/microcode.c
index b9fa8bb..b70eb16 100644
--- a/xen/arch/x86/microcode.c
+++ b/xen/arch/x86/microcode.c
@@ -573,6 +573,64 @@  static int do_microcode_update(void *patch)
     return ret;
 }
 
+static unsigned int unique_core_id(unsigned int cpu, unsigned int socket_shift)
+{
+    unsigned int core_id = cpu_to_cu(cpu);
+
+    if ( core_id == INVALID_CUID )
+        core_id = cpu_to_core(cpu);
+
+    return (cpu_to_socket(cpu) << socket_shift) + core_id;
+}
+
+static int has_parked_core(void)
+{
+    int ret = 0;
+
+    if ( park_offline_cpus )
+    {
+        unsigned int cpu, max_bits, core_width;
+        unsigned int max_sockets = 1, max_cores = 1;
+        struct cpuinfo_x86 *c = cpu_data;
+        unsigned long *bitmap;
+
+        for_each_present_cpu(cpu)
+        {
+            if ( x86_cpu_to_apicid[cpu] == BAD_APICID )
+                continue;
+
+            /* Note that cpu_to_socket() get an ID starting from 0. */
+            if ( cpu_to_socket(cpu) + 1 > max_sockets )
+                max_sockets = cpu_to_socket(cpu) + 1;
+
+            if ( c[cpu].x86_max_cores > max_cores )
+                max_cores = c[cpu].x86_max_cores;
+        }
+
+        core_width = fls(max_cores);
+        max_bits = max_sockets << core_width;
+        bitmap = xzalloc_array(unsigned long, BITS_TO_LONGS(max_bits));
+        if ( !bitmap )
+            return -ENOMEM;
+
+        for_each_present_cpu(cpu)
+        {
+            if ( cpu_online(cpu) || x86_cpu_to_apicid[cpu] == BAD_APICID )
+                continue;
+
+            __set_bit(unique_core_id(cpu, core_width), bitmap);
+        }
+
+        for_each_online_cpu(cpu)
+            __clear_bit(unique_core_id(cpu, core_width), bitmap);
+
+        ret = (find_first_bit(bitmap, max_bits) < max_bits);
+        xfree(bitmap);
+    }
+
+    return ret;
+}
+
 int microcode_update(XEN_GUEST_HANDLE_PARAM(const_void) buf, unsigned long len)
 {
     int ret;
@@ -611,6 +669,23 @@  int microcode_update(XEN_GUEST_HANDLE_PARAM(const_void) buf, unsigned long len)
      */
     ASSERT(cpumask_first(&cpu_online_map) == nmi_cpu);
 
+    /*
+     * If there is a core with all of its threads parked, late loading may
+     * cause differing ucode revisions in the system. Refuse this operation.
+     */
+    ret = has_parked_core();
+    if ( ret )
+    {
+        if ( ret > 0 )
+        {
+            printk(XENLOG_WARNING
+                   "Ucode loading aborted: found a parked core\n");
+            ret = -EPERM;
+        }
+        xfree(buffer);
+        goto put;
+    }
+
     patch = parse_blob(buffer, len);
     xfree(buffer);
     if ( IS_ERR(patch) )
diff --git a/xen/include/asm-x86/processor.h b/xen/include/asm-x86/processor.h
index c92956f..753deec 100644
--- a/xen/include/asm-x86/processor.h
+++ b/xen/include/asm-x86/processor.h
@@ -171,6 +171,7 @@  extern unsigned int init_intel_cacheinfo(struct cpuinfo_x86 *c);
 
 #define cpu_to_core(_cpu)   (cpu_data[_cpu].cpu_core_id)
 #define cpu_to_socket(_cpu) (cpu_data[_cpu].phys_proc_id)
+#define cpu_to_cu(_cpu)     (cpu_data[_cpu].compute_unit_id)
 
 unsigned int apicid_to_socket(unsigned int);