Message ID | 20220309165222.2843651-9-tjmercier@google.com (mailing list archive) |
---|---|
State | New |
Headers | show |
Series | Proposal for a GPU cgroup controller | expand |
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@google.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
On Wed, Mar 9, 2022 at 1:31 PM Shuah Khan <skhan@linuxfoundation.org> wrote: > > 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@google.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? I think this test should be runnable on any architecture, so long as the correct kernel configs (for binder and cgroups) are enabled. I have tested it on arm64 and x86-64. > > 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. Yes, currently it does. Thanks, I will add this check at the location you have pointed out in the TEST function. > > > +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. This would be very unusual, but possible if the child is (accidentally?) killed by a user or the low/out of memory killer. It looks like I can add more information with the WIFSIGNALED and WTERMSIG macros, so I will do that. > > > +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 Thanks for taking a look!
On Wed, Mar 9, 2022 at 8:53 AM T.J. Mercier <tjmercier@google.com> 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@google.com> Reviewed-by: Todd Kjos <tkjos@google.com> for the binder driver interactions. Need Christian to take a look at the binderfs interactions. > --- > .../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 > + > +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); > +} > + > +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); > + > + 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)); > + 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; > + } > + > + 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; > + > + 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"); > +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 > -- > 2.35.1.616.g0bdcbb4464-goog >
On Mon, Mar 14, 2022 at 05:43:40PM -0700, Todd Kjos wrote: > On Wed, Mar 9, 2022 at 8:53 AM T.J. Mercier <tjmercier@google.com> 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@google.com> > > Reviewed-by: Todd Kjos <tkjos@google.com> > for the binder driver interactions. Need Christian to take a look at > the binderfs interactions. Sorry, just saw this now. I'll take a look tomorrow!
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 + +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); +} + +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); + + 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)); + 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; + } + + 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; + + 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"); +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
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@google.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