diff mbox

[U-Boot,3/4] Add pxecfg command

Message ID 1307386599-4256-4-git-send-email-jason.hobbs@calxeda.com
State Changes Requested
Headers show

Commit Message

Jason Hobbs June 6, 2011, 6:56 p.m. UTC
Add pxecfg command, which is intended to mimic PXELINUX functionality.
'pxecfg get' uses tftp to retrieve a file based on UUID, MAC address or
IP address. 'pxecfg boot' interprets the contents of PXELINUX config
like file to boot using a specific initrd, kernel and kernel command
line.

This patch also adds a README.pxecfg file - see it for more details on
the pxecfg command.

Signed-off-by: Jason Hobbs <jason.hobbs@calxeda.com>
---
 common/Makefile     |    1 +
 common/cmd_pxecfg.c | 1070 +++++++++++++++++++++++++++++++++++++++++++++++++++
 doc/README.pxecfg   |  238 ++++++++++++
 include/common.h    |    5 +
 4 files changed, 1314 insertions(+), 0 deletions(-)
 create mode 100644 common/cmd_pxecfg.c
 create mode 100644 doc/README.pxecfg

Comments

Wolfgang Denk June 6, 2011, 7:45 p.m. UTC | #1
Dear "Jason Hobbs",

In message <1307386599-4256-4-git-send-email-jason.hobbs@calxeda.com> you wrote:
> Add pxecfg command, which is intended to mimic PXELINUX functionality.
> 'pxecfg get' uses tftp to retrieve a file based on UUID, MAC address or
> IP address. 'pxecfg boot' interprets the contents of PXELINUX config
> like file to boot using a specific initrd, kernel and kernel command
> line.
> 
> This patch also adds a README.pxecfg file - see it for more details on
> the pxecfg command.

Any reason for not calling this command simply "pxe" ?


Is [parts of] this code copied from somewhere? If yes, we need exact
reference (see bullet # 4 at
http://www.denx.de/wiki/view/U-Boot/Patches#Attributing_Code_Copyrights_Sign )

If it is not copied from other projects, then please change the
license to GPLv2+

...
> +	for (p = *outbuf; *p; p++)
> +		if (*p == ':')
> +			*p = '-';

Braces needed for multiline statement.

> +	bootfile_path = strdup(bootfile);
> +
> +	if (bootfile_path == NULL) {
> +		printf("oom\n");

Please use a more readable error message.

> +		return NULL;
> +	}
> +
> +	last_slash = strrchr(bootfile_path, '/');
> +
> +	if (last_slash == NULL) {
> +		printf("Invalid bootfile path (%s), no '/' found\n",
> +				bootfile_path);
> +
> +		return NULL;

Where is the requirement coming from that "bootfile" must contain a
path?  Is there any formal requirement a plain file name is illegal
[and could we not assume a path of "./" then] ?

> +	if (bootfile_path == NULL) {
> +		printf("No bootfile path in environment.\n");
> +		return -ENOENT;
> +	}
> +
> +	path_len = strlen(bootfile_path) + strlen(file_path) + 1;
> +
> +	if (path_len > MAX_TFTP_PATH_LEN) {
> +		printf("Base path too long (%s/%s)\n",
> +				bootfile_path, file_path);
> +
> +		free(bootfile_path);
> +		return -ENAMETOOLONG;
> +	}
> +
> +	sprintf(bootfile, "%s/%s", bootfile_path, file_path);
> +
> +	free(bootfile_path);
> +
> +	printf("Retreiving file: %s\n", bootfile);
> +
> +	sprintf(addr_buf, "%p", file_addr);
> +
> +	tftp_argv[1] = addr_buf;
> +	tftp_argv[2] = bootfile;

This looks like an extremly complicated way for a plain TFTP
download. Why are you performing all this acrobatics and juggling
with bootfile, instead of simply using what the user provided?


> +#define MAX_CFG_PATHS	12

Where is this number coming from?

> +static char **pxecfg_paths(char *uuid_str, char *mac_addr)
> +{
> +	char **ret_array;
> +	char ip_addr[9];
> +	int x, end_masks, mask_pos;
> +	size_t base_len = strlen("pxelinux.cfg/");
> +
> +	sprintf(ip_addr, "%08X", ntohl(NetOurIP));
> +
> +	ret_array = malloc(MAX_CFG_PATHS * sizeof(char *));
> +
> +	if (ret_array == NULL) {
> +		printf("oom\n");
> +		return NULL;
> +	}
> +
> +	for (x = 0; x < MAX_CFG_PATHS; x++)
> +		ret_array[x] = NULL;

Why do we need this array here?  I understand we are tyring one otopn
after the other, so we don't ever need more than a single entry of
this array at any time?

> +	while (iter_label)
> +		if (!strcmp(name, iter_label->name))
> +			return iter_label;
> +		else
> +			iter_label = iter_label->next;

Braces needed for multiline statement.  Please fix globally.

...
> +	/*
> +	 * We must dup the environment variable value here, because getenv
> +	 * returns a pointer directly into the environment, and the contents
> +	 * of the environment might shift during execution of a command.
> +	 */
> +	dupcmd = strdup(localcmd);

What do you mean by "the contents of the environment might shift" ???

> +	if (!dupcmd) {
> +		printf("oom\n");

See before: Please use readable error messages. Please fix globally.

> +		return -ENOMEM;
> +	}
> +
> +	if (label->append)
> +		setenv("bootargs", label->append);
> +
> +	printf("running: %s\n", dupcmd);

This should probably be a debug() ?

> +static void print_menu(struct pxecfg_menu *menu)
> +{
> +	struct pxecfg_menu_item *iter;
> +
> +	if (menu->title)
> +		printf("Menu: %s\n", menu->title);
> +
> +	iter = menu->labels;
> +
> +	while (iter) {
> +		print_label(iter);
> +
> +		iter = iter->next;
> +	}
> +}

Can we please split off the menu part, and make it generally usable?
Eventually we might look at other implementations of such code for
more general use?


> +		pxecfg_ram = getenv("pxecfg_ram");
> +		if (pxecfg_ram == NULL) {
> +			printf("Missing pxecfg_ram in environment\n");
> +			return 1;
> +		}

Eventually you may want to factor out this code which I see
repeatedly, and provide a unified error message with it.

> +int do_pxecfg(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
> +{
> +	if (argc < 2) {
> +		printf("pxecfg requires at least one argument\n");
> +		return EINVAL;
> +	}
> +
> +	if (!strcmp(argv[1], "get"))
> +		return get_pxecfg(argc, argv);
> +
> +	if (!strcmp(argv[1], "boot"))
> +		return boot_pxecfg(argc, argv);
> +
> +	printf("Invalid pxecfg command: %s\n", argv[1]);
> +
> +	return EINVAL;
> +}

> diff --git a/include/common.h b/include/common.h
> index fd389e7..597e757 100644
> --- a/include/common.h
> +++ b/include/common.h
> @@ -257,6 +257,11 @@ extern ulong load_addr;		/* Default Load Address */
>  /* common/cmd_doc.c */
>  void	doc_probe(unsigned long physadr);
>  
> +/* common/cmd_net.c */
> +#ifdef CONFIG_CMD_NET
> +int do_tftpb(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]);
> +#endif

You can drop the #ifdef here.

Best regards,

Wolfgang Denk
Jason Hobbs June 20, 2011, 11:04 p.m. UTC | #2
Dear Wolfgang,

I've prepared a new series of patches that I believe adresses most of
your comments, but I have a few responses and a question before sending
the new patch series out.

On Mon, Jun 06, 2011 at 09:45:22PM +0200, Wolfgang Denk wrote:
> > Add pxecfg command, which is intended to mimic PXELINUX functionality.
> > 'pxecfg get' uses tftp to retrieve a file based on UUID, MAC address or
> > IP address. 'pxecfg boot' interprets the contents of PXELINUX config
> > like file to boot using a specific initrd, kernel and kernel command
> > line.
> > 
> > This patch also adds a README.pxecfg file - see it for more details on
> > the pxecfg command.
> 
> Any reason for not calling this command simply "pxe" ?

PXE itself is a protocol made of some additional options in BOOTP
requests and responses, along with a bunch of specifics about what a
host platform is supposed to implement to run PXE images. This doesn't
provide PXE proper, it just mimics what PXELINUX does. PXELINUX isn't
PXE either - it just uses PXE to get its job done, by being retrieved
via the PXE protocol and using the PXE runtime environment to retrieve
files and continue booting. Since this isn't PXE and it isn't PXELINUX,
I made up 'pxecfg', and welcome other naming suggetions.
 
> > +	if (bootfile_path == NULL) {
> > +		printf("No bootfile path in environment.\n");
> > +		return -ENOENT;
> > +	}
> > +
> > +	path_len = strlen(bootfile_path) + strlen(file_path) + 1;
> > +
> > +	if (path_len > MAX_TFTP_PATH_LEN) {
> > +		printf("Base path too long (%s/%s)\n",
> > +				bootfile_path, file_path);
> > +
> > +		free(bootfile_path);
> > +		return -ENAMETOOLONG;
> > +	}
> > +
> > +	sprintf(bootfile, "%s/%s", bootfile_path, file_path);
> > +
> > +	free(bootfile_path);
> > +
> > +	printf("Retreiving file: %s\n", bootfile);
> > +
> > +	sprintf(addr_buf, "%p", file_addr);
> > +
> > +	tftp_argv[1] = addr_buf;
> > +	tftp_argv[2] = bootfile;
> 
> This looks like an extremly complicated way for a plain TFTP
> download. Why are you performing all this acrobatics and juggling
> with bootfile, instead of simply using what the user provided?

The directory (if any) that bootfile exists in is to be used as the base
for relative paths given in PXELINUX files.  pxecfg should follow the
same pattern, hence the acrobatics to modify what's given to us by the
user to prepend the base directory.

> > +	/*
> > +	 * We must dup the environment variable value here, because getenv
> > +	 * returns a pointer directly into the environment, and the contents
> > +	 * of the environment might shift during execution of a command.
> > +	 */
> > +	dupcmd = strdup(localcmd);
> 
> What do you mean by "the contents of the environment might shift" ???

One scenario is a localcmd that assigns a new value to the localcmd
variable as part of its execution:

  setenv("localcmd", "setenv localcmd; iminfo 0x0);
  localcmd = getenv("localcmd");
  run_command2(localcmd, 0);

From a previous patch in the series, run_command2() calls either
run_command() or parse_string_outer() depending on whether or not hush
is enabled. Should the simple/hush command execution always be safe with
a command like this?

> > +	printf("running: %s\n", dupcmd);
> 
> This should probably be a debug() ?

I don't think so - it's the one place a user watching the screen would
get to see what is being executed, which might be very useful to "end
users" who aren't eager to recompile to get some verbosity.

Thanks,
Jason
diff mbox

Patch

diff --git a/common/Makefile b/common/Makefile
index f81cff9..7e5e16e 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -136,6 +136,7 @@  COBJS-$(CONFIG_CMD_PCI) += cmd_pci.o
 endif
 COBJS-y += cmd_pcmcia.o
 COBJS-$(CONFIG_CMD_PORTIO) += cmd_portio.o
+COBJS-$(CONFIG_CMD_PXECFG) += cmd_pxecfg.o
 COBJS-$(CONFIG_CMD_REGINFO) += cmd_reginfo.o
 COBJS-$(CONFIG_CMD_REISER) += cmd_reiser.o
 COBJS-$(CONFIG_CMD_SATA) += cmd_sata.o
diff --git a/common/cmd_pxecfg.c b/common/cmd_pxecfg.c
new file mode 100644
index 0000000..d761ceb
--- /dev/null
+++ b/common/cmd_pxecfg.c
@@ -0,0 +1,1070 @@ 
+/*
+ * Copyright 2010-2011 Calxeda, Inc.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <common.h>
+#include <command.h>
+#include <malloc.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <errno.h>
+
+#define MAX_TFTP_PATH_LEN 127
+
+/*
+ * Returns the ethaddr environment variable formated with -'s instead of :'s
+ */
+static void format_mac_pxecfg(char **outbuf)
+{
+	char *p, *ethaddr;
+
+	*outbuf = NULL;
+
+	ethaddr = getenv("ethaddr");
+
+	if (!ethaddr)
+		return;
+
+	*outbuf = strdup(ethaddr);
+
+	if (*outbuf == NULL)
+		return;
+
+	for (p = *outbuf; *p; p++)
+		if (*p == ':')
+			*p = '-';
+}
+
+/*
+ * Returns the directory the file specified in the bootfile env variable is
+ * in.
+ */
+static char *get_bootfile_path(void)
+{
+	char *bootfile, *bootfile_path, *last_slash;
+
+	bootfile = getenv("bootfile");
+
+	if (bootfile == NULL) {
+		printf("Missing bootfile environment variable.\n");
+		return NULL;
+	}
+
+	bootfile_path = strdup(bootfile);
+
+	if (bootfile_path == NULL) {
+		printf("oom\n");
+		return NULL;
+	}
+
+	last_slash = strrchr(bootfile_path, '/');
+
+	if (last_slash == NULL) {
+		printf("Invalid bootfile path (%s), no '/' found\n",
+				bootfile_path);
+
+		return NULL;
+	}
+
+	*last_slash = '\0';
+
+	return bootfile_path;
+}
+
+static int get_relfile(char *file_path, void *file_addr)
+{
+	char *bootfile_path;
+	size_t path_len;
+	char bootfile[MAX_TFTP_PATH_LEN+1];
+	char addr_buf[10];
+	char *tftp_argv[] = {"tftp", NULL, NULL, NULL};
+
+	bootfile_path = get_bootfile_path();
+
+	if (bootfile_path == NULL) {
+		printf("No bootfile path in environment.\n");
+		return -ENOENT;
+	}
+
+	path_len = strlen(bootfile_path) + strlen(file_path) + 1;
+
+	if (path_len > MAX_TFTP_PATH_LEN) {
+		printf("Base path too long (%s/%s)\n",
+				bootfile_path, file_path);
+
+		free(bootfile_path);
+		return -ENAMETOOLONG;
+	}
+
+	sprintf(bootfile, "%s/%s", bootfile_path, file_path);
+
+	free(bootfile_path);
+
+	printf("Retreiving file: %s\n", bootfile);
+
+	sprintf(addr_buf, "%p", file_addr);
+
+	tftp_argv[1] = addr_buf;
+	tftp_argv[2] = bootfile;
+
+	if (do_tftpb(NULL, 0, 3, tftp_argv)) {
+		printf("File not found\n");
+		return -ENOENT;
+	}
+
+	return 1;
+}
+
+static int get_pxecfg_file(char *file_path, void *file_addr)
+{
+	unsigned long config_file_size;
+	int err;
+
+	err = get_relfile(file_path, file_addr);
+
+	if (err < 0)
+		return err;
+
+	config_file_size = simple_strtoul(getenv("filesize"), NULL, 16);
+	*(char *)(file_addr + config_file_size) = '\0';
+
+	return 1;
+}
+
+
+#define MAX_CFG_PATHS	12
+
+/*
+ * Returns a list of paths according to the pxelinux rules for paths to a
+ * config file.  The base of each path is the same -
+ * bootfilepath/pxelinux.cfg/uniquepart. bootfilepath is the directory
+ * bootfile (from the dhcp request) was in, joined with '/pxelinux.cfg'.  The
+ * unique part of each file comes from these things, in this order:
+ *
+ * pxeuuid env variable verbatim, if defined
+ * ethaddr env variable, reformatted with -'s instead of :'s
+ * hexadecimal formatted IP address
+ * hexadecimal IP address with last hex digit removed
+ * repeat removing another character until none are left
+ * the string 'default'
+ *
+ * see http://syslinux.zytor.com/wiki/index.php/PXELINUX
+ */
+static char **pxecfg_paths(char *uuid_str, char *mac_addr)
+{
+	char **ret_array;
+	char ip_addr[9];
+	int x, end_masks, mask_pos;
+	size_t base_len = strlen("pxelinux.cfg/");
+
+	sprintf(ip_addr, "%08X", ntohl(NetOurIP));
+
+	ret_array = malloc(MAX_CFG_PATHS * sizeof(char *));
+
+	if (ret_array == NULL) {
+		printf("oom\n");
+		return NULL;
+	}
+
+	for (x = 0; x < MAX_CFG_PATHS; x++)
+		ret_array[x] = NULL;
+
+	x = 0;
+
+	if (uuid_str) {
+		if (base_len + strlen(uuid_str) > MAX_TFTP_PATH_LEN)
+			printf("uuid_str is too long, skipping\n");
+		else  {
+			ret_array[x] = malloc(MAX_TFTP_PATH_LEN + 1);
+			if (!ret_array[x])
+				goto oom_error;
+
+			sprintf(ret_array[x++], "pxelinux.cfg/%s", uuid_str);
+		}
+	}
+
+	if (mac_addr) {
+		if (base_len + strlen(mac_addr) > MAX_TFTP_PATH_LEN)
+			printf("ethaddr is too long, skipping\n");
+		else {
+			ret_array[x] = malloc(MAX_TFTP_PATH_LEN + 1);
+			if (!ret_array[x])
+				goto oom_error;
+
+			sprintf(ret_array[x++], "pxelinux.cfg/%s", mac_addr);
+		}
+	}
+
+	for (end_masks = x + 8, mask_pos = 7; x < end_masks; x++, mask_pos--) {
+		ret_array[x] = malloc(MAX_TFTP_PATH_LEN + 1);
+		if (!ret_array[x])
+			goto oom_error;
+
+		sprintf(ret_array[x], "pxelinux.cfg/%s", ip_addr);
+
+		ip_addr[mask_pos] = '\0';
+	}
+
+	ret_array[x] = malloc(MAX_TFTP_PATH_LEN + 1);
+	if (!ret_array[x])
+		goto oom_error;
+
+	strcpy(ret_array[x++], "pxelinux.cfg/default");
+
+	return ret_array;
+
+oom_error:
+	printf("oom\n");
+
+	if (ret_array) {
+		while (ret_array[x])
+			free(ret_array[x++]);
+
+		free(ret_array);
+	}
+
+	return NULL;
+}
+
+/*
+ * Follows pxelinux's rules to download a pxecfg file from a tftp server.  The
+ * file is stored at the location given by the pxecfg_addr environment
+ * variable, which must be set.
+ */
+static int get_pxecfg(int argc, char * const argv[])
+{
+	char *mac_addr, *pxecfg_ram, **cfg_paths = NULL;
+	int i, cfg_file_found = 0, err;
+	void *pxecfg_addr;
+
+	pxecfg_ram = getenv("pxecfg_ram");
+
+	if (!pxecfg_ram) {
+		printf("Missing pxecfg_ram\n");
+		return 1;
+	}
+
+	pxecfg_addr = (void *)simple_strtoul(pxecfg_ram, NULL, 16);
+
+	format_mac_pxecfg(&mac_addr);
+
+	cfg_paths = pxecfg_paths(getenv("pxeuuid"), mac_addr);
+
+	if (cfg_paths == NULL)
+		return 1;
+
+	for (i = 0; cfg_paths[i]; i++) {
+		err = get_pxecfg_file(cfg_paths[i], pxecfg_addr);
+		if (err > 0) {
+			cfg_file_found = 1;
+			break;
+		}
+	}
+
+	for (i = 0; cfg_paths[i]; i++)
+		free(cfg_paths[i]);
+
+	if (!cfg_file_found)
+		return 1;
+
+	printf("Config file found!\n");
+
+	return 0;
+}
+
+struct pxecfg_menu_item {
+	char *name;
+	char *kernel;
+	char *append;
+	char *initrd;
+	int attempted;
+	int localboot;
+	struct pxecfg_menu_item *next;
+};
+
+struct pxecfg_menu {
+	char *default_label;
+	int timeout;
+	char *title;
+	int prompt;
+	struct pxecfg_menu_item *labels;
+};
+
+static void add_label(struct pxecfg_menu *menu, struct pxecfg_menu_item *item)
+{
+	struct pxecfg_menu_item *iter;
+
+	if (menu->labels == NULL) {
+		menu->labels = item;
+	} else {
+		iter = menu->labels;
+
+		while (iter->next != NULL)
+			iter = iter->next;
+
+		iter->next = item;
+	}
+
+	item->kernel = NULL;
+	item->append = NULL;
+	item->initrd = NULL;
+	item->next = NULL;
+	item->localboot = 0;
+	item->attempted = 0;
+}
+
+struct pxecfg_menu_item *label_by_name(struct pxecfg_menu_item *head,
+					char *name)
+{
+	struct pxecfg_menu_item *iter_label = head;
+
+	while (iter_label)
+		if (!strcmp(name, iter_label->name))
+			return iter_label;
+		else
+			iter_label = iter_label->next;
+
+	return NULL;
+}
+
+enum token_type {
+	T_EOL,
+	T_STRING,
+	T_EOF,
+	T_MENU,
+	T_TITLE,
+	T_TIMEOUT,
+	T_LABEL,
+	T_KERNEL,
+	T_APPEND,
+	T_INITRD,
+	T_LOCALBOOT,
+	T_DEFAULT,
+	T_PROMPT,
+	T_INCLUDE,
+	T_INVALID
+};
+
+struct token {
+	char *val;
+	enum token_type type;
+};
+
+enum lex_state {
+	L_NORMAL = 0,
+	L_KEYWORD,
+	L_SLITERAL
+};
+
+static const struct token keywords[] = {
+	{"menu", T_MENU},
+	{"title", T_TITLE},
+	{"timeout", T_TIMEOUT},
+	{"default", T_DEFAULT},
+	{"prompt", T_PROMPT},
+	{"label", T_LABEL},
+	{"kernel", T_KERNEL},
+	{"localboot", T_LOCALBOOT},
+	{"append", T_APPEND},
+	{"initrd", T_INITRD},
+	{"include", T_INCLUDE},
+	{NULL, T_INVALID}
+};
+
+static char *get_string(char **p, struct token *t, char delim, int lower)
+{
+	char *b, *e;
+	size_t len, i;
+
+	b = e = *p;
+
+	while (*e) {
+		if ((delim == ' ' && isspace(*e)) || delim == *e)
+			break;
+		e++;
+	}
+
+	len = e - b;
+
+	t->val = malloc(len + 1);
+	if (!t->val) {
+		printf("oom\n");
+		return NULL;
+	}
+
+	for (i = 0; i < len; i++, b++) {
+		if (lower)
+			t->val[i] = tolower(*b);
+		else
+			t->val[i] = *b;
+	}
+
+	t->val[len] = '\0';
+
+	*p = e;
+
+	t->type = T_STRING;
+
+	return t->val;
+}
+
+static void get_keyword(struct token *t)
+{
+	int i;
+
+	for (i = 0; keywords[i].val; i++) {
+		if (!strcmp(t->val, keywords[i].val)) {
+			t->type = keywords[i].type;
+			break;
+		}
+	}
+}
+
+static void get_token(char **p, struct token *t, enum lex_state state)
+{
+	char *c = *p;
+
+	t->type = T_INVALID;
+
+	/* eat non EOL whitespace */
+	while (*c == ' ' || *c == '\t')
+		c++;
+
+	/* eat comments */
+	if (*c == '#')
+		while (*c && *c != '\n')
+			c++;
+
+	if (*c == '\n') {
+		t->type = T_EOL;
+		c++;
+	} else if (*c == '\0') {
+		t->type = T_EOF;
+		c++;
+	} else if (state == L_SLITERAL)
+		get_string(&c, t, '\n', 0);
+	else if (state == L_KEYWORD) {
+		get_string(&c, t, ' ', 1);
+		get_keyword(t);
+	}
+
+	*p = c;
+}
+
+static void eol_or_eof(char **c)
+{
+	while (**c && **c != '\n')
+		(*c)++;
+}
+
+static int parse_sliteral(char **c, char **dst)
+{
+	struct token t;
+	char *s = *c;
+
+	get_token(c, &t, L_SLITERAL);
+
+	if (t.type != T_STRING) {
+		printf("Expected string literal: %.*s\n", (int)(*c - s), s);
+		return -EINVAL;
+	}
+
+	*dst = t.val;
+
+	return 1;
+}
+
+static int parse_integer(char **c, int *dst)
+{
+	struct token t;
+	char *s = *c;
+
+	get_token(c, &t, L_SLITERAL);
+
+	if (t.type != T_STRING) {
+		printf("Expected string: %.*s\n", (int)(*c - s), s);
+		return -EINVAL;
+	}
+
+	*dst = (int)simple_strtoul(t.val, NULL, 10);
+
+	free(t.val);
+
+	return 1;
+}
+
+static int parse_pxecfg_top(char *p, struct pxecfg_menu *m, int nest_level);
+
+static int handle_include(char **c, char *base, struct pxecfg_menu *m,
+						int nest_level)
+{
+	char *include_path;
+	int err;
+
+	err = parse_sliteral(c, &include_path);
+
+	if (err < 0) {
+		printf("Expected include path\n");
+		return err;
+	}
+
+	err = get_pxecfg_file(include_path, base);
+
+	if (err < 0) {
+		printf("Couldn't get %s\n", include_path);
+		return err;
+	}
+
+	return parse_pxecfg_top(base, m, nest_level);
+}
+
+static int parse_menu(char **c, struct pxecfg_menu *m, char *b, int nest_level)
+{
+	struct token t;
+	char *s = *c;
+	int err;
+
+	get_token(c, &t, L_KEYWORD);
+
+	switch (t.type) {
+	case T_TITLE:
+		err = parse_sliteral(c, &m->title);
+		break;
+
+	case T_INCLUDE:
+		err = handle_include(c, b + strlen(b) + 1, m,
+						nest_level + 1);
+		break;
+
+	default:
+		printf("Ignoring malformed menu command: %.*s\n",
+				(int)(*c - s), s);
+	}
+
+	if (err < 0)
+		return err;
+
+	eol_or_eof(c);
+
+	return 1;
+}
+
+static int parse_label_menu(char **c, struct pxecfg_menu *m,
+				struct pxecfg_menu_item *i)
+{
+	struct token t;
+	char *s;
+
+	s = *c;
+
+	get_token(c, &t, L_KEYWORD);
+
+	switch (t.type) {
+	case T_DEFAULT:
+		if (m->default_label != NULL)
+			free(m->default_label);
+
+		m->default_label = i->name;
+		break;
+	default:
+		printf("Ignoring malformed menu command: %.*s\n",
+				(int)(*c - s), s);
+	}
+
+	eol_or_eof(c);
+
+	return 0;
+}
+
+static int parse_label(char **c, struct pxecfg_menu *m)
+{
+	struct token t;
+	char *s;
+	struct pxecfg_menu_item *i;
+	int err;
+
+	i = malloc(sizeof(*i));
+
+	if (!i)
+		return -ENOMEM;
+
+	err = parse_sliteral(c, &i->name);
+
+	if (err < 0) {
+		printf("Expected label name\n");
+		return -EINVAL;
+	}
+
+	add_label(m, i);
+
+	while (1) {
+		s = *c;
+		get_token(c, &t, L_KEYWORD);
+
+		err = 0;
+		switch (t.type) {
+		case T_MENU:
+			err = parse_label_menu(c, m, i);
+			break;
+
+		case T_KERNEL:
+			err = parse_sliteral(c, &i->kernel);
+			break;
+
+		case T_APPEND:
+			err = parse_sliteral(c, &i->append);
+			break;
+
+		case T_INITRD:
+			err = parse_sliteral(c, &i->initrd);
+			break;
+
+		case T_LOCALBOOT:
+			err = parse_integer(c, &i->localboot);
+			break;
+
+		case T_EOL:
+			break;
+
+		/*
+		 * A label ends when we either get to the end of a file, or
+		 * get some input we otherwise don't have a handler defined
+		 * for.
+		 */
+		default:
+			/* put it back */
+			*c = s;
+			return 1;
+		}
+
+		if (err < 0)
+			return err;
+	}
+}
+
+#define MAX_NEST_LEVEL 16
+
+static int parse_pxecfg_top(char *p, struct pxecfg_menu *m, int nest_level)
+{
+	struct token t;
+	char *s, *b;
+	int err;
+
+	b = p;
+
+	if (nest_level > MAX_NEST_LEVEL) {
+		printf("Maximum nesting exceeded\n");
+		return -EMLINK;
+	}
+
+	while (1) {
+		s = p;
+
+		get_token(&p, &t, L_KEYWORD);
+
+		err = 0;
+		switch (t.type) {
+		case T_MENU:
+			err = parse_menu(&p, m, b, nest_level);
+			break;
+
+		case T_TIMEOUT:
+			err = parse_integer(&p, &m->timeout);
+			break;
+
+		case T_LABEL:
+			err = parse_label(&p, m);
+			break;
+
+		case T_DEFAULT:
+			err = parse_sliteral(&p, &m->default_label);
+			break;
+
+		case T_PROMPT:
+			err = parse_integer(&p, &m->prompt);
+			break;
+
+		case T_EOL:
+			break;
+
+		case T_EOF:
+			return 1;
+
+		default:
+			printf("Ignoring unknown command: %.*s\n",
+							(int)(p - s), s);
+			eol_or_eof(&p);
+		}
+
+		if (err < 0)
+			return err;
+	}
+}
+
+static struct pxecfg_menu *parse_pxecfg(char *menucfg)
+{
+	struct pxecfg_menu *menu;
+
+	menu = malloc(sizeof(*menu));
+
+	if (menu == NULL) {
+		printf("oom\n");
+		return NULL;
+	}
+
+	menu->timeout = -1;
+	menu->prompt = 0;
+	menu->default_label = NULL;
+	menu->title = NULL;
+	menu->labels = NULL;
+
+	if (parse_pxecfg_top(menucfg, menu, 1) < 0) {
+		free(menu);
+		return NULL;
+	}
+
+	return menu;
+}
+
+static int perform_localboot(struct pxecfg_menu_item *label)
+{
+	char *localcmd, *dupcmd;
+	int ret;
+
+	localcmd = getenv("localcmd");
+
+	if (!localcmd) {
+		printf("localcmd not defined\n");
+		return -ENOENT;
+	}
+
+	/*
+	 * We must dup the environment variable value here, because getenv
+	 * returns a pointer directly into the environment, and the contents
+	 * of the environment might shift during execution of a command.
+	 */
+	dupcmd = strdup(localcmd);
+
+	if (!dupcmd) {
+		printf("oom\n");
+		return -ENOMEM;
+	}
+
+	if (label->append)
+		setenv("bootargs", label->append);
+
+	printf("running: %s\n", dupcmd);
+
+	ret = run_command2(dupcmd, 0);
+
+	free(dupcmd);
+
+	return ret;
+}
+
+static int get_relfile_envaddr(char *file_path, char *envaddr_name)
+{
+	void *file_addr;
+	char *envaddr;
+
+	envaddr = getenv(envaddr_name);
+
+	if (!envaddr) {
+		printf("missing: %s\n", envaddr_name);
+		return -ENOENT;
+	}
+
+	file_addr = (void *)simple_strtoul(envaddr, NULL, 16);
+
+	return get_relfile(file_path, file_addr);
+}
+
+static void print_label(struct pxecfg_menu_item *label)
+{
+	printf("Label: %s\n", label->name);
+
+	if (label->kernel)
+		printf("\tkernel: %s\n", label->kernel);
+
+	if (label->append)
+		printf("\tappend: %s\n", label->append);
+
+	if (label->initrd)
+		printf("\tinitrd: %s\n", label->initrd);
+}
+
+static void print_menu(struct pxecfg_menu *menu)
+{
+	struct pxecfg_menu_item *iter;
+
+	if (menu->title)
+		printf("Menu: %s\n", menu->title);
+
+	iter = menu->labels;
+
+	while (iter) {
+		print_label(iter);
+
+		iter = iter->next;
+	}
+}
+
+static void destroy_label(struct pxecfg_menu_item *label)
+{
+	if (label->name)
+		free(label->name);
+
+	if (label->kernel)
+		free(label->kernel);
+
+	if (label->append)
+		free(label->append);
+
+	if (label->initrd)
+		free(label->initrd);
+
+	free(label);
+}
+
+static void destroy_menu(struct pxecfg_menu *menu)
+{
+	struct pxecfg_menu_item *iter, *next;
+
+	if (!menu)
+		return;
+
+	iter = menu->labels;
+
+	while (iter) {
+		next = iter->next;
+		destroy_label(iter);
+		iter = next;
+	}
+
+	free(menu);
+}
+
+/*
+ * Do what it takes to boot a chosen label.
+ *
+ * Retreive the kernel and initrd, and prepare bootargs.
+ */
+static void boot_label(struct pxecfg_menu_item *label)
+{
+	char *bootm_argv[] = { "bootm", NULL, NULL, NULL, NULL };
+
+	print_label(label);
+
+	label->attempted = 1;
+
+	if (label->localboot) {
+		perform_localboot(label);
+		return;
+	}
+
+	if (label->kernel == NULL) {
+		printf("No kernel given, skipping label\n");
+		return;
+	}
+
+	if (label->initrd) {
+		if (get_relfile_envaddr(label->initrd, "initrd_ram") < 0) {
+			printf("Skipping label\n");
+			return;
+		}
+
+		bootm_argv[2] = getenv("initrd_ram");
+	} else
+		bootm_argv[2] = "-";
+
+	if (get_relfile_envaddr(label->kernel, "kernel_ram") < 0) {
+		printf("Skipping label\n");
+		return;
+	}
+
+	if (label->append)
+		setenv("bootargs", label->append);
+
+	bootm_argv[1] = getenv("kernel_ram");
+
+	/*
+	 * fdt usage is optional - if unset, this stays NULL.
+	 */
+	bootm_argv[3] = getenv("fdtaddr");
+
+	do_bootm(NULL, 0, 4, bootm_argv);
+}
+
+/*
+ * Change the default to the user's choice.
+ */
+static void user_choice(struct pxecfg_menu *menu)
+{
+	char cbuf[CONFIG_SYS_CBSIZE];
+	struct pxecfg_menu_item *choice_label = NULL;
+
+	while (!choice_label) {
+		int readret;
+
+		cbuf[0] = '\0';
+
+		print_menu(menu);
+
+		readret = readline_into_buffer("Enter label: ", cbuf);
+
+		if (readret >= 0) {
+			choice_label = label_by_name(menu->labels, cbuf);
+
+			if (!choice_label)
+				printf("%s not found\n", cbuf);
+			else
+				boot_label(choice_label);
+		} else {
+			printf("^C\n");
+			return;
+		}
+	}
+}
+
+/*
+ * Honor the timeout period from the cfg file or from the bootdelay
+ * environment variable.
+ *
+ * If interrupted, allow the user to interactively choose a menu to boot from.
+ */
+static int delay_for_input(int timeout)
+{
+	char *bootdelay;
+
+	bootdelay = getenv("bootdelay");
+
+	if (timeout == -1) {
+		if (bootdelay)
+			timeout = simple_strtol(bootdelay, NULL, 10);
+		else
+			timeout = 0;
+	}
+
+	if (timeout) {
+		printf("using the timeout: %d\n", timeout);
+
+		if (_abortboot(timeout/10)) {
+			printf("autoboot aborted!\n");
+			return 1;
+		}
+	}
+
+	return 0;
+}
+
+static void handle_pxecfg(struct pxecfg_menu *menu)
+{
+	struct pxecfg_menu_item *iter_label, *label = NULL;
+
+	if (menu->labels == NULL) {
+		printf("Configuration provided no labels, can't boot\n");
+		return;
+	}
+
+	if (menu->prompt || delay_for_input(menu->timeout)) {
+		user_choice(menu);
+		return;
+	}
+
+	if (menu->default_label) {
+		printf("Attempting to boot from default label: %s\n",
+				menu->default_label);
+
+		label = label_by_name(menu->labels, menu->default_label);
+
+		if (label == NULL)
+			label = menu->labels;
+	} else
+		label = menu->labels;
+
+	while (label) {
+		boot_label(label);
+
+		label = NULL;
+
+		iter_label = menu->labels;
+
+		while (!label && iter_label) {
+			if (!iter_label->attempted)
+				label = iter_label;
+
+			iter_label = iter_label->next;
+		}
+	}
+}
+
+static int boot_pxecfg(int argc, char * const argv[])
+{
+	unsigned long pxecfg_addr;
+	struct pxecfg_menu *menu;
+	char *pxecfg_ram;
+
+	if (argc == 2) {
+		pxecfg_ram = getenv("pxecfg_ram");
+		if (pxecfg_ram == NULL) {
+			printf("Missing pxecfg_ram in environment\n");
+			return 1;
+		}
+
+		pxecfg_addr = simple_strtoul(pxecfg_ram, NULL, 16);
+	} else if (argc == 3) {
+		pxecfg_addr = simple_strtoul(argv[2], NULL, 16);
+	} else {
+		printf("Invalid number of arguments\n");
+		return 1;
+	}
+
+	menu = parse_pxecfg((char *)(pxecfg_addr));
+
+	if (menu == NULL) {
+		printf("Error parsing config file\n");
+		return 1;
+	}
+
+	handle_pxecfg(menu);
+
+	destroy_menu(menu);
+
+	return 0;
+}
+
+int do_pxecfg(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
+{
+	if (argc < 2) {
+		printf("pxecfg requires at least one argument\n");
+		return EINVAL;
+	}
+
+	if (!strcmp(argv[1], "get"))
+		return get_pxecfg(argc, argv);
+
+	if (!strcmp(argv[1], "boot"))
+		return boot_pxecfg(argc, argv);
+
+	printf("Invalid pxecfg command: %s\n", argv[1]);
+
+	return EINVAL;
+}
+
+U_BOOT_CMD(
+	pxecfg, 2, 1, do_pxecfg,
+	"commands to get and boot from pxecfg files",
+	"get - try to retrieve a pxecfg file using tftp\npxecfg "
+	"boot [pxecfg_addr] - boot from the pxecfg file at pxecfg_addr\n"
+);
diff --git a/doc/README.pxecfg b/doc/README.pxecfg
new file mode 100644
index 0000000..57abef8
--- /dev/null
+++ b/doc/README.pxecfg
@@ -0,0 +1,238 @@ 
+/*
+ * Copyright 2010-2011 Calxeda, Inc.
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+The pxecfg commands provide a near subset of the functionality provided by
+the PXELINUX boot loader. This allows U-boot based systems to be controlled
+remotely using the same PXE based techniques that many non U-boot based servers
+use. To avoid identity confusion with PXELINUX, and because not all behavior is
+identical, we call this feature 'pxecfg'.
+
+Commands
+========
+
+pxecfg get
+----------
+     syntax: pxecfg get
+
+     follows PXELINUX's rules for retrieving configuration files from a tftp
+     server, and supports a subset of PXELINUX's config file syntax.
+
+     Environment
+     -----------
+     get_pxecfg requires two environment variables to be set:
+
+     pxecfg_ram - should be set to a location in RAM large enough to hold
+     pxecfg files while they're being processed. Up to 16 config files may be
+     held in memory at once. The exact number and size of the files varies with
+     how the system is being used. A typical config file is a few hundred bytes
+     long.
+
+     bootfile,serverip - these two are typically set in the DHCP response
+     handler, and correspond to fields in the DHCP response.
+
+     get_pxecfg optionally supports these two environment variables being set:
+
+     ethaddr - this is the standard MAC address for the ethernet adapter in use.
+     getpxe_cfg uses it to look for a configuration file specific to a system's
+     MAC address.
+
+     pxeuuid - this is a UUID in standard form using lower case hexadecimal
+     digits, for example, 550e8400-e29b-41d4-a716-446655440000. get_pxecfg uses
+     it to look for a configuration file based on the system's UUID.
+
+     File Paths
+     ----------
+     get_pxecfg repeatedly tries to download config files until it either
+     successfully downloads one or runs out of paths to try. The order and
+     contents of paths it tries mirrors exactly that of PXELINUX - you can read
+     in more detail about it at:
+
+     http://syslinux.zytor.com/wiki/index.php/Doc/pxelinux
+
+pxecfg boot
+-----------
+     syntax: pxecfg boot [pxecfg_addr]
+
+     Interprets a pxecfg file stored in memory.
+
+     pxecfg_addr is an optional argument giving the location of the pxecfg file
+
+     Environment
+     -----------
+     There are some environment variables that may need to be set, depending on
+     conditions.
+
+     pxecfg_ram - if the optional argument pxecfg_addr is not supplied, an
+     environment variable named pxecfg_ram must be supplied. This is typically
+     the same value as is used for the get_pxecfg command.
+
+     bootfile - typically set in the DHCP response handler based on the same
+     field in the DHCP respone, this path is used to generate the base directory
+     that all other paths to files retrieved by boot_pxecfg will use.
+
+     serverip - typically set in the DHCP response handler, this is the IP
+     address of the tftp server from which other files will be retrieved.
+
+     kernel_ram,initrd_ram - locations in RAM at which boot_pxecfg will store
+     the kernel and initrd it retrieves from tftp. These locations will be
+     passed to the bootm command to boot the kernel. These environment variables
+     are required to be set.
+
+     fdtaddr - the location of a fdt blob. If this is set, it will be passed to
+     bootm when booting a kernel.
+
+pxecfg file format
+==================
+The pxecfg file format is more or less a subset of the PXELINUX file format, see
+http://syslinux.zytor.com/wiki/index.php/PXELINUX. It's composed of one line
+commands - global commands, and commands specific to labels. Lines begining with
+# are treated as comments. White space between and at the beginning of lines is
+ignored.
+
+The size of pxecfg files and the number of labels is only limited by the amount
+of RAM available to U-boot. Memory for labels is dynamically allocated as
+they're parsed, and memory for pxecfg files is statically allocated, and its
+location is given by the pxecfg_ram environment variable. the pxecfg code is
+not aware of the size of the pxecfg memory and will outgrow it if pxecfg files
+are too large.
+
+Supported global commands
+-------------------------
+Unrecognized commands are ignored.
+
+default <label>     - the label named here is treated as the default and is
+                      the first label boot_pxecfg attempts to boot.
+
+menu title <string> - sets a title for the menu of labels being displayed.
+
+menu include <path> - use tftp to retrieve the pxecfg file at <path>, which
+                      is then immediately parsed as if the start of its
+                      contents were the next line in the current file. nesting
+                      of include up to 16 files deep is supported.
+
+prompt <flag>       - if 1, always prompt the user to enter a label to boot
+                      from. if 0, only prompt the user if timeout expires.
+
+timeout <num>	    - wait for user input for <num>/10 seconds before
+                      auto-booting a node.
+
+label <name>        - begin a label definition. labels continue until
+                      a command not recognized as a label command is seen,
+                      or EOF is reached.
+
+Supported label commands
+------------------------
+labels end when a command not recognized as a label command is reached, or EOF.
+
+menu default        - set this label as the default label to boot; this is
+                      the same behavior as the global default command but
+                      specified in a different way
+
+kernel <path>       - if this label is chosen, use tftp to retrieve the kernel
+                      at <path>. it will be stored at the address indicated in
+                      the kernel_ram environment variable, and that address
+                      will be passed to bootm to boot this kernel.
+
+append <string>     - use <string> as the kernel command line when booting this
+                      label.
+
+initrd <path>       - if this label is chosen, use tftp to retrieve the initrd
+                      at <path>. it will be stored at the address indicated in
+                      the initrd_ram environment variable, and that address
+                      will be passed to bootm.
+
+localboot <flag>    - Run the command defined by "localcmd" in the environment.
+                      <flag> is ignored and is only here to match the syntax of
+                      PXELINUX config files.
+
+Example
+-------
+Here's a couple of example files to show how this works.
+
+------------/tftpboot/pxelinux.cfg/menus/linux.list----------
+menu title Linux selections
+
+# This is the default label
+label install
+	menu label Default Install Image
+	kernel kernels/install.bin
+	append console=ttyAMA0,38400 debug earlyprintk
+	initrd initrds/uzInitrdDebInstall
+
+# Just another label
+label linux-2.6.38
+	kernel kernels/linux-2.6.38.bin
+	append root=/dev/sdb1
+
+# The locally installed kernel
+label local
+	menu label Locally installed kernel
+	append root=/dev/sdb1
+	localboot 1
+-------------------------------------------------------------
+
+------------/tftpboot/pxelinux.cfg/default-------------------
+menu include pxelinux.cfg/menus/base.menu
+timeout 500
+
+default linux-2.6.38
+-------------------------------------------------------------
+
+When a pxecfg client retrieves and boots the default pxecfg file,
+boot_pxecfg will wait for user input for 5 seconds before booting
+the linux-2.6.38 label, which will cause /tftpboot/kernels/linux-2.6.38.bin
+to be downloaded, and boot with the command line "root=/dev/sdb1"
+
+Differences with PXELINUX
+=========================
+The biggest difference between pxecfg and PXELINUX is that since pxecfg
+is part of U-boot and is written entirely in C, it can run on platform
+with network support in U-boot.  Here are some of the other differences
+between PXELINUX and pxecfg.
+
+- pxecfg does not support the PXELINUX DHCP option codes specified in
+  RFC 5071, but could be extended to do so.
+
+- when pxecfg fails to boot, it will return control to U-boot, allowing
+  another command to run, other U-boot command, instead of resetting the
+  machine like PXELINUX.
+
+- pxecfg doesn't rely on or provide an UNDI/PXE stack in memory, it only
+  uses U-boot.
+
+- pxecfg doesn't provide the full menu implementation that PXELINUX
+  does, only a simple text based menu using the commands described in
+  this README.  With PXELINUX, it's possible to have a graphical boot
+  menu, submenus, passwords, etc. pxecfg could be extended to support
+  a more robust menuing system like that of PXELINUX's.
+
+- pxecfg expects U-boot uimg's as kernels.  anything that would work with
+  the 'bootm' command in U-boot could work with pxecfg.
+
+- pxecfg doesn't recognize initrd options in the append command - you must
+  specify initrd files using the initrd command
+
+- pxecfg only recognizes a single file on the initrd command line.  it
+  could be extended to support multiple
+
+- in pxecfg, the localboot command doesn't necessarily cause a local
+  disk boot - it will do whatever is defined in the 'localcmd' env
+  variable. And since it doesn't support a full UNDI/PXE stack, the
+  type field is ignored.
+
+- the interactive prompt in pxecfg only allows you to choose a label from
+  the menu.  if you want to boot something not listed, you can ctrl+c out
+  of pxecfg and use existing U-boot commands to accomplish it.
diff --git a/include/common.h b/include/common.h
index fd389e7..597e757 100644
--- a/include/common.h
+++ b/include/common.h
@@ -257,6 +257,11 @@  extern ulong load_addr;		/* Default Load Address */
 /* common/cmd_doc.c */
 void	doc_probe(unsigned long physadr);
 
+/* common/cmd_net.c */
+#ifdef CONFIG_CMD_NET
+int do_tftpb(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]);
+#endif
+
 /* common/cmd_nvedit.c */
 int	env_init     (void);
 void	env_relocate (void);