android: binder: Add allocator selftest
authorSherry Yang <sherryy@android.com>
Wed, 23 Aug 2017 15:46:40 +0000 (08:46 -0700)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Mon, 28 Aug 2017 14:47:17 +0000 (16:47 +0200)
binder_alloc_selftest tests that alloc_new_buf handles page allocation and
deallocation properly when allocate and free buffers. The test allocates 5
buffers of various sizes to cover all possible page alignment cases, and
frees the buffers using a list of exhaustive freeing order.

Test: boot the device with ANDROID_BINDER_IPC_SELFTEST config option
enabled. Allocator selftest passes.

Signed-off-by: Sherry Yang <sherryy@android.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/android/Kconfig
drivers/android/Makefile
drivers/android/binder.c
drivers/android/binder_alloc.h
drivers/android/binder_alloc_selftest.c [new file with mode: 0644]

index 8a748d4a4167a7d1ae082ac30759df4fc6e4e198..9801d852bd56e854de83868fba6750f51dc181ec 100644 (file)
@@ -44,6 +44,16 @@ config ANDROID_BINDER_IPC_32BIT
 
          Note that enabling this will break newer Android user-space.
 
+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.
+
 endif # if ANDROID
 
 endmenu
index 4b7c726bb560944c2bfd23e267a051018efffc05..a01254c43ee3f714020aff487314f32ec0b2bbde 100644 (file)
@@ -1,3 +1,4 @@
 ccflags-y += -I$(src)                  # needed for trace events
 
 obj-$(CONFIG_ANDROID_BINDER_IPC)       += binder.o binder_alloc.o
+obj-$(CONFIG_ANDROID_BINDER_IPC_SELFTEST) += binder_alloc_selftest.o
index 947eb7056fa7e6efff763b17f2df04155ca0b327..e4b6055aafc74caab97a4c160cb0e63dbfc00633 100644 (file)
@@ -4225,6 +4225,8 @@ static long binder_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
        /*pr_info("binder_ioctl: %d:%d %x %lx\n",
                        proc->pid, current->pid, cmd, arg);*/
 
+       binder_selftest_alloc(&proc->alloc);
+
        trace_binder_ioctl(cmd, arg);
 
        ret = wait_event_interruptible(binder_user_error_wait, binder_stop_on_user_error < 2);
index 088e4ffc62300391a79946de596c37285e23f23c..4f02cc084c15aad366cbd01b4ccc6b16c7ef68ad 100644 (file)
@@ -102,6 +102,11 @@ struct binder_alloc {
        int pid;
 };
 
+#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
 extern struct binder_buffer *binder_alloc_new_buf(struct binder_alloc *alloc,
                                                  size_t data_size,
                                                  size_t offsets_size,
diff --git a/drivers/android/binder_alloc_selftest.c b/drivers/android/binder_alloc_selftest.c
new file mode 100644 (file)
index 0000000..cc00ab6
--- /dev/null
@@ -0,0 +1,271 @@
+/* binder_alloc_selftest.c
+ *
+ * Android IPC Subsystem
+ *
+ * Copyright (C) 2017 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/mm_types.h>
+#include <linux/err.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);
+
+/**
+ * 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,
+};
+
+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)
+{
+       void *page_addr, *end;
+       int page_index;
+
+       end = (void *)PAGE_ALIGN((uintptr_t)buffer + size);
+       for (page_addr = buffer; page_addr < end; page_addr += PAGE_SIZE) {
+               page_index = (page_addr - alloc->buffer) / PAGE_SIZE;
+               if (!alloc->pages[page_index]) {
+                       pr_err("incorrect alloc state at page index %d\n",
+                              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)
+{
+       int i;
+
+       for (i = 0; i < BUFFER_NUM; i++)
+               binder_alloc_free_buf(alloc, buffers[seq[i]]);
+
+       for (i = 0; i < (alloc->buffer_size / PAGE_SIZE); i++) {
+               if ((!alloc->pages[i]) == (i == 0)) {
+                       pr_err("incorrect free state at page index %d\n", i);
+                       binder_selftest_failures++;
+               }
+       }
+}
+
+static void binder_selftest_alloc_free(struct binder_alloc *alloc,
+                                      size_t *sizes, int *seq)
+{
+       struct binder_buffer *buffers[BUFFER_NUM];
+
+       binder_selftest_alloc_buf(alloc, buffers, sizes, seq);
+       binder_selftest_free_buf(alloc, buffers, sizes, seq);
+}
+
+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)
+{
+       int i;
+
+       if (index == BUFFER_NUM) {
+               binder_selftest_alloc_free(alloc, sizes, seq);
+               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);
+       }
+}
+
+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]
+               - sizeof(struct binder_buffer) * BUFFER_NUM;
+       binder_selftest_free_seq(alloc, front_sizes, seq, 0);
+       binder_selftest_free_seq(alloc, back_sizes, seq, 0);
+}
+
+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 + sizeof(struct binder_buffer))
+                    * 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);
+       }
+}
+
+/**
+ * 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
+ * allocated after buffer alloc and freed after freeing buffer.
+ */
+void binder_selftest_alloc(struct binder_alloc *alloc)
+{
+       size_t end_offset[BUFFER_NUM];
+
+       if (!binder_selftest_run)
+               return;
+       mutex_lock(&binder_selftest_lock);
+       if (!binder_selftest_run || !alloc->vma)
+               goto done;
+       pr_info("STARTED\n");
+       binder_selftest_alloc_offset(alloc, end_offset, 0);
+       binder_selftest_run = false;
+       if (binder_selftest_failures > 0)
+               pr_info("%d tests FAILED\n", binder_selftest_failures);
+       else
+               pr_info("PASSED\n");
+
+done:
+       mutex_unlock(&binder_selftest_lock);
+}