Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -495,9 +495,9 @@ The following table enumerates the valid `family_id` options:
| Key | Description |
|-------------|-------------|
| `rp2040` | Original Raspberry Pi Pico and Pico-W |
| `rp2035` | Raspberry Pi Pico 2 |
| `rp2350` | Raspberry Pi Pico 2 |
| `data` | Raspberry Pi Pico 2 |
| `universal` | Universal format for both `rp2040` and `rp2035` |
| `universal` | Universal format for both `rp2040` and `rp2350` |

> Note the convenience of universal uf2 binaries comes with the expense of being twice the size, as both versions are included in the universal uf2.

Expand Down
54 changes: 45 additions & 9 deletions src/atomvm_pico_flash_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -119,11 +119,17 @@ env_opts() ->

%% @private
find_picotool() ->
case os:find_executable("picotool") of
false ->
"";
Picotool ->
Picotool
%% We use a mock picotool for CI tests to simulate resetting and mounting a device.
case os:getenv("ATOMVM_REBAR3_TEST_MODE") of
"true" ->
os:getenv("ATOMVM_REBAR3_TEST_PICOTOOL");
_ ->
case os:find_executable("picotool") of
false ->
"";
Picotool ->
Picotool
end
end.

%% @private
Expand Down Expand Up @@ -201,12 +207,19 @@ wait_for_mount(_Mount, 30) ->

%% @private
get_pico_mount(Mount) ->
Count =
case os:getenv("ATOMVM_REBAR3_TEST_MODE") of
"true" ->
25;
_ ->
0
end,
case Mount of
"" ->
case find_mounted_pico() of
not_found ->
rebar_api:info("Waiting for an RP2 device to mount...", []),
wait_for_mount(Mount, 0);
wait_for_mount(Mount, Count);
{ok, Pico} ->
{ok, Pico}
end;
Expand All @@ -219,7 +232,7 @@ get_pico_mount(Mount) ->
rebar_api:info("Waiting for the device at path ~s to mount...", [
string:trim(Mount)
]),
wait_for_mount(Mount, 0)
wait_for_mount(Mount, Count)
end
end.

Expand Down Expand Up @@ -270,7 +283,12 @@ do_reset(ResetPort, Picotool) ->
rebar_api:warn("Disconnecting serial monitor with: `~s' in 5 seconds...", [
DevReset
]),
timer:sleep(5000),
case os:getenv("ATOMVM_REBAR3_TEST_MODE") of
"true" ->
ok;
_ ->
timer:sleep(5000)
end,
RebootReturn = os:cmd(DevReset),
RebootStatus = string:trim(RebootReturn),
case RebootStatus of
Expand All @@ -297,6 +315,16 @@ get_uf2_appname(ProjectApps) ->

%% @private
do_flash(ProjectApps, PicoPath, ResetDev, Picotool) ->
TestMode = os:getenv("ATOMVM_REBAR3_TEST_MODE"),
case TestMode of
"true" ->
rebar_api:info(
"Using picotool options:~n --path ~s~n --reset ~s~n --picotool ~s",
[PicoPath, ResetDev, Picotool]
);
_ ->
ok
end,
case needs_reset(ResetDev) of
false ->
rebar_api:debug("No Pico reset device found matching ~s.", [ResetDev]),
Expand All @@ -305,7 +333,15 @@ do_flash(ProjectApps, PicoPath, ResetDev, Picotool) ->
rebar_api:debug("Pico at ~s needs reset...", [ResetPort]),
do_reset(ResetPort, Picotool),
rebar_api:info("Waiting for the device at path ~s to settle and mount...", [PicoPath]),
wait_for_mount(PicoPath, 0)
%% Reduce the timeout for tests since we aren't waiting for real hardware
Count =
case TestMode of
"true" ->
25;
_ ->
0
end,
wait_for_mount(PicoPath, Count)
end,
{ok, Path} = get_pico_mount(PicoPath),
TargetUF2 = get_uf2_file(ProjectApps),
Expand Down
20 changes: 17 additions & 3 deletions src/atomvm_uf2create_provider.erl
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,8 @@ do(State) ->
StartAddrStr = parse_addr(maps:get(start, Opts)),
Image = maps:get(input, Opts, TargetAVM),
Uf2Flavor = validate_flavor(maps:get(family_id, Opts)),
ok = uf2tool:uf2create(Output, Uf2Flavor, StartAddrStr, Image),
ok = do_create_uf2(Output, Uf2Flavor, StartAddrStr, Image),
rebar_api:info("UF2 file written to ~s", [Output]),
{ok, State}
catch
C:E:S ->
Expand All @@ -99,6 +100,19 @@ format_error(Reason) ->
%% internal functions
%%

%% @private
do_create_uf2(Output, Uf2Flavor, StartAddrStr, Image) ->
case os:getenv("ATOMVM_REBAR3_TEST_MODE") of
"true" ->
rebar_api:info(
"Using uf2create options:~n --output ~s~n --family_id ~p~n --start 0x~.16B~n --input ~s",
[Output, Uf2Flavor, StartAddrStr, Image]
);
_ ->
ok
end,
uf2tool:uf2create(Output, Uf2Flavor, StartAddrStr, Image).

%% @private
get_opts(State) ->
{ParsedArgs, _} = rebar_state:command_parsed_args(State),
Expand Down Expand Up @@ -153,9 +167,9 @@ validate_flavor(Flavor) ->
rp2040;
"rp2040" ->
rp2040;
rp2035 ->
rp2350 ->
data;
"rp2035" ->
"rp2350" ->
data;
data ->
data;
Expand Down
4 changes: 3 additions & 1 deletion test/driver/apps/rebar_overrides/rebar.config
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,7 @@
{atomvm_rebar3_plugin, [
{packbeam, [{start, start}]},
{esp32_flash, [{chip, "esp32c3"}]},
{stm32_flash, [{offset, "0x1234"}]}
{stm32_flash, [{offset, "0x1234"}]},
{uf2create, [{start, "0x10180800"}, {family_id, "rp2350"}]},
{pico_flash, [{reset, "/dev/FAKE0"}]}
]}.
39 changes: 39 additions & 0 deletions test/driver/scripts/picotool.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/bin/sh
##
## Copyright (c) Winford (UncleGrumpy) <winford@object.stream>
## All rights reserved.
##
## SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later

set -e

Args="${@}"

while [ "${1}" != "" ]; do
case ${1} in
"reboot" )
shift
if ( [ ${1} = "-f" ] && [ ${2} = "-u" ] ) then
if [ -L $TEST_MYAPP_LOCK ] then
## Since we are locked make sure mount doesn't exist or contain test artifacts
[-d $TEST_MYAPP_MOUNT] && rm -r $TEST_MYAPP_MOUNT
rm $TEST_MYAPP_LOCK
mkdir $TEST_MYAPP_MOUNT
elif ([ -L $TEST_REBAR_OVERRIDES_LOCK ]) then
[-d $TEST_REBAR_OVERRIDES_MOUNT] && rm -r $TEST_REBAR_OVERRIDES_MOUNT
rm $TEST_REBAR_OVERRIDES_LOCK
mkdir $TEST_REBAR_OVERRIDES_MOUNT
else
echo "No accessible RP-series devices in BOOTSEL mode were found."
exit 1
fi
echo "The device was asked to reboot into BOOTSEL mode."
fi
break
esac
shift
done

echo "${Args}"

exit 0
184 changes: 184 additions & 0 deletions test/driver/src/pico_flash_tests.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
%%
%% Copyright (c) 2023 <fred@dushin.net>
%% All rights reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
%
-module(pico_flash_tests).

-export([run/1]).

run(Opts) ->
ok = test_flags(Opts),
ok = test_env_overrides(Opts),
ok = test_rebar_overrides(Opts),
ok.

%% @private
test_flags(Opts) ->
%% test default options, and no device connected (this test wiill fail if a real pico is connected!)
test_flags(Opts, [], [
{"--path\n", ""},
{"--reset\n", ""},
{"Pico not mounted after 30 seconds.", "giving up..."}
]),

%% Simulate a device that needs reset
Reset1 = os:getenv("TEST_MYAPP_LOCK"),
%% devices needing reset can only be device files or symlinks
file:make_symlink("/dev/null", Reset1),
Path1 = os:getenv("TEST_MYAPP_MOUNT"),
%% make sure the mount is not present (picotool.sh will create the mount after removing lock dev)
file:del_dir_r("TEST_MYAPP_MOUNT"),
test_flags(
Opts,
[
{"-r", Reset1},
{"-p", Path1}
],
[
{"--path", Path1},
{"--reset", Reset1},
{
"Copying myapp.uf2 to " ++ Path1,
"===> Successfully loaded myapp application to the device."
}
]
),

ok.

%% @private
test_flags(Opts, Flags, FlagExpectList) ->
AppsDir = maps:get(apps_dir, Opts),
AppDir = test:make_path([AppsDir, "myapp"]),

Cmd = create_pico_flash_cmd(AppDir, Flags, []),
Output = test:execute_cmd(Cmd, Opts),
test:debug(Output, Opts),

lists:foreach(
fun({Flag, Value}) ->
test:expect_contains(io_lib:format("~s ~s", [Flag, Value]), Output)
end,
FlagExpectList
),

test:tick().

%% @private
test_env_overrides(Opts) ->
Reset = os:getenv("TEST_MYAPP_LOCK"),
file:make_symlink("/dev/null", Reset),
test_env_overrides(
Opts, "ATOMVM_REBAR3_PLUGIN_PICO_RESET_DEV", Reset, "--reset"
),

Path = os:getenv("TEST_MYAPP_MOUNT"),
%% there is no dev lock, so we must make sure the mount exists and is empty
file:del_dir_r(Path),
file:make_dir(Path),
test_env_overrides(Opts, "ATOMVM_REBAR3_PLUGIN_PICO_MOUNT_PATH", Path, "--path"),
%% cleanup
file:del_dir_r(Path),

file:make_dir(Path),
test_env_overrides(
Opts, "ATOMVM_REBAR3_PLUGIN_PICOTOOL", string:trim(os:cmd("which echo")), "--picotool"
),
%% cleanup
file:del_dir_r(Path),
ok.

%% @private
test_env_overrides(Opts, EnvVar, Value, Flag) ->
AppsDir = maps:get(apps_dir, Opts),
AppDir = test:make_path([AppsDir, "myapp"]),
%% if we are not testing path overrides use the test path since no device is present
Flags =
case Flag of
"--path" ->
[];
_ ->
[{"-p", os:getenv("TEST_MYAPP_MOUNT")}]
end,
Cmd = create_pico_flash_cmd(AppDir, Flags, [{EnvVar, Value}]),
Output = test:execute_cmd(Cmd, Opts),
test:debug(Output, Opts),

ok = test:expect_contains(io_lib:format("~s ~s", [Flag, Value]), Output),

test:tick().

%% @private
test_rebar_overrides(Opts) ->
%% the rebar_overrides rebar.config specifies reset /dev/FAKE0
Path = os:getenv("TEST_REBAR_OVERRIDES_MOUNT"),
file:del_dir_r(Path),
file:make_dir(Path),
test_rebar_overrides(
Opts,
[{"-p", Path}],
"ATOMVM_REBAR3_PLUGIN_PICO_RESET_DEV",
"/dev/ttyACM0",
"--reset",
"/dev/FAKE0"
),
%% cleanup
file:del_dir_r(Path),

%% Simulate a device needing reset, the mock picotool.sh will create the mount matching the reset device.
Reset = os:getenv("TEST_REBAR_OVERRIDES_LOCK"),
file:make_symlink("/dev/null", Reset),
test_rebar_overrides(
Opts,
[{"-r", Reset}, {"-p", Path}],
"ATOMVM_REBAR3_PLUGIN_PICO_RESET_DEV",
"/dev/tty.usbserial-0001",
"--reset",
Reset
),
%% cleanup
file:del_dir_r(Path),

%% Simulte a device already in BOOTSEL mode
file:make_dir(Path),
test_rebar_overrides(
Opts,
[{"-p", Path}],
"ATOMVM_REBAR3_PLUGIN_PICO_MOUNT_PATH",
"/mnt/RP2350",
"--path",
Path
),
ok.

%% @private
test_rebar_overrides(Opts, Flags, EnvVar, Value, Flag, ExpectedValue) ->
AppsDir = maps:get(apps_dir, Opts),
AppDir = test:make_path([AppsDir, "rebar_overrides"]),

Cmd = create_pico_flash_cmd(AppDir, Flags, [{EnvVar, Value}]),
Output = test:execute_cmd(Cmd, Opts),
test:debug(Output, Opts),

ok = test:expect_contains(io_lib:format("~s ~s", [Flag, ExpectedValue]), Output),

test:tick().

%% @private
create_pico_flash_cmd(AppDir, Opts, Env) ->
test:create_rebar3_cmd(AppDir, pico_flash, Opts, Env).
Loading