diff --git a/MAINTAINERS b/MAINTAINERS index 6db5354322..ae0262edfa 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -760,6 +760,13 @@ F: test/env/ F: tools/env* F: tools/mkenvimage.c +ENVIRONMENT AS TEXT +M: Simon Glass +R: Wolfgang Denk +S: Maintained +F: doc/usage/environment.rst +F: scripts/env2string.awk + FPGA M: Michal Simek S: Maintained diff --git a/Makefile b/Makefile index 0220e8ded9..338ae3341e 100644 --- a/Makefile +++ b/Makefile @@ -517,6 +517,7 @@ version_h := include/generated/version_autogenerated.h timestamp_h := include/generated/timestamp_autogenerated.h defaultenv_h := include/generated/defaultenv_autogenerated.h dt_h := include/generated/dt.h +env_h := include/generated/environment.h no-dot-config-targets := clean clobber mrproper distclean \ help %docs check% coccicheck \ @@ -1794,6 +1795,69 @@ quiet_cmd_sym ?= SYM $@ u-boot.sym: u-boot FORCE $(call if_changed,sym) +# Environment processing +# --------------------------------------------------------------------------- + +# Directory where we expect the .env file, if it exists +ENV_DIR := $(srctree)/board/$(BOARDDIR) + +# Basename of .env file, stripping quotes +ENV_SOURCE_FILE := $(CONFIG_ENV_SOURCE_FILE:"%"=%) + +# Filename of .env file +ENV_FILE_CFG := $(ENV_DIR)/$(ENV_SOURCE_FILE).env + +# Default filename, if CONFIG_ENV_SOURCE_FILE is empty +ENV_FILE_BOARD := $(ENV_DIR)/$(CONFIG_SYS_BOARD:"%"=%).env + +# Select between the CONFIG_ENV_SOURCE_FILE and the default one +ENV_FILE := $(if $(ENV_SOURCE_FILE),$(ENV_FILE_CFG),$(wildcard $(ENV_FILE_BOARD))) + +# Run the environment text file through the preprocessor, but only if it is +# non-empty, to save time and possible build errors if something is wonky with +# the board +quiet_cmd_gen_envp = ENVP $@ + cmd_gen_envp = \ + if [ -s "$(ENV_FILE)" ]; then \ + $(CPP) -P $(CFLAGS) -x assembler-with-cpp -D__ASSEMBLY__ \ + -D__UBOOT_CONFIG__ \ + -I . -I include -I $(srctree)/include \ + -include linux/kconfig.h -include include/config.h \ + -I$(srctree)/arch/$(ARCH)/include \ + $< -o $@; \ + else \ + echo -n >$@ ; \ + fi +include/generated/env.in: include/generated/env.txt FORCE + $(call cmd,gen_envp) + +# Regenerate the environment if it changes +# We use 'wildcard' since the file is not required to exist (at present), in +# which case we don't want this dependency, but instead should create an empty +# file +# This rule is useful since it shows the source file for the environment +quiet_cmd_envc = ENVC $@ + cmd_envc = \ + if [ -f "$<" ]; then \ + cat $< > $@; \ + elif [ -n "$(ENV_SOURCE_FILE)" ]; then \ + echo "Missing file $(ENV_FILE_CFG)"; \ + else \ + echo -n >$@ ; \ + fi + +include/generated/env.txt: $(wildcard $(ENV_FILE)) FORCE + $(call cmd,envc) + +# Write out the resulting environment, converted to a C string +quiet_cmd_gen_envt = ENVT $@ + cmd_gen_envt = \ + awk -f $(srctree)/scripts/env2string.awk $< >$@ +$(env_h): include/generated/env.in + $(call cmd,gen_envt) + +# --------------------------------------------------------------------------- + # The actual objects are generated when descending, # make sure no implicit rule kicks in $(sort $(u-boot-init) $(u-boot-main)): $(u-boot-dirs) ; @@ -1849,7 +1913,7 @@ endif # prepare2 creates a makefile if using a separate output directory prepare2: prepare3 outputmakefile cfg -prepare1: prepare2 $(version_h) $(timestamp_h) $(dt_h) \ +prepare1: prepare2 $(version_h) $(timestamp_h) $(dt_h) $(env_h) \ include/config/auto.conf ifeq ($(wildcard $(LDSCRIPT)),) @echo >&2 " Could not find linker script." diff --git a/config.mk b/config.mk index 7bb1fd4ed1..2595aed218 100644 --- a/config.mk +++ b/config.mk @@ -50,8 +50,10 @@ endif ifneq ($(BOARD),) ifdef VENDOR BOARDDIR = $(VENDOR)/$(BOARD) +ENVDIR=${vendor}/env else BOARDDIR = $(BOARD) +ENVDIR=${board}/env endif endif ifdef BOARD diff --git a/doc/usage/environment.rst b/doc/usage/environment.rst index 7a733b4455..043c02d9a9 100644 --- a/doc/usage/environment.rst +++ b/doc/usage/environment.rst @@ -15,7 +15,86 @@ environment is erased by accident, a default environment is provided. Some configuration options can be set using Environment Variables. -List of environment variables (most likely not complete): +Text-based Environment +---------------------- + +The default environment for a board is created using a `.env` environment file +using a simple text format. The base filename for this is defined by +`CONFIG_ENV_SOURCE_FILE`, or `CONFIG_SYS_BOARD` if that is empty. + +The file must be in the board directory and have a .env extension, so +assuming that there is a board vendor, the resulting filename is therefore:: + + board///.env + +or:: + + board///.env + +This is a plain text file where you can type your environment variables in +the form `var=value`. Blank lines and multi-line variables are supported. +The conversion script looks for a line that starts in column 1 with a string +and has an equals sign immediately afterwards. Spaces before the = are not +permitted. It is a good idea to indent your scripts so that only the 'var=' +appears at the start of a line. + +To add additional text to a variable you can use `var+=value`. This text is +merged into the variable during the make process and made available as a +single value to U-Boot. Variables can contain `+` characters but in the unlikely +event that you want to have a variable name ending in plus, put a backslash +before the `+` so that the script knows you are not adding to an existing +variable but assigning to a new one:: + + maximum\+=value + +This file can include C-style comments. Blank lines and multi-line +variables are supported, and you can use normal C preprocessor directives +and CONFIG defines from your board config also. + +For example, for snapper9260 you would create a text file called +`board/bluewater/snapper9260.env` containing the environment text. + +Example:: + + stdout=serial + #ifdef CONFIG_LCD + stdout+=,lcd + #endif + bootcmd= + /* U-Boot script for booting */ + + if [ -z ${tftpserverip} ]; then + echo "Use 'setenv tftpserverip a.b.c.d' to set IP address." + fi + + usb start; setenv autoload n; bootp; + tftpboot ${tftpserverip}: + bootm + failed= + /* Print a message when boot fails */ + echo CONFIG_SYS_BOARD boot failed - please check your image + echo Load address is CONFIG_SYS_LOAD_ADDR + +If CONFIG_ENV_SOURCE_FILE is empty and the default filename is not present, then +the old-style C environment is used instead. See below. + +Old-style C environment +----------------------- + +Traditionally, the default environment is created in `include/env_default.h`, +and can be augmented by various `CONFIG` defines. See that file for details. In +particular you can define `CONFIG_EXTRA_ENV_SETTINGS` in your board file +to add environment variables. + +Board maintainers are encouraged to migrate to the text-based environment as it +is easier to maintain. The distro-board script still requires the old-style +environment but work is underway to address this. + + +List of environment variables +----------------------------- + +This is most-likely not complete: baudrate see CONFIG_BAUDRATE diff --git a/doc/usage/index.rst b/doc/usage/index.rst index 356f2a5618..4314112ff3 100644 --- a/doc/usage/index.rst +++ b/doc/usage/index.rst @@ -10,6 +10,7 @@ Use U-Boot netconsole partitions cmdline + environment Shell commands -------------- diff --git a/env/Kconfig b/env/Kconfig index 06d72bad1d..24966f8c37 100644 --- a/env/Kconfig +++ b/env/Kconfig @@ -3,6 +3,24 @@ menu "Environment" config ENV_SUPPORT def_bool y +config ENV_SOURCE_FILE + string "Environment file to use" + default "" + help + This sets the basename to use to generate the default environment. + This a text file as described in doc/usage/environment.rst + + The file must be in the board directory and have a .env extension, so + the resulting filename is typically + board///.env + + If the file is not present, an error is produced. + + If this CONFIG is empty, U-Boot uses CONFIG SYS_BOARD as a default, if + the file board///.env exists. Otherwise the + environment is assumed to come from the ad-hoc + CONFIG_EXTRA_ENV_SETTINGS #define + config SAVEENV def_bool y if CMD_SAVEENV diff --git a/env/embedded.c b/env/embedded.c index 208553e6af..9f26e6cad9 100644 --- a/env/embedded.c +++ b/env/embedded.c @@ -66,6 +66,7 @@ #endif #define DEFAULT_ENV_INSTANCE_EMBEDDED +#include #include #ifdef CONFIG_ENV_ADDR_REDUND diff --git a/include/env_default.h b/include/env_default.h index 23430dc70d..401e84e3d5 100644 --- a/include/env_default.h +++ b/include/env_default.h @@ -10,6 +10,10 @@ #include #include +#ifndef USE_HOSTCC +#include +#endif + #ifdef DEFAULT_ENV_INSTANCE_EMBEDDED env_t embedded_environment __UBOOT_ENV_SECTION__(environment) = { ENV_CRC, /* CRC Sum */ @@ -110,6 +114,13 @@ const char default_environment[] = { #if defined(CONFIG_BOOTCOUNT_BOOTLIMIT) && (CONFIG_BOOTCOUNT_BOOTLIMIT > 0) "bootlimit=" __stringify(CONFIG_BOOTCOUNT_BOOTLIMIT)"\0" #endif +#ifdef CONFIG_EXTRA_ENV_TEXT +# ifdef CONFIG_EXTRA_ENV_SETTINGS +# error "Your board uses a text-file environment, so must not define CONFIG_EXTRA_ENV_SETTINGS" +# endif + /* This is created in the Makefile */ + CONFIG_EXTRA_ENV_TEXT +#endif #ifdef CONFIG_EXTRA_ENV_SETTINGS CONFIG_EXTRA_ENV_SETTINGS #endif diff --git a/scripts/env2string.awk b/scripts/env2string.awk new file mode 100644 index 0000000000..57d0fc8f3b --- /dev/null +++ b/scripts/env2string.awk @@ -0,0 +1,80 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# Copyright 2021 Google, Inc +# +# SPDX-License-Identifier: GPL-2.0+ +# +# Awk script to parse a text file containing an environment and convert it +# to a C string which can be compiled into U-Boot. + +# The resulting output is: +# +# #define CONFIG_EXTRA_ENV_TEXT "" +# +# If the input is empty, this script outputs a comment instead. + +BEGIN { + # env holds the env variable we are currently processing + env = ""; + ORS = "" +} + +# Skip empty lines, as these are generated by the clang preprocessor +NF { + # Quote quotes + gsub("\"", "\\\"") + + # Is this the start of a new environment variable? + if (match($0, "^([^ \t=][^ =]*)=(.*)$", arr)) { + if (length(env) != 0) { + # Record the value of the variable now completed + vars[var] = env + } + var = arr[1] + env = arr[2] + + # Deal with += which concatenates the new string to the existing + # variable + if (length(env) != 0 && match(var, "^(.*)[+]$", var_arr)) + { + # Allow var\+=val to indicate that the variable name is + # var+ and this is not actually a concatenation + if (substr(var_arr[1], length(var_arr[1])) == "\\") { + # Drop the backslash + sub(/\\[+]$/, "+", var) + } else { + var = var_arr[1] + env = vars[var] env + } + } + } else { + # Change newline to space + gsub(/^[ \t]+/, "") + + # Don't keep leading spaces generated by the previous blank line + if (length(env) == 0) { + env = $0 + } else { + env = env " " $0 + } + } +} + +END { + # Record the value of the variable now completed. If the variable is + # empty it is not set. + if (length(env) != 0) { + vars[var] = env + } + + if (length(vars) != 0) { + printf("%s", "#define CONFIG_EXTRA_ENV_TEXT \"") + + # Print out all the variables + for (var in vars) { + env = vars[var] + print var "=" vars[var] "\\0" + } + print "\"\n" + } +} diff --git a/test/py/tests/test_env.py b/test/py/tests/test_env.py index 9bed2f48d7..f85cb03138 100644 --- a/test/py/tests/test_env.py +++ b/test/py/tests/test_env.py @@ -7,6 +7,7 @@ import os import os.path from subprocess import call, check_call, CalledProcessError +import tempfile import pytest import u_boot_utils @@ -515,3 +516,109 @@ def test_env_ext4(state_test_env): finally: if fs_img: call('rm -f %s' % fs_img, shell=True) + +def test_env_text(u_boot_console): + """Test the script that converts the environment to a text file""" + + def check_script(intext, expect_val): + """Check a test case + + Args: + intext: Text to pass to the script + expect_val: Expected value of the CONFIG_EXTRA_ENV_TEXT string, or + None if we expect it not to be defined + """ + with tempfile.TemporaryDirectory() as path: + fname = os.path.join(path, 'infile') + with open(fname, 'w') as inf: + print(intext, file=inf) + result = u_boot_utils.run_and_log(cons, ['awk', '-f', script, fname]) + if expect_val is not None: + expect = '#define CONFIG_EXTRA_ENV_TEXT "%s"\n' % expect_val + assert result == expect + else: + assert result == '' + + cons = u_boot_console + script = os.path.join(cons.config.source_dir, 'scripts', 'env2string.awk') + + # simple script with a single var + check_script('fred=123', 'fred=123\\0') + + # no vars + check_script('', None) + + # two vars + check_script('''fred=123 +ernie=456''', 'fred=123\\0ernie=456\\0') + + # blank lines + check_script('''fred=123 + + +ernie=456 + +''', 'fred=123\\0ernie=456\\0') + + # append + check_script('''fred=123 +ernie=456 +fred+= 456''', 'fred=123 456\\0ernie=456\\0') + + # append from empty + check_script('''fred= +ernie=456 +fred+= 456''', 'fred= 456\\0ernie=456\\0') + + # variable with + in it + check_script('fred+ernie=123', 'fred+ernie=123\\0') + + # ignores variables that are empty + check_script('''fred= +fred+= +ernie=456''', 'ernie=456\\0') + + # single-character env name + check_script('''f=123 +e=456 +f+= 456''', 'e=456\\0f=123 456\\0') + + # contains quotes + check_script('''fred="my var" +ernie=another"''', 'fred=\\"my var\\"\\0ernie=another\\"\\0') + + # variable name ending in + + check_script('''fred\\+=my var +fred++= again''', 'fred+=my var again\\0') + + # variable name containing + + check_script('''fred+jane=both +fred+jane+=again +ernie=456''', 'fred+jane=bothagain\\0ernie=456\\0') + + # multi-line vars - new vars always start at column 1 + check_script('''fred=first + second +\tthird with tab + + after blank + confusing=oops +ernie=another"''', 'fred=first second third with tab after blank confusing=oops\\0ernie=another\\"\\0') + + # real-world example + check_script('''ubifs_boot= + env exists bootubipart || + env set bootubipart UBI; + env exists bootubivol || + env set bootubivol boot; + if ubi part ${bootubipart} && + ubifsmount ubi${devnum}:${bootubivol}; + then + devtype=ubi; + run scan_dev_for_boot; + fi +''', + 'ubifs_boot=env exists bootubipart || env set bootubipart UBI; ' + 'env exists bootubivol || env set bootubivol boot; ' + 'if ubi part ${bootubipart} && ubifsmount ubi${devnum}:${bootubivol}; ' + 'then devtype=ubi; run scan_dev_for_boot; fi\\0')