From 79c8161aeac99344506e989af2ca92b13e0d0676 Mon Sep 17 00:00:00 2001 From: Jordan Crouse Date: Thu, 31 Dec 2020 08:29:02 +0530 Subject: msm: kgsl: Stop using memdesc->usermem. It shouldn't be illegal for memory descriptors to have multiple virtual memory mappings under certain circumstances. If that is the case tracking a single usermem address for each memdesc no longer makes much sense. Get rid of the memdesc->usermem member and use an atomic counter to track mappings instead. Change-Id: Ic0dedbad31bafcd1019ccc8e68657cb7e3c72727 Signed-off-by: Jordan Crouse Signed-off-by: Harshitha Sai Neelati Signed-off-by: Kamal Agrawal --- drivers/gpu/msm/kgsl.c | 49 ++++++++++++++++++++++++---------------- drivers/gpu/msm/kgsl.h | 11 +++++---- drivers/gpu/msm/kgsl_debugfs.c | 22 ++++++++++++------ drivers/gpu/msm/kgsl_sharedmem.c | 6 +---- drivers/gpu/msm/kgsl_trace.h | 8 +++---- 5 files changed, 56 insertions(+), 40 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index e081e579d142..1f6e562bad67 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2020, The Linux Foundation. All rights reserved. +/* Copyright (c) 2008-2021, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -266,6 +266,7 @@ kgsl_mem_entry_create(void) /* put this ref in the caller functions after init */ kref_get(&entry->refcount); } + atomic_set(&entry->map_count, 0); return entry; } #ifdef CONFIG_DMA_SHARED_BUFFER @@ -2125,7 +2126,7 @@ static int check_vma(unsigned long hostptr, u64 size) return true; } -static int memdesc_sg_virt(struct kgsl_memdesc *memdesc) +static int memdesc_sg_virt(struct kgsl_memdesc *memdesc, unsigned long useraddr) { int ret = 0; long npages = 0, i; @@ -2147,13 +2148,13 @@ static int memdesc_sg_virt(struct kgsl_memdesc *memdesc) } down_read(¤t->mm->mmap_sem); - if (!check_vma(memdesc->useraddr, memdesc->size)) { + if (!check_vma(useraddr, memdesc->size)) { up_read(¤t->mm->mmap_sem); ret = ~EFAULT; goto out; } - npages = get_user_pages(current, current->mm, memdesc->useraddr, + npages = get_user_pages(current, current->mm, useraddr, sglen, write ? FOLL_WRITE : 0, pages, NULL); up_read(¤t->mm->mmap_sem); @@ -2192,7 +2193,6 @@ static int kgsl_setup_anon_useraddr(struct kgsl_pagetable *pagetable, entry->memdesc.pagetable = pagetable; entry->memdesc.size = (uint64_t) size; - entry->memdesc.useraddr = hostptr; entry->memdesc.flags |= KGSL_MEMFLAGS_USERMEM_ADDR; if (kgsl_memdesc_use_cpu_map(&entry->memdesc)) { @@ -2200,15 +2200,15 @@ static int kgsl_setup_anon_useraddr(struct kgsl_pagetable *pagetable, /* Register the address in the database */ ret = kgsl_mmu_set_svm_region(pagetable, - (uint64_t) entry->memdesc.useraddr, (uint64_t) size); + (uint64_t) hostptr, (uint64_t) size); if (ret) return ret; - entry->memdesc.gpuaddr = (uint64_t) entry->memdesc.useraddr; + entry->memdesc.gpuaddr = (uint64_t) hostptr; } - return memdesc_sg_virt(&entry->memdesc); + return memdesc_sg_virt(&entry->memdesc, hostptr); } static int match_file(const void *p, struct file *file, unsigned int fd) @@ -2293,8 +2293,8 @@ static int kgsl_setup_dmabuf_useraddr(struct kgsl_device *device, return ret; } - /* Setup the user addr/cache mode for cache operations */ - entry->memdesc.useraddr = hostptr; + /* Setup the cache mode for cache operations */ + _setup_cache_mode(entry, vma); up_read(¤t->mm->mmap_sem); return 0; @@ -3312,7 +3312,12 @@ long kgsl_ioctl_gpumem_get_info(struct kgsl_device_private *dev_priv, param->flags = (unsigned int) entry->memdesc.flags; param->size = (size_t) entry->memdesc.size; param->mmapsize = (size_t) kgsl_memdesc_footprint(&entry->memdesc); - param->useraddr = entry->memdesc.useraddr; + /* + * Entries can have multiple user mappings so thre isn't any one address + * we can report. Plus, the user should already know their mappings, so + * there isn't any value in reporting it back to them. + */ + param->useraddr = 0; kgsl_mem_entry_put(entry); return result; @@ -3778,9 +3783,6 @@ static int _sparse_bind(struct kgsl_process_private *process, if (memdesc->gpuaddr) return -EINVAL; - if (memdesc->useraddr != 0) - return -EINVAL; - pagetable = memdesc->pagetable; /* Clear out any mappings */ @@ -4060,7 +4062,12 @@ long kgsl_ioctl_gpuobj_info(struct kgsl_device_private *dev_priv, param->flags = entry->memdesc.flags; param->size = entry->memdesc.size; param->va_len = kgsl_memdesc_footprint(&entry->memdesc); - param->va_addr = (uint64_t) entry->memdesc.useraddr; + /* + * Entries can have multiple user mappings so thre isn't any one address + * we can report. Plus, the user should already know their mappings, so + * there isn't any value in reporting it back to them. + */ + param->va_addr = 0; kgsl_mem_entry_put(entry); return 0; @@ -4228,6 +4235,8 @@ static void kgsl_gpumem_vm_open(struct vm_area_struct *vma) if (kgsl_mem_entry_get(entry) == 0) vma->vm_private_data = NULL; + + atomic_inc(&entry->map_count); } static int @@ -4251,7 +4260,8 @@ kgsl_gpumem_vm_close(struct vm_area_struct *vma) if (!entry) return; - entry->memdesc.useraddr = 0; + atomic_dec(&entry->map_count); + kgsl_mem_entry_put(entry); } @@ -4290,7 +4300,8 @@ get_mmap_entry(struct kgsl_process_private *private, } } - if (entry->memdesc.useraddr != 0) { + /* Don't allow ourselves to remap user memory */ + if (entry->memdesc.flags & KGSL_MEMFLAGS_USERMEM_ADDR) { ret = -EBUSY; goto err_put; } @@ -4612,9 +4623,9 @@ static int kgsl_mmap(struct file *file, struct vm_area_struct *vma) vma->vm_file = file; - entry->memdesc.useraddr = vma->vm_start; + atomic_inc(&entry->map_count); - trace_kgsl_mem_mmap(entry); + trace_kgsl_mem_mmap(entry, vma->vm_start); return 0; } diff --git a/drivers/gpu/msm/kgsl.h b/drivers/gpu/msm/kgsl.h index 6b8ef82d340f..6e6edf67064d 100644 --- a/drivers/gpu/msm/kgsl.h +++ b/drivers/gpu/msm/kgsl.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2008-2016,2018-2019, The Linux Foundation. All rights reserved. +/* Copyright (c) 2008-2016,2018-2019,2021, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -197,11 +197,9 @@ struct kgsl_memdesc_ops { * @pagetable: Pointer to the pagetable that the object is mapped in * @hostptr: Kernel virtual address * @hostptr_count: Number of threads using hostptr - * @useraddr: User virtual address (if applicable) * @gpuaddr: GPU virtual address * @physaddr: Physical address of the memory object * @size: Size of the memory object - * @mapsize: Size of memory mapped in userspace * @priv: Internal flags and settings * @sgt: Scatter gather table for allocated pages * @ops: Function hooks for the memdesc memory type @@ -216,11 +214,9 @@ struct kgsl_memdesc { struct kgsl_pagetable *pagetable; void *hostptr; unsigned int hostptr_count; - unsigned long useraddr; uint64_t gpuaddr; phys_addr_t physaddr; uint64_t size; - uint64_t mapsize; unsigned int priv; struct sg_table *sgt; struct kgsl_memdesc_ops *ops; @@ -278,6 +274,11 @@ struct kgsl_mem_entry { struct work_struct work; spinlock_t bind_lock; struct rb_root bind_tree; + /* + * @map_count: Count how many vmas this object is mapped in - used for + * debugfs accounting + */ + atomic_t map_count; }; struct kgsl_device_private; diff --git a/drivers/gpu/msm/kgsl_debugfs.c b/drivers/gpu/msm/kgsl_debugfs.c index 592257a332d1..9add4eef3483 100644 --- a/drivers/gpu/msm/kgsl_debugfs.c +++ b/drivers/gpu/msm/kgsl_debugfs.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2002,2008-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2002,2008-2017,2021 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -149,7 +149,11 @@ static int print_mem_entry(void *data, void *ptr) flags[3] = get_alignflag(m); flags[4] = get_cacheflag(m); flags[5] = kgsl_memdesc_use_cpu_map(m) ? 'p' : '-'; - flags[6] = (m->useraddr) ? 'Y' : 'N'; + /* + * Show Y if at least one vma has this entry + * mapped (could be multiple) + */ + flags[6] = atomic_read(&entry->map_count) ? 'Y' : 'N'; flags[7] = kgsl_memdesc_is_secured(m) ? 's' : '-'; flags[8] = m->flags & KGSL_MEMFLAGS_SPARSE_PHYS ? 'P' : '-'; flags[9] = '\0'; @@ -160,12 +164,16 @@ static int print_mem_entry(void *data, void *ptr) kgsl_get_egl_counts(entry, &egl_surface_count, &egl_image_count); - seq_printf(s, "%pK %pK %16llu %5d %9s %10s %16s %5d %16llu %6d %6d", + seq_printf(s, "%pK %d %16llu %5d %9s %10s %16s %5d %16d %6d %6d", (uint64_t *)(uintptr_t) m->gpuaddr, - (unsigned long *) m->useraddr, - m->size, entry->id, flags, + /* + * Show zero for the useraddr - we can't reliably track + * that value for multiple vmas anyway + */ + 0, m->size, entry->id, flags, memtype_str(usermem_type), - usage, (m->sgt ? m->sgt->nents : 0), m->mapsize, + usage, (m->sgt ? m->sgt->nents : 0), + atomic_read(&entry->map_count), egl_surface_count, egl_image_count); if (entry->metadata[0] != 0) @@ -235,7 +243,7 @@ static int process_mem_seq_show(struct seq_file *s, void *ptr) if (ptr == SEQ_START_TOKEN) { seq_printf(s, "%16s %16s %16s %5s %9s %10s %16s %5s %16s %6s %6s\n", "gpuaddr", "useraddr", "size", "id", "flags", "type", - "usage", "sglen", "mapsize", "eglsrf", "eglimg"); + "usage", "sglen", "mapcount", "eglsrf", "eglimg"); return 0; } else return print_mem_entry(s, ptr); diff --git a/drivers/gpu/msm/kgsl_sharedmem.c b/drivers/gpu/msm/kgsl_sharedmem.c index 4c54553e9977..c343d90e564c 100644 --- a/drivers/gpu/msm/kgsl_sharedmem.c +++ b/drivers/gpu/msm/kgsl_sharedmem.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2002,2007-2017 The Linux Foundation. All rights reserved. +/* Copyright (c) 2002,2007-2017,2021 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -373,8 +373,6 @@ static int kgsl_page_alloc_vmfault(struct kgsl_memdesc *memdesc, get_page(page); vmf->page = page; - memdesc->mapsize += PAGE_SIZE; - return 0; } @@ -504,8 +502,6 @@ static int kgsl_contiguous_vmfault(struct kgsl_memdesc *memdesc, else if (ret == -EFAULT) return VM_FAULT_SIGBUS; - memdesc->mapsize += PAGE_SIZE; - return VM_FAULT_NOPAGE; } diff --git a/drivers/gpu/msm/kgsl_trace.h b/drivers/gpu/msm/kgsl_trace.h index 6438c6e65b97..2d80f00bf942 100644 --- a/drivers/gpu/msm/kgsl_trace.h +++ b/drivers/gpu/msm/kgsl_trace.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2011-2016,2021 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -433,9 +433,9 @@ TRACE_EVENT(kgsl_mem_alloc, TRACE_EVENT(kgsl_mem_mmap, - TP_PROTO(struct kgsl_mem_entry *mem_entry), + TP_PROTO(struct kgsl_mem_entry *mem_entry, unsigned long useraddr), - TP_ARGS(mem_entry), + TP_ARGS(mem_entry, useraddr), TP_STRUCT__entry( __field(unsigned long, useraddr) @@ -447,7 +447,7 @@ TRACE_EVENT(kgsl_mem_mmap, ), TP_fast_assign( - __entry->useraddr = mem_entry->memdesc.useraddr; + __entry->useraddr = useraddr; __entry->gpuaddr = mem_entry->memdesc.gpuaddr; __entry->size = mem_entry->memdesc.size; kgsl_get_memory_usage(__entry->usage, sizeof(__entry->usage), -- cgit v1.2.3 From 854ef3ce73f55f3ce1a193322796f155cb40717b Mon Sep 17 00:00:00 2001 From: Jordan Crouse Date: Thu, 31 Dec 2020 13:55:39 +0530 Subject: msm: kgsl: Protect the memdesc->gpuaddr in SVM use cases. When SVM is being used there can only be one GPU address assigned to the memory descriptor. Don't allow the GPU address to be changed after it has been negotiated the first time by a process. Change-Id: Ic0dedbad2a1b3ccdc2c1598a6c501b2be288d64e Signed-off-by: Jordan Crouse Signed-off-by: Harshitha Sai Neelati Signed-off-by: Kamal Agrawal --- drivers/gpu/msm/kgsl.c | 35 ++++++++++++++++++++++++++++++++--- drivers/gpu/msm/kgsl.h | 5 +++++ drivers/gpu/msm/kgsl_iommu.c | 10 +++++++++- drivers/gpu/msm/kgsl_mmu.c | 10 ++++++++-- drivers/gpu/msm/kgsl_sharedmem.c | 1 + drivers/gpu/msm/kgsl_sharedmem.h | 3 ++- 6 files changed, 57 insertions(+), 7 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index 1f6e562bad67..a9b6e24728c7 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -2428,6 +2428,8 @@ long kgsl_ioctl_gpuobj_import(struct kgsl_device_private *dev_priv, if (entry == NULL) return -ENOMEM; + spin_lock_init(&entry->memdesc.lock); + param->flags &= KGSL_MEMFLAGS_GPUREADONLY | KGSL_MEMTYPE_MASK | KGSL_MEMALIGN_MASK @@ -2701,6 +2703,8 @@ long kgsl_ioctl_map_user_mem(struct kgsl_device_private *dev_priv, if (entry == NULL) return -ENOMEM; + spin_lock_init(&entry->memdesc.lock); + /* * Convert from enum value to KGSL_MEM_ENTRY value, so that * we can use the latter consistently everywhere. @@ -3481,6 +3485,8 @@ long kgsl_ioctl_sparse_virt_alloc(struct kgsl_device_private *dev_priv, if (entry == NULL) return -ENOMEM; + spin_lock_init(&entry->memdesc.lock); + entry->memdesc.flags = KGSL_MEMFLAGS_SPARSE_VIRT; entry->memdesc.size = param->size; entry->memdesc.cur_bindings = 0; @@ -4334,19 +4340,34 @@ static unsigned long _gpu_set_svm_region(struct kgsl_process_private *private, { int ret; + /* + * Protect access to the gpuaddr here to prevent multiple vmas from + * trying to map a SVM region at the same time + */ + spin_lock(&entry->memdesc.lock); + + if (entry->memdesc.gpuaddr) { + spin_unlock(&entry->memdesc.lock); + return (unsigned long) -EBUSY; + } + ret = kgsl_mmu_set_svm_region(private->pagetable, (uint64_t) addr, (uint64_t) size); - if (ret != 0) - return ret; + if (ret != 0) { + spin_unlock(&entry->memdesc.lock); + return (unsigned long) ret; + } entry->memdesc.gpuaddr = (uint64_t) addr; + spin_unlock(&entry->memdesc.lock); + entry->memdesc.pagetable = private->pagetable; ret = kgsl_mmu_map(private->pagetable, &entry->memdesc); if (ret) { kgsl_mmu_put_gpuaddr(&entry->memdesc); - return ret; + return (unsigned long) ret; } kgsl_memfree_purge(private->pagetable, entry->memdesc.gpuaddr, @@ -4409,6 +4430,14 @@ static unsigned long _search_range(struct kgsl_process_private *private, result = _gpu_set_svm_region(private, entry, cpu, len); if (!IS_ERR_VALUE(result)) break; + /* + * _gpu_set_svm_region will return -EBUSY if we tried to set up + * SVM on an object that already has a GPU address. If + * that happens don't bother walking the rest of the + * region + */ + if ((long) result == -EBUSY) + return -EBUSY; trace_kgsl_mem_unmapped_area_collision(entry, cpu, len); diff --git a/drivers/gpu/msm/kgsl.h b/drivers/gpu/msm/kgsl.h index 6e6edf67064d..31257d291c7e 100644 --- a/drivers/gpu/msm/kgsl.h +++ b/drivers/gpu/msm/kgsl.h @@ -226,6 +226,11 @@ struct kgsl_memdesc { struct page **pages; unsigned int page_count; unsigned int cur_bindings; + /* + * @lock: Spinlock to protect the gpuaddr from being accessed by + * multiple entities trying to map the same SVM region at once + */ + spinlock_t lock; }; /* diff --git a/drivers/gpu/msm/kgsl_iommu.c b/drivers/gpu/msm/kgsl_iommu.c index f6ff4658c93b..7d7e44893842 100644 --- a/drivers/gpu/msm/kgsl_iommu.c +++ b/drivers/gpu/msm/kgsl_iommu.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2020, The Linux Foundation. All rights reserved. +/* Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -270,6 +270,7 @@ static void kgsl_setup_qdss_desc(struct kgsl_device *device) return; } + spin_lock_init(&gpu_qdss_desc.lock); gpu_qdss_desc.flags = 0; gpu_qdss_desc.priv = 0; gpu_qdss_desc.physaddr = gpu_qdss_entry[0]; @@ -315,6 +316,7 @@ static void kgsl_setup_qtimer_desc(struct kgsl_device *device) return; } + spin_lock_init(&gpu_qtimer_desc.lock); gpu_qtimer_desc.flags = 0; gpu_qtimer_desc.priv = 0; gpu_qtimer_desc.physaddr = gpu_qtimer_entry[0]; @@ -1499,6 +1501,7 @@ static int _setstate_alloc(struct kgsl_device *device, { int ret; + spin_lock_init(&iommu->setstate.lock); ret = kgsl_sharedmem_alloc_contig(device, &iommu->setstate, PAGE_SIZE); if (!ret) { @@ -2477,6 +2480,11 @@ static int kgsl_iommu_get_gpuaddr(struct kgsl_pagetable *pagetable, goto out; } + /* + * This path is only called in a non-SVM path with locks so we can be + * sure we aren't racing with anybody so we don't need to worry about + * taking the lock + */ ret = _insert_gpuaddr(pagetable, addr, size); if (ret == 0) { memdesc->gpuaddr = addr; diff --git a/drivers/gpu/msm/kgsl_mmu.c b/drivers/gpu/msm/kgsl_mmu.c index aa7157e882ac..2303e8ee0721 100644 --- a/drivers/gpu/msm/kgsl_mmu.c +++ b/drivers/gpu/msm/kgsl_mmu.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2002,2007-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2002,2007-2017,2021, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -443,10 +443,16 @@ void kgsl_mmu_put_gpuaddr(struct kgsl_memdesc *memdesc) if (PT_OP_VALID(pagetable, put_gpuaddr) && (unmap_fail == 0)) pagetable->pt_ops->put_gpuaddr(memdesc); + memdesc->pagetable = NULL; + + /* + * If SVM tries to take a GPU address it will lose the race until the + * gpuaddr returns to zero so we shouldn't need to worry about taking a + * lock here + */ if (!kgsl_memdesc_is_global(memdesc)) memdesc->gpuaddr = 0; - memdesc->pagetable = NULL; } EXPORT_SYMBOL(kgsl_mmu_put_gpuaddr); diff --git a/drivers/gpu/msm/kgsl_sharedmem.c b/drivers/gpu/msm/kgsl_sharedmem.c index c343d90e564c..97e4f8b6fe2d 100644 --- a/drivers/gpu/msm/kgsl_sharedmem.c +++ b/drivers/gpu/msm/kgsl_sharedmem.c @@ -342,6 +342,7 @@ int kgsl_allocate_user(struct kgsl_device *device, int ret; memdesc->flags = flags; + spin_lock_init(&memdesc->lock); if (kgsl_mmu_get_mmutype(device) == KGSL_MMU_TYPE_NONE) ret = kgsl_sharedmem_alloc_contig(device, memdesc, size); diff --git a/drivers/gpu/msm/kgsl_sharedmem.h b/drivers/gpu/msm/kgsl_sharedmem.h index e5da594b77b8..ee89cfb808d7 100644 --- a/drivers/gpu/msm/kgsl_sharedmem.h +++ b/drivers/gpu/msm/kgsl_sharedmem.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2002,2007-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2002,2007-2017,2021, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -283,6 +283,7 @@ static inline int kgsl_allocate_global(struct kgsl_device *device, memdesc->flags = flags; memdesc->priv = priv; + spin_lock_init(&memdesc->lock); if (((memdesc->priv & KGSL_MEMDESC_CONTIG) != 0) || (kgsl_mmu_get_mmutype(device) == KGSL_MMU_TYPE_NONE)) -- cgit v1.2.3 From 4150552fac96e162e1c8607a94b2ad30b45d3620 Mon Sep 17 00:00:00 2001 From: Archana Sriram Date: Sun, 18 Oct 2020 23:34:04 +0530 Subject: msm: kgsl: Compare pid pointer instead of TGID for a new process There is a possibility of sharing process_private between two unrelated processes due to PID wrapping. In kgsl_process_private_new(), instead of checking numeric TGID, compare the unique pid pointer of the current process with that of the existing processes in kgsl process list to allow sharing of process_private data judiciously. Also, in all required functions get TGID/PID of a process from its struct pid. Change-Id: I0e3d5d79275cdb3f3c304fb36322ad56b0d0b227 Signed-off-by: Archana Sriram Signed-off-by: Kamal Agrawal --- drivers/gpu/msm/adreno_debugfs.c | 4 ++-- drivers/gpu/msm/adreno_dispatch.c | 2 +- drivers/gpu/msm/adreno_profile.c | 4 ++-- drivers/gpu/msm/kgsl.c | 30 +++++++++++++++++++----------- drivers/gpu/msm/kgsl_debugfs.c | 9 +++++---- drivers/gpu/msm/kgsl_device.h | 8 ++++---- drivers/gpu/msm/kgsl_iommu.c | 4 ++-- drivers/gpu/msm/kgsl_sharedmem.c | 4 ++-- drivers/gpu/msm/kgsl_trace.h | 10 +++++----- 9 files changed, 42 insertions(+), 33 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/adreno_debugfs.c b/drivers/gpu/msm/adreno_debugfs.c index 2027ac66f737..201e63f5e9dc 100644 --- a/drivers/gpu/msm/adreno_debugfs.c +++ b/drivers/gpu/msm/adreno_debugfs.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2002,2008-2017, The Linux Foundation. All rights reserved. +/* Copyright (c) 2002,2008-2017,2020, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -281,7 +281,7 @@ static int ctx_print(struct seq_file *s, void *unused) ctx_type_str(drawctxt->type), drawctxt->base.priority, drawctxt->base.proc_priv->comm, - drawctxt->base.proc_priv->pid, + pid_nr(drawctxt->base.proc_priv->pid), drawctxt->base.tid); seq_puts(s, "flags: "); diff --git a/drivers/gpu/msm/adreno_dispatch.c b/drivers/gpu/msm/adreno_dispatch.c index da9d4e17b7c7..265befe31b5e 100644 --- a/drivers/gpu/msm/adreno_dispatch.c +++ b/drivers/gpu/msm/adreno_dispatch.c @@ -1696,7 +1696,7 @@ static inline const char *_kgsl_context_comm(struct kgsl_context *context) #define pr_fault(_d, _c, fmt, args...) \ dev_err((_d)->dev, "%s[%d]: " fmt, \ _kgsl_context_comm((_c)->context), \ - (_c)->context->proc_priv->pid, ##args) + pid_nr((_c)->context->proc_priv->pid), ##args) static void adreno_fault_header(struct kgsl_device *device, diff --git a/drivers/gpu/msm/adreno_profile.c b/drivers/gpu/msm/adreno_profile.c index e756c82287fd..93501d96ef8e 100644 --- a/drivers/gpu/msm/adreno_profile.c +++ b/drivers/gpu/msm/adreno_profile.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2013-2016, 2018, The Linux Foundation. All rights reserved. +/* Copyright (c) 2013-2016, 2018,2020, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -168,7 +168,7 @@ static int _build_pre_ib_cmds(struct adreno_device *adreno_dev, ibcmds += _ib_cmd_mem_write(adreno_dev, ibcmds, gpuaddr + data_offset, drawctxt->base.id, &data_offset); ibcmds += _ib_cmd_mem_write(adreno_dev, ibcmds, gpuaddr + data_offset, - drawctxt->base.proc_priv->pid, &data_offset); + pid_nr(drawctxt->base.proc_priv->pid), &data_offset); ibcmds += _ib_cmd_mem_write(adreno_dev, ibcmds, gpuaddr + data_offset, drawctxt->base.tid, &data_offset); ibcmds += _ib_cmd_mem_write(adreno_dev, ibcmds, gpuaddr + data_offset, diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index a9b6e24728c7..8c8a51021e21 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -541,7 +541,7 @@ int kgsl_context_init(struct kgsl_device_private *dev_priv, if (atomic_read(&proc_priv->ctxt_count) > KGSL_MAX_CONTEXTS_PER_PROC) { KGSL_DRV_ERR_RATELIMIT(device, "Per process context limit reached for pid %u", - dev_priv->process_priv->pid); + pid_nr(dev_priv->process_priv->pid)); spin_unlock(&proc_priv->ctxt_count_lock); return -ENOSPC; } @@ -866,6 +866,7 @@ static void kgsl_destroy_process_private(struct kref *kref) struct kgsl_process_private *private = container_of(kref, struct kgsl_process_private, refcount); + put_pid(private->pid); idr_destroy(&private->mem_idr); idr_destroy(&private->syncsource_idr); @@ -896,7 +897,7 @@ struct kgsl_process_private *kgsl_process_private_find(pid_t pid) mutex_lock(&kgsl_driver.process_mutex); list_for_each_entry(p, &kgsl_driver.process_list, list) { - if (p->pid == pid) { + if (pid_nr(p->pid) == pid) { if (kgsl_process_private_get(p)) private = p; break; @@ -910,13 +911,15 @@ static struct kgsl_process_private *kgsl_process_private_new( struct kgsl_device *device) { struct kgsl_process_private *private; - pid_t tgid = task_tgid_nr(current); + struct pid *cur_pid = get_task_pid(current->group_leader, PIDTYPE_PID); /* Search in the process list */ list_for_each_entry(private, &kgsl_driver.process_list, list) { - if (private->pid == tgid) { - if (!kgsl_process_private_get(private)) + if (private->pid == cur_pid) { + if (!kgsl_process_private_get(private)) { + put_pid(cur_pid); private = ERR_PTR(-EINVAL); + } return private; } } @@ -928,7 +931,7 @@ static struct kgsl_process_private *kgsl_process_private_new( kref_init(&private->refcount); - private->pid = tgid; + private->pid = cur_pid; get_task_comm(private->comm, current->group_leader); spin_lock_init(&private->mem_lock); @@ -939,12 +942,14 @@ static struct kgsl_process_private *kgsl_process_private_new( idr_init(&private->syncsource_idr); /* Allocate a pagetable for the new process object */ - private->pagetable = kgsl_mmu_getpagetable(&device->mmu, tgid); + private->pagetable = kgsl_mmu_getpagetable(&device->mmu, + pid_nr(cur_pid)); if (IS_ERR(private->pagetable)) { int err = PTR_ERR(private->pagetable); idr_destroy(&private->mem_idr); idr_destroy(&private->syncsource_idr); + put_pid(private->pid); kfree(private); private = ERR_PTR(err); @@ -1878,8 +1883,9 @@ static long gpumem_free_entry(struct kgsl_mem_entry *entry) if (entry->memdesc.pagetable != NULL) ptname = entry->memdesc.pagetable->name; - kgsl_memfree_add(entry->priv->pid, ptname, entry->memdesc.gpuaddr, - entry->memdesc.size, entry->memdesc.flags); + kgsl_memfree_add(pid_nr(entry->priv->pid), ptname, + entry->memdesc.gpuaddr, entry->memdesc.size, + entry->memdesc.flags); kgsl_mem_entry_put(entry); @@ -4569,13 +4575,15 @@ kgsl_get_unmapped_area(struct file *file, unsigned long addr, if (IS_ERR_VALUE(val)) KGSL_DRV_ERR_RATELIMIT(device, "get_unmapped_area: pid %d addr %lx pgoff %lx len %ld failed error %d\n", - private->pid, addr, pgoff, len, (int) val); + pid_nr(private->pid), addr, + pgoff, len, (int) val); } else { val = _get_svm_area(private, entry, addr, len, flags); if (IS_ERR_VALUE(val)) KGSL_DRV_ERR_RATELIMIT(device, "_get_svm_area: pid %d mmap_base %lx addr %lx pgoff %lx len %ld failed error %d\n", - private->pid, current->mm->mmap_base, addr, + pid_nr(private->pid), + current->mm->mmap_base, addr, pgoff, len, (int) val); } diff --git a/drivers/gpu/msm/kgsl_debugfs.c b/drivers/gpu/msm/kgsl_debugfs.c index 9add4eef3483..a389566aa391 100644 --- a/drivers/gpu/msm/kgsl_debugfs.c +++ b/drivers/gpu/msm/kgsl_debugfs.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2002,2008-2017,2021 The Linux Foundation. All rights reserved. +/* Copyright (c) 2002,2008-2017,2020-2021 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -401,7 +401,7 @@ void kgsl_process_init_debugfs(struct kgsl_process_private *private) unsigned char name[16]; struct dentry *dentry; - snprintf(name, sizeof(name), "%d", private->pid); + snprintf(name, sizeof(name), "%d", pid_nr(private->pid)); private->debug_root = debugfs_create_dir(name, proc_d_debugfs); @@ -421,14 +421,15 @@ void kgsl_process_init_debugfs(struct kgsl_process_private *private) } dentry = debugfs_create_file("mem", 0444, private->debug_root, - (void *) ((unsigned long) private->pid), &process_mem_fops); + (void *) ((unsigned long) pid_nr(private->pid)), + &process_mem_fops); if (IS_ERR_OR_NULL(dentry)) WARN((dentry == NULL), "Unable to create 'mem' file for %s\n", name); dentry = debugfs_create_file("sparse_mem", 0444, private->debug_root, - (void *) ((unsigned long) private->pid), + (void *) ((unsigned long) pid_nr(private->pid)), &process_sparse_mem_fops); if (IS_ERR_OR_NULL(dentry)) diff --git a/drivers/gpu/msm/kgsl_device.h b/drivers/gpu/msm/kgsl_device.h index 0ab6041b3bbd..14852326116c 100644 --- a/drivers/gpu/msm/kgsl_device.h +++ b/drivers/gpu/msm/kgsl_device.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2002,2007-2019, The Linux Foundation. All rights reserved. +/* Copyright (c) 2002,2007-2020, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -405,13 +405,13 @@ struct kgsl_context { #define pr_context(_d, _c, fmt, args...) \ dev_err((_d)->dev, "%s[%d]: " fmt, \ _context_comm((_c)), \ - (_c)->proc_priv->pid, ##args) + pid_nr((_c)->proc_priv->pid), ##args) /** * struct kgsl_process_private - Private structure for a KGSL process (across * all devices) * @priv: Internal flags, use KGSL_PROCESS_* values - * @pid: ID for the task owner of the process + * @pid: Identification structure for the task owner of the process * @comm: task name of the process * @mem_lock: Spinlock to protect the process memory lists * @refcount: kref object for reference counting the process @@ -428,7 +428,7 @@ struct kgsl_context { */ struct kgsl_process_private { unsigned long priv; - pid_t pid; + struct pid *pid; char comm[TASK_COMM_LEN]; spinlock_t mem_lock; struct kref refcount; diff --git a/drivers/gpu/msm/kgsl_iommu.c b/drivers/gpu/msm/kgsl_iommu.c index 7d7e44893842..08f5c6d9d50b 100644 --- a/drivers/gpu/msm/kgsl_iommu.c +++ b/drivers/gpu/msm/kgsl_iommu.c @@ -694,7 +694,7 @@ static void _get_entries(struct kgsl_process_private *private, prev->flags = p->memdesc.flags; prev->priv = p->memdesc.priv; prev->pending_free = p->pending_free; - prev->pid = private->pid; + prev->pid = pid_nr(private->pid); __kgsl_get_memory_usage(prev); } @@ -704,7 +704,7 @@ static void _get_entries(struct kgsl_process_private *private, next->flags = n->memdesc.flags; next->priv = n->memdesc.priv; next->pending_free = n->pending_free; - next->pid = private->pid; + next->pid = pid_nr(private->pid); __kgsl_get_memory_usage(next); } } diff --git a/drivers/gpu/msm/kgsl_sharedmem.c b/drivers/gpu/msm/kgsl_sharedmem.c index 97e4f8b6fe2d..ae9685cd65d0 100644 --- a/drivers/gpu/msm/kgsl_sharedmem.c +++ b/drivers/gpu/msm/kgsl_sharedmem.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2002,2007-2017,2021 The Linux Foundation. All rights reserved. +/* Copyright (c) 2002,2007-2017,2020-2021 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -202,7 +202,7 @@ void kgsl_process_init_sysfs(struct kgsl_device *device, /* Keep private valid until the sysfs enries are removed. */ kgsl_process_private_get(private); - snprintf(name, sizeof(name), "%d", private->pid); + snprintf(name, sizeof(name), "%d", pid_nr(private->pid)); if (kobject_init_and_add(&private->kobj, &ktype_mem_entry, kgsl_driver.prockobj, name)) { diff --git a/drivers/gpu/msm/kgsl_trace.h b/drivers/gpu/msm/kgsl_trace.h index 2d80f00bf942..e77c51fb66df 100644 --- a/drivers/gpu/msm/kgsl_trace.h +++ b/drivers/gpu/msm/kgsl_trace.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2016,2021 The Linux Foundation. All rights reserved. +/* Copyright (c) 2011-2016,2020-2021 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -417,7 +417,7 @@ TRACE_EVENT(kgsl_mem_alloc, TP_fast_assign( __entry->gpuaddr = mem_entry->memdesc.gpuaddr; __entry->size = mem_entry->memdesc.size; - __entry->tgid = mem_entry->priv->pid; + __entry->tgid = pid_nr(mem_entry->priv->pid); kgsl_get_memory_usage(__entry->usage, sizeof(__entry->usage), mem_entry->memdesc.flags); __entry->id = mem_entry->id; @@ -510,7 +510,7 @@ TRACE_EVENT(kgsl_mem_map, __entry->size = mem_entry->memdesc.size; __entry->fd = fd; __entry->type = kgsl_memdesc_usermem_type(&mem_entry->memdesc); - __entry->tgid = mem_entry->priv->pid; + __entry->tgid = pid_nr(mem_entry->priv->pid); kgsl_get_memory_usage(__entry->usage, sizeof(__entry->usage), mem_entry->memdesc.flags); __entry->id = mem_entry->id; @@ -545,7 +545,7 @@ TRACE_EVENT(kgsl_mem_free, __entry->gpuaddr = mem_entry->memdesc.gpuaddr; __entry->size = mem_entry->memdesc.size; __entry->type = kgsl_memdesc_usermem_type(&mem_entry->memdesc); - __entry->tgid = mem_entry->priv->pid; + __entry->tgid = pid_nr(mem_entry->priv->pid); kgsl_get_memory_usage(__entry->usage, sizeof(__entry->usage), mem_entry->memdesc.flags); __entry->id = mem_entry->id; @@ -580,7 +580,7 @@ TRACE_EVENT(kgsl_mem_sync_cache, __entry->gpuaddr = mem_entry->memdesc.gpuaddr; kgsl_get_memory_usage(__entry->usage, sizeof(__entry->usage), mem_entry->memdesc.flags); - __entry->tgid = mem_entry->priv->pid; + __entry->tgid = pid_nr(mem_entry->priv->pid); __entry->id = mem_entry->id; __entry->op = op; __entry->offset = offset; -- cgit v1.2.3 From 8b5ba278ed4b370ce85cda5f997d1027db168ac1 Mon Sep 17 00:00:00 2001 From: Archana Sriram Date: Mon, 14 Dec 2020 16:55:35 +0530 Subject: msm: kgsl: Correct the refcount on current process PID. In kgsl_process_private_new() function there is inconsistency in the refcount of current process PID. Fix this to avoid overflowing of reference counter leading to use after free of this struct. Change-Id: I6291b9a05e139337e7f8471d0f9409fc839969a3 Signed-off-by: Archana Sriram --- drivers/gpu/msm/kgsl.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index 8c8a51021e21..43ec8d8ff88b 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -917,17 +917,24 @@ static struct kgsl_process_private *kgsl_process_private_new( list_for_each_entry(private, &kgsl_driver.process_list, list) { if (private->pid == cur_pid) { if (!kgsl_process_private_get(private)) { - put_pid(cur_pid); private = ERR_PTR(-EINVAL); } + /* + * We need to hold only one reference to the PID for + * each process struct to avoid overflowing the + * reference counter which can lead to use-after-free. + */ + put_pid(cur_pid); return private; } } /* Create a new object */ private = kzalloc(sizeof(struct kgsl_process_private), GFP_KERNEL); - if (private == NULL) + if (private == NULL) { + put_pid(cur_pid); return ERR_PTR(-ENOMEM); + } kref_init(&private->refcount); -- cgit v1.2.3 From 8fb3f17b3ad1d21fcb2f990d3fbcb45d6c1bdb05 Mon Sep 17 00:00:00 2001 From: Neeraja P Date: Thu, 7 Jan 2021 20:48:09 +0530 Subject: msm: kgsl: Deregister gpu address on memdesc_sg_virt failure When memdesc_sg_virt returns error in kgsl_setup_anon_useraddr function, the gpu address registered in SVM region will not be deregistered. This change deregisters the gpu address on memdesc_sg_virt failure. Change-Id: Ic99167e283a0c6331bb9f5f0b608b6cdb3c918e4 Signed-off-by: Neeraja P --- drivers/gpu/msm/kgsl.c | 10 ++++++++-- drivers/gpu/msm/kgsl_mmu.c | 3 ++- 2 files changed, 10 insertions(+), 3 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index 43ec8d8ff88b..58eac18d3e49 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -2200,6 +2200,8 @@ static int kgsl_setup_anon_useraddr(struct kgsl_pagetable *pagetable, { /* Map an anonymous memory chunk */ + int ret; + if (size == 0 || offset != 0 || !IS_ALIGNED(size, PAGE_SIZE)) return -EINVAL; @@ -2209,7 +2211,6 @@ static int kgsl_setup_anon_useraddr(struct kgsl_pagetable *pagetable, entry->memdesc.flags |= KGSL_MEMFLAGS_USERMEM_ADDR; if (kgsl_memdesc_use_cpu_map(&entry->memdesc)) { - int ret; /* Register the address in the database */ ret = kgsl_mmu_set_svm_region(pagetable, @@ -2221,7 +2222,12 @@ static int kgsl_setup_anon_useraddr(struct kgsl_pagetable *pagetable, entry->memdesc.gpuaddr = (uint64_t) hostptr; } - return memdesc_sg_virt(&entry->memdesc, hostptr); + ret = memdesc_sg_virt(&entry->memdesc, hostptr); + + if (ret && kgsl_memdesc_use_cpu_map(&entry->memdesc)) + kgsl_mmu_put_gpuaddr(&entry->memdesc); + + return ret; } static int match_file(const void *p, struct file *file, unsigned int fd) diff --git a/drivers/gpu/msm/kgsl_mmu.c b/drivers/gpu/msm/kgsl_mmu.c index 2303e8ee0721..d0a45e713d0d 100644 --- a/drivers/gpu/msm/kgsl_mmu.c +++ b/drivers/gpu/msm/kgsl_mmu.c @@ -432,7 +432,8 @@ void kgsl_mmu_put_gpuaddr(struct kgsl_memdesc *memdesc) if (memdesc->size == 0 || memdesc->gpuaddr == 0) return; - if (!kgsl_memdesc_is_global(memdesc)) + if (!kgsl_memdesc_is_global(memdesc) && + (KGSL_MEMDESC_MAPPED & memdesc->priv)) unmap_fail = kgsl_mmu_unmap(pagetable, memdesc); /* -- cgit v1.2.3 From 124ebd87ef2f05c9d61cd85c90e8e9790fd0d9b2 Mon Sep 17 00:00:00 2001 From: Kamal Agrawal Date: Mon, 13 Sep 2021 15:59:25 +0530 Subject: msm: kgsl: Fix out of bound write in adreno_profile_submit_time Make sure there is enough room in the memory descriptor to store the entire profiling buffer object. Change-Id: I1e1c73097bb2bba9645b0a3c66fdbbc71d8ba8fa Signed-off-by: Kamal Agrawal --- drivers/gpu/msm/kgsl_drawobj.c | 35 +++++++++++------------------------ 1 file changed, 11 insertions(+), 24 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/kgsl_drawobj.c b/drivers/gpu/msm/kgsl_drawobj.c index 9ba15b61af00..bc11111bb74f 100644 --- a/drivers/gpu/msm/kgsl_drawobj.c +++ b/drivers/gpu/msm/kgsl_drawobj.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2016-2017,2019, The Linux Foundation. All rights reserved. +/* Copyright (c) 2016-2017,2019,2021, The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -585,6 +585,7 @@ static void add_profiling_buffer(struct kgsl_device *device, { struct kgsl_mem_entry *entry; struct kgsl_drawobj *drawobj = DRAWOBJ(cmdobj); + u64 start; if (!(drawobj->flags & KGSL_DRAWOBJ_PROFILING)) return; @@ -601,7 +602,14 @@ static void add_profiling_buffer(struct kgsl_device *device, gpuaddr); if (entry != NULL) { - if (!kgsl_gpuaddr_in_memdesc(&entry->memdesc, gpuaddr, size)) { + start = id ? (entry->memdesc.gpuaddr + offset) : gpuaddr; + /* + * Make sure there is enough room in the object to store the + * entire profiling buffer object + */ + if (!kgsl_gpuaddr_in_memdesc(&entry->memdesc, gpuaddr, size) || + !kgsl_gpuaddr_in_memdesc(&entry->memdesc, start, + sizeof(struct kgsl_drawobj_profiling_buffer))) { kgsl_mem_entry_put(entry); entry = NULL; } @@ -614,28 +622,7 @@ static void add_profiling_buffer(struct kgsl_device *device, return; } - - if (!id) { - cmdobj->profiling_buffer_gpuaddr = gpuaddr; - } else { - u64 off = offset + sizeof(struct kgsl_drawobj_profiling_buffer); - - /* - * Make sure there is enough room in the object to store the - * entire profiling buffer object - */ - if (off < offset || off >= entry->memdesc.size) { - dev_err(device->dev, - "ignore invalid profile offset ctxt %d id %d offset %lld gpuaddr %llx size %lld\n", - drawobj->context->id, id, offset, gpuaddr, size); - kgsl_mem_entry_put(entry); - return; - } - - cmdobj->profiling_buffer_gpuaddr = - entry->memdesc.gpuaddr + offset; - } - + cmdobj->profiling_buffer_gpuaddr = start; cmdobj->profiling_buf_entry = entry; } -- cgit v1.2.3 From e392a84f25f5ac4ab684b9360a805c7dcfdc80bf Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Fri, 7 Jan 2022 02:20:41 +0530 Subject: msm: kgsl: Perform cache flush on the pages obtained using get_user_pages() Consider a scenario where user allocates anonymous memory but does not write to it. Here the physical pages are not yet allocated. Now when this memory is requested to be imported, a list of newly allocated zero pages is obtained using get_user_pages(). Currently cache flush is not done for these pages and hence GPU sees stale data. Fix this by performing cache flush on these pages. Change-Id: Id1e8aa20e8a9de112761732ed92f30c01088840b Signed-off-by: Puranam V G Tejaswi Signed-off-by: Sebanti Das Signed-off-by: Kamal Agrawal --- drivers/gpu/msm/kgsl.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index 58eac18d3e49..1d4b74350bbc 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -1,4 +1,5 @@ /* Copyright (c) 2008-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -2182,6 +2183,15 @@ static int memdesc_sg_virt(struct kgsl_memdesc *memdesc, unsigned long useraddr) ret = sg_alloc_table_from_pages(memdesc->sgt, pages, npages, 0, memdesc->size, GFP_KERNEL); + + if (ret) + goto out; + + ret = kgsl_cache_range_op(memdesc, 0, memdesc->size, + KGSL_CACHE_OP_FLUSH); + + if (ret) + sg_free_table(memdesc->sgt); out: if (ret) { for (i = 0; i < npages; i++) -- cgit v1.2.3 From 2967159ad303bf665c64467ad1328ee24cfda123 Mon Sep 17 00:00:00 2001 From: Pankaj Gupta Date: Tue, 1 Mar 2022 17:02:08 +0530 Subject: msm: kgsl: Add a sysfs node to control performance counter reads Currently performance counters are global and can be read by anyone. Change the behaviour to disable reading global counters as default and add a sysfs node to enable/disable reads. Change-Id: Ic3785acd9bd7425c2a844ed103d7b870d9f80adf Signed-off-by: Mohammed Mirza Mandayappurath Manzoor Signed-off-by: Harshitha Sai Neelati Signed-off-by: Pankaj Gupta Signed-off-by: Kamal Agrawal --- drivers/gpu/msm/adreno.c | 3 +++ drivers/gpu/msm/adreno.h | 7 +++++++ drivers/gpu/msm/adreno_compat.c | 9 +++++++++ drivers/gpu/msm/adreno_ioctl.c | 9 +++++++++ drivers/gpu/msm/adreno_sysfs.c | 33 ++++++++++++++++++++++++++++++++- 5 files changed, 60 insertions(+), 1 deletion(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/adreno.c b/drivers/gpu/msm/adreno.c index 6df074cd2876..902d024e5016 100644 --- a/drivers/gpu/msm/adreno.c +++ b/drivers/gpu/msm/adreno.c @@ -1,4 +1,5 @@ /* Copyright (c) 2002,2007-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1016,6 +1017,8 @@ static int adreno_probe(struct platform_device *pdev) adreno_debugfs_init(adreno_dev); adreno_profile_init(adreno_dev); + adreno_dev->perfcounter = false; + adreno_sysfs_init(adreno_dev); kgsl_pwrscale_init(&pdev->dev, CONFIG_QCOM_ADRENO_DEFAULT_GOVERNOR); diff --git a/drivers/gpu/msm/adreno.h b/drivers/gpu/msm/adreno.h index 5434cfb4083b..1868b0d5ec0a 100644 --- a/drivers/gpu/msm/adreno.h +++ b/drivers/gpu/msm/adreno.h @@ -1,4 +1,5 @@ /* Copyright (c) 2008-2018,2020 The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -439,6 +440,12 @@ struct adreno_device { struct list_head active_list; spinlock_t active_list_lock; + + /* + * @perfcounter: Flag to clear perfcounters across contexts and + * controls perfcounter ioctl read + */ + bool perfcounter; }; /** diff --git a/drivers/gpu/msm/adreno_compat.c b/drivers/gpu/msm/adreno_compat.c index 5a8d587d4536..faaec90ba74a 100644 --- a/drivers/gpu/msm/adreno_compat.c +++ b/drivers/gpu/msm/adreno_compat.c @@ -1,4 +1,5 @@ /* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -229,6 +230,14 @@ static long adreno_ioctl_perfcounter_read_compat( struct kgsl_perfcounter_read_compat *read32 = data; struct kgsl_perfcounter_read read; + /* + * When performance counter zapping is enabled, the counters are cleared + * across context switches. Reading the counters when they are zapped is + * not permitted. + */ + if (!adreno_dev->perfcounter) + return -EPERM; + read.reads = (struct kgsl_perfcounter_read_group __user *) (uintptr_t)read32->reads; read.count = read32->count; diff --git a/drivers/gpu/msm/adreno_ioctl.c b/drivers/gpu/msm/adreno_ioctl.c index 0d5e3e094c36..579ba2b61863 100644 --- a/drivers/gpu/msm/adreno_ioctl.c +++ b/drivers/gpu/msm/adreno_ioctl.c @@ -1,4 +1,5 @@ /* Copyright (c) 2002,2007-2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -76,6 +77,14 @@ static long adreno_ioctl_perfcounter_read(struct kgsl_device_private *dev_priv, struct adreno_device *adreno_dev = ADRENO_DEVICE(dev_priv->device); struct kgsl_perfcounter_read *read = data; + /* + * When performance counter zapping is enabled, the counters are cleared + * across context switches. Reading the counters when they are zapped is + * not permitted. + */ + if (!adreno_dev->perfcounter) + return -EPERM; + return (long) adreno_perfcounter_read_group(adreno_dev, read->reads, read->count); } diff --git a/drivers/gpu/msm/adreno_sysfs.c b/drivers/gpu/msm/adreno_sysfs.c index f893b29e816e..a6c179f7c932 100644 --- a/drivers/gpu/msm/adreno_sysfs.c +++ b/drivers/gpu/msm/adreno_sysfs.c @@ -1,4 +1,5 @@ /* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -233,6 +234,31 @@ static unsigned int _lm_show(struct adreno_device *adreno_dev) return test_bit(ADRENO_LM_CTRL, &adreno_dev->pwrctrl_flag); } +static unsigned int _perfcounter_show(struct adreno_device *adreno_dev) +{ + return adreno_dev->perfcounter; +} + +static int _perfcounter_store(struct adreno_device *adreno_dev, + unsigned int val) +{ + struct kgsl_device *device = KGSL_DEVICE(adreno_dev); + + if (adreno_dev->perfcounter == val) + return 0; + + mutex_lock(&device->mutex); + + /* Power down the GPU before changing the state */ + kgsl_pwrctrl_change_state(device, KGSL_STATE_SUSPEND); + adreno_dev->perfcounter = val; + kgsl_pwrctrl_change_state(device, KGSL_STATE_SLUMBER); + + mutex_unlock(&device->mutex); + + return 0; +} + static ssize_t _sysfs_store_u32(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) @@ -327,6 +353,7 @@ static ADRENO_SYSFS_BOOL(lm); static ADRENO_SYSFS_BOOL(preemption); static ADRENO_SYSFS_BOOL(hwcg); static ADRENO_SYSFS_BOOL(throttling); +static ADRENO_SYSFS_BOOL(perfcounter); @@ -343,6 +370,7 @@ static const struct device_attribute *_attr_list[] = { &adreno_attr_preemption.attr, &adreno_attr_hwcg.attr, &adreno_attr_throttling.attr, + &adreno_attr_perfcounter.attr, NULL, }; @@ -499,8 +527,11 @@ int adreno_sysfs_init(struct adreno_device *adreno_dev) ret = kgsl_create_device_sysfs_files(device->dev, _attr_list); /* Add the PPD directory and files */ - if (ret == 0) + if (ret == 0) { + /* Notify userspace */ + kobject_uevent(&device->dev->kobj, KOBJ_ADD); ppd_sysfs_init(adreno_dev); + } return 0; } -- cgit v1.2.3 From da8317596949c2d2f69c512db0cb93212e02e086 Mon Sep 17 00:00:00 2001 From: Abhishek Barman Date: Tue, 7 Dec 2021 12:50:34 +0530 Subject: msm: kgsl: Fix gpuaddr_in_range() to check upper bound Currently gpuaddr_in_range() accepts only the gpuaddr & returns true if it lies in valid range. But this does not mean that the entire buffer is within range. Modify the function to accept size as a parameter and check that both starting & ending points of buffer lie within mmu range. Change-Id: I1d722295b9a27e746bfdb6d3bf409ffe722193cb Signed-off-by: Rohan Sethi Signed-off-by: Abhishek Barman Signed-off-by: Kamal Agrawal --- drivers/gpu/msm/adreno_dispatch.c | 4 +++- drivers/gpu/msm/kgsl.c | 2 +- drivers/gpu/msm/kgsl_iommu.c | 10 ++++++---- drivers/gpu/msm/kgsl_mmu.c | 8 +++++--- drivers/gpu/msm/kgsl_mmu.h | 7 +++++-- 5 files changed, 20 insertions(+), 11 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/adreno_dispatch.c b/drivers/gpu/msm/adreno_dispatch.c index 265befe31b5e..196d1890672c 100644 --- a/drivers/gpu/msm/adreno_dispatch.c +++ b/drivers/gpu/msm/adreno_dispatch.c @@ -1,4 +1,5 @@ /* Copyright (c) 2013-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -1149,7 +1150,8 @@ static inline bool _verify_ib(struct kgsl_device_private *dev_priv, } /* Make sure that the address is mapped */ - if (!kgsl_mmu_gpuaddr_in_range(private->pagetable, ib->gpuaddr)) { + if (!kgsl_mmu_gpuaddr_in_range(private->pagetable, ib->gpuaddr, + ib->size)) { pr_context(device, context, "ctxt %d invalid ib gpuaddr %llX\n", context->id, ib->gpuaddr); return false; diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index 1d4b74350bbc..d82471f198ba 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -1273,7 +1273,7 @@ kgsl_sharedmem_find(struct kgsl_process_private *private, uint64_t gpuaddr) if (!private) return NULL; - if (!kgsl_mmu_gpuaddr_in_range(private->pagetable, gpuaddr)) + if (!kgsl_mmu_gpuaddr_in_range(private->pagetable, gpuaddr, 0)) return NULL; spin_lock(&private->mem_lock); diff --git a/drivers/gpu/msm/kgsl_iommu.c b/drivers/gpu/msm/kgsl_iommu.c index 08f5c6d9d50b..2017810a3c7d 100644 --- a/drivers/gpu/msm/kgsl_iommu.c +++ b/drivers/gpu/msm/kgsl_iommu.c @@ -1,4 +1,5 @@ /* Copyright (c) 2011-2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -2524,20 +2525,21 @@ static int kgsl_iommu_svm_range(struct kgsl_pagetable *pagetable, } static bool kgsl_iommu_addr_in_range(struct kgsl_pagetable *pagetable, - uint64_t gpuaddr) + uint64_t gpuaddr, uint64_t size) { struct kgsl_iommu_pt *pt = pagetable->priv; if (gpuaddr == 0) return false; - if (gpuaddr >= pt->va_start && gpuaddr < pt->va_end) + if (gpuaddr >= pt->va_start && (gpuaddr + size) < pt->va_end) return true; - if (gpuaddr >= pt->compat_va_start && gpuaddr < pt->compat_va_end) + if (gpuaddr >= pt->compat_va_start && + (gpuaddr + size) < pt->compat_va_end) return true; - if (gpuaddr >= pt->svm_start && gpuaddr < pt->svm_end) + if (gpuaddr >= pt->svm_start && (gpuaddr + size) < pt->svm_end) return true; return false; diff --git a/drivers/gpu/msm/kgsl_mmu.c b/drivers/gpu/msm/kgsl_mmu.c index d0a45e713d0d..228f3396ae90 100644 --- a/drivers/gpu/msm/kgsl_mmu.c +++ b/drivers/gpu/msm/kgsl_mmu.c @@ -1,4 +1,5 @@ /* Copyright (c) 2002,2007-2017,2021, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -606,10 +607,11 @@ enum kgsl_mmutype kgsl_mmu_get_mmutype(struct kgsl_device *device) EXPORT_SYMBOL(kgsl_mmu_get_mmutype); bool kgsl_mmu_gpuaddr_in_range(struct kgsl_pagetable *pagetable, - uint64_t gpuaddr) + uint64_t gpuaddr, uint64_t size) { if (PT_OP_VALID(pagetable, addr_in_range)) - return pagetable->pt_ops->addr_in_range(pagetable, gpuaddr); + return pagetable->pt_ops->addr_in_range(pagetable, + gpuaddr, size); return false; } @@ -645,7 +647,7 @@ EXPORT_SYMBOL(kgsl_mmu_get_qtimer_global_entry); */ static bool nommu_gpuaddr_in_range(struct kgsl_pagetable *pagetable, - uint64_t gpuaddr) + uint64_t gpuaddr, uint64_t size) { return (gpuaddr != 0) ? true : false; } diff --git a/drivers/gpu/msm/kgsl_mmu.h b/drivers/gpu/msm/kgsl_mmu.h index 505fe591a53e..fcf3b188f9f7 100644 --- a/drivers/gpu/msm/kgsl_mmu.h +++ b/drivers/gpu/msm/kgsl_mmu.h @@ -1,4 +1,5 @@ /* Copyright (c) 2002,2007-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -99,7 +100,8 @@ struct kgsl_mmu_pt_ops { int (*set_svm_region)(struct kgsl_pagetable *, uint64_t, uint64_t); int (*svm_range)(struct kgsl_pagetable *, uint64_t *, uint64_t *, uint64_t); - bool (*addr_in_range)(struct kgsl_pagetable *pagetable, uint64_t); + bool (*addr_in_range)(struct kgsl_pagetable *pagetable, + uint64_t, uint64_t); int (*mmu_map_offset)(struct kgsl_pagetable *pt, uint64_t virtaddr, uint64_t virtoffset, struct kgsl_memdesc *memdesc, uint64_t physoffset, @@ -187,7 +189,8 @@ unsigned int kgsl_virtaddr_to_physaddr(void *virtaddr); unsigned int kgsl_mmu_log_fault_addr(struct kgsl_mmu *mmu, u64 ttbr0, uint64_t addr); enum kgsl_mmutype kgsl_mmu_get_mmutype(struct kgsl_device *device); -bool kgsl_mmu_gpuaddr_in_range(struct kgsl_pagetable *pt, uint64_t gpuaddr); +bool kgsl_mmu_gpuaddr_in_range(struct kgsl_pagetable *pt, uint64_t gpuaddr, + uint64_t size); int kgsl_mmu_get_region(struct kgsl_pagetable *pagetable, uint64_t gpuaddr, uint64_t size); -- cgit v1.2.3 From 5a359aba0364672acc23726dc807945ae6ce6397 Mon Sep 17 00:00:00 2001 From: ravnar Date: Wed, 20 Apr 2022 19:51:19 +0530 Subject: msm: kgsl: Remove 'fd' dependency to get dma_buf handle Get the dma_buf handle directly from 'vm_file' after doing necessary checks on the file. Change-Id: Id5eec16588d64e4e28483b32bb52d4d3d9b86b99 Signed-off-by: ravnar Signed-off-by: Sanjay Yadav --- drivers/gpu/msm/kgsl.c | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index d82471f198ba..c41c5649490d 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -2240,15 +2240,6 @@ static int kgsl_setup_anon_useraddr(struct kgsl_pagetable *pagetable, return ret; } -static int match_file(const void *p, struct file *file, unsigned int fd) -{ - /* - * We must return fd + 1 because iterate_fd stops searching on - * non-zero return, but 0 is a valid fd. - */ - return (p == file) ? (fd + 1) : 0; -} - static void _setup_cache_mode(struct kgsl_mem_entry *entry, struct vm_area_struct *vma) { @@ -2287,8 +2278,6 @@ static int kgsl_setup_dmabuf_useraddr(struct kgsl_device *device, vma = find_vma(current->mm, hostptr); if (vma && vma->vm_file) { - int fd; - ret = check_vma_flags(vma, entry->memdesc.flags); if (ret) { up_read(¤t->mm->mmap_sem); @@ -2304,10 +2293,13 @@ static int kgsl_setup_dmabuf_useraddr(struct kgsl_device *device, return -EFAULT; } - /* Look for the fd that matches this the vma file */ - fd = iterate_fd(current->files, 0, match_file, vma->vm_file); - if (fd != 0) - dmabuf = dma_buf_get(fd - 1); + /* + * Take a refcount because dma_buf_put() decrements the + * refcount + */ + get_file(vma->vm_file); + + dmabuf = vma->vm_file->private_data; } if (IS_ERR_OR_NULL(dmabuf)) { -- cgit v1.2.3 From 59ceabe0d2423b929530d080c75a69f6feedabf0 Mon Sep 17 00:00:00 2001 From: Puranam V G Tejaswi Date: Tue, 14 Feb 2023 16:09:02 +0530 Subject: msm: kgsl: Use dma_buf_get() to get dma_buf structure Currently we don't ensure if vma->vm_file is associated with dma_buf. This can cause issues later when private_data from a non dma_buf file is used as dma_buf structure. Hence get the fd that is associated with vma->vm_file and use dma_buf_get() to get pointer to dma_buf structure. dma_buf_get() ensures that the file from the input fd is associated with dma_buf. Change-Id: Ib78aef8b16bedca5ca86d3a132278ff9f07dce73 Signed-off-by: Puranam V G Tejaswi --- drivers/gpu/msm/kgsl.c | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/kgsl.c b/drivers/gpu/msm/kgsl.c index fc6ab898ccaa..4b9f779e525e 100644 --- a/drivers/gpu/msm/kgsl.c +++ b/drivers/gpu/msm/kgsl.c @@ -1,5 +1,5 @@ /* Copyright (c) 2008-2021, The Linux Foundation. All rights reserved. - * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -2241,6 +2241,15 @@ static int kgsl_setup_anon_useraddr(struct kgsl_pagetable *pagetable, return ret; } +static int match_file(const void *p, struct file *file, unsigned int fd) +{ + /* + * We must return fd + 1 because iterate_fd stops searching on + * non-zero return, but 0 is a valid fd. + */ + return (p == file) ? (fd + 1) : 0; +} + static void _setup_cache_mode(struct kgsl_mem_entry *entry, struct vm_area_struct *vma) { @@ -2279,6 +2288,8 @@ static int kgsl_setup_dmabuf_useraddr(struct kgsl_device *device, vma = find_vma(current->mm, hostptr); if (vma && vma->vm_file) { + int fd; + ret = check_vma_flags(vma, entry->memdesc.flags); if (ret) { up_read(¤t->mm->mmap_sem); @@ -2294,13 +2305,27 @@ static int kgsl_setup_dmabuf_useraddr(struct kgsl_device *device, return -EFAULT; } - /* - * Take a refcount because dma_buf_put() decrements the - * refcount - */ - get_file(vma->vm_file); - - dmabuf = vma->vm_file->private_data; + /* Look for the fd that matches this vma file */ + fd = iterate_fd(current->files, 0, match_file, vma->vm_file); + if (fd) { + dmabuf = dma_buf_get(fd - 1); + if (IS_ERR(dmabuf)) { + up_read(¤t->mm->mmap_sem); + return PTR_ERR(dmabuf); + } + /* + * It is possible that the fd obtained from iterate_fd + * was closed before passing the fd to dma_buf_get(). + * Hence dmabuf returned by dma_buf_get() could be + * different from vma->vm_file->private_data. Return + * failure if this happens. + */ + if (dmabuf != vma->vm_file->private_data) { + dma_buf_put(dmabuf); + up_read(¤t->mm->mmap_sem); + return -EBADF; + } + } } if (IS_ERR_OR_NULL(dmabuf)) { -- cgit v1.2.3 From 95fc4fff573f91177290e2e07022f531c6524029 Mon Sep 17 00:00:00 2001 From: Kamal Agrawal Date: Mon, 13 Feb 2023 16:51:37 +0530 Subject: msm: kgsl: Make sure that pool pages don't have any extra references Before putting a page back in the pool be sure that it doesn't have any additional references that would be a signal that somebody else is looking at the page and that it would be a bad idea to keep it around and run the risk of accidentally handing it to a different process. Change-Id: Ic0dedbad0cf2ffb34b76ad23e393c5a911114b82 Signed-off-by: Jordan Crouse Signed-off-by: Kamal Agrawal --- drivers/gpu/msm/kgsl_pool.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/gpu') diff --git a/drivers/gpu/msm/kgsl_pool.c b/drivers/gpu/msm/kgsl_pool.c index cd5f8e3efe98..77bb50ded36f 100644 --- a/drivers/gpu/msm/kgsl_pool.c +++ b/drivers/gpu/msm/kgsl_pool.c @@ -1,4 +1,5 @@ /* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and @@ -83,6 +84,15 @@ _kgsl_pool_zero_page(struct page *p, unsigned int pool_order) static void _kgsl_pool_add_page(struct kgsl_page_pool *pool, struct page *p) { + /* + * Sanity check to make sure we don't re-pool a page that + * somebody else has a reference to. + */ + if (WARN_ON_ONCE(unlikely(page_count(p) > 1))) { + __free_pages(p, pool->pool_order); + return; + } + _kgsl_pool_zero_page(p, pool->pool_order); spin_lock(&pool->list_lock); -- cgit v1.2.3