From: Matheus Tavares Bernardino <[email protected]>

Bare-metal Hexagon programs use OPENDIR, READDIR, and CLOSEDIR
semihosting calls to enumerate host directories. Directory handles are shared
by CPUs in a cluster, allowing guest indices to resolve across calls.

Use lock_user_string()/unlock_user() to read the OPENDIR path from guest
memory instead of copying it into a fixed-size buffer.

CLOSEDIR clears its slot in the directory list, so a stale index reports
EBADF instead of dereferencing a freed pointer.

Also add a functional test for the new semihosting operations.

Signed-off-by: Matheus Tavares Bernardino <[email protected]>
Signed-off-by: Brian Cain <[email protected]>
---
 include/hw/hexagon/hexagon.h              |  14 ++-
 hw/hexagon/hex-subsys.c                   |  32 ++++---
 target/hexagon/hexswi.c                   | 108 ++++++++++++++++++++++
 tests/functional/hexagon/test_systests.py |  11 +++
 4 files changed, 153 insertions(+), 12 deletions(-)

diff --git a/include/hw/hexagon/hexagon.h b/include/hw/hexagon/hexagon.h
index 62398eeb359..58bdcabe87e 100644
--- a/include/hw/hexagon/hexagon.h
+++ b/include/hw/hexagon/hexagon.h
@@ -11,6 +11,7 @@
 
 #include "system/memory.h"
 #include "hw/core/boards.h"
+#include "hw/cpu/cluster.h"
 
 struct hexagon_board_boot_info {
     uint64_t ram_size;
@@ -159,6 +160,17 @@ struct hexagon_machine_config {
     union hexagon_config_table cfgtable;
 };
 
+#define TYPE_HEXAGON_CLUSTER_STATE "hexagon-cluster-state"
+OBJECT_DECLARE_SIMPLE_TYPE(HexagonClusterState, HEXAGON_CLUSTER_STATE)
+
+struct HexagonClusterState {
+    CPUClusterState parent_obj;
+
+    struct {
+        GList *dir_list;
+    } semihosting;
+};
+
 #define TYPE_HEXAGON_COMMON_MACHINE "hexagon-common-machine"
 OBJECT_DECLARE_SIMPLE_TYPE(HexagonCommonMachineState, HEXAGON_COMMON_MACHINE)
 
@@ -168,7 +180,7 @@ struct HexagonCommonMachineState {
     MemoryRegion ram;
     MemoryRegion cfgtable_rom;
     MemoryRegion vtcm;
-    DeviceState *cluster;
+    HexagonClusterState cluster;
     DeviceState *l2vic;
     DeviceState *qtimer;
     DeviceState *glob_regs;
diff --git a/hw/hexagon/hex-subsys.c b/hw/hexagon/hex-subsys.c
index 4e3a418340e..f76f803dfb0 100644
--- a/hw/hexagon/hex-subsys.c
+++ b/hw/hexagon/hex-subsys.c
@@ -98,14 +98,11 @@ static DeviceState *tlb_create(HexagonCommonMachineState 
*hms,
     return tlb;
 }
 
-static DeviceState *cluster_create(HexagonCommonMachineState *hms)
+static void cluster_create(HexagonCommonMachineState *hms)
 {
-    DeviceState *cluster = qdev_new(TYPE_CPU_CLUSTER);
-
-    object_property_add_child(OBJECT(hms), "cluster", OBJECT(cluster));
-    qdev_prop_set_uint32(cluster, "cluster-id", 0);
-
-    return cluster;
+    object_initialize_child(OBJECT(hms), "cluster", &hms->cluster,
+                            TYPE_HEXAGON_CLUSTER_STATE);
+    qdev_prop_set_uint32(DEVICE(&hms->cluster), "cluster-id", 0);
 }
 
 void hex_subsys_create(HexagonCommonMachineState *hms,
@@ -135,7 +132,7 @@ void hex_subsys_create(HexagonCommonMachineState *hms,
                                     &hms->vtcm);
     }
 
-    hms->cluster = cluster_create(hms);
+    cluster_create(hms);
     hms->l2vic = l2vic_create(hms, m_cfg);
     hms->qtimer = qtimer_create(hms, m_cfg);
     hms->glob_regs = globalreg_create(hms, m_cfg, rev);
@@ -144,7 +141,7 @@ void hex_subsys_create(HexagonCommonMachineState *hms,
 
 void hex_subsys_add_cpu(HexagonCommonMachineState *hms, DeviceState *cpu)
 {
-    object_property_add_child(OBJECT(hms->cluster), "cpu[*]", OBJECT(cpu));
+    object_property_add_child(OBJECT(&hms->cluster), "cpu[*]", OBJECT(cpu));
     object_property_set_link(OBJECT(cpu), "global-regs",
                              OBJECT(hms->glob_regs), &error_fatal);
     object_property_set_link(OBJECT(cpu), "tlb", OBJECT(hms->tlb),
@@ -158,10 +155,10 @@ void hex_subsys_realize_cluster(HexagonCommonMachineState 
*hms)
     /*
      * The cluster must be realized after its CPUs have been parented into it
      * (see hex_subsys_add_cpu()) but before any CPU is itself realized, since
-     * qdev_realize_and_unref() on a CPU latches cluster_index into the TCG
+     * qdev_realize() on a CPU latches cluster_index into the TCG
      * cflags at that point.
      */
-    qdev_realize_and_unref(hms->cluster, NULL, &error_fatal);
+    qdev_realize(DEVICE(&hms->cluster), NULL, &error_fatal);
 }
 
 void hex_subsys_realize_cpu(HexagonCommonMachineState *hms, DeviceState *cpu,
@@ -173,3 +170,16 @@ void hex_subsys_realize_cpu(HexagonCommonMachineState 
*hms, DeviceState *cpu,
         l2vic_connect_cpu(hms->l2vic, cpu);
     }
 }
+
+static const TypeInfo hexagon_cluster_type_info = {
+    .name = TYPE_HEXAGON_CLUSTER_STATE,
+    .parent = TYPE_CPU_CLUSTER,
+    .instance_size = sizeof(HexagonClusterState),
+};
+
+static void hexagon_cluster_register_types(void)
+{
+    type_register_static(&hexagon_cluster_type_info);
+}
+
+type_init(hexagon_cluster_register_types)
diff --git a/target/hexagon/hexswi.c b/target/hexagon/hexswi.c
index e56a60f2f06..ddea881de99 100644
--- a/target/hexagon/hexswi.c
+++ b/target/hexagon/hexswi.c
@@ -19,6 +19,7 @@
 #include "hex_mmu.h"
 #include "hexswi.h"
 #include "hw/hexagon/hexagon_globalreg.h"
+#include "hw/hexagon/hexagon.h"
 
 #ifdef CONFIG_USER_ONLY
 #error "This file is only used in system emulation"
@@ -31,6 +32,18 @@
 #include "semihosting/uaccess.h"
 #include "system/runstate.h"
 
+/* We start from 1 as 0 is used to signal an error from g_dir_open(). */
+static const int DIR_INDEX_OFFSET = 1;
+
+/*
+ * GDir does not surface "." and ".." itself, so we track how many of
+ * those synthetic entries have been served for this handle so far.
+ */
+typedef struct {
+    GDir *dir;
+    unsigned int dot_entries;
+} SemihostingDir;
+
 /* non-arm-compatible semihosting calls */
 #define HEXAGON_SPECIFIC_SWI_FLAGS \
     DEF_SWI_FLAG(OPEN,             0x01) \
@@ -401,6 +414,13 @@ static void coredump(CPUHexagonState *env)
     qemu_log_unlock(f);
 }
 
+static GList **hex_semihosting_dir_list(CPUHexagonState *env)
+{
+    HexagonCPU *cpu = env_archcpu(env);
+    HexagonClusterState *cluster = HEXAGON_CLUSTER_STATE(OBJECT(cpu)->parent);
+    return &cluster->semihosting.dir_list;
+}
+
 static void sim_handle_trap0(CPUHexagonState *env)
 {
     target_ulong what_swi, swi_info;
@@ -666,6 +686,94 @@ static void sim_handle_trap0(CPUHexagonState *env)
     }
     break;
 
+    case HEX_SYS_OPENDIR:
+    {
+        GDir *dir;
+        SemihostingDir *semidir;
+        char *buf;
+        int rc = 0, err = 0;
+
+        buf = lock_user_string(swi_info);
+        if (!buf) {
+            common_semi_cb(cs, -1, EFAULT);
+            break;
+        }
+
+        GList **dir_list = hex_semihosting_dir_list(env);
+        dir = g_dir_open(buf, 0, NULL);
+        if (dir != NULL) {
+            semidir = g_new(SemihostingDir, 1);
+            semidir->dir = dir;
+            semidir->dot_entries = 0;
+            *dir_list = g_list_append(*dir_list, semidir);
+            rc = g_list_index(*dir_list, semidir) + DIR_INDEX_OFFSET;
+        } else {
+            err = errno;
+        }
+        unlock_user(buf, swi_info, 0);
+        common_semi_cb(cs, rc, rc != 0 ? 0 : err);
+        break;
+    }
+
+    case HEX_SYS_READDIR:
+    {
+        const char *host_dir_entry = NULL;
+        int dir_index = swi_info - DIR_INDEX_OFFSET;
+        GList **dir_list = hex_semihosting_dir_list(env);
+        SemihostingDir *dir = g_list_nth_data(*dir_list, dir_index);
+        uint32_t rc = 0, err = 0;
+        size_t i, name_len;
+
+        if (dir) {
+            if (dir->dot_entries < 2) {
+                host_dir_entry = dir->dot_entries++ ? ".." : ".";
+            } else {
+                errno = 0;
+                host_dir_entry = g_dir_read_name(dir->dir);
+                if (host_dir_entry == NULL) {
+                    err = errno;
+                }
+            }
+        } else {
+            err = EBADF;
+        }
+
+        if (host_dir_entry) {
+            uint32_t guest_dir_entry = env->gpr[HEX_REG_R02];
+            /* GDir does not provide a portable inode number. */
+            hexagon_write_memory(env, guest_dir_entry, 4, 0, retaddr);
+            name_len = MIN(strlen(host_dir_entry), 254);
+            for (i = 0; i <= name_len; i++) {
+                hexagon_write_memory(env, guest_dir_entry + 4 + i, 1,
+                                     host_dir_entry[i], retaddr);
+            }
+            rc = guest_dir_entry;
+        }
+        common_semi_cb(cs, rc, err);
+        break;
+    }
+
+    case HEX_SYS_CLOSEDIR:
+    {
+        SemihostingDir *dir;
+        int ret = -1, err = 0;
+        int dir_index = swi_info - DIR_INDEX_OFFSET;
+        GList **dir_list = hex_semihosting_dir_list(env);
+        GList *node = g_list_nth(*dir_list, dir_index);
+
+        dir = node ? node->data : NULL;
+        if (dir != NULL) {
+            g_dir_close(dir->dir);
+            g_free(dir);
+            ret = 0;
+            node->data = NULL;
+        } else {
+            err = EBADF;
+        }
+        common_semi_cb(cs, ret, ret == 0 ? 0 : err);
+        break;
+    }
+
     case HEX_SYS_COREDUMP:
         coredump(env);
         break;
diff --git a/tests/functional/hexagon/test_systests.py 
b/tests/functional/hexagon/test_systests.py
index 2779efa9172..a1080f7b974 100755
--- a/tests/functional/hexagon/test_systests.py
+++ b/tests/functional/hexagon/test_systests.py
@@ -7,6 +7,7 @@
 import re
 import time
 import unittest
+from pathlib import Path
 
 from qemu_test import QemuSystemTest, Asset, wait_for_console_pattern
 
@@ -99,5 +100,15 @@ def test_mmu_multi_tlb(self):
     def test_timer_reg(self):
         self.run_exit_zero("timer_reg")
 
+    def test_dirent(self):
+        testdir = Path(self.scratch_file("_testdir_dirent"))
+        testdir.mkdir()
+        files = ["file1", "file2"]
+        for f in files:
+            testdir.joinpath(f).touch()
+        expected = ". .. " + " ".join(files)
+        self.run_console_pattern("dirent", expected, "-append",
+                                 str(testdir))
+
 if __name__ == "__main__":
     QemuSystemTest.main()
-- 
2.34.1

Reply via email to