Release shared memory to Linux when done

After dropping a memory object loaned to us by Linux, tell Linux to
attempt a reclaim() on it.

Bug: 117221195
Test: tipc-test -t send-fd; with logging on, see pages decremented
Change-Id: I2d9a34cc9aa2e027ba417f095f12ae0bacf92789
diff --git a/lib/trusty/include/lib/trusty/memref.h b/lib/trusty/include/lib/trusty/memref.h
index 519b605..3c221b2 100644
--- a/lib/trusty/include/lib/trusty/memref.h
+++ b/lib/trusty/include/lib/trusty/memref.h
@@ -74,3 +74,15 @@
                                    size_t size,
                                    uint32_t mmap_prot,
                                    struct handle** handle);
+
+/**
+ * memref_handle_to_vmm_obj() - Get the vmm_obj for enclosing memref
+ * @handle: Handle to extract from
+ *
+ * Checks a handle to see if it is backed by a memref, and returns the
+ * vmm_obj if it is.
+ *
+ * Return: Pointer to the vmm_obj for the enclosing memref if it is a memref,
+ *         NULL otherwise.
+ */
+struct vmm_obj* memref_handle_to_vmm_obj(struct handle* handle);
diff --git a/lib/trusty/memref.c b/lib/trusty/memref.c
index dd58fc1..9036b3b 100644
--- a/lib/trusty/memref.c
+++ b/lib/trusty/memref.c
@@ -77,6 +77,7 @@
 
 /* This is only safe to call when the handle is destroyed */
 static void memref_destroy(struct memref* memref) {
+    LTRACEF("dropping memref\n");
     vmm_obj_slice_release(&memref->slice);
     free(memref);
 }
@@ -265,3 +266,15 @@
     handle_decref(&memref->handle);
     return rc;
 }
+
+static bool handle_is_memref(struct handle* handle) {
+    return handle->ops == &memref_handle_ops;
+}
+
+struct vmm_obj* memref_handle_to_vmm_obj(struct handle* handle) {
+    if (handle_is_memref(handle)) {
+        return containerof(handle, struct memref, handle)->slice.obj;
+    } else {
+        return NULL;
+    }
+}
diff --git a/lib/trusty/tipc_virtio_dev.c b/lib/trusty/tipc_virtio_dev.c
index 6c7455b..e8ad974 100644
--- a/lib/trusty/tipc_virtio_dev.c
+++ b/lib/trusty/tipc_virtio_dev.c
@@ -34,6 +34,7 @@
 
 #include <kernel/vm.h>
 #include <lk/init.h>
+#include <lk/reflist.h>
 
 #include <virtio/virtio_config.h>
 #include <virtio/virtio_ring.h>
@@ -122,6 +123,7 @@
     TIPC_CTRL_MSGTYPE_CONN_REQ,
     TIPC_CTRL_MSGTYPE_CONN_RSP,
     TIPC_CTRL_MSGTYPE_DISC_REQ,
+    TIPC_CTRL_MSGTYPE_RELEASE,
 };
 
 /*
@@ -156,8 +158,80 @@
     uint32_t target;
 } __PACKED;
 
+struct tipc_release_body {
+    uint64_t id;
+} __PACKED;
+
 typedef int (*tipc_data_cb_t)(uint8_t* dst, size_t sz, void* ctx);
 
+struct tipc_ext_mem {
+    struct vmm_obj vmm_obj;
+    struct vmm_obj* ext_mem;
+    struct obj_ref ext_mem_ref;
+    struct tipc_dev* dev;
+    bool suppress_release;
+};
+
+static int tipc_ext_mem_check_flags(struct vmm_obj* obj, uint* arch_mmu_flags) {
+    struct tipc_ext_mem* tem = containerof(obj, struct tipc_ext_mem, vmm_obj);
+    return tem->ext_mem->ops->check_flags(tem->ext_mem, arch_mmu_flags);
+}
+
+int tipc_ext_mem_get_page(struct vmm_obj* obj,
+                          size_t offset,
+                          paddr_t* paddr,
+                          size_t* paddr_size) {
+    struct tipc_ext_mem* tem = containerof(obj, struct tipc_ext_mem, vmm_obj);
+    return tem->ext_mem->ops->get_page(tem->ext_mem, offset, paddr, paddr_size);
+}
+
+status_t release_shm(struct tipc_dev* dev, uint64_t shm_id);
+
+void tipc_ext_mem_destroy(struct vmm_obj* obj) {
+    struct tipc_ext_mem* tem = containerof(obj, struct tipc_ext_mem, vmm_obj);
+    struct ext_mem_obj* ext_mem =
+            containerof(tem->ext_mem, struct ext_mem_obj, vmm_obj);
+    /* Save the ext_mem ID as we're about to drop a reference to it */
+    ext_mem_obj_id_t ext_mem_id = ext_mem->id;
+    vmm_obj_del_ref(tem->ext_mem, &tem->ext_mem_ref);
+    /* In case this was the last reference, tell NS to try reclaiming */
+    if (!tem->suppress_release) {
+        if (release_shm(tem->dev, ext_mem_id) != NO_ERROR) {
+            TRACEF("Failed to release external memory: 0x%llx\n", ext_mem_id);
+        }
+    }
+    free(tem);
+}
+
+static struct vmm_obj_ops tipc_ext_mem_ops = {
+        .check_flags = tipc_ext_mem_check_flags,
+        .get_page = tipc_ext_mem_get_page,
+        .destroy = tipc_ext_mem_destroy,
+};
+
+static bool vmm_obj_is_tipc_ext_mem(struct vmm_obj* obj) {
+    return obj->ops == &tipc_ext_mem_ops;
+}
+
+static struct tipc_ext_mem* vmm_obj_to_tipc_ext_mem(struct vmm_obj* obj) {
+    if (vmm_obj_is_tipc_ext_mem(obj)) {
+        return containerof(obj, struct tipc_ext_mem, vmm_obj);
+    } else {
+        return NULL;
+    }
+}
+
+static void tipc_ext_mem_initialize(struct tipc_ext_mem* tem,
+                                    struct tipc_dev* dev,
+                                    struct vmm_obj* ext_mem,
+                                    struct obj_ref* ref) {
+    tem->dev = dev;
+    tem->ext_mem = ext_mem;
+    vmm_obj_add_ref(tem->ext_mem, &tem->ext_mem_ref);
+    tem->vmm_obj.ops = &tipc_ext_mem_ops;
+    obj_init(&tem->vmm_obj.obj, ref);
+}
+
 static int tipc_send_data(struct tipc_dev* dev,
                           uint32_t local,
                           uint32_t remote,
@@ -435,6 +509,18 @@
     return ERR_NOT_VALID;
 }
 
+/*
+ * Sets the suppression flag on a memref handle backed by a vmm_obj of a
+ * tipc_ext_mem.
+ */
+static void suppress_handle(struct handle* handle) {
+    struct vmm_obj* obj = memref_handle_to_vmm_obj(handle);
+    ASSERT(obj);
+    struct tipc_ext_mem* tem = vmm_obj_to_tipc_ext_mem(obj);
+    ASSERT(tem);
+    tem->suppress_release = true;
+}
+
 static int handle_chan_msg(struct tipc_dev* dev,
                            uint32_t remote,
                            uint32_t local,
@@ -460,7 +546,7 @@
             .num_handles = shm_cnt,
     };
 
-    LTRACEF("shm_cnt=%zu\n", shm_cnt);
+    LTRACEF("len=%zu, shm_cnt=%zu\n", len, shm_cnt);
 
     if (shm_cnt > MAX_MSG_HANDLES) {
         return ERR_INVALID_ARGS;
@@ -469,38 +555,55 @@
     for (shm_idx = 0; shm_idx < shm_cnt; shm_idx++) {
         struct vmm_obj* shm_obj;
         struct obj_ref shm_ref;
+        struct tipc_ext_mem* tem;
+        struct obj_ref tem_ref;
         /*
          * Read out separately to prevent it from changing between the two calls
          */
         uint64_t size64 = READ_ONCE(shm[shm_idx].size);
         if (size64 > SIZE_MAX) {
             TRACEF("Received shm object larger than SIZE_MAX\n");
-            goto release_shm;
+            goto out;
         }
         size_t size = size64;
 
+        obj_ref_init(&shm_ref);
+        obj_ref_init(&tem_ref);
+
         status_t ret =
                 ext_mem_get_vmm_obj(dev->vd.client_id, shm[shm_idx].obj_id,
                                     size, &shm_obj, &shm_ref);
         if (ret < 0) {
             LTRACEF("Failed to create ext_mem object\n");
-            goto release_shm;
+            goto out;
         }
 
-        ret = memref_create_from_vmm_obj(
-                shm_obj, 0, size, MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE,
-                &handles[shm_idx]);
+        tem = calloc(1, sizeof(struct tipc_ext_mem));
+        if (!tem) {
+            ret = ERR_NO_MEMORY;
+            goto out;
+        }
 
+        tipc_ext_mem_initialize(tem, dev, shm_obj, &tem_ref);
+        vmm_obj_del_ref(shm_obj, &shm_ref);
+        shm_obj = NULL;
+
+        ret = memref_create_from_vmm_obj(
+                &tem->vmm_obj, 0, size,
+                MMAP_FLAG_PROT_READ | MMAP_FLAG_PROT_WRITE, &handles[shm_idx]);
+        if (ret != NO_ERROR) {
+            tem->suppress_release = true;
+        }
         /*
          * We want to release our local ref whether or not we made a handle
          * successfully. If we made a handle, the handle's ref keeps it alive.
          * If we didn't, we want it cleaned up.
          */
-        vmm_obj_del_ref(&shm->vmm_obj, &shm_ref);
+        vmm_obj_del_ref(&tem->vmm_obj, &tem_ref);
 
         if (ret < 0) {
             TRACEF("Failed to create memref\n");
-            goto release_shm;
+            goto out;
         }
     }
 
@@ -513,11 +616,17 @@
     }
     mutex_release(&dev->ept_lock);
 
-release_shm:
+out:
+    /* Tear down the successfully processed handles */
     while (shm_idx > 0) {
         shm_idx--;
+        if (ret != NO_ERROR) {
+            LTRACEF("Suppressing handle release\n");
+            suppress_handle(handles[shm_idx]);
+        }
         handle_decref(handles[shm_idx]);
     }
+
     return ret;
 }
 
@@ -1241,3 +1350,20 @@
     free(dev);
     return ret;
 }
+
+status_t release_shm(struct tipc_dev* dev, uint64_t shm_id) {
+    struct {
+        struct tipc_ctrl_msg_hdr hdr;
+        struct tipc_release_body body;
+    } msg;
+
+    msg.hdr.type = TIPC_CTRL_MSGTYPE_RELEASE;
+    msg.hdr.body_len = sizeof(struct tipc_release_body);
+
+    msg.body.id = shm_id;
+
+    LTRACEF("release shm %llu\n", shm_id);
+
+    return tipc_send_buf(dev, TIPC_CTRL_ADDR, TIPC_CTRL_ADDR, &msg, sizeof(msg),
+                         true);
+}