[opencl] Add Fuchsia loader to OpenCL-ICD-Loader

Find and load OpenCL libraries via the fuchsia.opencl.loader.Loader service.

fixes: 91263
tests: fuchsia-pkg://fuchsia.com/libopencl_test#meta/libopencl_test.cm
Change-Id: I889e664aaf02ed36eaa3e8322c17b35463af50ec
diff --git a/fuchsia/BUILD.gn b/fuchsia/BUILD.gn
index aeb2059..caff16a 100644
--- a/fuchsia/BUILD.gn
+++ b/fuchsia/BUILD.gn
@@ -17,10 +17,19 @@
 import("//build/cpp/sdk_shared_library.gni")
 import("//build/sdk/sdk_documentation.gni")
 
-source_set("opencl_dlopen") {
+source_set("opencl_fuchsia") {
+
+  include_dirs = [
+    "../loader",
+    "../loader/fuchsia",
+  ]
+
+  defines = [ "CL_TARGET_OPENCL_VERSION=300" ]
+
   sources = [
     "../loader/fuchsia/dlopen_fuchsia.cc",
     "../loader/fuchsia/dlopen_fuchsia.h",
+    "../loader/fuchsia/icd_fuchsia.cc",
     "../loader/fuchsia/loader_service.cc",
     "../loader/fuchsia/loader_service.h",
   ]
@@ -31,6 +40,8 @@
     "//zircon/system/ulib/service:service-llcpp",
     "//zircon/system/ulib/syslog",
   ]
+
+  public_deps = [ "//third_party/OpenCL-Headers:opencl_headers" ]
 }
 
 source_set("opencl_icd") {
@@ -38,8 +49,11 @@
 
   defines = [ "CL_TARGET_OPENCL_VERSION=300" ]
 
+  configs -= [ "//build/config:symbol_visibility_hidden" ]
+
   sources = [
     "../loader/adapters.h",
+    "../loader/icd.c",
     "../loader/icd.h",
     "../loader/icd_dispatch.c",
     "../loader/icd_dispatch.h",
@@ -56,13 +70,8 @@
   libcxx_linkage = "static"
   no_headers = true
 
-  include_dirs = [
-    "loader",
-    "loader/fuchsia",
-  ]
-
   deps = [
-    ":opencl_dlopen",
+    ":opencl_fuchsia",
     ":opencl_icd",
     "//sdk/lib/fdio",
   ]
diff --git a/loader/fuchsia/dlopen_fuchsia.cc b/loader/fuchsia/dlopen_fuchsia.cc
index c082a11..9885924 100644
--- a/loader/fuchsia/dlopen_fuchsia.cc
+++ b/loader/fuchsia/dlopen_fuchsia.cc
@@ -64,8 +64,8 @@
 static void *dlopen_from_opencl_loader(const char *name, int mode) {
   // Connect to the opencl loader service to request this library.
 
-  auto &loader_svc = get_opencl_loader_service();
-  if (!loader_svc.client_end()) {
+  auto &loader_svc = opencl_loader::get_opencl_loader_service();
+  if (!loader_svc.is_valid()) {
     snprintf(g_error, sizeof(g_error),
              "libopencl.so:dlopen_fuchsia: no connection to loader svc\n");
     return nullptr;
diff --git a/loader/fuchsia/icd_fuchsia.cc b/loader/fuchsia/icd_fuchsia.cc
new file mode 100644
index 0000000..eb41fdc
--- /dev/null
+++ b/loader/fuchsia/icd_fuchsia.cc
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2021 The Khronos Group Inc.
+ *
+ * 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.
+ *
+ * OpenCL is a trademark of Apple Inc. used under license by Khronos.
+ */
+
+#include "dlopen_fuchsia.h"
+#include "loader_service.h"
+
+#include <dlfcn.h>
+#include <lib/syslog/global.h>
+#include <pthread.h>
+
+extern "C" {
+  #include "icd.h"
+}
+
+static pthread_once_t initialized = PTHREAD_ONCE_INIT;
+
+/*
+ *
+ * Vendor enumeration functions
+ *
+ */
+
+// go through the list of vendors in the two configuration files
+void khrIcdOsVendorsEnumerate(void)
+{
+    auto &loader_svc = opencl_loader::get_opencl_loader_service();
+    if (!loader_svc.is_valid()) {
+        FX_LOGF(ERROR, opencl_loader::kTag, "no connection to loader svc");
+        return;
+    }
+
+    // TODO(fxbug.dev/91413): Get the library name from the loader_service.
+    khrIcdVendorAdd("libopencl_fake.so");
+}
+
+// go through the list of vendors only once
+void khrIcdOsVendorsEnumerateOnce(void)
+{
+    pthread_once(&initialized, khrIcdOsVendorsEnumerate);
+}
+
+/*
+ *
+ * Dynamic library loading functions
+ *
+ */
+
+// dynamically load a library.  returns NULL on failure
+void *khrIcdOsLibraryLoad(const char *libraryName)
+{
+    return dlopen_fuchsia(libraryName, RTLD_NOW, true);
+}
+
+// get a function pointer from a loaded library.  returns NULL on failure.
+void *khrIcdOsLibraryGetFunctionAddress(void *library, const char *functionName)
+{
+    return dlsym(library, functionName);
+}
+
+// unload a library
+void khrIcdOsLibraryUnload(void *library)
+{
+    dlclose(library);
+}
diff --git a/loader/fuchsia/loader_service.cc b/loader/fuchsia/loader_service.cc
index bf1b540..238caba 100644
--- a/loader/fuchsia/loader_service.cc
+++ b/loader/fuchsia/loader_service.cc
@@ -15,9 +15,9 @@
  * limitations under the License.
  *
  */
-
 #include "loader_service.h"
 
+#include <fcntl.h>
 #include <fidl/fuchsia.io/cpp/wire.h>
 #include <fidl/fuchsia.opencl.loader/cpp/wire.h>
 #include <lib/fdio/directory.h>
@@ -27,78 +27,99 @@
 #include <lib/service/llcpp/service.h>
 #include <lib/syslog/global.h>
 #include <stdio.h>
+#include <string.h>
 #include <threads.h>
 #include <zircon/status.h>
 #include <zircon/syscalls.h>
 
+namespace opencl_loader {
+
 static fidl::WireSyncClient<fuchsia_opencl_loader::Loader> opencl_loader_svc;
 
 static zx_handle_t device_fs = ZX_HANDLE_INVALID;
 
-constexpr const char* kTag = "opencl";
+static int manifest_fs_fd = -1;
 
 void connect_to_opencl_loader_svc() {
-  auto svc = service::OpenServiceRoot();
-  auto client_end = service::ConnectAt<fuchsia_opencl_loader::Loader>(*svc);
-  if (!client_end.is_ok()) {
-    FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to connect to loader service: %s",
-            client_end.status_string());
-    return;
-  }
-  auto client = fidl::BindSyncClient(std::move(*client_end));
-  auto feature_result = client->GetSupportedFeatures();
-  fuchsia_opencl_loader::wire::Features features;
-  if (!feature_result.ok()) {
-    FX_LOGF(
-        ERROR, kTag,
-        "connect_to_opencl_loader_svc: Failed to get supported features, error \"%s\". Retrying.",
-        feature_result.error().lossy_description());
-    // If GetSupportedFeatures isn't available the connection will be closed.
-    // If that happens, reconnect and assume a default set of features.
+    auto svc = service::OpenServiceRoot();
     auto client_end = service::ConnectAt<fuchsia_opencl_loader::Loader>(*svc);
     if (!client_end.is_ok()) {
-      FX_LOGF(ERROR, NULL,
-              "connect_to_opencl_loader_svc: Failed to reconnect to loader service: %s",
-              client_end.status_string());
-      return;
+        FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to connect to loader service: %s", client_end.status_string());
+        return;
     }
-    client = fidl::BindSyncClient(std::move(*client_end));
-    features = fuchsia_opencl_loader::wire::Features::kGet;
-  } else {
+    auto client = fidl::BindSyncClient(std::move(*client_end));
+    auto feature_result = client->GetSupportedFeatures();
+    fuchsia_opencl_loader::wire::Features features;
+    if (!feature_result.ok()) {
+        FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to get supported features, error \"%s\".",
+                   feature_result.error().lossy_description());
+        return;
+    }
     features = feature_result->features;
-  }
-  zx::channel device_fs_client;
-  if (features & fuchsia_opencl_loader::wire::Features::kConnectToDeviceFs) {
-    zx::channel device_fs_server;
-    zx_status_t status = zx::channel::create(0, &device_fs_server, &device_fs_client);
-    if (status != ZX_OK) {
-      FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to create channel: %s",
-              zx_status_get_string(status));
-      return;
+    constexpr fuchsia_opencl_loader::wire::Features kMandatoryFeatures =
+        fuchsia_opencl_loader::wire::Features::kConnectToDeviceFs | fuchsia_opencl_loader::wire::Features::kConnectToManifestFs |
+        fuchsia_opencl_loader::wire::Features::kGet;
+    if ((features & kMandatoryFeatures) != kMandatoryFeatures) {
+        FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Missing mandatory feature 0x%x",
+                   static_cast<uint32_t>(kMandatoryFeatures & ~features));
+        return;
     }
-
-    auto result = client->ConnectToDeviceFs(std::move(device_fs_server));
-    if (!result.ok()) {
-      FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to connect to device fs: %s",
-              result.status_string());
-      return;
+    zx::channel device_fs_client;
+    {
+        zx::channel device_fs_server;
+        zx_status_t status = zx::channel::create(0, &device_fs_server, &device_fs_client);
+        if (status != ZX_OK) {
+            FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to create channel: %s",
+                       zx_status_get_string(status));
+            return;
+        }
+        auto result = client->ConnectToDeviceFs(std::move(device_fs_server));
+        if (!result.ok()) {
+            FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to connect to device fs: %s", result.status_string());
+            return;
+        }
     }
-  }
-
-  opencl_loader_svc = std::move(client);
-  device_fs = device_fs_client.release();
+    {
+        zx::channel manifest_fs_client;
+        zx::channel manifest_fs_server;
+        zx_status_t status = zx::channel::create(0, &manifest_fs_server, &manifest_fs_client);
+        if (status != ZX_OK) {
+            FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to create channel: %s",
+                       zx_status_get_string(status));
+            return;
+        }
+        // Wait for idle so clients will be sure that any existing ICDs will be completely available.
+        auto result = client->ConnectToManifestFs(fuchsia_opencl_loader::wire::ConnectToManifestOptions::kWaitForIdle,
+                                                  std::move(manifest_fs_server));
+        if (!result.ok()) {
+            FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to connect to manifest fs: %s", result.status_string());
+            return;
+        }
+        status = fdio_fd_create(manifest_fs_client.release(), &manifest_fs_fd);
+        if (status != ZX_OK) {
+            FX_LOGF(ERROR, kTag, "connect_to_opencl_loader_svc: Failed to create manifest fs fd: %s", zx_status_get_string(status));
+            return;
+        }
+    }
+    opencl_loader_svc = std::move(client);
+    device_fs = device_fs_client.release();
 }
 
 static once_flag svc_connect_once_flag = ONCE_FLAG_INIT;
 
 fidl::WireSyncClient<fuchsia_opencl_loader::Loader>& get_opencl_loader_service() {
-  call_once(&svc_connect_once_flag, connect_to_opencl_loader_svc);
-
-  return opencl_loader_svc;
+    call_once(&svc_connect_once_flag, connect_to_opencl_loader_svc);
+    return opencl_loader_svc;
 }
 
 zx_handle_t get_device_fs() {
-  call_once(&svc_connect_once_flag, connect_to_opencl_loader_svc);
+    call_once(&svc_connect_once_flag, connect_to_opencl_loader_svc);
+    return device_fs;
+}
 
-  return device_fs;
+int get_manifest_fs_fd(void) {
+    call_once(&svc_connect_once_flag, connect_to_opencl_loader_svc);
+    return manifest_fs_fd;
+}
+
 }
diff --git a/loader/fuchsia/loader_service.h b/loader/fuchsia/loader_service.h
index ceb3a1f..0c25fe9 100644
--- a/loader/fuchsia/loader_service.h
+++ b/loader/fuchsia/loader_service.h
@@ -22,6 +22,10 @@
 #include <fidl/fuchsia.opencl.loader/cpp/wire.h>
 #include <zircon/types.h>
 
+namespace opencl_loader {
+
+constexpr auto kTag = "opencl_loader";
+
 // Returns a singleton handle to the fuchsia.opencl.loader.Loader service. Does not transfer
 // ownership.
 fidl::WireSyncClient<fuchsia_opencl_loader::Loader>& get_opencl_loader_service();
@@ -30,4 +34,6 @@
 // fuchsia.opencl.loader/Loader.ConnectToDeviceFs. Does not transfer ownership.
 zx_handle_t get_device_fs();
 
+}
+
 #endif  // THIRD_PARTY_OPENCL_ICD_LOADER_LOADER_FUCHSIA_LOADER_SERVICE_H_
diff --git a/loader/icd.c b/loader/icd.c
index aa677b0..46f8eeb 100644
--- a/loader/icd.c
+++ b/loader/icd.c
@@ -318,6 +318,7 @@
 }
 #endif // defined(CL_ENABLE_LAYERS)
 
+#if !defined(__Fuchsia__)
 // Get next file or dirname given a string list or registry key path.
 // Note: the input string may be modified!
 static char *loader_get_next_path(char *path) {
@@ -357,6 +358,7 @@
         khrIcd_free_getenv(icdFilenames);
     }
 }
+#endif
 
 #if defined(CL_ENABLE_LAYERS)
 void khrIcdLayersEnumerateEnv(void)
diff --git a/loader/icd_platform.h b/loader/icd_platform.h
index f84f519..4d0db0d 100644
--- a/loader/icd_platform.h
+++ b/loader/icd_platform.h
@@ -19,7 +19,7 @@
 #ifndef _ICD_PLATFORM_H_
 #define _ICD_PLATFORM_H_
 
-#if defined(__linux__) || defined(__APPLE__)
+#if defined(__linux__) || defined(__APPLE__) || defined(__Fuchsia__)
 
 #define PATH_SEPARATOR  ':'
 #define DIRECTORY_SYMBOL '/'