Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit fe1d2cbb authored by Sunil Khatri's avatar Sunil Khatri
Browse files

msm: kgsl: Take the pagetable reference count only when needed



There could be a race condition where one thread is iterating
over ptlist inside kgsl_get_pagetable() with spinlock acquired
and going to call kref_put(). At the same time another thread is
inside kgsl_put_pagetable() without any spinlock calls kref_put()
and returns without calling kgsl_destroy_pagetable(). The decrement
of refcount by second thread leads to a situation where first thread
enters kgsl_destroy_pagetable_locked() and hence destroying the pt
node from list. This causes the destroyed pt node's next and prev
pointers set to POISON value resulting in kernel panic while executing
next iteration of ptlist.

Taking pt refcount while iterating over ptlist should be avoided if the
spinlock is already held and the pt is not returned to the caller. In
kgsl_get_pagetable(), since the loop is already holding the mutex, it
is safe to look at pt before taking a reference.

CRs-Fixed: 661374
Change-Id: Ide1f57cc1eb3cbf66fa399b6b72491755b999c7a
Signed-off-by: default avatarPrabhat Awasthi <pawasthi@codeaurora.org>
Signed-off-by: default avatarSunil Khatri <sunilkh@codeaurora.org>
parent 1b783572
Loading
Loading
Loading
Loading
+23 −52
Original line number Diff line number Diff line
@@ -230,41 +230,27 @@ int kgsl_add_global_pt_entry(struct kgsl_device *device,
}
EXPORT_SYMBOL(kgsl_add_global_pt_entry);

static void _kgsl_destroy_pagetable(struct kgsl_pagetable *pagetable)
{
	pagetable_remove_sysfs_objects(pagetable);

	kgsl_unmap_global_pt_entries(pagetable);

	if (pagetable->pool)
		gen_pool_destroy(pagetable->pool);

	pagetable->pt_ops->mmu_destroy_pagetable(pagetable);

	kfree(pagetable);
}

static void kgsl_destroy_pagetable(struct kref *kref)
{
	struct kgsl_pagetable *pagetable = container_of(kref,
		struct kgsl_pagetable, refcount);

	unsigned long flags;

	spin_lock_irqsave(&kgsl_driver.ptlock, flags);
	list_del(&pagetable->list);
	spin_unlock_irqrestore(&kgsl_driver.ptlock, flags);

	_kgsl_destroy_pagetable(pagetable);
}
	pagetable_remove_sysfs_objects(pagetable);

static void kgsl_destroy_pagetable_locked(struct kref *kref)
{
	struct kgsl_pagetable *pagetable = container_of(kref,
		struct kgsl_pagetable, refcount);
	kgsl_unmap_global_pt_entries(pagetable);

	list_del(&pagetable->list);
	if (pagetable->pool)
		gen_pool_destroy(pagetable->pool);

	pagetable->pt_ops->mmu_destroy_pagetable(pagetable);

	_kgsl_destroy_pagetable(pagetable);
	kfree(pagetable);
}

static inline void kgsl_put_pagetable(struct kgsl_pagetable *pagetable)
@@ -281,13 +267,10 @@ kgsl_get_pagetable(unsigned long name)

	spin_lock_irqsave(&kgsl_driver.ptlock, flags);
	list_for_each_entry(pt, &kgsl_driver.pagetable_list, list) {
		if (kref_get_unless_zero(&pt->refcount)) {
			if (pt->name == name) {
		if (name == pt->name && kref_get_unless_zero(&pt->refcount)) {
			ret = pt;
			break;
		}
			kref_put(&pt->refcount, kgsl_destroy_pagetable_locked);
		}
	}

	spin_unlock_irqrestore(&kgsl_driver.ptlock, flags);
@@ -433,15 +416,10 @@ kgsl_mmu_get_ptname_from_ptbase(struct kgsl_mmu *mmu, phys_addr_t pt_base)
		return KGSL_MMU_GLOBAL_PT;
	spin_lock(&kgsl_driver.ptlock);
	list_for_each_entry(pt, &kgsl_driver.pagetable_list, list) {
		if (kref_get_unless_zero(&pt->refcount)) {
		if (mmu->mmu_ops->mmu_pt_equal(mmu, pt, pt_base)) {
			ptid = (int) pt->name;
				kref_put(&pt->refcount,
					kgsl_destroy_pagetable_locked);
			break;
		}
			kref_put(&pt->refcount, kgsl_destroy_pagetable_locked);
		}
	}
	spin_unlock(&kgsl_driver.ptlock);

@@ -460,24 +438,17 @@ kgsl_mmu_log_fault_addr(struct kgsl_mmu *mmu, phys_addr_t pt_base,
		return 0;
	spin_lock(&kgsl_driver.ptlock);
	list_for_each_entry(pt, &kgsl_driver.pagetable_list, list) {
		if (kref_get_unless_zero(&pt->refcount)) {
		if (mmu->mmu_ops->mmu_pt_equal(mmu, pt, pt_base)) {
			if ((addr & ~(PAGE_SIZE-1)) == pt->fault_addr) {
				ret = 1;
					kref_put(&pt->refcount,
						kgsl_destroy_pagetable_locked);
				break;
			} else {
				pt->fault_addr =
					(addr & ~(PAGE_SIZE-1));
				ret = 0;
					kref_put(&pt->refcount,
						kgsl_destroy_pagetable_locked);
				break;
			}
		}
			kref_put(&pt->refcount, kgsl_destroy_pagetable_locked);
		}
	}
	spin_unlock(&kgsl_driver.ptlock);