[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <2194092f-8efa-2416-1b2d-ff7b138b274d@linuxfoundation.org>
Date: Wed, 9 Mar 2022 14:31:09 -0700
From: Shuah Khan <skhan@...uxfoundation.org>
To: "T.J. Mercier" <tjmercier@...gle.com>,
Maarten Lankhorst <maarten.lankhorst@...ux.intel.com>,
Maxime Ripard <mripard@...nel.org>,
Thomas Zimmermann <tzimmermann@...e.de>,
David Airlie <airlied@...ux.ie>,
Daniel Vetter <daniel@...ll.ch>,
Jonathan Corbet <corbet@....net>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Arve Hjønnevåg <arve@...roid.com>,
Todd Kjos <tkjos@...roid.com>,
Martijn Coenen <maco@...roid.com>,
Joel Fernandes <joel@...lfernandes.org>,
Christian Brauner <brauner@...nel.org>,
Hridya Valsaraju <hridya@...gle.com>,
Suren Baghdasaryan <surenb@...gle.com>,
Sumit Semwal <sumit.semwal@...aro.org>,
Christian König <christian.koenig@....com>,
Benjamin Gaignard <benjamin.gaignard@...aro.org>,
Liam Mark <lmark@...eaurora.org>,
Laura Abbott <labbott@...hat.com>,
Brian Starkey <Brian.Starkey@....com>,
John Stultz <john.stultz@...aro.org>,
Tejun Heo <tj@...nel.org>, Zefan Li <lizefan.x@...edance.com>,
Johannes Weiner <hannes@...xchg.org>,
Shuah Khan <shuah@...nel.org>,
Shuah Khan <skhan@...uxfoundation.org>
Cc: kaleshsingh@...gle.com, Kenny.Ho@....com,
dri-devel@...ts.freedesktop.org, linux-doc@...r.kernel.org,
linux-kernel@...r.kernel.org, linux-media@...r.kernel.org,
linaro-mm-sig@...ts.linaro.org, cgroups@...r.kernel.org,
linux-kselftest@...r.kernel.org
Subject: Re: [RFC v3 8/8] selftests: Add binder cgroup gpu memory transfer
test
On 3/9/22 9:52 AM, T.J. Mercier wrote:
> This test verifies that the cgroup GPU memory charge is transferred
> correctly when a dmabuf is passed between processes in two different
> cgroups and the sender specifies BINDER_BUFFER_FLAG_SENDER_NO_NEED in the
> binder transaction data containing the dmabuf file descriptor.
>
> Signed-off-by: T.J. Mercier <tjmercier@...gle.com>
> ---
> .../selftests/drivers/android/binder/Makefile | 8 +
> .../drivers/android/binder/binder_util.c | 254 +++++++++
> .../drivers/android/binder/binder_util.h | 32 ++
> .../selftests/drivers/android/binder/config | 4 +
> .../binder/test_dmabuf_cgroup_transfer.c | 480 ++++++++++++++++++
> 5 files changed, 778 insertions(+)
> create mode 100644 tools/testing/selftests/drivers/android/binder/Makefile
> create mode 100644 tools/testing/selftests/drivers/android/binder/binder_util.c
> create mode 100644 tools/testing/selftests/drivers/android/binder/binder_util.h
> create mode 100644 tools/testing/selftests/drivers/android/binder/config
> create mode 100644 tools/testing/selftests/drivers/android/binder/test_dmabuf_cgroup_transfer.c
>
> diff --git a/tools/testing/selftests/drivers/android/binder/Makefile b/tools/testing/selftests/drivers/android/binder/Makefile
> new file mode 100644
> index 000000000000..726439d10675
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/android/binder/Makefile
> @@ -0,0 +1,8 @@
> +# SPDX-License-Identifier: GPL-2.0
> +CFLAGS += -Wall
> +
Does this test inteded to be built on all architectures? Is arch
check necessary here?
Also does this test require root previleges - I see mount and
unmount operations in the test. If so add root check and skip
if non-root user runs the test.
> +TEST_GEN_PROGS = test_dmabuf_cgroup_transfer
> +
> +include ../../../lib.mk
> +
> +$(OUTPUT)/test_dmabuf_cgroup_transfer: ../../../cgroup/cgroup_util.c binder_util.c
> diff --git a/tools/testing/selftests/drivers/android/binder/binder_util.c b/tools/testing/selftests/drivers/android/binder/binder_util.c
> new file mode 100644
> index 000000000000..c9dcf5b9d42b
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/android/binder/binder_util.c
> @@ -0,0 +1,254 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include "binder_util.h"
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <unistd.h>
> +#include <sys/ioctl.h>
> +#include <sys/mman.h>
> +#include <sys/mount.h>
> +
> +#include <linux/limits.h>
> +#include <linux/android/binder.h>
> +#include <linux/android/binderfs.h>
> +
> +static const size_t BINDER_MMAP_SIZE = 64 * 1024;
> +
> +static void binderfs_unmount(const char *mountpoint)
> +{
> + if (umount2(mountpoint, MNT_DETACH))
> + fprintf(stderr, "Failed to unmount binderfs at %s: %s\n",
> + mountpoint, strerror(errno));
> + else
> + fprintf(stderr, "Binderfs unmounted: %s\n", mountpoint);
> +
> + if (rmdir(mountpoint))
> + fprintf(stderr, "Failed to remove binderfs mount %s: %s\n",
> + mountpoint, strerror(errno));
> + else
> + fprintf(stderr, "Binderfs mountpoint destroyed: %s\n", mountpoint);
Does umount require root previleges? Same commment as above about
non-root user running test.
> +}
> +
> +struct binderfs_ctx create_binderfs(const char *name)
> +{
> + int fd, ret, saved_errno;
> + struct binderfs_device device = { 0 };
> + struct binderfs_ctx ctx = { 0 };
> +
> + /*
> + * P_tmpdir is set to "/tmp/" on Android platforms where Binder is most
> + * commonly used, but this path does not actually exist on Android. We
> + * will first try using "/data/local/tmp" and fallback to P_tmpdir if
> + * that fails for non-Android platforms.
> + */
> + static const char tmpdir[] = "/data/local/tmp";
> + static const size_t MAX_TMPDIR_SIZE =
> + sizeof(tmpdir) > sizeof(P_tmpdir) ?
> + sizeof(tmpdir) : sizeof(P_tmpdir);
> + static const char template[] = "/binderfs_XXXXXX";
> +
> + char *mkdtemp_result;
> + char binderfs_mntpt[MAX_TMPDIR_SIZE + sizeof(template)];
> + char device_path[MAX_TMPDIR_SIZE + sizeof(template) + BINDERFS_MAX_NAME];
> +
> + snprintf(binderfs_mntpt, sizeof(binderfs_mntpt), "%s%s", tmpdir, template);
> +
> + mkdtemp_result = mkdtemp(binderfs_mntpt);
> + if (mkdtemp_result == NULL) {
> + fprintf(stderr, "Failed to create binderfs mountpoint at %s: %s.\n",
> + binderfs_mntpt, strerror(errno));
> + fprintf(stderr, "Trying fallback mountpoint...\n");
> + snprintf(binderfs_mntpt, sizeof(binderfs_mntpt), "%s%s", P_tmpdir, template);
> + if (mkdtemp(binderfs_mntpt) == NULL) {
> + fprintf(stderr, "Failed to create binderfs mountpoint at %s: %s\n",
> + binderfs_mntpt, strerror(errno));
> + return ctx;
> + }
> + }
> + fprintf(stderr, "Binderfs mountpoint created at %s\n", binderfs_mntpt);
Does mount require root previleges? Same commment as above about
non-root user running test.
> +
> + if (mount(NULL, binderfs_mntpt, "binder", 0, 0)) {
> + perror("Could not mount binderfs");
> + rmdir(binderfs_mntpt);
> + return ctx;
> + }
> + fprintf(stderr, "Binderfs mounted at %s\n", binderfs_mntpt);
> +
> + strncpy(device.name, name, sizeof(device.name));
> + snprintf(device_path, sizeof(device_path), "%s/binder-control", binderfs_mntpt);
> + fd = open(device_path, O_RDONLY | O_CLOEXEC);
> + if (!fd) {
> + perror("Failed to open binder-control device");
> + binderfs_unmount(binderfs_mntpt);
> + return ctx;
> + }
> +
> + ret = ioctl(fd, BINDER_CTL_ADD, &device);
> + saved_errno = errno;
> + close(fd);
> + errno = saved_errno;
> + if (ret) {
> + perror("Failed to allocate new binder device");
> + binderfs_unmount(binderfs_mntpt);
> + return ctx;
> + }
> +
> + fprintf(stderr, "Allocated new binder device with major %d, minor %d, and name %s at %s\n",
> + device.major, device.minor, device.name, binderfs_mntpt);
> +
> + ctx.name = strdup(name);
> + ctx.mountpoint = strdup(binderfs_mntpt);
> + return ctx;
> +}
> +
> +void destroy_binderfs(struct binderfs_ctx *ctx)
> +{
> + char path[PATH_MAX];
> +
> + snprintf(path, sizeof(path), "%s/%s", ctx->mountpoint, ctx->name);
> +
> + if (unlink(path))
> + fprintf(stderr, "Failed to unlink binder device %s: %s\n", path, strerror(errno));
> + else
> + fprintf(stderr, "Destroyed binder %s at %s\n", ctx->name, ctx->mountpoint);
> +
> + binderfs_unmount(ctx->mountpoint);
> +
> + free(ctx->name);
> + free(ctx->mountpoint);
> +}
> +
> +struct binder_ctx open_binder(struct binderfs_ctx *bfs_ctx)
> +{
> + struct binder_ctx ctx = {.fd = -1, .memory = NULL};
> + char path[PATH_MAX];
> +
> + snprintf(path, sizeof(path), "%s/%s", bfs_ctx->mountpoint, bfs_ctx->name);
> + ctx.fd = open(path, O_RDWR | O_NONBLOCK | O_CLOEXEC);
> + if (ctx.fd < 0) {
> + fprintf(stderr, "Error opening binder device %s: %s\n", path, strerror(errno));
Does this require root previleges?
> + return ctx;
> + }
> +
> + ctx.memory = mmap(NULL, BINDER_MMAP_SIZE, PROT_READ, MAP_SHARED, ctx.fd, 0);
> + if (ctx.memory == NULL) {
> + perror("Error mapping binder memory");
> + close(ctx.fd);
> + ctx.fd = -1;
> + }
> +
> + return ctx;
> +}
> +
> +void close_binder(struct binder_ctx *ctx)
> +{
> + if (munmap(ctx->memory, BINDER_MMAP_SIZE))
> + perror("Failed to unmap binder memory");
> + ctx->memory = NULL;
> +
> + if (close(ctx->fd))
> + perror("Failed to close binder");
> + ctx->fd = -1;
> +}
> +
> +int become_binder_context_manager(int binder_fd)
> +{
> + return ioctl(binder_fd, BINDER_SET_CONTEXT_MGR, 0);
> +}
> +
> +int do_binder_write_read(int binder_fd, void *writebuf, binder_size_t writesize,
> + void *readbuf, binder_size_t readsize)
> +{
> + int err;
> + struct binder_write_read bwr = {
> + .write_buffer = (binder_uintptr_t)writebuf,
> + .write_size = writesize,
> + .read_buffer = (binder_uintptr_t)readbuf,
> + .read_size = readsize
> + };
> +
> + do {
> + if (ioctl(binder_fd, BINDER_WRITE_READ, &bwr) >= 0)
> + err = 0;
> + else
> + err = -errno;
> + } while (err == -EINTR);
> +
> + if (err < 0) {
> + perror("BINDER_WRITE_READ");
> + return -1;
> + }
> +
> + if (bwr.write_consumed < writesize) {
> + fprintf(stderr, "Binder did not consume full write buffer %llu %llu\n",
> + bwr.write_consumed, writesize);
> + return -1;
> + }
> +
> + return bwr.read_consumed;
> +}
> +
> +static const char *reply_string(int cmd)
> +{
> + switch (cmd) {
> + case BR_ERROR:
> + return("BR_ERROR");
> + case BR_OK:
> + return("BR_OK");
> + case BR_TRANSACTION_SEC_CTX:
> + return("BR_TRANSACTION_SEC_CTX");
> + case BR_TRANSACTION:
> + return("BR_TRANSACTION");
> + case BR_REPLY:
> + return("BR_REPLY");
> + case BR_ACQUIRE_RESULT:
> + return("BR_ACQUIRE_RESULT");
> + case BR_DEAD_REPLY:
> + return("BR_DEAD_REPLY");
> + case BR_TRANSACTION_COMPLETE:
> + return("BR_TRANSACTION_COMPLETE");
> + case BR_INCREFS:
> + return("BR_INCREFS");
> + case BR_ACQUIRE:
> + return("BR_ACQUIRE");
> + case BR_RELEASE:
> + return("BR_RELEASE");
> + case BR_DECREFS:
> + return("BR_DECREFS");
> + case BR_ATTEMPT_ACQUIRE:
> + return("BR_ATTEMPT_ACQUIRE");
> + case BR_NOOP:
> + return("BR_NOOP");
> + case BR_SPAWN_LOOPER:
> + return("BR_SPAWN_LOOPER");
> + case BR_FINISHED:
> + return("BR_FINISHED");
> + case BR_DEAD_BINDER:
> + return("BR_DEAD_BINDER");
> + case BR_CLEAR_DEATH_NOTIFICATION_DONE:
> + return("BR_CLEAR_DEATH_NOTIFICATION_DONE");
> + case BR_FAILED_REPLY:
> + return("BR_FAILED_REPLY");
> + case BR_FROZEN_REPLY:
> + return("BR_FROZEN_REPLY");
> + case BR_ONEWAY_SPAM_SUSPECT:
> + return("BR_ONEWAY_SPAM_SUSPECT");
> + default:
> + return("Unknown");
> + };
> +}
> +
> +int expect_binder_reply(int32_t actual, int32_t expected)
> +{
> + if (actual != expected) {
> + fprintf(stderr, "Expected %s but received %s\n",
> + reply_string(expected), reply_string(actual));
> + return -1;
> + }
> + return 0;
> +}
> +
> diff --git a/tools/testing/selftests/drivers/android/binder/binder_util.h b/tools/testing/selftests/drivers/android/binder/binder_util.h
> new file mode 100644
> index 000000000000..807f5abe987e
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/android/binder/binder_util.h
> @@ -0,0 +1,32 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +
> +#ifndef SELFTEST_BINDER_UTIL_H
> +#define SELFTEST_BINDER_UTIL_H
> +
> +#include <stdint.h>
> +
> +#include <linux/android/binder.h>
> +
> +struct binderfs_ctx {
> + char *name;
> + char *mountpoint;
> +};
> +
> +struct binder_ctx {
> + int fd;
> + void *memory;
> +};
> +
> +struct binderfs_ctx create_binderfs(const char *name);
> +void destroy_binderfs(struct binderfs_ctx *ctx);
> +
> +struct binder_ctx open_binder(struct binderfs_ctx *bfs_ctx);
> +void close_binder(struct binder_ctx *ctx);
> +
> +int become_binder_context_manager(int binder_fd);
> +
> +int do_binder_write_read(int binder_fd, void *writebuf, binder_size_t writesize,
> + void *readbuf, binder_size_t readsize);
> +
> +int expect_binder_reply(int32_t actual, int32_t expected);
> +#endif
> diff --git a/tools/testing/selftests/drivers/android/binder/config b/tools/testing/selftests/drivers/android/binder/config
> new file mode 100644
> index 000000000000..fcc5f8f693b3
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/android/binder/config
> @@ -0,0 +1,4 @@
> +CONFIG_CGROUP_GPU=y
> +CONFIG_ANDROID=y
> +CONFIG_ANDROID_BINDERFS=y
> +CONFIG_ANDROID_BINDER_IPC=y
> diff --git a/tools/testing/selftests/drivers/android/binder/test_dmabuf_cgroup_transfer.c b/tools/testing/selftests/drivers/android/binder/test_dmabuf_cgroup_transfer.c
> new file mode 100644
> index 000000000000..9b952ab401cc
> --- /dev/null
> +++ b/tools/testing/selftests/drivers/android/binder/test_dmabuf_cgroup_transfer.c
> @@ -0,0 +1,480 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +/*
> + * This test verifies that the cgroup GPU memory charge is transferred correctly
> + * when a dmabuf is passed between processes in two different cgroups and the
> + * sender specifies BINDER_BUFFER_FLAG_SENDER_NO_NEED in the binder transaction
> + * data containing the dmabuf file descriptor.
> + *
> + * The gpu_cgroup_dmabuf_transfer test function becomes the binder context
> + * manager, then forks a child who initiates a transaction with the context
> + * manager by specifying a target of 0. The context manager reply contains a
> + * dmabuf file descriptor which was allocated by the gpu_cgroup_dmabuf_transfer
> + * test function, but should be charged to the child cgroup after the binder
> + * transaction.
> + */
> +
> +#include <errno.h>
> +#include <fcntl.h>
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <sys/epoll.h>
> +#include <sys/ioctl.h>
> +#include <sys/types.h>
> +#include <sys/wait.h>
> +
> +#include "binder_util.h"
> +#include "../../../cgroup/cgroup_util.h"
> +#include "../../../kselftest.h"
> +#include "../../../kselftest_harness.h"
> +
> +#include <linux/limits.h>
> +#include <linux/dma-heap.h>
> +#include <linux/android/binder.h>
> +
> +#define UNUSED(x) ((void)(x))
> +
> +static const unsigned int BINDER_CODE = 8675309; /* Any number will work here */
> +
> +struct cgroup_ctx {
> + char *root;
> + char *source;
> + char *dest;
> +};
> +
> +void destroy_cgroups(struct __test_metadata *_metadata, struct cgroup_ctx *ctx)
> +{
> + if (ctx->source != NULL) {
> + TH_LOG("Destroying cgroup: %s", ctx->source);
> + rmdir(ctx->source);
> + free(ctx->source);
> + }
> +
> + if (ctx->dest != NULL) {
> + TH_LOG("Destroying cgroup: %s", ctx->dest);
> + rmdir(ctx->dest);
> + free(ctx->dest);
> + }
> +
> + free(ctx->root);
> + ctx->root = ctx->source = ctx->dest = NULL;
> +}
> +
> +struct cgroup_ctx create_cgroups(struct __test_metadata *_metadata)
> +{
> + struct cgroup_ctx ctx = {0};
> + char root[PATH_MAX], *tmp;
> + static const char template[] = "/gpucg_XXXXXX";
> +
> + if (cg_find_unified_root(root, sizeof(root))) {
> + TH_LOG("Could not find cgroups root");
> + return ctx;
> + }
> +
> + if (cg_read_strstr(root, "cgroup.controllers", "gpu")) {
> + TH_LOG("Could not find GPU controller");
> + return ctx;
> + }
> +
> + if (cg_write(root, "cgroup.subtree_control", "+gpu")) {
> + TH_LOG("Could not enable GPU controller");
> + return ctx;
> + }
> +
> + ctx.root = strdup(root);
> +
> + snprintf(root, sizeof(root), "%s/%s", ctx.root, template);
> + tmp = mkdtemp(root);
> + if (tmp == NULL) {
> + TH_LOG("%s - Could not create source cgroup", strerror(errno));
> + destroy_cgroups(_metadata, &ctx);
> + return ctx;
> + }
> + ctx.source = strdup(tmp);
> +
> + snprintf(root, sizeof(root), "%s/%s", ctx.root, template);
> + tmp = mkdtemp(root);
> + if (tmp == NULL) {
> + TH_LOG("%s - Could not create destination cgroup", strerror(errno));
> + destroy_cgroups(_metadata, &ctx);
> + return ctx;
> + }
> + ctx.dest = strdup(tmp);
> +
> + TH_LOG("Created cgroups: %s %s", ctx.source, ctx.dest);
> +
> + return ctx;
> +}
> +
> +int dmabuf_heap_alloc(int fd, size_t len, int *dmabuf_fd)
> +{
> + struct dma_heap_allocation_data data = {
> + .len = len,
> + .fd = 0,
> + .fd_flags = O_RDONLY | O_CLOEXEC,
> + .heap_flags = 0,
> + };
> + int ret;
> +
> + if (!dmabuf_fd)
> + return -EINVAL;
> +
> + ret = ioctl(fd, DMA_HEAP_IOCTL_ALLOC, &data);
> + if (ret < 0)
> + return ret;
> + *dmabuf_fd = (int)data.fd;
> + return ret;
> +}
> +
> +/* The system heap is known to export dmabufs with support for cgroup tracking */
> +int alloc_dmabuf_from_system_heap(struct __test_metadata *_metadata, size_t bytes)
> +{
> + int heap_fd = -1, dmabuf_fd = -1;
> + static const char * const heap_path = "/dev/dma_heap/system";
> +
> + heap_fd = open(heap_path, O_RDONLY);
> + if (heap_fd < 0) {
> + TH_LOG("%s - open %s failed!\n", strerror(errno), heap_path);
> + return -1;
> + }
Same question about root preveliges?
> +
> + if (dmabuf_heap_alloc(heap_fd, bytes, &dmabuf_fd))
> + TH_LOG("dmabuf allocation failed! - %s", strerror(errno));
> + close(heap_fd);
> +
> + return dmabuf_fd;
> +}
> +
> +int binder_request_dmabuf(int binder_fd)
> +{
> + int ret;
> +
> + /*
> + * We just send an empty binder_buffer_object to initiate a transaction
> + * with the context manager, who should respond with a single dmabuf
> + * inside a binder_fd_array_object.
> + */
> +
> + struct binder_buffer_object bbo = {
> + .hdr.type = BINDER_TYPE_PTR,
> + .flags = 0,
> + .buffer = 0,
> + .length = 0,
> + .parent = 0, /* No parent */
> + .parent_offset = 0 /* No parent */
> + };
> +
> + binder_size_t offsets[] = {0};
> +
> + struct {
> + int32_t cmd;
> + struct binder_transaction_data btd;
> + } __attribute__((packed)) bc = {
> + .cmd = BC_TRANSACTION,
> + .btd = {
> + .target = { 0 },
> + .cookie = 0,
> + .code = BINDER_CODE,
> + .flags = TF_ACCEPT_FDS, /* We expect a FDA in the reply */
> + .data_size = sizeof(bbo),
> + .offsets_size = sizeof(offsets),
> + .data.ptr = {
> + (binder_uintptr_t)&bbo,
> + (binder_uintptr_t)offsets
> + }
> + },
> + };
> +
> + struct {
> + int32_t reply_noop;
> + } __attribute__((packed)) br;
> +
> + ret = do_binder_write_read(binder_fd, &bc, sizeof(bc), &br, sizeof(br));
> + if (ret >= sizeof(br) && expect_binder_reply(br.reply_noop, BR_NOOP)) {
> + return -1;
> + } else if (ret < sizeof(br)) {
> + fprintf(stderr, "Not enough bytes in binder reply %d\n", ret);
> + return -1;
> + }
> + return 0;
> +}
> +
> +int send_dmabuf_reply(int binder_fd, struct binder_transaction_data *tr, int dmabuf_fd)
> +{
> + int ret;
> + /*
> + * The trailing 0 is to achieve the necessary alignment for the binder
> + * buffer_size.
> + */
> + int fdarray[] = { dmabuf_fd, 0 };
> +
> + struct binder_buffer_object bbo = {
> + .hdr.type = BINDER_TYPE_PTR,
> + .flags = BINDER_BUFFER_FLAG_SENDER_NO_NEED,
> + .buffer = (binder_uintptr_t)fdarray,
> + .length = sizeof(fdarray),
> + .parent = 0, /* No parent */
> + .parent_offset = 0 /* No parent */
> + };
> +
> + struct binder_fd_array_object bfdao = {
> + .hdr.type = BINDER_TYPE_FDA,
> + .num_fds = 1,
> + .parent = 0, /* The binder_buffer_object */
> + .parent_offset = 0 /* FDs follow immediately */
> + };
> +
> + uint64_t sz = sizeof(fdarray);
> + uint8_t data[sizeof(sz) + sizeof(bbo) + sizeof(bfdao)];
> + binder_size_t offsets[] = {sizeof(sz), sizeof(sz)+sizeof(bbo)};
> +
> + memcpy(data, &sz, sizeof(sz));
> + memcpy(data + sizeof(sz), &bbo, sizeof(bbo));
> + memcpy(data + sizeof(sz) + sizeof(bbo), &bfdao, sizeof(bfdao));
> +
> + struct {
> + int32_t cmd;
> + struct binder_transaction_data_sg btd;
> + } __attribute__((packed)) bc = {
> + .cmd = BC_REPLY_SG,
> + .btd.transaction_data = {
> + .target = { tr->target.handle },
> + .cookie = tr->cookie,
> + .code = BINDER_CODE,
> + .flags = 0,
> + .data_size = sizeof(data),
> + .offsets_size = sizeof(offsets),
> + .data.ptr = {
> + (binder_uintptr_t)data,
> + (binder_uintptr_t)offsets
> + }
> + },
> + .btd.buffers_size = sizeof(fdarray)
> + };
> +
> + struct {
> + int32_t reply_noop;
> + } __attribute__((packed)) br;
> +
> + ret = do_binder_write_read(binder_fd, &bc, sizeof(bc), &br, sizeof(br));
> + if (ret >= sizeof(br) && expect_binder_reply(br.reply_noop, BR_NOOP)) {
> + return -1;
> + } else if (ret < sizeof(br)) {
> + fprintf(stderr, "Not enough bytes in binder reply %d\n", ret);
> + return -1;
> + }
> + return 0;
> +}
> +
> +struct binder_transaction_data *binder_wait_for_transaction(int binder_fd,
> + uint32_t *readbuf,
> + size_t readsize)
> +{
> + static const int MAX_EVENTS = 1, EPOLL_WAIT_TIME_MS = 3 * 1000;
> + struct binder_reply {
> + int32_t reply0;
> + int32_t reply1;
> + struct binder_transaction_data btd;
> + } *br;
> + struct binder_transaction_data *ret = NULL;
> + struct epoll_event events[MAX_EVENTS];
> + int epoll_fd, num_events, readcount;
> + uint32_t bc[] = { BC_ENTER_LOOPER };
> +
> + do_binder_write_read(binder_fd, &bc, sizeof(bc), NULL, 0);
> +
> + epoll_fd = epoll_create1(EPOLL_CLOEXEC);
> + if (epoll_fd == -1) {
> + perror("epoll_create");
> + return NULL;
> + }
> +
> + events[0].events = EPOLLIN;
> + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, binder_fd, &events[0])) {
> + perror("epoll_ctl add");
> + goto err_close;
> + }
> +
> + num_events = epoll_wait(epoll_fd, events, MAX_EVENTS, EPOLL_WAIT_TIME_MS);
> + if (num_events < 0) {
> + perror("epoll_wait");
> + goto err_ctl;
> + } else if (num_events == 0) {
> + fprintf(stderr, "No events\n");
> + goto err_ctl;
> + }
> +
> + readcount = do_binder_write_read(binder_fd, NULL, 0, readbuf, readsize);
> + fprintf(stderr, "Read %d bytes from binder\n", readcount);
> +
> + if (readcount < (int)sizeof(struct binder_reply)) {
> + fprintf(stderr, "read_consumed not large enough\n");
> + goto err_ctl;
> + }
> +
> + br = (struct binder_reply *)readbuf;
> + if (expect_binder_reply(br->reply0, BR_NOOP))
> + goto err_ctl;
> +
> + if (br->reply1 == BR_TRANSACTION) {
> + if (br->btd.code == BINDER_CODE)
> + ret = &br->btd;
> + else
> + fprintf(stderr, "Received transaction with unexpected code: %u\n",
> + br->btd.code);
> + } else {
> + expect_binder_reply(br->reply1, BR_TRANSACTION_COMPLETE);
> + }
> +
> +err_ctl:
> + if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, binder_fd, NULL))
> + perror("epoll_ctl del");
> +err_close:
> + close(epoll_fd);
> + return ret;
> +}
> +
> +static int child_request_dmabuf_transfer(const char *cgroup, void *arg)
> +{
> + UNUSED(cgroup);
> + int ret = -1;
> + uint32_t readbuf[32];
> + struct binderfs_ctx bfs_ctx = *(struct binderfs_ctx *)arg;
> + struct binder_ctx b_ctx;
> +
> + fprintf(stderr, "Child PID: %d\n", getpid());
> +
> + b_ctx = open_binder(&bfs_ctx);
> + if (b_ctx.fd < 0) {
> + fprintf(stderr, "Child unable to open binder\n");
> + return -1;
> + }
> +
> + if (binder_request_dmabuf(b_ctx.fd))
> + goto err;
> +
> + /* The child must stay alive until the binder reply is received */
> + if (binder_wait_for_transaction(b_ctx.fd, readbuf, sizeof(readbuf)) == NULL)
> + ret = 0;
> +
> + /*
> + * We don't close the received dmabuf here so that the parent can
> + * inspect the cgroup gpu memory charges to verify the charge transfer
> + * completed successfully.
> + */
> +err:
> + close_binder(&b_ctx);
> + fprintf(stderr, "Child done\n");
> + return ret;
> +}
> +
> +TEST(gpu_cgroup_dmabuf_transfer)
> +{
> + static const char * const GPUMEM_FILENAME = "gpu.memory.current";
> + static const size_t ONE_MiB = 1024 * 1024;
> +
> + int ret, dmabuf_fd;
> + uint32_t readbuf[32];
> + long memsize;
> + pid_t child_pid;
> + struct binderfs_ctx bfs_ctx;
> + struct binder_ctx b_ctx;
> + struct cgroup_ctx cg_ctx;
> + struct binder_transaction_data *tr;
> + struct flat_binder_object *fbo;
> + struct binder_buffer_object *bbo;
> +
If root previges is necessary - pls add check here and skip.
> + bfs_ctx = create_binderfs("testbinder");
> + if (bfs_ctx.name == NULL)
> + ksft_exit_skip("The Android binderfs filesystem is not available\n");
> +
> + cg_ctx = create_cgroups(_metadata);
> + if (cg_ctx.root == NULL) {
> + destroy_binderfs(&bfs_ctx);
> + ksft_exit_skip("cgroup v2 isn't mounted\n");
> + }
> +
> + ASSERT_EQ(cg_enter_current(cg_ctx.source), 0) {
> + TH_LOG("Could not move parent to cgroup: %s", cg_ctx.source);
> + goto err_cg;
> + }
> +
> + dmabuf_fd = alloc_dmabuf_from_system_heap(_metadata, ONE_MiB);
> + ASSERT_GE(dmabuf_fd, 0) {
> + goto err_cg;
> + }
> + TH_LOG("Allocated dmabuf");
> +
> + memsize = cg_read_key_long(cg_ctx.source, GPUMEM_FILENAME, "system");
> + ASSERT_EQ(memsize, ONE_MiB) {
> + TH_LOG("GPU memory used after allocation: %ld but it should be %lu",
> + memsize, (unsigned long)ONE_MiB);
> + goto err_dmabuf;
> + }
> +
> + b_ctx = open_binder(&bfs_ctx);
> + ASSERT_GE(b_ctx.fd, 0) {
> + TH_LOG("Parent unable to open binder");
> + goto err_dmabuf;
> + }
> + TH_LOG("Opened binder at %s/%s", bfs_ctx.mountpoint, bfs_ctx.name);
> +
> + ASSERT_EQ(become_binder_context_manager(b_ctx.fd), 0) {
> + TH_LOG("Cannot become context manager: %s", strerror(errno));
> + goto err_binder;
> + }
> +
> + child_pid = cg_run_nowait(cg_ctx.dest, child_request_dmabuf_transfer, &bfs_ctx);
> + ASSERT_GT(child_pid, 0) {
> + TH_LOG("Error forking: %s", strerror(errno));
> + goto err_binder;
> + }
> +
> + tr = binder_wait_for_transaction(b_ctx.fd, readbuf, sizeof(readbuf));
> + ASSERT_NE(tr, NULL) {
> + TH_LOG("Error receiving transaction request from child");
> + goto err_child;
> + }
> + fbo = (struct flat_binder_object *)tr->data.ptr.buffer;
> + ASSERT_EQ(fbo->hdr.type, BINDER_TYPE_PTR) {
> + TH_LOG("Did not receive a buffer object from child");
> + goto err_child;
> + }
> + bbo = (struct binder_buffer_object *)fbo;
> + ASSERT_EQ(bbo->length, 0) {
> + TH_LOG("Did not receive an empty buffer object from child");
> + goto err_child;
> + }
> +
> + TH_LOG("Received transaction from child");
> + send_dmabuf_reply(b_ctx.fd, tr, dmabuf_fd);
> +
> + ASSERT_EQ(cg_read_key_long(cg_ctx.dest, GPUMEM_FILENAME, "system"), ONE_MiB) {
> + TH_LOG("Destination cgroup does not have system charge!");
> + goto err_child;
> + }
> + ASSERT_EQ(cg_read_key_long(cg_ctx.source, GPUMEM_FILENAME, "system"), 0) {
> + TH_LOG("Source cgroup still has system charge!");
> + goto err_child;
> + }
> + TH_LOG("Charge transfer succeeded!");
> +
> +err_child:
> + waitpid(child_pid, &ret, 0);
> + if (WIFEXITED(ret))
> + TH_LOG("Child %d terminated with %d", child_pid, WEXITSTATUS(ret));
> + else
> + TH_LOG("Child terminated abnormally");
What does this mean? What are the conditions that could cause this?
Pls include more info. in the message.
> +err_binder:
> + close_binder(&b_ctx);
> +err_dmabuf:
> + close(dmabuf_fd);
> +err_cg:
> + destroy_cgroups(_metadata, &cg_ctx);
> + destroy_binderfs(&bfs_ctx);
> +}
> +
> +TEST_HARNESS_MAIN
>
thanks,
-- Shuah
Powered by blists - more mailing lists