linux/arch/powerpc/mm/drmem.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/*
   3 * Dynamic reconfiguration memory support
   4 *
   5 * Copyright 2017 IBM Corporation
   6 */
   7
   8#define pr_fmt(fmt) "drmem: " fmt
   9
  10#include <linux/kernel.h>
  11#include <linux/of.h>
  12#include <linux/of_fdt.h>
  13#include <linux/memblock.h>
  14#include <asm/prom.h>
  15#include <asm/drmem.h>
  16
  17static struct drmem_lmb_info __drmem_info;
  18struct drmem_lmb_info *drmem_info = &__drmem_info;
  19
  20u64 drmem_lmb_memory_max(void)
  21{
  22        struct drmem_lmb *last_lmb;
  23
  24        last_lmb = &drmem_info->lmbs[drmem_info->n_lmbs - 1];
  25        return last_lmb->base_addr + drmem_lmb_size();
  26}
  27
  28static u32 drmem_lmb_flags(struct drmem_lmb *lmb)
  29{
  30        /*
  31         * Return the value of the lmb flags field minus the reserved
  32         * bit used internally for hotplug processing.
  33         */
  34        return lmb->flags & ~DRMEM_LMB_RESERVED;
  35}
  36
  37static struct property *clone_property(struct property *prop, u32 prop_sz)
  38{
  39        struct property *new_prop;
  40
  41        new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL);
  42        if (!new_prop)
  43                return NULL;
  44
  45        new_prop->name = kstrdup(prop->name, GFP_KERNEL);
  46        new_prop->value = kzalloc(prop_sz, GFP_KERNEL);
  47        if (!new_prop->name || !new_prop->value) {
  48                kfree(new_prop->name);
  49                kfree(new_prop->value);
  50                kfree(new_prop);
  51                return NULL;
  52        }
  53
  54        new_prop->length = prop_sz;
  55#if defined(CONFIG_OF_DYNAMIC)
  56        of_property_set_flag(new_prop, OF_DYNAMIC);
  57#endif
  58        return new_prop;
  59}
  60
  61static int drmem_update_dt_v1(struct device_node *memory,
  62                              struct property *prop)
  63{
  64        struct property *new_prop;
  65        struct of_drconf_cell_v1 *dr_cell;
  66        struct drmem_lmb *lmb;
  67        u32 *p;
  68
  69        new_prop = clone_property(prop, prop->length);
  70        if (!new_prop)
  71                return -1;
  72
  73        p = new_prop->value;
  74        *p++ = cpu_to_be32(drmem_info->n_lmbs);
  75
  76        dr_cell = (struct of_drconf_cell_v1 *)p;
  77
  78        for_each_drmem_lmb(lmb) {
  79                dr_cell->base_addr = cpu_to_be64(lmb->base_addr);
  80                dr_cell->drc_index = cpu_to_be32(lmb->drc_index);
  81                dr_cell->aa_index = cpu_to_be32(lmb->aa_index);
  82                dr_cell->flags = cpu_to_be32(drmem_lmb_flags(lmb));
  83
  84                dr_cell++;
  85        }
  86
  87        of_update_property(memory, new_prop);
  88        return 0;
  89}
  90
  91static void init_drconf_v2_cell(struct of_drconf_cell_v2 *dr_cell,
  92                                struct drmem_lmb *lmb)
  93{
  94        dr_cell->base_addr = cpu_to_be64(lmb->base_addr);
  95        dr_cell->drc_index = cpu_to_be32(lmb->drc_index);
  96        dr_cell->aa_index = cpu_to_be32(lmb->aa_index);
  97        dr_cell->flags = cpu_to_be32(drmem_lmb_flags(lmb));
  98}
  99
 100static int drmem_update_dt_v2(struct device_node *memory,
 101                              struct property *prop)
 102{
 103        struct property *new_prop;
 104        struct of_drconf_cell_v2 *dr_cell;
 105        struct drmem_lmb *lmb, *prev_lmb;
 106        u32 lmb_sets, prop_sz, seq_lmbs;
 107        u32 *p;
 108
 109        /* First pass, determine how many LMB sets are needed. */
 110        lmb_sets = 0;
 111        prev_lmb = NULL;
 112        for_each_drmem_lmb(lmb) {
 113                if (!prev_lmb) {
 114                        prev_lmb = lmb;
 115                        lmb_sets++;
 116                        continue;
 117                }
 118
 119                if (prev_lmb->aa_index != lmb->aa_index ||
 120                    drmem_lmb_flags(prev_lmb) != drmem_lmb_flags(lmb))
 121                        lmb_sets++;
 122
 123                prev_lmb = lmb;
 124        }
 125
 126        prop_sz = lmb_sets * sizeof(*dr_cell) + sizeof(__be32);
 127        new_prop = clone_property(prop, prop_sz);
 128        if (!new_prop)
 129                return -1;
 130
 131        p = new_prop->value;
 132        *p++ = cpu_to_be32(lmb_sets);
 133
 134        dr_cell = (struct of_drconf_cell_v2 *)p;
 135
 136        /* Second pass, populate the LMB set data */
 137        prev_lmb = NULL;
 138        seq_lmbs = 0;
 139        for_each_drmem_lmb(lmb) {
 140                if (prev_lmb == NULL) {
 141                        /* Start of first LMB set */
 142                        prev_lmb = lmb;
 143                        init_drconf_v2_cell(dr_cell, lmb);
 144                        seq_lmbs++;
 145                        continue;
 146                }
 147
 148                if (prev_lmb->aa_index != lmb->aa_index ||
 149                    drmem_lmb_flags(prev_lmb) != drmem_lmb_flags(lmb)) {
 150                        /* end of one set, start of another */
 151                        dr_cell->seq_lmbs = cpu_to_be32(seq_lmbs);
 152                        dr_cell++;
 153
 154                        init_drconf_v2_cell(dr_cell, lmb);
 155                        seq_lmbs = 1;
 156                } else {
 157                        seq_lmbs++;
 158                }
 159
 160                prev_lmb = lmb;
 161        }
 162
 163        /* close out last LMB set */
 164        dr_cell->seq_lmbs = cpu_to_be32(seq_lmbs);
 165        of_update_property(memory, new_prop);
 166        return 0;
 167}
 168
 169int drmem_update_dt(void)
 170{
 171        struct device_node *memory;
 172        struct property *prop;
 173        int rc = -1;
 174
 175        memory = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");
 176        if (!memory)
 177                return -1;
 178
 179        prop = of_find_property(memory, "ibm,dynamic-memory", NULL);
 180        if (prop) {
 181                rc = drmem_update_dt_v1(memory, prop);
 182        } else {
 183                prop = of_find_property(memory, "ibm,dynamic-memory-v2", NULL);
 184                if (prop)
 185                        rc = drmem_update_dt_v2(memory, prop);
 186        }
 187
 188        of_node_put(memory);
 189        return rc;
 190}
 191
 192static void __init read_drconf_v1_cell(struct drmem_lmb *lmb,
 193                                       const __be32 **prop)
 194{
 195        const __be32 *p = *prop;
 196
 197        lmb->base_addr = dt_mem_next_cell(dt_root_addr_cells, &p);
 198        lmb->drc_index = of_read_number(p++, 1);
 199
 200        p++; /* skip reserved field */
 201
 202        lmb->aa_index = of_read_number(p++, 1);
 203        lmb->flags = of_read_number(p++, 1);
 204
 205        *prop = p;
 206}
 207
 208static void __init __walk_drmem_v1_lmbs(const __be32 *prop, const __be32 *usm,
 209                        void (*func)(struct drmem_lmb *, const __be32 **))
 210{
 211        struct drmem_lmb lmb;
 212        u32 i, n_lmbs;
 213
 214        n_lmbs = of_read_number(prop++, 1);
 215        if (n_lmbs == 0)
 216                return;
 217
 218        for (i = 0; i < n_lmbs; i++) {
 219                read_drconf_v1_cell(&lmb, &prop);
 220                func(&lmb, &usm);
 221        }
 222}
 223
 224static void __init read_drconf_v2_cell(struct of_drconf_cell_v2 *dr_cell,
 225                                       const __be32 **prop)
 226{
 227        const __be32 *p = *prop;
 228
 229        dr_cell->seq_lmbs = of_read_number(p++, 1);
 230        dr_cell->base_addr = dt_mem_next_cell(dt_root_addr_cells, &p);
 231        dr_cell->drc_index = of_read_number(p++, 1);
 232        dr_cell->aa_index = of_read_number(p++, 1);
 233        dr_cell->flags = of_read_number(p++, 1);
 234
 235        *prop = p;
 236}
 237
 238static void __init __walk_drmem_v2_lmbs(const __be32 *prop, const __be32 *usm,
 239                        void (*func)(struct drmem_lmb *, const __be32 **))
 240{
 241        struct of_drconf_cell_v2 dr_cell;
 242        struct drmem_lmb lmb;
 243        u32 i, j, lmb_sets;
 244
 245        lmb_sets = of_read_number(prop++, 1);
 246        if (lmb_sets == 0)
 247                return;
 248
 249        for (i = 0; i < lmb_sets; i++) {
 250                read_drconf_v2_cell(&dr_cell, &prop);
 251
 252                for (j = 0; j < dr_cell.seq_lmbs; j++) {
 253                        lmb.base_addr = dr_cell.base_addr;
 254                        dr_cell.base_addr += drmem_lmb_size();
 255
 256                        lmb.drc_index = dr_cell.drc_index;
 257                        dr_cell.drc_index++;
 258
 259                        lmb.aa_index = dr_cell.aa_index;
 260                        lmb.flags = dr_cell.flags;
 261
 262                        func(&lmb, &usm);
 263                }
 264        }
 265}
 266
 267#ifdef CONFIG_PPC_PSERIES
 268void __init walk_drmem_lmbs_early(unsigned long node,
 269                        void (*func)(struct drmem_lmb *, const __be32 **))
 270{
 271        const __be32 *prop, *usm;
 272        int len;
 273
 274        prop = of_get_flat_dt_prop(node, "ibm,lmb-size", &len);
 275        if (!prop || len < dt_root_size_cells * sizeof(__be32))
 276                return;
 277
 278        drmem_info->lmb_size = dt_mem_next_cell(dt_root_size_cells, &prop);
 279
 280        usm = of_get_flat_dt_prop(node, "linux,drconf-usable-memory", &len);
 281
 282        prop = of_get_flat_dt_prop(node, "ibm,dynamic-memory", &len);
 283        if (prop) {
 284                __walk_drmem_v1_lmbs(prop, usm, func);
 285        } else {
 286                prop = of_get_flat_dt_prop(node, "ibm,dynamic-memory-v2",
 287                                           &len);
 288                if (prop)
 289                        __walk_drmem_v2_lmbs(prop, usm, func);
 290        }
 291
 292        memblock_dump_all();
 293}
 294
 295#endif
 296
 297static int __init init_drmem_lmb_size(struct device_node *dn)
 298{
 299        const __be32 *prop;
 300        int len;
 301
 302        if (drmem_info->lmb_size)
 303                return 0;
 304
 305        prop = of_get_property(dn, "ibm,lmb-size", &len);
 306        if (!prop || len < dt_root_size_cells * sizeof(__be32)) {
 307                pr_info("Could not determine LMB size\n");
 308                return -1;
 309        }
 310
 311        drmem_info->lmb_size = dt_mem_next_cell(dt_root_size_cells, &prop);
 312        return 0;
 313}
 314
 315/*
 316 * Returns the property linux,drconf-usable-memory if
 317 * it exists (the property exists only in kexec/kdump kernels,
 318 * added by kexec-tools)
 319 */
 320static const __be32 *of_get_usable_memory(struct device_node *dn)
 321{
 322        const __be32 *prop;
 323        u32 len;
 324
 325        prop = of_get_property(dn, "linux,drconf-usable-memory", &len);
 326        if (!prop || len < sizeof(unsigned int))
 327                return NULL;
 328
 329        return prop;
 330}
 331
 332void __init walk_drmem_lmbs(struct device_node *dn,
 333                            void (*func)(struct drmem_lmb *, const __be32 **))
 334{
 335        const __be32 *prop, *usm;
 336
 337        if (init_drmem_lmb_size(dn))
 338                return;
 339
 340        usm = of_get_usable_memory(dn);
 341
 342        prop = of_get_property(dn, "ibm,dynamic-memory", NULL);
 343        if (prop) {
 344                __walk_drmem_v1_lmbs(prop, usm, func);
 345        } else {
 346                prop = of_get_property(dn, "ibm,dynamic-memory-v2", NULL);
 347                if (prop)
 348                        __walk_drmem_v2_lmbs(prop, usm, func);
 349        }
 350}
 351
 352static void __init init_drmem_v1_lmbs(const __be32 *prop)
 353{
 354        struct drmem_lmb *lmb;
 355
 356        drmem_info->n_lmbs = of_read_number(prop++, 1);
 357        if (drmem_info->n_lmbs == 0)
 358                return;
 359
 360        drmem_info->lmbs = kcalloc(drmem_info->n_lmbs, sizeof(*lmb),
 361                                   GFP_KERNEL);
 362        if (!drmem_info->lmbs)
 363                return;
 364
 365        for_each_drmem_lmb(lmb) {
 366                read_drconf_v1_cell(lmb, &prop);
 367                lmb_set_nid(lmb);
 368        }
 369}
 370
 371static void __init init_drmem_v2_lmbs(const __be32 *prop)
 372{
 373        struct drmem_lmb *lmb;
 374        struct of_drconf_cell_v2 dr_cell;
 375        const __be32 *p;
 376        u32 i, j, lmb_sets;
 377        int lmb_index;
 378
 379        lmb_sets = of_read_number(prop++, 1);
 380        if (lmb_sets == 0)
 381                return;
 382
 383        /* first pass, calculate the number of LMBs */
 384        p = prop;
 385        for (i = 0; i < lmb_sets; i++) {
 386                read_drconf_v2_cell(&dr_cell, &p);
 387                drmem_info->n_lmbs += dr_cell.seq_lmbs;
 388        }
 389
 390        drmem_info->lmbs = kcalloc(drmem_info->n_lmbs, sizeof(*lmb),
 391                                   GFP_KERNEL);
 392        if (!drmem_info->lmbs)
 393                return;
 394
 395        /* second pass, read in the LMB information */
 396        lmb_index = 0;
 397        p = prop;
 398
 399        for (i = 0; i < lmb_sets; i++) {
 400                read_drconf_v2_cell(&dr_cell, &p);
 401
 402                for (j = 0; j < dr_cell.seq_lmbs; j++) {
 403                        lmb = &drmem_info->lmbs[lmb_index++];
 404
 405                        lmb->base_addr = dr_cell.base_addr;
 406                        dr_cell.base_addr += drmem_info->lmb_size;
 407
 408                        lmb->drc_index = dr_cell.drc_index;
 409                        dr_cell.drc_index++;
 410
 411                        lmb->aa_index = dr_cell.aa_index;
 412                        lmb->flags = dr_cell.flags;
 413
 414                        lmb_set_nid(lmb);
 415                }
 416        }
 417}
 418
 419static int __init drmem_init(void)
 420{
 421        struct device_node *dn;
 422        const __be32 *prop;
 423
 424        dn = of_find_node_by_path("/ibm,dynamic-reconfiguration-memory");
 425        if (!dn) {
 426                pr_info("No dynamic reconfiguration memory found\n");
 427                return 0;
 428        }
 429
 430        if (init_drmem_lmb_size(dn)) {
 431                of_node_put(dn);
 432                return 0;
 433        }
 434
 435        prop = of_get_property(dn, "ibm,dynamic-memory", NULL);
 436        if (prop) {
 437                init_drmem_v1_lmbs(prop);
 438        } else {
 439                prop = of_get_property(dn, "ibm,dynamic-memory-v2", NULL);
 440                if (prop)
 441                        init_drmem_v2_lmbs(prop);
 442        }
 443
 444        of_node_put(dn);
 445        return 0;
 446}
 447late_initcall(drmem_init);
 448