[PATCH 9/9] ui: Support FIRMWARE messages and add ncurses/hostboot

Samuel Mendoza-Jonas sam.mj at au1.ibm.com
Tue Dec 15 14:15:30 AEDT 2015


This adds the 'Firmware Configuration' screen to the ncurses interface.
The content of this screen depends on attribute descriptions received
via the PB_PROTOCOL_ACTION_FIRMWARE message.
New widgets are defined from the message as either
a) An configurable override (single-value or enumeration), or
b) A named sub-group of overrides (sub-overrides are intented)

If the user updates an attribute override and the server successfully
commits it to flash, it is defined that the machine must immediately
reboot for the override to take effect. As such, this is a one-way
screen if the user chooses to make an update.

Signed-off-by: Samuel Mendoza-Jonas <sam.mj at au1.ibm.com>
---
 ui/common/discover-client.c   |  36 +++
 ui/common/discover-client.h   |   5 +
 ui/ncurses/Makefile.am        |   5 +-
 ui/ncurses/nc-cui.c           |  55 +++-
 ui/ncurses/nc-cui.h           |   5 +
 ui/ncurses/nc-firmware-help.c |   4 +
 ui/ncurses/nc-firmware.c      | 677 ++++++++++++++++++++++++++++++++++++++++++
 ui/ncurses/nc-firmware.h      |  34 +++
 ui/ncurses/nc-scr.h           |   1 +
 ui/ncurses/nc-widgets.c       |   4 +-
 ui/ncurses/nc-widgets.h       |   4 +-
 11 files changed, 820 insertions(+), 10 deletions(-)
 create mode 100644 ui/ncurses/nc-firmware-help.c
 create mode 100644 ui/ncurses/nc-firmware.c
 create mode 100644 ui/ncurses/nc-firmware.h

diff --git a/ui/common/discover-client.c b/ui/common/discover-client.c
index 14f36da..8611c64 100644
--- a/ui/common/discover-client.c
+++ b/ui/common/discover-client.c
@@ -151,10 +151,18 @@ static void update_config(struct discover_client *client,
 		client->ops.update_config(config, client->ops.cb_arg);
 }
 
+static void update_platform_options(struct discover_client *client,
+		struct platform_options *options)
+{
+	if (client->ops.update_platform_options)
+		client->ops.update_platform_options(options, client->ops.cb_arg);
+}
+
 static int discover_client_process(void *arg)
 {
 	struct discover_client *client = arg;
 	struct pb_protocol_message *message;
+	struct platform_options *options;
 	struct system_info *sysinfo;
 	struct boot_status *status;
 	struct boot_option *opt;
@@ -235,6 +243,16 @@ static int discover_client_process(void *arg)
 		}
 		update_config(client, config);
 		break;
+	case PB_PROTOCOL_ACTION_FIRMWARE:
+		options = talloc_zero(ctx, struct platform_options);
+
+		rc = pb_protocol_deserialise_platform_options(options, message);
+		if (rc) {
+			pb_log("%s: invalid platform message?\n", __func__);
+			return 0;
+		}
+		update_platform_options(client, options);
+		break;
 	default:
 		pb_log("%s: unknown action %d\n", __func__, message->action);
 	}
@@ -384,6 +402,24 @@ int discover_client_send_config(struct discover_client *client,
 	return pb_protocol_write_message(client->fd, message);
 }
 
+int discover_client_send_platform_options(struct discover_client *client,
+		struct platform_options *options)
+{
+	struct pb_protocol_message *message;
+	int len;
+
+	len = pb_protocol_platform_options_len(options);
+
+	message = pb_protocol_create_message(client,
+				PB_PROTOCOL_ACTION_FIRMWARE, len);
+	if (!message)
+		return -1;
+
+	pb_protocol_serialise_platform_options(options, message->payload, len);
+
+	return pb_protocol_write_message(client->fd, message);
+}
+
 int discover_client_send_url(struct discover_client *client,
 		char *url)
 {
diff --git a/ui/common/discover-client.h b/ui/common/discover-client.h
index 542a275..ca0fd91 100644
--- a/ui/common/discover-client.h
+++ b/ui/common/discover-client.h
@@ -37,6 +37,7 @@ struct discover_client_ops {
 	void (*update_status)(struct boot_status *status, void *arg);
 	void (*update_sysinfo)(struct system_info *sysinfo, void *arg);
 	void (*update_config)(struct config *sysinfo, void *arg);
+	void (*update_platform_options)(struct platform_options *options, void *arg);
 	void *cb_arg;
 };
 
@@ -83,6 +84,10 @@ int discover_client_send_reinit(struct discover_client *client);
 int discover_client_send_config(struct discover_client *client,
 		struct config *config);
 
+/* Send new platform option data to the discover server */
+int discover_client_send_platform_options(struct discover_client *client,
+		struct platform_options *options);
+
 /* Re-enumerate the collected devices & boot options, calling ops->device_add
  * and ops->boot_option_add on each.
  */
diff --git a/ui/ncurses/Makefile.am b/ui/ncurses/Makefile.am
index 265ae69..f79ba5b 100644
--- a/ui/ncurses/Makefile.am
+++ b/ui/ncurses/Makefile.am
@@ -21,9 +21,12 @@ ui_ncurses_common_libs = \
 noinst_LTLIBRARIES += ui/ncurses/libpbnc.la
 
 ui_ncurses_libpbnc_la_SOURCES = \
-	ui/ncurses/nc-config.c \
+ui/ncurses/nc-config.c \
 	ui/ncurses/nc-config.h \
 	ui/ncurses/nc-config-help.c \
+	ui/ncurses/nc-firmware.c \
+	ui/ncurses/nc-firmware.h \
+	ui/ncurses/nc-firmware-help.c \
 	ui/ncurses/nc-cui.c \
 	ui/ncurses/nc-cui.h \
 	ui/ncurses/nc-cui-help.c \
diff --git a/ui/ncurses/nc-cui.c b/ui/ncurses/nc-cui.c
index c975c0f..db8ed33 100644
--- a/ui/ncurses/nc-cui.c
+++ b/ui/ncurses/nc-cui.c
@@ -37,6 +37,7 @@
 #include "nc-cui.h"
 #include "nc-boot-editor.h"
 #include "nc-config.h"
+#include "nc-firmware.h"
 #include "nc-add-url.h"
 #include "nc-sysinfo.h"
 #include "nc-lang.h"
@@ -296,6 +297,20 @@ void cui_show_config(struct cui *cui)
 	cui_set_current(cui, config_screen_scr(cui->config_screen));
 }
 
+static void cui_firmware_exit(struct cui *cui)
+{
+	cui_set_current(cui, &cui->main->scr);
+	talloc_free(cui->firmware_screen);
+	cui->firmware_screen = NULL;
+}
+
+void cui_show_firmware(struct cui *cui)
+{
+	cui->firmware_screen = firmware_screen_init(cui, cui->platform_options,
+			cui_firmware_exit);
+	cui_set_current(cui, firmware_screen_scr(cui->firmware_screen));
+}
+
 static void cui_lang_exit(struct cui *cui)
 {
 	cui_set_current(cui, &cui->main->scr);
@@ -759,11 +774,30 @@ static void cui_update_config(struct config *config, void *arg)
 				_("Rescan devices"));
 }
 
+static void cui_update_platform_options(struct platform_options *options,
+					void *arg)
+{
+	struct cui *cui = cui_from_arg(arg);
+	cui->platform_options = talloc_steal(cui, options);
+
+	dump_firmware_config(options->options, options->n_options, 0);
+
+	if (cui->firmware_screen)
+		firmware_screen_update(cui->firmware_screen, options);
+
+	nc_scr_status_printf(cui->current, "Received platform options\n");
+}
+
 int cui_send_config(struct cui *cui, struct config *config)
 {
 	return discover_client_send_config(cui->client, config);
 }
 
+int cui_send_platform_options(struct cui *cui, struct platform_options *options)
+{
+	return discover_client_send_platform_options(cui->client, options);
+}
+
 int cui_send_url(struct cui *cui, char * url)
 {
 	return discover_client_send_url(cui->client, url);
@@ -786,6 +820,12 @@ static int menu_config_execute(struct pmenu_item *item)
 	return 0;
 }
 
+static int menu_firmware_execute(struct pmenu_item *item)
+{
+	cui_show_firmware(cui_from_item(item));
+	return 0;
+}
+
 static int menu_lang_execute(struct pmenu_item *item)
 {
 	cui_show_lang(cui_from_item(item));
@@ -813,7 +853,7 @@ static struct pmenu *main_menu_init(struct cui *cui)
 	struct pmenu *m;
 	int result;
 
-	m = pmenu_init(cui, 7, cui_on_exit);
+	m = pmenu_init(cui, 8, cui_on_exit);
 	if (!m) {
 		pb_log("%s: failed\n", __func__);
 		return NULL;
@@ -842,22 +882,26 @@ static struct pmenu *main_menu_init(struct cui *cui)
 	i->on_execute = menu_config_execute;
 	pmenu_item_insert(m, i, 2);
 
+	i = pmenu_item_create(m, _("Firmware configuration"));
+	i->on_execute = menu_firmware_execute;
+	pmenu_item_insert(m, i, 3);
+
 	/* this label isn't translated, so we don't want a gettext() here */
 	i = pmenu_item_create(m, "Language");
 	i->on_execute = menu_lang_execute;
-	pmenu_item_insert(m, i, 3);
+	pmenu_item_insert(m, i, 4);
 
 	i = pmenu_item_create(m, _("Rescan devices"));
 	i->on_execute = menu_reinit_execute;
-	pmenu_item_insert(m, i, 4);
+	pmenu_item_insert(m, i, 5);
 
 	i = pmenu_item_create(m, _("Retrieve config from URL"));
 	i->on_execute = menu_add_url_execute;
-	pmenu_item_insert(m, i, 5);
+	pmenu_item_insert(m, i, 6);
 
 	i = pmenu_item_create(m, _("Exit to shell"));
 	i->on_execute = pmenu_exit_cb;
-	pmenu_item_insert(m, i, 6);
+	pmenu_item_insert(m, i, 7);
 
 	result = pmenu_setup(m);
 
@@ -888,6 +932,7 @@ static struct discover_client_ops cui_client_ops = {
 	.update_status = cui_update_status,
 	.update_sysinfo = cui_update_sysinfo,
 	.update_config = cui_update_config,
+	.update_platform_options = cui_update_platform_options,
 };
 
 /**
diff --git a/ui/ncurses/nc-cui.h b/ui/ncurses/nc-cui.h
index 24a0761..69f9c40 100644
--- a/ui/ncurses/nc-cui.h
+++ b/ui/ncurses/nc-cui.h
@@ -58,7 +58,10 @@ struct cui {
 	struct system_info *sysinfo;
 	struct sysinfo_screen *sysinfo_screen;
 	struct config *config;
+	struct platform_options *platform_options;
+	unsigned int n_attrs;
 	struct config_screen *config_screen;
+	struct firmware_screen *firmware_screen;
 	struct add_url_screen *add_url_screen;
 	struct boot_editor *boot_editor;
 	struct lang_screen *lang_screen;
@@ -79,6 +82,7 @@ void cui_item_edit(struct pmenu_item *item);
 void cui_item_new(struct pmenu *menu);
 void cui_show_sysinfo(struct cui *cui);
 void cui_show_config(struct cui *cui);
+void cui_show_firmware(struct cui *cui);
 void cui_show_lang(struct cui *cui);
 void cui_show_help(struct cui *cui, const char *title,
 		const struct help_text *text);
@@ -86,6 +90,7 @@ void cui_show_subset(struct cui *cui, const char *title,
 		void *arg);
 void cui_show_add_url(struct cui *cui);
 int cui_send_config(struct cui *cui, struct config *config);
+int cui_send_platform_options(struct cui *cui, struct platform_options *options);
 int cui_send_url(struct cui *cui, char *url);
 void cui_send_reinit(struct cui *cui);
 
diff --git a/ui/ncurses/nc-firmware-help.c b/ui/ncurses/nc-firmware-help.c
new file mode 100644
index 0000000..4718f36
--- /dev/null
+++ b/ui/ncurses/nc-firmware-help.c
@@ -0,0 +1,4 @@
+#include "nc-helpscreen.h"
+
+struct help_text firmware_help_text = define_help_text("\
+TODO - Full help text for Attribute Overrides\n");
diff --git a/ui/ncurses/nc-firmware.c b/ui/ncurses/nc-firmware.c
new file mode 100644
index 0000000..b849317
--- /dev/null
+++ b/ui/ncurses/nc-firmware.c
@@ -0,0 +1,677 @@
+/*
+ *  Copyright (C) 2013 IBM Corporation
+ *
+ *  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; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#if defined(HAVE_CONFIG_H)
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <talloc/talloc.h>
+#include <types/types.h>
+#include <log/log.h>
+#include <i18n/i18n.h>
+
+#include "nc-cui.h"
+#include "nc-firmware.h"
+#include "nc-widgets.h"
+
+#define N_FIELDS	4
+
+extern struct help_text firmware_help_text;
+
+/*
+ * Widgets related to a platform option are stored in a tree mirroring the form
+ * of cui->platform_options. This allows us to collect the various widgets in a
+ * structure that implicitly describes the format and layout of the widgets
+ */
+struct sys_attr {
+	union {
+		struct nc_widget_textbox	*value;
+		struct nc_widget_select		*select;	/* LIMIT_ENUM */
+	};
+
+	//TODO - attribute descriptions - waiting on real examples for form
+	struct nc_widget_label		*description;
+	/*
+	 * Attributes always have a description, and several more descriptive
+	 * strings if enumerations are present - ie. both may be present
+	 */
+	unsigned int			n_enum_info;
+	struct nc_widget_label		**enum_info;
+};
+
+/*
+ * Every firmware_option has at least one nc_widget associated with it,
+ * even if that is just a group's label.
+ */
+struct sys_option {
+	struct nc_widget_label		*label;
+	int				level;
+
+	unsigned int			n_children;
+	union {
+		struct sys_attr		*attr;		/* FIRMWARE_ATTR */
+		struct sys_option	**group;	/* FIRMWARE_GROUP */
+	};
+
+	const struct firmware_option	*option;
+};
+
+struct firmware_screen {
+	struct nc_scr		scr;
+	struct cui		*cui;
+	struct nc_widgetset	*widgetset;
+	WINDOW			*pad;
+
+	bool			exit;
+	bool			show_help;
+	bool			need_redraw;
+	void			(*on_exit)(struct cui *);
+
+	int			scroll_y;
+
+	int			label_x;
+	int			field_x;
+	int			indent_width;
+	int			opt_field_width;
+
+	int			n_opts;
+	struct sys_option	**opts;
+
+	bool			show_descriptions;
+
+	struct {
+		struct nc_widget_label		*safe_mode;
+		struct nc_widget_button		*ok_b;
+		struct nc_widget_button		*help_b;
+		struct nc_widget_button		*cancel_b;
+	} widgets;
+};
+
+static struct firmware_screen *firmware_screen_from_scr(struct nc_scr *scr)
+{
+	struct firmware_screen *firmware_screen;
+
+	assert(scr->sig == pb_firmware_screen_sig);
+	firmware_screen = (struct firmware_screen *)
+		((char *)scr - (size_t)&((struct firmware_screen *)0)->scr);
+	assert(firmware_screen->scr.sig == pb_firmware_screen_sig);
+	return firmware_screen;
+}
+
+static void pad_refresh(struct firmware_screen *screen)
+{
+	int y, x, rows, cols;
+
+	getmaxyx(screen->scr.sub_ncw, rows, cols);
+	getbegyx(screen->scr.sub_ncw, y, x);
+
+	prefresh(screen->pad, screen->scroll_y, 0, y, x, rows, cols);
+}
+
+static void firmware_screen_process_key(struct nc_scr *scr, int key)
+{
+	struct firmware_screen *screen = firmware_screen_from_scr(scr);
+	bool handled;
+
+	handled = widgetset_process_key(screen->widgetset, key);
+
+	if (!handled) {
+		switch (key) {
+		case 'x':
+		case 27: /* esc */
+			screen->exit = true;
+			break;
+		case 'h':
+			screen->show_help = true;
+			break;
+		}
+	}
+
+	if (screen->exit) {
+		screen->on_exit(screen->cui);
+
+	} else if (screen->show_help) {
+		screen->show_help = false;
+		screen->need_redraw = true;
+		cui_show_help(screen->cui, _("System Firmware Settings"),
+				&firmware_help_text);
+
+	} else if (handled) {
+		pad_refresh(screen);
+	}
+}
+
+static void firmware_screen_resize(struct nc_scr *scr)
+{
+	struct firmware_screen *screen = firmware_screen_from_scr(scr);
+	(void)screen;
+}
+
+static int firmware_screen_post(struct nc_scr *scr)
+{
+	struct firmware_screen *screen = firmware_screen_from_scr(scr);
+	widgetset_post(screen->widgetset);
+	if (screen->need_redraw) {
+		nc_scr_frame_draw(scr);
+		redrawwin(scr->main_ncw);
+		screen->need_redraw = false;
+	}
+	wrefresh(screen->scr.main_ncw);
+	pad_refresh(screen);
+	return 0;
+}
+
+static int firmware_screen_unpost(struct nc_scr *scr)
+{
+	struct firmware_screen *screen = firmware_screen_from_scr(scr);
+	widgetset_unpost(screen->widgetset);
+	return 0;
+}
+
+struct nc_scr *firmware_screen_scr(struct firmware_screen *screen)
+{
+	return &screen->scr;
+}
+
+/* For each sys_option available, if it is a
+ * - attribute widget: check the current value and update option
+ * - group widget: recursively consider group
+ */
+static int process_group(struct firmware_screen *screen,
+			 struct sys_option **opts, unsigned int n_opts)
+{
+	const struct firmware_option *opt;
+	unsigned int i, updates = 0;
+	void *parent_ctx;
+	char *val;
+
+	for (i = 0; i < n_opts; i++) {
+		opt = opts[i]->option;
+		/* Process sub-group */
+		if (opt->type == FIRMWARE_GROUP) {
+			updates += process_group(screen, opts[i]->group,
+				      opts[i]->n_children);
+			continue;
+		}
+
+		/* Get current value */
+		if (opt->attr->limit == LIMIT_ENUM) {
+			val = talloc_asprintf(screen, "%u",
+				widget_select_get_value(opts[i]->attr->select));
+		} else {
+			val = widget_textbox_get_value(opts[i]->attr->value);
+		}
+
+		/* Difference? */
+		if (strlen(val)  == strlen(opt->attr->value))
+			if (!strncmp(val, opt->attr->value,
+				     strlen(opt->attr->value)))
+				continue;
+
+		/* Value differs in either length or value - update */
+		parent_ctx = talloc_parent(opt->attr->value);
+		talloc_free(opt->attr->value);
+		if (val == NULL || strlen(val) == 0) {
+			/* If the field is empty, reset the value to default */
+			pb_log("Reseting %s to default value (%s)\n",
+			       val, opt->attr->default_value);
+			opt->attr->value = talloc_strdup(parent_ctx,
+						opt->attr->default_value);
+		} else {
+			opt->attr->value = talloc_strdup(parent_ctx, val);
+			pb_log("Updated value for %s: %s\n",
+			       opt->attr->id, opt->attr->value);
+		}
+		updates++;
+	}
+
+	return updates;
+}
+
+static int screen_process_form(struct firmware_screen *screen)
+{
+	unsigned int updates;
+	int rc;
+
+	updates = process_group(screen, screen->opts, screen->n_opts);
+
+	pb_log("%s: %d modified attributes\n", __func__, updates);
+
+	rc = cui_send_platform_options(screen->cui,
+				       screen->cui->platform_options);
+
+	if (rc)
+		pb_log("cui_send_platform_options failed!\n");
+	else
+		pb_debug("platform options sent!\n");
+
+	return 0;
+}
+
+static void ok_click(void *arg)
+{
+	struct firmware_screen *screen = arg;
+	if (screen_process_form(screen))
+		/* errors are written to the status line, so we'll need
+		 * to refresh */
+		wrefresh(screen->scr.main_ncw);
+	else
+		screen->exit = true;
+}
+
+static void help_click(void *arg)
+{
+	struct firmware_screen *screen = arg;
+	screen->show_help = true;
+}
+
+static void cancel_click(void *arg)
+{
+	struct firmware_screen *screen = arg;
+	screen->exit = true;
+}
+
+static int layout_sys_option(struct firmware_screen *screen, int y,
+		struct sys_option *opt)
+{
+	int height = 0, tmp = 0, label_x;
+	struct nc_widget *wl, *wf;
+	unsigned int i;
+
+	label_x = screen->label_x + (5 * opt->level);
+	wl = widget_label_base(opt->label);
+	widget_move(wl, y, label_x);
+	widget_set_visible(wl, true);
+	height++;
+
+	/* Layout options belong to group */
+	if (opt->n_children && opt->option->type == FIRMWARE_GROUP) {
+		for (i = 0; i < opt->n_children; i++)
+			tmp += layout_sys_option(screen, y + height + tmp,
+						       opt->group[i]);
+		return height + tmp;
+	}
+
+	/* Layout singe-value option */
+	if (opt->option->attr->limit != LIMIT_ENUM) {
+		wf = widget_textbox_base(opt->attr->value);
+		widget_move(wf, y, label_x + widget_width(wl) + 2);
+		widget_set_visible(wf, true);
+		wl = widget_label_base(opt->attr->description);
+		widget_set_visible(wl, screen->show_descriptions);
+		if (screen->show_descriptions)
+			widget_move(wl, y, widget_x(wf) + widget_width(wf) + 2);
+		if (widget_x(wl) + widget_width(wl) > COLS)
+			pb_log("Warning: Attribute widgets for '%s' run over"
+				" display width\n", opt->option->name);
+		return height + 1;
+	}
+
+	/* Layout select widget for enum options */
+	wf = widget_select_base(opt->attr->select);
+	widget_move(wf, y, label_x + widget_width(wl) + 2);
+	widget_set_visible(wf, true);
+	if (widget_x(wf) + widget_width(wf) > COLS)
+		pb_log("Warning: Enum widgets for '%s' run over"
+			" display width\n", opt->option->name);
+	/*TODO  Ignoring enum descriptions for now until we get real examples */
+	widget_set_visible(widget_label_base(opt->attr->description), false);
+	for (i = 0; i < opt->attr->n_enum_info; i++)
+		widget_set_visible(widget_label_base(opt->attr->enum_info[i]),
+				   false);
+
+	return height + 2 * widget_height(wf);
+}
+
+static void firmware_screen_layout_widgets(struct firmware_screen *screen)
+{
+	int y = 0, i;
+
+	y++;
+
+	for (i = 0; i < screen->n_opts; i++) {
+		if (i && screen->opts[i]->option->type == FIRMWARE_GROUP)
+			y++;
+		y += layout_sys_option(screen, y, screen->opts[i]);
+	}
+
+	y += 1;
+
+	widget_move(widget_button_base(screen->widgets.ok_b),
+			y, screen->field_x);
+	widget_move(widget_button_base(screen->widgets.help_b),
+			y, screen->field_x + 10);
+	widget_move(widget_button_base(screen->widgets.cancel_b),
+			y, screen->field_x + 24);
+}
+
+static void firmware_screen_setup_empty(struct firmware_screen *screen)
+{
+	widget_new_label(screen->widgetset, 2, screen->field_x,
+			_("Waiting for platform data..."));
+	screen->widgets.cancel_b = widget_new_button(screen->widgetset,
+			4, screen->field_x, 9, _("Cancel"),
+			cancel_click, screen);
+}
+
+static inline size_t enum_max_length(struct firmware_attr *attr)
+{
+	unsigned int i;
+	size_t len, max = 0;
+
+	for (i = 0; i < attr->n_range; i++) {
+		len = strncols(attr->enums[i].display_name) + strlen("(*) ");
+		max = len > max ? len : max;
+	}
+
+	return max;
+}
+
+static void setup_enum_widget(struct firmware_screen *screen,
+			      struct firmware_attr *f_attr,
+			      struct sys_attr *s_attr)
+{
+	unsigned int selected, i;
+	size_t len;
+
+	if (f_attr->limit != LIMIT_ENUM) {
+		pb_debug("%s incorrectly called for non-enum limit\n",
+			 __func__);
+		return;
+	}
+
+	len = enum_max_length(f_attr);
+	s_attr->select = widget_new_select(screen->widgetset, 0, 0, len);
+	s_attr->enum_info = talloc_array(s_attr, struct nc_widget_label *,
+			f_attr->n_range);
+	s_attr->n_enum_info = f_attr->n_range;
+	selected = strtoul(f_attr->value, NULL, 0);
+	for (i = 0; i < f_attr->n_range; i++) {
+		widget_select_add_option(s_attr->select, i,
+					 f_attr->enums[i].display_name,
+					 selected == i);
+		s_attr->enum_info[i] = widget_new_label(screen->widgetset,
+					 0, 0,
+					 f_attr->enums[i].description);
+	}
+}
+
+static void setup_int_widget(struct firmware_screen *screen,
+			      struct firmware_attr *f_attr,
+			      struct sys_attr *s_attr)
+{
+	long int min, max;
+
+	if (f_attr->limit == LIMIT_ENUM) {
+		pb_debug("%s incorrectly called for LIMIT_ENUM\n", __func__);
+		return;
+	}
+
+	s_attr->value = widget_new_textbox(screen->widgetset, 0, 0,
+				screen->opt_field_width, f_attr->value);
+
+	if (f_attr->limit == LIMIT_RANGE && (f_attr->n_range == 2)) {
+		/* Specified limits will take precedence over specified size */
+		min = f_attr->range[0];
+		max = f_attr->range[1];
+	} else {
+
+		//TODO Specify field limits in UI
+		switch (f_attr->size) {
+		case 1:
+			min = f_attr->is_signed ? SCHAR_MIN : 0;
+			max = f_attr->is_signed ? SCHAR_MAX : UCHAR_MAX;
+			break;
+		case 2:
+			min = f_attr->is_signed ? SHRT_MIN : 0;
+			max = f_attr->is_signed ? SHRT_MAX : USHRT_MAX;
+			break;
+		case 4:
+			min = f_attr->is_signed ? INT_MIN : 0;
+			max = f_attr->is_signed ? INT_MAX : UINT_MAX;
+			break;
+		case 8:
+		default:
+			min = f_attr->is_signed ? LONG_MIN : 0;
+			max = f_attr->is_signed ? LONG_MAX : ULONG_MAX;
+			break;
+		}
+	}
+
+	widget_textbox_set_validator_integer(s_attr->value, min, max);
+}
+
+static struct sys_option *new_sys_option(struct firmware_screen *screen,
+		const void *ctx,
+		const struct firmware_option *opt, int level)
+{
+	struct sys_option *sys_opt;
+	unsigned int i;
+
+	sys_opt = talloc_zero(ctx, struct sys_option);
+
+	if (opt->type == FIRMWARE_GROUP) {
+		sys_opt->label = widget_new_label(screen->widgetset,
+					0, 0, opt->name);
+
+		sys_opt->n_children = opt->n_opts;
+		sys_opt->group = talloc_zero_array(sys_opt,
+					struct sys_option *, opt->n_opts);
+		for (i = 0; i < opt->n_opts; i++)
+			sys_opt->group[i] = new_sys_option(screen, sys_opt,
+							   &opt->opts[i], level + 1);
+	}
+
+	if (opt->type == FIRMWARE_ATTR) {
+		sys_opt->label = widget_new_label(screen->widgetset,
+					0, 0, opt->attr->display_name);
+		sys_opt->attr = talloc_zero(sys_opt, struct sys_attr);
+		if (opt->attr->limit == LIMIT_ENUM) {
+			/* Build select widget for enums */
+			setup_enum_widget(screen, opt->attr, sys_opt->attr);
+		} else {
+			/* Build textbox for single value */
+			setup_int_widget(screen, opt->attr, sys_opt->attr);
+		}
+		sys_opt->attr->description = widget_new_label(screen->widgetset,
+					0, 0, opt->attr->description);
+	}
+
+	sys_opt->level = level;
+	sys_opt->option = opt;
+
+	return sys_opt;
+}
+
+static void firmware_screen_setup_widgets(struct firmware_screen *screen,
+		const struct platform_options *options)
+{
+	struct nc_widgetset *set = screen->widgetset;
+	int i;
+
+	build_assert(sizeof(screen->widgets) / sizeof(struct widget *)
+			== N_FIELDS);
+
+	screen->opts = talloc_array(options, struct sys_option *,
+				       options->n_options);
+	screen->n_opts = options->n_options;
+	for (i = 0; i < screen->n_opts; i++) {
+		screen->opts[i] = new_sys_option(screen, screen->opts,
+					&options->options[i], 0);
+	}
+	pb_log("%d base-level options read, %d total options available\n",
+	       screen->n_opts, options->n_options);
+
+	screen->widgets.ok_b = widget_new_button(set, 0, 0, 6, _("OK"),
+			ok_click, screen);
+	screen->widgets.help_b = widget_new_button(set, 0, 0, 10, _("Help"),
+			help_click, screen);
+	screen->widgets.cancel_b = widget_new_button(set, 0, 0, 10, _("Cancel"),
+			cancel_click, screen);
+}
+
+static void firmware_screen_widget_focus(struct nc_widget *widget, void *arg)
+{
+	struct firmware_screen *screen = arg;
+	int w_y, s_max;
+
+	w_y = widget_y(widget) + widget_focus_y(widget);
+	s_max = getmaxy(screen->scr.sub_ncw) - 1;
+
+	if (w_y < screen->scroll_y)
+		screen->scroll_y = w_y;
+
+	else if (w_y + screen->scroll_y + 1 > s_max)
+		screen->scroll_y = 1 + w_y - s_max;
+
+	else
+		return;
+
+	pad_refresh(screen);
+}
+
+static int max_option_height(const struct firmware_option *opt, int n_opts)
+{
+	int i, height = 0;
+
+	for (i = 0; i < n_opts; i++) {
+		/* group +1 for label, +1 for newline */
+		if (opt[i].type == FIRMWARE_GROUP) {
+			height += 2 + max_option_height(opt[i].opts,
+						       opt[i].n_opts);
+		} else {
+			/* attrs(ENUM) + 2 for each enum/descripton combo */
+			/* attrs(RANGE) + 1 for description only */
+			if (opt[i].attr->limit == LIMIT_ENUM)
+				height += (2 * opt[i].attr->n_range);
+			else
+				height += 2;
+		}
+	}
+
+	return height;
+}
+
+static void firmware_screen_draw(struct firmware_screen *screen,
+		const struct platform_options *options)
+{
+	bool repost = false;
+	int height;
+
+	/*
+	 * Pad size is N_FIELDS (base fields) + the total possible number
+	 * of fields according to the firmware configuration (labels,
+	 * textboxes, select widgets, and descriptive labels).
+	 */
+	if (!options)
+		height = N_FIELDS + 6;
+	else
+		height = N_FIELDS
+			+ max_option_height(options->options,
+					  options->n_options)
+			+ 6;
+
+	if (!screen->pad || getmaxy(screen->pad) < height) {
+		if (screen->pad)
+			delwin(screen->pad);
+		screen->pad = newpad(height, COLS);
+	}
+
+	if (screen->widgetset) {
+		widgetset_unpost(screen->widgetset);
+		talloc_free(screen->widgetset);
+		/* If we have widgets, we also have firmware options */
+		talloc_free(screen->opts);
+		screen->n_opts = 0;
+		repost = true;
+	}
+
+	screen->widgetset = widgetset_create(screen, screen->scr.main_ncw,
+			screen->pad);
+	widgetset_set_widget_focus(screen->widgetset,
+			firmware_screen_widget_focus, screen);
+
+	if (!options) {
+		firmware_screen_setup_empty(screen);
+	} else {
+		firmware_screen_setup_widgets(screen, options);
+		firmware_screen_layout_widgets(screen);
+	}
+
+	if (repost)
+		widgetset_post(screen->widgetset);
+}
+
+void firmware_screen_update(struct firmware_screen *screen,
+		struct platform_options *options)
+{
+	talloc_free(screen->opts);
+	screen->n_opts = 0;
+	firmware_screen_draw(screen, options);
+	pad_refresh(screen);
+}
+
+static int firmware_screen_destroy(void *arg)
+{
+	struct firmware_screen *screen = arg;
+	if (screen->pad)
+		delwin(screen->pad);
+	return 0;
+}
+
+struct firmware_screen *firmware_screen_init(struct cui *cui,
+		struct platform_options *options,
+		void (*on_exit)(struct cui *))
+{
+	struct firmware_screen *screen;
+
+	screen = talloc_zero(cui, struct firmware_screen);
+	talloc_set_destructor(screen, firmware_screen_destroy);
+	nc_scr_init(&screen->scr, pb_firmware_screen_sig, 0,
+			cui, firmware_screen_process_key,
+			firmware_screen_post, firmware_screen_unpost,
+			firmware_screen_resize);
+
+	screen->cui = cui;
+	screen->on_exit = on_exit;
+	screen->label_x = 2;
+	screen->field_x = 17;
+	screen->indent_width = 4;
+	//TODO field widths should either be dynamic or a small set of
+	//allowable sizes depending on the info we have.
+	screen->opt_field_width = 8;
+	screen->need_redraw = false;
+	screen->show_descriptions = true;
+
+	screen->scr.frame.ltitle = talloc_strdup(screen,
+			_("Petitboot Firmware Configuration"));
+	screen->scr.frame.rtitle = NULL;
+	screen->scr.frame.help = talloc_strdup(screen,
+			_("tab=next, shift+tab=previous, x=exit, h=help"));
+	nc_scr_frame_draw(&screen->scr);
+
+	scrollok(screen->scr.sub_ncw, true);
+
+	firmware_screen_draw(screen, options);
+
+	return screen;
+}
diff --git a/ui/ncurses/nc-firmware.h b/ui/ncurses/nc-firmware.h
new file mode 100644
index 0000000..c839d1a
--- /dev/null
+++ b/ui/ncurses/nc-firmware.h
@@ -0,0 +1,34 @@
+/*
+ *  Copyright (C) 2013 IBM Corporation
+ *
+ *  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; version 2 of the License.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+#ifndef _NC_FIRMWARE_H
+#define _NC_FIRMWARE_H
+
+#include "types/types.h"
+#include "nc-cui.h"
+
+struct firmware_screen;
+
+struct firmware_screen *firmware_screen_init(struct cui *cui,
+		struct platform_options *options,
+		void (*on_exit)(struct cui *));
+
+struct nc_scr *firmware_screen_scr(struct firmware_screen *screen);
+void firmware_screen_update(struct firmware_screen *screen,
+		struct platform_options *options);
+
+#endif /* defined _NC_FIRMWARE_H */
diff --git a/ui/ncurses/nc-scr.h b/ui/ncurses/nc-scr.h
index be99b48..13f1a99 100644
--- a/ui/ncurses/nc-scr.h
+++ b/ui/ncurses/nc-scr.h
@@ -49,6 +49,7 @@ enum pb_nc_sig {
 	pb_lang_screen_sig	= 777,
 	pb_add_url_screen_sig	= 888,
 	pb_subset_screen_sig	= 101,
+	pb_firmware_screen_sig	= 969,
 	pb_removed_sig		= -999,
 };
 
diff --git a/ui/ncurses/nc-widgets.c b/ui/ncurses/nc-widgets.c
index 7e03e57..d4e3063 100644
--- a/ui/ncurses/nc-widgets.c
+++ b/ui/ncurses/nc-widgets.c
@@ -211,7 +211,7 @@ static int label_destructor(void *ptr)
 
 
 struct nc_widget_label *widget_new_label(struct nc_widgetset *set,
-		int y, int x, char *str)
+		int y, int x, const char *str)
 {
 	struct nc_widget_label *label;
 	FIELD *f;
@@ -368,7 +368,7 @@ static int textbox_destructor(void *ptr)
 }
 
 struct nc_widget_textbox *widget_new_textbox(struct nc_widgetset *set,
-		int y, int x, int len, char *str)
+		int y, int x, int len, const char *str)
 {
 	struct nc_widget_textbox *textbox;
 	FIELD *f;
diff --git a/ui/ncurses/nc-widgets.h b/ui/ncurses/nc-widgets.h
index 4b67da7..7d7910f 100644
--- a/ui/ncurses/nc-widgets.h
+++ b/ui/ncurses/nc-widgets.h
@@ -24,11 +24,11 @@ struct nc_widget_textbox;
 struct nc_widget_button;
 
 struct nc_widget_label *widget_new_label(struct nc_widgetset *set,
-		int y, int x, char *str);
+		int y, int x, const char *str);
 struct nc_widget_checkbox *widget_new_checkbox(struct nc_widgetset *set,
 		int y, int x, bool checked);
 struct nc_widget_textbox *widget_new_textbox(struct nc_widgetset *set,
-		int y, int x, int len, char *str);
+		int y, int x, int len, const char *str);
 struct nc_widget_subset *widget_new_subset(struct nc_widgetset *set,
 		int y, int x, int len, void *screen_cb);
 struct nc_widget_select *widget_new_select(struct nc_widgetset *set,
-- 
2.6.3



More information about the Petitboot mailing list