Commit 170ab110 authored by Joe Hershberger's avatar Joe Hershberger Committed by Tom Rini
Browse files

env: Add support for callbacks to environment vars



Add support for per-variable callbacks to the "hashtable" functions.
Signed-off-by: Joe Hershberger's avatarJoe Hershberger <joe.hershberger@ni.com>

!!!fix comment in callback
parent be11235a
......@@ -4213,6 +4213,36 @@ Please note that changes to some configuration parameters may take
only effect after the next boot (yes, that's just like Windoze :-).
Callback functions for environment variables:
---------------------------------------------
For some environment variables, the behavior of u-boot needs to change
when their values are changed. This functionailty allows functions to
be associated with arbitrary variables. On creation, overwrite, or
deletion, the callback will provide the opportunity for some side
effect to happen or for the change to be rejected.
The callbacks are named and associated with a function using the
U_BOOT_ENV_CALLBACK macro in your board or driver code.
These callbacks are associated with variables in one of two ways. The
static list can be added to by defining CONFIG_ENV_CALLBACK_LIST_STATIC
in the board configuration to a string that defines a list of
associations. The list must be in the following format:
entry = variable_name[:callback_name]
list = entry[,list]
If the callback name is not specified, then the callback is deleted.
Spaces are also allowed anywhere in the list.
Callbacks can also be associated by defining the ".callbacks" variable
with the same list format above. Any association in ".callbacks" will
override any association in the static list. You can define
CONFIG_ENV_CALLBACK_LIST_DEFAULT to a list (string) to define the
".callbacks" envirnoment variable in the default or embedded environment.
Command Line Parsing:
=====================
......
......@@ -44,6 +44,8 @@ COBJS-y += cmd_nvedit.o
COBJS-y += cmd_version.o
# environment
COBJS-y += env_attr.o
COBJS-y += env_callback.o
COBJS-y += env_common.o
COBJS-$(CONFIG_ENV_IS_IN_DATAFLASH) += env_dataflash.o
COBJS-$(CONFIG_ENV_IS_IN_EEPROM) += env_eeprom.o
......@@ -207,6 +209,8 @@ COBJS-y += env_common.o
COBJS-$(CONFIG_ENV_IS_IN_FLASH) += env_flash.o
COBJS-$(CONFIG_SPL_YMODEM_SUPPORT) += xyzModem.o
COBJS-$(CONFIG_SPL_NET_SUPPORT) += cmd_nvedit.o
COBJS-$(CONFIG_SPL_NET_SUPPORT) += env_attr.o
COBJS-$(CONFIG_SPL_NET_SUPPORT) += env_callback.o
COBJS-$(CONFIG_SPL_NET_SUPPORT) += env_common.o
COBJS-$(CONFIG_SPL_NET_SUPPORT) += env_nowhere.o
COBJS-$(CONFIG_SPL_NET_SUPPORT) += miiphyutil.o
......
/*
* (C) Copyright 2012
* Joe Hershberger, National Instruments, joe.hershberger@ni.com
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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.
*
* This program is distributed in the hope that 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <common.h>
#include <env_attr.h>
#include <errno.h>
#include <linux/string.h>
#include <malloc.h>
/*
* Iterate through the whole list calling the callback for each found element.
* "attr_list" takes the form:
* attributes = [^,:\s]*
* entry = name[:attributes]
* list = entry[,list]
*/
int env_attr_walk(const char *attr_list,
int (*callback)(const char *name, const char *attributes))
{
const char *entry, *entry_end;
char *name, *attributes;
if (!attr_list)
/* list not found */
return 1;
entry = attr_list;
do {
char *entry_cpy = NULL;
entry_end = strchr(entry, ENV_ATTR_LIST_DELIM);
/* check if this is the last entry in the list */
if (entry_end == NULL) {
int entry_len = strlen(entry);
if (entry_len) {
/*
* allocate memory to copy the entry into since
* we will need to inject '\0' chars and squash
* white-space before calling the callback
*/
entry_cpy = malloc(entry_len + 1);
if (entry_cpy)
/* copy the rest of the list */
strcpy(entry_cpy, entry);
else
return -ENOMEM;
}
} else {
int entry_len = entry_end - entry;
if (entry_len) {
/*
* allocate memory to copy the entry into since
* we will need to inject '\0' chars and squash
* white-space before calling the callback
*/
entry_cpy = malloc(entry_len + 1);
if (entry_cpy) {
/* copy just this entry and null term */
strncpy(entry_cpy, entry, entry_len);
entry_cpy[entry_len] = '\0';
} else
return -ENOMEM;
}
}
/* check if there is anything to process (e.g. not ",,,") */
if (entry_cpy != NULL) {
attributes = strchr(entry_cpy, ENV_ATTR_SEP);
/* check if there is a ':' */
if (attributes != NULL) {
/* replace the ':' with '\0' to term name */
*attributes++ = '\0';
/* remove white-space from attributes */
attributes = strim(attributes);
}
/* remove white-space from name */
name = strim(entry_cpy);
/* only call the callback if there is a name */
if (strlen(name) != 0) {
int retval = 0;
retval = callback(name, attributes);
if (retval) {
free(entry_cpy);
return retval;
}
}
}
free(entry_cpy);
entry = entry_end + 1;
} while (entry_end != NULL);
return 0;
}
/*
* Search for the last matching string in another string with the option to
* start looking at a certain point (i.e. ignore anything beyond that point).
*/
static char *reverse_strstr(const char *searched, const char *search_for,
const char *searched_start)
{
char *result = NULL;
if (*search_for == '\0')
return (char *)searched;
for (;;) {
char *match = strstr(searched, search_for);
/*
* Stop looking if no new match is found or looking past the
* searched_start pointer
*/
if (match == NULL || (searched_start != NULL &&
match + strlen(search_for) > searched_start))
break;
result = match;
searched = match + 1;
}
return result;
}
/*
* Retrieve the attributes string associated with a single name in the list
* There is no protection on attributes being too small for the value
*/
int env_attr_lookup(const char *attr_list, const char *name, char *attributes)
{
const char *entry = NULL;
if (!attributes)
/* bad parameter */
return -1;
if (!attr_list)
/* list not found */
return 1;
entry = reverse_strstr(attr_list, name, NULL);
while (entry != NULL) {
const char *prevch = entry - 1;
const char *nextch = entry + strlen(name);
/* Skip spaces */
while (*prevch == ' ')
prevch--;
while (*nextch == ' ')
nextch++;
/* check for an exact match */
if ((entry == attr_list ||
*prevch == ENV_ATTR_LIST_DELIM) &&
(*nextch == ENV_ATTR_SEP ||
*nextch == ENV_ATTR_LIST_DELIM ||
*nextch == '\0'))
break;
entry = reverse_strstr(attr_list, name, entry);
}
if (entry != NULL) {
int len;
/* skip the name */
entry += strlen(name);
/* skip spaces */
while (*entry == ' ')
entry++;
if (*entry != ENV_ATTR_SEP)
len = 0;
else {
const char *delim;
static const char delims[] = {
ENV_ATTR_LIST_DELIM, ' ', '\0'};
/* skip the attr sep */
entry += 1;
/* skip spaces */
while (*entry == ' ')
entry++;
delim = strpbrk(entry, delims);
if (delim == NULL)
len = strlen(entry);
else
len = delim - entry;
memcpy(attributes, entry, len);
}
attributes[len] = '\0';
/* success */
return 0;
}
/* not found in list */
return 2;
}
/*
* (C) Copyright 2012
* Joe Hershberger, National Instruments, joe.hershberger@ni.com
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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.
*
* This program is distributed in the hope that 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#include <common.h>
#include <environment.h>
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
DECLARE_GLOBAL_DATA_PTR;
#endif
/*
* Look up a callback function pointer by name
*/
struct env_clbk_tbl *find_env_callback(const char *name)
{
struct env_clbk_tbl *clbkp;
int i;
int num_callbacks = ll_entry_count(struct env_clbk_tbl, env_clbk);
if (name == NULL)
return NULL;
/* look up the callback in the linker-list */
for (i = 0, clbkp = ll_entry_start(struct env_clbk_tbl, env_clbk);
i < num_callbacks;
i++, clbkp++) {
if (strcmp(name, clbkp->name) == 0)
return clbkp;
}
return NULL;
}
/*
* Look for a possible callback for a newly added variable
* This is called specifically when the variable did not exist in the hash
* previously, so the blanket update did not find this variable.
*/
void env_callback_init(ENTRY *var_entry)
{
const char *var_name = var_entry->key;
const char *callback_list = getenv(ENV_CALLBACK_VAR);
char callback_name[256] = "";
struct env_clbk_tbl *clbkp;
int ret = 1;
/* look in the ".callbacks" var for a reference to this variable */
if (callback_list != NULL)
ret = env_attr_lookup(callback_list, var_name, callback_name);
/* only if not found there, look in the static list */
if (ret)
ret = env_attr_lookup(ENV_CALLBACK_LIST_STATIC, var_name,
callback_name);
/* if an association was found, set the callback pointer */
if (!ret && strlen(callback_name)) {
clbkp = find_env_callback(callback_name);
if (clbkp != NULL)
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
var_entry->callback = clbkp->callback + gd->reloc_off;
#else
var_entry->callback = clbkp->callback;
#endif
}
}
/*
* Called on each existing env var prior to the blanket update since removing
* a callback association should remove its callback.
*/
static int clear_callback(ENTRY *entry)
{
entry->callback = NULL;
return 0;
}
/*
* Call for each element in the list that associates variables to callbacks
*/
static int set_callback(const char *name, const char *value)
{
ENTRY e, *ep;
struct env_clbk_tbl *clbkp;
e.key = name;
e.data = NULL;
hsearch_r(e, FIND, &ep, &env_htab, 0);
/* does the env variable actually exist? */
if (ep != NULL) {
/* the assocaition delares no callback, so remove the pointer */
if (value == NULL || strlen(value) == 0)
ep->callback = NULL;
else {
/* assign the requested callback */
clbkp = find_env_callback(value);
if (clbkp != NULL)
#if defined(CONFIG_NEEDS_MANUAL_RELOC)
ep->callback = clbkp->callback + gd->reloc_off;
#else
ep->callback = clbkp->callback;
#endif
}
}
return 0;
}
static int on_callbacks(const char *name, const char *value, enum env_op op,
int flags)
{
/* remove all callbacks */
hwalk_r(&env_htab, clear_callback);
/* configure any static callback bindings */
env_attr_walk(ENV_CALLBACK_LIST_STATIC, set_callback);
/* configure any dynamic callback bindings */
env_attr_walk(value, set_callback);
return 0;
}
U_BOOT_ENV_CALLBACK(callbacks, on_callbacks);
/*
* (C) Copyright 2012
* Joe Hershberger, National Instruments, joe.hershberger@ni.com
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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.
*
* This program is distributed in the hope that 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#ifndef __ENV_ATTR_H__
#define __ENV_ATTR_H__
#define ENV_ATTR_LIST_DELIM ','
#define ENV_ATTR_SEP ':'
/*
* env_attr_walk takes as input an "attr_list" that takes the form:
* attributes = [^,:\s]*
* entry = name[:attributes]
* list = entry[,list]
* It will call the "callback" function with the "name" and attribute as "value"
* The callback may return a non-0 to abort the list walk.
* This return value will be passed through to the caller.
* 0 is returned on success.
*/
extern int env_attr_walk(const char *attr_list,
int (*callback)(const char *name, const char *value));
/*
* env_attr_lookup takes as input an "attr_list" with the same form as above.
* It also takes as input a "name" to look for.
* If the name is found in the list, it's value is copied into "attributes".
* There is no protection on attributes being too small for the value.
* It returns -1 if attributes is NULL, 1 if "name" is not found, 2 if
* "attr_list" is NULL.
* Returns 0 on success.
*/
extern int env_attr_lookup(const char *attr_list, const char *name,
char *attributes);
#endif /* __ENV_ATTR_H__ */
/*
* (C) Copyright 2012
* Joe Hershberger, National Instruments, joe.hershberger@ni.com
*
* See file CREDITS for list of people who contributed to this
* project.
*
* 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.
*
* This program is distributed in the hope that 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
* MA 02111-1307 USA
*/
#ifndef __ENV_CALLBACK_H__
#define __ENV_CALLBACK_H__
#include <linker_lists.h>
#include <search.h>
#define ENV_CALLBACK_VAR ".callbacks"
/* Board configs can define additional static callback bindings */
#ifndef CONFIG_ENV_CALLBACK_LIST_STATIC
#define CONFIG_ENV_CALLBACK_LIST_STATIC
#endif
/*
* This list of callback bindings is static, but may be overridden by defining
* a new association in the ".callbacks" environment variable.
*/
#define ENV_CALLBACK_LIST_STATIC ENV_CALLBACK_VAR ":callbacks," \
CONFIG_ENV_CALLBACK_LIST_STATIC
struct env_clbk_tbl {
const char *name; /* Callback name */
int (*callback)(const char *name, const char *value, enum env_op op,
int flags);
};
struct env_clbk_tbl *find_env_callback(const char *);
void env_callback_init(ENTRY *var_entry);
/*
* Define a callback that can be associated with variables.
* when associated through the ".callbacks" environment variable, the callback
* will be executed any time the variable is inserted, overwritten, or deleted.
*/
#define U_BOOT_ENV_CALLBACK(name, callback) \
ll_entry_declare(struct env_clbk_tbl, name, env_clbk, env_clbk) = \
{#name, callback}
#endif /* __ENV_CALLBACK_H__ */
......@@ -24,6 +24,8 @@
* MA 02111-1307 USA
*/
#include <env_callback.h>
#ifdef DEFAULT_ENV_INSTANCE_EMBEDDED
env_t environment __PPCENV__ = {
ENV_CRC, /* CRC Sum */
......@@ -36,6 +38,9 @@ static char default_environment[] = {
#else
const uchar default_environment[] = {
#endif
#ifdef CONFIG_ENV_CALLBACK_LIST_DEFAULT
ENV_CALLBACK_VAR "=" CONFIG_ENV_CALLBACK_LIST_DEFAULT "\0"
#endif
#ifdef CONFIG_BOOTARGS
"bootargs=" CONFIG_BOOTARGS "\0"
#endif
......
......@@ -164,6 +164,8 @@ extern void env_reloc(void);
#ifndef DO_DEPS_ONLY
#include <env_attr.h>
#include <env_callback.h>
#include <search.h>
extern struct hsearch_data env_htab;
......
......@@ -47,6 +47,8 @@ typedef enum {
typedef struct entry {
const char *key;
char *data;
int (*callback)(const char *name, const char *value, enum env_op op,
int flags);
} ENTRY;
/* Opaque type for internal use. */
......@@ -120,6 +122,9 @@ extern int himport_r(struct hsearch_data *__htab,
const char *__env, size_t __size, const char __sep,
int __flag, int nvars, char * const vars[]);
/* Walk the whole table calling the callback on each element */
extern int hwalk_r(struct hsearch_data *__htab, int (*callback)(ENTRY *));
/* Flags for himport_r(), hexport_r(), hdelete_r(), and hsearch_r() */
#define H_NOCLEAR (1 << 0) /* do not clear hash table before importing */
#define H_FORCE (1 << 1) /* overwrite read-only/write-once variables */
......
......@@ -54,7 +54,8 @@
#define CONFIG_ENV_MAX_ENTRIES 512
#endif
#include "search.h"
#include <env_callback.h>
#include <search.h>
/*
* [Aho,Sethi,Ullman] Compilers: Principles, Techniques and Tools, 1986
......@@ -274,6 +275,17 @@ static inline int _compare_and_overwrite_entry(ENTRY item, ACTION action,
return 0;
}
/* If there is a callback, call it */
if (htab->table[idx].entry.callback &&
htab->table[idx].entry.callback(item.key,
item.data, env_op_overwrite, flag)) {
debug("callback() rejected setting variable "
"%s, skipping it!\n", item.key);
__set_errno(EINVAL);
*retval = NULL;
return 0;
}
free(htab->table[idx].entry.data);
htab->table[idx].entry.data = strdup(item.data);
if (!htab->table[idx].entry.data) {
......@@ -398,6 +410,9 @@ int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval,
++htab->filled;
/* This is a new entry, so look up a possible callback */
env_callback_init(&htab->table[idx].entry);
/* check for permission */
if (htab->change_ok != NULL && htab->change_ok(
&htab->table[idx].entry, item.data, env_op_create, flag)) {
......@@ -409,6 +424,18 @@ int hsearch_r(ENTRY item, ACTION action, ENTRY ** retval,
return 0;
}
/* If there is a callback, call it */
if (htab->table[idx].entry.callback &&
htab->table[idx].entry.callback(item.key, item.data,
env_op_create, flag)) {
debug("callback() rejected setting variable "
"%s, skipping it!\n", item.key);
_hdelete(item.key, htab, &htab->table[idx].entry, idx);