[sysmem] sysmem-connector zircon service

Make fuchsia_sysmem_Allocator2 a zircon service.  The service finds a
sysmem device instance and forwards the service request to the driver
to be served directly from there.

This way a non-driver client of sysmem doesn't need to hassle with
finding the sysmem device instance etc.

A similar garnet service will be added which shares the sysmem-connector
code, so to anticipate that, sysmem-connector is a ulib.

Tested: /system/test/sys/sysmem-test
Change-Id: Ie2773eb919b67a5bc53bd0a4e8b50921e33127fc
diff --git a/system/core/devmgr/devmgr/main.cpp b/system/core/devmgr/devmgr/main.cpp
index f5d9786..66cf73f 100644
--- a/system/core/devmgr/devmgr/main.cpp
+++ b/system/core/devmgr/devmgr/main.cpp
@@ -409,6 +409,21 @@
     // services such as crashsvc and the profile service.
     launchpad_add_handle(lp, root_job_copy.release(), PA_HND(PA_USER0, 1));
 
+    // Give svchost access to /dev/class/sysmem, to enable svchost to forward sysmem service
+    // requests to the sysmem driver.  Create a namespace containing /dev/class/sysmem.
+    const char* nametable[1] = {};
+    uint32_t count = 0;
+    zx::channel fs_handle = devmgr::fs_clone("dev/class/sysmem");
+    if (fs_handle.is_valid()) {
+        nametable[count] = "/sysmem";
+        launchpad_add_handle(lp, fs_handle.release(), PA_HND(PA_NS_DIR, count++));
+    } else {
+        launchpad_abort(lp, ZX_ERR_BAD_STATE, "devmgr: failed to clone /dev/class/sysmem");
+        // The launchpad_go() call below will fail, but will still free lp.
+    }
+
+    launchpad_set_nametable(lp, count, nametable);
+
     const char* errmsg = nullptr;
     if ((status = launchpad_go(lp, nullptr, &errmsg)) < 0) {
         printf("devmgr: launchpad %s (%s) failed: %s: %d\n",
diff --git a/system/core/svchost/rules.mk b/system/core/svchost/rules.mk
index e86a513..7c414bc 100644
--- a/system/core/svchost/rules.mk
+++ b/system/core/svchost/rules.mk
@@ -11,6 +11,7 @@
 
 MODULE_SRCS += \
     $(LOCAL_DIR)/svchost.cpp \
+    $(LOCAL_DIR)/sysmem.cpp \
     $(LOCAL_DIR)/crashsvc.cpp
 
 MODULE_FIDL_LIBS := \
@@ -29,6 +30,7 @@
     system/ulib/svc \
     system/ulib/process-launcher \
     system/ulib/sysmem \
+    system/ulib/sysmem-connector \
     system/ulib/fs \
     system/ulib/async \
     system/ulib/async.cpp \
diff --git a/system/core/svchost/svchost.cpp b/system/core/svchost/svchost.cpp
index 53f7726..4fc8bfa 100644
--- a/system/core/svchost/svchost.cpp
+++ b/system/core/svchost/svchost.cpp
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+#include "sysmem.h"
+
 #include <fbl/algorithm.h>
 #include <lib/async-loop/cpp/loop.h>
 #include <lib/fdio/util.h>
@@ -172,6 +174,7 @@
     zx_service_provider_instance_t service_providers[] = {
         {.provider = launcher_get_service_provider(), .ctx = nullptr},
         {.provider = sysmem_get_service_provider(), .ctx = nullptr},
+        {.provider = sysmem2_get_service_provider(), .ctx = nullptr},
         {.provider = profile_get_service_provider(),
          .ctx = reinterpret_cast<void*>(static_cast<uintptr_t>(profile_root_job_copy))},
     };
@@ -185,7 +188,6 @@
         }
     }
 
-
     // if full system is not required drop simple logger service.
     zx_service_provider_instance_t logger_service{.provider = logger_get_service_provider(),
                                                   .ctx = nullptr};
diff --git a/system/core/svchost/sysmem.cpp b/system/core/svchost/sysmem.cpp
new file mode 100644
index 0000000..4410978
--- /dev/null
+++ b/system/core/svchost/sysmem.cpp
@@ -0,0 +1,54 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sysmem.h"
+
+#include <fuchsia/sysmem/c/fidl.h>
+#include <lib/sysmem-connector/sysmem-connector.h>
+#include <lib/zx/channel.h>
+
+#include <cstring>
+
+const char *kSysmemSvchostPath = "/sysmem";
+
+// We don't really need a service context, only a sysmem-connector context, so
+// we just directly use the sysmem-connector context as the only context.
+static zx_status_t sysmem2_init(void** out_ctx) {
+    return sysmem_connector_init(kSysmemSvchostPath, reinterpret_cast<sysmem_connector_t**>(out_ctx));
+}
+
+static zx_status_t sysmem2_connect(
+    void* ctx, async_dispatcher_t* dispatcher, const char* service_name,
+    zx_handle_t allocator2_request_param) {
+    zx::channel allocator2_request(allocator2_request_param);
+    sysmem_connector_t* connector = static_cast<sysmem_connector_t*>(ctx);
+    if (!strcmp(service_name, fuchsia_sysmem_Allocator2_Name)) {
+        sysmem_connector_queue_connection_request(connector, allocator2_request.release());
+    }
+    return ZX_ERR_NOT_SUPPORTED;
+}
+static void sysmem2_release(void* ctx) {
+    sysmem_connector_release(static_cast<sysmem_connector_t*>(ctx));
+}
+
+static constexpr const char* sysmem2_services[] = {
+    fuchsia_sysmem_Allocator2_Name,
+    nullptr,
+};
+
+static constexpr zx_service_ops_t sysmem2_ops = {
+    .init = sysmem2_init,
+    .connect = sysmem2_connect,
+    .release = sysmem2_release,
+};
+
+static constexpr zx_service_provider_t sysmem2_service_provider = {
+    .version = SERVICE_PROVIDER_VERSION,
+    .services = sysmem2_services,
+    .ops = &sysmem2_ops,
+};
+
+const zx_service_provider_t* sysmem2_get_service_provider() {
+    return &sysmem2_service_provider;
+}
diff --git a/system/core/svchost/sysmem.h b/system/core/svchost/sysmem.h
new file mode 100644
index 0000000..ce97106
--- /dev/null
+++ b/system/core/svchost/sysmem.h
@@ -0,0 +1,9 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <lib/svc/service.h>
+
+const zx_service_provider_t* sysmem2_get_service_provider();
diff --git a/system/ulib/sysmem-connector/include/lib/sysmem-connector/sysmem-connector.h b/system/ulib/sysmem-connector/include/lib/sysmem-connector/sysmem-connector.h
new file mode 100644
index 0000000..5c4bfc1
--- /dev/null
+++ b/system/ulib/sysmem-connector/include/lib/sysmem-connector/sysmem-connector.h
@@ -0,0 +1,41 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#pragma once
+
+#include <zircon/compiler.h>
+#include <zircon/types.h>
+
+__BEGIN_CDECLS
+
+// The client of sysmem-connector shouldn't need to know how large this struct
+// is (or even whether the pointer is to a heap allocation vs. within a heap
+// allocation), so it's declared here, but defined in sysmem-connector.cpp.
+typedef struct sysmem_connector sysmem_connector_t;
+
+// Allocate and initialize a sysmem_connector_t.  ZX_OK from this function
+// doesn't guarantee that the sysmem driver is found yet, only that the
+// connector has successfully been created and initialized.
+//
+// |sysmem_device_path| must remain valid for lifetime of sysmem_connector_t.
+// This is the path to the directory of sysmem device instances (just one
+// device instance will actually exist, unless something is going wrong).
+zx_status_t sysmem_connector_init(const char* sysmem_directory_path, sysmem_connector_t** out_connector);
+
+// allocator2_request is consumed.  A call to this function doesn't guarantee
+// that the request will reach the sysmem driver, only that the connector has
+// queued the request internally to be sent.
+//
+// If the sysmem driver can't be contacted for an extended duration, the request
+// may sit in the queue for that duration - there isn't a timeout, because that
+// would probably do more harm than good, since sysmem is always supposed to be
+// running.
+void sysmem_connector_queue_connection_request(
+    sysmem_connector_t* connector,
+    zx_handle_t allocator2_request);
+
+// This call is not allowed to fail.
+void sysmem_connector_release(sysmem_connector_t* connector);
+
+__END_CDECLS
diff --git a/system/ulib/sysmem-connector/rules.mk b/system/ulib/sysmem-connector/rules.mk
new file mode 100644
index 0000000..8e4bf9d
--- /dev/null
+++ b/system/ulib/sysmem-connector/rules.mk
@@ -0,0 +1,30 @@
+# Copyright 2019 The Fuchsia Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_TYPE := userlib
+
+MODULE_SRCS := \
+    $(LOCAL_DIR)/sysmem-connector.cpp \
+
+MODULE_COMPILEFLAGS += -fvisibility=hidden
+
+MODULE_FIDL_LIBS := \
+    system/fidl/fuchsia-sysmem
+
+MODULE_STATIC_LIBS := \
+    system/ulib/async \
+    system/ulib/async-loop \
+    system/ulib/async-loop.cpp \
+    system/ulib/fbl \
+    system/ulib/fzl \
+    system/ulib/zx \
+
+MODULE_LIBS := \
+    system/ulib/fdio \
+
+include make/module.mk
diff --git a/system/ulib/sysmem-connector/sysmem-connector.cpp b/system/ulib/sysmem-connector/sysmem-connector.cpp
new file mode 100644
index 0000000..bb6ce7a
--- /dev/null
+++ b/system/ulib/sysmem-connector/sysmem-connector.cpp
@@ -0,0 +1,318 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <lib/sysmem-connector/sysmem-connector.h>
+
+#include <fbl/auto_lock.h>
+#include <fbl/function.h>
+#include <fbl/mutex.h>
+#include <fbl/unique_fd.h>
+#include <fuchsia/sysmem/c/fidl.h>
+#include <lib/async/cpp/task.h>
+#include <lib/async-loop/cpp/loop.h>
+#include <lib/fdio/watcher.h>
+#include <lib/fdio/util.h>
+#include <lib/fzl/fdio.h>
+#include <lib/zx/channel.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <queue>
+#include <threads.h>
+
+// The actual sysmem FIDL server is in the sysmem driver.  The code that watches
+// for the driver and sends sysmem service requests to the driver is in
+// ulib/sysmem-connector.  The code here just needs to queue requests to
+// sysmem-connector.
+
+// Clients interact with sysmem-connector using a C ABI, but we want to use C++
+// for the implementation.  We have SysmemConnector inherit from an empty
+// sysmem_connector struct just to make the SysmemConnector class officially be
+// a class not a struct, and to make the SysmemConnector name consistent with
+// C++ coding conventions, and have member names with "_" at the end consistent
+// with coding conventions, etc.
+//
+// Every instance of sysmem_connector is actually a SysmemConnector and vice
+// versa.
+struct sysmem_connector {
+    // Intentionally declared as empty; never instantiated; see SysmemConnector.
+};
+class SysmemConnector : public sysmem_connector {
+  // public in this case just means public to this file.  The interface is via
+  // the functions declared in lib/sysmem-connector/sysmem-connector.h.
+  public:
+    SysmemConnector(const char* sysmem_device_path);
+    zx_status_t Start();
+    void QueueRequest(zx::channel allocator2_request);
+    void Stop();
+  private:
+    void Post(fbl::Closure to_run);
+
+    static zx_status_t DeviceAddedShim(int dirfd, int event, const char* fn, void* cookie);
+    zx_status_t DeviceAdded(int dirfd, int event, const char* fn);
+
+    zx_status_t ConnectToSysmemDriver();
+
+    void ProcessQueue();
+
+    //
+    // Set once during construction + Start(), never set again.
+    //
+
+    const char* sysmem_device_path_{};
+    async::Loop process_queue_loop_;
+    thrd_t process_queue_thrd_{};
+
+    //
+    // Only touched from process_queue_loop_'s one thread.
+    //
+
+    fbl::unique_fd sysmem_dir_fd_;
+    zx::channel driver_connector_client_;
+
+    //
+    // Synchronized using lock_.
+    //
+
+    fbl::Mutex lock_;
+    std::queue<zx::channel> connection_requests_ __TA_GUARDED(lock_);
+};
+
+SysmemConnector::SysmemConnector(const char* sysmem_device_path)
+    : sysmem_device_path_(sysmem_device_path),
+      process_queue_loop_(&kAsyncLoopConfigNoAttachToThread) {
+    ZX_DEBUG_ASSERT(sysmem_device_path_);
+}
+
+zx_status_t SysmemConnector::Start() {
+    ZX_DEBUG_ASSERT(!sysmem_dir_fd_);
+    {
+        int fd = open(sysmem_device_path_, O_DIRECTORY | O_RDONLY);
+        if (fd < 0) {
+            printf("sysmem-connector: Failed to open %s: %d\n", sysmem_device_path_, errno);
+            return ZX_ERR_INTERNAL;
+        }
+        sysmem_dir_fd_.reset(fd);
+    }
+    ZX_DEBUG_ASSERT(sysmem_dir_fd_);
+
+    // The process_queue_thrd_ is filled out before any code that checks it runs on the thread, because the current
+    // thread is the only thread that triggers anything to happen on the thread being created here, and that is only
+    // done after process_queue_thrd_ is filled out by the current thread.
+    zx_status_t status = process_queue_loop_.StartThread("SysmemConnector-ProcessQueue", &process_queue_thrd_);
+    if (status != ZX_OK) {
+        printf("sysmem-connector: process_queue_loop_.StartThread() failed - status: %d\n", status);
+        return status;
+    }
+    // Establish initial connection to sysmem driver async.
+    Post([this]{ConnectToSysmemDriver();});
+    return ZX_OK;
+}
+
+void SysmemConnector::QueueRequest(zx::channel allocator2_request) {
+    ZX_DEBUG_ASSERT(thrd_current() != process_queue_thrd_);
+    bool trigger_needed;
+    {  // scope lock
+        fbl::AutoLock lock(&lock_);
+        trigger_needed = connection_requests_.empty();
+        connection_requests_.emplace(std::move(allocator2_request));
+    }  // ~lock
+    if (trigger_needed) {
+        Post([this]{ProcessQueue();});
+    }
+}
+
+void SysmemConnector::Stop() {
+    ZX_DEBUG_ASSERT(thrd_current() != process_queue_thrd_);
+    process_queue_loop_.Quit();
+    process_queue_loop_.JoinThreads();
+    process_queue_loop_.Shutdown();
+}
+
+void SysmemConnector::Post(fbl::Closure to_run) {
+    zx_status_t post_status = async::PostTask(
+        process_queue_loop_.dispatcher(), std::move(to_run));
+    // We don't expect this post to ever fail.
+    ZX_ASSERT(post_status == ZX_OK);
+}
+
+zx_status_t SysmemConnector::DeviceAddedShim(int dirfd, int event, const char* fn, void* cookie) {
+    ZX_DEBUG_ASSERT(cookie);
+    SysmemConnector* connector = static_cast<SysmemConnector*>(cookie);
+    return connector->DeviceAdded(dirfd, event, fn);
+}
+
+zx_status_t SysmemConnector::DeviceAdded(int dirfd, int event, const char* filename) {
+    ZX_DEBUG_ASSERT(thrd_current() == process_queue_thrd_);
+    if (event != WATCH_EVENT_ADD_FILE) {
+        // Keep going on IDLE or REMOVE.  There's nothing else useful that the
+        // current thread can do until a sysmem device instance is available,
+        // and we don't have any reason to attempt to directly handle any
+        // REMOVE(s) since we'll do fdio_watch_directory() again later from
+        // scratch instead.
+        return ZX_OK;
+    }
+    ZX_DEBUG_ASSERT(event == WATCH_EVENT_ADD_FILE);
+
+    zx::channel driver_connector_client;
+    zx::channel driver_connector_server;
+    zx_status_t status = zx::channel::create(0, &driver_connector_client, &driver_connector_server);
+    if (status != ZX_OK) {
+        printf("SysmemConnector::DeviceAdded() zx::channel::create() failed - status: %d\n", status);
+        ZX_DEBUG_ASSERT(status != ZX_ERR_STOP);
+        // If channel create fails, give up on this attempt to find a sysmem
+        // device instance.  If another request arrives later, we'll try again
+        // later.
+        return status;
+    }
+
+    // We don't intend to close dirfd; all paths should release not close.
+    fbl::unique_fd unique_dirfd(dirfd);
+    fzl::FdioCaller caller(std::move(unique_dirfd));
+    status = fdio_service_connect_at(caller.borrow_channel(), filename, driver_connector_server.release());
+    // Never close dirfd.
+    caller.release().release();
+    if (status != ZX_OK) {
+        printf("SysmemConnector::DeviceAdded() fdio_service_connect_at() failed - status: %d\n", status);
+        ZX_DEBUG_ASSERT(status != ZX_ERR_STOP);
+        // If somehow fdio_service_connect_at() fails for this device instance,
+        // keep watching for another device instance.
+        return ZX_OK;
+    }
+
+    driver_connector_client_ = std::move(driver_connector_client);
+    ZX_DEBUG_ASSERT(driver_connector_client_);
+    return ZX_ERR_STOP;
+}
+
+zx_status_t SysmemConnector::ConnectToSysmemDriver() {
+    ZX_DEBUG_ASSERT(thrd_current() == process_queue_thrd_);
+    ZX_DEBUG_ASSERT(!driver_connector_client_);
+    // Returns ZX_ERR_STOP as soon as one of the 000, 001 device instances is
+    // found.  We rely on those to go away if the corresponding sysmem instance
+    // is no longer operational, so that we don't find them when we call
+    // ConnectToSysmemDriver() again upon discovering that we can't send to a
+    // previous device instance.
+    //
+    // TODO(dustingreen): Currently if this watch never finds a sysmem device
+    // instance, then sysmem_connector_release() will block forever.  This can
+    // be fixed once it's feasible to use DeviceWatcher (or similar) here
+    // instead (currently DeviceWatcher is in garnet not zircon).
+    zx_status_t watch_status = fdio_watch_directory(
+        sysmem_dir_fd_.get(), DeviceAddedShim, ZX_TIME_INFINITE, this);
+    if (watch_status != ZX_ERR_STOP) {
+        printf("sysmem-connector: Failed to find sysmem device - status: %d\n", watch_status);
+        return watch_status;
+    }
+    ZX_DEBUG_ASSERT(driver_connector_client_);
+    return ZX_OK;
+}
+
+void SysmemConnector::ProcessQueue() {
+    ZX_DEBUG_ASSERT(thrd_current() == process_queue_thrd_);
+    while (true) {
+        zx::channel allocator2_request;
+        {  // scope lock
+            fbl::AutoLock lock(&lock_);
+            if (connection_requests_.empty()) {
+                return;
+            }
+            allocator2_request = std::move(connection_requests_.front());
+            connection_requests_.pop();
+        }  // ~lock
+        ZX_DEBUG_ASSERT(allocator2_request);
+
+        // Poll for PEER_CLOSED just before we need the channel to be usable, to
+        // avoid routing a request to a stale no-longer-usable sysmem device
+        // instance.  This doesn't eliminate the inherent race where a request
+        // can be sent to an instance that's already started failing - that race
+        // is fine.  This check is just a best-effort way to avoid routing to a
+        // super-stale previous instance.
+        //
+        // TODO(dustingreen): When it becomes more convenient, switch to
+        // noticing PEER_CLOSED async.  Currently it doesn't seem particularly
+        // safe to use sysmem_fdio_caller_.borrow_channel() to borrow for an
+        // async wait.
+        if (driver_connector_client_) {
+            zx_signals_t observed;
+            zx_status_t wait_status = zx_object_wait_one(
+                driver_connector_client_.get(),
+                ZX_CHANNEL_PEER_CLOSED,
+                ZX_TIME_INFINITE_PAST,
+                &observed);
+            if (wait_status == ZX_OK) {
+                ZX_DEBUG_ASSERT(observed & ZX_CHANNEL_PEER_CLOSED);
+                // This way, we'll call ConnectToSysmemDriver() below.
+                driver_connector_client_.reset();
+            } else {
+                // Any other failing status is unexpected.
+                ZX_DEBUG_ASSERT(ZX_ERR_TIMED_OUT);
+            }
+        }
+
+        if (!driver_connector_client_) {
+            zx_status_t connect_status = ConnectToSysmemDriver();
+            if (connect_status != ZX_OK) {
+                // ~allocator2_request - we'll try again to connect to a sysmem
+                // instance next time a request comes in, but any given request
+                // gets a max of one attempt to connect to a sysmem device
+                // instance, in case attempts to find a sysmem device instance
+                // are just failing.
+                return;
+            }
+        }
+        ZX_DEBUG_ASSERT(driver_connector_client_);
+
+        zx_status_t send_connect_status = fuchsia_sysmem_DriverConnectorConnect(
+            driver_connector_client_.get(), allocator2_request.release());
+        if (send_connect_status != ZX_OK) {
+            // The most likely failing send_connect_status is
+            // ZX_ERR_PEER_CLOSED, which can happen if the channel closed since
+            // we checked above.  Since we don't really expect even
+            // ZX_ERR_PEER_CLOSED unless sysmem is having problems, complain
+            // about the error regardless of which error.
+            printf("SysmemConnector::ProcessQueue() DriverConnectorConnect() returned unexpected status: %d\n",
+                    send_connect_status);
+
+            // Regardless of the specific error, we want to try
+            // ConnectToSysmemDriver() again for the _next_ request.
+            driver_connector_client_.reset();
+
+            // We don't retry this request (the window for getting
+            // ZX_ERR_PEER_CLOSED is short due to check above, and exists in any
+            // case due to possibility of close from other end at any time), but
+            // the next request will try ConnectToSysmemDriver() again.
+            //
+            // continue with next request
+        }
+    }
+}
+
+zx_status_t sysmem_connector_init(const char* sysmem_device_path, sysmem_connector_t** out_connector) {
+    SysmemConnector* connector = new SysmemConnector(sysmem_device_path);
+    zx_status_t status = connector->Start();
+    if (status != ZX_OK) {
+        printf("sysmem_connector_init() connector->Start() failed - status: %d\n", status);
+        return status;
+    }
+    *out_connector = connector;
+    return ZX_OK;
+}
+
+void sysmem_connector_queue_connection_request(
+    sysmem_connector_t* connector_param,
+    zx_handle_t allocator2_request_param) {
+    zx::channel allocator2_request(allocator2_request_param);
+    ZX_DEBUG_ASSERT(connector_param);
+    ZX_DEBUG_ASSERT(allocator2_request);
+    SysmemConnector* connector = static_cast<SysmemConnector*>(connector_param);
+    connector->QueueRequest(std::move(allocator2_request));
+}
+
+void sysmem_connector_release(sysmem_connector_t* connector_param) {
+    ZX_DEBUG_ASSERT(connector_param);
+    SysmemConnector* connector = static_cast<SysmemConnector*>(connector_param);
+    connector->Stop();
+    delete connector;
+}
diff --git a/system/ulib/sysmem/rules.mk b/system/ulib/sysmem/rules.mk
index a758f97..22f12e1 100644
--- a/system/ulib/sysmem/rules.mk
+++ b/system/ulib/sysmem/rules.mk
@@ -9,7 +9,7 @@
 MODULE_TYPE := userlib
 
 MODULE_SRCS := \
-    $(LOCAL_DIR)/sysmem.cpp
+    $(LOCAL_DIR)/sysmem.cpp \
 
 MODULE_COMPILEFLAGS += -fvisibility=hidden
 
diff --git a/system/utest/sysmem/sysmem_tests.cpp b/system/utest/sysmem/sysmem_tests.cpp
index ab753d0..df01ab9 100644
--- a/system/utest/sysmem/sysmem_tests.cpp
+++ b/system/utest/sysmem/sysmem_tests.cpp
@@ -47,6 +47,21 @@
     return ZX_OK;
 }
 
+zx_status_t connect_to_sysmem_service(zx::channel* allocator2_client_param) {
+    zx_status_t status;
+
+    zx::channel allocator2_client;
+    zx::channel allocator2_server;
+    status = zx::channel::create(0, &allocator2_client, &allocator2_server);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    status = fdio_service_connect("/svc/fuchsia.sysmem.Allocator2", allocator2_server.release());
+    ASSERT_EQ(status, ZX_OK, "");
+
+    *allocator2_client_param = std::move(allocator2_client);
+    return ZX_OK;
+}
+
 zx_koid_t get_koid(zx_handle_t handle) {
     zx_info_handle_basic_t info;
     zx_status_t status = zx_object_get_info(
@@ -60,8 +75,53 @@
     return info.koid;
 }
 
+zx_status_t verify_connectivity(zx::channel& allocator2_client) {
+    zx_status_t status;
+
+    zx::channel collection_client;
+    zx::channel collection_server;
+    status = zx::channel::create(0, &collection_client, &collection_server);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    status = fuchsia_sysmem_Allocator2AllocateNonSharedCollection(allocator2_client.get(), collection_server.release());
+    ASSERT_EQ(status, ZX_OK, "");
+
+    status = fuchsia_sysmem_BufferCollectionSync(collection_client.get());
+    ASSERT_EQ(status, ZX_OK, "");
+
+    return ZX_OK;
+}
+
 }  // namespace
 
+extern "C" bool test_sysmem_driver_connection(void) {
+    BEGIN_TEST;
+
+    zx_status_t status;
+    zx::channel allocator2_client;
+    status = connect_to_sysmem_driver(&allocator2_client);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    status = verify_connectivity(allocator2_client);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    END_TEST;
+}
+
+extern "C" bool test_sysmem_service_connection(void) {
+    BEGIN_TEST;
+
+    zx_status_t status;
+    zx::channel allocator2_client;
+    status = connect_to_sysmem_service(&allocator2_client);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    status = verify_connectivity(allocator2_client);
+    ASSERT_EQ(status, ZX_OK, "");
+
+    END_TEST;
+}
+
 extern "C" bool test_sysmem_token_one_participant_no_image_constraints(void) {
     BEGIN_TEST;
 
@@ -512,6 +572,8 @@
 }
 
 BEGIN_TEST_CASE(sysmem_tests)
+    RUN_TEST(test_sysmem_driver_connection)
+    RUN_TEST(test_sysmem_service_connection)
     RUN_TEST(test_sysmem_token_one_participant_no_image_constraints)
     RUN_TEST(test_sysmem_token_one_participant_with_image_constraints)
     RUN_TEST(test_sysmem_no_token)