// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2019, Intel Corporation. * * Heterogeneous Memory Attributes Table (HMAT) representation * * This program parses and reports the platform's HMAT tables, and registers * the applicable attributes with the node's interfaces. */ #define pr_fmt(fmt) "acpi/hmat: " fmt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static u8 hmat_revision; static int hmat_disable __initdata; void __init disable_hmat(void) { hmat_disable = 1; } static LIST_HEAD(targets); static LIST_HEAD(initiators); static LIST_HEAD(localities); static DEFINE_MUTEX(target_lock); /* * The defined enum order is used to prioritize attributes to break ties when * selecting the best performing node. */ enum locality_types { WRITE_LATENCY, READ_LATENCY, WRITE_BANDWIDTH, READ_BANDWIDTH, }; static struct memory_locality *localities_types[4]; struct target_cache { struct list_head node; struct node_cache_attrs cache_attrs; }; enum { NODE_ACCESS_CLASS_GENPORT_SINK_LOCAL = ACCESS_COORDINATE_MAX, NODE_ACCESS_CLASS_GENPORT_SINK_CPU, NODE_ACCESS_CLASS_MAX, }; struct memory_target { struct list_head node; unsigned int memory_pxm; unsigned int processor_pxm; struct resource memregions; struct access_coordinate coord[NODE_ACCESS_CLASS_MAX]; struct list_head caches; struct node_cache_attrs cache_attrs; u8 gen_port_device_handle[ACPI_SRAT_DEVICE_HANDLE_SIZE]; bool registered; bool ext_updated; /* externally updated */ }; struct memory_initiator { struct list_head node; unsigned int processor_pxm; bool has_cpu; }; struct memory_locality { struct list_head node; struct acpi_hmat_locality *hmat_loc; }; static struct memory_initiator *find_mem_initiator(unsigned int cpu_pxm) { struct memory_initiator *initiator; list_for_each_entry(initiator, &initiators, node) if (initiator->processor_pxm == cpu_pxm) return initiator; return NULL; } static struct memory_target *find_mem_target(unsigned int mem_pxm) { struct memory_target *target; list_for_each_entry(target, &targets, node) if (target->memory_pxm == mem_pxm) return target; return NULL; } static struct memory_target *acpi_find_genport_target(u32 uid) { struct memory_target *target; u32 target_uid; u8 *uid_ptr; list_for_each_entry(target, &targets, node) { uid_ptr = target->gen_port_device_handle + 8; target_uid = *(u32 *)uid_ptr; if (uid == target_uid) return target; } return NULL; } /** * acpi_get_genport_coordinates - Retrieve the access coordinates for a generic port * @uid: ACPI unique id * @coord: The access coordinates written back out for the generic port. * Expect 2 levels array. * * Return: 0 on success. Errno on failure. * * Only supports device handles that are ACPI. Assume ACPI0016 HID for CXL. */ int acpi_get_genport_coordinates(u32 uid, struct access_coordinate *coord) { struct memory_target *target; guard(mutex)(&target_lock); target = acpi_find_genport_target(uid); if (!target) return -ENOENT; coord[ACCESS_COORDINATE_LOCAL] = target->coord[NODE_ACCESS_CLASS_GENPORT_SINK_LOCAL]; coord[ACCESS_COORDINATE_CPU] = target->coord[NODE_ACCESS_CLASS_GENPORT_SINK_CPU]; return 0; } EXPORT_SYMBOL_NS_GPL(acpi_get_genport_coordinates, "CXL"); static __init void alloc_memory_initiator(unsigned int cpu_pxm) { struct memory_initiator *initiator; if (pxm_to_node(cpu_pxm) == NUMA_NO_NODE) return; initiator = find_mem_initiator(cpu_pxm); if (initiator) return; initiator = kzalloc(sizeof(*initiator), GFP_KERNEL); if (!initiator) return; initiator->processor_pxm = cpu_pxm; initiator->has_cpu = node_state(pxm_to_node(cpu_pxm), N_CPU); list_add_tail(&initiator->node, &initiators); } static __init struct memory_target *alloc_target(unsigned int mem_pxm) { struct memory_target *target; target = find_mem_target(mem_pxm); if (!target) { target = kzalloc(sizeof(*target), GFP_KERNEL); if (!target) return NULL; target->memory_pxm = mem_pxm; target->processor_pxm = PXM_INVAL; target->memregions = (struct resource) { .name = "ACPI mem", .start = 0, .end = -1, .flags = IORESOURCE_MEM, }; list_add_tail(&target->node, &targets); INIT_LIST_HEAD(&target->caches); } return target; } static __init void alloc_memory_target(unsigned int mem_pxm, resource_size_t start, resource_size_t len) { struct memory_target *target; target = alloc_target(mem_pxm); if (!target) return; /* * There are potentially multiple ranges per PXM, so record each * in the per-target memregions resource tree. */ if (!__request_region(&target->memregions, start, len, "memory target", IORESOURCE_MEM)) pr_warn("failed to reserve %#llx - %#llx in pxm: %d\n", start, start + len, mem_pxm); } static __init void alloc_genport_target(unsigned int mem_pxm, u8 *handle) { struct memory_target *target; target = alloc_target(mem_pxm); if (!target) return; memcpy(target->gen_port_device_handle, handle, ACPI_SRAT_DEVICE_HANDLE_SIZE); } static __init const char *hmat_data_type(u8 type) { switch (type) { case ACPI_HMAT_ACCESS_LATENCY: return "Access Latency"; case ACPI_HMAT_READ_LATENCY: return "Read Latency"; case ACPI_HMAT_WRITE_LATENCY: return "Write Latency"; case ACPI_HMAT_ACCESS_BANDWIDTH: return "Access Bandwidth"; case ACPI_HMAT_READ_BANDWIDTH: return "Read Bandwidth"; case ACPI_HMAT_WRITE_BANDWIDTH: return "Write Bandwidth"; default: return "Reserved"; } } static __init const char *hmat_data_type_suffix(u8 type) { switch (type) { case ACPI_HMAT_ACCESS_LATENCY: case ACPI_HMAT_READ_LATENCY: case ACPI_HMAT_WRITE_LATENCY: return " nsec"; case ACPI_HMAT_ACCESS_BANDWIDTH: case ACPI_HMAT_READ_BANDWIDTH: case ACPI_HMAT_WRITE_BANDWIDTH: return " MB/s"; default: return ""; } } static u32 hmat_normalize(u16 entry, u64 base, u8 type) { u32 value; /* * Check for invalid and overflow values */ if (entry == 0xffff || !entry) return 0; else if (base > (UINT_MAX / (entry))) return 0; /* * Divide by the base unit for version 1, convert latency from * picosenonds to nanoseconds if revision 2. */ value = entry * base; if (hmat_revision == 1) { if (value < 10) return 0; value = DIV_ROUND_UP(value, 10); } else if (hmat_revision == 2) { switch (type) { case ACPI_HMAT_ACCESS_LATENCY: case ACPI_HMAT_READ_LATENCY: case ACPI_HMAT_WRITE_LATENCY: value = DIV_ROUND_UP(value, 1000); break; default: break; } } return value; } static void hmat_update_target_access(struct memory_target *target, u8 type, u32 value, int access) { switch (type) { case ACPI_HMAT_ACCESS_LATENCY: target->coord[access].read_latency = value; target->coord[access].write_latency = value; break; case ACPI_HMAT_READ_LATENCY: target->coord[access].read_latency = value; break; case ACPI_HMAT_WRITE_LATENCY: target->coord[access].write_latency = value; break; case ACPI_HMAT_ACCESS_BANDWIDTH: target->coord[access].read_bandwidth = value; target->coord[access].write_bandwidth = value; break; case ACPI_HMAT_READ_BANDWIDTH: target->coord[access].read_bandwidth = value; break; case ACPI_HMAT_WRITE_BANDWIDTH: target->coord[access].write_bandwidth = value; break; default: break; } } int hmat_update_target_coordinates(int nid, struct access_coordinate *coord, enum access_coordinate_class access) { struct memory_target *target; int pxm; if (nid == NUMA_NO_NODE) return -EINVAL; pxm = node_to_pxm(nid); guard(mutex)(&target_lock); target = find_mem_target(pxm); if (!target) return -ENODEV; hmat_update_target_access(target, ACPI_HMAT_READ_LATENCY, coord->read_latency, access); hmat_update_target_access(target, ACPI_HMAT_WRITE_LATENCY, coord->write_latency, access); hmat_update_target_access(target, ACPI_HMAT_READ_BANDWIDTH, coord->read_bandwidth, access); hmat_update_target_access(target, ACPI_HMAT_WRITE_BANDWIDTH, coord->write_bandwidth, access); target->ext_updated = true; return 0; } EXPORT_SYMBOL_GPL(hmat_update_target_coordinates); static __init void hmat_add_locality(struct acpi_hmat_locality *hmat_loc) { struct memory_locality *loc; loc = kzalloc(sizeof(*loc), GFP_KERNEL); if (!loc) { pr_notice_once("Failed to allocate HMAT locality\n"); return; } loc->hmat_loc = hmat_loc; list_add_tail(&loc->node, &localities); switch (hmat_loc->data_type) { case ACPI_HMAT_ACCESS_LATENCY: localities_types[READ_LATENCY] = loc; localities_types[WRITE_LATENCY] = loc; break; case ACPI_HMAT_READ_LATENCY: localities_types[READ_LATENCY] = loc; break; case ACPI_HMAT_WRITE_LATENCY: localities_types[WRITE_LATENCY] = loc; break; case ACPI_HMAT_ACCESS_BANDWIDTH: localities_types[READ_BANDWIDTH] = loc; localities_types[WRITE_BANDWIDTH] = loc; break; case ACPI_HMAT_READ_BANDWIDTH: localities_types[READ_BANDWIDTH] = loc; break; case ACPI_HMAT_WRITE_BANDWIDTH: localities_types[WRITE_BANDWIDTH] = loc; break; default: break; } } static __init void hmat_update_target(unsigned int tgt_pxm, unsigned int init_pxm, u8 mem_hier, u8 type, u32 value) { struct memory_target *target = find_mem_target(tgt_pxm); if (mem_hier != ACPI_HMAT_MEMORY) return; if (target && target->processor_pxm == init_pxm) { hmat_update_target_access(target, type, value, ACCESS_COORDINATE_LOCAL); /* If the node has a CPU, update access ACCESS_COORDINATE_CPU */ if (node_state(pxm_to_node(init_pxm), N_CPU)) hmat_update_target_access(target, type, value, ACCESS_COORDINATE_CPU); } } static __init int hmat_parse_locality(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_hmat_locality *hmat_loc = (void *)header; unsigned int init, targ, total_size, ipds, tpds; u32 *inits, *targs, value; u16 *entries; u8 type, mem_hier; if (hmat_loc->header.length < sizeof(*hmat_loc)) { pr_notice("Unexpected locality header length: %u\n", hmat_loc->header.length); return -EINVAL; } type = hmat_loc->data_type; mem_hier = hmat_loc->flags & ACPI_HMAT_MEMORY_HIERARCHY; ipds = hmat_loc->number_of_initiator_Pds; tpds = hmat_loc->number_of_target_Pds; total_size = sizeof(*hmat_loc) + sizeof(*entries) * ipds * tpds + sizeof(*inits) * ipds + sizeof(*targs) * tpds; if (hmat_loc->header.length < total_size) { pr_notice("Unexpected locality header length:%u, minimum required:%u\n", hmat_loc->header.length, total_size); return -EINVAL; } pr_info("Locality: Flags:%02x Type:%s Initiator Domains:%u Target Domains:%u Base:%lld\n", hmat_loc->flags, hmat_data_type(type), ipds, tpds, hmat_loc->entry_base_unit); inits = (u32 *)(hmat_loc + 1); targs = inits + ipds; entries = (u16 *)(targs + tpds); for (init = 0; init < ipds; init++) { alloc_memory_initiator(inits[init]); for (targ = 0; targ < tpds; targ++) { value = hmat_normalize(entries[init * tpds + targ], hmat_loc->entry_base_unit, type); pr_info(" Initiator-Target[%u-%u]:%u%s\n", inits[init], targs[targ], value, hmat_data_type_suffix(type)); hmat_update_target(targs[targ], inits[init], mem_hier, type, value); } } if (mem_hier == ACPI_HMAT_MEMORY) hmat_add_locality(hmat_loc); return 0; } static __init int hmat_parse_cache(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_hmat_cache *cache = (void *)header; struct memory_target *target; struct target_cache *tcache; u32 attrs; if (cache->header.length < sizeof(*cache)) { pr_notice("Unexpected cache header length: %u\n", cache->header.length); return -EINVAL; } attrs = cache->cache_attributes; pr_info("Cache: Domain:%u Size:%llu Attrs:%08x SMBIOS Handles:%d\n", cache->memory_PD, cache->cache_size, attrs, cache->number_of_SMBIOShandles); target = find_mem_target(cache->memory_PD); if (!target) return 0; tcache = kzalloc(sizeof(*tcache), GFP_KERNEL); if (!tcache) { pr_notice_once("Failed to allocate HMAT cache info\n"); return 0; } tcache->cache_attrs.size = cache->cache_size; tcache->cache_attrs.level = (attrs & ACPI_HMAT_CACHE_LEVEL) >> 4; tcache->cache_attrs.line_size = (attrs & ACPI_HMAT_CACHE_LINE_SIZE) >> 16; switch ((attrs & ACPI_HMAT_CACHE_ASSOCIATIVITY) >> 8) { case ACPI_HMAT_CA_DIRECT_MAPPED: tcache->cache_attrs.indexing = NODE_CACHE_DIRECT_MAP; break; case ACPI_HMAT_CA_COMPLEX_CACHE_INDEXING: tcache->cache_attrs.indexing = NODE_CACHE_INDEXED; break; case ACPI_HMAT_CA_NONE: default: tcache->cache_attrs.indexing = NODE_CACHE_OTHER; break; } switch ((attrs & ACPI_HMAT_WRITE_POLICY) >> 12) { case ACPI_HMAT_CP_WB: tcache->cache_attrs.write_policy = NODE_CACHE_WRITE_BACK; break; case ACPI_HMAT_CP_WT: tcache->cache_attrs.write_policy = NODE_CACHE_WRITE_THROUGH; break; case ACPI_HMAT_CP_NONE: default: tcache->cache_attrs.write_policy = NODE_CACHE_WRITE_OTHER; break; } list_add_tail(&tcache->node, &target->caches); return 0; } static int __init hmat_parse_proximity_domain(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_hmat_proximity_domain *p = (void *)header; struct memory_target *target = NULL; if (p->header.length != sizeof(*p)) { pr_notice("Unexpected address range header length: %u\n", p->header.length); return -EINVAL; } if (hmat_revision == 1) pr_info("Memory (%#llx length %#llx) Flags:%04x Processor Domain:%u Memory Domain:%u\n", p->reserved3, p->reserved4, p->flags, p->processor_PD, p->memory_PD); else pr_info("Memory Flags:%04x Processor Domain:%u Memory Domain:%u\n", p->flags, p->processor_PD, p->memory_PD); if ((hmat_revision == 1 && p->flags & ACPI_HMAT_MEMORY_PD_VALID) || hmat_revision > 1) { target = find_mem_target(p->memory_PD); if (!target) { pr_debug("Memory Domain missing from SRAT\n"); return -EINVAL; } } if (target && p->flags & ACPI_HMAT_PROCESSOR_PD_VALID) { int p_node = pxm_to_node(p->processor_PD); if (p_node == NUMA_NO_NODE) { pr_debug("Invalid Processor Domain\n"); return -EINVAL; } target->processor_pxm = p->processor_PD; } return 0; } static int __init hmat_parse_subtable(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_hmat_structure *hdr = (void *)header; if (!hdr) return -EINVAL; switch (hdr->type) { case ACPI_HMAT_TYPE_PROXIMITY: return hmat_parse_proximity_domain(header, end); case ACPI_HMAT_TYPE_LOCALITY: return hmat_parse_locality(header, end); case ACPI_HMAT_TYPE_CACHE: return hmat_parse_cache(header, end); default: return -EINVAL; } } static __init int srat_parse_mem_affinity(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_srat_mem_affinity *ma = (void *)header; if (!ma) return -EINVAL; if (!(ma->flags & ACPI_SRAT_MEM_ENABLED)) return 0; alloc_memory_target(ma->proximity_domain, ma->base_address, ma->length); return 0; } static __init int srat_parse_genport_affinity(union acpi_subtable_headers *header, const unsigned long end) { struct acpi_srat_generic_affinity *ga = (void *)header; if (!ga) return -EINVAL; if (!(ga->flags & ACPI_SRAT_GENERIC_AFFINITY_ENABLED)) return 0; /* Skip PCI device_handle for now */ if (ga->device_handle_type != 0) return 0; alloc_genport_target(ga->proximity_domain, (u8 *)ga->device_handle); return 0; } static u32 hmat_initiator_perf(struct memory_target *target, struct memory_initiator *initiator, struct acpi_hmat_locality *hmat_loc) { unsigned int ipds, tpds, i, idx = 0, tdx = 0; u32 *inits, *targs; u16 *entries; ipds = hmat_loc->number_of_initiator_Pds; tpds = hmat_loc->number_of_target_Pds; inits = (u32 *)(hmat_loc + 1); targs = inits + ipds; entries = (u16 *)(targs + tpds); for (i = 0; i < ipds; i++) { if (inits[i] == initiator->processor_pxm) { idx = i; break; } } if (i == ipds) return 0; for (i = 0; i < tpds; i++) { if (targs[i] == target->memory_pxm) { tdx = i; break; } } if (i == tpds) return 0; return hmat_normalize(entries[idx * tpds + tdx], hmat_loc->entry_base_unit, hmat_loc->data_type); } static bool hmat_update_best(u8 type, u32 value, u32 *best) { bool updated = false; if (!value) return false; switch (type) { case ACPI_HMAT_ACCESS_LATENCY: case ACPI_HMAT_READ_LATENCY: case ACPI_HMAT_WRITE_LATENCY: if (!*best || *best > value) { *best = value; updated = true; } break; case ACPI_HMAT_ACCESS_BANDWIDTH: case ACPI_HMAT_READ_BANDWIDTH: case ACPI_HMAT_WRITE_BANDWIDTH: if (!*best || *best < value) { *best = value; updated = true; } break; } return updated; } static int initiator_cmp(void *priv, const struct list_head *a, const struct list_head *b) { struct memory_initiator *ia; struct memory_initiator *ib; ia = list_entry(a, struct memory_initiator, node); ib = list_entry(b, struct memory_initiator, node); return ia->processor_pxm - ib->processor_pxm; } static int initiators_to_nodemask(unsigned long *p_nodes) { struct memory_initiator *initiator; if (list_empty(&initiators)) return -ENXIO; list_for_each_entry(initiator, &initiators, node) set_bit(initiator->processor_pxm, p_nodes); return 0; } static void hmat_update_target_attrs(struct memory_target *target, unsigned long *p_nodes, int access) { struct memory_initiator *initiator; unsigned int cpu_nid; struct memory_locality *loc = NULL; u32 best = 0; int i; /* Don't update if an external agent has changed the data. */ if (target->ext_updated) return; /* Don't update for generic port if there's no device handle */ if ((access == NODE_ACCESS_CLASS_GENPORT_SINK_LOCAL || access == NODE_ACCESS_CLASS_GENPORT_SINK_CPU) && !(*(u16 *)target->gen_port_device_handle)) return; bitmap_zero(p_nodes, MAX_NUMNODES); /* * If the Address Range Structure provides a local processor pxm, set * only that one. Otherwise, find the best performance attributes and * collect all initiators that match. */ if (target->processor_pxm != PXM_INVAL) { cpu_nid = pxm_to_node(target->processor_pxm); if (access == ACCESS_COORDINATE_LOCAL || node_state(cpu_nid, N_CPU)) { set_bit(target->processor_pxm, p_nodes); return; } } if (list_empty(&localities)) return; /* * We need the initiator list sorted so we can use bitmap_clear for * previously set initiators when we find a better memory accessor. * We'll also use the sorting to prime the candidate nodes with known * initiators. */ list_sort(NULL, &initiators, initiator_cmp); if (initiators_to_nodemask(p_nodes) < 0) return; for (i = WRITE_LATENCY; i <= READ_BANDWIDTH; i++) { loc = localities_types[i]; if (!loc) continue; best = 0; list_for_each_entry(initiator, &initiators, node) { u32 value; if ((access == ACCESS_COORDINATE_CPU || access == NODE_ACCESS_CLASS_GENPORT_SINK_CPU) && !initiator->has_cpu) { clear_bit(initiator->processor_pxm, p_nodes); continue; } if (!test_bit(initiator->processor_pxm, p_nodes)) continue; value = hmat_initiator_perf(target, initiator, loc->hmat_loc); if (hmat_update_best(loc->hmat_loc->data_type, value, &best)) bitmap_clear(p_nodes, 0, initiator->processor_pxm); if (value != best) clear_bit(initiator->processor_pxm, p_nodes); } if (best) hmat_update_target_access(target, loc->hmat_loc->data_type, best, access); } } static void __hmat_register_target_initiators(struct memory_target *target, unsigned long *p_nodes, int access) { unsigned int mem_nid, cpu_nid; int i; mem_nid = pxm_to_node(target->memory_pxm); hmat_update_target_attrs(target, p_nodes, access); for_each_set_bit(i, p_nodes, MAX_NUMNODES) { cpu_nid = pxm_to_node(i); register_memory_node_under_compute_node(mem_nid, cpu_nid, access); } } static void hmat_update_generic_target(struct memory_target *target) { static DECLARE_BITMAP(p_nodes, MAX_NUMNODES); hmat_update_target_attrs(target, p_nodes, NODE_ACCESS_CLASS_GENPORT_SINK_LOCAL); hmat_update_target_attrs(target, p_nodes, NODE_ACCESS_CLASS_GENPORT_SINK_CPU); } static void hmat_register_target_initiators(struct memory_target *target) { static DECLARE_BITMAP(p_nodes, MAX_NUMNODES); __hmat_register_target_initiators(target, p_nodes, ACCESS_COORDINATE_LOCAL); __hmat_register_target_initiators(target, p_nodes, ACCESS_COORDINATE_CPU); } static void hmat_register_target_cache(struct memory_target *target) { unsigned mem_nid = pxm_to_node(target->memory_pxm); struct target_cache *tcache; list_for_each_entry(tcache, &target->caches, node) node_add_cache(mem_nid, &tcache->cache_attrs); } static void hmat_register_target_perf(struct memory_target *target, int access) { unsigned mem_nid = pxm_to_node(target->memory_pxm); node_set_perf_attrs(mem_nid, &target->coord[access], access); } static void hmat_register_target_devices(struct memory_target *target) { struct resource *res; /* * Do not bother creating devices if no driver is available to * consume them. */ if (!IS_ENABLED(CONFIG_DEV_DAX_HMEM)) return; for (res = target->memregions.child; res; res = res->sibling) { int target_nid = pxm_to_node(target->memory_pxm); hmem_register_resource(target_nid, res); } } static void hmat_register_target(struct memory_target *target) { int nid = pxm_to_node(target->memory_pxm); /* * Devices may belong to either an offline or online * node, so unconditionally add them. */ hmat_register_target_devices(target); /* * Register generic port perf numbers. The nid may not be * initialized and is still NUMA_NO_NODE. */ mutex_lock(&target_lock); if (*(u16 *)target->gen_port_device_handle) { hmat_update_generic_target(target); target->registered = true; } mutex_unlock(&target_lock); /* * Skip offline nodes. This can happen when memory * marked EFI_MEMORY_SP, "specific purpose", is applied * to all the memory in a proximity domain leading to * the node being marked offline / unplugged, or if * memory-only "hotplug" node is offline. */ if (nid == NUMA_NO_NODE || !node_online(nid)) return; mutex_lock(&target_lock); if (!target->registered) { hmat_register_target_initiators(target); hmat_register_target_cache(target); hmat_register_target_perf(target, ACCESS_COORDINATE_LOCAL); hmat_register_target_perf(target, ACCESS_COORDINATE_CPU); target->registered = true; } mutex_unlock(&target_lock); } static void hmat_register_targets(void) { struct memory_target *target; list_for_each_entry(target, &targets, node) hmat_register_target(target); } static int hmat_callback(struct notifier_block *self, unsigned long action, void *arg) { struct memory_target *target; struct memory_notify *mnb = arg; int pxm, nid = mnb->status_change_nid; if (nid == NUMA_NO_NODE || action != MEM_ONLINE) return NOTIFY_OK; pxm = node_to_pxm(nid); target = find_mem_target(pxm); if (!target) return NOTIFY_OK; hmat_register_target(target); return NOTIFY_OK; } static int __init hmat_set_default_dram_perf(void) { int rc; int nid, pxm; struct memory_target *target; struct access_coordinate *attrs; for_each_node_mask(nid, default_dram_nodes) { pxm = node_to_pxm(nid); target = find_mem_target(pxm); if (!target) continue; attrs = &target->coord[ACCESS_COORDINATE_CPU]; rc = mt_set_default_dram_perf(nid, attrs, "ACPI HMAT"); if (rc) return rc; } return 0; } static int hmat_calculate_adistance(struct notifier_block *self, unsigned long nid, void *data) { static DECLARE_BITMAP(p_nodes, MAX_NUMNODES); struct memory_target *target; struct access_coordinate *perf; int *adist = data; int pxm; pxm = node_to_pxm(nid); target = find_mem_target(pxm); if (!target) return NOTIFY_OK; mutex_lock(&target_lock); hmat_update_target_attrs(target, p_nodes, ACCESS_COORDINATE_CPU); mutex_unlock(&target_lock); perf = &target->coord[ACCESS_COORDINATE_CPU]; if (mt_perf_to_adistance(perf, adist)) return NOTIFY_OK; return NOTIFY_STOP; } static struct notifier_block hmat_adist_nb __meminitdata = { .notifier_call = hmat_calculate_adistance, .priority = 100, }; static __init void hmat_free_structures(void) { struct memory_target *target, *tnext; struct memory_locality *loc, *lnext; struct memory_initiator *initiator, *inext; struct target_cache *tcache, *cnext; list_for_each_entry_safe(target, tnext, &targets, node) { struct resource *res, *res_next; list_for_each_entry_safe(tcache, cnext, &target->caches, node) { list_del(&tcache->node); kfree(tcache); } list_del(&target->node); res = target->memregions.child; while (res) { res_next = res->sibling; __release_region(&target->memregions, res->start, resource_size(res)); res = res_next; } kfree(target); } list_for_each_entry_safe(initiator, inext, &initiators, node) { list_del(&initiator->node); kfree(initiator); } list_for_each_entry_safe(loc, lnext, &localities, node) { list_del(&loc->node); kfree(loc); } } static __init int hmat_init(void) { struct acpi_table_header *tbl; enum acpi_hmat_type i; acpi_status status; if (srat_disabled() || hmat_disable) return 0; status = acpi_get_table(ACPI_SIG_SRAT, 0, &tbl); if (ACPI_FAILURE(status)) return 0; if (acpi_table_parse_entries(ACPI_SIG_SRAT, sizeof(struct acpi_table_srat), ACPI_SRAT_TYPE_MEMORY_AFFINITY, srat_parse_mem_affinity, 0) < 0) goto out_put; if (acpi_table_parse_entries(ACPI_SIG_SRAT, sizeof(struct acpi_table_srat), ACPI_SRAT_TYPE_GENERIC_PORT_AFFINITY, srat_parse_genport_affinity, 0) < 0) goto out_put; acpi_put_table(tbl); status = acpi_get_table(ACPI_SIG_HMAT, 0, &tbl); if (ACPI_FAILURE(status)) goto out_put; hmat_revision = tbl->revision; switch (hmat_revision) { case 1: case 2: break; default: pr_notice("Ignoring: Unknown revision:%d\n", hmat_revision); goto out_put; } for (i = ACPI_HMAT_TYPE_PROXIMITY; i < ACPI_HMAT_TYPE_RESERVED; i++) { if (acpi_table_parse_entries(ACPI_SIG_HMAT, sizeof(struct acpi_table_hmat), i, hmat_parse_subtable, 0) < 0) { pr_notice("Ignoring: Invalid table"); goto out_put; } } hmat_register_targets(); /* Keep the table and structures if the notifier may use them */ if (hotplug_memory_notifier(hmat_callback, HMAT_CALLBACK_PRI)) goto out_put; if (!hmat_set_default_dram_perf()) register_mt_adistance_algorithm(&hmat_adist_nb); return 0; out_put: hmat_free_structures(); acpi_put_table(tbl); return 0; } subsys_initcall(hmat_init);