semihosting: add --semihosting-config arg sub-argument

Add new "arg" sub-argument to the --semihosting-config allowing the user
to pass multiple input arguments separately. It is required for example
by UHI semihosting to construct argc and argv.

Also, update ARM semihosting to support new option (at the moment it is
the only target which cares about arguments).

If the semihosting is enabled and no semihosting args have been specified,
then fall back to -kernel/-append. The -append string is split on whitespace
before initializing semihosting.argv[1..n]; this is different from what
QEMU MIPS machines' pseudo-bootloaders do (i.e. argv[1] contains the whole
-append), but is more intuitive from UHI user's point of view and Linux
kernel just does not care as it concatenates argv[1..n] into single cmdline
string anyway.

Signed-off-by: Leon Alrae <leon.alrae@imgtec.com>
Message-id: 1434643256-16858-3-git-send-email-leon.alrae@imgtec.com
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
diff --git a/vl.c b/vl.c
index a286877..278cd6a 100644
--- a/vl.c
+++ b/vl.c
@@ -488,6 +488,9 @@
         }, {
             .name = "target",
             .type = QEMU_OPT_STRING,
+        }, {
+            .name = "arg",
+            .type = QEMU_OPT_STRING,
         },
         { /* end of list */ }
     },
@@ -1251,6 +1254,9 @@
 typedef struct SemihostingConfig {
     bool enabled;
     SemihostingTarget target;
+    const char **argv;
+    int argc;
+    const char *cmdline; /* concatenated argv */
 } SemihostingConfig;
 
 static SemihostingConfig semihosting;
@@ -1265,6 +1271,58 @@
     return semihosting.target;
 }
 
+const char *semihosting_get_arg(int i)
+{
+    if (i >= semihosting.argc) {
+        return NULL;
+    }
+    return semihosting.argv[i];
+}
+
+int semihosting_get_argc(void)
+{
+    return semihosting.argc;
+}
+
+const char *semihosting_get_cmdline(void)
+{
+    if (semihosting.cmdline == NULL && semihosting.argc > 0) {
+        semihosting.cmdline = g_strjoinv(" ", (gchar **)semihosting.argv);
+    }
+    return semihosting.cmdline;
+}
+
+static int add_semihosting_arg(void *opaque,
+                               const char *name, const char *val,
+                               Error **errp)
+{
+    SemihostingConfig *s = opaque;
+    if (strcmp(name, "arg") == 0) {
+        s->argc++;
+        /* one extra element as g_strjoinv() expects NULL-terminated array */
+        s->argv = g_realloc(s->argv, (s->argc + 1) * sizeof(void *));
+        s->argv[s->argc - 1] = val;
+        s->argv[s->argc] = NULL;
+    }
+    return 0;
+}
+
+/* Use strings passed via -kernel/-append to initialize semihosting.argv[] */
+static inline void semihosting_arg_fallback(const char *file, const char *cmd)
+{
+    char *cmd_token;
+
+    /* argv[0] */
+    add_semihosting_arg(&semihosting, "arg", file, NULL);
+
+    /* split -append and initialize argv[1..n] */
+    cmd_token = strtok(g_strdup(cmd), " ");
+    while (cmd_token) {
+        add_semihosting_arg(&semihosting, "arg", cmd_token, NULL);
+        cmd_token = strtok(NULL, " ");
+    }
+}
+
 /***********************************************************/
 /* USB devices */
 
@@ -3669,6 +3727,9 @@
                     } else {
                         semihosting.target = SEMIHOSTING_TARGET_AUTO;
                     }
+                    /* Set semihosting argument count and vector */
+                    qemu_opt_foreach(opts, add_semihosting_arg,
+                                     &semihosting, NULL);
                 } else {
                     fprintf(stderr, "Unsupported semihosting-config %s\n",
                             optarg);
@@ -4237,6 +4298,11 @@
         exit(1);
     }
 
+    if (semihosting_enabled() && !semihosting_get_argc() && kernel_filename) {
+        /* fall back to the -kernel/-append */
+        semihosting_arg_fallback(kernel_filename, kernel_cmdline);
+    }
+
     os_set_line_buffering();
 
 #ifdef CONFIG_SPICE