Skip to content

Commit

Permalink
Add firmware command
Browse files Browse the repository at this point in the history
  • Loading branch information
sylane committed Aug 30, 2024
1 parent 5ee260a commit 65ef411
Show file tree
Hide file tree
Showing 7 changed files with 758 additions and 85 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- New `-t/--tar` option to the deploy command to save a grisp release tarball in
the `_grisp/deploy` directory.
- New firmware command to generate GRiSP 2 binary firmwares: [#83](https://github.com/grisp/rebar3_grisp/pull/83)

### Changed

Expand Down
170 changes: 170 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,176 @@ all the files will be bundled in a a tarball under `_grisp/deploy`:
rebar3 grisp deploy --tar
```


### Generate GRiSP 2 Firmwares

The `firmware` command generates binary files that can be written on GRiSP 2
eMMC. There is three types of firmware that can be generated:

- **System Firmware**:
The system firmware is the content of a system partition on the eMMC.
When using A/B software update, the system firmware can be written either
on the first or the second system partition. By default, the command will
generate a system firmware under `_grisp/firmware` but it can be disabled with
the option `-b false` or `--system false`.
- **eMMC Image Firmware**:
The eMMC image firmware is a full image containing the bootloader, the
partition table and the system partitions. It is meant to be written on the
GRiSP 2 board to reset it completely with the new software. If the image is
truncated (it is by default), the image only contains the first system
partition. It means that when writing the firmware to the eMMC, the second
system partition will be untouched. To generate an eMMC image firmware under
`_grisp/firmware`, add the option `-i` or `--image`, to disable truncating
so the image contains both system partitions, uses the option `-t false` or
`--truncate false`.
- **Bootloader Firmware**:
The bootloader firmware contains only the bootloader and the partition table.
To generate it under `_grisp/firmware`, add the option `-b` or `--bootloader`.

e.g.

Generate a system firmware for the default release:

rebar3 grisp firmware

Generate a system firmware for a specific release:

rebar3 grisp firmware --relname myapp --relvsn 1.2.3

Generate all firmwares, forcing existing files to be overwritten and forcing the
generation of the software bundle even if one already exists in `_grisp/deploy`:

rebar3 grisp firmware --bootloader --image --force --force-bundle


#### Firmware Update

Description of the variables in the commands that will follow:
- **`${RELNAME}`**: The relx release names used when generating the firmware.
- **`${RELVSN}`**: The relx release version used when generating the firmware.
- **`${USER}`**: The username of the account running the command.
- **`${GRISP_BOARD_SERIAL}`**: The serial number of the GRiSP 2 board.

To write a system firmware to a GRiSP 2 board:

- Copy the firmware to the SD card:

**`macOS`** `$ cp _grisp/firmware/grisp2.${RELNAME}.${RELVSN}.sys.gz /Volumes/GRISP`
**`Linux`** `$ cp _grisp/firmware/grisp2.${RELNAME}.${RELVSN}.sys.gz /media/${USER}/GRISP`

- Unmount the SD card:

**`macOS`** `$ diskutil umount /Volumes/GRISP`
**`Linux`** `$ umount /media/${USER}/GRISP`

- Open a serial console to the GRiSP board:

**`macOS`** `$ screen /dev/tty.usbserial-0${GRISP_BOARD_SERIAL}1 115200`
**`Linux`** `$ screen /dev/ttyUSB1 115200`

- Insert the SD card in the GRiSP 2 board.
- Reset the board using the onboard reset button.
- Enter into barebox console mode by pressing any key before 3 seconds.
- Consult the current active system partition:

**`Barebox`** `$ echo $state.bootstate.active_system`

- Write the firmware. If the current active system is `0`, use device
`/dev/mmc1.0`, if it is `1` use device `/dev/mmc1.1`:

**`Barebox`** `$ uncompress /mnt/mmc/grisp2.${RELNAME}.${RELVSN}.sys.gz /dev/mmc1.0`

- Remove the SD card.
- Reset the GRiSP board again.

To reset a GRiSP 2 board eMMC, either with a truncated or full image firmware:

- Copy the firmware to the SD card:

**`macOS`** `$ cp _grisp/firmware/grisp2.${RELNAME}.${RELVSN}.emmc.gz /Volumes/GRISP`
**`Linux`** `$ cp _grisp/firmware/grisp2.${RELNAME}.${RELVSN}.emmc.gz /media/${USER}/GRISP`

- Unmount the SD card:

**`macOS`** `$ diskutil umount /Volumes/GRISP`
**`Linux`** `$ umount /media/${USER}/GRISP`

- Open a serial console to the GRiSP board:

**`macOS`** `$ screen /dev/tty.usbserial-0${GRISP_BOARD_SERIAL}1 115200`
**`Linux`** `$ screen /dev/ttyUSB1 115200`

- Insert the SD card in the GRiSP 2 board.
- Reset the board using the onboard reset button.
- Enter into barebox console mode by pressing any key before 3 seconds.
- Set the current active system partition to the first one:

**`Barebox`** `$ let state.bootstate.active_system=0`
**`Barebox`** `$ state -s`

- Write the firmware:

**`Barebox`** `$ uncompress /mnt/mmc/grisp2.${RELNAME}.${RELVSN}.emmc.gz /dev/mmc1`

- Remove the SD card.
- Reset the GRiSP board again.

To reset only the bootloader of the board:

- Copy the firmware to the SD card:

**`macOS`** `$ cp _grisp/firmware/grisp2.${RELNAME}.${RELVSN}.boot.gz /Volumes/GRISP`
**`Linux`** `$ cp _grisp/firmware/grisp2.${RELNAME}.${RELVSN}.boot.gz /media/${USER}/GRISP`

- Unmount the SD card:

**`macOS`** `$ diskutil umount /Volumes/GRISP`
**`Linux`** `$ umount /media/${USER}/GRISP`

- Open a serial console to the GRiSP board:

**`macOS`** `$ screen /dev/tty.usbserial-0${GRISP_BOARD_SERIAL}1 115200`
**`Linux`** `$ screen /dev/ttyUSB1 115200`

- Insert the SD card in the GRiSP 2 board.
- Reset the board using the onboard reset button.
- Enter into barebox console mode by pressing any key before 3 seconds.
- Write the firmware:

**`Barebox`** `$ uncompress /mnt/mmc/grisp2.${RELNAME}.${RELVSN}.boot.gz /dev/mmc1`

- Remove the SD card.
- Reset the GRiSP board again.


#### Cautions

##### With truncated image firmwares

When writing a truncated eMMC image firmware, only the first system partition is
written. If the active system is the second one, the board will continue to boot
the old software. You will need to manually change the active system partition
in the bootloader console and restart the board.

To consule the current active system partition in the bootloader console:

$ echo $state.bootstate.active_system

To change the current active system partition to the first one:

$ let state.bootstate.active_system=0
$ state -s


##### With writing system firmware on inactive system partition

When writing a system firmware, be sure to do it on the active system
partition or the board will continue to boot the old software.
The device for the first system is `/dev/mmc1.0` and the one for the second
system is `/dev/mmc1.1`. See [the caution about truncated images firmware](#with-truncated-image-firmwares)
for details on how to consult and change the current active system partition.


### Configuration

`rebar.config`:
Expand Down
3 changes: 2 additions & 1 deletion src/rebar3_grisp.erl
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,6 @@ init(State) ->
rebar3_grisp_configure,
rebar3_grisp_package,
rebar3_grisp_version,
rebar3_grisp_report
rebar3_grisp_report,
rebar3_grisp_firmware
]).
30 changes: 9 additions & 21 deletions src/rebar3_grisp_build.erl
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ do(RState) ->
Apps = rebar3_grisp_util:apps(RState),

ProjectRoot = rebar_dir:root_dir(RState),
ToolchainRoot = toolchain_root(Config),
ToolchainRoot = toolchain_root(RState),

Flags = maps:from_list([
{F, rebar3_grisp_util:get(F, Opts, D)}
Expand Down Expand Up @@ -113,28 +113,16 @@ format_error(Reason) ->

%--- Internal ------------------------------------------------------------------

toolchain_root(Config) ->
DockerImg = rebar3_grisp_util:get([build, toolchain, docker], Config, error),
TCDir = rebar3_grisp_util:get([build, toolchain, directory], Config, error),
case os:getenv("GRISP_TOOLCHAIN", TCDir) of
error -> case DockerImg of
error -> abort_no_toolchain();
DockerImage ->
ensure_docker(),
{docker, DockerImage}
end;
Directory ->
{directory, Directory}
end.

ensure_docker() ->
case rebar3_grisp_util:sh("docker info",[return_on_error]) of
{error, _} -> abort("Docker is not available");
{ok, _} -> ok
toolchain_root(RebarState) ->
case rebar3_grisp_util:toolchain_root(RebarState) of
undefined -> abort_no_toolchain();
{docker, _DockerImage} = Result -> Result;
{directory, _Directory} = Result -> Result;
{error, docker_not_found} -> abort("Docker is not available")
end.

abort_no_toolchain() ->
"Please specify the full path to the toolchain directory in your rebar.conf:
abort("Please specify the full path to the toolchain directory in your rebar.conf:
{grisp, [
...,
Expand All @@ -147,7 +135,7 @@ abort_no_toolchain() ->
]}
]}.
Alternatively, you can set the GRISP_TOOLCHAIN environment variable.".
Alternatively, you can set the GRISP_TOOLCHAIN environment variable.").

abort_no_build() ->
abort("There was no build section found in your rebar.conf").
Expand Down
79 changes: 16 additions & 63 deletions src/rebar3_grisp_deploy.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
abort/2
]).

-define(MAX_DDOT, 2).

%--- Callbacks -----------------------------------------------------------------

-spec init(rebar_state:t()) -> {ok, rebar_state:t()}.
Expand Down Expand Up @@ -60,14 +62,17 @@ do(RState) ->
{Args, _} = rebar_state:command_parsed_args(RState),
Force = proplists:get_value(force, Args, false),
Tar = proplists:get_value(tar, Args, false),
RelNameArg = proplists:get_value(relname, Args),
RelVsnArg = proplists:get_value(relvsn, Args),

ProjectRoot = rebar_dir:root_dir(RState),
Apps = rebar3_grisp_util:apps(RState),

CustomBuild = rebar3_grisp_util:should_build(Config),

try
{RelName, RelVsn} = select_release(Args, RState),
{RelName, RelVsn}
= rebar3_grisp_util:select_release(RState, RelNameArg, RelVsnArg),
DistSpec = case {Tar, CopyDest} of
{false, D} when D =:= undefined; D =:= "" ->
error(no_deploy_destination);
Expand Down Expand Up @@ -125,8 +130,8 @@ do(RState) ->
"Multiple releases defined!~n"
"You must specify a name and optionally a version. Examples:~n"
"~n"
" rebar3 grisp release --relname ~p~n"
" rebar3 grisp release --relname ~p --relvsn ~s~n",
" rebar3 grisp deploy --relname ~p~n"
" rebar3 grisp deploy --relname ~p --relvsn ~s~n",
[Name, Name, Version]
);
error:no_release_configured ->
Expand Down Expand Up @@ -235,12 +240,7 @@ copy_dist_spec(RState, CopyDest, Force) ->
}}.

bundle_dist_spec(RState, RelName, RelVsn, Force) ->
Config = rebar3_grisp_util:config(RState),
Board = rebar3_grisp_util:platform(Config),
BundleDir = rebar3_grisp_util:deploy_dir(RState),
BundleName = iolist_to_binary(io_lib:format("~s.~s.~s.tar.gz",
[Board, RelName, RelVsn])),
BundleFile = filename:join(BundleDir, BundleName),
BundleFile = rebar3_grisp_util:bundle_file_path(RState, RelName, RelVsn),
{bundle, #{
type => archive,
force => Force,
Expand Down Expand Up @@ -300,25 +300,25 @@ event([deploy, distribute, bundle, files, {init, _Dest}]) ->
event([deploy, distribute, _Name, files, {_, #{app := App, target := File}}]) ->
io:format(" [~p] ~s~n", [App, File]);
event([deploy, distribute, bundle, archive, {closed, Path}]) ->
RelPath = grisp_tools_util:make_relative(Path),
RelPath = grisp_tools_util:maybe_relative(Path, ?MAX_DDOT),
console("* GRiSP deploy bundle archived in ~s", [RelPath]);
event([deploy, distribute, _Name, {error, dir_missing, Path}]) ->
RelPath = grisp_tools_util:make_relative(Path),
RelPath = grisp_tools_util:maybe_relative(Path, ?MAX_DDOT),
abort("Missing directory: ~s", [RelPath]);
event([deploy, distribute, _Name, {error, dir_access, Path}]) ->
RelPath = grisp_tools_util:make_relative(Path),
RelPath = grisp_tools_util:maybe_relative(Path, ?MAX_DDOT),
abort("Directory not accessible: ~s", [RelPath]);
event([deploy, distribute, _Name, {error, not_a_directory, Path}]) ->
RelPath = grisp_tools_util:make_relative(Path),
RelPath = grisp_tools_util:maybe_relative(Path, ?MAX_DDOT),
abort("Not a proper directory: ~s", [RelPath]);
event([deploy, distribute, _Name, {error, file_access, Path}]) ->
RelPath = grisp_tools_util:make_relative(Path),
RelPath = grisp_tools_util:maybe_relative(Path, ?MAX_DDOT),
abort("File not accessible: ~s", [RelPath]);
event([deploy, distribute, _Name, {error, not_a_file, Path}]) ->
RelPath = grisp_tools_util:make_relative(Path),
RelPath = grisp_tools_util:maybe_relative(Path, ?MAX_DDOT),
abort("Not a proper file: ~s", [RelPath]);
event([deploy, distribute, _Name, {error, file_exists, Path}]) ->
RelPath = grisp_tools_util:make_relative(Path),
RelPath = grisp_tools_util:maybe_relative(Path, ?MAX_DDOT),
abort(
"Destination ~s already exists (use --force to overwrite)",
[RelPath]
Expand Down Expand Up @@ -374,60 +374,13 @@ release_handler(#{name := Name, version := Version, erts := Root}, RState) ->

% Utility functions

select_release(Args, RState) ->
Relx = rebar_state:get(RState, relx, []),

Releases = [element(2, R) || R <- Relx, element(1, R) == 'release'],
[error(no_release_configured) || length(Releases) == 0],

RelName = list_to_atom(proplists:get_value(relname, Args, "undefined")),
RelVsn = proplists:get_value(relvsn, Args),
Indexed = index_releases(Releases),

case {{RelName, RelVsn}, lists:keyfind(RelName, 1, Indexed)} of
{{undefined, _}, _} when length(Indexed) > 1 ->
error({release_not_selected, Indexed});
{{undefined, undefined}, _} when length(Indexed) == 1 ->
[{Name, [Version|_]}] = Indexed,
{Name, Version};
{{RelName, undefined}, {RelName, [Version|_]}} ->
{RelName, Version};
{{RelName, RelVsn} = Release, {RelName, Versions}} ->
case lists:member(RelVsn, Versions) of
true -> {RelName, RelVsn};
false -> error({unknown_release_version, Release, Versions})
end;
{Release, false} ->
error({unknown_release_name, Release, lists:map(fun({N, _}) -> N end, Indexed)})
end.

index_releases(Releases) ->
Index = lists:foldl(fun({Name, Version}, Acc) ->
Versions = proplists:get_value(Name, Acc, []),
lists:keystore(Name, 1, Acc, {Name, [Version|Versions]})
% maps:update_with(Name, fun(L) -> [Version|L] end, [Version], Acc)
end, [], Releases),
lists:map(fun({Name, Versions}) ->
{Name, lists:usort(fun(V1, V2) ->
rlx_util:parsed_vsn_lte(
rlx_util:parse_vsn(V2), % Highest version first
rlx_util:parse_vsn(V1)
)
end, Versions)}
end, Index).

rel_args(Name, Version, Args) ->
RelArgs = case lists:splitwith(fun("--") -> false; (_) -> true end, Args) of
{_, ["--"|Rest]} -> Rest;
{_, _} -> []
end,
["-n", Name, "-v", Version|RelArgs] -- ["-h", "--help", "--version"].

get_option(Arg, ConfigKey, State) ->
get_arg_option(Arg, State, fun(Config) ->
rebar3_grisp_util:get(ConfigKey, Config)
end).

get_option(Arg, ConfigKey, State, Default) ->
get_arg_option(Arg, State, fun(Config) ->
rebar3_grisp_util:get(ConfigKey, Config, Default)
Expand Down
Loading

0 comments on commit 65ef411

Please sign in to comment.