diff --git a/acpi/src/lib.rs b/acpi/src/lib.rs
index 0b254df1..0f01d963 100644
--- a/acpi/src/lib.rs
+++ b/acpi/src/lib.rs
@@ -73,6 +73,7 @@ pub mod mcfg;
 pub mod rsdp;
 pub mod sdt;
 pub mod spcr;
+pub mod srat;
 
 #[cfg(feature = "allocator_api")]
 mod managed_slice;
diff --git a/acpi/src/madt.rs b/acpi/src/madt.rs
index 9f1427dd..75bf0d98 100644
--- a/acpi/src/madt.rs
+++ b/acpi/src/madt.rs
@@ -191,6 +191,7 @@ impl Madt {
                     let processor = Processor {
                         processor_uid: entry.processor_id as u32,
                         local_apic_id: entry.apic_id as u32,
+                        proximity_domain: None,
                         state,
                         is_ap,
                     };
@@ -216,6 +217,7 @@ impl Madt {
                     let processor = Processor {
                         processor_uid: entry.processor_uid,
                         local_apic_id: entry.x2apic_id,
+                        proximity_domain: None,
                         state,
                         is_ap,
                     };
diff --git a/acpi/src/platform/mod.rs b/acpi/src/platform/mod.rs
index bf4097c3..eb8b4eda 100644
--- a/acpi/src/platform/mod.rs
+++ b/acpi/src/platform/mod.rs
@@ -1,15 +1,7 @@
 pub mod interrupt;
 
 use crate::{
-    address::GenericAddress,
-    fadt::Fadt,
-    madt::{Madt, MadtError, MpProtectedModeWakeupCommand, MultiprocessorWakeupMailbox},
-    AcpiError,
-    AcpiHandler,
-    AcpiResult,
-    AcpiTables,
-    ManagedSlice,
-    PowerProfile,
+    address::GenericAddress, fadt::Fadt, madt::{Madt, MadtError, MpProtectedModeWakeupCommand, MultiprocessorWakeupMailbox}, srat::Srat, AcpiError, AcpiHandler, AcpiResult, AcpiTables, ManagedSlice, PowerProfile
 };
 use core::{alloc::Allocator, mem, ptr};
 use interrupt::InterruptModel;
@@ -35,6 +27,8 @@ pub struct Processor {
     /// The ID of the local APIC of the processor. Will be less than `256` if the APIC is being used, but can be
     /// greater than this if the X2APIC is being used.
     pub local_apic_id: u32,
+    /// Proximity domain to which the processor belongs to.
+    pub proximity_domain: Option<u32>,
 
     /// The state of this processor. Check that the processor is not `Disabled` before attempting to bring it up!
     pub state: ProcessorState,
@@ -45,6 +39,19 @@ pub struct Processor {
     pub is_ap: bool,
 }
 
+impl Processor {
+    fn attach_affinity<A>(&mut self, topology: &ProcessorTopology<A>)
+    where
+        A: Allocator,
+    {
+        for affinity in topology.processor_affinities.iter() {
+            if affinity.local_apic_id == self.local_apic_id {
+                self.proximity_domain = Some(affinity.proximity_domain);
+            }
+        }
+    }
+}
+
 #[derive(Debug, Clone)]
 pub struct ProcessorInfo<'a, A>
 where
@@ -62,6 +69,44 @@ where
     pub(crate) fn new(boot_processor: Processor, application_processors: ManagedSlice<'a, Processor, A>) -> Self {
         Self { boot_processor, application_processors }
     }
+
+    fn attach_affinity(&mut self, topology: &ProcessorTopology<'a, A>) {
+        self.boot_processor.attach_affinity(topology);
+        for application_processor in self.application_processors.iter_mut() {
+            application_processor.attach_affinity(topology);
+        }
+    }
+}
+
+pub struct ProcessorAffinity {
+    pub local_apic_id: u32,
+    pub proximity_domain: u32,
+    pub is_enabled: bool,
+}
+
+pub struct ProcessorTopology<'a, A>
+where
+    A: Allocator,
+{
+    pub processor_affinities: ManagedSlice<'a, ProcessorAffinity, A>,
+}
+
+#[derive(Debug, Clone)]
+pub struct MemoryRange {
+    pub base_address: u64,
+    pub length: u64,
+    pub proximity_domain: Option<u32>,
+    pub hot_pluggable: bool,
+    pub non_volatile: bool,
+    pub is_enabled: bool,
+}
+
+#[derive(Debug, Clone)]
+pub struct MemoryInfo<'a, A>
+where
+    A: Allocator,
+{
+    pub memory_ranges: ManagedSlice<'a, MemoryRange, A>,
 }
 
 /// Information about the ACPI Power Management Timer (ACPI PM Timer).
@@ -95,6 +140,7 @@ where
     /// On `x86_64` platforms that support the APIC, the processor topology must also be inferred from the
     /// interrupt model. That information is stored here, if present.
     pub processor_info: Option<ProcessorInfo<'a, A>>,
+    pub memory_info: Option<MemoryInfo<'a, A>>,
     pub pm_timer: Option<PmTimer>,
     /*
      * TODO: we could provide a nice view of the hardware register blocks in the FADT here.
@@ -119,17 +165,34 @@ where
     where
         H: AcpiHandler,
     {
-        let fadt = tables.find_table::<Fadt>()?;
-        let power_profile = fadt.power_profile();
+        let (power_profile, pm_timer) = {
+            let fadt = tables.find_table::<Fadt>()?;
+            (fadt.power_profile(), PmTimer::new(&fadt)?)
+        };
+
+        let (interrupt_model, processor_info) = {
+            let madt = tables.find_table::<Madt>();
+            match madt {
+                Ok(madt) => madt.get().parse_interrupt_model_in(allocator.clone())?,
+                Err(_) => (InterruptModel::Unknown, None),
+            }
+        };
 
-        let madt = tables.find_table::<Madt>();
-        let (interrupt_model, processor_info) = match madt {
-            Ok(madt) => madt.get().parse_interrupt_model_in(allocator)?,
-            Err(_) => (InterruptModel::Unknown, None),
+        let (processor_info, memory_info) = {
+            let srat = tables.find_table::<Srat>();
+            match srat {
+                Ok(srat) => {
+                    let (processor_topology, memory_info) = srat.get().parse_topology_in(allocator)?;
+                    (processor_info.map(|mut processor_info| {
+                        processor_info.attach_affinity(&processor_topology);
+                        processor_info
+                    }), Some(memory_info))
+                }
+                Err(_) => (processor_info, None),
+            }
         };
-        let pm_timer = PmTimer::new(&fadt)?;
 
-        Ok(PlatformInfo { power_profile, interrupt_model, processor_info, pm_timer })
+        Ok(PlatformInfo { power_profile, interrupt_model, processor_info, memory_info, pm_timer })
     }
 }
 
diff --git a/acpi/src/srat.rs b/acpi/src/srat.rs
new file mode 100644
index 00000000..51d33896
--- /dev/null
+++ b/acpi/src/srat.rs
@@ -0,0 +1,335 @@
+use core::{marker::{PhantomData, PhantomPinned}, mem, pin::Pin};
+use core::alloc::Allocator;
+
+use bitflags::bitflags;
+
+use crate::{platform::{MemoryInfo, ProcessorTopology}, sdt::{SdtHeader, Signature}, AcpiResult, AcpiTable};
+
+/// System Resource Affinity Table (SRAT).
+/// 
+/// This optional table provides information that allows OSPM to associate the following types of
+/// devices with system locality / proximity domains and clock domains:
+/// - processors,
+/// - memory ranges (including those provided by hot-added memory devices), and
+/// - generic initiators (e.g. heterogeneous processors and accelerators, GPUs, and I/O devices
+///   with integrated compute or DMA engines).
+#[repr(C, packed)]
+pub struct Srat {
+    header: SdtHeader,
+    _reserved_1: u32,
+    _reserved_2: u64,
+    _pinned: PhantomPinned,
+}
+
+/// ### Safety: Implementation properly represents a valid SRAT.
+unsafe impl AcpiTable for Srat {
+    const SIGNATURE: Signature = Signature::SRAT;
+
+    fn header(&self) -> &SdtHeader {
+        &self.header
+    }
+}
+
+impl Srat {
+    #[cfg(feature = "allocator_api")]
+    pub fn parse_topology_in<'a, A>(
+        self: Pin<&Self>,
+        allocator: A
+    ) -> AcpiResult<(ProcessorTopology<'a, A>, MemoryInfo<'a, A>)>
+    where
+        A: Allocator + Clone,
+    {
+        for entry in self.entries() {
+            match entry {
+                SratEntry::LocalApic(_) |
+                SratEntry::LocalX2Apic(_) => {
+                    return self.parse_apic_topology_in(allocator);
+                }
+                SratEntry::Gicc(_) |
+                SratEntry::GicIts(_) => {
+                    unimplemented!();
+                }
+                SratEntry::Memory(_) |
+                SratEntry::GenericInitiator(_) => {}
+            };
+        }
+
+        unreachable!();
+    }
+
+    #[cfg(feature = "allocator_api")]
+    pub fn parse_apic_topology_in<'a, A>(
+        self: Pin<&Self>,
+        allocator: A
+    ) -> AcpiResult<(ProcessorTopology<'a, A>, MemoryInfo<'a, A>)>
+    where
+        A: Allocator + Clone,
+    {
+        use crate::{platform::{MemoryRange, ProcessorAffinity}, ManagedSlice};
+
+        let mut processor_count = 0;
+        let mut memory_range_count = 0;
+
+        for entry in self.entries() {
+            match entry {
+                SratEntry::LocalApic(_) |
+                SratEntry::LocalX2Apic(_) => processor_count += 1,
+                SratEntry::Memory(_) => memory_range_count += 1,
+                _ => (),
+            }
+        }
+
+        let mut processor_affinities = ManagedSlice::new_in(processor_count, allocator.clone())?;
+        let mut memory_ranges = ManagedSlice::new_in(memory_range_count, allocator.clone())?;
+
+        processor_count = 0;
+        memory_range_count = 0;
+
+        for entry in self.entries() {
+            match entry {
+                SratEntry::LocalApic(entry) => {
+                    let local_apic_id = entry.apic_id as u32;
+                    let mut proximity_domain = entry.proximity_domain_low as u32;
+                    for i in 0..3 {
+                        let shift = 8 * (3 - i);
+                        proximity_domain += (entry.proximity_domain_high[i] as u32) << shift;
+                    }
+                    let flags = entry.flags;
+                    let is_enabled = flags.contains(LocalApicAffinityFlags::ENABLED);
+                    let processor_affinity = ProcessorAffinity { local_apic_id, proximity_domain, is_enabled };
+                    processor_affinities[processor_count] = processor_affinity;
+                    processor_count += 1;
+                }
+                SratEntry::LocalX2Apic(entry) => {
+                    let local_apic_id = entry.x2apic_id;
+                    let proximity_domain = entry.proximity_domain;
+                    let flags = entry.flags;
+                    let is_enabled = flags.contains(LocalX2ApicAffinityFlags::ENABLED);
+                    let processor_affinity = ProcessorAffinity { local_apic_id, proximity_domain, is_enabled };
+                    processor_affinities[processor_count] = processor_affinity;
+                    processor_count += 1;
+                }
+                SratEntry::Memory(entry) => {
+                    let flags = entry.flags;
+                    let base_address = entry.base_address_low as u64 + ((entry.base_address_high as u64) << 32);
+                    let length = entry.length_low as u64 + ((entry.length_high as u64) << 32);
+                    let proximity_domain = Some(entry.proximity_domain);
+                    let hot_pluggable = flags.contains(MemoryAffinityFlags::HOT_PLUGGABLE);
+                    let non_volatile = flags.contains(MemoryAffinityFlags::NON_VOLATILE);
+                    let is_enabled = flags.contains(MemoryAffinityFlags::ENABLED);
+                    let memory_range = MemoryRange {
+                        base_address,
+                        length,
+                        proximity_domain,
+                        hot_pluggable,
+                        non_volatile,
+                        is_enabled,
+                    };
+                    memory_ranges[memory_range_count] = memory_range;
+                    memory_range_count += 1;
+                }
+                // TODO: parse information of generic initiators
+                SratEntry::GenericInitiator(_) => {}
+                _ => {}
+            }
+        }
+
+        let processor_topology = ProcessorTopology { processor_affinities };
+        let memory_info = MemoryInfo { memory_ranges };
+        Ok((processor_topology, memory_info))
+    }
+
+    fn entries(self: Pin<&Self>) -> SratEntryIter {
+        let ptr = unsafe { Pin::into_inner_unchecked(self) as *const Srat as *const u8 };
+        SratEntryIter {
+            pointer: unsafe { ptr.add(mem::size_of::<Srat>()) },
+            remaining_length: self.header.length - mem::size_of::<Srat>() as u32,
+            _phantom: PhantomData,
+        }
+    }
+}
+
+struct SratEntryIter<'a> {
+    pointer: *const u8,
+    remaining_length: u32,
+    _phantom: PhantomData<&'a ()>,
+}
+
+impl<'a> Iterator for SratEntryIter<'a> {
+    type Item = SratEntry<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.remaining_length > 0 {
+            let entry_pointer = self.pointer;
+            let entry_header = unsafe { *(self.pointer as *const AffinityEntryHeader) };
+
+            self.pointer = unsafe { self.pointer.offset(entry_header.length as isize) };
+            self.remaining_length -= entry_header.length as u32;
+
+            match entry_header.r#type {
+                AffinityEntryType::LocalApic => Some(SratEntry::LocalApic(unsafe { &*(entry_pointer as *const LocalApicAffinityEntry) })),
+                AffinityEntryType::Memory => Some(SratEntry::Memory(unsafe { &*(entry_pointer as *const MemoryAffinityEntry) })),
+                AffinityEntryType::LocalX2Apic => Some(SratEntry::LocalX2Apic(unsafe { &*(entry_pointer as *const LocalX2ApicAffinityEntry) })),
+                AffinityEntryType::Gicc => Some(SratEntry::Gicc(unsafe { &*(entry_pointer as *const GiccAffinityEntry) })),
+                AffinityEntryType::GicIts => Some(SratEntry::GicIts(unsafe { &*(entry_pointer as *const GicItsAffinityEntry) })),
+                AffinityEntryType::GenericInitiator => Some(SratEntry::GenericInitiator(unsafe { &*(entry_pointer as *const GenericInitiatorAffinityEntry) })),
+            }
+        } else {
+            None
+        }
+    }
+}
+
+enum SratEntry <'a>{
+    LocalApic(&'a LocalApicAffinityEntry),
+    Memory(&'a MemoryAffinityEntry),
+    LocalX2Apic(&'a LocalX2ApicAffinityEntry),
+    // TODO: remove the attributes after we can parse these entries.
+    #[allow(unused)]
+    Gicc(&'a GiccAffinityEntry),
+    #[allow(unused)]
+    GicIts(&'a GicItsAffinityEntry),
+    #[allow(unused)]
+    GenericInitiator(&'a GenericInitiatorAffinityEntry),
+}
+
+#[derive(Clone, Copy)]
+#[repr(C, packed)]
+struct AffinityEntryHeader {
+    r#type: AffinityEntryType,
+    length: u8,
+}
+
+#[allow(unused)]
+#[derive(Clone, Copy)]
+#[repr(u8)]
+enum AffinityEntryType {
+    LocalApic = 0,
+    Memory = 1,
+    LocalX2Apic = 2,
+    Gicc = 3,
+    GicIts = 4,
+    GenericInitiator = 5,
+}
+
+#[repr(C, packed)]
+struct LocalApicAffinityEntry {
+    header: AffinityEntryHeader,
+    proximity_domain_low: u8,
+    apic_id: u8,
+    flags: LocalApicAffinityFlags,
+    local_sapic_eid: u8,
+    proximity_domain_high: [u8; 3],
+    clock_domain: u32,
+}
+
+bitflags! {
+    #[derive(Clone, Copy)]
+    struct LocalApicAffinityFlags: u32 {
+        const ENABLED = 1;
+    }
+}
+
+#[repr(C, packed)]
+struct MemoryAffinityEntry {
+    header: AffinityEntryHeader,
+    proximity_domain: u32,
+    _reserved_1: u16,
+    base_address_low: u32,
+    base_address_high: u32,
+    length_low: u32,
+    length_high: u32,
+    _reserved_2: u32,
+    flags: MemoryAffinityFlags,
+    _reserved_3: u64,
+}
+
+bitflags! {
+    #[derive(Clone, Copy)]
+    struct MemoryAffinityFlags: u32 {
+        const ENABLED = 1;
+        const HOT_PLUGGABLE = 1 << 1;
+        const NON_VOLATILE = 1 << 2;
+    }
+}
+
+#[repr(C, packed)]
+struct LocalX2ApicAffinityEntry {
+    header: AffinityEntryHeader,
+    _reserved_1: u16,
+    proximity_domain: u32,
+    x2apic_id: u32,
+    flags: LocalX2ApicAffinityFlags,
+    clock_domain: u32,
+    _reserved_2: u32,
+}
+
+type LocalX2ApicAffinityFlags = LocalApicAffinityFlags;
+
+#[repr(C, packed)]
+struct GiccAffinityEntry {
+    header: AffinityEntryHeader,
+    proximity_domain: u32,
+    acpi_processor_uid: u32,
+    flags: GiccAffinityFlags,
+    clock_domain: u32,
+}
+
+type GiccAffinityFlags = LocalApicAffinityFlags;
+
+#[repr(C, packed)]
+struct GicItsAffinityEntry {
+    header: AffinityEntryHeader,
+    proximity_domain: u32,
+    _reserved: u16,
+    its_id: u32,
+}
+
+#[repr(C, packed)]
+struct GenericInitiatorAffinityEntry {
+    header: AffinityEntryHeader,
+    _reserved_1: u8,
+    device_handle_type: DeviceHandleType,
+    proximity_domain: u32,
+    device_handle: DeviceHandle,
+    flags: GenericInitiatorAffinityFlags,
+    _reserved_2: u32,
+}
+
+#[allow(unused)]
+#[repr(u8)]
+#[non_exhaustive]
+enum DeviceHandleType {
+    Acpi = 0,
+    Pci = 1,
+}
+
+// TODO: remove this attribute after we can parse generic initiator affinity entry.
+#[allow(unused)]
+#[repr(C)]
+enum DeviceHandle {
+    Acpi(AcpiDeviceHandle),
+    Pci(PciDeviceHandle),
+}
+
+#[repr(C, packed)]
+struct AcpiDeviceHandle {
+    acpi_hid: u64,
+    acpi_uid: u32,
+    _reserved: u32,
+}
+
+#[repr(C, packed)]
+struct PciDeviceHandle {
+    pci_segment: u16,
+    pci_bdf_number: u16,
+    _reserved: [u32; 3],
+}
+
+bitflags! {
+    #[derive(Clone, Copy)]
+    struct GenericInitiatorAffinityFlags: u32 {
+        const ENABLED = 1;
+        const ARCHITECTURAL_TRANSACTIONS = 1 << 1;
+    }
+}
diff --git a/aml/src/value.rs b/aml/src/value.rs
index 4da19842..21da8f8f 100644
--- a/aml/src/value.rs
+++ b/aml/src/value.rs
@@ -560,7 +560,7 @@ impl Args {
         }
 
         let mut args: Vec<Option<AmlValue>> = list.into_iter().map(Option::Some).collect();
-        args.extend(core::iter::repeat(None).take(7 - args.len()));
+        args.extend(core::iter::repeat_n(None, 7 - args.len()));
         Ok(Args(args.try_into().unwrap()))
     }
 
diff --git a/aml_tester/src/main.rs b/aml_tester/src/main.rs
index 50fab073..a17c247a 100644
--- a/aml_tester/src/main.rs
+++ b/aml_tester/src/main.rs
@@ -13,7 +13,7 @@ use aml::{AmlContext, DebugVerbosity};
 use clap::{Arg, ArgAction, ArgGroup};
 use std::{
     cell::RefCell,
-    collections::{HashMap, HashSet},
+    collections::HashSet,
     ffi::OsStr,
     fs::{self, File},
     io::{Read, Write},