diff options
| -rw-r--r-- | README | 30 | ||||
| -rw-r--r-- | common/Makefile | 4 | ||||
| -rw-r--r-- | common/env_attr.c | 222 | ||||
| -rw-r--r-- | common/env_callback.c | 144 | ||||
| -rw-r--r-- | include/env_attr.h | 55 | ||||
| -rw-r--r-- | include/env_callback.h | 62 | ||||
| -rw-r--r-- | include/env_default.h | 5 | ||||
| -rw-r--r-- | include/environment.h | 2 | ||||
| -rw-r--r-- | include/search.h | 5 | ||||
| -rw-r--r-- | lib/hashtable.c | 67 | 
10 files changed, 592 insertions, 4 deletions
| @@ -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:  ===================== diff --git a/common/Makefile b/common/Makefile index ce3035937..04812e9b2 100644 --- a/common/Makefile +++ b/common/Makefile @@ -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 diff --git a/common/env_attr.c b/common/env_attr.c new file mode 100644 index 000000000..7d330a58b --- /dev/null +++ b/common/env_attr.c @@ -0,0 +1,222 @@ +/* + * (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; +} diff --git a/common/env_callback.c b/common/env_callback.c new file mode 100644 index 000000000..78ca3674f --- /dev/null +++ b/common/env_callback.c @@ -0,0 +1,144 @@ +/* + * (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); diff --git a/include/env_attr.h b/include/env_attr.h new file mode 100644 index 000000000..6ef114f5d --- /dev/null +++ b/include/env_attr.h @@ -0,0 +1,55 @@ +/* + * (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__ */ diff --git a/include/env_callback.h b/include/env_callback.h new file mode 100644 index 000000000..5a460f353 --- /dev/null +++ b/include/env_callback.h @@ -0,0 +1,62 @@ +/* + * (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__ */ diff --git a/include/env_default.h b/include/env_default.h index a1db73a2c..d05eba161 100644 --- a/include/env_default.h +++ b/include/env_default.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 diff --git a/include/environment.h b/include/environment.h index 4b19f32be..6c3021552 100644 --- a/include/environment.h +++ b/include/environment.h @@ -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; diff --git a/include/search.h b/include/search.h index 1e48deb1c..d68e24a03 100644 --- a/include/search.h +++ b/include/search.h @@ -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 */ diff --git a/lib/hashtable.c b/lib/hashtable.c index 7c6b96cac..e9226665f 100644 --- a/lib/hashtable.c +++ b/lib/hashtable.c @@ -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); +			__set_errno(EINVAL); +			*retval = NULL; +			return 0; +		} +  		/* return new entry */  		*retval = &htab->table[idx].entry;  		return 1; @@ -437,6 +464,7 @@ static void _hdelete(const char *key, struct hsearch_data *htab, ENTRY *ep,  	debug("hdelete: DELETING key \"%s\"\n", key);  	free((void *)ep->key);  	free(ep->data); +	ep->callback = NULL;  	htab->table[idx].used = -1;  	--htab->filled; @@ -466,6 +494,15 @@ int hdelete_r(const char *key, struct hsearch_data *htab, int flag)  		return 0;  	} +	/* If there is a callback, call it */ +	if (htab->table[idx].entry.callback && +	    htab->table[idx].entry.callback(key, NULL, env_op_delete, flag)) { +		debug("callback() rejected deleting variable " +			"%s, skipping it!\n", key); +		__set_errno(EINVAL); +		return 0; +	} +  	_hdelete(key, htab, ep, idx);  	return 1; @@ -838,11 +875,9 @@ int himport_r(struct hsearch_data *htab,  		e.data = value;  		hsearch_r(e, ENTER, &rv, htab, flag); -		if (rv == NULL) { +		if (rv == NULL)  			printf("himport_r: can't insert \"%s=%s\" into hash table\n",  				name, value); -			return 0; -		}  		debug("INSERT: table %p, filled %d/%d rv %p ==> name=\"%s\" value=\"%s\"\n",  			htab, htab->filled, htab->size, @@ -873,3 +908,27 @@ int himport_r(struct hsearch_data *htab,  	debug("INSERT: done\n");  	return 1;		/* everything OK */  } + +/* + * hwalk_r() + */ + +/* + * Walk all of the entries in the hash, calling the callback for each one. + * this allows some generic operation to be performed on each element. + */ +int hwalk_r(struct hsearch_data *htab, int (*callback)(ENTRY *)) +{ +	int i; +	int retval; + +	for (i = 1; i <= htab->size; ++i) { +		if (htab->table[i].used > 0) { +			retval = callback(&htab->table[i].entry); +			if (retval) +				return retval; +		} +	} + +	return 0; +} |