U-Boot menu菜单分析

文章目录

  • 前言
  • 目标
  • 环境背景
  • U-Boot如何自动调起菜单
  • U-Boot添加自定义命令
  • 实践

前言

在某个厂家的开发板中,在进入它的U-Boot后,会自动弹出一个菜单页面,输入对应的选项就会执行对应的功能。如SD卡镜像更新、显示设置等:
image.png

目标

本文主要分析U-Boot在程序中的执行顺序,又如何在U-Boot阶段调起菜单?相信大家都试过,在U-Boot倒数结束前按任意按键后,会进入U-Boot命令行模式。
这里先留一个问题:如何做到按键按下后,调启的是自己的U-Boot菜单,而不再是进入冷冰冰的命令行模式?

环境背景

本文介绍所用的U-Boot版本:2018

U-Boot如何自动调起菜单

U-Boot的入口程序文件是<u-boot>/common/main.c,入口函数main_loop()

/* We come here after U-Boot is initialised and ready to process commands */
/* 在U-Boot初始化并准备好处理命令之后,我们来到这里。 */
void main_loop(void)
{
    const char *s;

    bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");	

    #ifdef CONFIG_VERSION_VARIABLE
    env_set("ver", version_string);  /* set version variable */		
    #endif /* CONFIG_VERSION_VARIABLE */

    cli_init();							//命令初始化有关,初始化 hush shell 相关的变量

    run_preboot_environment_command();	//获取环境变量 perboot 的内容

    #if defined(CONFIG_UPDATE_TFTP)
    update_tftp(0UL, NULL, NULL);		
    #endif /* CONFIG_UPDATE_TFTP */

    s = bootdelay_process();	//此函数会读取环境变量 bootdelay 和 bootcmd 的内容
    if (cli_process_fdt(&s))
        cli_secure_boot_cmd(s);

    autoboot_command(s);		//开启倒计时,并在倒计时结束前检测是否有按键按下

    cli_loop();					//命令行处理函数(即进入U-Boot命令行)
    panic("No CLI available");
}

关键函数是autoboot_command(),该函数的实现在<u-boot>/common/autoboot.c

void autoboot_command(const char *s)
{
    debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

    if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {	// 倒计时过程中,没有按键按下
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    int prev = disable_ctrlc(1);    /* disable Control C checking */
#endif

    run_command_list(s, -1, 0);		// 倒计时结束后,启动内核

#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    disable_ctrlc(prev);    /* restore Control C checking */
#endif
    }

#ifdef CONFIG_MENUKEY
    if (menukey == CONFIG_MENUKEY) {
        s = env_get("menucmd");
        if (s)
            run_command_list(s, -1, 0);
    }
#endif /* CONFIG_MENUKEY */
}

进入autoboot_command()后,先看第一个if

void autoboot_command(const char *s)
{
    ...
        
    if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay))

    ...
}

这里有三个条件:

  • stored_bootdelay != -1:stored_bootdelay是倒数的总时间,就是常见的3秒、5秒不等;
  • s:传进来的参数s不能为空;
  • !abortboot(stored_bootdelay):该函数会从stored_bootdelay开始倒计时,期间判断是否有按键按下。函数实现如下,倒计时过程中若检测到按键按下,则令abort=1。无按键按下,则abort=0。最后返回abort
static int __abortboot(int bootdelay)
{
        int abort = 0;
        unsigned long ts;

#ifdef CONFIG_MENUPROMPT
        printf(CONFIG_MENUPROMPT);
#else
        printf("Hit any key to stop autoboot: %2d ", bootdelay);
#endif

        /*
         * Check if key already pressed
         */
        if (tstc()) {   /* we got a key press   */
                (void) getc();  /* consume input        */
                puts("\b\b\b 0");
                abort = 1;      /* don't auto boot      */
        }

        while ((bootdelay > 0) && (!abort)) {
                --bootdelay;
                /* delay 1000 ms */
                ts = get_timer(0);
                do {
                        if (tstc()) {   /* we got a key press   */
                                abort  = 1;     /* don't auto boot      */
                                bootdelay = 0;  /* no more delay        */
# ifdef CONFIG_MENUKEY
                                menukey = getc();
# else
                                (void) getc();  /* consume input        */
# endif
                                break;
                        }
                        udelay(10000);
                } while (!abort && get_timer(ts) < 1000);

                printf("\b\b\b%2d ", bootdelay);
        }

        putc('\n');

        return abort;
}

刚刚说了,abortboot()函数执行期间有按键按下的话,abortboot()会返回1,那就不会进入第一个if,程序会接着往下运行直至该函数运行结束。autoboot_command()结束后继续返回到main_loop(),随后立刻执行cli_loop(),进入我们所熟悉的U-Boot命令行模式。

void main_loop(void)
{
    ...
    
    autoboot_command(s);		//检查倒计时是否结束

    cli_loop();					//命令行处理函数
}

image.png
至此,就实现了U-Boot倒数期间,有按键按下,则进入U-Boot的命令行模式。
现在继续回到第一个if

void autoboot_command(const char *s)
{
    ...
        
    if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {	// 倒计时过程中,没有按键按下
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    int prev = disable_ctrlc(1);    /* disable Control C checking */
#endif

    run_command_list(s, -1, 0);		// 倒计时结束后,启动内核

#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    disable_ctrlc(prev);    /* restore Control C checking */
#endif
    }

    ...
}

如果在autoboot倒计时结束前,一直没有按键按下呢?那abortboot()最后会返回0,第一个if的三个条件全部满足。进入ifrun_command_list()执行一系列命令后,启动内核。注意,这里的现象是直接启动内核,run_command_list()后的程序不再执行。


解析到这里,我们得出一个结论:在autoboot倒计时中,如果有按键按下的话,会进入U-Boot的命令行模式。无按键按下则在倒计时结束后直接启动内核。
那现在可以回答第一个问题,如何做到按下按键后,是自启动U-Boot菜单,而不是进入U-Boot命令行呢?答案是在执行cli_loop()之前,我们可以在autoboot检测到按键按下后,调用run_command()函数执行menu命令,从而调起菜单。

void autoboot_command(const char *s)
{
    debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

    if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    int prev = disable_ctrlc(1);    /* disable Control C checking */
#endif

    run_command_list(s, -1, 0);

#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    disable_ctrlc(prev);    /* restore Control C checking */
#endif
    }

    //启动菜单
    run_command("menu", 0);
    
#ifdef CONFIG_MENUKEY
    if (menukey == CONFIG_MENUKEY) {
        s = env_get("menucmd");
        if (s)
            run_command_list(s, -1, 0);
    }
#endif /* CONFIG_MENUKEY */
}

U-Boot添加自定义命令

难道通过run_command()执行menu命令后,菜单就自己出来了?这是一个理所当然的猜想。实际上U-Boot根本不认识menu命令:
image.png
接下来看看如何添加U-Boot命令,参考一下别人的代码:

int do_brightness(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	int rcode = 0;
	ulong side;
	ulong bright;

	switch (argc) {
	case 3:
		side = simple_strtoul(argv[1], NULL, 10);
		bright = simple_strtoul(argv[2], NULL, 10);
		if ((side >= 0) && (side <= 3) &&
			(bright >= 0) && (bright <= 1000)) {
			vcxk_setbrightness(side, bright);
			rcode = 0;
		} else {
			printf("parameters out of range\n");
			printf("Usage:\n%s\n", cmdtp->usage);
			rcode = 1;
		}
		break;
	default:
		printf("Usage:\n%s\n", cmdtp->usage);
		rcode = 1;
		break;
	}
	return rcode;
}

U_BOOT_CMD(
	bright,	3,	0,	do_brightness,
	"sets the display brightness\n",
	" <side> <0..1000>\n        side: 0/3=both; 1=first; 2=second\n"
);

先看最底下的U_BOOT_CMD,这是一个宏,用来添加U-Boot命令:

#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
  • _name:命令的名字
  • _maxargs:添加的命令最多有几个参数
  • _rep:是否重复(1重复,0不重复),指在U-Boot命令行按下Enter键的时候,重复执行上次的命令
  • _cmd:执行函数(即执行该命令后,运行哪个函数)
  • _usage:短帮助信息
  • _help:长帮助信息

再来看看执行函数do_brightness的声名:

int (*cmd)(struct cmd_tbl_s *cmdtp, int flag, int argc, char *const argv[]);
  • cmdtp:Table entry describing the command (see above).
  • flag:A bitmap which may contain the following bit
    • CMD_FLAG_REPEAT - The last command is repeated.
    • CMD_FLAG_BOOTD - The command is called by the bootd command.
    • CMD_FLAG_ENV - The command is called by the run command.
  • argc:执行命令时,传入的参数数量
  • argv:传入的参数

实践

下面,添加一个U-Boot菜单,不过只作打印,没有实际功能。
<u-boot>/drivers下创建一个名为mymenu的文件夹:
image.png
mymenu文件夹下创建mymenu.c,内容如下:

#include <common.h>
#include <command.h>
#include <linux/ctype.h>
#include <cli.h>
#include <fs.h>

static int do_mymenu(struct cmd_tbl_s *cmdtp, int flag, int argc, char *const argv[])
{
    if(argc != 2)
        return 0;
    
    if(!strcmp(argv[1], "pageone"))
        printf("\n======== pageone ========\n");            
    else if(!strcmp(argv[1], "pagetwo"))
        printf("\n======== pagetwo ========\n"); 
    else
        printf("\n======== pageone ========\n"); 

    printf("== [1] xxxxxx\n");
    printf("== [2] xxxxxx\n");
    printf("== [3] xxxxxx\n");
    printf("== [4] xxxxxx\n");
    printf("=========================\n\n");

    return 0;
}

U_BOOT_CMD(
	mymenu,	2,	1, do_mymenu,
	"here is uboot mymenu\n",
	"here is uboot mymenu, make in 2024-05-15\n"
);

还需在mymenu文件夹下创建一个Makefile文件,内容如下:

obj-y += mymenu.o

最后修改<u-boot>/drivers/下的Makefile,在结尾加上如下内容,表示要编译mymenu路径下的文件:
image.png
编译U-Boot,更新U-Boot,重启单板,在U-Boot倒计时结束前,按任意按键进入U-Boot命令行,输入mymenu后,可以看到命令被正确识别,对应的函数也执行成功:
image.png
那如何做到按任意按键后直接调起菜单呢?上面有说过,可以在autoboot检测到按键按下后,调用run_command()函数执行mymenu命令,从而调起菜单。

void autoboot_command(const char *s)
{
    debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "<UNDEFINED>");

    if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    int prev = disable_ctrlc(1);    /* disable Control C checking */
#endif

    run_command_list(s, -1, 0);

#if defined(CONFIG_AUTOBOOT_KEYED) && !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    disable_ctrlc(prev);    /* restore Control C checking */
#endif
    }

    //启动菜单
    run_command("mymenu", 0);
    
#ifdef CONFIG_MENUKEY
    if (menukey == CONFIG_MENUKEY) {
        s = env_get("menucmd");
        if (s)
            run_command_list(s, -1, 0);
    }
#endif /* CONFIG_MENUKEY */
}

image.png
剩下的菜单程序编写就是根据实际功能来开发了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/639015.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ESP8266实现获取天气情况

利用太极创客提供的ESP8266 心知天气库获取天气情况并显示 心知天气库地址&#xff1a; ESP8266-心知天气: 本库主要功能为使用ESP8266物联网开发板通过心知天气 API 获取天气等信息。 clone到本地: git clone https://gitee.com/taijichuangke/ESP8266-Seniverse.git 安装该…

go语言的一些常见踩坑问题

开始之前&#xff0c;介绍一下​最近很火的开源技术&#xff0c;低代码。 作为一种软件开发技术逐渐进入了人们的视角里&#xff0c;它利用自身独特的优势占领市场一角——让使用者可以通过可视化的方式&#xff0c;以更少的编码&#xff0c;更快速地构建和交付应用软件&#…

ArkUI-X开发指南:【SDK配置和构建说明】

ArkUI-X SDK配置和构建说明 ArkUI-X SDK是ArkUI-X开源项目的编译产物&#xff0c;可将ArkUI-X SDK集成到现有Android和iOS应用工程中&#xff0c;使开发者基于一套ArkTS主代码&#xff0c;就可以构建支持多平台的精美、高性能应用。SDK内容包含ArkUI跨平台运行时&#xff0c;组…

【高频】从输入URL到页面展示到底发生了什么?

一、相关衍生面试问题&#xff1a; 浏览器输入美团网站&#xff0c;从回车到浏览器展示经历了哪些过程 &#xff1f; http输入网页之后的流程&#xff1f; 百度搜索页面&#xff0c;从点开搜索框&#xff0c;到显示搜索页面经历了什么&#xff1f; 二、探究各个过程&#x…

[Algorithm][回溯][记忆化搜索][最长递增子序列][猜数字大小Ⅱ][矩阵中的最长递增路径]详细讲解

目录 1.最长递增子序列1.题目链接2.算法原理详解3.代码实现 2.猜数字大小 II1.题目链接2.算法原理详解3.代码实现 3.矩阵中的最长递增路径1.题目链接2.算法原理详解3.代码实现 1.最长递增子序列 1.题目链接 最长递增子序列 2.算法原理详解 题目解析&#xff1a;从每个位置&am…

【BUG】Edge|联想电脑 Bing 搜索报错“Ref A: 乱码、 Ref B:乱码、Ref C: 日期” 的解决办法

文章目录 省流版前言解决办法 详细解释版前言问题描述与排查过程解决办法与总结 省流版 我原以为我解决了&#xff0c;才发的博客&#xff0c;晚上用了一下其他设备发现还是会出现这个问题… 这篇博客并未解决该问题&#xff0c;如果评论里有人解决了这个问题不胜感激&#x…

博客说明 5/12~5/24【个人】

博客说明 5/12~5/24【个人】 前言版权博客说明 5/12~5/24【个人】对比最后 前言 2024-5-24 13:39:23 对我在2024年5月12日到5月24日发布的博客做一下简要的说明 以下内容源自《【个人】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作…

Undet for SketchUp 2023.3 点云建模软件 支持支持草图大师sketchup2021-2022-2023

1.Undet for sketchup 2023.3支持草图大师sketchup2021-2022-2023。支持机载雷达扫描、车载扫描还是地面扫描&#xff0c;对AEC行业用户来说&#xff0c;真正需要的是如何将这些数据快速处理为三维模型&#xff0c;这样才能将这些信息延展到BIM领域发挥效用。因此面对这些海量的…

【真实项目中收获的提升】- 前后端联调

场景 小型项目前后端联调&#xff0c;不需要部署到sit或uat环境下。 解决方法 后端部署frp服务 下载frp软件 配置frpc.ini文件&#xff0c;先把文件设置可编辑(可同时配置多个 例如下面的chz 上面打码的是frp服务器地址) 然后起start.bat 其实就是执行frpc -c frpc.ini命令…

4、PHP的xml注入漏洞(xxe)

青少年ctf&#xff1a;PHP的XXE 1、打开网页是一个PHP版本页面 2、CTRLf搜索xml&#xff0c;发现2.8.0版本&#xff0c;含有xml漏洞 3、bp抓包 4、使用代码出发bug GET /simplexml_load_string.php HTTP/1.1 补充&#xff1a; <?xml version"1.0" encoding&quo…

DockerNetwork

Docker Network Docker Network 是 Docker 引擎提供的一种功能&#xff0c;用于管理 Docker 容器之间以及容器与外部网络之间的网络通信。它允许用户定义和配置容器的网络环境&#xff0c;以便容器之间可以相互通信&#xff0c;并与外部网络进行连接。 Docker Network 提供了以…

docker容器安装mysql

linux: centOS-7 hadoop: 3.3.6 前置章节&#xff1a; (图文并茂)基于CentOS-7搭建hadoop3.3.6大数据集群-CSDN博客 可选&#xff1a;zookeeper安装教程-CSDN博客 1.安装docker 1.1 添加docker的repo源 sudo yum-config-manager --add-repo http://mirrors.aliyun.com/…

猫狗电子玩具底层方案开发

物在现代年轻人中的地位已经超越了简单的“宠物”定义&#xff0c;它们已经成为了家庭的一部分&#xff0c;是人们生活中不可或缺的伴侣和朋友。 因此营运而生了很多宠物用品&#xff0c;比如喂食器、电子逗猫棒、饮水机、说话按钮等等宠物电子玩具周边。宠物玩具在宠物生活中…

Python-3.12.0文档解读-内置函数hash()详细说明+记忆策略+常用场景+巧妙用法+综合技巧

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 详细说明 功能描述 参数 返回值 特性 使用示例 注意事项 记忆策略 常用场景 …

【数学建模】储药柜的设计

2014高教社杯全国大学生数学建模竞赛D题目 题目描述 储药柜的结构类似于书橱&#xff0c;通常由若干个横向隔板和竖向隔板将储药柜分割成若干个储药槽(如图1所示)。为保证药品分拣的准确率&#xff0c;防止发药错误&#xff0c;一个储药槽内只能摆放同一种药品。药品在储药槽…

SpringBoot实现增量部署

目录&#xff1a; 1、使用背景2、实现流程3、部署增量包到项目中并启动4、说明 1、使用背景 最近发现公司发布版本时候&#xff0c;很齐全&#xff0c;接口文档&#xff0c;部署方式等都很好&#xff0c;其中有个增量部署包&#xff0c;有点兴趣&#xff0c;不清楚怎么生成增量…

vue3+ts+vant4 实现购物车 前端代码

一、功能效果 二、前端代码 购物车的vue代码 <template><van-nav-bar left-text"返回" title"购物车" click-left"onClickLeft"><template #right><van-popover v-model:show"showPopover" placement"bot…

SQLite数据库免改造透明加密解决方案:给数据加把锁

在数字化时代&#xff0c;信息安全和隐私保护显得尤为重要。TDE透明加密技术&#xff0c;是一种在用户无感知的情况下对数据进行加密和解密的技术。它能够在数据生成、存储、传输和使用过程中自动进行加密处理&#xff0c;无需用户手动操作。透明加密技术的核心在于其透明性&am…

登录接口测试

登录接口测试 数据驱动

java spring cloud 企业工程管理系统源码+二次开发+定制化服务 em

在建筑行业中&#xff0c;工程项目管理软件&#xff08;工程项目管理系统&#xff09;扮演着至关重要的角色&#xff0c;它为建设工程项目管理提供了全方位、全过程的综合管理支持。从项目组织建设、策划决策、规划设计&#xff0c;到施工建设、竣工交付、总结评估&#xff0c;…