[opentitantool] Support multi-layer alias conf

We have an agreed way of connecting HyperDebug to CW310, which should be
declared in a JSON conf file, such that opentitan tool understands a
name such as A3 to be an alias of the HyperDebug native pin CN7_18.

Separately, the ChromeOS team has a particular idea of how to use the
OpenTitan chip pins, and wishes to use the name AP_FLASH_SEL as an alias
for A3.  This is mostly independent of CW310 and HyperDebug, and would
apply even to future ASIC development board.

Because of the above, I foresee that we would want to simultaneously
provide a CW310/HyperDebug configuration file, and a CromeOS/EarlGrey
configuration file to OpenTitan tool.

This PR allows the --conf option to take multiple arguments, and it
adapts the alias resolution logic to be transitive.

Change-Id: I6f8a328f269dfee246a19c4d3bae9fd60f056aec
Signed-off-by: Jes B. Klinke <jbk@chromium.org>
diff --git a/sw/host/opentitanlib/src/app/mod.rs b/sw/host/opentitanlib/src/app/mod.rs
index c3109be..c0ce49b 100644
--- a/sw/host/opentitanlib/src/app/mod.rs
+++ b/sw/host/opentitanlib/src/app/mod.rs
@@ -123,10 +123,16 @@
     /// string as is.
     fn map_name(map: &HashMap<String, String>, name: &str) -> String {
         let name = name.to_uppercase();
-        // TODO(#8769): Support multi-level aliasing, either by
-        // flattening after parsing all files, or by repeated lookup
-        // here.
-        map.get(&name).cloned().unwrap_or(name)
+        match map.get(&name) {
+            Some(v) => {
+                if v.eq(&name) {
+                    name
+                } else {
+                    Self::map_name(map, v)
+                }
+            }
+            None => name,
+        }
     }
 
     /// Apply given configuration to a single pins.
diff --git a/sw/host/opentitanlib/src/backend/mod.rs b/sw/host/opentitanlib/src/backend/mod.rs
index a3806f0..273e241 100644
--- a/sw/host/opentitanlib/src/backend/mod.rs
+++ b/sw/host/opentitanlib/src/backend/mod.rs
@@ -47,8 +47,8 @@
     #[structopt(flatten)]
     ti50emulator_opts: ti50emulator::Ti50EmulatorOpts,
 
-    #[structopt(long, help = "Configuration files")]
-    conf: Option<PathBuf>,
+    #[structopt(long, number_of_values(1), help = "Configuration files")]
+    conf: Vec<PathBuf>,
 }
 
 #[derive(Error, Debug)]
@@ -60,7 +60,7 @@
 /// Creates the requested backend interface according to [`BackendOpts`].
 pub fn create(args: &BackendOpts) -> Result<TransportWrapper> {
     let interface = args.interface.as_str();
-    let (backend, conf) = match interface {
+    let (backend, default_conf) = match interface {
         "" => (create_empty_transport()?, None),
         "proxy" => (proxy::create(&args.proxy_opts)?, None),
         "verilator" => (
@@ -91,8 +91,14 @@
     };
     let mut env = TransportWrapper::new(backend);
 
-    for conf_file in args.conf.as_ref().map(PathBuf::as_ref).or(conf) {
-        process_config_file(&mut env, conf_file)?
+    if args.conf.is_empty() {
+        for conf_file in default_conf {
+            // Zero or one iteration
+            process_config_file(&mut env, conf_file)?
+        }
+    }
+    for conf_file in &args.conf {
+        process_config_file(&mut env, conf_file.as_ref())?
     }
     Ok(env)
 }