diff --git a/xen/arch/x86/numa.c b/xen/arch/x86/numa.c
index 4b0b297c7e..27cee68a4d 100644
--- a/xen/arch/x86/numa.c
+++ b/xen/arch/x86/numa.c
@@ -100,6 +100,19 @@ unsigned int __init arch_get_dma_bitsize(void)
                  + PAGE_SHIFT, 32);
 }
 
+/**
+ * @brief Retrieves the RAM range for a given index from the e820 memory map.
+ *
+ * This function fetches the starting and ending addresses of a RAM range
+ * specified by the given index idx from the e820 memory map.
+ *
+ * @param idx The index of the RAM range in the e820 memory map to retrieve.
+ * @param start Pointer to store the starting address of the RAM range.
+ * @param end Pointer to store the exclusive ending address of the RAM range.
+ *
+ * @return 0 on success, -ENOENT if the index is out of bounds,
+ *         or -ENODATA if the memory map at index idx is not of type E820_RAM.
+ */
 int __init arch_get_ram_range(unsigned int idx, paddr_t *start, paddr_t *end)
 {
     if ( idx >= e820.nr_map )
diff --git a/xen/arch/x86/x86_64/mm.c b/xen/arch/x86/x86_64/mm.c
index b2a280fba3..66b9bed057 100644
--- a/xen/arch/x86/x86_64/mm.c
+++ b/xen/arch/x86/x86_64/mm.c
@@ -1334,6 +1334,9 @@ int memory_add(unsigned long spfn, unsigned long epfn, unsigned int pxm)
     share_hotadd_m2p_table(&info);
     transfer_pages_to_heap(&info);
 
+    /* Update the node's present pages (like the total_pages of the system) */
+    NODE_DATA(node)->node_present_pages += epfn - spfn;
+
     return 0;
 
 destroy_m2p:
diff --git a/xen/common/numa.c b/xen/common/numa.c
index 209c546a3b..38369e487b 100644
--- a/xen/common/numa.c
+++ b/xen/common/numa.c
@@ -4,6 +4,7 @@
  * Adapted for Xen: Ryan Harper <ryanh@us.ibm.com>
  */
 
+#include "xen/pfn.h"
 #include <xen/init.h>
 #include <xen/keyhandler.h>
 #include <xen/mm.h>
@@ -499,15 +500,41 @@ int __init compute_hash_shift(const struct node *nodes,
     return shift;
 }
 
-/* Initialize NODE_DATA given nodeid and start/end */
+/**
+ * @brief Initialize a NUMA node's node_data structure at boot.
+ *
+ * It is given the NUMA node's index in the node_data array as well
+ * as the start and exclusive end address of the node's memory span
+ * as arguments and initializes the node_data entry with this information.
+ *
+ * It then initializes the total number of usable memory pages within
+ * the NUMA node's memory span using the arch_get_ram_range() function.
+ *
+ * @param nodeid The index into the node_data array for the node.
+ * @param start The starting physical address of the node's memory range.
+ * @param end The exclusive ending physical address of the node's memory range.
+ */
 void __init setup_node_bootmem(nodeid_t nodeid, paddr_t start, paddr_t end)
 {
     unsigned long start_pfn = paddr_to_pfn(start);
     unsigned long end_pfn = paddr_to_pfn(end);
-
-    NODE_DATA(nodeid)->node_start_pfn = start_pfn;
-    NODE_DATA(nodeid)->node_spanned_pages = end_pfn - start_pfn;
-
+    struct node_data *numa_node = NODE_DATA(nodeid);
+    unsigned int idx = 0;
+    int err;
+
+    numa_node->node_start_pfn = start_pfn;
+    numa_node->node_spanned_pages = end_pfn - start_pfn;
+    numa_node->node_present_pages = 0;
+
+    /* Calculate the number of present RAM pages within the node */
+    do {
+        paddr_t ram_start, ram_end;
+
+        err = arch_get_ram_range(idx++, &ram_start, &ram_end);
+        if ( !err && ram_start < end && ram_end > start )
+            numa_node->node_present_pages += PFN_DOWN(min(ram_end, end)) -
+                                             PFN_UP(max(ram_start, start));
+    } while ( err != -ENOENT );
     node_set_online(nodeid);
 }
 
diff --git a/xen/include/xen/numa.h b/xen/include/xen/numa.h
index fd1511a6fb..f6c1f27ca1 100644
--- a/xen/include/xen/numa.h
+++ b/xen/include/xen/numa.h
@@ -68,9 +68,24 @@ extern unsigned int memnode_shift;
 extern unsigned long memnodemapsize;
 extern nodeid_t *memnodemap;
 
+/* The memory information of NUMA nodes in the node_data[] array */
 struct node_data {
+    /* The starting page frame number (lowest pfn) of the NUMA node */
     unsigned long node_start_pfn;
+
+    /*
+     * The number of pages spanned by the NUMA node, including memory holes.
+     * Used for the pyhsical memory range when scrubbing unallocated memory.
+     */
     unsigned long node_spanned_pages;
+
+    /*
+     * Number of usable memory pages that are available in this NUMA node.
+     * The sum of these values from all NUMA nodes reflects total_pages.
+     * The Xen Hypervisor does not use this field internally, but it is useful
+     * for reporting the memory information of NUMA nodes to management tools.
+     */
+    unsigned long node_present_pages;
 };
 
 extern struct node_data node_data[];
@@ -91,6 +106,7 @@ static inline nodeid_t mfn_to_nid(mfn_t mfn)
 
 #define node_start_pfn(nid)     (NODE_DATA(nid)->node_start_pfn)
 #define node_spanned_pages(nid) (NODE_DATA(nid)->node_spanned_pages)
+#define node_present_pages(nid) (NODE_DATA(nid)->node_present_pages)
 #define node_end_pfn(nid)       (NODE_DATA(nid)->node_start_pfn + \
                                  NODE_DATA(nid)->node_spanned_pages)
 
@@ -123,6 +139,7 @@ extern void numa_set_processor_nodes_parsed(nodeid_t node);
 extern mfn_t first_valid_mfn;
 
 #define node_spanned_pages(nid) (max_page - mfn_x(first_valid_mfn))
+#define node_present_pages(nid) total_pages
 #define node_start_pfn(nid) mfn_x(first_valid_mfn)
 #define __node_distance(a, b) 20
 
