| /* | 
 |  * 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 <linux/kernel.h> | 
 | #include <linux/of.h> | 
 | #include <linux/of_fdt.h> | 
 | #include <linux/memblock.h> | 
 | #include <asm/prom.h> | 
 | #include <asm/drmem.h> | 
 |  | 
 | 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; | 
 | } | 
 |  | 
 | static void init_drconf_v2_cell(struct of_drconf_cell_v2 *dr_cell, | 
 | 				struct 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(lmb->flags); | 
 | } | 
 |  | 
 | static int drmem_update_dt_v2(struct device_node *memory, | 
 | 			      struct property *prop) | 
 | { | 
 | 	struct property *new_prop; | 
 | 	struct of_drconf_cell_v2 *dr_cell; | 
 | 	struct drmem_lmb *lmb, *prev_lmb; | 
 | 	u32 lmb_sets, prop_sz, seq_lmbs; | 
 | 	u32 *p; | 
 |  | 
 | 	/* First pass, determine how many LMB sets are needed. */ | 
 | 	lmb_sets = 0; | 
 | 	prev_lmb = NULL; | 
 | 	for_each_drmem_lmb(lmb) { | 
 | 		if (!prev_lmb) { | 
 | 			prev_lmb = lmb; | 
 | 			lmb_sets++; | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		if (prev_lmb->aa_index != lmb->aa_index || | 
 | 		    prev_lmb->flags != lmb->flags) | 
 | 			lmb_sets++; | 
 |  | 
 | 		prev_lmb = lmb; | 
 | 	} | 
 |  | 
 | 	prop_sz = lmb_sets * sizeof(*dr_cell) + sizeof(__be32); | 
 | 	new_prop = clone_property(prop, prop_sz); | 
 | 	if (!new_prop) | 
 | 		return -1; | 
 |  | 
 | 	p = new_prop->value; | 
 | 	*p++ = cpu_to_be32(lmb_sets); | 
 |  | 
 | 	dr_cell = (struct of_drconf_cell_v2 *)p; | 
 |  | 
 | 	/* Second pass, populate the LMB set data */ | 
 | 	prev_lmb = NULL; | 
 | 	seq_lmbs = 0; | 
 | 	for_each_drmem_lmb(lmb) { | 
 | 		if (prev_lmb == NULL) { | 
 | 			/* Start of first LMB set */ | 
 | 			prev_lmb = lmb; | 
 | 			init_drconf_v2_cell(dr_cell, lmb); | 
 | 			seq_lmbs++; | 
 | 			continue; | 
 | 		} | 
 |  | 
 | 		if (prev_lmb->aa_index != lmb->aa_index || | 
 | 		    prev_lmb->flags != lmb->flags) { | 
 | 			/* end of one set, start of another */ | 
 | 			dr_cell->seq_lmbs = cpu_to_be32(seq_lmbs); | 
 | 			dr_cell++; | 
 |  | 
 | 			init_drconf_v2_cell(dr_cell, lmb); | 
 | 			seq_lmbs = 1; | 
 | 		} else { | 
 | 			seq_lmbs++; | 
 | 		} | 
 |  | 
 | 		prev_lmb = lmb; | 
 | 	} | 
 |  | 
 | 	/* close out last LMB set */ | 
 | 	dr_cell->seq_lmbs = cpu_to_be32(seq_lmbs); | 
 | 	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); | 
 | 	} else { | 
 | 		prop = of_find_property(memory, "ibm,dynamic-memory-v2", NULL); | 
 | 		if (prop) | 
 | 			rc = drmem_update_dt_v2(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); | 
 | 	} | 
 | } | 
 |  | 
 | static void __init read_drconf_v2_cell(struct of_drconf_cell_v2 *dr_cell, | 
 | 				       const __be32 **prop) | 
 | { | 
 | 	const __be32 *p = *prop; | 
 |  | 
 | 	dr_cell->seq_lmbs = of_read_number(p++, 1); | 
 | 	dr_cell->base_addr = dt_mem_next_cell(dt_root_addr_cells, &p); | 
 | 	dr_cell->drc_index = of_read_number(p++, 1); | 
 | 	dr_cell->aa_index = of_read_number(p++, 1); | 
 | 	dr_cell->flags = of_read_number(p++, 1); | 
 |  | 
 | 	*prop = p; | 
 | } | 
 |  | 
 | static void __init __walk_drmem_v2_lmbs(const __be32 *prop, const __be32 *usm, | 
 | 			void (*func)(struct drmem_lmb *, const __be32 **)) | 
 | { | 
 | 	struct of_drconf_cell_v2 dr_cell; | 
 | 	struct drmem_lmb lmb; | 
 | 	u32 i, j, lmb_sets; | 
 |  | 
 | 	lmb_sets = of_read_number(prop++, 1); | 
 |  | 
 | 	for (i = 0; i < lmb_sets; i++) { | 
 | 		read_drconf_v2_cell(&dr_cell, &prop); | 
 |  | 
 | 		for (j = 0; j < dr_cell.seq_lmbs; j++) { | 
 | 			lmb.base_addr = dr_cell.base_addr; | 
 | 			dr_cell.base_addr += drmem_lmb_size(); | 
 |  | 
 | 			lmb.drc_index = dr_cell.drc_index; | 
 | 			dr_cell.drc_index++; | 
 |  | 
 | 			lmb.aa_index = dr_cell.aa_index; | 
 | 			lmb.flags = dr_cell.flags; | 
 |  | 
 | 			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); | 
 | 	} else { | 
 | 		prop = of_get_flat_dt_prop(node, "ibm,dynamic-memory-v2", | 
 | 					   &len); | 
 | 		if (prop) | 
 | 			__walk_drmem_v2_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); | 
 | 	} else { | 
 | 		prop = of_get_property(dn, "ibm,dynamic-memory-v2", NULL); | 
 | 		if (prop) | 
 | 			__walk_drmem_v2_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 void __init init_drmem_v2_lmbs(const __be32 *prop) | 
 | { | 
 | 	struct drmem_lmb *lmb; | 
 | 	struct of_drconf_cell_v2 dr_cell; | 
 | 	const __be32 *p; | 
 | 	u32 i, j, lmb_sets; | 
 | 	int lmb_index; | 
 |  | 
 | 	lmb_sets = of_read_number(prop++, 1); | 
 |  | 
 | 	/* first pass, calculate the number of LMBs */ | 
 | 	p = prop; | 
 | 	for (i = 0; i < lmb_sets; i++) { | 
 | 		read_drconf_v2_cell(&dr_cell, &p); | 
 | 		drmem_info->n_lmbs += dr_cell.seq_lmbs; | 
 | 	} | 
 |  | 
 | 	drmem_info->lmbs = kcalloc(drmem_info->n_lmbs, sizeof(*lmb), | 
 | 				   GFP_KERNEL); | 
 | 	if (!drmem_info->lmbs) | 
 | 		return; | 
 |  | 
 | 	/* second pass, read in the LMB information */ | 
 | 	lmb_index = 0; | 
 | 	p = prop; | 
 |  | 
 | 	for (i = 0; i < lmb_sets; i++) { | 
 | 		read_drconf_v2_cell(&dr_cell, &p); | 
 |  | 
 | 		for (j = 0; j < dr_cell.seq_lmbs; j++) { | 
 | 			lmb = &drmem_info->lmbs[lmb_index++]; | 
 |  | 
 | 			lmb->base_addr = dr_cell.base_addr; | 
 | 			dr_cell.base_addr += drmem_info->lmb_size; | 
 |  | 
 | 			lmb->drc_index = dr_cell.drc_index; | 
 | 			dr_cell.drc_index++; | 
 |  | 
 | 			lmb->aa_index = dr_cell.aa_index; | 
 | 			lmb->flags = dr_cell.flags; | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | 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); | 
 | 	} else { | 
 | 		prop = of_get_property(dn, "ibm,dynamic-memory-v2", NULL); | 
 | 		if (prop) | 
 | 			init_drmem_v2_lmbs(prop); | 
 | 	} | 
 |  | 
 | 	of_node_put(dn); | 
 | 	return 0; | 
 | } | 
 | late_initcall(drmem_init); |