[U-Boot] [PATCH v2 0/6] Add support for pxecfg commands

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'.
As an example, support for the pxecfg commands is enabled for the ca9x4_ct_vxp config.
Additional details are available in the README file added as part of this patch series.
v2 of this patch series separates the menu code from the pxecfg code, giving a reusable menu implementation. It also contains various smaller changes documented in the comment section of the patches.
Jason Hobbs (6): cosmetic, main: correct indentation/spacing issues common: add run_command2 for running simple or hush commands common: make abortboot available for menu use Add generic, reusable menu code Add pxecfg command arm: ca9x4_ct_vxp: enable pxecfg support
common/Makefile | 2 + common/cmd_pxecfg.c | 924 ++++++++++++++++++++++++++++++++++++++++ common/hush.c | 2 +- common/main.c | 60 ++-- common/menu.c | 338 +++++++++++++++ doc/README.pxecfg | 238 +++++++++++ include/common.h | 7 + include/configs/ca9x4_ct_vxp.h | 5 + include/hush.h | 2 +- include/menu.h | 40 ++ 10 files changed, 1584 insertions(+), 34 deletions(-) create mode 100644 common/cmd_pxecfg.c create mode 100644 common/menu.c create mode 100644 doc/README.pxecfg create mode 100644 include/menu.h

Signed-off-by: Jason Hobbs jason.hobbs@calxeda.com --- changes in v2: - new in v2
common/main.c | 12 ++++++------ 1 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/common/main.c b/common/main.c index dcbacc9..f05ee53 100644 --- a/common/main.c +++ b/common/main.c @@ -406,15 +406,15 @@ void main_loop (void)
# ifdef CONFIG_MENUKEY if (menukey == CONFIG_MENUKEY) { - s = getenv("menucmd"); - if (s) { + s = getenv("menucmd"); + if (s) { # ifndef CONFIG_SYS_HUSH_PARSER - run_command (s, 0); + run_command(s, 0); # else - parse_string_outer(s, FLAG_PARSE_SEMICOLON | - FLAG_EXIT_FROM_LOOP); + parse_string_outer(s, FLAG_PARSE_SEMICOLON | + FLAG_EXIT_FROM_LOOP); # endif - } + } } #endif /* CONFIG_MENUKEY */ #endif /* CONFIG_BOOTDELAY */

Signed-off-by: Jason Hobbs jason.hobbs@calxeda.com --- changes in v2: - whitespace correction
common/hush.c | 2 +- common/main.c | 46 +++++++++++++++++++--------------------------- include/common.h | 1 + include/hush.h | 2 +- 4 files changed, 22 insertions(+), 29 deletions(-)
diff --git a/common/hush.c b/common/hush.c index 85a6030..940889b 100644 --- a/common/hush.c +++ b/common/hush.c @@ -3217,7 +3217,7 @@ int parse_stream_outer(struct in_str *inp, int flag) #ifndef __U_BOOT__ static int parse_string_outer(const char *s, int flag) #else -int parse_string_outer(char *s, int flag) +int parse_string_outer(const char *s, int flag) #endif /* __U_BOOT__ */ { struct in_str input; diff --git a/common/main.c b/common/main.c index f05ee53..1f64523 100644 --- a/common/main.c +++ b/common/main.c @@ -342,12 +342,7 @@ void main_loop (void) int prev = disable_ctrlc(1); /* disable Control C checking */ # endif
-# ifndef CONFIG_SYS_HUSH_PARSER - run_command (p, 0); -# else - parse_string_outer(p, FLAG_PARSE_SEMICOLON | - FLAG_EXIT_FROM_LOOP); -# endif + run_command2(p, 0);
# ifdef CONFIG_AUTOBOOT_KEYED disable_ctrlc(prev); /* restore Control C checking */ @@ -392,12 +387,7 @@ void main_loop (void) int prev = disable_ctrlc(1); /* disable Control C checking */ # endif
-# ifndef CONFIG_SYS_HUSH_PARSER - run_command (s, 0); -# else - parse_string_outer(s, FLAG_PARSE_SEMICOLON | - FLAG_EXIT_FROM_LOOP); -# endif + run_command2(s, 0);
# ifdef CONFIG_AUTOBOOT_KEYED disable_ctrlc(prev); /* restore Control C checking */ @@ -407,14 +397,8 @@ void main_loop (void) # ifdef CONFIG_MENUKEY if (menukey == CONFIG_MENUKEY) { s = getenv("menucmd"); - if (s) { -# ifndef CONFIG_SYS_HUSH_PARSER - run_command(s, 0); -# else - parse_string_outer(s, FLAG_PARSE_SEMICOLON | - FLAG_EXIT_FROM_LOOP); -# endif - } + if (s) + run_command2(s, 0); } #endif /* CONFIG_MENUKEY */ #endif /* CONFIG_BOOTDELAY */ @@ -1396,6 +1380,19 @@ int run_command (const char *cmd, int flag) return rc ? rc : repeatable; }
+int run_command2(const char *cmd, int flag) +{ +#ifndef CONFIG_SYS_HUSH_PARSER + if (run_command(cmd, flag) == -1) + return 1; +#else + if (parse_string_outer(cmd, + FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP) != 0) + return 1; +#endif + return 0; +} + /****************************************************************************/
#if defined(CONFIG_CMD_RUN) @@ -1413,14 +1410,9 @@ int do_run (cmd_tbl_t * cmdtp, int flag, int argc, char * const argv[]) printf ("## Error: "%s" not defined\n", argv[i]); return 1; } -#ifndef CONFIG_SYS_HUSH_PARSER - if (run_command (arg, flag) == -1) - return 1; -#else - if (parse_string_outer(arg, - FLAG_PARSE_SEMICOLON | FLAG_EXIT_FROM_LOOP) != 0) + + if (run_command2(arg, flag) != 0) return 1; -#endif } return 0; } diff --git a/include/common.h b/include/common.h index 1e4a6a5..e659630 100644 --- a/include/common.h +++ b/include/common.h @@ -228,6 +228,7 @@ int print_buffer (ulong addr, void* data, uint width, uint count, uint linelen); /* common/main.c */ void main_loop (void); int run_command (const char *cmd, int flag); +int run_command2 (const char *cmd, int flag); int readline (const char *const prompt); int readline_into_buffer (const char *const prompt, char * buffer); int parse_line (char *, char *[]); diff --git a/include/hush.h b/include/hush.h index 5c566cc..ecf9222 100644 --- a/include/hush.h +++ b/include/hush.h @@ -29,7 +29,7 @@ #define FLAG_REPARSING (1 << 2) /* >=2nd pass */
extern int u_boot_hush_start(void); -extern int parse_string_outer(char *, int); +extern int parse_string_outer(const char *, int); extern int parse_file_outer(void);
int set_local_var(const char *s, int flg_export);

Signed-off-by: Jason Hobbs jason.hobbs@calxeda.com --- changes in v2: - expose abortboot externally instead of using a wrapper - expose abortboot externally when CONFIG_MENU is set
common/main.c | 12 ++++++++---- include/common.h | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/common/main.c b/common/main.c index 1f64523..a031a33 100644 --- a/common/main.c +++ b/common/main.c @@ -56,10 +56,6 @@ void update_tftp (void);
#define MAX_DELAY_STOP_STR 32
-#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) -static int abortboot(int); -#endif - #undef DEBUG_PARSER
char console_buffer[CONFIG_SYS_CBSIZE + 1]; /* console I/O buffer */ @@ -91,7 +87,11 @@ extern void mdm_init(void); /* defined in board.c */ */ #if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0) # if defined(CONFIG_AUTOBOOT_KEYED) +#ifdef CONFIG_MENU +int abortboot(int bootdelay) +#else static __inline__ int abortboot(int bootdelay) +#endif { int abort = 0; uint64_t etime = endtick(bootdelay); @@ -205,7 +205,11 @@ static __inline__ int abortboot(int bootdelay) static int menukey = 0; #endif
+#ifdef CONFIG_MENU +int abortboot(int bootdelay) +#else static __inline__ int abortboot(int bootdelay) +#endif { int abort = 0;
diff --git a/include/common.h b/include/common.h index e659630..d8b8a79 100644 --- a/include/common.h +++ b/include/common.h @@ -234,6 +234,9 @@ int readline_into_buffer (const char *const prompt, char * buffer); int parse_line (char *, char *[]); void init_cmd_timeout(void); void reset_cmd_timeout(void); +#ifdef CONFIG_MENU +int abortboot(int bootdelay); +#endif
/* arch/$(ARCH)/lib/board.c */ void board_init_f (ulong) __attribute__ ((noreturn));

On Thursday, June 23, 2011 14:27:32 Jason Hobbs wrote:
+#ifdef CONFIG_MENU +int abortboot(int bootdelay) +#else static __inline__ int abortboot(int bootdelay) +#endif
more ifdef trickery here than necessary: #ifndef CONFIG_MENU static inline #endif int abortboot(int bootdelay) { -mike

Mike,
On Thu, Jun 23, 2011 at 06:59:40PM -0400, Mike Frysinger wrote:
more ifdef trickery here than necessary: #ifndef CONFIG_MENU static inline #endif int abortboot(int bootdelay) {
Thanks - I'll change this in the next version of the patch.
Jason

Dear "Jason Hobbs",
In message 1308853655-12407-4-git-send-email-jason.hobbs@calxeda.com you wrote:
Signed-off-by: Jason Hobbs jason.hobbs@calxeda.com
changes in v2:
- expose abortboot externally instead of using a wrapper
- expose abortboot externally when CONFIG_MENU is set
common/main.c | 12 ++++++++---- include/common.h | 3 +++ 2 files changed, 11 insertions(+), 4 deletions(-)
...
+#ifdef CONFIG_MENU
This being a new CONFIG_ variable, it must be documented in the README. But as is this makes little sense, so I suggest you change the order of your patches and add the abortboot support to the menu system in a second step, after adding (and documenting) the menu stuff.
Best regards,
Wolfgang Denk

Dear Wolfgang,
On Fri, Jun 24, 2011 at 07:18:55AM +0200, Wolfgang Denk wrote:
+#ifdef CONFIG_MENU
This being a new CONFIG_ variable, it must be documented in the README. But as is this makes little sense, so I suggest you change the order of your patches and add the abortboot support to the menu system in a second step, after adding (and documenting) the menu stuff.
I knew it wasn't right, but I didn't know what else to do. I'll follow your suggestion in the next version of this patch series.
Thanks, Jason

This will be used first by the pxecfg code, but is intended to be generic and reusable for other jobs in U-boot.
Signed-off-by: Jason Hobbs jason.hobbs@calxeda.com --- changes in v2: - new in v2
common/Makefile | 1 + common/menu.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/menu.h | 40 +++++++ 3 files changed, 379 insertions(+), 0 deletions(-) create mode 100644 common/menu.c create mode 100644 include/menu.h
diff --git a/common/Makefile b/common/Makefile index f81cff9..9b413cd 100644 --- a/common/Makefile +++ b/common/Makefile @@ -171,6 +171,7 @@ COBJS-$(CONFIG_CMD_KGDB) += kgdb.o kgdb_stubs.o COBJS-$(CONFIG_KALLSYMS) += kallsyms.o COBJS-$(CONFIG_LCD) += lcd.o COBJS-$(CONFIG_LYNXKDI) += lynxkdi.o +COBJS-$(CONFIG_MENU) += menu.o COBJS-$(CONFIG_MODEM_SUPPORT) += modem.o COBJS-$(CONFIG_UPDATE_TFTP) += update.o COBJS-$(CONFIG_USB_KEYBOARD) += usb_kbd.o diff --git a/common/menu.c b/common/menu.c new file mode 100644 index 0000000..e92d302 --- /dev/null +++ b/common/menu.c @@ -0,0 +1,338 @@ +/* + * Copyright 2010-2011 Calxeda, Inc. + * + * 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 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 <malloc.h> +#include <errno.h> +#include <linux/list.h> + +#include "menu.h" + +struct menu_item { + char *key; + void *data; + struct list_head list; +}; + +struct menu { + struct menu_item *default_item; + int timeout; + char *title; + int prompt; + void (*item_data_print)(void *); + void (*item_data_destroy)(void *); + struct list_head items; +}; + +struct callback_with_data { + void *(*callback)(void *, void *); + void *data; +}; + +static inline void *menu_items_iter(struct menu *m, + void *(*callback)(struct menu *, struct menu_item *, void *), + void *extra) +{ + struct list_head *pos, *n; + struct menu_item *item; + void *ret; + + list_for_each_safe(pos, n, &m->items) { + item = list_entry(pos, struct menu_item, list); + + ret = callback(m, item, extra); + + if (ret) + return ret; + } + + return NULL; +} + +static inline void *menu_item_print(struct menu *m, + struct menu_item *item, + void *extra) +{ + if (!m->item_data_print) + printf("%s:\n", item->key); + else + m->item_data_print(item->data); + + return NULL; +} + +static inline void *menu_item_destroy(struct menu *m, + struct menu_item *item, + void *extra) +{ + if (m->item_data_destroy) + m->item_data_destroy(item->data); + + if (item->key) + free(item->key); + + free(item); + + return NULL; +} + +static inline void menu_display(struct menu *m) +{ + if (m->title) + printf("%s\n", m->title); + + menu_items_iter(m, menu_item_print, NULL); +} + +static inline void *menu_item_key_match(struct menu *m, + struct menu_item *item, + void *key) +{ + if (!key || !item->key) { + if (key == item) + return item; + + return NULL; + } + + if (strcmp(item->key, key) == 0) + return item; + + return NULL; +} + +static inline struct menu_item *menu_item_by_key(struct menu *m, char *key) +{ + return menu_items_iter(m, menu_item_key_match, key); +} + +static inline void *menu_item_data_cb(struct menu *m, + struct menu_item *item, + void *extra) +{ + + struct callback_with_data *cb_data = extra; + + return cb_data->callback(item->data, cb_data->data); +} + +static inline int delay_for_input(int timeout) +{ + if (!timeout) + return 0; + + if (abortboot(timeout/10)) { + printf("autoboot aborted!\n"); + return 1; + } + + return 0; +} + +void *menu_items_data_iter(struct menu *m, + void *(*callback)(void *, void *), + void *extra) +{ + struct callback_with_data cb_data; + + if (!m || !callback) + return NULL; + + cb_data.callback = callback; + cb_data.data = extra; + + return menu_items_iter(m, menu_item_data_cb, &cb_data); +} + +int menu_should_prompt(struct menu *m) +{ + if (!m) + return -EINVAL; + + return m->prompt || delay_for_input(m->timeout); +} + +int menu_default_set(struct menu *m, char *key) +{ + struct menu_item *item; + + if (!m) + return -EINVAL; + + item = menu_item_by_key(m, key); + + if (!item) + return -ENOENT; + + m->default_item = item; + + return 1; +} + +void *menu_default_choice(struct menu *m) +{ + if (!m) + return NULL; + + if (m->default_item) + return m->default_item->data; + + return NULL; +} + +void *menu_user_choice(struct menu *m) +{ + char cbuf[CONFIG_SYS_CBSIZE]; + struct menu_item *choice_item = NULL; + + if (!m) + return NULL; + + while (!choice_item) { + int readret; + + cbuf[0] = '\0'; + + menu_display(m); + + readret = readline_into_buffer("Enter choice: ", cbuf); + + if (readret >= 0) { + choice_item = menu_item_by_key(m, cbuf); + + if (!choice_item) + printf("%s not found\n", cbuf); + else + return choice_item->data; + } else { + printf("^C\n"); + return NULL; + } + } + + return NULL; +} + +/* + * note that this replaces the data of an item if it already exists, but + * doesn't change the order of the item. + */ +int menu_item_add(struct menu *m, char *item_key, void *item_data) +{ + struct menu_item *item; + + if (!m) + return -EINVAL; + + item = menu_item_by_key(m, item_key); + + if (item) { + m->item_data_destroy(item->data); + item->data = item_data; + return 1; + } + + item = malloc(sizeof *item); + + if (!item) + return -ENOMEM; + + item->key = strdup(item_key); + + if (!item->key) { + free(item); + return -ENOMEM; + } + + item->data = item_data; + + list_add_tail(&item->list, &m->items); + + return 1; +} + +struct menu *menu_create(void (*item_data_print)(void *), + void (*item_data_destroy)(void *)) +{ + struct menu *m; + + m = malloc(sizeof *m); + + if (!m) + return NULL; + + m->default_item = NULL; + m->timeout = 0; + m->title = NULL; + m->prompt = 0; + + m->item_data_print = item_data_print; + m->item_data_destroy = item_data_destroy; + + INIT_LIST_HEAD(&m->items); + + return m; +} + +int menu_title_set(struct menu *m, char *title) +{ + char *new_title; + + if (!m || !title) + return -EINVAL; + + new_title = strdup(title); + + if (!new_title) + return -ENOMEM; + + if (m->title) + free(m->title); + + m->title = new_title; + + return 1; +} + +void menu_destroy(struct menu *m) +{ + if (!m) + return; + + menu_items_iter(m, menu_item_destroy, NULL); + + if (m->title) + free(m->title); + + free(m); +} + +void menu_prompt_set(struct menu *m, int prompt) +{ + if (!m) + return; + + m->prompt = prompt; +} + +void menu_timeout_set(struct menu *m, int timeout) +{ + if (!m) + return; + + m->timeout = timeout; +} diff --git a/include/menu.h b/include/menu.h new file mode 100644 index 0000000..5ffb3b8 --- /dev/null +++ b/include/menu.h @@ -0,0 +1,40 @@ +/* + * Copyright 2010-2011 Calxeda, Inc. + * + * 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 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/. + */ + +#ifndef __MENU_H__ +#define __MENU_H__ + +struct menu; +struct menu_item; + +void *menu_items_data_iter(struct menu *m, + void *(*callback)(void *, void *), + void *extra); + +int menu_should_prompt(struct menu *m); +int menu_default_set(struct menu *m, char *key); +void *menu_default_choice(struct menu *m); +void *menu_user_choice(struct menu *m); +int menu_item_add(struct menu *m, char *item_key, void *item_data); +struct menu *menu_create(void (*item_data_print)(void *), + void (*item_data_destroy)(void *)); +int menu_title_set(struct menu *m, char *title); +void menu_destroy(struct menu *m); +void menu_prompt_set(struct menu *m, int prompt); +void menu_timeout_set(struct menu *m, int timeout); + +#endif /* __MENU_H__ */

Dear "Jason Hobbs",
In message 1308853655-12407-5-git-send-email-jason.hobbs@calxeda.com you wrote:
This will be used first by the pxecfg code, but is intended to be generic and reusable for other jobs in U-boot.
Signed-off-by: Jason Hobbs jason.hobbs@calxeda.com
changes in v2:
- new in v2
common/Makefile | 1 + common/menu.c | 338 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ include/menu.h | 40 +++++++ 3 files changed, 379 insertions(+), 0 deletions(-) create mode 100644 common/menu.c create mode 100644 include/menu.h
Is this all new code, or was it copied from some other project? If so, reference would be needed.
Then, we need documentation how this is going to be used. Please add a README with documentation of the interfaces and usage examples.
Also, I would like to know why you didn't adapt for example the menu code from barebox - which specific advantages do you see in the code you have chosen?
Best regards,
Wolfgang Denk

On Thu, Jun 23, 2011 at 10:03:12PM +0200, Wolfgang Denk wrote:
Dear "Jason Hobbs", Is this all new code, or was it copied from some other project? If so, reference would be needed.
All new code.
Then, we need documentation how this is going to be used. Please add a README with documentation of the interfaces and usage examples.
Ok - I'll add this.
Also, I would like to know why you didn't adapt for example the menu code from barebox - which specific advantages do you see in the code you have chosen?
I looked over barebox's menu code after you pointed it out, and it does have more functionality and a fancier interface than this implementation. Unfortunately, it appears that there aren't any menus defined to use it in the barebox codebase to provide a real example for it.
The primary advantage of the implementation I've provided over the barebox implementation is simplicity.
From a user interface perspective, there are no ANSI terminal codes
used, and selection of a choice is just typing out the name of the desired choice. It should work on any terminal, and would be easy to use with expect type automation tools. This simple prompt is also the default behavior for pxelinux, so should be familiar to end users.
From an implementation perspective, the code does exactly what's needed
for pxecfg without adding extra features that would require some invention of functionality to prevent introduction of untested and dead code. It could work equally well for other jobs that just require a user to make a choice between some options presented on the screen, and if anything else is needed in the future, it should be extensible to accommodate. The simplicity makes the code size small - ~1k when built for my platform, vs ~2.5k for barebox's menu code.
An example complexity from barebox's menu code we don't use here is each menu entry having its own display, destroy, and "call when selected action" method. Instead, we implement display the same way for all menus, destroy the same way for all entries in a menu, and the selected entry is merely returned to the caller for it to handle.
The implementation also hides the details of the menu's internal structure from consumers of the interface - the menu and menu_item struct's are not externally defined, forcing all interaction to go through the function interfaces. Interface consumers provide a name and an opaque pointer to a data structure of the user's choice for each item, and the rest is shielded from the consumer's view. This should help prevent coding errors and make maintenance of the internals easier by preventing consumers from relying the implementation rather than the interface.
Thanks, Jason

Dear "Jason Hobbs",
In message 20110624202511.GA18667@jhobbs-laptop you wrote:
On Thu, Jun 23, 2011 at 10:03:12PM +0200, Wolfgang Denk wrote:
Dear "Jason Hobbs", Is this all new code, or was it copied from some other project? If so, reference would be needed.
All new code.
Then, we need documentation how this is going to be used. Please add a README with documentation of the interfaces and usage examples.
Ok - I'll add this.
Also, I would like to know why you didn't adapt for example the menu code from barebox - which specific advantages do you see in the code you have chosen?
I looked over barebox's menu code after you pointed it out, and it does have more functionality and a fancier interface than this implementation. Unfortunately, it appears that there aren't any menus defined to use it in the barebox codebase to provide a real example for it.
The primary advantage of the implementation I've provided over the barebox implementation is simplicity.
...
Thanks for the detailed explanation.
Best regards,
Wolfgang Denk

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 --- changes in v2: - call abortboot directly instead of via a wrapper - change the license to GPLv2+ - cleanup brace usage in multiline statements, conditionals - allow bootfile to not exist, or to be a standalone filename - try to clarify what's going on with get_relfile - try cfg paths one by one instead of building an array - refactor getenv/printfs to a new from_env function - use the new generic menu code instead of that being integrated - drop the ifdef from do_tftp in common.h - use a clearer comment wrt to localcmd dup - default to no timeout
common/Makefile | 1 + common/cmd_pxecfg.c | 924 +++++++++++++++++++++++++++++++++++++++++++++++++++ doc/README.pxecfg | 238 +++++++++++++ include/common.h | 3 + 4 files changed, 1166 insertions(+), 0 deletions(-) create mode 100644 common/cmd_pxecfg.c create mode 100644 doc/README.pxecfg
diff --git a/common/Makefile b/common/Makefile index 9b413cd..8885c6d 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..b44db8c --- /dev/null +++ b/common/cmd_pxecfg.c @@ -0,0 +1,924 @@ +/* + * Copyright 2010-2011 Calxeda, Inc. + * + * 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 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> + +#include "menu.h" + +#define MAX_TFTP_PATH_LEN 127 + +static char *from_env(char *envvar) +{ + char *ret; + + ret = getenv(envvar); + + if (!ret) + printf("missing environment variable: %s\n", envvar); + + return ret; +} + +/* + * Returns the ethaddr environment variable formated with -'s instead of :'s + */ +static void format_mac_pxecfg(char **outbuf) +{ + char *p, *ethaddr; + + *outbuf = NULL; + + ethaddr = from_env("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; + size_t path_len; + + bootfile = from_env("bootfile"); + + if (!bootfile) + return NULL; + + last_slash = strrchr(bootfile, '/'); + + if (last_slash == NULL) + return NULL; + + path_len = (last_slash - bootfile) + 1; + + bootfile_path = malloc(path_len + 1); + + if (bootfile_path == NULL) { + printf("out of memory\n"); + return NULL; + } + + strncpy(bootfile_path, bootfile, path_len); + + bootfile_path[path_len] = '\0'; + + return bootfile_path; +} + +/* + * As in pxelinux, paths to files in pxecfg files are relative to the location + * of bootfile. get_relfile takes a path from a pxecfg file and joins it with + * the bootfile path to get the full path to the target file. + */ +static int get_relfile(char *file_path, void *file_addr) +{ + char *bootfile_path; + size_t path_len; + char relfile[MAX_TFTP_PATH_LEN+1]; + char addr_buf[10]; + char *tftp_argv[] = {"tftp", NULL, NULL, NULL}; + + bootfile_path = get_bootfile_path(); + + path_len = strlen(file_path); + + if (bootfile_path) + path_len += strlen(bootfile_path); + + if (path_len > MAX_TFTP_PATH_LEN) { + printf("Base path too long (%s%s)\n", + bootfile_path ? bootfile_path : "", + file_path); + + if (bootfile_path) + free(bootfile_path); + + return -ENAMETOOLONG; + } + + sprintf(relfile, "%s%s", + bootfile_path ? bootfile_path : "", + file_path); + + if (bootfile_path) + free(bootfile_path); + + printf("Retreiving file: %s\n", relfile); + + sprintf(addr_buf, "%p", file_addr); + + tftp_argv[1] = addr_buf; + tftp_argv[2] = relfile; + + 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; +} + +static int get_pxelinux_path(char *file, void *pxecfg_addr) +{ + size_t base_len = strlen("pxelinux.cfg/"); + char path[MAX_TFTP_PATH_LEN+1]; + + if (base_len + strlen(file) > MAX_TFTP_PATH_LEN) { + printf("path too long, skipping\n"); + return -ENAMETOOLONG; + } + + sprintf(path, "pxelinux.cfg/%s", file); + + return get_pxecfg_file(path, pxecfg_addr); +} + +static int pxecfg_uuid_path(void *pxecfg_addr) +{ + char *uuid_str; + + uuid_str = from_env("pxeuuid"); + + if (!uuid_str) + return -ENOENT; + + return get_pxelinux_path(uuid_str, pxecfg_addr); +} + +static int pxecfg_mac_path(void *pxecfg_addr) +{ + char *mac_str = NULL; + + format_mac_pxecfg(&mac_str); + + if (!mac_str) + return -ENOENT; + + return get_pxelinux_path(mac_str, pxecfg_addr); +} + +static int pxecfg_ipaddr_paths(void *pxecfg_addr) +{ + char ip_addr[9]; + int mask_pos, err; + + sprintf(ip_addr, "%08X", ntohl(NetOurIP)); + + for (mask_pos = 7; mask_pos >= 0; mask_pos--) { + err = get_pxelinux_path(ip_addr, pxecfg_addr); + + if (err > 0) + return err; + + ip_addr[mask_pos] = '\0'; + } + + return -ENOENT; +} + +/* + * 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. + * + * UUID comes from pxeuuid env variable, if defined + * MAC addr comes from ethaddr env variable, if defined + * IP + * + * see http://syslinux.zytor.com/wiki/index.php/PXELINUX + */ +static int get_pxecfg(int argc, char * const argv[]) +{ + char *pxecfg_ram; + void *pxecfg_addr; + + pxecfg_ram = from_env("pxecfg_ram"); + + if (!pxecfg_ram) + return 1; + + pxecfg_addr = (void *)simple_strtoul(pxecfg_ram, NULL, 16); + + if (pxecfg_uuid_path(pxecfg_addr) > 0 + || pxecfg_mac_path(pxecfg_addr) > 0 + || pxecfg_ipaddr_paths(pxecfg_addr) > 0 + || get_pxelinux_path("default", pxecfg_addr) > 0) { + + printf("Config file found\n"); + + return 1; + } + + printf("Config file not found\n"); + + return 0; +} + +static int get_relfile_envaddr(char *file_path, char *envaddr_name) +{ + void *file_addr; + char *envaddr; + + envaddr = from_env(envaddr_name); + + if (!envaddr) + return -ENOENT; + + file_addr = (void *)simple_strtoul(envaddr, NULL, 16); + + return get_relfile(file_path, file_addr); +} + +struct pxecfg_label { + char *name; + char *kernel; + char *append; + char *initrd; + int attempted; + int localboot; +}; + +struct pxecfg_label *label_create(void) +{ + struct pxecfg_label *label; + + label = malloc(sizeof *label); + + if (!label) + return NULL; + + label->name = NULL; + label->kernel = NULL; + label->append = NULL; + label->initrd = NULL; + label->localboot = 0; + label->attempted = 0; + + return label; +} + +static void label_destroy(void *data) +{ + struct pxecfg_label *label = data; + + 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 label_print(void *data) +{ + struct pxecfg_label *label = data; + + 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 int label_localboot(struct pxecfg_label *label) +{ + char *localcmd, *dupcmd; + int ret; + + localcmd = from_env("localcmd"); + + if (!localcmd) + return -ENOENT; + + /* + * dup the command to avoid any issues with the version of it existing + * in the environment changing during the execution of the command. + */ + dupcmd = strdup(localcmd); + + if (!dupcmd) { + printf("out of memory\n"); + return -ENOMEM; + } + + if (label->append) + setenv("bootargs", label->append); + + printf("running: %s\n", dupcmd); + + ret = run_command2(dupcmd, 0); + + free(dupcmd); + + return ret; +} + +/* + * Do what it takes to boot a chosen label. + * + * Retreive the kernel and initrd, and prepare bootargs. + */ +static void label_boot(struct pxecfg_label *label) +{ + char *bootm_argv[] = { "bootm", NULL, NULL, NULL, NULL }; + + label_print(label); + + label->attempted = 1; + + if (label->localboot) { + label_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); +} + +static void *label_boot_unattempted(void *data, void *extra) +{ + struct pxecfg_label *label = data; + + if (!label->attempted) + label_boot(label); + + 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("out of memory\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 menu *m, int nest_level); + +static int handle_include(char **c, char *base, struct 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 menu *m, char *b, int nest_level) +{ + struct token t; + char *s = *c, *title; + int err; + + get_token(c, &t, L_KEYWORD); + + switch (t.type) { + case T_TITLE: + err = parse_sliteral(c, &title); + + if (title) { + menu_title_set(m, title); + free(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 menu *m, + struct pxecfg_label *label) +{ + struct token t; + char *s; + + s = *c; + + get_token(c, &t, L_KEYWORD); + + switch (t.type) { + case T_DEFAULT: + menu_default_set(m, label->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 menu *m) +{ + struct token t; + char *s; + struct pxecfg_label *label; + int err; + + label = label_create(); + if (!label) + return -ENOMEM; + + err = parse_sliteral(c, &label->name); + if (err < 0) { + printf("Expected label name\n"); + label_destroy(label); + return -EINVAL; + } + + err = menu_item_add(m, label->name, label); + if (err < 0) { + label_destroy(label); + return err; + } + + while (1) { + s = *c; + get_token(c, &t, L_KEYWORD); + + err = 0; + switch (t.type) { + case T_MENU: + err = parse_label_menu(c, m, label); + break; + + case T_KERNEL: + err = parse_sliteral(c, &label->kernel); + break; + + case T_APPEND: + err = parse_sliteral(c, &label->append); + break; + + case T_INITRD: + err = parse_sliteral(c, &label->initrd); + break; + + case T_LOCALBOOT: + err = parse_integer(c, &label->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 menu *m, int nest_level) +{ + struct token t; + char *s, *b, *label_name; + int err, prompt, timeout; + + 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, &timeout); + menu_timeout_set(m, timeout); + break; + + case T_LABEL: + err = parse_label(&p, m); + break; + + case T_DEFAULT: + err = parse_sliteral(&p, &label_name); + + if (label_name) { + menu_default_set(m, label_name); + free(label_name); + } + + break; + + case T_PROMPT: + err = parse_integer(&p, &prompt); + menu_prompt_set(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 menu *parse_pxecfg(char *menucfg) +{ + struct menu *m; + + m = menu_create(label_print, label_destroy); + + if (!m) + return NULL; + + if (parse_pxecfg_top(menucfg, m, 1) < 0) { + menu_destroy(m); + return NULL; + } + + return m; +} + +static void handle_pxecfg(struct menu *m) +{ + struct pxecfg_label *label = NULL; + + if (menu_should_prompt(m)) { + label = menu_user_choice(m); + + if (label) + label_boot(label); + + return; + } + + label = menu_default_choice(m); + + if (label) { + printf("Attempting to boot from default label: %s\n", + label->name); + + label_boot(label); + } + + menu_items_data_iter(m, label_boot_unattempted, NULL); +} + +static int boot_pxecfg(int argc, char * const argv[]) +{ + unsigned long pxecfg_addr; + struct menu *menu; + char *pxecfg_ram; + + if (argc == 2) { + pxecfg_ram = from_env("pxecfg_ram"); + if (!pxecfg_ram) + 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); + + menu_destroy(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 d8b8a79..a28abe1 100644 --- a/include/common.h +++ b/include/common.h @@ -259,6 +259,9 @@ extern ulong load_addr; /* Default Load Address */ /* common/cmd_doc.c */ void doc_probe(unsigned long physadr);
+/* common/cmd_net.c */ +int do_tftpb(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]); + /* common/cmd_nvedit.c */ int env_init (void); void env_relocate (void);

Signed-off-by: Jason Hobbs jason.hobbs@calxeda.com --- changes in v2: - use CONFIG_MENU to enable building the menu for pxecfg use
include/configs/ca9x4_ct_vxp.h | 5 +++++ 1 files changed, 5 insertions(+), 0 deletions(-)
diff --git a/include/configs/ca9x4_ct_vxp.h b/include/configs/ca9x4_ct_vxp.h index 7f83249..f3d0e3f 100644 --- a/include/configs/ca9x4_ct_vxp.h +++ b/include/configs/ca9x4_ct_vxp.h @@ -73,6 +73,8 @@ /* Command line configuration */ #define CONFIG_CMD_BDI #define CONFIG_CMD_DHCP +#define CONFIG_CMD_PXECFG +#define CONFIG_MENU #define CONFIG_CMD_ELF #define CONFIG_CMD_ENV #define CONFIG_CMD_FLASH @@ -136,6 +138,9 @@ "kerneladdr=0x44100000\0" \ "initrdaddr=0x44800000\0" \ "maxinitrd=0x1800000\0" \ + "pxecfg_ram=0x88000000\0" \ + "initrd_ram=0x61000000\0" \ + "kernel_ram=0x80008000\0" \ "console=ttyAMA0,38400n8\0" \ "dram=1024M\0" \ "root=/dev/sda1 rw\0" \
participants (3)
-
Jason Hobbs
-
Mike Frysinger
-
Wolfgang Denk