]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
env: Allow U-Boot scripts to be placed in a .env file
authorSimon Glass <sjg@chromium.org>
Fri, 22 Oct 2021 03:08:46 +0000 (21:08 -0600)
committerTom Rini <trini@konsulko.com>
Tue, 16 Nov 2021 19:35:08 +0000 (14:35 -0500)
At present U-Boot environment variables, and thus scripts, are defined
by CONFIG_EXTRA_ENV_SETTINGS. It is painful to add large amounts of text
to this file and dealing with quoting and newlines is harder than it
should be. It would be better if we could just type the script into a
text file and have it included by U-Boot.

Add a feature that brings in a .env file associated with the board
config, if present. To use it, create a file in a board/<vendor>
directory, typically called <board>.env and controlled by the
CONFIG_ENV_SOURCE_FILE option.

The environment variables should be of the form "var=value". Values can
extend to multiple lines. See the README under 'Environment Variables:'
for more information and an example.

In many cases environment variables need access to the U-Boot CONFIG
variables to select different options. Enable this so that the environment
scripts can be as useful as the ones currently in the board config files.
This uses the C preprocessor, means that comments can be included in the
environment using /* ... */

Also support += to allow variables to be appended to. This is needed when
using the preprocessor.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Marek BehĂșn <marek.behun@nic.cz>
Tested-by: Marek BehĂșn <marek.behun@nic.cz>
MAINTAINERS
Makefile
config.mk
doc/usage/environment.rst
doc/usage/index.rst
env/Kconfig
env/embedded.c
include/env_default.h
scripts/env2string.awk [new file with mode: 0644]
test/py/tests/test_env.py

index 6db5354322fe26d272a4e23c24706c0ba13b5b3e..ae0262edfadee31130ee3642ab1bb1cc472a0d0f 100644 (file)
@@ -760,6 +760,13 @@ F: test/env/
 F:     tools/env*
 F:     tools/mkenvimage.c
 
+ENVIRONMENT AS TEXT
+M:     Simon Glass <sjg@chromium.org>
+R:     Wolfgang Denk <wd@denx.de>
+S:     Maintained
+F:     doc/usage/environment.rst
+F:     scripts/env2string.awk
+
 FPGA
 M:     Michal Simek <michal.simek@xilinx.com>
 S:     Maintained
index 0220e8ded99331d5d3e26e0a38a2f92dcc6fb52d..338ae3341e6d92fb8fdf741e06564c2b3023269b 100644 (file)
--- 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."
index 7bb1fd4ed1b5036e76d083ff00f425ef54f98834..2595aed218b951fa240789dfa3dba2a507565328 100644 (file)
--- 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
index 7a733b44556666b32b0327d6da5a5898b1465995..043c02d9a94edd3b3c3e779f15f33ea1914e1199 100644 (file)
@@ -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/<vendor>/<board>/<CONFIG_ENV_SOURCE_FILE>.env
+
+or::
+
+   board/<vendor>/<board>/<CONFIG_SYS_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
index 356f2a56181b2bbdcbc6ddb8ad494f1af0969ef9..4314112ff349dcce00a190aa6ccc8044b62332bf 100644 (file)
@@ -10,6 +10,7 @@ Use U-Boot
    netconsole
    partitions
    cmdline
+   environment
 
 Shell commands
 --------------
index 06d72bad1dcb73a4d46ae02f9424de93e86c676e..24966f8c37380f713c34db3fa2bd3c5b241b657a 100644 (file)
@@ -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/<vendor>/<board>/<CONFIG_ENV_SOURCE_FILE>.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/<vendor>/<board>/<SYS_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
 
index 208553e6af172489d4da74774eb1e68f9dab3322..9f26e6cad9c48c063898a3ecb752bf39e041c600 100644 (file)
@@ -66,6 +66,7 @@
 #endif
 
 #define DEFAULT_ENV_INSTANCE_EMBEDDED
+#include <config.h>
 #include <env_default.h>
 
 #ifdef CONFIG_ENV_ADDR_REDUND
index 23430dc70d7fd12074f7c1b1280dc8036e5798e4..401e84e3d51ff345d512b7574fccb1c429f6d006 100644 (file)
 #include <env_callback.h>
 #include <linux/stringify.h>
 
+#ifndef USE_HOSTCC
+#include <generated/environment.h>
+#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 (file)
index 0000000..57d0fc8
--- /dev/null
@@ -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 "<environment here>"
+#
+# 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"
+       }
+}
index 9bed2f48d77e674a2b4b062bdb15cbb18e7e966a..f85cb031382e9881e36a87c0ae66b1cccb2922c4 100644 (file)
@@ -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')