From: Petr Hodina <[email protected]> Add the core sectioned boot menu engine:
- the menu data tables - the pixel layout/drawing primitives (video uclass fills + vidconsole bitmap text) - the navigation loop built on bootmenu_loop() so volume up/down/power work via BUTTON_REMAP_PHONE_KEYS Signed-off-by: Petr Hodina <[email protected]> --- MAINTAINERS | 1 + cmd/Makefile | 2 +- cmd/phonemenu.c | 372 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 374 insertions(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index 4b0b0b2f3a5..696abefa1e6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -688,6 +688,7 @@ L: [email protected] S: Maintained T: git https://source.denx.de/u-boot/custodians/u-boot-snapdragon.git F: cmd/phone_util.c +F: cmd/phonemenu.c F: configs/qcm6490_defconfig F: configs/qcs9100_defconfig F: drivers/*/*/pm8???-* diff --git a/cmd/Makefile b/cmd/Makefile index 762572156cd..913d94abdb2 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -36,7 +36,7 @@ obj-$(CONFIG_CMD_BMP) += bmp.o obj-$(CONFIG_CMD_BOOTCOUNT) += bootcount.o obj-$(CONFIG_CMD_BOOTEFI) += bootefi.o obj-$(CONFIG_CMD_BOOTMENU) += bootmenu.o -obj-$(CONFIG_CMD_PHONEMENU) += phone_util.o +obj-$(CONFIG_CMD_PHONEMENU) += phone_util.o phonemenu.o obj-$(CONFIG_CMD_BOOTSTAGE) += bootstage.o obj-$(CONFIG_CMD_BOOTZ) += bootz.o obj-$(CONFIG_CMD_BOOTI) += booti.o diff --git a/cmd/phonemenu.c b/cmd/phonemenu.c new file mode 100644 index 00000000000..30f994bcb64 --- /dev/null +++ b/cmd/phonemenu.c @@ -0,0 +1,372 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <cli.h> +#include <command.h> +#include <dm.h> +#include <dm/ofnode.h> +#include <dm/uclass.h> +#include <env.h> +#include <log.h> +#include <malloc.h> +#include <menu.h> +#include <video.h> +#include <video_console.h> +#include <version.h> +#include <linux/string.h> + +/* Layout (pixels) */ +#define CARD_MARGIN_X 40 +#define HEADER_TITLE_Y 150 +#define HEADER_SLOT_Y 200 +#define HEADER_VERSION_Y 240 +#define HR_Y 290 +#define HR_THICK 2 +#define BODY_TOP (HR_Y + HR_THICK + 36) +#define SECTION_LABEL_H 44 +#define ITEM_H 72 +#define FOOTER_PAD 120 +#define INDENT_SECTION 88 +#define INDENT_ITEM 120 + +/* Colours packed for X8R8G8B8 */ +#define RGB(r, g, b) (((u32)(r) << 16) | ((u32)(g) << 8) | (u32)(b)) +#define COL_BG RGB(0x0d, 0x11, 0x17) +#define COL_HR RGB(0x30, 0x36, 0x3d) +#define COL_TITLE RGB(0xf0, 0xf3, 0xf6) +#define COL_SUB RGB(0x7d, 0x85, 0x90) +#define COL_SECTION RGB(0x58, 0xa6, 0xff) +#define COL_ITEM RGB(0xc9, 0xd1, 0xd9) +#define COL_HL_BG RGB(0x1f, 0x6f, 0xeb) +#define COL_HL_FG RGB(0xff, 0xff, 0xff) +#define COL_FOOTER RGB(0x7d, 0x85, 0x90) + +/* The 16x32 bitmap font */ +#define FONT_W 16 +#define FONT_H 32 + +enum pm_section { + PM_SEC_BOOT, + PM_SEC_USB, + PM_SEC_DIAG, + PM_SEC_SYS, + PM_SEC_COUNT, +}; + +struct pm_item { + const char *label; + const char *command; + enum pm_section section; +}; + +static const char * const pm_section_label[PM_SEC_COUNT] = { + [PM_SEC_BOOT] = "BOOT", + [PM_SEC_USB] = "USB MODES", + [PM_SEC_DIAG] = "DIAGNOSTICS", + [PM_SEC_SYS] = "SYSTEM", +}; + +static const struct pm_item pm_main[] = { + { "Boot", "run bootcmd", PM_SEC_BOOT }, + { "Advanced menu >", "phonemenu advanced", PM_SEC_BOOT }, + { "Reset", "reset", PM_SEC_SYS }, + { "Power off", "phone_poweroff", PM_SEC_SYS }, +}; + +static const struct pm_item pm_advanced[] = { +#if IS_ENABLED(CONFIG_USB_ETHER) && IS_ENABLED(CONFIG_CMD_TFTPBOOT) + { "TFTP boot", "run tftp_boot; phonemenu advanced", PM_SEC_BOOT }, +#endif +#if IS_ENABLED(CONFIG_USB_ETHER) && IS_ENABLED(CONFIG_CMD_PXE) + { "PXE boot", "run pxe_boot; phonemenu advanced", PM_SEC_BOOT }, +#endif +#if IS_ENABLED(CONFIG_USB_FUNCTION_ACM) + { "Serial gadget", "run serial_gadget", PM_SEC_USB }, +#endif +#if IS_ENABLED(CONFIG_CMD_USB_MASS_STORAGE) + { "USB mass storage", "ums 0 ${storage} 0; phonemenu advanced", PM_SEC_USB }, +#endif +#if IS_ENABLED(CONFIG_CMD_FASTBOOT) + { "Fastboot", "run fastboot; phonemenu advanced", PM_SEC_USB }, +#endif +#if IS_ENABLED(CONFIG_CMD_DFU) + { "DFU", "dfu_pwr; phonemenu advanced", PM_SEC_USB }, +#endif + { "Diagnostics >", "phonemenu diag", PM_SEC_DIAG }, + { "< Back", "phonemenu", PM_SEC_SYS }, +}; + +static const struct pm_item pm_diag[] = { + { "Dump clocks", "clk dump; pause; phonemenu diag", PM_SEC_DIAG }, + { "Dump environment", "printenv; pause; phonemenu diag", PM_SEC_DIAG }, + { "Bootargs", "fdt addr $fdt_addr_r; fdt print /chosen bootargs; pause; " + "phonemenu diag", PM_SEC_DIAG }, + { "Board info", "bdinfo; pause; phonemenu diag", PM_SEC_DIAG }, + { "Buttons", "button list; pause; phonemenu diag", PM_SEC_DIAG }, + { "< Back", "phonemenu advanced", PM_SEC_SYS }, +}; + +struct pm_state { + struct udevice *vid; + struct udevice *vc; + struct video_priv *vp; + int w, h; + const struct pm_item *items; + int count; + int active; + int last_active; + int *row_y; + int *section_y; + int footer_y; + const char *title; + const char *subtitle; + const char *version; +}; + +static int pm_init_dev(struct pm_state *s) +{ + int ret; + + ret = uclass_first_device_err(UCLASS_VIDEO, &s->vid); + if (ret) + return ret; + ret = uclass_first_device_err(UCLASS_VIDEO_CONSOLE, &s->vc); + if (ret) + return ret; + + s->vp = dev_get_uclass_priv(s->vid); + s->w = video_get_xsize(s->vid); + s->h = video_get_ysize(s->vid); + return 0; +} + +static void pm_text(struct pm_state *s, int x, int y, u32 fg, u32 bg, + const char *str) +{ + s->vp->colour_fg = fg; + s->vp->colour_bg = bg; + vidconsole_set_cursor_pos(s->vc, x, y); + vidconsole_put_string(s->vc, (char *)str); +} + +static int pm_center_x(struct pm_state *s, const char *str) +{ + int px = (int)strlen(str) * FONT_W; + + return (s->w - px) / 2; +} + +static void pm_layout(struct pm_state *s) +{ + enum pm_section prev = PM_SEC_COUNT; + int y = BODY_TOP; + int i; + + for (i = 0; i < s->count; i++) { + if (s->items[i].section != prev) { + s->section_y[i] = y; + y += SECTION_LABEL_H; + prev = s->items[i].section; + } else { + s->section_y[i] = -1; + } + s->row_y[i] = y; + y += ITEM_H; + } + s->footer_y = s->h - FOOTER_PAD; +} + +static void pm_draw_item(struct pm_state *s, int i, bool active) +{ + int y = s->row_y[i]; + int text_y = y + (ITEM_H - FONT_H) / 2; + u32 fg, bg; + + if (active) { + video_fill_part(s->vid, CARD_MARGIN_X, y, + s->w - CARD_MARGIN_X, y + ITEM_H, + COL_HL_BG); + fg = COL_HL_FG; + bg = COL_HL_BG; + } else { + video_fill_part(s->vid, CARD_MARGIN_X, y, + s->w - CARD_MARGIN_X, y + ITEM_H, + COL_BG); + fg = COL_ITEM; + bg = COL_BG; + } + + pm_text(s, INDENT_ITEM, text_y, fg, bg, s->items[i].label); +} + +static void pm_draw_frame(struct pm_state *s) +{ + int hr_x0 = CARD_MARGIN_X + 20; + int hr_x1 = s->w - CARD_MARGIN_X - 20; + const char *foot = "Vol up/down to move Power to select"; + int i; + + video_fill_part(s->vid, 0, 0, s->w, s->h, COL_BG); + + pm_text(s, pm_center_x(s, s->title), HEADER_TITLE_Y, + COL_TITLE, COL_BG, s->title); + + if (s->subtitle && *s->subtitle) + pm_text(s, pm_center_x(s, s->subtitle), HEADER_SLOT_Y, + COL_SUB, COL_BG, s->subtitle); + + if (s->version && *s->version) + pm_text(s, pm_center_x(s, s->version), HEADER_VERSION_Y, + COL_SUB, COL_BG, s->version); + + video_fill_part(s->vid, hr_x0, HR_Y, hr_x1, HR_Y + HR_THICK, COL_HR); + + for (i = 0; i < s->count; i++) { + if (s->section_y[i] < 0) + continue; + pm_text(s, INDENT_SECTION, s->section_y[i], + COL_SECTION, COL_BG, + pm_section_label[s->items[i].section]); + } + + video_fill_part(s->vid, hr_x0, s->footer_y - 36, + hr_x1, s->footer_y - 36 + HR_THICK, COL_HR); + pm_text(s, pm_center_x(s, foot), s->footer_y, + COL_FOOTER, COL_BG, foot); +} + +static void pm_render_all(struct pm_state *s) +{ + int i; + + pm_draw_frame(s); + for (i = 0; i < s->count; i++) + pm_draw_item(s, i, i == s->active); + video_sync(s->vid, true); +} + +static int pm_run(const struct pm_item *items, int count, + const char *title, const char *subtitle, const char *version) +{ + struct pm_state s = { 0 }; + struct cli_ch_state cch; + struct bootmenu_data nav = { + .delay = -1, + .active = 0, + .last_active = 0, + .count = count, + .first = NULL, + }; + const char *cmd; + bool running = true; + int ret = CMD_RET_SUCCESS; + + if (pm_init_dev(&s)) + return CMD_RET_FAILURE; + + s.items = items; + s.count = count; + s.title = title; + s.subtitle = subtitle; + s.version = version; + s.row_y = calloc(count, sizeof(int)); + s.section_y = calloc(count, sizeof(int)); + if (!s.row_y || !s.section_y) { + ret = CMD_RET_FAILURE; + goto cleanup; + } + + pm_layout(&s); + pm_render_all(&s); + + cli_ch_init(&cch); + while (running) { + enum bootmenu_key k = bootmenu_loop(&nav, &cch); + + switch (k) { + case BKEY_UP: + /* Wrap from item 0 around to the last item. */ + s.last_active = s.active; + s.active = s.active ? s.active - 1 : count - 1; + pm_draw_item(&s, s.last_active, false); + pm_draw_item(&s, s.active, true); + video_sync(s.vid, true); + break; + case BKEY_DOWN: + /* Wrap from the last item back to item 0. */ + s.last_active = s.active; + s.active = (s.active < count - 1) ? s.active + 1 : 0; + pm_draw_item(&s, s.last_active, false); + pm_draw_item(&s, s.active, true); + video_sync(s.vid, true); + break; + case BKEY_SELECT: + running = false; + break; + case BKEY_QUIT: + s.active = count - 1; + running = false; + break; + default: + break; + } + } + + /* Repaint to BG before running the chosen command */ + video_fill_part(s.vid, 0, 0, s.w, s.h, COL_BG); + vidconsole_set_cursor_pos(s.vc, 16, 0); + s.vp->colour_fg = COL_ITEM; + s.vp->colour_bg = COL_BG; + video_sync(s.vid, true); + + cmd = items[s.active].command; + if (cmd && cmd[0]) + run_command((char *)cmd, 0); + /* an empty command leaves us at the console */ + +cleanup: + free(s.row_y); + free(s.section_y); + return ret; +} + +static const char *pm_codename(void) +{ + static char buf[32]; + const char *compat; + const char *p; + + compat = ofnode_read_string(ofnode_root(), "compatible"); + if (!compat || !*compat) + return "unknown"; + + p = strchr(compat, ','); + if (p) + compat = p + 1; + + strlcpy(buf, compat, sizeof(buf)); + return buf; +} + +static int do_phonemenu(struct cmd_tbl *cmdtp, int flag, int argc, + char *const argv[]) +{ + const char *sub = (argc > 1) ? argv[1] : "main"; + + if (!strcmp(sub, "advanced")) + return pm_run(pm_advanced, ARRAY_SIZE(pm_advanced), + "Advanced menu", NULL, U_BOOT_VERSION); + + if (!strcmp(sub, "diag")) + return pm_run(pm_diag, ARRAY_SIZE(pm_diag), + "Diagnostics", NULL, U_BOOT_VERSION); + + return pm_run(pm_main, ARRAY_SIZE(pm_main), + pm_codename(), NULL, U_BOOT_VERSION); +} + +U_BOOT_CMD(phonemenu, 2, 0, do_phonemenu, + "pixel-drawn sectioned boot menu", + "[advanced|diag]\n" + " - Render the main menu (default), the advanced menu or the\n" + " diagnostics submenu.\n" + " Navigation: Vol up/down to move, Power to select.\n"); -- 2.55.0
