FROMLIST: android: binder: Add allocator selftest
authorSherry Yang <sherryy@android.com>
Thu, 22 Jun 2017 21:37:45 +0000 (14:37 -0700)
committerDanny Wood <danwood76@gmail.com>
Fri, 8 Nov 2019 12:03:12 +0000 (12:03 +0000)
(from https://patchwork.kernel.org/patch/9928609/)

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.

Bug: 36007193
Change-Id: I2fe396232b7dfe4bbc50bdba99ca0de9be63cc37
Signed-off-by: Sherry Yang <sherryy@android.com>
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 4d4cdc1a6e257c2df4aab9ca0ec9126cb6969415..35a7fc8d3b652c48aea9255fb351f544450f84ea 100644 (file)
@@ -31,6 +31,16 @@ 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_IPC_32BIT
        bool
        depends on !64BIT && ANDROID_BINDER_IPC
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 a0e7d983736f42e352ac4c2a1d38c9d4702e3cfa..47d5f77772bcc64282680db030fae67026cf3564 100644 (file)
@@ -4587,6 +4587,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);
+}