Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 | // SPDX-License-Identifier: GPL-2.0 /* * Handles hot and cold plug of persistent memory regions on pseries. */ #define pr_fmt(fmt) "pseries-pmem: " fmt #include <linux/kernel.h> #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/sched.h> /* for idle_task_exit */ #include <linux/sched/hotplug.h> #include <linux/cpu.h> #include <linux/of.h> #include <linux/of_platform.h> #include <linux/slab.h> #include <asm/rtas.h> #include <asm/firmware.h> #include <asm/machdep.h> #include <asm/vdso_datapage.h> #include <asm/plpar_wrappers.h> #include <asm/topology.h> #include "pseries.h" static struct device_node *pmem_node; static ssize_t pmem_drc_add_node(u32 drc_index) { struct device_node *dn; int rc; pr_debug("Attempting to add pmem node, drc index: %x\n", drc_index); rc = dlpar_acquire_drc(drc_index); if (rc) { pr_err("Failed to acquire DRC, rc: %d, drc index: %x\n", rc, drc_index); return -EINVAL; } dn = dlpar_configure_connector(cpu_to_be32(drc_index), pmem_node); if (!dn) { pr_err("configure-connector failed for drc %x\n", drc_index); dlpar_release_drc(drc_index); return -EINVAL; } /* NB: The of reconfig notifier creates platform device from the node */ rc = dlpar_attach_node(dn, pmem_node); if (rc) { pr_err("Failed to attach node %pOF, rc: %d, drc index: %x\n", dn, rc, drc_index); if (dlpar_release_drc(drc_index)) dlpar_free_cc_nodes(dn); return rc; } pr_info("Successfully added %pOF, drc index: %x\n", dn, drc_index); return 0; } static ssize_t pmem_drc_remove_node(u32 drc_index) { struct device_node *dn; uint32_t index; int rc; for_each_child_of_node(pmem_node, dn) { if (of_property_read_u32(dn, "ibm,my-drc-index", &index)) continue; if (index == drc_index) break; } if (!dn) { pr_err("Attempting to remove unused DRC index %x\n", drc_index); return -ENODEV; } pr_debug("Attempting to remove %pOF, drc index: %x\n", dn, drc_index); /* * NB: tears down the ibm,pmemory device as a side-effect */ rc = dlpar_detach_node(dn); if (rc) return rc; rc = dlpar_release_drc(drc_index); if (rc) { pr_err("Failed to release drc (%x) for CPU %pOFn, rc: %d\n", drc_index, dn, rc); dlpar_attach_node(dn, pmem_node); return rc; } pr_info("Successfully removed PMEM with drc index: %x\n", drc_index); return 0; } int dlpar_hp_pmem(struct pseries_hp_errorlog *hp_elog) { u32 drc_index; int rc; /* slim chance, but we might get a hotplug event while booting */ if (!pmem_node) pmem_node = of_find_node_by_type(NULL, "ibm,persistent-memory"); if (!pmem_node) { pr_err("Hotplug event for a pmem device, but none exists\n"); return -ENODEV; } if (hp_elog->id_type != PSERIES_HP_ELOG_ID_DRC_INDEX) { pr_err("Unsupported hotplug event type %d\n", hp_elog->id_type); return -EINVAL; } drc_index = hp_elog->_drc_u.drc_index; lock_device_hotplug(); if (hp_elog->action == PSERIES_HP_ELOG_ACTION_ADD) { rc = pmem_drc_add_node(drc_index); } else if (hp_elog->action == PSERIES_HP_ELOG_ACTION_REMOVE) { rc = pmem_drc_remove_node(drc_index); } else { pr_err("Unsupported hotplug action (%d)\n", hp_elog->action); rc = -EINVAL; } unlock_device_hotplug(); return rc; } static const struct of_device_id drc_pmem_match[] = { { .type = "ibm,persistent-memory", }, {} }; static int pseries_pmem_init(void) { /* * Only supported on POWER8 and above. */ if (!cpu_has_feature(CPU_FTR_ARCH_207S)) return 0; pmem_node = of_find_node_by_type(NULL, "ibm,persistent-memory"); if (!pmem_node) return 0; /* * The generic OF bus probe/populate handles creating platform devices * from the child (ibm,pmemory) nodes. The generic code registers an of * reconfig notifier to handle the hot-add/remove cases too. */ of_platform_bus_probe(pmem_node, drc_pmem_match, NULL); return 0; } machine_arch_initcall(pseries, pseries_pmem_init); |