[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <CAFuZdDLZnnk4pE0MRxzrvTQMbvGUEuzT=9oqSdPPwxVAx9ySng@mail.gmail.com>
Date: Mon, 14 Jul 2025 21:52:25 -0700
From: Carlos Llamas <cmllamas@...gle.com>
To: Tiffany Yang <ynaffit@...gle.com>
Cc: linux-kernel@...r.kernel.org, keescook@...gle.com, kernel-team@...roid.com,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>, Arve Hjønnevåg <arve@...roid.com>,
Todd Kjos <tkjos@...roid.com>, Martijn Coenen <maco@...roid.com>,
Joel Fernandes <joelagnelf@...dia.com>, Christian Brauner <brauner@...nel.org>,
Suren Baghdasaryan <surenb@...gle.com>, Brendan Higgins <brendan.higgins@...ux.dev>,
David Gow <davidgow@...gle.com>, Rae Moar <rmoar@...gle.com>, linux-kselftest@...r.kernel.org,
kunit-dev@...glegroups.com
Subject: Re: [PATCH v3 5/6] binder: Convert binder_alloc selftests to KUnit
On Mon, Jul 14, 2025 at 11:53:18AM -0700, Tiffany Yang wrote:
> Convert the existing binder_alloc_selftest tests into KUnit tests. These
> tests allocate and free an exhaustive combination of buffers with
> various sizes and alignments. This change allows them to be run without
> blocking or otherwise interfering with other processes in binder.
>
> This test is refactored into more meaningful cases in the subsequent
> patch.
>
> Signed-off-by: Tiffany Yang <ynaffit@...gle.com>
> ---
> v2:
> * Fix build warning Reported-by: kernel test robot <lkp@...el.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202506281837.hReNHJjO-lkp@intel.com/
> ---
> drivers/android/Kconfig | 10 -
> drivers/android/Makefile | 1 -
> drivers/android/binder.c | 5 -
> drivers/android/binder_alloc.c | 3 +
> drivers/android/binder_alloc.h | 5 -
> drivers/android/binder_alloc_selftest.c | 345 ---------------------
> drivers/android/tests/binder_alloc_kunit.c | 279 +++++++++++++++++
> 7 files changed, 282 insertions(+), 366 deletions(-)
> delete mode 100644 drivers/android/binder_alloc_selftest.c
>
> diff --git a/drivers/android/Kconfig b/drivers/android/Kconfig
> index b1bc7183366c..5b3b8041f827 100644
> --- a/drivers/android/Kconfig
> +++ b/drivers/android/Kconfig
> @@ -37,16 +37,6 @@ config ANDROID_BINDER_DEVICES
> created. Each binder device has its own context manager, and is
> therefore logically separated from the other devices.
>
> -config ANDROID_BINDER_IPC_SELFTEST
> - bool "Android Binder IPC Driver Selftest"
> - depends on ANDROID_BINDER_IPC
> - help
> - This feature allows binder selftest to run.
> -
> - Binder selftest checks the allocation and free of binder buffers
> - exhaustively with combinations of various buffer sizes and
> - alignments.
> -
> config ANDROID_BINDER_ALLOC_KUNIT_TEST
> tristate "KUnit Tests for Android Binder Alloc" if !KUNIT_ALL_TESTS
> depends on ANDROID_BINDER_IPC && KUNIT
> diff --git a/drivers/android/Makefile b/drivers/android/Makefile
> index 74d02a335d4e..c5d47be0276c 100644
> --- a/drivers/android/Makefile
> +++ b/drivers/android/Makefile
> @@ -3,5 +3,4 @@ ccflags-y += -I$(src) # needed for trace events
>
> obj-$(CONFIG_ANDROID_BINDERFS) += binderfs.o
> obj-$(CONFIG_ANDROID_BINDER_IPC) += binder.o binder_alloc.o
> -obj-$(CONFIG_ANDROID_BINDER_IPC_SELFTEST) += binder_alloc_selftest.o
> obj-$(CONFIG_ANDROID_BINDER_ALLOC_KUNIT_TEST) += tests/
> diff --git a/drivers/android/binder.c b/drivers/android/binder.c
> index 9dfe90c284fc..7b2653a5d59c 100644
> --- a/drivers/android/binder.c
> +++ b/drivers/android/binder.c
> @@ -5718,11 +5718,6 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
> struct binder_thread *thread;
> void __user *ubuf = (void __user *)arg;
>
> - /*pr_info("binder_ioctl: %d:%d %x %lx\n",
> - proc->pid, current->pid, cmd, arg);*/
Thanks, this had been bothering me for a while.
> -
> - binder_selftest_alloc(&proc->alloc);
> -
> trace_binder_ioctl(cmd, arg);
>
> ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
> diff --git a/drivers/android/binder_alloc.c b/drivers/android/binder_alloc.c
> index c79e5c6721f0..74a184014fa7 100644
> --- a/drivers/android/binder_alloc.c
> +++ b/drivers/android/binder_alloc.c
> @@ -701,6 +701,7 @@ struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
> out:
> return buffer;
> }
> +EXPORT_SYMBOL_IF_KUNIT(binder_alloc_new_buf);
>
> static unsigned long buffer_start_page(struct binder_buffer *buffer)
> {
> @@ -879,6 +880,7 @@ void binder_alloc_free_buf(struct binder_alloc *alloc,
> binder_free_buf_locked(alloc, buffer);
> mutex_unlock(&alloc->mutex);
> }
> +EXPORT_SYMBOL_IF_KUNIT(binder_alloc_free_buf);
>
> /**
> * binder_alloc_mmap_handler() - map virtual address space for proc
> @@ -1217,6 +1219,7 @@ enum lru_status binder_alloc_free_page(struct list_head *item,
> err_mmget:
> return LRU_SKIP;
> }
> +EXPORT_SYMBOL_IF_KUNIT(binder_alloc_free_page);
>
> static unsigned long
> binder_shrink_count(struct shrinker *shrink, struct shrink_control *sc)
> diff --git a/drivers/android/binder_alloc.h b/drivers/android/binder_alloc.h
> index dc8dce2469a7..bed97c2cad92 100644
> --- a/drivers/android/binder_alloc.h
> +++ b/drivers/android/binder_alloc.h
> @@ -121,11 +121,6 @@ struct binder_alloc {
> bool oneway_spam_detected;
> };
>
> -#ifdef CONFIG_ANDROID_BINDER_IPC_SELFTEST
> -void binder_selftest_alloc(struct binder_alloc *alloc);
> -#else
> -static inline void binder_selftest_alloc(struct binder_alloc *alloc) {}
> -#endif
> enum lru_status binder_alloc_free_page(struct list_head *item,
> struct list_lru_one *lru,
> void *cb_arg);
> diff --git a/drivers/android/binder_alloc_selftest.c b/drivers/android/binder_alloc_selftest.c
> deleted file mode 100644
> index 8b18b22aa3de..000000000000
> --- a/drivers/android/binder_alloc_selftest.c
> +++ /dev/null
> @@ -1,345 +0,0 @@
> -// SPDX-License-Identifier: GPL-2.0-only
> -/* binder_alloc_selftest.c
> - *
> - * Android IPC Subsystem
> - *
> - * Copyright (C) 2017 Google, Inc.
> - */
> -
> -#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
> -
> -#include <linux/err.h>
> -#include <linux/list_lru.h>
> -#include <linux/mm_types.h>
> -#include "binder_alloc.h"
> -
> -#define BUFFER_NUM 5
> -#define BUFFER_MIN_SIZE (PAGE_SIZE / 8)
> -
> -static bool binder_selftest_run = true;
> -static int binder_selftest_failures;
> -static DEFINE_MUTEX(binder_selftest_lock);
> -static struct list_lru binder_selftest_freelist;
> -
> -/**
> - * enum buf_end_align_type - Page alignment of a buffer
> - * end with regard to the end of the previous buffer.
> - *
> - * In the pictures below, buf2 refers to the buffer we
> - * are aligning. buf1 refers to previous buffer by addr.
> - * Symbol [ means the start of a buffer, ] means the end
> - * of a buffer, and | means page boundaries.
> - */
> -enum buf_end_align_type {
> - /**
> - * @SAME_PAGE_UNALIGNED: The end of this buffer is on
> - * the same page as the end of the previous buffer and
> - * is not page aligned. Examples:
> - * buf1 ][ buf2 ][ ...
> - * buf1 ]|[ buf2 ][ ...
> - */
> - SAME_PAGE_UNALIGNED = 0,
> - /**
> - * @SAME_PAGE_ALIGNED: When the end of the previous buffer
> - * is not page aligned, the end of this buffer is on the
> - * same page as the end of the previous buffer and is page
> - * aligned. When the previous buffer is page aligned, the
> - * end of this buffer is aligned to the next page boundary.
> - * Examples:
> - * buf1 ][ buf2 ]| ...
> - * buf1 ]|[ buf2 ]| ...
> - */
> - SAME_PAGE_ALIGNED,
> - /**
> - * @NEXT_PAGE_UNALIGNED: The end of this buffer is on
> - * the page next to the end of the previous buffer and
> - * is not page aligned. Examples:
> - * buf1 ][ buf2 | buf2 ][ ...
> - * buf1 ]|[ buf2 | buf2 ][ ...
> - */
> - NEXT_PAGE_UNALIGNED,
> - /**
> - * @NEXT_PAGE_ALIGNED: The end of this buffer is on
> - * the page next to the end of the previous buffer and
> - * is page aligned. Examples:
> - * buf1 ][ buf2 | buf2 ]| ...
> - * buf1 ]|[ buf2 | buf2 ]| ...
> - */
> - NEXT_PAGE_ALIGNED,
> - /**
> - * @NEXT_NEXT_UNALIGNED: The end of this buffer is on
> - * the page that follows the page after the end of the
> - * previous buffer and is not page aligned. Examples:
> - * buf1 ][ buf2 | buf2 | buf2 ][ ...
> - * buf1 ]|[ buf2 | buf2 | buf2 ][ ...
> - */
> - NEXT_NEXT_UNALIGNED,
> - /**
> - * @LOOP_END: The number of enum values in &buf_end_align_type.
> - * It is used for controlling loop termination.
> - */
> - LOOP_END,
> -};
> -
> -static void pr_err_size_seq(size_t *sizes, int *seq)
> -{
> - int i;
> -
> - pr_err("alloc sizes: ");
> - for (i = 0; i < BUFFER_NUM; i++)
> - pr_cont("[%zu]", sizes[i]);
> - pr_cont("\n");
> - pr_err("free seq: ");
> - for (i = 0; i < BUFFER_NUM; i++)
> - pr_cont("[%d]", seq[i]);
> - pr_cont("\n");
> -}
> -
> -static bool check_buffer_pages_allocated(struct binder_alloc *alloc,
> - struct binder_buffer *buffer,
> - size_t size)
> -{
> - unsigned long page_addr;
> - unsigned long end;
> - int page_index;
> -
> - end = PAGE_ALIGN(buffer->user_data + size);
> - page_addr = buffer->user_data;
> - for (; page_addr < end; page_addr += PAGE_SIZE) {
> - page_index = (page_addr - alloc->vm_start) / PAGE_SIZE;
> - if (!alloc->pages[page_index] ||
> - !list_empty(page_to_lru(alloc->pages[page_index]))) {
> - pr_err("expect alloc but is %s at page index %d\n",
> - alloc->pages[page_index] ?
> - "lru" : "free", page_index);
> - return false;
> - }
> - }
> - return true;
> -}
> -
> -static void binder_selftest_alloc_buf(struct binder_alloc *alloc,
> - struct binder_buffer *buffers[],
> - size_t *sizes, int *seq)
> -{
> - int i;
> -
> - for (i = 0; i < BUFFER_NUM; i++) {
> - buffers[i] = binder_alloc_new_buf(alloc, sizes[i], 0, 0, 0);
> - if (IS_ERR(buffers[i]) ||
> - !check_buffer_pages_allocated(alloc, buffers[i],
> - sizes[i])) {
> - pr_err_size_seq(sizes, seq);
> - binder_selftest_failures++;
> - }
> - }
> -}
> -
> -static void binder_selftest_free_buf(struct binder_alloc *alloc,
> - struct binder_buffer *buffers[],
> - size_t *sizes, int *seq, size_t end)
> -{
> - int i;
> -
> - for (i = 0; i < BUFFER_NUM; i++)
> - binder_alloc_free_buf(alloc, buffers[seq[i]]);
> -
> - for (i = 0; i <= (end - 1) / PAGE_SIZE; i++) {
> - if (list_empty(page_to_lru(alloc->pages[i]))) {
> - pr_err_size_seq(sizes, seq);
> - pr_err("expect lru but is %s at page index %d\n",
> - alloc->pages[i] ? "alloc" : "free", i);
> - binder_selftest_failures++;
> - }
> - }
> -}
> -
> -static void binder_selftest_free_page(struct binder_alloc *alloc)
> -{
> - int i;
> - unsigned long count;
> -
> - while ((count = list_lru_count(&binder_selftest_freelist))) {
> - list_lru_walk(&binder_selftest_freelist, binder_alloc_free_page,
> - NULL, count);
> - }
> -
> - for (i = 0; i < (alloc->buffer_size / PAGE_SIZE); i++) {
> - if (alloc->pages[i]) {
> - pr_err("expect free but is %s at page index %d\n",
> - list_empty(page_to_lru(alloc->pages[i])) ?
> - "alloc" : "lru", i);
> - binder_selftest_failures++;
> - }
> - }
> -}
> -
> -static void binder_selftest_alloc_free(struct binder_alloc *alloc,
> - size_t *sizes, int *seq, size_t end)
> -{
> - struct binder_buffer *buffers[BUFFER_NUM];
> -
> - binder_selftest_alloc_buf(alloc, buffers, sizes, seq);
> - binder_selftest_free_buf(alloc, buffers, sizes, seq, end);
> -
> - /* Allocate from lru. */
> - binder_selftest_alloc_buf(alloc, buffers, sizes, seq);
> - if (list_lru_count(&binder_selftest_freelist))
> - pr_err("lru list should be empty but is not\n");
> -
> - binder_selftest_free_buf(alloc, buffers, sizes, seq, end);
> - binder_selftest_free_page(alloc);
> -}
> -
> -static bool is_dup(int *seq, int index, int val)
> -{
> - int i;
> -
> - for (i = 0; i < index; i++) {
> - if (seq[i] == val)
> - return true;
> - }
> - return false;
> -}
> -
> -/* Generate BUFFER_NUM factorial free orders. */
> -static void binder_selftest_free_seq(struct binder_alloc *alloc,
> - size_t *sizes, int *seq,
> - int index, size_t end)
> -{
> - int i;
> -
> - if (index == BUFFER_NUM) {
> - binder_selftest_alloc_free(alloc, sizes, seq, end);
> - return;
> - }
> - for (i = 0; i < BUFFER_NUM; i++) {
> - if (is_dup(seq, index, i))
> - continue;
> - seq[index] = i;
> - binder_selftest_free_seq(alloc, sizes, seq, index + 1, end);
> - }
> -}
> -
> -static void binder_selftest_alloc_size(struct binder_alloc *alloc,
> - size_t *end_offset)
> -{
> - int i;
> - int seq[BUFFER_NUM] = {0};
> - size_t front_sizes[BUFFER_NUM];
> - size_t back_sizes[BUFFER_NUM];
> - size_t last_offset, offset = 0;
> -
> - for (i = 0; i < BUFFER_NUM; i++) {
> - last_offset = offset;
> - offset = end_offset[i];
> - front_sizes[i] = offset - last_offset;
> - back_sizes[BUFFER_NUM - i - 1] = front_sizes[i];
> - }
> - /*
> - * Buffers share the first or last few pages.
> - * Only BUFFER_NUM - 1 buffer sizes are adjustable since
> - * we need one giant buffer before getting to the last page.
> - */
> - back_sizes[0] += alloc->buffer_size - end_offset[BUFFER_NUM - 1];
> - binder_selftest_free_seq(alloc, front_sizes, seq, 0,
> - end_offset[BUFFER_NUM - 1]);
> - binder_selftest_free_seq(alloc, back_sizes, seq, 0, alloc->buffer_size);
> -}
> -
> -static void binder_selftest_alloc_offset(struct binder_alloc *alloc,
> - size_t *end_offset, int index)
> -{
> - int align;
> - size_t end, prev;
> -
> - if (index == BUFFER_NUM) {
> - binder_selftest_alloc_size(alloc, end_offset);
> - return;
> - }
> - prev = index == 0 ? 0 : end_offset[index - 1];
> - end = prev;
> -
> - BUILD_BUG_ON(BUFFER_MIN_SIZE * BUFFER_NUM >= PAGE_SIZE);
> -
> - for (align = SAME_PAGE_UNALIGNED; align < LOOP_END; align++) {
> - if (align % 2)
> - end = ALIGN(end, PAGE_SIZE);
> - else
> - end += BUFFER_MIN_SIZE;
> - end_offset[index] = end;
> - binder_selftest_alloc_offset(alloc, end_offset, index + 1);
> - }
> -}
> -
> -int binder_selftest_alloc_get_page_count(struct binder_alloc *alloc)
> -{
> - struct page *page;
> - int allocated = 0;
> - int i;
> -
> - for (i = 0; i < alloc->buffer_size / PAGE_SIZE; i++) {
> - page = alloc->pages[i];
> - if (page)
> - allocated++;
> - }
> - return allocated;
> -}
> -
> -/**
> - * binder_selftest_alloc() - Test alloc and free of buffer pages.
> - * @alloc: Pointer to alloc struct.
> - *
> - * Allocate BUFFER_NUM buffers to cover all page alignment cases,
> - * then free them in all orders possible. Check that pages are
> - * correctly allocated, put onto lru when buffers are freed, and
> - * are freed when binder_alloc_free_page is called.
> - */
> -void binder_selftest_alloc(struct binder_alloc *alloc)
> -{
> - struct list_lru *prev_freelist;
> - size_t end_offset[BUFFER_NUM];
> -
> - if (!binder_selftest_run)
> - return;
> - mutex_lock(&binder_selftest_lock);
> - if (!binder_selftest_run || !alloc->mapped)
> - goto done;
> -
> - prev_freelist = alloc->freelist;
> -
> - /*
> - * It is not safe to modify this process's alloc->freelist if it has any
> - * pages on a freelist. Since the test runs before any binder ioctls can
> - * be dealt with, none of its pages should be allocated yet.
> - */
> - if (binder_selftest_alloc_get_page_count(alloc)) {
> - pr_err("process has existing alloc state\n");
> - goto cleanup;
> - }
> -
> - if (list_lru_init(&binder_selftest_freelist)) {
> - pr_err("failed to init test freelist\n");
> - goto cleanup;
> - }
> -
> - alloc->freelist = &binder_selftest_freelist;
> -
> - pr_info("STARTED\n");
> - binder_selftest_alloc_offset(alloc, end_offset, 0);
> - if (binder_selftest_failures > 0)
> - pr_info("%d tests FAILED\n", binder_selftest_failures);
> - else
> - pr_info("PASSED\n");
> -
> - if (list_lru_count(&binder_selftest_freelist))
> - pr_err("expect test freelist to be empty\n");
> -
> -cleanup:
> - /* Even if we didn't run the test, it's no longer thread-safe. */
> - binder_selftest_run = false;
> - alloc->freelist = prev_freelist;
> - list_lru_destroy(&binder_selftest_freelist);
> -done:
> - mutex_unlock(&binder_selftest_lock);
> -}
> diff --git a/drivers/android/tests/binder_alloc_kunit.c b/drivers/android/tests/binder_alloc_kunit.c
> index 4b68b5687d33..9e185e2036e5 100644
> --- a/drivers/android/tests/binder_alloc_kunit.c
> +++ b/drivers/android/tests/binder_alloc_kunit.c
> @@ -21,6 +21,265 @@ MODULE_IMPORT_NS("EXPORTED_FOR_KUNIT_TESTING");
>
> #define BINDER_MMAP_SIZE SZ_128K
>
> +#define BUFFER_NUM 5
> +#define BUFFER_MIN_SIZE (PAGE_SIZE / 8)
> +
> +static int binder_alloc_test_failures;
> +
> +/**
> + * enum buf_end_align_type - Page alignment of a buffer
> + * end with regard to the end of the previous buffer.
> + *
> + * In the pictures below, buf2 refers to the buffer we
> + * are aligning. buf1 refers to previous buffer by addr.
> + * Symbol [ means the start of a buffer, ] means the end
> + * of a buffer, and | means page boundaries.
> + */
> +enum buf_end_align_type {
> + /**
> + * @SAME_PAGE_UNALIGNED: The end of this buffer is on
> + * the same page as the end of the previous buffer and
> + * is not page aligned. Examples:
> + * buf1 ][ buf2 ][ ...
> + * buf1 ]|[ buf2 ][ ...
> + */
> + SAME_PAGE_UNALIGNED = 0,
> + /**
> + * @SAME_PAGE_ALIGNED: When the end of the previous buffer
> + * is not page aligned, the end of this buffer is on the
> + * same page as the end of the previous buffer and is page
> + * aligned. When the previous buffer is page aligned, the
> + * end of this buffer is aligned to the next page boundary.
> + * Examples:
> + * buf1 ][ buf2 ]| ...
> + * buf1 ]|[ buf2 ]| ...
> + */
> + SAME_PAGE_ALIGNED,
> + /**
> + * @NEXT_PAGE_UNALIGNED: The end of this buffer is on
> + * the page next to the end of the previous buffer and
> + * is not page aligned. Examples:
> + * buf1 ][ buf2 | buf2 ][ ...
> + * buf1 ]|[ buf2 | buf2 ][ ...
> + */
> + NEXT_PAGE_UNALIGNED,
> + /**
> + * @NEXT_PAGE_ALIGNED: The end of this buffer is on
> + * the page next to the end of the previous buffer and
> + * is page aligned. Examples:
> + * buf1 ][ buf2 | buf2 ]| ...
> + * buf1 ]|[ buf2 | buf2 ]| ...
> + */
> + NEXT_PAGE_ALIGNED,
> + /**
> + * @NEXT_NEXT_UNALIGNED: The end of this buffer is on
> + * the page that follows the page after the end of the
> + * previous buffer and is not page aligned. Examples:
> + * buf1 ][ buf2 | buf2 | buf2 ][ ...
> + * buf1 ]|[ buf2 | buf2 | buf2 ][ ...
> + */
> + NEXT_NEXT_UNALIGNED,
> + /**
> + * @LOOP_END: The number of enum values in &buf_end_align_type.
> + * It is used for controlling loop termination.
> + */
> + LOOP_END,
> +};
> +
> +static void pr_err_size_seq(struct kunit *test, size_t *sizes, int *seq)
> +{
> + int i;
> +
> + kunit_err(test, "alloc sizes: ");
> + for (i = 0; i < BUFFER_NUM; i++)
> + pr_cont("[%zu]", sizes[i]);
> + pr_cont("\n");
> + kunit_err(test, "free seq: ");
> + for (i = 0; i < BUFFER_NUM; i++)
> + pr_cont("[%d]", seq[i]);
> + pr_cont("\n");
> +}
> +
> +static bool check_buffer_pages_allocated(struct kunit *test,
> + struct binder_alloc *alloc,
> + struct binder_buffer *buffer,
> + size_t size)
> +{
> + unsigned long page_addr;
> + unsigned long end;
> + int page_index;
> +
> + end = PAGE_ALIGN(buffer->user_data + size);
> + page_addr = buffer->user_data;
> + for (; page_addr < end; page_addr += PAGE_SIZE) {
> + page_index = (page_addr - alloc->vm_start) / PAGE_SIZE;
> + if (!alloc->pages[page_index] ||
> + !list_empty(page_to_lru(alloc->pages[page_index]))) {
> + kunit_err(test, "expect alloc but is %s at page index %d\n",
> + alloc->pages[page_index] ?
> + "lru" : "free", page_index);
> + return false;
> + }
> + }
> + return true;
> +}
> +
> +static void binder_alloc_test_alloc_buf(struct kunit *test,
> + struct binder_alloc *alloc,
> + struct binder_buffer *buffers[],
> + size_t *sizes, int *seq)
> +{
> + int i;
> +
> + for (i = 0; i < BUFFER_NUM; i++) {
> + buffers[i] = binder_alloc_new_buf(alloc, sizes[i], 0, 0, 0);
> + if (IS_ERR(buffers[i]) ||
> + !check_buffer_pages_allocated(test, alloc, buffers[i], sizes[i])) {
> + pr_err_size_seq(test, sizes, seq);
> + binder_alloc_test_failures++;
> + }
> + }
> +}
> +
> +static void binder_alloc_test_free_buf(struct kunit *test,
> + struct binder_alloc *alloc,
> + struct binder_buffer *buffers[],
> + size_t *sizes, int *seq, size_t end)
> +{
> + int i;
> +
> + for (i = 0; i < BUFFER_NUM; i++)
> + binder_alloc_free_buf(alloc, buffers[seq[i]]);
> +
> + for (i = 0; i <= (end - 1) / PAGE_SIZE; i++) {
> + if (list_empty(page_to_lru(alloc->pages[i]))) {
> + pr_err_size_seq(test, sizes, seq);
> + kunit_err(test, "expect lru but is %s at page index %d\n",
> + alloc->pages[i] ? "alloc" : "free", i);
> + binder_alloc_test_failures++;
> + }
> + }
> +}
> +
> +static void binder_alloc_test_free_page(struct kunit *test,
> + struct binder_alloc *alloc)
> +{
> + unsigned long count;
> + int i;
> +
> + while ((count = list_lru_count(alloc->freelist))) {
> + list_lru_walk(alloc->freelist, binder_alloc_free_page,
> + NULL, count);
> + }
> +
> + for (i = 0; i < (alloc->buffer_size / PAGE_SIZE); i++) {
> + if (alloc->pages[i]) {
> + kunit_err(test, "expect free but is %s at page index %d\n",
> + list_empty(page_to_lru(alloc->pages[i])) ?
> + "alloc" : "lru", i);
> + binder_alloc_test_failures++;
> + }
> + }
> +}
> +
> +static void binder_alloc_test_alloc_free(struct kunit *test,
> + struct binder_alloc *alloc,
> + size_t *sizes, int *seq, size_t end)
> +{
> + struct binder_buffer *buffers[BUFFER_NUM];
> +
> + binder_alloc_test_alloc_buf(test, alloc, buffers, sizes, seq);
> + binder_alloc_test_free_buf(test, alloc, buffers, sizes, seq, end);
> +
> + /* Allocate from lru. */
> + binder_alloc_test_alloc_buf(test, alloc, buffers, sizes, seq);
> + if (list_lru_count(alloc->freelist))
> + kunit_err(test, "lru list should be empty but is not\n");
> +
> + binder_alloc_test_free_buf(test, alloc, buffers, sizes, seq, end);
> + binder_alloc_test_free_page(test, alloc);
> +}
> +
> +static bool is_dup(int *seq, int index, int val)
> +{
> + int i;
> +
> + for (i = 0; i < index; i++) {
> + if (seq[i] == val)
> + return true;
> + }
> + return false;
> +}
> +
> +/* Generate BUFFER_NUM factorial free orders. */
> +static void permute_frees(struct kunit *test, struct binder_alloc *alloc,
> + size_t *sizes, int *seq, int index, size_t end)
> +{
> + int i;
> +
> + if (index == BUFFER_NUM) {
> + binder_alloc_test_alloc_free(test, alloc, sizes, seq, end);
> + return;
> + }
> + for (i = 0; i < BUFFER_NUM; i++) {
> + if (is_dup(seq, index, i))
> + continue;
> + seq[index] = i;
> + permute_frees(test, alloc, sizes, seq, index + 1, end);
> + }
> +}
> +
> +static void gen_buf_sizes(struct kunit *test, struct binder_alloc *alloc,
> + size_t *end_offset)
> +{
> + size_t last_offset, offset = 0;
> + size_t front_sizes[BUFFER_NUM];
> + size_t back_sizes[BUFFER_NUM];
> + int seq[BUFFER_NUM] = {0};
> + int i;
> +
> + for (i = 0; i < BUFFER_NUM; i++) {
> + last_offset = offset;
> + offset = end_offset[i];
> + front_sizes[i] = offset - last_offset;
> + back_sizes[BUFFER_NUM - i - 1] = front_sizes[i];
> + }
> + /*
> + * Buffers share the first or last few pages.
> + * Only BUFFER_NUM - 1 buffer sizes are adjustable since
> + * we need one giant buffer before getting to the last page.
> + */
> + back_sizes[0] += alloc->buffer_size - end_offset[BUFFER_NUM - 1];
> + permute_frees(test, alloc, front_sizes, seq, 0,
> + end_offset[BUFFER_NUM - 1]);
> + permute_frees(test, alloc, back_sizes, seq, 0, alloc->buffer_size);
> +}
> +
> +static void gen_buf_offsets(struct kunit *test, struct binder_alloc *alloc,
> + size_t *end_offset, int index)
> +{
> + size_t end, prev;
> + int align;
> +
> + if (index == BUFFER_NUM) {
> + gen_buf_sizes(test, alloc, end_offset);
> + return;
> + }
> + prev = index == 0 ? 0 : end_offset[index - 1];
> + end = prev;
> +
> + BUILD_BUG_ON(BUFFER_MIN_SIZE * BUFFER_NUM >= PAGE_SIZE);
> +
> + for (align = SAME_PAGE_UNALIGNED; align < LOOP_END; align++) {
> + if (align % 2)
> + end = ALIGN(end, PAGE_SIZE);
> + else
> + end += BUFFER_MIN_SIZE;
> + end_offset[index] = end;
> + gen_buf_offsets(test, alloc, end_offset, index + 1);
> + }
> +}
> +
> struct binder_alloc_test {
> struct binder_alloc alloc;
> struct list_lru binder_test_freelist;
> @@ -56,6 +315,25 @@ static void binder_alloc_test_mmap(struct kunit *test)
> KUNIT_EXPECT_TRUE(test, list_is_last(&buf->entry, &alloc->buffers));
> }
>
> +/**
> + * binder_alloc_exhaustive_test() - Exhaustively test alloc and free of buffer pages.
> + * @test: The test context object.
> + *
> + * Allocate BUFFER_NUM buffers to cover all page alignment cases,
> + * then free them in all orders possible. Check that pages are
> + * correctly allocated, put onto lru when buffers are freed, and
> + * are freed when binder_alloc_free_page() is called.
> + */
> +static void binder_alloc_exhaustive_test(struct kunit *test)
> +{
> + struct binder_alloc_test *priv = test->priv;
> + size_t end_offset[BUFFER_NUM];
> +
> + gen_buf_offsets(test, &priv->alloc, end_offset, 0);
> +
> + KUNIT_EXPECT_EQ(test, binder_alloc_test_failures, 0);
> +}
> +
> /* ===== End test cases ===== */
>
> static void binder_alloc_test_vma_close(struct vm_area_struct *vma)
> @@ -149,6 +427,7 @@ static void binder_alloc_test_exit(struct kunit *test)
> static struct kunit_case binder_alloc_test_cases[] = {
> KUNIT_CASE(binder_alloc_test_init_freelist),
> KUNIT_CASE(binder_alloc_test_mmap),
> + KUNIT_CASE(binder_alloc_exhaustive_test),
> {}
> };
>
> --
> 2.50.0.727.gbf7dc18ff4-goog
>
LGTM!
Acked-by: Carlos Llamas <cmllamas@...gle.com>
Powered by blists - more mailing lists