diff mbox series

[11/11] bootm: Support string substitution in bootargs

Message ID 20201019135602.3943835-12-sjg@chromium.org
State Superseded
Delegated to: Tom Rini
Headers show
Series bootm: Support substitions in bootargs and add tests | expand

Commit Message

Simon Glass Oct. 19, 2020, 1:56 p.m. UTC
In some cases it is necessary to pass parameters to Linux so that it will
boot correctly. For example, the rootdev parameter is often used to
specify the root device. However the root device may change depending on
whence U-Boot loads the kernel. At present it is necessary to build up
the command line by adding device strings to it one by one.

It is often more convenient to provide a template for bootargs, with
U-Boot doing the substitution from other environment variables.

Add a way to substitute strings in the bootargs variable. This allows
things like "rootdev=%U" to be used in bootargs, with the %U substitution
providing the UUID of the root device.

For example, to substitute the GUID of the kernel partition:

  setenv bootargs "console=/dev/ttyS0 rootdev=%U/PARTNROFF=1 kern_guid=%U"
  part uuid mmc 2:2 uuid
  setenv bootargs_U $uuid
  bootm

This is particularly useful when the command line from another place. For
example, Chrome OS stores the command line next to the kernel itself. It
depends on the kernel version being used as well as the hardware features,
so it is extremely difficult to devise a U-Boot script that works on all
boards and kernel versions. With this feature, the command line can be
read from disk and used directly, with a few substitutions set up.

Signed-off-by: Simon Glass <sjg@chromium.org>
---

 README              |  16 +++++++
 arch/Kconfig        |   1 +
 common/Kconfig.boot |  20 ++++++++
 common/bootm.c      |  72 +++++++++++++++++++++++++++--
 include/bootm.h     |  14 ++++--
 test/bootm.c        | 110 ++++++++++++++++++++++++++++++++++++++++++--
 6 files changed, 221 insertions(+), 12 deletions(-)

Comments

Rasmus Villemoes Oct. 19, 2020, 2:43 p.m. UTC | #1
On 19/10/2020 15.56, Simon Glass wrote:
> In some cases it is necessary to pass parameters to Linux so that it will
> boot correctly. For example, the rootdev parameter is often used to
> specify the root device. However the root device may change depending on
> whence U-Boot loads the kernel. At present it is necessary to build up
> the command line by adding device strings to it one by one.
> 
> It is often more convenient to provide a template for bootargs, with
> U-Boot doing the substitution from other environment variables.
> 
> Add a way to substitute strings in the bootargs variable. This allows
> things like "rootdev=%U" to be used in bootargs, with the %U substitution
> providing the UUID of the root device.
> 
> For example, to substitute the GUID of the kernel partition:
> 
>   setenv bootargs "console=/dev/ttyS0 rootdev=%U/PARTNROFF=1 kern_guid=%U"
>   part uuid mmc 2:2 uuid
>   setenv bootargs_U $uuid
>   bootm
> 
> This is particularly useful when the command line from another place. For
> example, Chrome OS stores the command line next to the kernel itself. It
> depends on the kernel version being used as well as the hardware features,
> so it is extremely difficult to devise a U-Boot script that works on all
> boards and kernel versions. With this feature, the command line can be
> read from disk and used directly, with a few substitutions set up.
> 
> Signed-off-by: Simon Glass <sjg@chromium.org>
> ---
> 
>  README              |  16 +++++++
>  arch/Kconfig        |   1 +
>  common/Kconfig.boot |  20 ++++++++
>  common/bootm.c      |  72 +++++++++++++++++++++++++++--
>  include/bootm.h     |  14 ++++--
>  test/bootm.c        | 110 ++++++++++++++++++++++++++++++++++++++++++--
>  6 files changed, 221 insertions(+), 12 deletions(-)
> 
> diff --git a/README b/README
> index 91c5a1a8fa3..263e31ab7f6 100644
> --- a/README
> +++ b/README
> @@ -3229,6 +3229,22 @@ List of environment variables (most likely not complete):
>  
>    bootargs	- Boot arguments when booting an RTOS image
>  
> +  bootargs_subst - Substitution parameters for bootargs. These are applied after
> +		  the commandline has been built. The format is:
> +
> +		    <key>=<value>[!<key>=<value>...]
> +
> +		  where
> +		    <key> is a string to replace
> +		    <value> is the value to replace it with (either a simple
> +		       string or an environment variable starting with $
> +
> +		  One use for this is to insert the root-disk UUID into the
> +		  command line where bootargs contains "root=%U"
> +
> +		    part uuid mmc 2:2 uuid
> +		    setenv cmdline_repl %U=$uuid

cmdline_repl seems to be stale, it should be bootargs_subst. But the
whole paragraph seems stale, as you actually implement and test the
bootargs_X approach.

Anyway, this does seem useful, but I really question the choice of
leaving %A in there if bootargs_A does not exist. It's hard if not
impossible to create an environment variable whose value is empty, and I
can easily imagine it would be useful to allow a %A to expand to
nothing. So why not just use the usual semantics of requiring a double
%% to put a single % in the output? It's probably quite rare that one
would need that anyway.

Rasmus
Wolfgang Denk Oct. 19, 2020, 2:54 p.m. UTC | #2
Dear Simon,

In message <20201019135602.3943835-12-sjg@chromium.org> you wrote:
> In some cases it is necessary to pass parameters to Linux so that it will
> boot correctly. For example, the rootdev parameter is often used to
> specify the root device. However the root device may change depending on
> whence U-Boot loads the kernel. At present it is necessary to build up
> the command line by adding device strings to it one by one.
>
> It is often more convenient to provide a template for bootargs, with
> U-Boot doing the substitution from other environment variables.
>
> Add a way to substitute strings in the bootargs variable. This allows
> things like "rootdev=%U" to be used in bootargs, with the %U substitution
> providing the UUID of the root device.

Argh, no, please don't.

You add something unconditionally to common code which very few
people need.  U-Boot size is growing all the time because of such
... features.  This may be acceptable on the systems you have in
mind, but I consider this selfish.

Why do we have to add yet another non-standard way of substituting
variables in a string?  Can we not use alreay existing methonds
instead?

Why do you have to use "%U" in your template instead of for example
"${uuid}" ?



Best regards,

Wolfgang Denk
Michael Walle Oct. 19, 2020, 3:47 p.m. UTC | #3
Am 2020-10-19 16:54, schrieb Wolfgang Denk:
> Dear Simon,
> 
> In message <20201019135602.3943835-12-sjg@chromium.org> you wrote:
>> In some cases it is necessary to pass parameters to Linux so that it 
>> will
>> boot correctly. For example, the rootdev parameter is often used to
>> specify the root device. However the root device may change depending 
>> on
>> whence U-Boot loads the kernel. At present it is necessary to build up
>> the command line by adding device strings to it one by one.
>> 
>> It is often more convenient to provide a template for bootargs, with
>> U-Boot doing the substitution from other environment variables.
>> 
>> Add a way to substitute strings in the bootargs variable. This allows
>> things like "rootdev=%U" to be used in bootargs, with the %U 
>> substitution
>> providing the UUID of the root device.
> 
> Argh, no, please don't.
> 
> You add something unconditionally to common code which very few
> people need.  U-Boot size is growing all the time because of such
> ... features.  This may be acceptable on the systems you have in
> mind, but I consider this selfish.
> 
> Why do we have to add yet another non-standard way of substituting
> variables in a string?  Can we not use alreay existing methonds
> instead?
> 
> Why do you have to use "%U" in your template instead of for example
> "${uuid}" ?

I'd second that. Having variables evaluated inside the bootargs would
be very valuable IMHO.

At the moment we have some cumbersome constructs like
   set_bootargs="setenv bootargs bla ${var}"

-michael
Simon Glass Oct. 19, 2020, 3:50 p.m. UTC | #4
Hi Wolfgang,

On Mon, 19 Oct 2020 at 08:55, Wolfgang Denk <wd@denx.de> wrote:
>
> Dear Simon,
>
> In message <20201019135602.3943835-12-sjg@chromium.org> you wrote:
> > In some cases it is necessary to pass parameters to Linux so that it will
> > boot correctly. For example, the rootdev parameter is often used to
> > specify the root device. However the root device may change depending on
> > whence U-Boot loads the kernel. At present it is necessary to build up
> > the command line by adding device strings to it one by one.
> >
> > It is often more convenient to provide a template for bootargs, with
> > U-Boot doing the substitution from other environment variables.
> >
> > Add a way to substitute strings in the bootargs variable. This allows
> > things like "rootdev=%U" to be used in bootargs, with the %U substitution
> > providing the UUID of the root device.
>
> Argh, no, please don't.
>
> You add something unconditionally to common code which very few
> people need.  U-Boot size is growing all the time because of such
> ... features.  This may be acceptable on the systems you have in
> mind, but I consider this selfish.

Did you see the Kconfig option?

>
> Why do we have to add yet another non-standard way of substituting
> variables in a string?  Can we not use alreay existing methonds
> instead?

What sort of methods?

>
> Why do you have to use "%U" in your template instead of for example
> "${uuid}" ?

This is what Chrome OS uses, so it is easier this way, Otherwise I
have to replace %U with ${uuid} first.

Which code can I use to parse the ${var} thing?

Regards,
Simon
Simon Glass Oct. 19, 2020, 3:50 p.m. UTC | #5
Hi Rasmus,

On Mon, 19 Oct 2020 at 08:43, Rasmus Villemoes
<rasmus.villemoes@prevas.dk> wrote:
>
> On 19/10/2020 15.56, Simon Glass wrote:
> > In some cases it is necessary to pass parameters to Linux so that it will
> > boot correctly. For example, the rootdev parameter is often used to
> > specify the root device. However the root device may change depending on
> > whence U-Boot loads the kernel. At present it is necessary to build up
> > the command line by adding device strings to it one by one.
> >
> > It is often more convenient to provide a template for bootargs, with
> > U-Boot doing the substitution from other environment variables.
> >
> > Add a way to substitute strings in the bootargs variable. This allows
> > things like "rootdev=%U" to be used in bootargs, with the %U substitution
> > providing the UUID of the root device.
> >
> > For example, to substitute the GUID of the kernel partition:
> >
> >   setenv bootargs "console=/dev/ttyS0 rootdev=%U/PARTNROFF=1 kern_guid=%U"
> >   part uuid mmc 2:2 uuid
> >   setenv bootargs_U $uuid
> >   bootm
> >
> > This is particularly useful when the command line from another place. For
> > example, Chrome OS stores the command line next to the kernel itself. It
> > depends on the kernel version being used as well as the hardware features,
> > so it is extremely difficult to devise a U-Boot script that works on all
> > boards and kernel versions. With this feature, the command line can be
> > read from disk and used directly, with a few substitutions set up.
> >
> > Signed-off-by: Simon Glass <sjg@chromium.org>
> > ---
> >
> >  README              |  16 +++++++
> >  arch/Kconfig        |   1 +
> >  common/Kconfig.boot |  20 ++++++++
> >  common/bootm.c      |  72 +++++++++++++++++++++++++++--
> >  include/bootm.h     |  14 ++++--
> >  test/bootm.c        | 110 ++++++++++++++++++++++++++++++++++++++++++--
> >  6 files changed, 221 insertions(+), 12 deletions(-)
> >
> > diff --git a/README b/README
> > index 91c5a1a8fa3..263e31ab7f6 100644
> > --- a/README
> > +++ b/README
> > @@ -3229,6 +3229,22 @@ List of environment variables (most likely not complete):
> >
> >    bootargs   - Boot arguments when booting an RTOS image
> >
> > +  bootargs_subst - Substitution parameters for bootargs. These are applied after
> > +               the commandline has been built. The format is:
> > +
> > +                 <key>=<value>[!<key>=<value>...]
> > +
> > +               where
> > +                 <key> is a string to replace
> > +                 <value> is the value to replace it with (either a simple
> > +                    string or an environment variable starting with $
> > +
> > +               One use for this is to insert the root-disk UUID into the
> > +               command line where bootargs contains "root=%U"
> > +
> > +                 part uuid mmc 2:2 uuid
> > +                 setenv cmdline_repl %U=$uuid
>
> cmdline_repl seems to be stale, it should be bootargs_subst. But the
> whole paragraph seems stale, as you actually implement and test the
> bootargs_X approach.

Yes, thanks, will fix.

>
> Anyway, this does seem useful, but I really question the choice of
> leaving %A in there if bootargs_A does not exist. It's hard if not
> impossible to create an environment variable whose value is empty, and I
> can easily imagine it would be useful to allow a %A to expand to
> nothing. So why not just use the usual semantics of requiring a double
> %% to put a single % in the output? It's probably quite rare that one
> would need that anyway.

I did wonder about the empty env var thing. IMO it would be nice to
support empty variables, so we can distinguish between an empty one
and a missing one.

My concern with removing the var is if people have % in the string.
This way I don't have to worry about quoting, etc. See Wolgang's email
in this thread too.

Regards,
Simon
Simon Glass Oct. 19, 2020, 3:52 p.m. UTC | #6
Hi Michael,

On Mon, 19 Oct 2020 at 09:47, Michael Walle <michael@walle.cc> wrote:
>
> Am 2020-10-19 16:54, schrieb Wolfgang Denk:
> > Dear Simon,
> >
> > In message <20201019135602.3943835-12-sjg@chromium.org> you wrote:
> >> In some cases it is necessary to pass parameters to Linux so that it
> >> will
> >> boot correctly. For example, the rootdev parameter is often used to
> >> specify the root device. However the root device may change depending
> >> on
> >> whence U-Boot loads the kernel. At present it is necessary to build up
> >> the command line by adding device strings to it one by one.
> >>
> >> It is often more convenient to provide a template for bootargs, with
> >> U-Boot doing the substitution from other environment variables.
> >>
> >> Add a way to substitute strings in the bootargs variable. This allows
> >> things like "rootdev=%U" to be used in bootargs, with the %U
> >> substitution
> >> providing the UUID of the root device.
> >
> > Argh, no, please don't.
> >
> > You add something unconditionally to common code which very few
> > people need.  U-Boot size is growing all the time because of such
> > ... features.  This may be acceptable on the systems you have in
> > mind, but I consider this selfish.
> >
> > Why do we have to add yet another non-standard way of substituting
> > variables in a string?  Can we not use alreay existing methonds
> > instead?
> >
> > Why do you have to use "%U" in your template instead of for example
> > "${uuid}" ?
>
> I'd second that. Having variables evaluated inside the bootargs would
> be very valuable IMHO.
>
> At the moment we have some cumbersome constructs like
>    set_bootargs="setenv bootargs bla ${var}"

Yes it is a real pain. The substitution happens on first parse two, so
you have to put these commands in separate variables if you are
building things up.

Another thing to mention is that years ago I sent a series to help
with that last bit, i.e. make variables evaluate recursively. I'm
trying to remember what happened to it.

Regards,
Simon
Wolfgang Denk Oct. 20, 2020, 1:17 p.m. UTC | #7
Dear Simon,

In message <CAPnjgZ3sg693VXsEpvjnvkuUzfF612cxWvpWuvx_XiVNgQH4Jg@mail.gmail.com> you wrote:
>
> > You add something unconditionally to common code which very few
> > people need.  U-Boot size is growing all the time because of such
> > ... features.  This may be acceptable on the systems you have in
> > mind, but I consider this selfish.
>
> Did you see the Kconfig option?

So you claim the size does not grow with the feature not selected?

> > Why do we have to add yet another non-standard way of substituting
> > variables in a string?  Can we not use alreay existing methonds
> > instead?
>
> What sort of methods?

Variable substitution?

> > Why do you have to use "%U" in your template instead of for example
> > "${uuid}" ?
>
> This is what Chrome OS uses, so it is easier this way, Otherwise I
> have to replace %U with ${uuid} first.

That's what I meant when I wrote "selfish" ;-)

> Which code can I use to parse the ${var} thing?

setenv and run ?

Best regards,

Wolfgang Denk
Wolfgang Denk Oct. 20, 2020, 1:19 p.m. UTC | #8
Dear Simon,

In message <CAPnjgZ3gkCO6_DdQh3XJrsf9SdVa0v3pGuwejGBoDHPEijyRkg@mail.gmail.com> you wrote:
>
> I did wonder about the empty env var thing. IMO it would be nice to
> support empty variables, so we can distinguish between an empty one
> and a missing one.

What exactly is the use case for this that justifies the additional
code needed to implement this feature?

Please keep in mind that this is a boot loader, and we are not
attending a contest for feature-completeness.

[Non-random signature this time.]

Best regards,

Wolfgang Denk
Wolfgang Denk Oct. 20, 2020, 1:26 p.m. UTC | #9
Dear Simon,

In message <CAPnjgZ2rAQZN_bdQ5C-9cB4u8F8PrhZkBWYx0zWxj_yEBrV2qw@mail.gmail.com> you wrote:
>
> > At the moment we have some cumbersome constructs like
> >    set_bootargs="setenv bootargs bla ${var}"
>
> Yes it is a real pain. The substitution happens on first parse two, so
> you have to put these commands in separate variables if you are
> building things up.

Come on, is it really that big a problem?

You define all your needed settings (foo, bar, baz, and maybe uuid),
and then you run a single command

	setenv bootargs "${foo} ${bar} %{baz} ${uuid}"

?

Yes, it takes one additional step, but it's simple and does not need
extra code.

[And if someone bothered to update hush to a recent version, and
while doing so revisited the adaptions needed for U-Boot, we could
also do much better.  IIRC, things like command substitution were
omitted then because of the code size it would have required; given
today's resources this might be an optional feature for many.]

Best regards,

Wolfgang Denk
Simon Glass Oct. 20, 2020, 7:23 p.m. UTC | #10
Hi Wolfgang,

On Tue, 20 Oct 2020 at 07:17, Wolfgang Denk <wd@denx.de> wrote:
>
> Dear Simon,
>
> In message <CAPnjgZ3sg693VXsEpvjnvkuUzfF612cxWvpWuvx_XiVNgQH4Jg@mail.gmail.com> you wrote:
> >
> > > You add something unconditionally to common code which very few
> > > people need.  U-Boot size is growing all the time because of such
> > > ... features.  This may be acceptable on the systems you have in
> > > mind, but I consider this selfish.
> >
> > Did you see the Kconfig option?
>
> So you claim the size does not grow with the feature not selected?

That's what I see, except that there is some size growth due to the
refactoring / adding tests.

>
> > > Why do we have to add yet another non-standard way of substituting
> > > variables in a string?  Can we not use alreay existing methonds
> > > instead?
> >
> > What sort of methods?
>
> Variable substitution?
>
> > > Why do you have to use "%U" in your template instead of for example
> > > "${uuid}" ?
> >
> > This is what Chrome OS uses, so it is easier this way, Otherwise I
> > have to replace %U with ${uuid} first.
>
> That's what I meant when I wrote "selfish" ;-)
>
> > Which code can I use to parse the ${var} thing?
>
> setenv and run ?

Will respond on other emails.

Regards,
SImon
Simon Glass Oct. 20, 2020, 7:23 p.m. UTC | #11
Hi Wolfgang,

On Tue, 20 Oct 2020 at 07:26, Wolfgang Denk <wd@denx.de> wrote:
>
> Dear Simon,
>
> In message <CAPnjgZ2rAQZN_bdQ5C-9cB4u8F8PrhZkBWYx0zWxj_yEBrV2qw@mail.gmail.com> you wrote:
> >
> > > At the moment we have some cumbersome constructs like
> > >    set_bootargs="setenv bootargs bla ${var}"
> >
> > Yes it is a real pain. The substitution happens on first parse two, so
> > you have to put these commands in separate variables if you are
> > building things up.
>
> Come on, is it really that big a problem?
>
> You define all your needed settings (foo, bar, baz, and maybe uuid),
> and then you run a single command
>
>         setenv bootargs "${foo} ${bar} %{baz} ${uuid}"
>
> ?
>
> Yes, it takes one additional step, but it's simple and does not need
> extra code.

It is actually not simple, for three reasons:

1. With zboot the args come from the kernel setup.bin and must be modified
2. With Chrome OS the args come from the kernel partition and must be
augmented / substituted
3. The above command cannot be in the same env var as anything else,
since substitution breaks in that case

So you end up with a really complicated mess of environment variables
and scripts that is barely manageable. I want it to be simple.

See here for example (this only deals with 3 above, not 1 and 2, which
would still need custom code without my series)

https://chromium.googlesource.com/chromiumos/third_party/u-boot/+/refs/heads/chromeos-v2013.06/include/configs/chromeos.h#204

>
> [And if someone bothered to update hush to a recent version, and
> while doing so revisited the adaptions needed for U-Boot, we could
> also do much better.  IIRC, things like command substitution were
> omitted then because of the code size it would have required; given
> today's resources this might be an optional feature for many.]

Yes I agree, so long as the code size increase is not horrific.

+Tom Rini what do you think?

Regards,
Simon
Wolfgang Denk Oct. 21, 2020, 7:16 a.m. UTC | #12
Dear Simon,

In message <CAPnjgZ1SfwRa_LXh0zu3Oug=GLEUQx5MdCCvqW90-FV5hvX9uw@mail.gmail.com> you wrote:
>
> > Yes, it takes one additional step, but it's simple and does not need
> > extra code.
>
> It is actually not simple, for three reasons:
>
> 1. With zboot the args come from the kernel setup.bin and must be modified

Don't use zboot, then.  In my opinion, zboot is crap and should
never have been added, but there were so many requests for it.
Nevertheless, the use of crippleware is a sin that carries with it
its own punishment.

Don't do it, then.

Or, if you feel there is some value in zboot that should be
conserved, fix it to work like the rest of U-Boot.

> 2. With Chrome OS the args come from the kernel partition and must be
> augmented / substituted

OK.  But then why can we not still use the standard variable
substitution mechanism we already have?

> 3. The above command cannot be in the same env var as anything else,
> since substitution breaks in that case

Sorry, I don't understand what you mean heare.  What is "the same
env var" versus "anything else"?  Maybe you can give a specific example?


> So you end up with a really complicated mess of environment variables
> and scripts that is barely manageable. I want it to be simple.

Again, I can't follow you.  Why must there be a mess?

> See here for example (this only deals with 3 above, not 1 and 2, which
> would still need custom code without my series)
>
> https://chromium.googlesource.com/chromiumos/third_party/u-boot/+/refs/heads/chromeos-v2013.06/include/configs/chromeos.h#204

Sorry - all I see there is some complex settings if these make sense
I can't tell) - but how would things be better if you could - for
example - use "%U" instead of "${uuid}" as you suggested?

Also, is your approach not necessarily limited? You can now think of
a handful of variables you may want to pass, say root device, root
partition, uuid, maybe MAC address etc.  But the next user will need
kernel args that you did not think of - so how do we proceed?  Add
all features anybody needs to that new code?  That certainly does
not scale.  Or mix "%FOO" and "${foo}" style arguments?  That's even
worse.  I really fail to see the benefits, I see only ugliness and
problems.


Best regards,

Wolfgang Denk
Simon Glass Oct. 21, 2020, 3:51 p.m. UTC | #13
Hi Wolfgang,

On Wed, 21 Oct 2020 at 01:16, Wolfgang Denk <wd@denx.de> wrote:
>
> Dear Simon,
>
> In message <CAPnjgZ1SfwRa_LXh0zu3Oug=GLEUQx5MdCCvqW90-FV5hvX9uw@mail.gmail.com> you wrote:
> >
> > > Yes, it takes one additional step, but it's simple and does not need
> > > extra code.
> >
> > It is actually not simple, for three reasons:
> >
> > 1. With zboot the args come from the kernel setup.bin and must be modified
>
> Don't use zboot, then.  In my opinion, zboot is crap and should
> never have been added, but there were so many requests for it.
> Nevertheless, the use of crippleware is a sin that carries with it
> its own punishment.
>
> Don't do it, then.
>
> Or, if you feel there is some value in zboot that should be
> conserved, fix it to work like the rest of U-Boot.

Well my series does that to a large extent. It is much more like bootm
now, in that it is properly split into stages. I think it would be
possible to combine parts of it into bootm as a future step, although
it is non-trivial, and I think we should build out more tests first.

But zboot does make use of an existing command line and does all the
weird x86 processing, so I don't know how we could get rid of it.

>
> > 2. With Chrome OS the args come from the kernel partition and must be
> > augmented / substituted
>
> OK.  But then why can we not still use the standard variable
> substitution mechanism we already have?

I don't think the actual mechanism is a big deal. We could do a string
replace (does U-Boot support that?) of %U with ${uuid} for example, to
make it work for my case.

Or we could go with an idea I previously rejected, to just provide a
simple string replace feature, with any special characters in the
search string. For example:

setenv bootargs_repl "%U=${uuid} %P=${partid}"

>
> > 3. The above command cannot be in the same env var as anything else,
> > since substitution breaks in that case
>
> Sorry, I don't understand what you mean heare.  What is "the same
> env var" versus "anything else"?  Maybe you can give a specific example?

Did you see the 'run regen_scripts' script?

At present I can do

setenv uuid blah; setenv partid blah; bootm

but with your scheme I need the 'run regen_script' to set the
variables correctly, which is a real pain as my example shows.

>
>
> > So you end up with a really complicated mess of environment variables
> > and scripts that is barely manageable. I want it to be simple.
>
> Again, I can't follow you.  Why must there be a mess?
>
> > See here for example (this only deals with 3 above, not 1 and 2, which
> > would still need custom code without my series)
> >
> > https://chromium.googlesource.com/chromiumos/third_party/u-boot/+/refs/heads/chromeos-v2013.06/include/configs/chromeos.h#204
>
> Sorry - all I see there is some complex settings if these make sense
> I can't tell) - but how would things be better if you could - for
> example - use "%U" instead of "${uuid}" as you suggested?

My point here is not about %U, it is about the pain of multiple stages
to get the variables right. WIth bootargs substitution we can just set
the variables and boot.

>
> Also, is your approach not necessarily limited? You can now think of
> a handful of variables you may want to pass, say root device, root
> partition, uuid, maybe MAC address etc.  But the next user will need
> kernel args that you did not think of - so how do we proceed?  Add
> all features anybody needs to that new code?  That certainly does
> not scale.  Or mix "%FOO" and "${foo}" style arguments?  That's even
> worse.  I really fail to see the benefits, I see only ugliness and
> problems.

These scripts are board-specific, so each board can do what it likes
here. But the nice thing is not having to manually build up the
bootargs.

Can we step past the %U business? I think we can use the ${var} stuff
with some substitution.

Regards,
Simon
Wolfgang Denk Oct. 22, 2020, 12:32 p.m. UTC | #14
Dear Simon,

In message <CAPnjgZ2Vn3oWAHy71smw-+fRo28HUA0K32DP6FPoGtjtK3XoFQ@mail.gmail.com> you wrote:
>
> Well my series does that to a large extent. It is much more like bootm
> now, in that it is properly split into stages. I think it would be
> possible to combine parts of it into bootm as a future step, although
> it is non-trivial, and I think we should build out more tests first.

Hm... I fear that this is rather cementing the zboot support so we
never get rid of it...
>
> But zboot does make use of an existing command line and does all the
> weird x86 processing, so I don't know how we could get rid of it.

How about a command that takes this existing command line and
"imports" it into a standard bootargs variable?

> I don't think the actual mechanism is a big deal. We could do a string
> replace (does U-Boot support that?) of %U with ${uuid} for example, to
> make it work for my case.

Yes, we can.

=> env print commandline bootargs
## Error: "commandline" not defined
## Error: "bootargs" not defined
=> env set commandline 'This is a %A test for %U replacement.'
=> setexpr bootargs gsub %A boring "${commandline}"
bootargs=This is a boring test for %U replacement.
=> setexpr bootargs gsub %U '${uuid}'
bootargs=This is a boring test for ${uuid} replacement.
=> env print bootargs
bootargs=This is a boring test for ${uuid} replacement.

> Or we could go with an idea I previously rejected, to just provide a
> simple string replace feature, with any special characters in the
> search string. For example:

But we have all this already, even with regular expressions and
(simple) backreferences.


> > Sorry, I don't understand what you mean heare.  What is "the same
> > env var" versus "anything else"?  Maybe you can give a specific example?
>
> Did you see the 'run regen_scripts' script?
>
> At present I can do
>
> setenv uuid blah; setenv partid blah; bootm
>
> but with your scheme I need the 'run regen_script' to set the
> variables correctly, which is a real pain as my example shows.

Just insert a "run" there, and you are done with it.

> My point here is not about %U, it is about the pain of multiple stages
> to get the variables right. WIth bootargs substitution we can just set
> the variables and boot.

I think this is not so much a bootargs question (or at least it
should not be it).  bootargs is just another environment variable
without any specific properties except that it's content is being
passed to the kerenl, so you want to have the needed variable
substituion doen before that.

It was an intentional decison not to do this automagically, based on
the thinking that accorsing to UNNIX philosophy it is not each
individual command which implements things like wildcard or variable
substitution, but rather this is something the shell does for you,
and the commands are dumb in this respect.  This works reasonably
well, except that we don't pass bootargs as argument to the bootm
command - rather it is done internally by default, this lacking the
variable substituion.

You may call this a design bug, and I will accept the blame for it.

To me the obvious fix would be to correct this behavious such that
we extend bootm to accept the bootargs argument as a command line
parameter, thus enabling variable substitution there, too.

As far as I'm concenred, I don't think this is necessary as all it
takes to work around this is a single call of a "run" command which
can do exactly that, too.

But I agree, this is just a workaround, and it would be more
consistent to pass bootargs as argument directly.


And this is also the direction I see how the zboot stuff should be
fixed:  import the Linux command line into the environment, modify
it as needed (if necessary using regular expression matching /
string substituion to fix it up to use common U-Boot variable
names), and then pass it like the rest of the world to the kernel.

> Can we step past the %U business? I think we can use the ${var} stuff
> with some substitution.

Don't we have that already?  What exactly is it you miss?

Best regards,

Wolfgang Denk
Simon Glass Nov. 3, 2020, 3:11 p.m. UTC | #15
Hi Wolfgang,

On Thu, 22 Oct 2020 at 06:32, Wolfgang Denk <wd@denx.de> wrote:
>
> Dear Simon,
>
> In message <CAPnjgZ2Vn3oWAHy71smw-+fRo28HUA0K32DP6FPoGtjtK3XoFQ@mail.gmail.com> you wrote:
> >
> > Well my series does that to a large extent. It is much more like bootm
> > now, in that it is properly split into stages. I think it would be
> > possible to combine parts of it into bootm as a future step, although
> > it is non-trivial, and I think we should build out more tests first.
>
> Hm... I fear that this is rather cementing the zboot support so we
> never get rid of it...
> >
> > But zboot does make use of an existing command line and does all the
> > weird x86 processing, so I don't know how we could get rid of it.
>
> How about a command that takes this existing command line and
> "imports" it into a standard bootargs variable?
>
> > I don't think the actual mechanism is a big deal. We could do a string
> > replace (does U-Boot support that?) of %U with ${uuid} for example, to
> > make it work for my case.
>
> Yes, we can.
>
> => env print commandline bootargs
> ## Error: "commandline" not defined
> ## Error: "bootargs" not defined
> => env set commandline 'This is a %A test for %U replacement.'
> => setexpr bootargs gsub %A boring "${commandline}"
> bootargs=This is a boring test for %U replacement.
> => setexpr bootargs gsub %U '${uuid}'
> bootargs=This is a boring test for ${uuid} replacement.
> => env print bootargs
> bootargs=This is a boring test for ${uuid} replacement.
>
> > Or we could go with an idea I previously rejected, to just provide a
> > simple string replace feature, with any special characters in the
> > search string. For example:
>
> But we have all this already, even with regular expressions and
> (simple) backreferences.
>
>
> > > Sorry, I don't understand what you mean heare.  What is "the same
> > > env var" versus "anything else"?  Maybe you can give a specific example?
> >
> > Did you see the 'run regen_scripts' script?
> >
> > At present I can do
> >
> > setenv uuid blah; setenv partid blah; bootm
> >
> > but with your scheme I need the 'run regen_script' to set the
> > variables correctly, which is a real pain as my example shows.
>
> Just insert a "run" there, and you are done with it.
>
> > My point here is not about %U, it is about the pain of multiple stages
> > to get the variables right. WIth bootargs substitution we can just set
> > the variables and boot.
>
> I think this is not so much a bootargs question (or at least it
> should not be it).  bootargs is just another environment variable
> without any specific properties except that it's content is being
> passed to the kerenl, so you want to have the needed variable
> substituion doen before that.
>
> It was an intentional decison not to do this automagically, based on
> the thinking that accorsing to UNNIX philosophy it is not each
> individual command which implements things like wildcard or variable
> substitution, but rather this is something the shell does for you,
> and the commands are dumb in this respect.  This works reasonably
> well, except that we don't pass bootargs as argument to the bootm
> command - rather it is done internally by default, this lacking the
> variable substituion.
>
> You may call this a design bug, and I will accept the blame for it.
>
> To me the obvious fix would be to correct this behavious such that
> we extend bootm to accept the bootargs argument as a command line
> parameter, thus enabling variable substitution there, too.
>
> As far as I'm concenred, I don't think this is necessary as all it
> takes to work around this is a single call of a "run" command which
> can do exactly that, too.
>
> But I agree, this is just a workaround, and it would be more
> consistent to pass bootargs as argument directly.
>
>
> And this is also the direction I see how the zboot stuff should be
> fixed:  import the Linux command line into the environment, modify
> it as needed (if necessary using regular expression matching /
> string substituion to fix it up to use common U-Boot variable
> names), and then pass it like the rest of the world to the kernel.
>
> > Can we step past the %U business? I think we can use the ${var} stuff
> > with some substitution.
>
> Don't we have that already?  What exactly is it you miss?

I have sent a new version of this series that uses ${var}.

Regards,
Simon
diff mbox series

Patch

diff --git a/README b/README
index 91c5a1a8fa3..263e31ab7f6 100644
--- a/README
+++ b/README
@@ -3229,6 +3229,22 @@  List of environment variables (most likely not complete):
 
   bootargs	- Boot arguments when booting an RTOS image
 
+  bootargs_subst - Substitution parameters for bootargs. These are applied after
+		  the commandline has been built. The format is:
+
+		    <key>=<value>[!<key>=<value>...]
+
+		  where
+		    <key> is a string to replace
+		    <value> is the value to replace it with (either a simple
+		       string or an environment variable starting with $
+
+		  One use for this is to insert the root-disk UUID into the
+		  command line where bootargs contains "root=%U"
+
+		    part uuid mmc 2:2 uuid
+		    setenv cmdline_repl %U=$uuid
+
   bootfile	- Name of the image to load with TFTP
 
   bootm_low	- Memory range available for image processing in the bootm
diff --git a/arch/Kconfig b/arch/Kconfig
index 6caf2338bcf..421ea9a9b51 100644
--- a/arch/Kconfig
+++ b/arch/Kconfig
@@ -141,6 +141,7 @@  config SANDBOX
 	imply CMD_PMC
 	imply CMD_CLONE
 	imply SILENT_CONSOLE
+	imply BOOTARGS_SUBST
 
 config SH
 	bool "SuperH architecture"
diff --git a/common/Kconfig.boot b/common/Kconfig.boot
index 4191bfb34df..c5668cf2931 100644
--- a/common/Kconfig.boot
+++ b/common/Kconfig.boot
@@ -848,6 +848,26 @@  config BOOTARGS
 	  CONFIG_BOOTARGS goes into the environment value "bootargs". Note that
 	  this value will also override the "chosen" node in FDT blob.
 
+config BOOTARGS_SUBST
+	bool "Support substituting strings in boot arguments"
+	help
+	  This allows substituting string values in the boot arguments. These
+	  are applied after the commandline has been built. Set the
+	  'bootargs_subst' envvar to control this. The format is:
+
+		<key>=<value>[!<key>=<value>...]
+
+		  where
+		    <key> is a string to replace
+		    <value> is the value to replace it with (either a simple
+		       string or an environment variable starting with $
+
+		  One use for this is to insert the root-disk UUID into the
+		  command line where bootargs contains "root=%U"
+
+		    part uuid mmc 2:2 uuid
+		    setenv cmdline_repl %U=$uuid
+
 config USE_BOOTCOMMAND
 	bool "Enable a default value for bootcmd"
 	help
diff --git a/common/bootm.c b/common/bootm.c
index 5484c2be900..0a89a72a660 100644
--- a/common/bootm.c
+++ b/common/bootm.c
@@ -540,6 +540,68 @@  static int fixup_silent_linux(char *buf, int maxlen)
 	return 0;
 }
 
+/**
+ * process_subst() - Handle substitution of %x fields in the environment
+ *
+ * For every "%x" found, it is replaced with the value of envvar "bootargs_x"
+ * where x is a case-sensitive alphanumeric character. If that environment
+ * variable does not exist, no substitution is made and the %x remains, to
+ * allow for plain % characters in the string.
+ *
+ * @buf: Buffer containing the string to process
+ * @maxlen: Maximum length of buffer
+ * @return 0 if OK, -ENOSPC if @maxlen is too small
+ */
+static int process_subst(char *buf, int maxlen)
+{
+	const char *in;
+	char *cmdline, *out;
+	bool percent;
+	int size;
+
+	/* Move to end of buffer */
+	size = strlen(buf) + 1;
+	cmdline = buf + maxlen - size;
+	if (buf + size > cmdline)
+		return -ENOSPC;
+	memmove(cmdline, buf, size);
+
+	/* Replace %c with value of envvar 'bootargs_%c' */
+	percent = false;
+	for (in = cmdline, out = buf; *in; in++) {
+		/* If we catch up with the input string, we're out of space */
+		if (out >= in)
+			return -ENOSPC;
+		if (percent) {
+			char var[12];
+			const char *val;
+
+			snprintf(var, sizeof(var), "bootargs_%c", *in);
+			val = env_get(var);
+			if (val) {
+				for (; *val; val++) {
+					if (out >= in)
+						return -ENOSPC;
+					*out++ = *val;
+				}
+				percent = false;
+				continue;
+			} else {
+				/* No value, put the % back */
+				*out++ = '%';
+			}
+		} else if (*in == '%') {
+			percent = true;
+			continue;
+		}
+		*out++ = *in;
+		percent = false;
+	}
+	*out++ = '\0';
+
+	return 0;
+}
+
 int bootm_process_cmdline(char *buf, int maxlen, int flags)
 {
 	int ret;
@@ -552,6 +614,11 @@  int bootm_process_cmdline(char *buf, int maxlen, int flags)
 		if (ret)
 			return log_msg_ret("silent", ret);
 	}
+	if (IS_ENABLED(CONFIG_BOOTARGS_SUBST) && (flags & BOOTM_CL_SUBST)) {
+		ret = process_subst(buf, maxlen);
+		if (ret)
+			return log_msg_ret("silent", ret);
+	}
 
 	return 0;
 }
@@ -567,7 +634,7 @@  int bootm_process_cmdline_env(int flags)
 	/* First check if any action is needed */
 	do_silent = IS_ENABLED(CONFIG_SILENT_CONSOLE) &&
 	    !IS_ENABLED(CONFIG_SILENT_U_BOOT_ONLY) && (flags & BOOTM_CL_SILENT);
-	if (!do_silent)
+	if (!do_silent && !IS_ENABLED(CONFIG_BOOTARGS_SUBST))
 		return 0;
 
 	env = env_get("bootargs");
@@ -700,8 +767,7 @@  int do_bootm_states(struct cmd_tbl *cmdtp, int flag, int argc,
 	if (!ret && (states & BOOTM_STATE_OS_BD_T))
 		ret = boot_fn(BOOTM_STATE_OS_BD_T, argc, argv, images);
 	if (!ret && (states & BOOTM_STATE_OS_PREP)) {
-		ret = bootm_process_cmdline_env(images->os.os == IH_OS_LINUX ?
-						BOOTM_CL_SILENT : 0);
+		ret = bootm_process_cmdline_env(images->os.os == IH_OS_LINUX);
 		if (ret) {
 			printf("Cmdline setup failed (err=%d)\n", ret);
 			ret = CMD_RET_FAILURE;
diff --git a/include/bootm.h b/include/bootm.h
index 8d95fb2a90a..7f88ec718b8 100644
--- a/include/bootm.h
+++ b/include/bootm.h
@@ -78,8 +78,9 @@  void switch_to_non_secure_mode(void);
 /* Flags to control bootm_process_cmdline() */
 enum bootm_cmdline_t {
 	BOOTM_CL_SILENT	= 1 << 0,	/* Do silent console processing */
+	BOOTM_CL_SUBST	= 1 << 1,	/* Do substitution */
 
-	BOOTM_CL_ALL	= 1,		/* All substitutions */
+	BOOTM_CL_ALL	= 3,		/* All substitutions */
 };
 
 /**
@@ -95,7 +96,10 @@  void board_preboot_os(void);
 /*
  * bootm_process_cmdline() - Process fix-ups for the command line
  *
- * This handles: making Linux boot silently if requested ('silent_linux' envvar)
+ * This handles:
+ *
+ *  - making Linux boot silently if requested ('silent_linux' envvar)
+ *  - performing substitutions in the command line ('bootargs_subst' envvar)
  *
  * @maxlen must provide enough space for the string being processed plus the
  * resulting string
@@ -111,8 +115,10 @@  int bootm_process_cmdline(char *buf, int maxlen, int flags);
 /**
  * bootm_process_cmdline_env() - Process fix-ups for the command line
  *
- * Updates the 'bootargs' envvar as required. This handles making Linux boot
- * silently if requested ('silent_linux' envvar)
+ * Updates the 'bootargs' envvar as required. This handles:
+ *
+ *  - making Linux boot silently if requested ('silent_linux' envvar)
+ *  - performing substitutions in the command line ('bootargs_subst' envvar)
  *
  * @flags: Flags to control what happens (see bootm_cmdline_t)
  * @return 0 if OK, -ENOMEM if out of memory
diff --git a/test/bootm.c b/test/bootm.c
index d0b29441d65..8a83be9719f 100644
--- a/test/bootm.c
+++ b/test/bootm.c
@@ -116,22 +116,122 @@  static int bootm_test_silent(struct unit_test_state *uts)
 }
 BOOTM_TEST(bootm_test_silent, 0);
 
+/* Test substitution processing */
+static int bootm_test_subst(struct unit_test_state *uts)
+{
+	char buf[BUF_SIZE];
+
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("some%Athing", buf);
+
+	/* Replace with shorter string */
+	ut_assertok(env_set("bootargs_A", "a"));
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someathing", buf);
+
+	/* Replace with same-length string */
+	ut_assertok(env_set("bootargs_A", "ab"));
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabthing", buf);
+
+	/* Replace with longer string */
+	ut_assertok(env_set("bootargs_A", "abc"));
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthing", buf);
+
+	/* Check it is case sensitive */
+	strcpy(buf, "some%athing");
+	ut_assertok(bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("some%athing", buf);
+
+	/* Check too long - need 12 bytes for each string */
+	strcpy(buf, "some%Athing");
+	ut_asserteq(-ENOSPC,
+		    bootm_process_cmdline(buf, 12 * 2 - 1, BOOTM_CL_SUBST));
+
+	/* Check just enough space */
+	strcpy(buf, "some%Athing");
+	ut_assertok(bootm_process_cmdline(buf, 12 * 2, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthing", buf);
+
+	/*
+	 * Check the substition string being too long. This results in a string
+	 * of 12 (13 bytes). Allow one more byte margin.
+	 */
+	ut_assertok(env_set("bootargs_A", "1234567890"));
+	strcpy(buf, "a%Ac");
+	ut_asserteq(-ENOSPC,
+		    bootm_process_cmdline(buf, 13, BOOTM_CL_SUBST));
+
+	strcpy(buf, "a%Ac");
+	ut_asserteq(0, bootm_process_cmdline(buf, 14, BOOTM_CL_SUBST));
+
+	/* Check multiple substitutions */
+	ut_assertok(env_set("bootargs_A", "abc"));
+	strcpy(buf, "some%Athing%Belse");
+	ut_asserteq(0, bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthing%Belse", buf);
+
+	/* Check multiple substitutions */
+	ut_assertok(env_set("bootargs_B", "123"));
+	strcpy(buf, "some%Athing%Belse");
+	ut_asserteq(0, bootm_process_cmdline(buf, BUF_SIZE, BOOTM_CL_SUBST));
+	ut_asserteq_str("someabcthing123else", buf);
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_subst, 0);
+
 /* Test silent processing in the bootargs variable */
 static int bootm_test_silent_var(struct unit_test_state *uts)
 {
 	env_set("bootargs", NULL);
-	env_set("silent_linux", NULL);
-	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SILENT));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SUBST));
 	ut_assertnull(env_get("bootargs"));
 
-	env_set("bootargs", CONSOLE_STR);
-	env_set("silent_linux", "yes");
+	ut_assertok(env_set("bootargs", "some%Athing"));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SUBST));
+	ut_asserteq_str("some%Athing", env_get("bootargs"));
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_silent_var, 0);
+
+/* Test substitution processing in the bootargs variable */
+static int bootm_test_subst_var(struct unit_test_state *uts)
+{
+	env_set("bootargs", NULL);
 	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SILENT));
 	ut_asserteq_str("console=", env_get("bootargs"));
 
+	ut_assertok(env_set("bootargs", "some%Athing"));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_SILENT));
+	ut_asserteq_str("some%Athing console=", env_get("bootargs"));
+
 	return 0;
 }
-BOOTM_TEST(bootm_test_silent_var, 0);
+BOOTM_TEST(bootm_test_subst_var, 0);
+
+/* Test substitution and silent console processing in the bootargs variable */
+static int bootm_test_subst_both(struct unit_test_state *uts)
+{
+	ut_assertok(env_set("silent_linux", "yes"));
+	env_set("bootargs", NULL);
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_ALL));
+	ut_asserteq_str("console=", env_get("bootargs"));
+
+	ut_assertok(env_set("bootargs", "some%Athing " CONSOLE_STR));
+	ut_assertok(env_set("bootargs_A", "1234567890"));
+	ut_assertok(bootm_process_cmdline_env(BOOTM_CL_ALL));
+	ut_asserteq_str("some1234567890thing console=", env_get("bootargs"));
+
+	return 0;
+}
+BOOTM_TEST(bootm_test_subst_both, 0);
 
 int do_ut_bootm(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[])
 {