1872 lines
42 KiB
C
1872 lines
42 KiB
C
|
/*
|
||
|
* Copyright (c) 2015-2016, NVIDIA CORPORATION. All rights reserved.
|
||
|
*
|
||
|
* Author: Laxman Dewangan<ldewangan@nvidia.com>
|
||
|
*
|
||
|
* This program is free software; you can redistribute it and/or modify it
|
||
|
* under the terms and conditions of the GNU General Public License,
|
||
|
* version 2, as published by the Free Software Foundation.
|
||
|
*
|
||
|
* This program is distributed in the hope it will be useful, but WITHOUT
|
||
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||
|
* more details.
|
||
|
*/
|
||
|
|
||
|
#include <linux/init.h>
|
||
|
#include <linux/spinlock.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/string.h>
|
||
|
#include <linux/proc_fs.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_fdt.h>
|
||
|
#include "of_private.h"
|
||
|
|
||
|
#define MAXIMUM_FNAME_LENGTH 300
|
||
|
|
||
|
enum plugin_manager_match_type {
|
||
|
PLUGIN_MANAGER_MATCH_EXACT,
|
||
|
PLUGIN_MANAGER_MATCH_PARTIAL,
|
||
|
PLUGIN_MANAGER_MATCH_GE,
|
||
|
PLUGIN_MANAGER_MATCH_LT,
|
||
|
};
|
||
|
|
||
|
struct connection_info {
|
||
|
int level;
|
||
|
const char *uid_str;
|
||
|
struct device_node *node;
|
||
|
struct device_node *org_pm;
|
||
|
struct device_node *copy_node;
|
||
|
struct device_node *parent_conn_node;
|
||
|
struct device_node *child_conn_node;
|
||
|
};
|
||
|
|
||
|
static int link_connection_to_plugin_modules(struct device_node *plmroot,
|
||
|
struct device_node *plcroot,
|
||
|
struct device_node *ds,
|
||
|
struct device_node *connector);
|
||
|
|
||
|
static struct property *__of_copy_property(const struct property *prop,
|
||
|
void *new_value, int val_len,
|
||
|
gfp_t flags)
|
||
|
{
|
||
|
struct property *propn;
|
||
|
int nlen;
|
||
|
void *nval;
|
||
|
|
||
|
propn = kzalloc(sizeof(*propn), flags);
|
||
|
if (!propn)
|
||
|
return NULL;
|
||
|
|
||
|
propn->name = kstrdup(prop->name, flags);
|
||
|
if (!propn->name)
|
||
|
goto err_fail_name;
|
||
|
|
||
|
nlen = (new_value) ? val_len : prop->length;
|
||
|
nval = (new_value) ? new_value : prop->value;
|
||
|
if (nlen > 0) {
|
||
|
propn->value = kzalloc(nlen, flags);
|
||
|
if (!propn->value)
|
||
|
goto err_fail_value;
|
||
|
memcpy(propn->value, nval, nlen);
|
||
|
propn->length = nlen;
|
||
|
}
|
||
|
return propn;
|
||
|
|
||
|
err_fail_value:
|
||
|
kfree(propn->name);
|
||
|
err_fail_name:
|
||
|
kfree(propn);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static struct property *__of_create_property_by_name(const char *name,
|
||
|
void *new_value,
|
||
|
int val_len)
|
||
|
{
|
||
|
struct property *propn;
|
||
|
|
||
|
if (!name)
|
||
|
return NULL;
|
||
|
|
||
|
propn = kzalloc(sizeof(*propn), GFP_KERNEL);
|
||
|
if (!propn)
|
||
|
return NULL;
|
||
|
|
||
|
propn->name = kstrdup(name, GFP_KERNEL);
|
||
|
if (!propn->name)
|
||
|
goto err_fail_name;
|
||
|
|
||
|
if ((val_len > 0) || !new_value) {
|
||
|
propn->value = kzalloc(val_len, GFP_KERNEL);
|
||
|
if (!propn->value)
|
||
|
goto err_fail_value;
|
||
|
memcpy(propn->value, new_value, val_len);
|
||
|
propn->length = val_len;
|
||
|
}
|
||
|
|
||
|
return propn;
|
||
|
|
||
|
err_fail_value:
|
||
|
kfree(propn->name);
|
||
|
err_fail_name:
|
||
|
kfree(propn);
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static void free_property(struct property *pp)
|
||
|
{
|
||
|
if (!pp)
|
||
|
return;
|
||
|
|
||
|
kfree(pp->name);
|
||
|
kfree(pp->value);
|
||
|
kfree(pp);
|
||
|
}
|
||
|
|
||
|
static struct device_node *of_get_child_by_last_name(struct device_node *node,
|
||
|
const char *name)
|
||
|
{
|
||
|
struct device_node *child;
|
||
|
|
||
|
for_each_child_of_node(node, child) {
|
||
|
const char *lname = strrchr(child->full_name, '/');
|
||
|
|
||
|
if (!strcmp(lname + 1, name))
|
||
|
return child;
|
||
|
}
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static struct device_node *of_get_nested_child_by_name(struct device_node *node,
|
||
|
const char *name)
|
||
|
{
|
||
|
struct device_node *cnode = node;
|
||
|
const char *cur_name = name;
|
||
|
char child_name[100];
|
||
|
int nlen;
|
||
|
int len = strlen(name);
|
||
|
|
||
|
if (!len)
|
||
|
return NULL;
|
||
|
|
||
|
while (len) {
|
||
|
nlen = strcspn(cur_name, "/");
|
||
|
if (!nlen)
|
||
|
return NULL;
|
||
|
|
||
|
memcpy(child_name, cur_name, nlen);
|
||
|
child_name[nlen] = '\0';
|
||
|
|
||
|
cnode = of_get_child_by_last_name(cnode, child_name);
|
||
|
if (!cnode)
|
||
|
return NULL;
|
||
|
/* '/' adjustment. */
|
||
|
nlen++;
|
||
|
cur_name += nlen;
|
||
|
if (len <= nlen)
|
||
|
break;
|
||
|
|
||
|
len -= nlen;
|
||
|
}
|
||
|
|
||
|
return cnode;
|
||
|
}
|
||
|
|
||
|
static void of_add_node_to_parent(struct device_node *parent,
|
||
|
struct device_node *child)
|
||
|
{
|
||
|
struct device_node *last_sibling;
|
||
|
|
||
|
child->sibling = NULL;
|
||
|
child->parent = parent;
|
||
|
|
||
|
if (!parent->child) {
|
||
|
parent->child = child;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
last_sibling = parent->child;
|
||
|
while (last_sibling->sibling)
|
||
|
last_sibling = last_sibling->sibling;
|
||
|
last_sibling->sibling = child;
|
||
|
}
|
||
|
|
||
|
static int plugin_module_get_uid(void)
|
||
|
{
|
||
|
static atomic_t pm_uid = ATOMIC_INIT(-1);
|
||
|
|
||
|
return atomic_inc_return(&pm_uid);
|
||
|
}
|
||
|
|
||
|
struct device_node *create_simple_device_node(const char *path,
|
||
|
const char *add_name,
|
||
|
size_t data_size)
|
||
|
{
|
||
|
struct device_node *new_np;
|
||
|
|
||
|
new_np = kzalloc(sizeof(*new_np), GFP_KERNEL);
|
||
|
if (!new_np)
|
||
|
return NULL;
|
||
|
|
||
|
new_np->full_name = kasprintf(GFP_KERNEL, "%s/%s", path, add_name);
|
||
|
new_np->name = kasprintf(GFP_KERNEL, "%s", add_name);
|
||
|
|
||
|
if (data_size) {
|
||
|
new_np->data = kzalloc(data_size, GFP_KERNEL);
|
||
|
if (!new_np->data)
|
||
|
goto clean;
|
||
|
}
|
||
|
|
||
|
return new_np;
|
||
|
|
||
|
clean:
|
||
|
kfree(new_np->full_name);
|
||
|
kfree(new_np->name);
|
||
|
kfree(new_np);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static void free_simple_device_node(struct device_node *np)
|
||
|
{
|
||
|
if (!np)
|
||
|
return;
|
||
|
|
||
|
kfree(np->full_name);
|
||
|
kfree(np->name);
|
||
|
kfree(np->data);
|
||
|
kfree(np);
|
||
|
}
|
||
|
|
||
|
struct device_node *duplicate_single_node(struct device_node *np,
|
||
|
const char *base_dir,
|
||
|
const char *path,
|
||
|
const char *new_name)
|
||
|
{
|
||
|
struct device_node *dup;
|
||
|
struct property *pp, *new_pp;
|
||
|
int ret;
|
||
|
const char *add_name;
|
||
|
char fname[MAXIMUM_FNAME_LENGTH + 1] = {};
|
||
|
|
||
|
dup = kzalloc(sizeof(*dup), GFP_KERNEL);
|
||
|
if (!dup)
|
||
|
return NULL;
|
||
|
|
||
|
if (new_name) {
|
||
|
add_name = new_name;
|
||
|
} else {
|
||
|
add_name = strrchr(np->full_name, '/');
|
||
|
add_name++;
|
||
|
}
|
||
|
|
||
|
if (path) {
|
||
|
strncpy(fname, path, MAXIMUM_FNAME_LENGTH);
|
||
|
} else {
|
||
|
const char *lname = strrchr(np->full_name, '/');
|
||
|
int llen = strlen(np->full_name) - strlen(lname);
|
||
|
|
||
|
strncpy(fname, np->full_name, MAXIMUM_FNAME_LENGTH);
|
||
|
fname[llen] = '\0';
|
||
|
}
|
||
|
|
||
|
if (base_dir)
|
||
|
dup->full_name = kasprintf(GFP_KERNEL, "%s%s/%s",
|
||
|
base_dir, fname, add_name);
|
||
|
else
|
||
|
dup->full_name = kasprintf(GFP_KERNEL, "%s/%s",
|
||
|
fname, add_name);
|
||
|
|
||
|
of_node_init(dup);
|
||
|
|
||
|
for_each_property_of_node(np, pp) {
|
||
|
if (!strcmp(pp->name, "name"))
|
||
|
new_pp = __of_copy_property(pp, (void *)add_name,
|
||
|
strlen(add_name),
|
||
|
GFP_KERNEL);
|
||
|
else
|
||
|
new_pp = __of_copy_property(pp, NULL, 0, GFP_KERNEL);
|
||
|
if (!new_pp) {
|
||
|
kfree(dup->full_name);
|
||
|
kfree(dup);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
ret = of_add_property(dup, new_pp);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Prop %s can not be added on node %s\n",
|
||
|
new_pp->name, dup->full_name);
|
||
|
free_property(new_pp);
|
||
|
kfree(dup->full_name);
|
||
|
kfree(dup);
|
||
|
return NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
dup->name = __of_get_property(dup, "name", NULL) ? : "<NULL>";
|
||
|
dup->type = __of_get_property(dup, "device_type", NULL) ? : "<NULL>";
|
||
|
|
||
|
return dup;
|
||
|
}
|
||
|
|
||
|
struct device_node *get_copy_of_node(struct device_node *np,
|
||
|
const char *base_dir,
|
||
|
const char *path, const char *new_name)
|
||
|
{
|
||
|
struct device_node *dup;
|
||
|
struct device_node *child, *child_dup;
|
||
|
struct device_node *prev_child = NULL;
|
||
|
|
||
|
dup = duplicate_single_node(np, base_dir, path, new_name);
|
||
|
if (!dup)
|
||
|
return NULL;
|
||
|
|
||
|
for_each_child_of_node(np, child) {
|
||
|
child_dup = get_copy_of_node(child, NULL, dup->full_name, NULL);
|
||
|
if (!child_dup) {
|
||
|
kfree(dup);
|
||
|
return NULL;
|
||
|
}
|
||
|
child_dup->parent = dup;
|
||
|
child_dup->sibling = NULL;
|
||
|
if (!prev_child)
|
||
|
dup->child = child_dup;
|
||
|
else
|
||
|
prev_child->sibling = child_dup;
|
||
|
prev_child = child_dup;
|
||
|
}
|
||
|
|
||
|
return dup;
|
||
|
}
|
||
|
|
||
|
static struct device_node *add_module_connection(struct device_node *parent,
|
||
|
struct device_node *pm_node,
|
||
|
const char *child_name)
|
||
|
{
|
||
|
struct device_node *child;
|
||
|
struct connection_info *cinfo;
|
||
|
|
||
|
child = create_simple_device_node(parent->full_name, child_name,
|
||
|
sizeof(*cinfo));
|
||
|
if (!child) {
|
||
|
pr_info("Can not create device node %s\n", child_name);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
cinfo = child->data;
|
||
|
cinfo->org_pm = pm_node;
|
||
|
cinfo->node = child;
|
||
|
of_add_node_to_parent(parent, child);
|
||
|
|
||
|
return child;
|
||
|
}
|
||
|
|
||
|
static int of_get_next_phandle(void)
|
||
|
{
|
||
|
static phandle curr_handle;
|
||
|
static bool first_time = true;
|
||
|
struct device_node *np;
|
||
|
phandle next_handle;
|
||
|
unsigned long flags;
|
||
|
|
||
|
raw_spin_lock_irqsave(&devtree_lock, flags);
|
||
|
|
||
|
if (first_time) {
|
||
|
for_each_of_allnodes(np) {
|
||
|
if (np->phandle > curr_handle)
|
||
|
curr_handle = np->phandle;
|
||
|
}
|
||
|
first_time = false;
|
||
|
}
|
||
|
|
||
|
next_handle = curr_handle++;
|
||
|
raw_spin_unlock_irqrestore(&devtree_lock, flags);
|
||
|
|
||
|
return next_handle;
|
||
|
}
|
||
|
|
||
|
static struct property *__of_string_append(struct device_node *target,
|
||
|
struct property *prop)
|
||
|
{
|
||
|
struct property *new_prop, *tprop;
|
||
|
const char *tprop_name, *curr_str;
|
||
|
int slen, tlen, lenp;
|
||
|
|
||
|
tprop_name = of_prop_next_string(prop, NULL);
|
||
|
if (!tprop_name)
|
||
|
return NULL;
|
||
|
|
||
|
new_prop = kzalloc(sizeof(*new_prop), GFP_KERNEL);
|
||
|
if (!new_prop)
|
||
|
return NULL;
|
||
|
|
||
|
new_prop->name = kstrdup(tprop_name, GFP_KERNEL);
|
||
|
if (!new_prop->name)
|
||
|
goto err_fail_name;
|
||
|
|
||
|
curr_str = of_prop_next_string(prop, tprop_name);
|
||
|
for (slen = 0; curr_str; curr_str = of_prop_next_string(prop, curr_str))
|
||
|
slen += strlen(curr_str);
|
||
|
|
||
|
tprop = of_find_property(target, tprop_name, &lenp);
|
||
|
tlen = (tprop) ? tprop->length : 0;
|
||
|
|
||
|
new_prop->value = kmalloc(slen + tlen, GFP_KERNEL);
|
||
|
if (!new_prop->value)
|
||
|
goto err_fail_value;
|
||
|
|
||
|
if (tlen)
|
||
|
memcpy(new_prop->value, tprop->value, tlen);
|
||
|
|
||
|
if (slen) {
|
||
|
curr_str = of_prop_next_string(prop, tprop_name);
|
||
|
memcpy(new_prop->value + tlen, curr_str, slen);
|
||
|
}
|
||
|
|
||
|
new_prop->length = slen + tlen;
|
||
|
|
||
|
return new_prop;
|
||
|
|
||
|
err_fail_value:
|
||
|
kfree(new_prop->name);
|
||
|
err_fail_name:
|
||
|
kfree(new_prop);
|
||
|
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
static int do_property_override_from_overlay(struct device_node *target,
|
||
|
struct device_node *overlay)
|
||
|
{
|
||
|
struct property *prop;
|
||
|
struct property *tprop;
|
||
|
struct property *new_prop;
|
||
|
const char *pval;
|
||
|
int lenp = 0;
|
||
|
int ret;
|
||
|
|
||
|
pr_debug("Update properties from %s to %s\n", overlay->full_name,
|
||
|
target->full_name);
|
||
|
|
||
|
for_each_property_of_node(overlay, prop) {
|
||
|
/* Skip those we do not want to proceed */
|
||
|
if (!strcmp(prop->name, "name") ||
|
||
|
!strcmp(prop->name, "phandle") ||
|
||
|
!strcmp(prop->name, "linux,phandle"))
|
||
|
continue;
|
||
|
if (!strcmp(prop->name, "delete-target-property")) {
|
||
|
if (prop->length <= 0)
|
||
|
continue;
|
||
|
pval = (const char *)prop->value;
|
||
|
pr_info("Removing Prop %s from target %s\n",
|
||
|
pval, target->full_name);
|
||
|
tprop = of_find_property(target, pval, &lenp);
|
||
|
if (tprop)
|
||
|
of_remove_property(target, tprop);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (!strcmp(prop->name, "append-string-property")) {
|
||
|
if (prop->length <= 0)
|
||
|
continue;
|
||
|
|
||
|
new_prop = __of_string_append(target, prop);
|
||
|
if (!new_prop) {
|
||
|
pr_err("Prop %s can not be appended\n",
|
||
|
of_prop_next_string(prop, NULL));
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
goto add_prop;
|
||
|
}
|
||
|
|
||
|
new_prop = __of_copy_property(prop, NULL, 0, GFP_KERNEL);
|
||
|
if (!new_prop) {
|
||
|
pr_err("Prop %s can not be duplicated\n",
|
||
|
prop->name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
add_prop:
|
||
|
tprop = of_find_property(target, new_prop->name, &lenp);
|
||
|
if (!tprop) {
|
||
|
ret = of_add_property(target, new_prop);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Prop %s can not be added on node %s\n",
|
||
|
new_prop->name, target->full_name);
|
||
|
goto cleanup;
|
||
|
}
|
||
|
} else {
|
||
|
ret = of_update_property(target, new_prop);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Prop %s can not be updated on node %s\n",
|
||
|
new_prop->name, target->full_name);
|
||
|
goto cleanup;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
|
||
|
cleanup:
|
||
|
free_property(new_prop);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static int plugin_manager_get_fabid(const char *id_str)
|
||
|
{
|
||
|
int fabid = 0;
|
||
|
int id;
|
||
|
int i;
|
||
|
|
||
|
if (strlen(id_str) < 13)
|
||
|
return -EINVAL;
|
||
|
|
||
|
for (i = 0; i < 3; ++i) {
|
||
|
id = id_str[10 + i];
|
||
|
switch (id) {
|
||
|
case 48 ... 57: /* 0 to 9 */
|
||
|
id = id - 48;
|
||
|
break;
|
||
|
case 65 ... 90: /* A to Z */
|
||
|
id = id - 65 + 10;
|
||
|
break;
|
||
|
case 97 ... 122: /* a to z */
|
||
|
id = id - 97 + 10;
|
||
|
break;
|
||
|
default:
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Make digit position to 100x to avoid carry */
|
||
|
fabid = fabid * 100 + id;
|
||
|
}
|
||
|
|
||
|
return fabid;
|
||
|
}
|
||
|
|
||
|
static bool plugin_manager_match_id(struct device_node *np, const char *id_name)
|
||
|
{
|
||
|
struct property *prop;
|
||
|
const char *in_str = id_name;
|
||
|
int match_type = PLUGIN_MANAGER_MATCH_EXACT;
|
||
|
int valid_str_len = strlen(id_name);
|
||
|
int fabid = 0, prop_fabid;
|
||
|
int i;
|
||
|
|
||
|
if ((valid_str_len > 2) && (in_str[0] == '>') && (in_str[1] == '=')) {
|
||
|
in_str += 2;
|
||
|
valid_str_len -= 2;
|
||
|
match_type = PLUGIN_MANAGER_MATCH_GE;
|
||
|
goto match_type_done;
|
||
|
}
|
||
|
|
||
|
if ((valid_str_len > 1) && (in_str[0] == '<')) {
|
||
|
in_str += 1;
|
||
|
valid_str_len -= 1;
|
||
|
match_type = PLUGIN_MANAGER_MATCH_LT;
|
||
|
goto match_type_done;
|
||
|
}
|
||
|
|
||
|
if ((valid_str_len > 1) && (in_str[0] == '^')) {
|
||
|
in_str += 1;
|
||
|
valid_str_len -= 1;
|
||
|
match_type = PLUGIN_MANAGER_MATCH_PARTIAL;
|
||
|
goto match_type_done;
|
||
|
}
|
||
|
|
||
|
for (i = 0; i < valid_str_len; ++i) {
|
||
|
if (in_str[i] == '*') {
|
||
|
valid_str_len = i;
|
||
|
match_type = PLUGIN_MANAGER_MATCH_PARTIAL;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
match_type_done:
|
||
|
if ((match_type == PLUGIN_MANAGER_MATCH_GE) ||
|
||
|
(match_type == PLUGIN_MANAGER_MATCH_LT)) {
|
||
|
fabid = plugin_manager_get_fabid(in_str);
|
||
|
if (fabid < 0)
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for_each_property_of_node(np, prop) {
|
||
|
/* Skip those we do not want to proceed */
|
||
|
if (!strcmp(prop->name, "name") ||
|
||
|
!strcmp(prop->name, "phandle") ||
|
||
|
!strcmp(prop->name, "linux,phandle"))
|
||
|
continue;
|
||
|
switch (match_type) {
|
||
|
case PLUGIN_MANAGER_MATCH_EXACT:
|
||
|
if (strlen(prop->name) != valid_str_len)
|
||
|
break;
|
||
|
if (!memcmp(in_str, prop->name, valid_str_len))
|
||
|
return true;
|
||
|
break;
|
||
|
|
||
|
case PLUGIN_MANAGER_MATCH_PARTIAL:
|
||
|
if (strlen(prop->name) < valid_str_len)
|
||
|
break;
|
||
|
if (!memcmp(in_str, prop->name, valid_str_len))
|
||
|
return true;
|
||
|
break;
|
||
|
|
||
|
case PLUGIN_MANAGER_MATCH_GE:
|
||
|
case PLUGIN_MANAGER_MATCH_LT:
|
||
|
if (strlen(prop->name) < 13)
|
||
|
break;
|
||
|
if (memcmp(in_str, prop->name, 10))
|
||
|
break;
|
||
|
prop_fabid = plugin_manager_get_fabid(prop->name);
|
||
|
if (prop_fabid < 0)
|
||
|
break;
|
||
|
if (prop_fabid >= fabid &&
|
||
|
match_type == PLUGIN_MANAGER_MATCH_GE)
|
||
|
return true;
|
||
|
if (prop_fabid < fabid &&
|
||
|
match_type == PLUGIN_MANAGER_MATCH_LT)
|
||
|
return true;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static int do_property_overrides(struct device_node *target,
|
||
|
struct device_node *overlay)
|
||
|
{
|
||
|
struct device_node *tchild, *ochild;
|
||
|
const char *address_name;
|
||
|
int ret;
|
||
|
|
||
|
ret = do_property_override_from_overlay(target, overlay);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Target %s update with overlay %s failed: %d\n",
|
||
|
target->name, overlay->name, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
for_each_child_of_node(overlay, ochild) {
|
||
|
address_name = strrchr(ochild->full_name, '/');
|
||
|
tchild = of_get_child_by_last_name(target, address_name + 1);
|
||
|
if (!tchild) {
|
||
|
pr_err("Overlay node %s not found in target node %s\n",
|
||
|
ochild->full_name, target->full_name);
|
||
|
continue;
|
||
|
}
|
||
|
ret = do_property_overrides(tchild, ochild);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Target %s update with overlay %s failed: %d\n",
|
||
|
tchild->name, ochild->name, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int handle_properties_overrides(struct device_node *np,
|
||
|
struct device_node *target)
|
||
|
{
|
||
|
struct device_node *overlay;
|
||
|
int ret;
|
||
|
|
||
|
if (!target) {
|
||
|
target = of_parse_phandle(np, "target", 0);
|
||
|
if (!target) {
|
||
|
pr_err("Node %s does not have targer node\n",
|
||
|
np->name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
overlay = of_get_child_by_name(np, "_overlay_");
|
||
|
if (!overlay) {
|
||
|
pr_err("Node %s does not have Overlay\n", np->name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = do_property_overrides(target, overlay);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Target %s update with overlay %s failed: %d\n",
|
||
|
target->name, overlay->name, ret);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int __init plugin_manager(struct device_node *np)
|
||
|
{
|
||
|
struct device_node *board_np, *nct_np, *odm_np, *cnp;
|
||
|
struct device_node *config_np, *chip_np;
|
||
|
const char *bname;
|
||
|
struct property *prop;
|
||
|
int board_count;
|
||
|
int odm_count, nct_count, chip_id_count;
|
||
|
int cname_count, cval_count;
|
||
|
int nchild;
|
||
|
bool found = false;
|
||
|
bool override_on_all_match;
|
||
|
int ret;
|
||
|
|
||
|
override_on_all_match = of_property_read_bool(np,
|
||
|
"enable-override-on-all-matches");
|
||
|
|
||
|
cname_count = of_property_count_strings(np, "config-names");
|
||
|
cval_count = of_property_count_u32_elems(np, "configs");
|
||
|
if (cname_count != cval_count) {
|
||
|
pr_err("Node %s does not have config-names and configs\n",
|
||
|
np->name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
board_count = of_property_count_strings(np, "ids");
|
||
|
odm_count = of_property_count_strings(np, "odm-data");
|
||
|
nct_count = of_property_count_strings(np, "nct-data");
|
||
|
chip_id_count = of_property_count_strings(np, "chip-id");
|
||
|
if ((board_count <= 0) && (odm_count <= 0) && (cname_count <= 0) &&
|
||
|
(nct_count <= 0) && (chip_id_count <= 0)) {
|
||
|
pr_err("Node %s does not have property ids, nct and odm data\n",
|
||
|
np->name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
nchild = of_get_child_count(np);
|
||
|
if (!nchild) {
|
||
|
pr_err("Node %s does not have Overlay child\n", np->name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
/* Match the IDs or odm data */
|
||
|
board_np = of_find_node_by_path("/chosen/plugin-manager/ids");
|
||
|
odm_np = of_find_node_by_path("/chosen/plugin-manager/odm-data");
|
||
|
nct_np = of_find_node_by_path("/chosen/plugin-manager/nct-data");
|
||
|
chip_np = of_find_node_by_path("/chosen/plugin-manager/chip-id");
|
||
|
config_np = of_find_node_by_path("/chosen/plugin-manager/configs");
|
||
|
if (!board_np && !odm_np && !config_np && !nct_np && !chip_np) {
|
||
|
pr_err("chosen/plugin-manager does'nt have ids, nct and odm-data\n");
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if ((board_count > 0) && board_np) {
|
||
|
of_property_for_each_string(np, "ids", prop, bname) {
|
||
|
found = plugin_manager_match_id(board_np, bname);
|
||
|
if (found) {
|
||
|
pr_info("node %s match with board %s\n",
|
||
|
np->full_name, bname);
|
||
|
if (override_on_all_match)
|
||
|
break;
|
||
|
goto search_done;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (override_on_all_match && !found)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ((odm_count > 0) && odm_np) {
|
||
|
bool is_anded_odm_overrides;
|
||
|
|
||
|
is_anded_odm_overrides = of_property_read_bool(np,
|
||
|
"odm-anded-override");
|
||
|
|
||
|
of_property_for_each_string(np, "odm-data", prop, bname) {
|
||
|
found = of_property_read_bool(odm_np, bname);
|
||
|
if (found) {
|
||
|
pr_info("node %s match with odm-data %s\n",
|
||
|
np->full_name, bname);
|
||
|
|
||
|
if (is_anded_odm_overrides)
|
||
|
continue;
|
||
|
|
||
|
if (override_on_all_match)
|
||
|
break;
|
||
|
goto search_done;
|
||
|
} else {
|
||
|
if (is_anded_odm_overrides)
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (override_on_all_match && !found)
|
||
|
return 0;
|
||
|
|
||
|
if (!override_on_all_match)
|
||
|
goto search_done;
|
||
|
}
|
||
|
|
||
|
if ((nct_count > 0) && nct_np) {
|
||
|
of_property_for_each_string(np, "nct-data", prop, bname) {
|
||
|
found = of_property_read_bool(nct_np, bname);
|
||
|
if (found) {
|
||
|
pr_info("node %s match with nct-data %s\n",
|
||
|
np->full_name, bname);
|
||
|
if (override_on_all_match)
|
||
|
break;
|
||
|
goto search_done;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (override_on_all_match && !found)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ((chip_id_count > 0) && chip_np) {
|
||
|
of_property_for_each_string(np, "chip-id", prop, bname) {
|
||
|
found = of_property_read_bool(chip_np, bname);
|
||
|
if (found) {
|
||
|
pr_info("node %s match with chip-id %s\n",
|
||
|
np->full_name, bname);
|
||
|
if (override_on_all_match)
|
||
|
break;
|
||
|
goto search_done;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (override_on_all_match && !found)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if ((cname_count > 0) && config_np) {
|
||
|
int index = 0;
|
||
|
u32 pval = 0, pmv = 0, mask, value;
|
||
|
|
||
|
of_property_for_each_string(np, "config-names", prop, bname) {
|
||
|
ret = of_property_read_u32_index(np, "configs",
|
||
|
index, &pmv);
|
||
|
if (ret < 0) {
|
||
|
pr_info("node %s do not have proper configs\n",
|
||
|
np->name);
|
||
|
return ret;
|
||
|
}
|
||
|
index++;
|
||
|
ret = of_property_read_u32(config_np, bname, &pval);
|
||
|
if (ret < 0)
|
||
|
continue;
|
||
|
|
||
|
mask = (pmv >> 8) & 0xFF;
|
||
|
value = pmv & 0xFF;
|
||
|
pval &= 0xFF;
|
||
|
found = ((pval & mask) == value);
|
||
|
if (found) {
|
||
|
pr_info("node %s match with config %s\n",
|
||
|
np->full_name, bname);
|
||
|
if (override_on_all_match)
|
||
|
break;
|
||
|
goto search_done;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (override_on_all_match && !found)
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
search_done:
|
||
|
if (!found)
|
||
|
return 0;
|
||
|
|
||
|
for_each_child_of_node(np, cnp)
|
||
|
handle_properties_overrides(cnp, NULL);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int get_node_address(const struct device_node *np)
|
||
|
{
|
||
|
char *name = strrchr(np->full_name, '@');
|
||
|
int addr = 0;
|
||
|
|
||
|
if (!name)
|
||
|
return 0;
|
||
|
|
||
|
name++;
|
||
|
if (*name == '0')
|
||
|
addr = memparse(name, &name);
|
||
|
|
||
|
return addr;
|
||
|
}
|
||
|
|
||
|
static struct device_node *plugin_module_get_node_by_path(
|
||
|
struct device_node *pm_node, const char *rpath)
|
||
|
{
|
||
|
struct connection_info *cinfo = pm_node->data;
|
||
|
const char *end_str, *pdev_str, *dev_path;
|
||
|
struct device_node *cnode = pm_node;
|
||
|
char child_name[100];
|
||
|
char *nfpath;
|
||
|
int nlen, ret;
|
||
|
bool uuid;
|
||
|
|
||
|
if (!((rpath[0] == '.' && rpath[1] == '/')))
|
||
|
return NULL;
|
||
|
|
||
|
pdev_str = rpath + 2;
|
||
|
end_str = pdev_str + strlen(pdev_str);
|
||
|
|
||
|
while (pdev_str < end_str) {
|
||
|
uuid = false;
|
||
|
nlen = strcspn(pdev_str, "/");
|
||
|
if (!nlen)
|
||
|
return NULL;
|
||
|
|
||
|
if (of_property_read_bool(cnode, "make-unique-node-name")) {
|
||
|
snprintf(child_name, 100, "%s_%s",
|
||
|
cinfo->uid_str, pdev_str);
|
||
|
child_name[strlen(cinfo->uid_str) + nlen + 1] = '\0';
|
||
|
uuid = true;
|
||
|
} else {
|
||
|
memcpy(child_name, pdev_str, nlen);
|
||
|
child_name[nlen] = '\0';
|
||
|
}
|
||
|
|
||
|
pdev_str += nlen + 1;
|
||
|
|
||
|
cnode = of_get_child_by_name(cnode, child_name);
|
||
|
if (!cnode)
|
||
|
break;
|
||
|
|
||
|
if (!of_property_read_bool(cnode, "PM-RELOCATED"))
|
||
|
continue;
|
||
|
|
||
|
if (of_property_read_bool(cnode, "make-unique-node-name"))
|
||
|
uuid = true;
|
||
|
|
||
|
ret = of_property_read_string(cnode, "device-path", &dev_path);
|
||
|
if (ret < 0)
|
||
|
return NULL;
|
||
|
|
||
|
if (uuid)
|
||
|
nfpath = kasprintf(GFP_KERNEL, "%s/%s_%s",
|
||
|
dev_path, cinfo->uid_str, pdev_str);
|
||
|
else
|
||
|
nfpath = kasprintf(GFP_KERNEL, "%s/%s",
|
||
|
dev_path, pdev_str);
|
||
|
if (!nfpath)
|
||
|
return NULL;
|
||
|
|
||
|
cnode = of_find_node_by_path(nfpath);
|
||
|
kfree(nfpath);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return cnode;
|
||
|
}
|
||
|
|
||
|
static struct property *plugin_module_get_property_by_path_name(
|
||
|
struct device_node *pm_node, const char *pname)
|
||
|
{
|
||
|
struct property *prop = NULL;
|
||
|
struct device_node *pnode;
|
||
|
char *path, *prop_name;
|
||
|
int len;
|
||
|
|
||
|
path = kasprintf(GFP_KERNEL, "%s", pname);
|
||
|
if (!path)
|
||
|
return NULL;
|
||
|
|
||
|
prop_name = strrchr(path, '/');
|
||
|
len = strlen(path) - strlen(prop_name);
|
||
|
path[len] = '\0';
|
||
|
prop_name++;
|
||
|
|
||
|
pnode = plugin_module_get_node_by_path(pm_node, path);
|
||
|
if (!pnode)
|
||
|
goto end;
|
||
|
|
||
|
prop = of_find_property(pnode, prop_name, NULL);
|
||
|
end:
|
||
|
kfree(path);
|
||
|
return prop;
|
||
|
}
|
||
|
|
||
|
static struct property *plugin_module_create_property_to_path(
|
||
|
struct device_node *pm_node, struct property *ref_prop,
|
||
|
const char *pname)
|
||
|
{
|
||
|
struct property *prop = NULL;
|
||
|
struct device_node *pnode;
|
||
|
char *path, *prop_name;
|
||
|
void *prop_value = (ref_prop) ? ref_prop->value : NULL;
|
||
|
int prop_len = (ref_prop) ? ref_prop->length : 0;
|
||
|
int len;
|
||
|
int ret;
|
||
|
|
||
|
path = kasprintf(GFP_KERNEL, "%s", pname);
|
||
|
if (!path)
|
||
|
return NULL;
|
||
|
|
||
|
prop_name = strrchr(path, '/');
|
||
|
len = strlen(path) - strlen(prop_name);
|
||
|
path[len] = '\0';
|
||
|
prop_name++;
|
||
|
|
||
|
pnode = plugin_module_get_node_by_path(pm_node, path);
|
||
|
if (!pnode)
|
||
|
goto end;
|
||
|
|
||
|
prop = __of_create_property_by_name(prop_name, prop_value, prop_len);
|
||
|
if (!prop)
|
||
|
goto end;
|
||
|
|
||
|
ret = of_add_property(pnode, prop);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Prop %s can not be added on node %s\n",
|
||
|
prop->name, pnode->full_name);
|
||
|
free_property(prop);
|
||
|
prop = NULL;
|
||
|
}
|
||
|
|
||
|
end:
|
||
|
kfree(path);
|
||
|
return prop;
|
||
|
}
|
||
|
|
||
|
static void plugin_module_resolve_uid(struct device_node *cnp)
|
||
|
{
|
||
|
struct connection_info *cinfo = cnp->data;
|
||
|
struct device_node *np = cinfo->copy_node;
|
||
|
struct device_node *funcs, *sub_funcs, *child;
|
||
|
struct property *name_prop;
|
||
|
char *lname, *nname, *fname, *nfname;
|
||
|
const void *ovalue;
|
||
|
int len;
|
||
|
int uid;
|
||
|
|
||
|
funcs = of_get_child_by_name(np, "functions");
|
||
|
if (!funcs)
|
||
|
return;
|
||
|
|
||
|
uid = plugin_module_get_uid();
|
||
|
cinfo->uid_str = kasprintf(GFP_KERNEL, "PM%03d", uid);
|
||
|
if (!cinfo->uid_str)
|
||
|
return;
|
||
|
|
||
|
fname = kzalloc(MAXIMUM_FNAME_LENGTH + 1, GFP_KERNEL);
|
||
|
if (!fname)
|
||
|
return;
|
||
|
|
||
|
for_each_child_of_node(funcs, sub_funcs) {
|
||
|
if (!of_property_read_bool(sub_funcs, "make-unique-node-name"))
|
||
|
continue;
|
||
|
|
||
|
for_each_child_of_node(sub_funcs, child) {
|
||
|
ovalue = child->full_name;
|
||
|
snprintf(fname, MAXIMUM_FNAME_LENGTH, "%s",
|
||
|
child->full_name);
|
||
|
lname = strrchr(fname, '/');
|
||
|
len = strlen(fname) - strlen(lname);
|
||
|
fname[len] = '\0';
|
||
|
nfname = kasprintf(GFP_KERNEL, "%s/%s_%s",
|
||
|
fname, cinfo->uid_str, lname + 1);
|
||
|
if (!nfname) {
|
||
|
pr_err("Failed to resolve full name\n");
|
||
|
continue;
|
||
|
}
|
||
|
child->full_name = nfname;
|
||
|
kfree(ovalue);
|
||
|
|
||
|
name_prop = of_find_property(child, "name", NULL);
|
||
|
if (!name_prop) {
|
||
|
child->name = "<NULL>";
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ovalue = name_prop->value;
|
||
|
len = strlen(cinfo->uid_str) + strlen(child->name) + 2;
|
||
|
nname = kzalloc(len, GFP_KERNEL);
|
||
|
if (!nname) {
|
||
|
pr_err("Failed to create UID name\n");
|
||
|
continue;
|
||
|
}
|
||
|
snprintf(nname, len, "%s_%s",
|
||
|
cinfo->uid_str, child->name);
|
||
|
|
||
|
name_prop->value = nname;
|
||
|
name_prop->length = len;
|
||
|
kfree(ovalue);
|
||
|
|
||
|
child->name = nname;
|
||
|
pr_debug("Resolve the unique ID of node %s\n",
|
||
|
child->full_name);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
kfree(fname);
|
||
|
}
|
||
|
|
||
|
static int create_dup_nodes_for_connected_plm(struct device_node *cnp)
|
||
|
{
|
||
|
struct connection_info *cinfo = cnp->data;
|
||
|
struct device_node *child;
|
||
|
struct device_node *np = NULL;
|
||
|
int ret;
|
||
|
|
||
|
if (cinfo)
|
||
|
np = cinfo->org_pm;
|
||
|
|
||
|
if (np) {
|
||
|
cinfo->copy_node = get_copy_of_node(np, NULL, NULL, NULL);
|
||
|
if (!cinfo->copy_node) {
|
||
|
pr_err("Failed to copy node %s\n", np->full_name);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
/* Resolve the UID */
|
||
|
plugin_module_resolve_uid(cnp);
|
||
|
}
|
||
|
|
||
|
for_each_child_of_node(cnp, child) {
|
||
|
ret = create_dup_nodes_for_connected_plm(child);
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int link_module_node_to_connector_node(struct device_node *cnp)
|
||
|
{
|
||
|
struct connection_info *cinfo = cnp->data;
|
||
|
struct device_node *connector, *module;
|
||
|
struct device_node *ds, *us, *ds_con;
|
||
|
struct connection_info *cdata, *mdata;
|
||
|
|
||
|
if (!cinfo->copy_node)
|
||
|
return 0;
|
||
|
|
||
|
ds = of_get_child_by_name(cinfo->copy_node, "downstream");
|
||
|
if (!ds) {
|
||
|
pr_debug("There is no do downstream connectors: %s\n",
|
||
|
cinfo->copy_node->full_name);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
for_each_child_of_node(cnp, connector) {
|
||
|
ds_con = of_get_child_by_name(ds, connector->name);
|
||
|
if (!ds_con) {
|
||
|
pr_err("Node %s does not have connector %s\n",
|
||
|
ds->full_name, connector->name);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
module = connector->child;
|
||
|
mdata = module->data;
|
||
|
cdata = connector->data;
|
||
|
if (!mdata->copy_node) {
|
||
|
pr_debug("Module %s does not have plug-in node\n",
|
||
|
module->name);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
us = of_get_child_by_name(mdata->copy_node, "upstream");
|
||
|
if (!us) {
|
||
|
pr_err("Module %s does not have upstream node\n",
|
||
|
module->name);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
mdata->parent_conn_node = ds_con;
|
||
|
cdata->child_conn_node = us;
|
||
|
|
||
|
link_module_node_to_connector_node(module);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void update_new_path_node_and_children(struct device_node *np,
|
||
|
const char *new_path)
|
||
|
{
|
||
|
struct device_node *child;
|
||
|
const char *new_fname, *lname;
|
||
|
|
||
|
lname = strrchr(np->full_name, '/');
|
||
|
new_fname = kasprintf(GFP_KERNEL, "%s%s", new_path, lname);
|
||
|
kfree(np->full_name);
|
||
|
np->full_name = new_fname;
|
||
|
|
||
|
__of_attach_node_sysfs(np);
|
||
|
|
||
|
for_each_child_of_node(np, child)
|
||
|
update_new_path_node_and_children(child, np->full_name);
|
||
|
}
|
||
|
|
||
|
static int process_sub_devs_copy(struct device_node *pm_node,
|
||
|
struct device_node *sub_dev)
|
||
|
{
|
||
|
struct device_node *us_dev_np, *bus = NULL;
|
||
|
struct device_node *dev_child, *child, *next_child;
|
||
|
struct property *nprop;
|
||
|
const char *dev_str;
|
||
|
int ret;
|
||
|
char path[200];
|
||
|
char prop_name[50];
|
||
|
const char *sp;
|
||
|
const char *us_dev;
|
||
|
unsigned long flags;
|
||
|
int llen;
|
||
|
|
||
|
ret = of_property_read_string(sub_dev, "device-path", &dev_str);
|
||
|
if (ret < 0) {
|
||
|
pr_err("device-path property not found in node %s\n",
|
||
|
sub_dev->full_name);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
memset(path, 0, 200);
|
||
|
memcpy(path, dev_str, strlen(dev_str));
|
||
|
|
||
|
if ((dev_str[0] == '.' && dev_str[1] == '/')) {
|
||
|
sp = strrchr(dev_str, '/');
|
||
|
memset(prop_name, 0, 50);
|
||
|
memcpy(prop_name, sp + 1, strlen(sp));
|
||
|
|
||
|
llen = strlen(dev_str) - strlen(sp);
|
||
|
path[llen] = '\0';
|
||
|
|
||
|
us_dev_np = of_get_nested_child_by_name(pm_node, path + 2);
|
||
|
if (!us_dev_np) {
|
||
|
pr_err("Node %s not found at %s\n",
|
||
|
path + 2, pm_node->full_name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ret = of_property_read_string(us_dev_np, prop_name, &us_dev);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Node %s does not have prop %s\n\n",
|
||
|
us_dev_np->full_name, prop_name);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
bus = of_find_node_by_path(us_dev);
|
||
|
} else if (dev_str[0] == '/') {
|
||
|
bus = of_find_node_by_path(dev_str);
|
||
|
} else {
|
||
|
pr_err("device-path %s (%d) not processed: %s\n",
|
||
|
dev_str, (int)strlen(dev_str), sub_dev->full_name);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (!bus) {
|
||
|
pr_err("Node path %s not found\n", us_dev);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
dev_child = sub_dev->child;
|
||
|
|
||
|
mutex_lock(&of_mutex);
|
||
|
raw_spin_lock_irqsave(&devtree_lock, flags);
|
||
|
|
||
|
child = sub_dev->child;
|
||
|
while (child) {
|
||
|
next_child = child->sibling;
|
||
|
|
||
|
of_add_node_to_parent(bus, child);
|
||
|
|
||
|
pr_debug("Added child %s to bus %s\n",
|
||
|
child->full_name, bus->full_name);
|
||
|
child = next_child;
|
||
|
}
|
||
|
|
||
|
raw_spin_unlock_irqrestore(&devtree_lock, flags);
|
||
|
|
||
|
while (dev_child) {
|
||
|
update_new_path_node_and_children(dev_child,
|
||
|
bus->full_name);
|
||
|
dev_child = dev_child->sibling;
|
||
|
}
|
||
|
|
||
|
mutex_unlock(&of_mutex);
|
||
|
|
||
|
nprop = __of_create_property_by_name("PM-RELOCATED", NULL, 0);
|
||
|
if (!nprop)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
ret = of_add_property(sub_dev, nprop);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Prop %s can not be added on node %s\n",
|
||
|
nprop->name, sub_dev->full_name);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ops_copy_property_value(struct device_node *np,
|
||
|
struct device_node *ops)
|
||
|
{
|
||
|
const char *sprop_str, *tprop_str;
|
||
|
struct property *sprop, *tprop;
|
||
|
int ret;
|
||
|
|
||
|
ret = of_property_read_string(ops, "source-property", &sprop_str);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Failed to read source-property @%s\n", ops->full_name);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = of_property_read_string(ops, "target-property", &tprop_str);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Failed to read target-property @%s\n", ops->full_name);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
sprop = plugin_module_get_property_by_path_name(np, sprop_str);
|
||
|
if (!sprop) {
|
||
|
pr_err("Failed to get property %s:%s\n",
|
||
|
sprop_str, ops->full_name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
tprop = plugin_module_get_property_by_path_name(np, tprop_str);
|
||
|
if (!tprop) {
|
||
|
plugin_module_create_property_to_path(np, sprop, tprop_str);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (!sprop->length || !sprop->value) {
|
||
|
pr_err("No value for source %s\n", sprop_str);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (sprop->length == tprop->length) {
|
||
|
memcpy(tprop->value, sprop->value, tprop->length);
|
||
|
} else {
|
||
|
void *oval = tprop->value;
|
||
|
void *nval = kzalloc(sprop->length, GFP_KERNEL);
|
||
|
|
||
|
if (!nval)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
tprop->value = nval;
|
||
|
memcpy(tprop->value, sprop->value, sprop->length);
|
||
|
tprop->length = sprop->length;
|
||
|
kfree(oval);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ops_resolve_property_value(struct device_node *np,
|
||
|
struct device_node *ops)
|
||
|
{
|
||
|
struct device_node *ref_node;
|
||
|
const char *tprop_str;
|
||
|
struct property *sprop, *tprop;
|
||
|
void *oval, *nval;
|
||
|
int ret, len;
|
||
|
|
||
|
ret = of_property_read_string(ops, "target-property", &tprop_str);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Failed to read target-property @%s\n", ops->full_name);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
tprop = plugin_module_get_property_by_path_name(np, tprop_str);
|
||
|
if (!tprop) {
|
||
|
pr_err("Failed to get Property %s at %s\n",
|
||
|
tprop_str, np->full_name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
ref_node = plugin_module_get_node_by_path(np, tprop->value);
|
||
|
if (!ref_node) {
|
||
|
pr_debug("Failed to get node %s @%s\n",
|
||
|
(char *)tprop->value, ops->full_name);
|
||
|
goto try_for_property;
|
||
|
}
|
||
|
|
||
|
len = strlen(ref_node->full_name);
|
||
|
nval = kzalloc(len + 2, GFP_KERNEL);
|
||
|
if (!nval) {
|
||
|
pr_err("Memory allocation failed %s\n", ops->full_name);
|
||
|
return -ENOMEM;
|
||
|
}
|
||
|
|
||
|
memcpy(nval, ref_node->full_name, len);
|
||
|
tprop->length = len + 2;
|
||
|
goto copy_to_target;
|
||
|
|
||
|
try_for_property:
|
||
|
sprop = plugin_module_get_property_by_path_name(np, tprop->value);
|
||
|
if (!sprop) {
|
||
|
pr_err("Failed to get Property %s at %s\n",
|
||
|
(char *)tprop->value, np->full_name);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
if (sprop->length == tprop->length) {
|
||
|
memcpy(tprop->value, sprop->value, sprop->length);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
nval = kzalloc(sprop->length, GFP_KERNEL);
|
||
|
if (!nval)
|
||
|
return -ENOMEM;
|
||
|
|
||
|
memcpy(nval, sprop->value, sprop->length);
|
||
|
tprop->length = sprop->length;
|
||
|
|
||
|
copy_to_target:
|
||
|
oval = tprop->value;
|
||
|
tprop->value = nval;
|
||
|
kfree(oval);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int ops_resolve_property_handle(struct device_node *np,
|
||
|
struct device_node *ops)
|
||
|
{
|
||
|
const char *rnod_str, *tprop_str;
|
||
|
struct property *tprop;
|
||
|
struct device_node *ref_node;
|
||
|
int ret;
|
||
|
|
||
|
ret = of_property_read_string(ops, "target-property", &tprop_str);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Failed to read target-property @%s\n", ops->full_name);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ret = of_property_read_string(ops, "reference-node", &rnod_str);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Failed to read refernce-node @%s\n", ops->full_name);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
ref_node = plugin_module_get_node_by_path(np, rnod_str);
|
||
|
if (!ref_node) {
|
||
|
pr_err("Failed to get node %s @%s\n", rnod_str, ops->full_name);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
if (!ref_node->phandle)
|
||
|
ref_node->phandle = of_get_next_phandle();
|
||
|
|
||
|
tprop = plugin_module_get_property_by_path_name(np, tprop_str);
|
||
|
if (!tprop) {
|
||
|
tprop = plugin_module_create_property_to_path(np, NULL,
|
||
|
tprop_str);
|
||
|
if (!tprop) {
|
||
|
pr_err("Failed to create property %s\n", tprop_str);
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
tprop->value = kzalloc(8, GFP_KERNEL);
|
||
|
if (!tprop->value)
|
||
|
return -ENOMEM;
|
||
|
tprop->length = 8;
|
||
|
}
|
||
|
|
||
|
*(uint32_t *)tprop->value = cpu_to_be32(ref_node->phandle);
|
||
|
|
||
|
pr_debug("%s: %s and inode %s handle %u\n",
|
||
|
__func__, ops->full_name, ref_node->full_name,
|
||
|
ref_node->phandle);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int process_sub_functons(struct device_node *pm_node,
|
||
|
struct device_node *funcs)
|
||
|
{
|
||
|
struct device_node *ops;
|
||
|
|
||
|
for_each_child_of_node(funcs, ops) {
|
||
|
if (of_property_read_bool(ops, "resolve-property-value"))
|
||
|
ops_resolve_property_value(pm_node, ops);
|
||
|
else if (of_property_read_bool(ops, "copy-property-value"))
|
||
|
ops_copy_property_value(pm_node, ops);
|
||
|
else if (of_property_read_bool(ops, "resolve-property-handle"))
|
||
|
ops_resolve_property_handle(pm_node, ops);
|
||
|
else
|
||
|
pr_err("Unidentified ops at %s\n", ops->full_name);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int copy_all_node_properties(struct device_node *dest,
|
||
|
struct device_node *src)
|
||
|
{
|
||
|
struct property *sprop, *dprop;
|
||
|
int ret;
|
||
|
|
||
|
for_each_property_of_node(src, sprop) {
|
||
|
if (!strcmp(sprop->name, "name") ||
|
||
|
!strcmp(sprop->name, "phandle") ||
|
||
|
!strcmp(sprop->name, "linux,phandle"))
|
||
|
continue;
|
||
|
|
||
|
dprop = of_find_property(dest, sprop->name, NULL);
|
||
|
/* New property */
|
||
|
if (!dprop) {
|
||
|
dprop = __of_copy_property(sprop, NULL, 0, GFP_KERNEL);
|
||
|
if (!dprop) {
|
||
|
pr_err("Prop %s can not be duplicated\n",
|
||
|
sprop->name);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ret = of_add_property(dest, dprop);
|
||
|
if (ret < 0) {
|
||
|
pr_err("Prop %s can not be added on node %s\n",
|
||
|
dprop->name, dest->full_name);
|
||
|
free_property(dprop);
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Boolean property */
|
||
|
if (!sprop->length) {
|
||
|
if (dprop->length) {
|
||
|
kfree(dprop->value);
|
||
|
dprop->value = NULL;
|
||
|
dprop->length = 0;
|
||
|
}
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Different length property */
|
||
|
if (dprop->length != sprop->length) {
|
||
|
void *old_val = dprop->value;
|
||
|
void *new_val = kzalloc(sprop->length, GFP_KERNEL);
|
||
|
|
||
|
memcpy(new_val, sprop->value, sprop->length);
|
||
|
dprop->value = new_val;
|
||
|
dprop->length = sprop->length;
|
||
|
kfree(old_val);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* Same length */
|
||
|
memcpy(dprop->value, sprop->value, sprop->length);
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void map_module_connector_to_parent(struct device_node *module_us_np,
|
||
|
struct device_node *ds_con_np)
|
||
|
{
|
||
|
struct device_node *ds_pins, *module_pins;
|
||
|
const char *lname;
|
||
|
|
||
|
for_each_child_of_node(ds_con_np, ds_pins) {
|
||
|
lname = strrchr(ds_pins->full_name, '/');
|
||
|
lname++;
|
||
|
|
||
|
module_pins = of_get_child_by_last_name(module_us_np, lname);
|
||
|
if (!module_pins) {
|
||
|
module_pins = get_copy_of_node(ds_pins, NULL,
|
||
|
module_us_np->full_name,
|
||
|
lname);
|
||
|
of_add_node_to_parent(module_us_np, module_pins);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
copy_all_node_properties(module_pins, ds_pins);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void process_plugin_module_connections(struct device_node *cnp)
|
||
|
{
|
||
|
struct connection_info *cinfo = cnp->data;
|
||
|
struct connection_info *cdata, *mdata;
|
||
|
struct device_node *connector, *module;
|
||
|
struct device_node *funcs, *sub_func;
|
||
|
|
||
|
if (!cinfo->copy_node)
|
||
|
return;
|
||
|
|
||
|
if (!cinfo->copy_node->data)
|
||
|
cinfo->copy_node->data = cinfo;
|
||
|
|
||
|
/* Perform functons */
|
||
|
funcs = of_get_child_by_name(cinfo->copy_node, "functions");
|
||
|
if (!funcs)
|
||
|
goto handle_ds_connection;
|
||
|
|
||
|
for_each_child_of_node(funcs, sub_func) {
|
||
|
if (of_property_read_bool(sub_func, "copy-subdevices"))
|
||
|
process_sub_devs_copy(cinfo->copy_node, sub_func);
|
||
|
else
|
||
|
process_sub_functons(cinfo->copy_node, sub_func);
|
||
|
}
|
||
|
|
||
|
handle_ds_connection:
|
||
|
/* Copy Downstream connectors to connected module's upstream */
|
||
|
for_each_child_of_node(cnp, connector) {
|
||
|
module = connector->child;
|
||
|
if (!module)
|
||
|
continue;
|
||
|
|
||
|
mdata = module->data;
|
||
|
cdata = connector->data;
|
||
|
if (mdata->parent_conn_node) {
|
||
|
pr_debug("Copying node %s to %s\n",
|
||
|
mdata->parent_conn_node->full_name,
|
||
|
cdata->child_conn_node->full_name);
|
||
|
map_module_connector_to_parent(
|
||
|
cdata->child_conn_node,
|
||
|
mdata->parent_conn_node);
|
||
|
}
|
||
|
|
||
|
process_plugin_module_connections(module);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static struct device_node *connect_all_child_modules(
|
||
|
struct device_node *plmroot,
|
||
|
struct device_node *plcroot,
|
||
|
struct device_node *module)
|
||
|
{
|
||
|
struct connection_info *cinfo;
|
||
|
struct device_node *nplcroot = plcroot;
|
||
|
struct device_node *child, *ds;
|
||
|
struct property *prop;
|
||
|
const char *module_id = NULL;
|
||
|
const char *s;
|
||
|
bool found;
|
||
|
|
||
|
for_each_property_of_node(module, prop) {
|
||
|
/* Skip those we do not want to proceed */
|
||
|
if (!strcmp(prop->name, "name") ||
|
||
|
!strcmp(prop->name, "phandle") ||
|
||
|
!strcmp(prop->name, "linux,phandle"))
|
||
|
continue;
|
||
|
|
||
|
/* Other property is module ID: Support only one module */
|
||
|
nplcroot = add_module_connection(plcroot, NULL, prop->name);
|
||
|
if (!nplcroot) {
|
||
|
pr_info("Not able to create %s\n", prop->name);
|
||
|
return NULL;
|
||
|
}
|
||
|
module_id = prop->name;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!module_id) {
|
||
|
pr_info("Module name not found at %s, parent %s\n",
|
||
|
module->full_name, nplcroot->full_name);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Get the plugin-module node whose asset-id match with module ID */
|
||
|
found = false;
|
||
|
for_each_child_of_node(plmroot, child) {
|
||
|
of_property_for_each_string(child, "asset-id", prop, s) {
|
||
|
if (!strcmp(module_id, s)) {
|
||
|
pr_debug("Asset matched ID %s:%s\n", module_id,
|
||
|
child->full_name);
|
||
|
cinfo = nplcroot->data;
|
||
|
cinfo->org_pm = child;
|
||
|
found = true;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if (found)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!found) {
|
||
|
pr_debug("Plugin module %s not found\n", module->full_name);
|
||
|
return NULL;
|
||
|
}
|
||
|
|
||
|
/* Process for down stream connectors for this module */
|
||
|
ds = of_get_child_by_name(child, "downstream");
|
||
|
if (ds) {
|
||
|
link_connection_to_plugin_modules(plmroot, nplcroot,
|
||
|
ds, module);
|
||
|
return nplcroot;
|
||
|
}
|
||
|
|
||
|
for_each_child_of_node(module, child) {
|
||
|
nplcroot = connect_all_child_modules(plmroot, nplcroot, child);
|
||
|
if (!nplcroot) {
|
||
|
pr_info("Deadend found at %s\n", child->full_name);
|
||
|
return NULL;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
return nplcroot;
|
||
|
}
|
||
|
|
||
|
static int link_connection_to_plugin_modules(struct device_node *plmroot,
|
||
|
struct device_node *plcroot,
|
||
|
struct device_node *ds,
|
||
|
struct device_node *connector)
|
||
|
{
|
||
|
struct device_node *ds_con, *module, *new_connector;
|
||
|
struct device_node *nplcroot;
|
||
|
const char *s, *con_name;
|
||
|
int ret, naddr, i;
|
||
|
int mod_add, add;
|
||
|
bool found;
|
||
|
|
||
|
for_each_child_of_node(ds, ds_con) {
|
||
|
/* Match the connection bus with plug-in-module */
|
||
|
ret = of_property_read_string(ds_con, "identification-bus", &s);
|
||
|
if (ret)
|
||
|
continue;
|
||
|
|
||
|
new_connector = NULL;
|
||
|
if (*s == '/') {
|
||
|
con_name = strrchr(connector->full_name, '/');
|
||
|
if (!strcmp(con_name, s))
|
||
|
new_connector = connector;
|
||
|
} else if ((*s == '.') && (*(s + 1) == '/')) {
|
||
|
new_connector = of_get_nested_child_by_name(connector,
|
||
|
s + 2);
|
||
|
}
|
||
|
|
||
|
if (!new_connector)
|
||
|
continue;
|
||
|
|
||
|
naddr = of_property_count_u32_elems(ds_con,
|
||
|
"identification-slave-add");
|
||
|
if (naddr <= 0) {
|
||
|
nplcroot = add_module_connection(plcroot, NULL,
|
||
|
ds_con->name);
|
||
|
if (!nplcroot)
|
||
|
return 0;
|
||
|
|
||
|
for_each_child_of_node(new_connector, module)
|
||
|
connect_all_child_modules(plmroot,
|
||
|
nplcroot, module);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
nplcroot = NULL;
|
||
|
for_each_child_of_node(new_connector, module) {
|
||
|
found = false;
|
||
|
|
||
|
mod_add = get_node_address(module);
|
||
|
if (mod_add <= 0)
|
||
|
continue;
|
||
|
|
||
|
for (i = 0; i < naddr; ++i) {
|
||
|
u32 pval;
|
||
|
|
||
|
ret = of_property_read_u32_index(
|
||
|
ds_con, "identification-slave-add",
|
||
|
i, &pval);
|
||
|
if (ret < 0)
|
||
|
continue;
|
||
|
|
||
|
add = pval;
|
||
|
if (add == mod_add)
|
||
|
found = true;
|
||
|
}
|
||
|
|
||
|
if (found) {
|
||
|
if (!nplcroot)
|
||
|
nplcroot = add_module_connection(
|
||
|
plcroot, NULL,
|
||
|
ds_con->name);
|
||
|
if (!nplcroot)
|
||
|
return 0;
|
||
|
connect_all_child_modules(plmroot, nplcroot,
|
||
|
module);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void connection_manager(void)
|
||
|
{
|
||
|
struct device_node *plmroot, *conroot;
|
||
|
struct device_node *plmmods, *ds, *connector;
|
||
|
struct device_node *plcroot;
|
||
|
struct connection_info *cinfo;
|
||
|
|
||
|
plmroot = of_find_node_by_path("/plugin-modules");
|
||
|
if (!plmroot) {
|
||
|
pr_info("Plugin module not found\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
conroot = of_find_node_by_path("/chosen/plugin-manager/ids/connection");
|
||
|
if (!conroot) {
|
||
|
pr_info("chosen/conenction not found\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
plcroot = create_simple_device_node("/", "plugin-connection",
|
||
|
sizeof(*cinfo));
|
||
|
if (!plcroot) {
|
||
|
pr_info("Failed to create node /plugin-connection\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
for_each_child_of_node(plmroot, plmmods) {
|
||
|
if (of_property_read_bool(plmmods, "the-root-base-board")) {
|
||
|
cinfo = plcroot->data;
|
||
|
cinfo->node = plcroot;
|
||
|
cinfo->org_pm = plmmods;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Create connection tree based on module connected */
|
||
|
for_each_child_of_node(conroot, connector) {
|
||
|
for_each_child_of_node(plmroot, plmmods) {
|
||
|
ds = of_get_child_by_name(plmmods, "downstream");
|
||
|
if (!ds)
|
||
|
continue;
|
||
|
link_connection_to_plugin_modules(plmroot, plcroot, ds,
|
||
|
connector);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Duplicate plug-in module nodes to each of modules for override */
|
||
|
create_dup_nodes_for_connected_plm(plcroot);
|
||
|
|
||
|
/* Link modules node to connector node */
|
||
|
link_module_node_to_connector_node(plcroot);
|
||
|
|
||
|
/* Process upstream, device and downstream nodes */
|
||
|
process_plugin_module_connections(plcroot);
|
||
|
|
||
|
free_simple_device_node(plcroot);
|
||
|
}
|
||
|
|
||
|
static int __init plugin_manager_init(void)
|
||
|
{
|
||
|
struct device_node *pm_node;
|
||
|
struct device_node *child;
|
||
|
int ret;
|
||
|
|
||
|
pr_info("Initializing plugin-manager\n");
|
||
|
|
||
|
connection_manager();
|
||
|
|
||
|
pm_node = of_find_node_by_path("/plugin-manager");
|
||
|
if (!pm_node) {
|
||
|
pr_info("Plugin-manager not available\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
if (!of_device_is_available(pm_node)) {
|
||
|
pr_info("Plugin-manager status disabled\n");
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
for_each_available_child_of_node(pm_node, child) {
|
||
|
ret = plugin_manager(child);
|
||
|
if (ret < 0)
|
||
|
pr_err("Error in parsing node %s: %d\n",
|
||
|
child->full_name, ret);
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
core_initcall(plugin_manager_init);
|