/* * Dynamic reconfiguration memory support * * Copyright 2017 IBM Corporation * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #define pr_fmt(fmt) "drmem: " fmt #include #include #include #include #include #include static struct drmem_lmb_info __drmem_info; struct drmem_lmb_info *drmem_info = &__drmem_info; u64 drmem_lmb_memory_max(void) { struct drmem_lmb *last_lmb; last_lmb = &drmem_info->lmbs[drmem_info->n_lmbs - 1]; return last_lmb->base_addr + drmem_lmb_size(); } static u32 drmem_lmb_flags(struct drmem_lmb *lmb) { /* * Return the value of the lmb flags field minus the reserved * bit used internally for hotplug processing. */ return lmb->flags & ~DRMEM_LMB_RESERVED; } static struct property *clone_property(struct property *prop, u32 prop_sz) { struct property *new_prop; new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL); if (!new_prop) return NULL; new_prop->name = kstrdup(prop->name, GFP_KERNEL); new_prop->value = kzalloc(prop_sz, GFP_KERNEL); if (!new_prop->name || !new_prop->value) { kfree(new_prop->name); kfree(new_prop->value); kfree(new_prop); return NULL; } new_prop->length = prop_sz; #if defined(CONFIG_OF_DYNAMIC) of_property_set_flag(new_prop, OF_DYNAMIC); #endif return new_prop; } static int drmem_update_dt_v1(struct device_node *memory, struct property *prop) { struct property *new_prop; struct of_drconf_cell_v1 *dr_cell; struct drmem_lmb *lmb; u32 *p; new_prop = clone_property(prop, prop->length); if (!new_prop) return -1; p = new_prop->value; *p++ = cpu_to_be32(drmem_info->n_lmbs); dr_cell = (struct of_drconf_cell_v1 *)p; for_each_drmem_lmb(lmb) { dr_cell->base_addr = cpu_to_be64(lmb->base_addr); dr_cell->drc_index = cpu_to_be32(lmb->drc_index); dr_cell->aa_index = cpu_to_be32(lmb->aa_index); dr_cell->flags = cpu_to_be32(drmem_lmb_flags(lmb)); dr_cell++; } of_update_property(memory, new_prop); return 0; } int drmem_update_dt(void) { struct device_node *memory; struct property *prop; int rc = -1; memory = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory"); if (!memory) return -1; prop = of_find_property(memory, "ibm,dynamic-memory", NULL); if (prop) rc = drmem_update_dt_v1(memory, prop); of_node_put(memory); return rc; } static void __init read_drconf_v1_cell(struct drmem_lmb *lmb, const __be32 **prop) { const __be32 *p = *prop; lmb->base_addr = dt_mem_next_cell(dt_root_addr_cells, &p); lmb->drc_index = of_read_number(p++, 1); p++; /* skip reserved field */ lmb->aa_index = of_read_number(p++, 1); lmb->flags = of_read_number(p++, 1); *prop = p; } static void __init __walk_drmem_v1_lmbs(const __be32 *prop, const __be32 *usm, void (*func)(struct drmem_lmb *, const __be32 **)) { struct drmem_lmb lmb; u32 i, n_lmbs; n_lmbs = of_read_number(prop++, 1); for (i = 0; i < n_lmbs; i++) { read_drconf_v1_cell(&lmb, &prop); func(&lmb, &usm); } } #ifdef CONFIG_PPC_PSERIES void __init walk_drmem_lmbs_early(unsigned long node, void (*func)(struct drmem_lmb *, const __be32 **)) { const __be32 *prop, *usm; int len; prop = of_get_flat_dt_prop(node, "ibm,lmb-size", &len); if (!prop || len < dt_root_size_cells * sizeof(__be32)) return; drmem_info->lmb_size = dt_mem_next_cell(dt_root_size_cells, &prop); usm = of_get_flat_dt_prop(node, "linux,drconf-usable-memory", &len); prop = of_get_flat_dt_prop(node, "ibm,dynamic-memory", &len); if (prop) __walk_drmem_v1_lmbs(prop, usm, func); memblock_dump_all(); } #endif static int __init init_drmem_lmb_size(struct device_node *dn) { const __be32 *prop; int len; if (drmem_info->lmb_size) return 0; prop = of_get_property(dn, "ibm,lmb-size", &len); if (!prop || len < dt_root_size_cells * sizeof(__be32)) { pr_info("Could not determine LMB size\n"); return -1; } drmem_info->lmb_size = dt_mem_next_cell(dt_root_size_cells, &prop); return 0; } /* * Returns the property linux,drconf-usable-memory if * it exists (the property exists only in kexec/kdump kernels, * added by kexec-tools) */ static const __be32 *of_get_usable_memory(struct device_node *dn) { const __be32 *prop; u32 len; prop = of_get_property(dn, "linux,drconf-usable-memory", &len); if (!prop || len < sizeof(unsigned int)) return NULL; return prop; } void __init walk_drmem_lmbs(struct device_node *dn, void (*func)(struct drmem_lmb *, const __be32 **)) { const __be32 *prop, *usm; if (init_drmem_lmb_size(dn)) return; usm = of_get_usable_memory(dn); prop = of_get_property(dn, "ibm,dynamic-memory", NULL); if (prop) __walk_drmem_v1_lmbs(prop, usm, func); } static void __init init_drmem_v1_lmbs(const __be32 *prop) { struct drmem_lmb *lmb; drmem_info->n_lmbs = of_read_number(prop++, 1); drmem_info->lmbs = kcalloc(drmem_info->n_lmbs, sizeof(*lmb), GFP_KERNEL); if (!drmem_info->lmbs) return; for_each_drmem_lmb(lmb) read_drconf_v1_cell(lmb, &prop); } static int __init drmem_init(void) { struct device_node *dn; const __be32 *prop; dn = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory"); if (!dn) { pr_info("No dynamic reconfiguration memory found\n"); return 0; } if (init_drmem_lmb_size(dn)) { of_node_put(dn); return 0; } prop = of_get_property(dn, "ibm,dynamic-memory", NULL); if (prop) init_drmem_v1_lmbs(prop); of_node_put(dn); return 0; } late_initcall(drmem_init);