root/arch/powerpc/platforms/pseries/reconfig.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * pSeries_reconfig.c - support for dynamic reconfiguration (including PCI
 * Hotplug and Dynamic Logical Partitioning on RPA platforms).
 *
 * Copyright (C) 2005 Nathan Lynch
 * Copyright (C) 2005 IBM Corporation
 */

#include <linux/kernel.h>
#include <linux/notifier.h>
#include <linux/proc_fs.h>
#include <linux/security.h>
#include <linux/slab.h>
#include <linux/of.h>

#include <asm/machdep.h>
#include <linux/uaccess.h>
#include <asm/mmu.h>

#include "of_helpers.h"

static int pSeries_reconfig_add_node(const char *path, struct property *proplist)
{
        struct device_node *np;
        int err = -ENOMEM;

        np = kzalloc_obj(*np);
        if (!np)
                goto out_err;

        np->full_name = kstrdup(kbasename(path), GFP_KERNEL);
        if (!np->full_name)
                goto out_err;

        np->properties = proplist;
        of_node_set_flag(np, OF_DYNAMIC);
        of_node_init(np);

        np->parent = pseries_of_derive_parent(path);
        if (IS_ERR(np->parent)) {
                err = PTR_ERR(np->parent);
                goto out_err;
        }

        err = of_attach_node(np);
        if (err) {
                printk(KERN_ERR "Failed to add device node %s\n", path);
                goto out_err;
        }

        of_node_put(np->parent);

        return 0;

out_err:
        if (np) {
                of_node_put(np->parent);
                kfree(np->full_name);
                kfree(np);
        }
        return err;
}

static int pSeries_reconfig_remove_node(struct device_node *np)
{
        struct device_node *parent, *child;

        parent = of_get_parent(np);
        if (!parent)
                return -EINVAL;

        if ((child = of_get_next_child(np, NULL))) {
                of_node_put(child);
                of_node_put(parent);
                return -EBUSY;
        }

        of_detach_node(np);
        of_node_put(parent);
        return 0;
}

/*
 * /proc/powerpc/ofdt - yucky binary interface for adding and removing
 * OF device nodes.  Should be deprecated as soon as we get an
 * in-kernel wrapper for the RTAS ibm,configure-connector call.
 */

static void release_prop_list(const struct property *prop)
{
        struct property *next;
        for (; prop; prop = next) {
                next = prop->next;
                kfree(prop->name);
                kfree(prop->value);
                kfree(prop);
        }

}

/**
 * parse_next_property - process the next property from raw input buffer
 * @buf: input buffer, must be nul-terminated
 * @end: end of the input buffer + 1, for validation
 * @name: return value; set to property name in buf
 * @length: return value; set to length of value
 * @value: return value; set to the property value in buf
 *
 * Note that the caller must make copies of the name and value returned,
 * this function does no allocation or copying of the data.  Return value
 * is set to the next name in buf, or NULL on error.
 */
static char * parse_next_property(char *buf, char *end, char **name, int *length,
                                  unsigned char **value)
{
        char *tmp;

        *name = buf;

        tmp = strchr(buf, ' ');
        if (!tmp) {
                printk(KERN_ERR "property parse failed in %s at line %d\n",
                       __func__, __LINE__);
                return NULL;
        }
        *tmp = '\0';

        if (++tmp >= end) {
                printk(KERN_ERR "property parse failed in %s at line %d\n",
                       __func__, __LINE__);
                return NULL;
        }

        /* now we're on the length */
        *length = -1;
        *length = simple_strtoul(tmp, &tmp, 10);
        if (*length == -1) {
                printk(KERN_ERR "property parse failed in %s at line %d\n",
                       __func__, __LINE__);
                return NULL;
        }
        if (*tmp != ' ' || ++tmp >= end) {
                printk(KERN_ERR "property parse failed in %s at line %d\n",
                       __func__, __LINE__);
                return NULL;
        }

        /* now we're on the value */
        *value = tmp;
        tmp += *length;
        if (tmp > end) {
                printk(KERN_ERR "property parse failed in %s at line %d\n",
                       __func__, __LINE__);
                return NULL;
        }
        else if (tmp < end && *tmp != ' ' && *tmp != '\0') {
                printk(KERN_ERR "property parse failed in %s at line %d\n",
                       __func__, __LINE__);
                return NULL;
        }
        tmp++;

        /* and now we should be on the next name, or the end */
        return tmp;
}

static struct property *new_property(const char *name, const int length,
                                     const unsigned char *value, struct property *last)
{
        struct property *new = kzalloc_obj(*new);

        if (!new)
                return NULL;

        if (!(new->name = kstrdup(name, GFP_KERNEL)))
                goto cleanup;
        if (!(new->value = kmalloc(length + 1, GFP_KERNEL)))
                goto cleanup;

        memcpy(new->value, value, length);
        *(((char *)new->value) + length) = 0;
        new->length = length;
        new->next = last;
        return new;

cleanup:
        kfree(new->name);
        kfree(new->value);
        kfree(new);
        return NULL;
}

static int do_add_node(char *buf, size_t bufsize)
{
        char *path, *end, *name;
        struct device_node *np;
        struct property *prop = NULL;
        unsigned char* value;
        int length, rv = 0;

        end = buf + bufsize;
        path = buf;
        buf = strchr(buf, ' ');
        if (!buf)
                return -EINVAL;
        *buf = '\0';
        buf++;

        if ((np = of_find_node_by_path(path))) {
                of_node_put(np);
                return -EINVAL;
        }

        /* rv = build_prop_list(tmp, bufsize - (tmp - buf), &proplist); */
        while (buf < end &&
               (buf = parse_next_property(buf, end, &name, &length, &value))) {
                struct property *last = prop;

                prop = new_property(name, length, value, last);
                if (!prop) {
                        rv = -ENOMEM;
                        prop = last;
                        goto out;
                }
        }
        if (!buf) {
                rv = -EINVAL;
                goto out;
        }

        rv = pSeries_reconfig_add_node(path, prop);

out:
        if (rv)
                release_prop_list(prop);
        return rv;
}

static int do_remove_node(char *buf)
{
        struct device_node *node;
        int rv = -ENODEV;

        if ((node = of_find_node_by_path(buf)))
                rv = pSeries_reconfig_remove_node(node);

        of_node_put(node);
        return rv;
}

static char *parse_node(char *buf, size_t bufsize, struct device_node **npp)
{
        char *handle_str;
        phandle handle;
        *npp = NULL;

        handle_str = buf;

        buf = strchr(buf, ' ');
        if (!buf)
                return NULL;
        *buf = '\0';
        buf++;

        handle = simple_strtoul(handle_str, NULL, 0);

        *npp = of_find_node_by_phandle(handle);
        return buf;
}

static int do_add_property(char *buf, size_t bufsize)
{
        struct property *prop = NULL;
        struct device_node *np;
        unsigned char *value;
        char *name, *end;
        int length;
        end = buf + bufsize;
        buf = parse_node(buf, bufsize, &np);

        if (!np)
                return -ENODEV;

        if (parse_next_property(buf, end, &name, &length, &value) == NULL)
                return -EINVAL;

        prop = new_property(name, length, value, NULL);
        if (!prop)
                return -ENOMEM;

        of_add_property(np, prop);

        return 0;
}

static int do_remove_property(char *buf, size_t bufsize)
{
        struct device_node *np;
        char *tmp;
        buf = parse_node(buf, bufsize, &np);

        if (!np)
                return -ENODEV;

        tmp = strchr(buf,' ');
        if (tmp)
                *tmp = '\0';

        if (strlen(buf) == 0)
                return -EINVAL;

        return of_remove_property(np, of_find_property(np, buf, NULL));
}

static int do_update_property(char *buf, size_t bufsize)
{
        struct device_node *np;
        unsigned char *value;
        char *name, *end, *next_prop;
        int length;
        struct property *newprop;
        buf = parse_node(buf, bufsize, &np);
        end = buf + bufsize;

        if (!np)
                return -ENODEV;

        next_prop = parse_next_property(buf, end, &name, &length, &value);
        if (!next_prop)
                return -EINVAL;

        if (!strlen(name))
                return -ENODEV;

        newprop = new_property(name, length, value, NULL);
        if (!newprop)
                return -ENOMEM;

        if (!strcmp(name, "slb-size") || !strcmp(name, "ibm,slb-size"))
                slb_set_size(*(int *)value);

        return of_update_property(np, newprop);
}

/**
 * ofdt_write - perform operations on the Open Firmware device tree
 *
 * @file: not used
 * @buf: command and arguments
 * @count: size of the command buffer
 * @off: not used
 *
 * Operations supported at this time are addition and removal of
 * whole nodes along with their properties.  Operations on individual
 * properties are not implemented (yet).
 */
static ssize_t ofdt_write(struct file *file, const char __user *buf, size_t count,
                          loff_t *off)
{
        int rv;
        char *kbuf;
        char *tmp;

        rv = security_locked_down(LOCKDOWN_DEVICE_TREE);
        if (rv)
                return rv;

        kbuf = memdup_user_nul(buf, count);
        if (IS_ERR(kbuf))
                return PTR_ERR(kbuf);

        tmp = strchr(kbuf, ' ');
        if (!tmp) {
                rv = -EINVAL;
                goto out;
        }
        *tmp = '\0';
        tmp++;

        if (!strcmp(kbuf, "add_node"))
                rv = do_add_node(tmp, count - (tmp - kbuf));
        else if (!strcmp(kbuf, "remove_node"))
                rv = do_remove_node(tmp);
        else if (!strcmp(kbuf, "add_property"))
                rv = do_add_property(tmp, count - (tmp - kbuf));
        else if (!strcmp(kbuf, "remove_property"))
                rv = do_remove_property(tmp, count - (tmp - kbuf));
        else if (!strcmp(kbuf, "update_property"))
                rv = do_update_property(tmp, count - (tmp - kbuf));
        else
                rv = -EINVAL;
out:
        kfree(kbuf);
        return rv ? rv : count;
}

static const struct proc_ops ofdt_proc_ops = {
        .proc_write     = ofdt_write,
        .proc_lseek     = noop_llseek,
};

/* create /proc/powerpc/ofdt write-only by root */
static int proc_ppc64_create_ofdt(void)
{
        struct proc_dir_entry *ent;

        ent = proc_create("powerpc/ofdt", 0200, NULL, &ofdt_proc_ops);
        if (ent)
                proc_set_size(ent, 0);

        return 0;
}
machine_device_initcall(pseries, proc_ppc64_create_ofdt);