scripts: west flash with stm32CubeProgrammer and ext-loader

Add the option to support an external loader for flashing
hex file to internal and external NOR flash using
the STM32CubProgrammer CLI with a  board_runner_args
"--extload=MX25LM51245G_STM32U585I-IOT02A.stldr"
The absolute path of the stldr file is added to the
stm32CubeProgrammer command.

Signed-off-by: Francois Ramu <francois.ramu@st.com>
diff --git a/scripts/west_commands/runners/core.py b/scripts/west_commands/runners/core.py
index 21a60ed..fdc3801 100644
--- a/scripts/west_commands/runners/core.py
+++ b/scripts/west_commands/runners/core.py
@@ -236,6 +236,10 @@
     - reset: whether the runner supports a --reset option, which
       resets the device after a flash operation is complete.
 
+    - extload: whether the runner supports a --extload option, which
+      must be given one time and is passed on to the underlying tool
+      that the runner wraps.
+
     - tool_opt: whether the runner supports a --tool-opt (-O) option, which
       can be given multiple times and is passed on to the underlying tool
       that the runner wraps.
@@ -250,6 +254,7 @@
     flash_addr: bool = False
     erase: bool = False
     reset: bool = False
+    extload: bool = False
     tool_opt: bool = False
     file: bool = False
 
@@ -531,6 +536,10 @@
                                   "Default action depends on each specific runner."
                                   if caps.reset else argparse.SUPPRESS))
 
+        parser.add_argument('--extload', dest='extload',
+                            help=(cls.extload_help() if caps.extload
+                                  else argparse.SUPPRESS))
+
         parser.add_argument('-O', '--tool-opt', dest='tool_opt',
                             default=[], action='append',
                             help=(cls.tool_opt_help() if caps.tool_opt
@@ -561,6 +570,8 @@
             _missing_cap(cls, '--erase')
         if args.reset and not caps.reset:
             _missing_cap(cls, '--reset')
+        if args.extload and not caps.extload:
+            _missing_cap(cls, '--extload')
         if args.tool_opt and not caps.tool_opt:
             _missing_cap(cls, '--tool-opt')
         if args.file and not caps.file:
@@ -650,6 +661,15 @@
                   connected.'''
 
     @classmethod
+    def extload_help(cls) -> str:
+        ''' Get the ArgParse help text for the --extload option.'''
+        return '''External loader to be used by stm32cubeprogrammer
+                  to program the targeted external memory.
+                  The runner requires the external loader (*.stldr) filename.
+                  This external loader (*.stldr) must be located within
+                  STM32CubeProgrammer/bin/ExternalLoader directory.'''
+
+    @classmethod
     def tool_opt_help(cls) -> str:
         ''' Get the ArgParse help text for the --tool-opt option.'''
         return '''Option to pass on to the underlying tool used
diff --git a/scripts/west_commands/runners/stm32cubeprogrammer.py b/scripts/west_commands/runners/stm32cubeprogrammer.py
index b57864a..43d9a19 100644
--- a/scripts/west_commands/runners/stm32cubeprogrammer.py
+++ b/scripts/west_commands/runners/stm32cubeprogrammer.py
@@ -37,6 +37,7 @@
         cli: Optional[Path],
         use_elf: bool,
         erase: bool,
+        extload: Optional[str],
         tool_opt: List[str],
     ) -> None:
         super().__init__(cfg)
@@ -51,6 +52,12 @@
         self._use_elf = use_elf
         self._erase = erase
 
+        if extload:
+            p = STM32CubeProgrammerBinaryRunner._get_stm32cubeprogrammer_path().parent.resolve() / 'ExternalLoader'
+            self._extload = ['-el', str(p / extload)]
+        else:
+            self._extload = []
+
         self._tool_opt: List[str] = list()
         for opts in [shlex.split(opt) for opt in tool_opt]:
             self._tool_opt += opts
@@ -112,7 +119,7 @@
 
     @classmethod
     def capabilities(cls):
-        return RunnerCaps(commands={"flash"}, erase=True, tool_opt=True)
+        return RunnerCaps(commands={"flash"}, erase=True, extload=True, tool_opt=True)
 
     @classmethod
     def do_add_parser(cls, parser):
@@ -152,6 +159,10 @@
         )
 
     @classmethod
+    def extload_help(cls) -> str:
+        return "External Loader for STM32_Programmer_CLI"
+
+    @classmethod
     def tool_opt_help(cls) -> str:
         return "Additional options for STM32_Programmer_CLI"
 
@@ -168,6 +179,7 @@
             cli=args.cli,
             use_elf=args.use_elf,
             erase=args.erase,
+            extload=args.extload,
             tool_opt=args.tool_opt,
         )
 
@@ -192,6 +204,9 @@
 
         cmd += ["--connect", connect_opts]
         cmd += self._tool_opt
+        if self._extload:
+            # external loader to come after the tool option in STM32CubeProgrammer
+            cmd += self._extload
 
         # erase first if requested
         if self._erase: