[profile] Introduce role-based scheduler profile API.

Introduce a role-based profile API to avoid hard coding profile
parameters in client code, especially in out-of-tree code that
requires a roll to update.

- Add SetProfileByRole method the ProfileProvider protocol.
- Add skeleton implementation of the SetProfileByRole method.
- Add device_set_profile_by_role wrapper to DDK and fake/no DDK.
- Add tests for SetProfileByRole method.
- Move tests from bootfs to core.

Bug: 40858
Change-Id: Ieca272aa49a6c75f8676b4d278894174576e61e2
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/491260
Reviewed-by: Tom Bergan <tombergan@google.com>
Reviewed-by: Suraj Malhotra <surajmalhotra@google.com>
Reviewed-by: James Robinson <jamesr@google.com>
Reviewed-by: Carlos Pizano <cpu@google.com>
Commit-Queue: Corey Tabaka <eieio@google.com>
API-Review: Carlos Pizano <cpu@google.com>
diff --git a/bundles/bringup/BUILD.gn b/bundles/bringup/BUILD.gn
index 09c4751..851eefb8 100644
--- a/bundles/bringup/BUILD.gn
+++ b/bundles/bringup/BUILD.gn
@@ -34,7 +34,6 @@
     "//src/zircon/tests/futex-ownership:futex-ownership-bootfs-test",
     "//src/zircon/tests/job-policy:policy-bootfs-test",
     "//src/zircon/tests/kcounter:kcounter-bootfs-test",
-    "//src/zircon/tests/profile:profile-bootfs-test",
     "//src/zircon/tests/thread-state:thread-state-bootfs-test",
     "//src/zircon/tests/time:time-bootfs-test",
     "//src/zircon/tests/timers:timers-bootfs-test",
diff --git a/garnet/bin/hwstress/profile_manager_test.cc b/garnet/bin/hwstress/profile_manager_test.cc
index 28e4008..03dda23 100644
--- a/garnet/bin/hwstress/profile_manager_test.cc
+++ b/garnet/bin/hwstress/profile_manager_test.cc
@@ -69,6 +69,11 @@
     ZX_PANIC("unexpected call");
   }
 
+  void SetProfileByRole(zx::thread thread, std::string role,
+                        SetProfileByRoleCallback callback) override {
+    ZX_PANIC("unexpected call");
+  }
+
   bool get_affinity_profile_called = false;
   bool get_profile_called = false;
   uint32_t requested_priority = -1;
diff --git a/sdk/fidl/fuchsia.scheduler/profile.fidl b/sdk/fidl/fuchsia.scheduler/profile.fidl
index 58d0869..a670d26 100644
--- a/sdk/fidl/fuchsia.scheduler/profile.fidl
+++ b/sdk/fidl/fuchsia.scheduler/profile.fidl
@@ -15,12 +15,27 @@
 [Discoverable, ForDeprecatedCBindings]
 protocol ProfileProvider {
     /// Obtain a profile handle.
+    ///
+    /// TODO(fxbug.dev/40858): This API will be deprecated and removed in the future, use
+    /// SetProfileByRole instead.
     GetProfile(uint32 priority, string:64 name) -> (zx.status status, zx.handle:PROFILE? profile);
 
     /// Obtain a deadline profile handle.
+    ///
+    /// TODO(fxbug.dev/40858): This API will be deprecated and removed in the future, use
+    /// SetProfileByRole instead.
     GetDeadlineProfile(uint64 capacity, uint64 deadline, uint64 period, string:64 name)
         -> (zx.status status, zx.handle:PROFILE? profile);
 
     /// Obtain a handle for a profile that sets CPU affinity.
+    ///
+    /// TODO(fxbug.dev/40858): This API will be deprecated and removed in the future, use
+    /// SetProfileByRole instead.
     GetCpuAffinityProfile(CpuSet cpu_mask) -> (zx.status status, zx.handle:PROFILE? profile);
+
+    /// Sets the given thread's profile based on the requested role. The exact
+    /// parameters of the profile are system dependent and may vary based on
+    /// device-specific tuning and/or runtime system goals.
+    SetProfileByRole(zx.handle:THREAD thread, string:2048 role)
+        -> (zx.status status);
 };
diff --git a/src/devices/bin/driver_host/api.cc b/src/devices/bin/driver_host/api.cc
index bbba538..f6d4717 100644
--- a/src/devices/bin/driver_host/api.cc
+++ b/src/devices/bin/driver_host/api.cc
@@ -244,6 +244,11 @@
   return internal::get_scheduler_deadline_profile(capacity, deadline, period, name, out_profile);
 }
 
+__EXPORT zx_status_t device_set_profile_by_role(zx_device_t* device, zx_handle_t thread,
+                                                const char* role, size_t role_size) {
+  return internal::set_scheduler_profile_by_role(thread, role, role_size);
+}
+
 __EXPORT const char* device_get_name(zx_device_t* dev) { return dev->name(); }
 
 __EXPORT zx_device_t* device_get_parent(zx_device_t* dev) {
diff --git a/src/devices/bin/driver_host/scheduler_profile.cc b/src/devices/bin/driver_host/scheduler_profile.cc
index ead171b2..507b9e9 100644
--- a/src/devices/bin/driver_host/scheduler_profile.cc
+++ b/src/devices/bin/driver_host/scheduler_profile.cc
@@ -8,6 +8,7 @@
 #include <lib/fdio/directory.h>
 #include <lib/zx/channel.h>
 #include <lib/zx/profile.h>
+#include <lib/zx/thread.h>
 #include <stdio.h>
 #include <string.h>
 #include <zircon/types.h>
@@ -62,4 +63,24 @@
   return ZX_OK;
 }
 
+zx_status_t set_scheduler_profile_by_role(zx_handle_t thread, const char* role, size_t role_size) {
+  zx::unowned_thread original_thread{thread};
+  zx::thread duplicate_thread;
+  zx_status_t status =
+      original_thread->duplicate(ZX_RIGHT_TRANSFER | ZX_RIGHT_MANAGE_THREAD, &duplicate_thread);
+  if (status != ZX_OK) {
+    return status;
+  }
+
+  zx_status_t fidl_status = ZX_ERR_INTERNAL;
+  status = fuchsia_scheduler_ProfileProviderSetProfileByRole(scheduler_profile_provider, thread,
+                                                             role, role_size, &fidl_status);
+  if (status != ZX_OK) {
+    return status;
+  }
+  if (fidl_status != ZX_OK) {
+    return fidl_status;
+  }
+  return ZX_OK;
+}
 }  // namespace internal
diff --git a/src/devices/bin/driver_host/scheduler_profile.h b/src/devices/bin/driver_host/scheduler_profile.h
index 220ca85..303ee8f 100644
--- a/src/devices/bin/driver_host/scheduler_profile.h
+++ b/src/devices/bin/driver_host/scheduler_profile.h
@@ -13,6 +13,7 @@
 
 zx_status_t get_scheduler_deadline_profile(uint64_t capacity, uint64_t deadline, uint64_t period,
                                            const char* name, zx_handle_t* profile);
+zx_status_t set_scheduler_profile_by_role(zx_handle_t thread, const char* role, size_t role_size);
 }  // namespace internal
 
 #endif  // SRC_DEVICES_BIN_DRIVER_HOST_SCHEDULER_PROFILE_H_
diff --git a/src/devices/testing/fake_ddk/fake_ddk.cc b/src/devices/testing/fake_ddk/fake_ddk.cc
index 22c9e36..92e0cb4 100644
--- a/src/devices/testing/fake_ddk/fake_ddk.cc
+++ b/src/devices/testing/fake_ddk/fake_ddk.cc
@@ -537,6 +537,13 @@
 }
 
 __EXPORT
+zx_status_t device_set_profile_by_role(zx_device_t* device, zx_handle_t thread, const char* role,
+                                       size_t role_size) {
+  // This is currently a no-op.
+  return ZX_OK;
+}
+
+__EXPORT
 void device_fidl_transaction_take_ownership(fidl_txn_t* txn, device_fidl_txn_t* new_txn) {
   auto fidl_txn = fake_ddk::FromDdkInternalTransaction(ddk::internal::Transaction::FromTxn(txn));
 
diff --git a/src/devices/testing/no_ddk/no_ddk.cc b/src/devices/testing/no_ddk/no_ddk.cc
index 6411561..8d278d3a 100644
--- a/src/devices/testing/no_ddk/no_ddk.cc
+++ b/src/devices/testing/no_ddk/no_ddk.cc
@@ -97,6 +97,13 @@
   return ZX_OK;
 }
 
+__EXPORT
+zx_status_t device_set_profile_by_role(zx_device_t* device, zx_handle_t thread, const char* role,
+                                       size_t role_size) {
+  // This is currently a no-op.
+  return ZX_OK;
+}
+
 __EXPORT __WEAK zx_status_t load_firmware(zx_device_t* device, const char* path, zx_handle_t* fw,
                                           size_t* size) {
   // This is currently a no-op.
diff --git a/src/lib/ddk/include/lib/ddk/driver.h b/src/lib/ddk/include/lib/ddk/driver.h
index ba5f79c..e8375a9 100644
--- a/src/lib/ddk/include/lib/ddk/driver.h
+++ b/src/lib/ddk/include/lib/ddk/driver.h
@@ -264,15 +264,36 @@
 // priority.
 //
 // The current arguments are transitional, and will likely change in the future.
+//
+// TODO(fxbug.dev/40858): This API will be deprecated and removed in the future, use
+// device_set_profile_by_role instead.
 zx_status_t device_get_profile(zx_device_t* device, uint32_t priority, const char* name,
                                zx_handle_t* out_profile);
 
 // Retrieves a deadline profile handle into |out_profile| from the scheduler for
 // the given deadline parameters.  See |device_get_profile|
+//
+// TODO(fxbug.dev/40858): This API will be deprecated and removed in the future, use
+// device_set_profile_by_role instead.
 zx_status_t device_get_deadline_profile(zx_device_t* device, uint64_t capacity, uint64_t deadline,
                                         uint64_t period, const char* name,
                                         zx_handle_t* out_profile);
 
+// Requests that the given thread be assigned a profile with parameters appropriate for the given
+// role. The available roles and the specific parameters assigned are device-dependent and may also
+// vary across builds. Requests are not guaranteed to be honored for all roles and requestors, and
+// may either be rejected or silently ignored, based on system-specific criteria.
+//
+// |role| is name of the requested role. This must not be nullptr, even if |role_size| is zero.
+//
+// |thread| is a borrowed handle that must have DUPLICATE, TRANSFER, and MANAGE_THREAD rights. It is
+// duplicated internally before transfer to the ProfileProvider service to complete the profile
+// assignment.
+//
+// This API should be used in preference to hard coding parameters in drivers.
+zx_status_t device_set_profile_by_role(zx_device_t* device, zx_handle_t thread, const char* role,
+                                       size_t role_size);
+
 // A description of a part of a device fragment.  It provides a bind program
 // that will match a device on the path from the root of the device tree to the
 // target device.
diff --git a/src/media/audio/audio_core/testing/fake_profile_provider.h b/src/media/audio/audio_core/testing/fake_profile_provider.h
index 8aa5b4b..cdaf873 100644
--- a/src/media/audio/audio_core/testing/fake_profile_provider.h
+++ b/src/media/audio/audio_core/testing/fake_profile_provider.h
@@ -50,6 +50,12 @@
   void GetCpuAffinityProfile(fuchsia::scheduler::CpuSet cpu_mask,
                              GetProfileCallback callback) override {}
 
+  // |fuchsia::scheduler::ProfileProvider|
+  void SetProfileByRole(zx::thread thread, std::string role,
+                        SetProfileByRoleCallback callback) override {
+    callback(ZX_ERR_NOT_SUPPORTED);
+  }
+
   std::unordered_set<uint32_t> valid_priorities_;
   fidl::BindingSet<fuchsia::scheduler::ProfileProvider> bindings_;
 };
diff --git a/src/zircon/tests/BUILD.gn b/src/zircon/tests/BUILD.gn
index 46f3a4d..80e1048 100644
--- a/src/zircon/tests/BUILD.gn
+++ b/src/zircon/tests/BUILD.gn
@@ -12,6 +12,7 @@
     "kernel-clocks:tests",
     "kernel-cmdline:tests",
     "processor:tests",
+    "profile:tests",
     "property:tests",
     "register-state:tests",
     "status:tests",
diff --git a/src/zircon/tests/profile/BUILD.gn b/src/zircon/tests/profile/BUILD.gn
index fe60c58..e549b36a 100644
--- a/src/zircon/tests/profile/BUILD.gn
+++ b/src/zircon/tests/profile/BUILD.gn
@@ -3,7 +3,7 @@
 # found in the LICENSE file.
 
 import("//build/test.gni")
-import("//build/testing/bootfs_test.gni")
+import("//src/sys/build/fuchsia_unittest_package.gni")
 
 test("profile") {
   output_name = "profile-test"
@@ -18,7 +18,12 @@
   ]
 }
 
-bootfs_test("profile-bootfs-test") {
-  name = "profile-test"
+fuchsia_unittest_package("profile-test-pkg") {
+  manifest = "meta/profile-test.cmx"
   deps = [ ":profile" ]
 }
+
+group("tests") {
+  testonly = true
+  deps = [ ":profile-test-pkg" ]
+}
diff --git a/src/zircon/tests/profile/meta/profile-test.cmx b/src/zircon/tests/profile/meta/profile-test.cmx
new file mode 100644
index 0000000..ebe3a56
--- /dev/null
+++ b/src/zircon/tests/profile/meta/profile-test.cmx
@@ -0,0 +1,20 @@
+{
+  "facets": {
+        "fuchsia.test": {
+            "system-services": [
+                "fuchsia.scheduler.ProfileProvider"
+            ]
+        }
+    },
+    "include": [
+        "sdk/lib/diagnostics/syslog/client.shard.cmx"
+    ],
+    "program": {
+        "binary": "test/profile-test"
+    },
+    "sandbox": {
+        "services": [
+            "fuchsia.scheduler.ProfileProvider"
+        ]
+    }
+}
\ No newline at end of file
diff --git a/src/zircon/tests/profile/profile.cc b/src/zircon/tests/profile/profile.cc
index 26bf947..cb9ca16 100644
--- a/src/zircon/tests/profile/profile.cc
+++ b/src/zircon/tests/profile/profile.cc
@@ -10,8 +10,13 @@
 #include <lib/zx/channel.h>
 #include <lib/zx/object.h>
 #include <lib/zx/profile.h>
+#include <lib/zx/thread.h>
+#include <threads.h>
+#include <zircon/errors.h>
 #include <zircon/syscalls.h>
 
+#include <string>
+
 #include <zxtest/zxtest.h>
 
 namespace {
@@ -21,7 +26,7 @@
                           zx::profile* profile) {
   zx_handle_t raw_profile_handle;
   zx_status_t result = fuchsia_scheduler_ProfileProviderGetProfile(
-      profile_provider.get(), priority, name.c_str(), name.length(), server_status,
+      profile_provider.get(), priority, name.data(), name.length(), server_status,
       &raw_profile_handle);
   if (result != ZX_OK) {
     return ZX_OK;
@@ -36,7 +41,7 @@
                                   zx::profile* profile) {
   zx_handle_t raw_profile_handle;
   zx_status_t result = fuchsia_scheduler_ProfileProviderGetDeadlineProfile(
-      profile_provider.get(), capacity, relative_deadline, period, name.c_str(), name.length(),
+      profile_provider.get(), capacity, relative_deadline, period, name.data(), name.length(),
       server_status, &raw_profile_handle);
   if (result != ZX_OK) {
     return ZX_OK;
@@ -45,6 +50,18 @@
   return ZX_OK;
 }
 
+zx_status_t SetProfileByRole(const zx::channel& profile_provider, zx::unowned_thread thread,
+                             const std::string& role, zx_status_t* server_status) {
+  zx::thread duplicate;
+  if (zx_status_t status =
+          thread->duplicate(ZX_RIGHT_TRANSFER | ZX_RIGHT_MANAGE_THREAD, &duplicate);
+      status != ZX_OK) {
+    return status;
+  }
+  return fuchsia_scheduler_ProfileProviderSetProfileByRole(
+      profile_provider.get(), duplicate.release(), role.data(), role.length(), server_status);
+}
+
 void GetProfileProvider(zx::channel* channel) {
   // Connect to ProfileProvider.
   zx::channel server_endpoint;
@@ -110,4 +127,27 @@
   CheckBasicDetails(profile);
 }
 
+// Test setting a profile via the SetProfileByRole FIDL method.
+TEST(Profile, SetProfileByRole) {
+  zx::channel provider;
+  GetProfileProvider(&provider);
+  ASSERT_FALSE(CURRENT_TEST_HAS_FAILURES());
+
+  zx_status_t server_status;
+  zx_status_t status;
+
+  status = SetProfileByRole(provider, zx::thread::self(), "fuchsia.test-role:ok", &server_status);
+  EXPECT_OK(status);
+  EXPECT_OK(server_status);
+
+  status = SetProfileByRole(provider, zx::unowned_thread{}, "fuchsia.test-role:ok", &server_status);
+  EXPECT_EQ(ZX_ERR_BAD_HANDLE, status);
+  EXPECT_OK(server_status);
+
+  status =
+      SetProfileByRole(provider, zx::thread::self(), "fuchsia.test-role:not-found", &server_status);
+  EXPECT_OK(status);
+  EXPECT_EQ(ZX_ERR_NOT_FOUND, server_status);
+}
+
 }  // namespace
diff --git a/zircon/system/ulib/profile/profile.cc b/zircon/system/ulib/profile/profile.cc
index 3afa770..0f6d9cb 100644
--- a/zircon/system/ulib/profile/profile.cc
+++ b/zircon/system/ulib/profile/profile.cc
@@ -5,15 +5,21 @@
 #include "zircon/syscalls/profile.h"
 
 #include <fuchsia/scheduler/c/fidl.h>
+#include <inttypes.h>
 #include <lib/fidl-async/bind.h>
 #include <lib/profile/profile.h>
 #include <lib/syslog/global.h>
 #include <lib/zx/profile.h>
 #include <string.h>
 #include <zircon/assert.h>
+#include <zircon/errors.h>
+#include <zircon/status.h>
 #include <zircon/syscalls.h>
+#include <zircon/syscalls/object.h>
+#include <zircon/types.h>
 
 #include <algorithm>
+#include <string>
 
 namespace {
 
@@ -66,10 +72,58 @@
       txn, status, status == ZX_OK ? profile.release() : ZX_HANDLE_INVALID);
 }
 
+zx_status_t SetProfileByRoleSimple(void* ctx, zx_handle_t thread, const char* role_data,
+                                   size_t role_size, fidl_txn_t* txn) {
+  auto root_job = static_cast<zx_handle_t>(reinterpret_cast<uintptr_t>(ctx));
+
+  // Log the requested role and PID:TID of the thread being assigned.
+  zx_info_handle_basic_t handle_info{};
+  zx_status_t status = zx_object_get_info(thread, ZX_INFO_HANDLE_BASIC, &handle_info,
+                                          sizeof(handle_info), nullptr, nullptr);
+  if (status != ZX_OK) {
+    FX_LOGF(WARNING, "ProfileProvider", "Failed to get info for thread handle: %s",
+            zx_status_get_string(status));
+    handle_info.koid = ZX_KOID_INVALID;
+    handle_info.related_koid = ZX_KOID_INVALID;
+  }
+  const std::string role{role_data, role_size};
+  FX_LOGF(INFO, "ProfileProvider", "Role \"%s\" requested by %" PRId64 ":%" PRId64, role.c_str(),
+          handle_info.related_koid, handle_info.koid);
+
+  // Select the profile parameters based on the requested role. New roles and
+  // logic for selecting parameters may be added here as needed.
+  //
+  // TODO(fxbug.dev/40858): Move the role definitions into a device-specific
+  // configuration file.
+  zx_profile_info_t info = {};
+  if (role == "fuchsia.default") {
+    info.flags = ZX_PROFILE_INFO_FLAG_PRIORITY;
+    info.priority = ZX_PRIORITY_DEFAULT;
+  } else if (role == "fuchsia.test-role:not-found") {
+    return fuchsia_scheduler_ProfileProviderSetProfileByRole_reply(txn, ZX_ERR_NOT_FOUND);
+  } else if (role == "fuchsia.test-role:ok") {
+    return fuchsia_scheduler_ProfileProviderSetProfileByRole_reply(txn, ZX_OK);
+  } else {
+    return fuchsia_scheduler_ProfileProviderSetProfileByRole_reply(txn, ZX_ERR_NOT_FOUND);
+  }
+
+  zx::profile profile;
+  status = zx_profile_create(root_job, 0u, &info, profile.reset_and_get_address());
+  if (status != ZX_OK) {
+    // Failing to create a profile is likely due to a programming error in
+    // this handler. The most likely cause is invalid profile parameters.
+    return fuchsia_scheduler_ProfileProviderSetProfileByRole_reply(txn, ZX_ERR_INTERNAL);
+  }
+
+  status = zx_object_set_profile(thread, profile.get(), 0);
+  return fuchsia_scheduler_ProfileProviderSetProfileByRole_reply(txn, status);
+}
+
 fuchsia_scheduler_ProfileProvider_ops ops = {
     .GetProfile = GetProfileSimple,
     .GetDeadlineProfile = GetDeadlineProfileSimple,
     .GetCpuAffinityProfile = GetCpuAffinityProfileSimple,
+    .SetProfileByRole = SetProfileByRoleSimple,
 };
 
 constexpr const char* profile_svc_names[] = {