summaryrefslogtreecommitdiff
path: root/mali_kbase/mali_kbase_mem_pool.c
diff options
context:
space:
mode:
authorSuzanne Candanedo <suzanne.candanedo@arm.com>2023-04-12 12:31:53 +0100
committerGuus Sliepen <gsliepen@google.com>2023-05-09 07:46:56 +0000
commit41f159f6de2788d7ce6993ba20218bcb8392ace1 (patch)
tree74f90aee88180d6e4dabbb2d7e326abfb2e70d78 /mali_kbase/mali_kbase_mem_pool.c
parentb08aa4e87a4adc0af4fea283d3af26637e2fdd8a (diff)
downloadgpu-41f159f6de2788d7ce6993ba20218bcb8392ace1.tar.gz
[Official] MIDCET-4458, GPUCORE-36402: Check for process exit before page alloc from kthread
The backing pages for native GPU allocations aren't always allocated in the ioctl context. A JIT_ALLOC softjob or KCPU command can get processed in the kernel worker thread. GPU page fault handling is anyways done in a kernel thread. Userspace can make Kbase allocate large number of backing pages from the kernel thread to cause out of memory situation, which would eventually lead to a kernel panic as OoM killer would run out of suitable processes to kill. Though Kbase will account for the backing pages and OoM killer will try to kill the culprit process, the memory already allocated by the process won't get freed as context termination would remain blocked or won't kick-in until kernel thread keeps trying to allocate the backing pages. For the allocation that is done from the context of kernel thread, OoM killer won't consider the kernel thread for killing and kernel would keep retrying to allocate physical page as long as the OoM killer is able to kill processes. For the memory allocation done from the ioctl context, kernel would eventually stop retrying when it sees that process has been marked for killing by the OoM killer. This commit adds a check for process exit in the page allocation loop. The check allows kernel thread to swiftly exit the page allocation loop once OoM killer has initiated the killing of culprit process (for which kernel thread is trying to allocate pages) thereby unblocking context termination and freeing of GPU memory already allocated by the process. This helps in preventing the kernel panic and also limits the number of innocent processes that gets killed. The use of __GFP_RETRY_MAYFAIL flag didn't help in all the scenarios. The flag ensures that OoM killer is not invoked directly and kernel doesn't keep retrying to allocate the page. But when system is running low on memory, other threads can invoke the OoM killer and the page allocation request from kthread could continue to get satisfied due to the killing of other processes and so the kthread may not always timely exit the page allocation loop. (cherry picked from commit 3c5c9328a7fc552e61972c1bbff4b56696682d30) GPUCORE-36402: Fix potential memleak and NULL ptr deref issue in Kbase The commit 3c5c9328a7fc552e61972c1bbff4b56696682d30 updated Kbase to check for the process exit in every iteration of the page allocation loop when the allocation is done from the context of kernel worker thread. The commit introduced a potential memleak and NULL pointer dereference issue (which was reported by Coverity). This commit adds the required fix for the 2 issues and also sets the task pointer only for the Userspace created contexts and not for the contexts created by Kbase i.e. privileged context created for the HW counter dumping and for the WA of HW issue TRYM-3485. Bug: 275614526 Change-Id: I8107edce09a2cb52d8586fc9f7990a25166f590e Signed-off-by: Guus Sliepen <gsliepen@google.com> Provenance: https://code.ipdelivery.arm.com/c/GPU/mali-ddk/+/5169 (cherry picked from commit 8294169160ebb0d11d7d22b11311ddf887fb0b63)
Diffstat (limited to 'mali_kbase/mali_kbase_mem_pool.c')
-rw-r--r--mali_kbase/mali_kbase_mem_pool.c69
1 files changed, 62 insertions, 7 deletions
diff --git a/mali_kbase/mali_kbase_mem_pool.c b/mali_kbase/mali_kbase_mem_pool.c
index 01a0534..563036a 100644
--- a/mali_kbase/mali_kbase_mem_pool.c
+++ b/mali_kbase/mali_kbase_mem_pool.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
- * (C) COPYRIGHT 2015-2021 ARM Limited. All rights reserved.
+ * (C) COPYRIGHT 2015-2023 ARM Limited. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
@@ -27,6 +27,11 @@
#include <linux/shrinker.h>
#include <linux/atomic.h>
#include <linux/version.h>
+#if KERNEL_VERSION(4, 11, 0) <= LINUX_VERSION_CODE
+#include <linux/sched/signal.h>
+#else
+#include <linux/signal.h>
+#endif
#define pool_dbg(pool, format, ...) \
dev_dbg(pool->kbdev->dev, "%s-pool [%zu/%zu]: " format, \
@@ -70,6 +75,47 @@ static void kbase_mem_pool_ordered_add_array_spill_locked(
struct tagged_addr *pages, struct list_head *spillover_list,
bool zero, bool sync);
+/**
+ * can_alloc_page() - Check if the current thread can allocate a physical page
+ *
+ * @pool: Pointer to the memory pool.
+ * @page_owner: Pointer to the task/process that created the Kbase context
+ * for which a page needs to be allocated. It can be NULL if
+ * the page won't be associated with Kbase context.
+ * @alloc_from_kthread: Flag indicating that the current thread is a kernel thread.
+ *
+ * This function checks if the current thread is a kernel thread and can make a
+ * request to kernel to allocate a physical page. If the kernel thread is allocating
+ * a page for the Kbase context and the process that created the context is exiting
+ * or is being killed, then there is no point in doing a page allocation.
+ *
+ * The check done by the function is particularly helpful when the system is running
+ * low on memory. When a page is allocated from the context of a kernel thread, OoM
+ * killer doesn't consider the kernel thread for killing and kernel keeps retrying
+ * to allocate the page as long as the OoM killer is able to kill processes.
+ * The check allows kernel thread to quickly exit the page allocation loop once OoM
+ * killer has initiated the killing of @page_owner, thereby unblocking the context
+ * termination for @page_owner and freeing of GPU memory allocated by it. This helps
+ * in preventing the kernel panic and also limits the number of innocent processes
+ * that get killed.
+ *
+ * Return: true if the page can be allocated otherwise false.
+ */
+static inline bool can_alloc_page(struct kbase_mem_pool *pool, struct task_struct *page_owner,
+ const bool alloc_from_kthread)
+{
+ if (likely(!alloc_from_kthread || !page_owner))
+ return true;
+
+ if ((page_owner->flags & PF_EXITING) || fatal_signal_pending(page_owner)) {
+ dev_info(pool->kbdev->dev, "%s : Process %s/%d exiting", __func__, page_owner->comm,
+ task_pid_nr(page_owner));
+ return false;
+ }
+
+ return true;
+}
+
static size_t kbase_mem_pool_capacity(struct kbase_mem_pool *pool)
{
ssize_t max_size = kbase_mem_pool_max_size(pool);
@@ -274,11 +320,12 @@ static size_t kbase_mem_pool_shrink(struct kbase_mem_pool *pool,
return nr_freed;
}
-int kbase_mem_pool_grow(struct kbase_mem_pool *pool,
- size_t nr_to_grow)
+int kbase_mem_pool_grow(struct kbase_mem_pool *pool, size_t nr_to_grow,
+ struct task_struct *page_owner)
{
struct page *p;
size_t i;
+ const bool alloc_from_kthread = !!(current->flags & PF_KTHREAD);
kbase_mem_pool_lock(pool);
@@ -293,6 +340,9 @@ int kbase_mem_pool_grow(struct kbase_mem_pool *pool,
}
kbase_mem_pool_unlock(pool);
+ if (unlikely(!can_alloc_page(pool, page_owner, alloc_from_kthread)))
+ return -ENOMEM;
+
p = kbase_mem_alloc_page(pool);
if (!p) {
kbase_mem_pool_lock(pool);
@@ -324,7 +374,7 @@ void kbase_mem_pool_trim(struct kbase_mem_pool *pool, size_t new_size)
if (new_size < cur_size)
kbase_mem_pool_shrink(pool, cur_size - new_size);
else if (new_size > cur_size)
- err = kbase_mem_pool_grow(pool, new_size - cur_size);
+ err = kbase_mem_pool_grow(pool, new_size - cur_size, NULL);
if (err) {
size_t grown_size = kbase_mem_pool_size(pool);
@@ -593,13 +643,15 @@ void kbase_mem_pool_free_locked(struct kbase_mem_pool *pool, struct page *p,
}
int kbase_mem_pool_alloc_pages(struct kbase_mem_pool *pool, size_t nr_4k_pages,
- struct tagged_addr *pages, bool partial_allowed)
+ struct tagged_addr *pages, bool partial_allowed,
+ struct task_struct *page_owner)
{
struct page *p;
size_t nr_from_pool;
size_t i = 0;
int err = -ENOMEM;
size_t nr_pages_internal;
+ const bool alloc_from_kthread = !!(current->flags & PF_KTHREAD);
nr_pages_internal = nr_4k_pages / (1u << (pool->order));
@@ -631,8 +683,8 @@ int kbase_mem_pool_alloc_pages(struct kbase_mem_pool *pool, size_t nr_4k_pages,
if (i != nr_4k_pages && pool->next_pool) {
/* Allocate via next pool */
- err = kbase_mem_pool_alloc_pages(pool->next_pool,
- nr_4k_pages - i, pages + i, partial_allowed);
+ err = kbase_mem_pool_alloc_pages(pool->next_pool, nr_4k_pages - i, pages + i,
+ partial_allowed, page_owner);
if (err < 0)
goto err_rollback;
@@ -641,6 +693,9 @@ int kbase_mem_pool_alloc_pages(struct kbase_mem_pool *pool, size_t nr_4k_pages,
} else {
/* Get any remaining pages from kernel */
while (i != nr_4k_pages) {
+ if (unlikely(!can_alloc_page(pool, page_owner, alloc_from_kthread)))
+ goto err_rollback;
+
p = kbase_mem_alloc_page(pool);
if (!p) {
if (partial_allowed)