diff --git a/docs/misc/arm/device-tree/cpupools.txt b/docs/misc/arm/device-tree/cpupools.txt
new file mode 100644
index 000000000000..d5a82ed0d45a
--- /dev/null
+++ b/docs/misc/arm/device-tree/cpupools.txt
@@ -0,0 +1,156 @@
+Boot time cpupools
+==================
+
+When BOOT_TIME_CPUPOOLS is enabled in the Xen configuration, it is possible to
+create cpupools during boot phase by specifying them in the device tree.
+
+Cpupools specification nodes shall be direct childs of /chosen node.
+Each cpupool node contains the following properties:
+
+- compatible (mandatory)
+
+    Must always include the compatiblity string: "xen,cpupool".
+
+- cpupool-cpus (mandatory)
+
+    Must be a list of device tree phandle to nodes describing cpus (e.g. having
+    device_type = "cpu"), it can't be empty.
+
+- cpupool-sched (optional)
+
+    Must be a device tree phandle to a node having "xen,scheduler" compatible
+    (description below), it has no effect when the cpupool refers to the cpupool
+    number zero, in that case the default Xen scheduler is selected (sched=<...>
+    boot argument).
+
+
+A scheduler specification node is a device tree node that contains the following
+properties:
+
+- compatible (mandatory)
+
+    Must always include the compatiblity string: "xen,scheduler".
+
+- sched-name (mandatory)
+
+    Must be a string having the name of a Xen scheduler, check the sched=<...>
+    boot argument for allowed values.
+
+
+Constraints
+===========
+
+If no cpupools are specified, all cpus will be assigned to one cpupool
+implicitly created (Pool-0).
+
+If cpupools node are specified, but not every cpu brought up by Xen is assigned,
+all the not assigned cpu will be assigned to an additional cpupool.
+
+If a cpu is assigned to a cpupool, but it's not brought up correctly, Xen will
+stop.
+
+
+Examples
+========
+
+A system having two types of core, the following device tree specification will
+instruct Xen to have two cpupools:
+
+- The cpupool with id 0 will have 4 cpus assigned.
+- The cpupool with id 1 will have 2 cpus assigned.
+
+The following example can work only if hmp-unsafe=1 is passed to Xen boot
+arguments, otherwise not all cores will be brought up by Xen and the cpupool
+creation process will stop Xen.
+
+
+a72_1: cpu@0 {
+        compatible = "arm,cortex-a72";
+        reg = <0x0 0x0>;
+        device_type = "cpu";
+        [...]
+};
+
+a72_2: cpu@1 {
+        compatible = "arm,cortex-a72";
+        reg = <0x0 0x1>;
+        device_type = "cpu";
+        [...]
+};
+
+a53_1: cpu@100 {
+        compatible = "arm,cortex-a53";
+        reg = <0x0 0x100>;
+        device_type = "cpu";
+        [...]
+};
+
+a53_2: cpu@101 {
+        compatible = "arm,cortex-a53";
+        reg = <0x0 0x101>;
+        device_type = "cpu";
+        [...]
+};
+
+a53_3: cpu@102 {
+        compatible = "arm,cortex-a53";
+        reg = <0x0 0x102>;
+        device_type = "cpu";
+        [...]
+};
+
+a53_4: cpu@103 {
+        compatible = "arm,cortex-a53";
+        reg = <0x0 0x103>;
+        device_type = "cpu";
+        [...]
+};
+
+chosen {
+
+    sched: sched_a {
+        compatible = "xen,scheduler";
+        sched-name = "credit2";
+    };
+    cpupool_a {
+        compatible = "xen,cpupool";
+        cpupool-cpus = <&a53_1 &a53_2 &a53_3 &a53_4>;
+    };
+    cpupool_b {
+        compatible = "xen,cpupool";
+        cpupool-cpus = <&a72_1 &a72_2>;
+        cpupool-sched = <&sched>;
+    };
+
+    [...]
+
+};
+
+
+A system having the cpupools specification below will instruct Xen to have three
+cpupools:
+
+- The cpupool Pool-0 will have 2 cpus assigned.
+- The cpupool Pool-1 will have 2 cpus assigned.
+- The cpupool Pool-2 will have 2 cpus assigned (created by Xen with all the not
+  assigned cpus a53_3 and a53_4).
+
+chosen {
+
+    sched: sched_a {
+        compatible = "xen,scheduler";
+        sched-name = "null";
+    };
+    cpupool_a {
+        compatible = "xen,cpupool";
+        cpupool-cpus = <&a53_1 &a53_2>;
+    };
+    cpupool_b {
+        compatible = "xen,cpupool";
+        cpupool-cpus = <&a72_1 &a72_2>;
+        cpupool-sched = <&sched>;
+    };
+
+    [...]
+
+};
\ No newline at end of file
diff --git a/xen/common/Kconfig b/xen/common/Kconfig
index 64439438891c..dc9eed31682f 100644
--- a/xen/common/Kconfig
+++ b/xen/common/Kconfig
@@ -22,6 +22,14 @@ config GRANT_TABLE
 
 	  If unsure, say Y.
 
+config BOOT_TIME_CPUPOOLS
+	bool "Create cpupools at boot time"
+	depends on HAS_DEVICE_TREE
+	default n
+	help
+	  Creates cpupools during boot time and assigns cpus to them. Cpupools
+	  options can be specified in the device tree.
+
 config ALTERNATIVE_CALL
 	bool
 
diff --git a/xen/common/Makefile b/xen/common/Makefile
index dc8d3a13f5b8..c5949785ab28 100644
--- a/xen/common/Makefile
+++ b/xen/common/Makefile
@@ -1,5 +1,6 @@
 obj-$(CONFIG_ARGO) += argo.o
 obj-y += bitmap.o
+obj-$(CONFIG_BOOT_TIME_CPUPOOLS) += boot_cpupools.o
 obj-$(CONFIG_HYPFS_CONFIG) += config_data.o
 obj-$(CONFIG_CORE_PARKING) += core_parking.o
 obj-y += cpu.o
diff --git a/xen/common/boot_cpupools.c b/xen/common/boot_cpupools.c
new file mode 100644
index 000000000000..e8529a902d21
--- /dev/null
+++ b/xen/common/boot_cpupools.c
@@ -0,0 +1,212 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * xen/common/boot_cpupools.c
+ *
+ * Code to create cpupools at boot time for arm architecture.
+ *
+ * Copyright (C) 2022 Arm Ltd.
+ */
+
+#include <xen/sched.h>
+
+#define BTCPUPOOLS_DT_NODE_NO_REG     (-1)
+#define BTCPUPOOLS_DT_NODE_NO_LOG_CPU (-2)
+
+struct pool_map {
+    int pool_id;
+    int sched_id;
+    struct cpupool *pool;
+};
+
+static struct pool_map __initdata pool_cpu_map[NR_CPUS] =
+    { [0 ... NR_CPUS-1] = {.pool_id = -1, .sched_id = -1, .pool = NULL} };
+static unsigned int __initdata next_pool_id;
+
+#ifdef CONFIG_ARM
+static int __init get_logical_cpu_from_hw_id(unsigned int hwid)
+{
+    unsigned int i;
+
+    for ( i = 0; i < nr_cpu_ids; i++ )
+        if ( cpu_logical_map(i) == hwid )
+            return i;
+
+    return -1;
+}
+
+static int __init
+get_logical_cpu_from_cpu_node(const struct dt_device_node *cpu_node)
+{
+    unsigned int cpu_reg, cpu_num;
+    const __be32 *prop;
+
+    prop = dt_get_property(cpu_node, "reg", NULL);
+    if ( !prop )
+        return BTCPUPOOLS_DT_NODE_NO_REG;
+
+    cpu_reg = dt_read_number(prop, dt_n_addr_cells(cpu_node));
+
+    cpu_num = get_logical_cpu_from_hw_id(cpu_reg);
+    if ( cpu_num < 0 )
+        return BTCPUPOOLS_DT_NODE_NO_LOG_CPU;
+
+    return cpu_num;
+}
+
+static int __init check_and_get_sched_id(const char* scheduler_name)
+{
+    int sched_id = sched_get_id_by_name(scheduler_name);
+
+    if ( sched_id < 0 )
+        panic("Scheduler %s does not exists!\n", scheduler_name);
+
+    return sched_id;
+}
+
+void __init btcpupools_dtb_parse(void)
+{
+    const struct dt_device_node *chosen, *node;
+
+    chosen = dt_find_node_by_path("/chosen");
+    if ( !chosen )
+        return;
+
+    dt_for_each_child_node(chosen, node)
+    {
+        const struct dt_device_node *phandle_node;
+        int sched_id = -1;
+        const char* scheduler_name;
+        unsigned int i = 0;
+
+        if ( !dt_device_is_compatible(node, "xen,cpupool") )
+            continue;
+
+        phandle_node = dt_parse_phandle(node, "cpupool-sched", 0);
+        if ( phandle_node )
+        {
+            if ( !dt_device_is_compatible(phandle_node, "xen,scheduler") )
+                panic("cpupool-sched must be a xen,scheduler compatible"
+                      "node!\n");
+            if ( !dt_property_read_string(phandle_node, "sched-name",
+                                          &scheduler_name) )
+                sched_id = check_and_get_sched_id(scheduler_name);
+            else
+                panic("Error trying to read sched-name in %s!\n",
+                      dt_node_name(phandle_node));
+        }
+
+        phandle_node = dt_parse_phandle(node, "cpupool-cpus", i++);
+        if ( !phandle_node )
+            panic("Missing or empty cpupool-cpus property!\n");
+
+        while ( phandle_node )
+        {
+            int cpu_num;
+
+            cpu_num = get_logical_cpu_from_cpu_node(phandle_node);
+
+            if ( cpu_num < 0 )
+                panic("Error retrieving logical cpu from node %s (%d)\n",
+                      dt_node_name(node), cpu_num);
+
+            if ( pool_cpu_map[cpu_num].pool_id != -1 )
+                panic("Logical cpu %d already added to a cpupool!\n", cpu_num);
+
+            pool_cpu_map[cpu_num].pool_id = next_pool_id;
+            pool_cpu_map[cpu_num].sched_id = sched_id;
+
+            phandle_node = dt_parse_phandle(node, "cpupool-cpus", i++);
+        }
+
+        /* Let Xen generate pool ids */
+        next_pool_id++;
+    }
+}
+#endif
+
+void __init btcpupools_allocate_pools(const cpumask_t *cpu_online_map)
+{
+    unsigned int cpu_num;
+
+    /*
+     * If there are no cpupools, the value of next_pool_id is zero, so the code
+     * below will assign every cpu to cpupool0 as the default behavior.
+     * When there are cpupools, the code below is assigning all the not
+     * assigned cpu to a new pool (next_pool_id value is the last id + 1).
+     * In the same loop we check if there is any assigned cpu that is not
+     * online.
+     */
+    for ( cpu_num = 0; cpu_num < nr_cpu_ids; cpu_num++ )
+        if ( cpumask_test_cpu(cpu_num, cpu_online_map) )
+        {
+            if ( pool_cpu_map[cpu_num].pool_id < 0 )
+                pool_cpu_map[cpu_num].pool_id = next_pool_id;
+        }
+        else
+            if ( pool_cpu_map[cpu_num].pool_id >= 0 )
+                panic("Pool-%d contains cpu%u that is not online!\n",
+                      pool_cpu_map[cpu_num].pool_id, cpu_num);
+
+#ifdef CONFIG_X86
+    /* Cpu0 must be in cpupool0 for x86 */
+    if ( pool_cpu_map[0].pool_id != 0 )
+    {
+        /* The cpupool containing cpu0 will become cpupool0 */
+        unsigned int swap_id = pool_cpu_map[0].pool_id;
+        for_each_cpu ( cpu_num, cpu_online_map )
+            if ( pool_cpu_map[cpu_num].pool_id == swap_id )
+                pool_cpu_map[cpu_num].pool_id = 0;
+            else if ( pool_cpu_map[cpu_num].pool_id == 0 )
+                pool_cpu_map[cpu_num].pool_id = swap_id;
+    }
+#endif
+
+    for_each_cpu ( cpu_num, cpu_online_map )
+    {
+        struct cpupool *pool = NULL;
+        int pool_id, sched_id;
+
+        pool_id = pool_cpu_map[cpu_num].pool_id;
+        sched_id = pool_cpu_map[cpu_num].sched_id;
+
+        if ( pool_id )
+        {
+            unsigned int i;
+
+            /* Look for previously created pool with id pool_id */
+            for ( i = 0; i < cpu_num; i++ )
+                if ( (pool_cpu_map[i].pool_id == pool_id) &&
+                     pool_cpu_map[i].pool )
+                {
+                    pool = pool_cpu_map[i].pool;
+                    break;
+                }
+
+            /* If no pool was created before, create it */
+            if ( !pool )
+                pool = cpupool_create_pool(pool_id, sched_id);
+            if ( !pool )
+                panic("Error creating pool id %u!\n", pool_id);
+        }
+        else
+            pool = cpupool0;
+
+        pool_cpu_map[cpu_num].pool = pool;
+        printk(XENLOG_INFO "Logical CPU %u in Pool-%u.\n", cpu_num, pool_id);
+    }
+}
+
+struct cpupool *__init btcpupools_get_cpupool(unsigned int cpu)
+{
+    return pool_cpu_map[cpu].pool;
+}
+
+/*
+ * Local variables:
+ * mode: C
+ * c-file-style: "BSD"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: nil
+ * End:
+ */
diff --git a/xen/common/sched/cpupool.c b/xen/common/sched/cpupool.c
index 89a891af7076..b2495ad6d03e 100644
--- a/xen/common/sched/cpupool.c
+++ b/xen/common/sched/cpupool.c
@@ -1247,12 +1247,16 @@ static int __init cf_check cpupool_init(void)
     cpupool_put(cpupool0);
     register_cpu_notifier(&cpu_nfb);
 
+    btcpupools_dtb_parse();
+
+    btcpupools_allocate_pools(&cpu_online_map);
+
     spin_lock(&cpupool_lock);
 
     cpumask_copy(&cpupool_free_cpus, &cpu_online_map);
 
     for_each_cpu ( cpu, &cpupool_free_cpus )
-        cpupool_assign_cpu_locked(cpupool0, cpu);
+        cpupool_assign_cpu_locked(btcpupools_get_cpupool(cpu), cpu);
 
     spin_unlock(&cpupool_lock);
 
diff --git a/xen/include/xen/sched.h b/xen/include/xen/sched.h
index 2c10303f0187..de4e8feea399 100644
--- a/xen/include/xen/sched.h
+++ b/xen/include/xen/sched.h
@@ -1176,6 +1176,25 @@ extern void cf_check dump_runq(unsigned char key);
 
 void arch_do_physinfo(struct xen_sysctl_physinfo *pi);
 
+#ifdef CONFIG_BOOT_TIME_CPUPOOLS
+void btcpupools_allocate_pools(const cpumask_t *cpu_online_map);
+struct cpupool *btcpupools_get_cpupool(unsigned int cpu);
+
+#ifdef CONFIG_ARM
+void btcpupools_dtb_parse(void);
+#else
+static inline void btcpupools_dtb_parse(void) {}
+#endif
+
+#else
+static inline void btcpupools_allocate_pools(const cpumask_t *cpu_online_map) {}
+static inline void btcpupools_dtb_parse(void) {}
+static inline struct cpupool *btcpupools_get_cpupool(unsigned int cpu)
+{
+    return cpupool0;
+}
+#endif
+
 #endif /* __SCHED_H__ */
 
 /*
