| /* | 
|  * | 
|  * (C) COPYRIGHT 2011-2017 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 | 
|  * Foundation, and any use by you of this program is subject to the terms | 
|  * of such GNU licence. | 
|  * | 
|  * A copy of the licence is included with the program, and can also be obtained | 
|  * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, | 
|  * Boston, MA  02110-1301, USA. | 
|  * | 
|  */ | 
|   | 
|   | 
|   | 
| #include <linux/anon_inodes.h> | 
| #include <linux/atomic.h> | 
| #include <linux/hrtimer.h> | 
| #include <linux/jiffies.h> | 
| #include <linux/kthread.h> | 
| #include <linux/list.h> | 
| #include <linux/mm.h> | 
| #include <linux/poll.h> | 
| #include <linux/preempt.h> | 
| #include <linux/slab.h> | 
| #include <linux/wait.h> | 
|   | 
| #include <mali_kbase.h> | 
| #include <mali_kbase_hwaccess_instr.h> | 
| #include <mali_kbase_hwaccess_jm.h> | 
| #include <mali_kbase_hwcnt_reader.h> | 
| #include <mali_kbase_mem_linux.h> | 
| #include <mali_kbase_tlstream.h> | 
|   | 
| /*****************************************************************************/ | 
|   | 
| /* Hwcnt reader API version */ | 
| #define HWCNT_READER_API        1 | 
|   | 
| /* The number of nanoseconds in a second. */ | 
| #define NSECS_IN_SEC            1000000000ull /* ns */ | 
|   | 
| /* The time resolution of dumping service. */ | 
| #define DUMPING_RESOLUTION      500000ull /* ns */ | 
|   | 
| /* The maximal supported number of dumping buffers. */ | 
| #define MAX_BUFFER_COUNT        32 | 
|   | 
| /* Size and number of hw counters blocks. */ | 
| #define NR_CNT_BLOCKS_PER_GROUP 8 | 
| #define NR_CNT_PER_BLOCK        64 | 
| #define NR_BYTES_PER_CNT        4 | 
| #define NR_BYTES_PER_HDR        16 | 
| #define PRFCNT_EN_MASK_OFFSET   0x8 | 
|   | 
| /*****************************************************************************/ | 
|   | 
| enum { | 
|     SHADER_HWCNT_BM, | 
|     TILER_HWCNT_BM, | 
|     MMU_L2_HWCNT_BM, | 
|     JM_HWCNT_BM | 
| }; | 
|   | 
| enum vinstr_state { | 
|     VINSTR_IDLE, | 
|     VINSTR_DUMPING, | 
|     VINSTR_SUSPENDING, | 
|     VINSTR_SUSPENDED, | 
|     VINSTR_RESUMING | 
| }; | 
|   | 
| /** | 
|  * struct kbase_vinstr_context - vinstr context per device | 
|  * @lock:              protects the entire vinstr context | 
|  * @kbdev:             pointer to kbase device | 
|  * @kctx:              pointer to kbase context | 
|  * @vmap:              vinstr vmap for mapping hwcnt dump buffer | 
|  * @gpu_va:            GPU hwcnt dump buffer address | 
|  * @cpu_va:            the CPU side mapping of the hwcnt dump buffer | 
|  * @dump_size:         size of the dump buffer in bytes | 
|  * @bitmap:            current set of counters monitored, not always in sync | 
|  *                     with hardware | 
|  * @reprogram:         when true, reprogram hwcnt block with the new set of | 
|  *                     counters | 
|  * @state:             vinstr state | 
|  * @state_lock:        protects information about vinstr state | 
|  * @suspend_waitq:     notification queue to trigger state re-validation | 
|  * @suspend_cnt:       reference counter of vinstr's suspend state | 
|  * @suspend_work:      worker to execute on entering suspended state | 
|  * @resume_work:       worker to execute on leaving suspended state | 
|  * @nclients:          number of attached clients, pending or otherwise | 
|  * @waiting_clients:   head of list of clients being periodically sampled | 
|  * @idle_clients:      head of list of clients being idle | 
|  * @suspended_clients: head of list of clients being suspended | 
|  * @thread:            periodic sampling thread | 
|  * @waitq:             notification queue of sampling thread | 
|  * @request_pending:   request for action for sampling thread | 
|  */ | 
| struct kbase_vinstr_context { | 
|     struct mutex             lock; | 
|     struct kbase_device      *kbdev; | 
|     struct kbase_context     *kctx; | 
|   | 
|     struct kbase_vmap_struct vmap; | 
|     u64                      gpu_va; | 
|     void                     *cpu_va; | 
|     size_t                   dump_size; | 
|     u32                      bitmap[4]; | 
|     bool                     reprogram; | 
|   | 
|     enum vinstr_state        state; | 
|     struct spinlock          state_lock; | 
|     wait_queue_head_t        suspend_waitq; | 
|     unsigned int             suspend_cnt; | 
|     struct work_struct       suspend_work; | 
|     struct work_struct       resume_work; | 
|   | 
|     u32                      nclients; | 
|     struct list_head         waiting_clients; | 
|     struct list_head         idle_clients; | 
|     struct list_head         suspended_clients; | 
|   | 
|     struct task_struct       *thread; | 
|     wait_queue_head_t        waitq; | 
|     atomic_t                 request_pending; | 
| }; | 
|   | 
| /** | 
|  * struct kbase_vinstr_client - a vinstr client attached to a vinstr context | 
|  * @vinstr_ctx:    vinstr context client is attached to | 
|  * @list:          node used to attach this client to list in vinstr context | 
|  * @buffer_count:  number of buffers this client is using | 
|  * @event_mask:    events this client reacts to | 
|  * @dump_size:     size of one dump buffer in bytes | 
|  * @bitmap:        bitmap request for JM, TILER, SHADER and MMU counters | 
|  * @legacy_buffer: userspace hwcnt dump buffer (legacy interface) | 
|  * @kernel_buffer: kernel hwcnt dump buffer (kernel client interface) | 
|  * @accum_buffer:  temporary accumulation buffer for preserving counters | 
|  * @dump_time:     next time this clients shall request hwcnt dump | 
|  * @dump_interval: interval between periodic hwcnt dumps | 
|  * @dump_buffers:  kernel hwcnt dump buffers allocated by this client | 
|  * @dump_buffers_meta: metadata of dump buffers | 
|  * @meta_idx:      index of metadata being accessed by userspace | 
|  * @read_idx:      index of buffer read by userspace | 
|  * @write_idx:     index of buffer being written by dumping service | 
|  * @waitq:         client's notification queue | 
|  * @pending:       when true, client has attached but hwcnt not yet updated | 
|  */ | 
| struct kbase_vinstr_client { | 
|     struct kbase_vinstr_context        *vinstr_ctx; | 
|     struct list_head                   list; | 
|     unsigned int                       buffer_count; | 
|     u32                                event_mask; | 
|     size_t                             dump_size; | 
|     u32                                bitmap[4]; | 
|     void __user                        *legacy_buffer; | 
|     void                               *kernel_buffer; | 
|     void                               *accum_buffer; | 
|     u64                                dump_time; | 
|     u32                                dump_interval; | 
|     char                               *dump_buffers; | 
|     struct kbase_hwcnt_reader_metadata *dump_buffers_meta; | 
|     atomic_t                           meta_idx; | 
|     atomic_t                           read_idx; | 
|     atomic_t                           write_idx; | 
|     wait_queue_head_t                  waitq; | 
|     bool                               pending; | 
| }; | 
|   | 
| /** | 
|  * struct kbasep_vinstr_wake_up_timer - vinstr service thread wake up timer | 
|  * @hrtimer:    high resolution timer | 
|  * @vinstr_ctx: vinstr context | 
|  */ | 
| struct kbasep_vinstr_wake_up_timer { | 
|     struct hrtimer              hrtimer; | 
|     struct kbase_vinstr_context *vinstr_ctx; | 
| }; | 
|   | 
| /*****************************************************************************/ | 
|   | 
| static int kbasep_vinstr_service_task(void *data); | 
|   | 
| static unsigned int kbasep_vinstr_hwcnt_reader_poll( | 
|         struct file *filp, | 
|         poll_table  *wait); | 
| static long kbasep_vinstr_hwcnt_reader_ioctl( | 
|         struct file   *filp, | 
|         unsigned int  cmd, | 
|         unsigned long arg); | 
| static int kbasep_vinstr_hwcnt_reader_mmap( | 
|         struct file           *filp, | 
|         struct vm_area_struct *vma); | 
| static int kbasep_vinstr_hwcnt_reader_release( | 
|         struct inode *inode, | 
|         struct file  *filp); | 
|   | 
| /* The timeline stream file operations structure. */ | 
| static const struct file_operations vinstr_client_fops = { | 
|     .poll           = kbasep_vinstr_hwcnt_reader_poll, | 
|     .unlocked_ioctl = kbasep_vinstr_hwcnt_reader_ioctl, | 
|     .compat_ioctl   = kbasep_vinstr_hwcnt_reader_ioctl, | 
|     .mmap           = kbasep_vinstr_hwcnt_reader_mmap, | 
|     .release        = kbasep_vinstr_hwcnt_reader_release, | 
| }; | 
|   | 
| /*****************************************************************************/ | 
|   | 
| static int enable_hwcnt(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     struct kbase_context *kctx = vinstr_ctx->kctx; | 
|     struct kbase_device *kbdev = kctx->kbdev; | 
|     struct kbase_uk_hwcnt_setup setup; | 
|     int err; | 
|   | 
|     setup.dump_buffer = vinstr_ctx->gpu_va; | 
|     setup.jm_bm       = vinstr_ctx->bitmap[JM_HWCNT_BM]; | 
|     setup.tiler_bm    = vinstr_ctx->bitmap[TILER_HWCNT_BM]; | 
|     setup.shader_bm   = vinstr_ctx->bitmap[SHADER_HWCNT_BM]; | 
|     setup.mmu_l2_bm   = vinstr_ctx->bitmap[MMU_L2_HWCNT_BM]; | 
|   | 
|     /* Mark the context as active so the GPU is kept turned on */ | 
|     /* A suspend won't happen here, because we're in a syscall from a | 
|      * userspace thread. */ | 
|     kbase_pm_context_active(kbdev); | 
|   | 
|     /* Schedule the context in */ | 
|     kbasep_js_schedule_privileged_ctx(kbdev, kctx); | 
|     err = kbase_instr_hwcnt_enable_internal(kbdev, kctx, &setup); | 
|     if (err) { | 
|         /* Release the context. This had its own Power Manager Active | 
|          * reference */ | 
|         kbasep_js_release_privileged_ctx(kbdev, kctx); | 
|   | 
|         /* Also release our Power Manager Active reference */ | 
|         kbase_pm_context_idle(kbdev); | 
|     } | 
|   | 
|     return err; | 
| } | 
|   | 
| static void disable_hwcnt(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     struct kbase_context *kctx = vinstr_ctx->kctx; | 
|     struct kbase_device *kbdev = kctx->kbdev; | 
|     int err; | 
|   | 
|     err = kbase_instr_hwcnt_disable_internal(kctx); | 
|     if (err) { | 
|         dev_warn(kbdev->dev, "Failed to disable HW counters (ctx:%p)", | 
|                 kctx); | 
|         return; | 
|     } | 
|   | 
|     /* Release the context. This had its own Power Manager Active reference. */ | 
|     kbasep_js_release_privileged_ctx(kbdev, kctx); | 
|   | 
|     /* Also release our Power Manager Active reference. */ | 
|     kbase_pm_context_idle(kbdev); | 
|   | 
|     dev_dbg(kbdev->dev, "HW counters dumping disabled for context %p", kctx); | 
| } | 
|   | 
| static int reprogram_hwcnt(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     disable_hwcnt(vinstr_ctx); | 
|     return enable_hwcnt(vinstr_ctx); | 
| } | 
|   | 
| static void hwcnt_bitmap_set(u32 dst[4], u32 src[4]) | 
| { | 
|     dst[JM_HWCNT_BM]     = src[JM_HWCNT_BM]; | 
|     dst[TILER_HWCNT_BM]  = src[TILER_HWCNT_BM]; | 
|     dst[SHADER_HWCNT_BM] = src[SHADER_HWCNT_BM]; | 
|     dst[MMU_L2_HWCNT_BM] = src[MMU_L2_HWCNT_BM]; | 
| } | 
|   | 
| static void hwcnt_bitmap_union(u32 dst[4], u32 src[4]) | 
| { | 
|     dst[JM_HWCNT_BM]     |= src[JM_HWCNT_BM]; | 
|     dst[TILER_HWCNT_BM]  |= src[TILER_HWCNT_BM]; | 
|     dst[SHADER_HWCNT_BM] |= src[SHADER_HWCNT_BM]; | 
|     dst[MMU_L2_HWCNT_BM] |= src[MMU_L2_HWCNT_BM]; | 
| } | 
|   | 
| size_t kbase_vinstr_dump_size(struct kbase_device *kbdev) | 
| { | 
|     size_t dump_size; | 
|   | 
| #ifndef CONFIG_MALI_NO_MALI | 
|     if (kbase_hw_has_feature(kbdev, BASE_HW_FEATURE_V4)) { | 
|         u32 nr_cg; | 
|   | 
|         nr_cg = kbdev->gpu_props.num_core_groups; | 
|         dump_size = nr_cg * NR_CNT_BLOCKS_PER_GROUP * | 
|                 NR_CNT_PER_BLOCK * | 
|                 NR_BYTES_PER_CNT; | 
|     } else | 
| #endif /* CONFIG_MALI_NO_MALI */ | 
|     { | 
|         /* assume v5 for now */ | 
|         base_gpu_props *props = &kbdev->gpu_props.props; | 
|         u32 nr_l2 = props->l2_props.num_l2_slices; | 
|         u64 core_mask = props->coherency_info.group[0].core_mask; | 
|         u32 nr_blocks = fls64(core_mask); | 
|   | 
|         /* JM and tiler counter blocks are always present */ | 
|         dump_size = (2 + nr_l2 + nr_blocks) * | 
|                 NR_CNT_PER_BLOCK * | 
|                 NR_BYTES_PER_CNT; | 
|     } | 
|     return dump_size; | 
| } | 
| KBASE_EXPORT_TEST_API(kbase_vinstr_dump_size); | 
|   | 
| static size_t kbasep_vinstr_dump_size_ctx( | 
|         struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     return kbase_vinstr_dump_size(vinstr_ctx->kctx->kbdev); | 
| } | 
|   | 
| static int kbasep_vinstr_map_kernel_dump_buffer( | 
|         struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     struct kbase_va_region *reg; | 
|     struct kbase_context *kctx = vinstr_ctx->kctx; | 
|     u64 flags, nr_pages; | 
|   | 
|     flags = BASE_MEM_PROT_CPU_RD | BASE_MEM_PROT_GPU_WR; | 
|     vinstr_ctx->dump_size = kbasep_vinstr_dump_size_ctx(vinstr_ctx); | 
|     nr_pages = PFN_UP(vinstr_ctx->dump_size); | 
|   | 
|     reg = kbase_mem_alloc(kctx, nr_pages, nr_pages, 0, &flags, | 
|             &vinstr_ctx->gpu_va); | 
|     if (!reg) | 
|         return -ENOMEM; | 
|   | 
|     vinstr_ctx->cpu_va = kbase_vmap( | 
|             kctx, | 
|             vinstr_ctx->gpu_va, | 
|             vinstr_ctx->dump_size, | 
|             &vinstr_ctx->vmap); | 
|     if (!vinstr_ctx->cpu_va) { | 
|         kbase_mem_free(kctx, vinstr_ctx->gpu_va); | 
|         return -ENOMEM; | 
|     } | 
|   | 
|     return 0; | 
| } | 
|   | 
| static void kbasep_vinstr_unmap_kernel_dump_buffer( | 
|         struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     struct kbase_context *kctx = vinstr_ctx->kctx; | 
|   | 
|     kbase_vunmap(kctx, &vinstr_ctx->vmap); | 
|     kbase_mem_free(kctx, vinstr_ctx->gpu_va); | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_create_kctx - create kernel context for vinstr | 
|  * @vinstr_ctx: vinstr context | 
|  * Return: zero on success | 
|  */ | 
| static int kbasep_vinstr_create_kctx(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     struct kbase_device *kbdev = vinstr_ctx->kbdev; | 
|     struct kbasep_kctx_list_element *element; | 
|     unsigned long flags; | 
|     bool enable_backend = false; | 
|     int err; | 
|   | 
|     vinstr_ctx->kctx = kbase_create_context(vinstr_ctx->kbdev, true); | 
|     if (!vinstr_ctx->kctx) | 
|         return -ENOMEM; | 
|   | 
|     /* Map the master kernel dump buffer.  The HW dumps the counters | 
|      * into this memory region. */ | 
|     err = kbasep_vinstr_map_kernel_dump_buffer(vinstr_ctx); | 
|     if (err) { | 
|         kbase_destroy_context(vinstr_ctx->kctx); | 
|         vinstr_ctx->kctx = NULL; | 
|         return err; | 
|     } | 
|   | 
|     /* Add kernel context to list of contexts associated with device. */ | 
|     element = kzalloc(sizeof(*element), GFP_KERNEL); | 
|     if (element) { | 
|         element->kctx = vinstr_ctx->kctx; | 
|         mutex_lock(&kbdev->kctx_list_lock); | 
|         list_add(&element->link, &kbdev->kctx_list); | 
|   | 
|         /* Inform timeline client about new context. | 
|          * Do this while holding the lock to avoid tracepoint | 
|          * being created in both body and summary stream. */ | 
|         KBASE_TLSTREAM_TL_NEW_CTX( | 
|                 vinstr_ctx->kctx, | 
|                 (u32)(vinstr_ctx->kctx->id), | 
|                 (u32)(vinstr_ctx->kctx->tgid)); | 
|   | 
|         mutex_unlock(&kbdev->kctx_list_lock); | 
|     } else { | 
|         /* Don't treat this as a fail - just warn about it. */ | 
|         dev_warn(kbdev->dev, | 
|                 "couldn't add kctx to kctx_list\n"); | 
|     } | 
|   | 
|     /* Don't enable hardware counters if vinstr is suspended. | 
|      * Note that vinstr resume code is run under vinstr context lock, | 
|      * lower layer will be enabled as needed on resume. */ | 
|     spin_lock_irqsave(&vinstr_ctx->state_lock, flags); | 
|     if (VINSTR_IDLE == vinstr_ctx->state) | 
|         enable_backend = true; | 
|     spin_unlock_irqrestore(&vinstr_ctx->state_lock, flags); | 
|     if (enable_backend) | 
|         err = enable_hwcnt(vinstr_ctx); | 
|   | 
|     if (err) { | 
|         kbasep_vinstr_unmap_kernel_dump_buffer(vinstr_ctx); | 
|         kbase_destroy_context(vinstr_ctx->kctx); | 
|         if (element) { | 
|             mutex_lock(&kbdev->kctx_list_lock); | 
|             list_del(&element->link); | 
|             kfree(element); | 
|             mutex_unlock(&kbdev->kctx_list_lock); | 
|         } | 
|         KBASE_TLSTREAM_TL_DEL_CTX(vinstr_ctx->kctx); | 
|         vinstr_ctx->kctx = NULL; | 
|         return err; | 
|     } | 
|   | 
|     vinstr_ctx->thread = kthread_run( | 
|             kbasep_vinstr_service_task, | 
|             vinstr_ctx, | 
|             "mali_vinstr_service"); | 
|     if (!vinstr_ctx->thread) { | 
|         disable_hwcnt(vinstr_ctx); | 
|         kbasep_vinstr_unmap_kernel_dump_buffer(vinstr_ctx); | 
|         kbase_destroy_context(vinstr_ctx->kctx); | 
|         if (element) { | 
|             mutex_lock(&kbdev->kctx_list_lock); | 
|             list_del(&element->link); | 
|             kfree(element); | 
|             mutex_unlock(&kbdev->kctx_list_lock); | 
|         } | 
|         KBASE_TLSTREAM_TL_DEL_CTX(vinstr_ctx->kctx); | 
|         vinstr_ctx->kctx = NULL; | 
|         return -EFAULT; | 
|     } | 
|   | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_destroy_kctx - destroy vinstr's kernel context | 
|  * @vinstr_ctx: vinstr context | 
|  */ | 
| static void kbasep_vinstr_destroy_kctx(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     struct kbase_device             *kbdev = vinstr_ctx->kbdev; | 
|     struct kbasep_kctx_list_element *element; | 
|     struct kbasep_kctx_list_element *tmp; | 
|     bool                            found = false; | 
|   | 
|     /* Release hw counters dumping resources. */ | 
|     vinstr_ctx->thread = NULL; | 
|     disable_hwcnt(vinstr_ctx); | 
|     kbasep_vinstr_unmap_kernel_dump_buffer(vinstr_ctx); | 
|     kbase_destroy_context(vinstr_ctx->kctx); | 
|   | 
|     /* Remove kernel context from the device's contexts list. */ | 
|     mutex_lock(&kbdev->kctx_list_lock); | 
|     list_for_each_entry_safe(element, tmp, &kbdev->kctx_list, link) { | 
|         if (element->kctx == vinstr_ctx->kctx) { | 
|             list_del(&element->link); | 
|             kfree(element); | 
|             found = true; | 
|         } | 
|     } | 
|     mutex_unlock(&kbdev->kctx_list_lock); | 
|   | 
|     if (!found) | 
|         dev_warn(kbdev->dev, "kctx not in kctx_list\n"); | 
|   | 
|     /* Inform timeline client about context destruction. */ | 
|     KBASE_TLSTREAM_TL_DEL_CTX(vinstr_ctx->kctx); | 
|   | 
|     vinstr_ctx->kctx = NULL; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_attach_client - Attach a client to the vinstr core | 
|  * @vinstr_ctx:    vinstr context | 
|  * @buffer_count:  requested number of dump buffers | 
|  * @bitmap:        bitmaps describing which counters should be enabled | 
|  * @argp:          pointer where notification descriptor shall be stored | 
|  * @kernel_buffer: pointer to kernel side buffer | 
|  * | 
|  * Return: vinstr opaque client handle or NULL on failure | 
|  */ | 
| static struct kbase_vinstr_client *kbasep_vinstr_attach_client( | 
|         struct kbase_vinstr_context *vinstr_ctx, u32 buffer_count, | 
|         u32 bitmap[4], void *argp, void *kernel_buffer) | 
| { | 
|     struct task_struct         *thread = NULL; | 
|     struct kbase_vinstr_client *cli; | 
|   | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     if (buffer_count > MAX_BUFFER_COUNT | 
|         || (buffer_count & (buffer_count - 1))) | 
|         return NULL; | 
|   | 
|     cli = kzalloc(sizeof(*cli), GFP_KERNEL); | 
|     if (!cli) | 
|         return NULL; | 
|   | 
|     cli->vinstr_ctx   = vinstr_ctx; | 
|     cli->buffer_count = buffer_count; | 
|     cli->event_mask   = | 
|         (1 << BASE_HWCNT_READER_EVENT_MANUAL) | | 
|         (1 << BASE_HWCNT_READER_EVENT_PERIODIC); | 
|     cli->pending      = true; | 
|   | 
|     hwcnt_bitmap_set(cli->bitmap, bitmap); | 
|   | 
|     mutex_lock(&vinstr_ctx->lock); | 
|   | 
|     hwcnt_bitmap_union(vinstr_ctx->bitmap, cli->bitmap); | 
|     vinstr_ctx->reprogram = true; | 
|   | 
|     /* If this is the first client, create the vinstr kbase | 
|      * context. This context is permanently resident until the | 
|      * last client exits. */ | 
|     if (!vinstr_ctx->nclients) { | 
|         hwcnt_bitmap_set(vinstr_ctx->bitmap, cli->bitmap); | 
|         if (kbasep_vinstr_create_kctx(vinstr_ctx) < 0) | 
|             goto error; | 
|   | 
|         vinstr_ctx->reprogram = false; | 
|         cli->pending = false; | 
|     } | 
|   | 
|     /* The GPU resets the counter block every time there is a request | 
|      * to dump it. We need a per client kernel buffer for accumulating | 
|      * the counters. */ | 
|     cli->dump_size    = kbasep_vinstr_dump_size_ctx(vinstr_ctx); | 
|     cli->accum_buffer = kzalloc(cli->dump_size, GFP_KERNEL); | 
|     if (!cli->accum_buffer) | 
|         goto error; | 
|   | 
|     /* Prepare buffers. */ | 
|     if (cli->buffer_count) { | 
|         int *fd = (int *)argp; | 
|         size_t tmp; | 
|   | 
|         /* Allocate area for buffers metadata storage. */ | 
|         tmp = sizeof(struct kbase_hwcnt_reader_metadata) * | 
|             cli->buffer_count; | 
|         cli->dump_buffers_meta = kmalloc(tmp, GFP_KERNEL); | 
|         if (!cli->dump_buffers_meta) | 
|             goto error; | 
|   | 
|         /* Allocate required number of dumping buffers. */ | 
|         cli->dump_buffers = (char *)__get_free_pages( | 
|                 GFP_KERNEL | __GFP_ZERO, | 
|                 get_order(cli->dump_size * cli->buffer_count)); | 
|         if (!cli->dump_buffers) | 
|             goto error; | 
|   | 
|         /* Create descriptor for user-kernel data exchange. */ | 
|         *fd = anon_inode_getfd( | 
|                 "[mali_vinstr_desc]", | 
|                 &vinstr_client_fops, | 
|                 cli, | 
|                 O_RDONLY | O_CLOEXEC); | 
|         if (0 > *fd) | 
|             goto error; | 
|     } else if (kernel_buffer) { | 
|         cli->kernel_buffer = kernel_buffer; | 
|     } else { | 
|         cli->legacy_buffer = (void __user *)argp; | 
|     } | 
|   | 
|     atomic_set(&cli->read_idx, 0); | 
|     atomic_set(&cli->meta_idx, 0); | 
|     atomic_set(&cli->write_idx, 0); | 
|     init_waitqueue_head(&cli->waitq); | 
|   | 
|     vinstr_ctx->nclients++; | 
|     list_add(&cli->list, &vinstr_ctx->idle_clients); | 
|   | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     return cli; | 
|   | 
| error: | 
|     kfree(cli->dump_buffers_meta); | 
|     if (cli->dump_buffers) | 
|         free_pages( | 
|                 (unsigned long)cli->dump_buffers, | 
|                 get_order(cli->dump_size * cli->buffer_count)); | 
|     kfree(cli->accum_buffer); | 
|     if (!vinstr_ctx->nclients && vinstr_ctx->kctx) { | 
|         thread = vinstr_ctx->thread; | 
|         kbasep_vinstr_destroy_kctx(vinstr_ctx); | 
|     } | 
|     kfree(cli); | 
|   | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     /* Thread must be stopped after lock is released. */ | 
|     if (thread) | 
|         kthread_stop(thread); | 
|   | 
|     return NULL; | 
| } | 
|   | 
| void kbase_vinstr_detach_client(struct kbase_vinstr_client *cli) | 
| { | 
|     struct kbase_vinstr_context *vinstr_ctx; | 
|     struct kbase_vinstr_client  *iter, *tmp; | 
|     struct task_struct          *thread = NULL; | 
|     u32 zerobitmap[4] = { 0 }; | 
|     int cli_found = 0; | 
|   | 
|     KBASE_DEBUG_ASSERT(cli); | 
|     vinstr_ctx = cli->vinstr_ctx; | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     mutex_lock(&vinstr_ctx->lock); | 
|   | 
|     list_for_each_entry_safe(iter, tmp, &vinstr_ctx->idle_clients, list) { | 
|         if (iter == cli) { | 
|             vinstr_ctx->reprogram = true; | 
|             cli_found = 1; | 
|             list_del(&iter->list); | 
|             break; | 
|         } | 
|     } | 
|     if (!cli_found) { | 
|         list_for_each_entry_safe( | 
|                 iter, tmp, &vinstr_ctx->waiting_clients, list) { | 
|             if (iter == cli) { | 
|                 vinstr_ctx->reprogram = true; | 
|                 cli_found = 1; | 
|                 list_del(&iter->list); | 
|                 break; | 
|             } | 
|         } | 
|     } | 
|     KBASE_DEBUG_ASSERT(cli_found); | 
|   | 
|     kfree(cli->dump_buffers_meta); | 
|     free_pages( | 
|             (unsigned long)cli->dump_buffers, | 
|             get_order(cli->dump_size * cli->buffer_count)); | 
|     kfree(cli->accum_buffer); | 
|     kfree(cli); | 
|   | 
|     vinstr_ctx->nclients--; | 
|     if (!vinstr_ctx->nclients) { | 
|         thread = vinstr_ctx->thread; | 
|         kbasep_vinstr_destroy_kctx(vinstr_ctx); | 
|     } | 
|   | 
|     /* Rebuild context bitmap now that the client has detached */ | 
|     hwcnt_bitmap_set(vinstr_ctx->bitmap, zerobitmap); | 
|     list_for_each_entry(iter, &vinstr_ctx->idle_clients, list) | 
|         hwcnt_bitmap_union(vinstr_ctx->bitmap, iter->bitmap); | 
|     list_for_each_entry(iter, &vinstr_ctx->waiting_clients, list) | 
|         hwcnt_bitmap_union(vinstr_ctx->bitmap, iter->bitmap); | 
|   | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     /* Thread must be stopped after lock is released. */ | 
|     if (thread) | 
|         kthread_stop(thread); | 
| } | 
| KBASE_EXPORT_TEST_API(kbase_vinstr_detach_client); | 
|   | 
| /* Accumulate counters in the dump buffer */ | 
| static void accum_dump_buffer(void *dst, void *src, size_t dump_size) | 
| { | 
|     size_t block_size = NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT; | 
|     u32 *d = dst; | 
|     u32 *s = src; | 
|     size_t i, j; | 
|   | 
|     for (i = 0; i < dump_size; i += block_size) { | 
|         /* skip over the header block */ | 
|         d += NR_BYTES_PER_HDR / sizeof(u32); | 
|         s += NR_BYTES_PER_HDR / sizeof(u32); | 
|         for (j = 0; j < (block_size - NR_BYTES_PER_HDR) / sizeof(u32); j++) { | 
|             /* saturate result if addition would result in wraparound */ | 
|             if (U32_MAX - *d < *s) | 
|                 *d = U32_MAX; | 
|             else | 
|                 *d += *s; | 
|             d++; | 
|             s++; | 
|         } | 
|     } | 
| } | 
|   | 
| /* This is the Midgard v4 patch function.  It copies the headers for each | 
|  * of the defined blocks from the master kernel buffer and then patches up | 
|  * the performance counter enable mask for each of the blocks to exclude | 
|  * counters that were not requested by the client. */ | 
| static void patch_dump_buffer_hdr_v4( | 
|         struct kbase_vinstr_context *vinstr_ctx, | 
|         struct kbase_vinstr_client *cli) | 
| { | 
|     u32 *mask; | 
|     u8 *dst = cli->accum_buffer; | 
|     u8 *src = vinstr_ctx->cpu_va; | 
|     u32 nr_cg = vinstr_ctx->kctx->kbdev->gpu_props.num_core_groups; | 
|     size_t i, group_size, group; | 
|     enum { | 
|         SC0_BASE    = 0 * NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT, | 
|         SC1_BASE    = 1 * NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT, | 
|         SC2_BASE    = 2 * NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT, | 
|         SC3_BASE    = 3 * NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT, | 
|         TILER_BASE  = 4 * NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT, | 
|         MMU_L2_BASE = 5 * NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT, | 
|         JM_BASE     = 7 * NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT | 
|     }; | 
|   | 
|     group_size = NR_CNT_BLOCKS_PER_GROUP * | 
|             NR_CNT_PER_BLOCK * | 
|             NR_BYTES_PER_CNT; | 
|     for (i = 0; i < nr_cg; i++) { | 
|         group = i * group_size; | 
|         /* copy shader core headers */ | 
|         memcpy(&dst[group + SC0_BASE], &src[group + SC0_BASE], | 
|                NR_BYTES_PER_HDR); | 
|         memcpy(&dst[group + SC1_BASE], &src[group + SC1_BASE], | 
|                NR_BYTES_PER_HDR); | 
|         memcpy(&dst[group + SC2_BASE], &src[group + SC2_BASE], | 
|               NR_BYTES_PER_HDR); | 
|         memcpy(&dst[group + SC3_BASE], &src[group + SC3_BASE], | 
|               NR_BYTES_PER_HDR); | 
|   | 
|         /* copy tiler header */ | 
|         memcpy(&dst[group + TILER_BASE], &src[group + TILER_BASE], | 
|               NR_BYTES_PER_HDR); | 
|   | 
|         /* copy mmu header */ | 
|         memcpy(&dst[group + MMU_L2_BASE], &src[group + MMU_L2_BASE], | 
|               NR_BYTES_PER_HDR); | 
|   | 
|         /* copy job manager header */ | 
|         memcpy(&dst[group + JM_BASE], &src[group + JM_BASE], | 
|               NR_BYTES_PER_HDR); | 
|   | 
|         /* patch the shader core enable mask */ | 
|         mask = (u32 *)&dst[group + SC0_BASE + PRFCNT_EN_MASK_OFFSET]; | 
|         *mask &= cli->bitmap[SHADER_HWCNT_BM]; | 
|         mask = (u32 *)&dst[group + SC1_BASE + PRFCNT_EN_MASK_OFFSET]; | 
|         *mask &= cli->bitmap[SHADER_HWCNT_BM]; | 
|         mask = (u32 *)&dst[group + SC2_BASE + PRFCNT_EN_MASK_OFFSET]; | 
|         *mask &= cli->bitmap[SHADER_HWCNT_BM]; | 
|         mask = (u32 *)&dst[group + SC3_BASE + PRFCNT_EN_MASK_OFFSET]; | 
|         *mask &= cli->bitmap[SHADER_HWCNT_BM]; | 
|   | 
|         /* patch the tiler core enable mask */ | 
|         mask = (u32 *)&dst[group + TILER_BASE + PRFCNT_EN_MASK_OFFSET]; | 
|         *mask &= cli->bitmap[TILER_HWCNT_BM]; | 
|   | 
|         /* patch the mmu core enable mask */ | 
|         mask = (u32 *)&dst[group + MMU_L2_BASE + PRFCNT_EN_MASK_OFFSET]; | 
|         *mask &= cli->bitmap[MMU_L2_HWCNT_BM]; | 
|   | 
|         /* patch the job manager enable mask */ | 
|         mask = (u32 *)&dst[group + JM_BASE + PRFCNT_EN_MASK_OFFSET]; | 
|         *mask &= cli->bitmap[JM_HWCNT_BM]; | 
|     } | 
| } | 
|   | 
| /* This is the Midgard v5 patch function.  It copies the headers for each | 
|  * of the defined blocks from the master kernel buffer and then patches up | 
|  * the performance counter enable mask for each of the blocks to exclude | 
|  * counters that were not requested by the client. */ | 
| static void patch_dump_buffer_hdr_v5( | 
|         struct kbase_vinstr_context *vinstr_ctx, | 
|         struct kbase_vinstr_client *cli) | 
| { | 
|     struct kbase_device *kbdev = vinstr_ctx->kctx->kbdev; | 
|     u32 i, nr_l2; | 
|     u64 core_mask; | 
|     u32 *mask; | 
|     u8 *dst = cli->accum_buffer; | 
|     u8 *src = vinstr_ctx->cpu_va; | 
|     size_t block_size = NR_CNT_PER_BLOCK * NR_BYTES_PER_CNT; | 
|   | 
|     /* copy and patch job manager header */ | 
|     memcpy(dst, src, NR_BYTES_PER_HDR); | 
|     mask = (u32 *)&dst[PRFCNT_EN_MASK_OFFSET]; | 
|     *mask &= cli->bitmap[JM_HWCNT_BM]; | 
|     dst += block_size; | 
|     src += block_size; | 
|   | 
|     /* copy and patch tiler header */ | 
|     memcpy(dst, src, NR_BYTES_PER_HDR); | 
|     mask = (u32 *)&dst[PRFCNT_EN_MASK_OFFSET]; | 
|     *mask &= cli->bitmap[TILER_HWCNT_BM]; | 
|     dst += block_size; | 
|     src += block_size; | 
|   | 
|     /* copy and patch MMU/L2C headers */ | 
|     nr_l2 = kbdev->gpu_props.props.l2_props.num_l2_slices; | 
|     for (i = 0; i < nr_l2; i++) { | 
|         memcpy(dst, src, NR_BYTES_PER_HDR); | 
|         mask = (u32 *)&dst[PRFCNT_EN_MASK_OFFSET]; | 
|         *mask &= cli->bitmap[MMU_L2_HWCNT_BM]; | 
|         dst += block_size; | 
|         src += block_size; | 
|     } | 
|   | 
|     /* copy and patch shader core headers */ | 
|     core_mask = kbdev->gpu_props.props.coherency_info.group[0].core_mask; | 
|     while (0ull != core_mask) { | 
|         memcpy(dst, src, NR_BYTES_PER_HDR); | 
|         if (0ull != (core_mask & 1ull)) { | 
|             /* if block is not reserved update header */ | 
|             mask = (u32 *)&dst[PRFCNT_EN_MASK_OFFSET]; | 
|             *mask &= cli->bitmap[SHADER_HWCNT_BM]; | 
|         } | 
|         dst += block_size; | 
|         src += block_size; | 
|   | 
|         core_mask >>= 1; | 
|     } | 
| } | 
|   | 
| /** | 
|  * accum_clients - accumulate dumped hw counters for all known clients | 
|  * @vinstr_ctx: vinstr context | 
|  */ | 
| static void accum_clients(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     struct kbase_vinstr_client *iter; | 
|     int v4 = 0; | 
|   | 
| #ifndef CONFIG_MALI_NO_MALI | 
|     v4 = kbase_hw_has_feature(vinstr_ctx->kbdev, BASE_HW_FEATURE_V4); | 
| #endif | 
|   | 
|     list_for_each_entry(iter, &vinstr_ctx->idle_clients, list) { | 
|         /* Don't bother accumulating clients whose hwcnt requests | 
|          * have not yet been honoured. */ | 
|         if (iter->pending) | 
|             continue; | 
|         if (v4) | 
|             patch_dump_buffer_hdr_v4(vinstr_ctx, iter); | 
|         else | 
|             patch_dump_buffer_hdr_v5(vinstr_ctx, iter); | 
|         accum_dump_buffer( | 
|                 iter->accum_buffer, | 
|                 vinstr_ctx->cpu_va, | 
|                 iter->dump_size); | 
|     } | 
|     list_for_each_entry(iter, &vinstr_ctx->waiting_clients, list) { | 
|         /* Don't bother accumulating clients whose hwcnt requests | 
|          * have not yet been honoured. */ | 
|         if (iter->pending) | 
|             continue; | 
|         if (v4) | 
|             patch_dump_buffer_hdr_v4(vinstr_ctx, iter); | 
|         else | 
|             patch_dump_buffer_hdr_v5(vinstr_ctx, iter); | 
|         accum_dump_buffer( | 
|                 iter->accum_buffer, | 
|                 vinstr_ctx->cpu_va, | 
|                 iter->dump_size); | 
|     } | 
| } | 
|   | 
| /*****************************************************************************/ | 
|   | 
| /** | 
|  * kbasep_vinstr_get_timestamp - return timestamp | 
|  * | 
|  * Function returns timestamp value based on raw monotonic timer. Value will | 
|  * wrap around zero in case of overflow. | 
|  * | 
|  * Return: timestamp value | 
|  */ | 
| static u64 kbasep_vinstr_get_timestamp(void) | 
| { | 
|     struct timespec64 ts; | 
|   | 
|     ktime_get_raw_ts64(&ts); | 
|     return (u64)ts.tv_sec * NSECS_IN_SEC + ts.tv_nsec; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_add_dump_request - register client's dumping request | 
|  * @cli:             requesting client | 
|  * @waiting_clients: list of pending dumping requests | 
|  */ | 
| static void kbasep_vinstr_add_dump_request( | 
|         struct kbase_vinstr_client *cli, | 
|         struct list_head *waiting_clients) | 
| { | 
|     struct kbase_vinstr_client *tmp; | 
|   | 
|     if (list_empty(waiting_clients)) { | 
|         list_add(&cli->list, waiting_clients); | 
|         return; | 
|     } | 
|     list_for_each_entry(tmp, waiting_clients, list) { | 
|         if (tmp->dump_time > cli->dump_time) { | 
|             list_add_tail(&cli->list, &tmp->list); | 
|             return; | 
|         } | 
|     } | 
|     list_add_tail(&cli->list, waiting_clients); | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_collect_and_accumulate - collect hw counters via low level | 
|  *                                        dump and accumulate them for known | 
|  *                                        clients | 
|  * @vinstr_ctx: vinstr context | 
|  * @timestamp: pointer where collection timestamp will be recorded | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static int kbasep_vinstr_collect_and_accumulate( | 
|         struct kbase_vinstr_context *vinstr_ctx, u64 *timestamp) | 
| { | 
|     unsigned long flags; | 
|     int rcode; | 
|   | 
| #ifdef CONFIG_MALI_NO_MALI | 
|     /* The dummy model needs the CPU mapping. */ | 
|     gpu_model_set_dummy_prfcnt_base_cpu(vinstr_ctx->cpu_va); | 
| #endif | 
|   | 
|     spin_lock_irqsave(&vinstr_ctx->state_lock, flags); | 
|     if (VINSTR_IDLE != vinstr_ctx->state) { | 
|         spin_unlock_irqrestore(&vinstr_ctx->state_lock, flags); | 
|         return -EAGAIN; | 
|     } else { | 
|         vinstr_ctx->state = VINSTR_DUMPING; | 
|     } | 
|     spin_unlock_irqrestore(&vinstr_ctx->state_lock, flags); | 
|   | 
|     /* Request HW counters dump. | 
|      * Disable preemption to make dump timestamp more accurate. */ | 
|     preempt_disable(); | 
|     *timestamp = kbasep_vinstr_get_timestamp(); | 
|     rcode = kbase_instr_hwcnt_request_dump(vinstr_ctx->kctx); | 
|     preempt_enable(); | 
|   | 
|     if (!rcode) | 
|         rcode = kbase_instr_hwcnt_wait_for_dump(vinstr_ctx->kctx); | 
|     WARN_ON(rcode); | 
|   | 
|     spin_lock_irqsave(&vinstr_ctx->state_lock, flags); | 
|     switch (vinstr_ctx->state) | 
|     { | 
|     case VINSTR_SUSPENDING: | 
|         schedule_work(&vinstr_ctx->suspend_work); | 
|         break; | 
|     case VINSTR_DUMPING: | 
|         vinstr_ctx->state = VINSTR_IDLE; | 
|         wake_up_all(&vinstr_ctx->suspend_waitq); | 
|         break; | 
|     default: | 
|         break; | 
|     } | 
|     spin_unlock_irqrestore(&vinstr_ctx->state_lock, flags); | 
|   | 
|     /* Accumulate values of collected counters. */ | 
|     if (!rcode) | 
|         accum_clients(vinstr_ctx); | 
|   | 
|     return rcode; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_fill_dump_buffer - copy accumulated counters to empty kernel | 
|  *                                  buffer | 
|  * @cli:       requesting client | 
|  * @timestamp: timestamp when counters were collected | 
|  * @event_id:  id of event that caused triggered counters collection | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static int kbasep_vinstr_fill_dump_buffer( | 
|         struct kbase_vinstr_client *cli, u64 timestamp, | 
|         enum base_hwcnt_reader_event event_id) | 
| { | 
|     unsigned int write_idx = atomic_read(&cli->write_idx); | 
|     unsigned int read_idx  = atomic_read(&cli->read_idx); | 
|   | 
|     struct kbase_hwcnt_reader_metadata *meta; | 
|     void                               *buffer; | 
|   | 
|     /* Check if there is a place to copy HWC block into. */ | 
|     if (write_idx - read_idx == cli->buffer_count) | 
|         return -1; | 
|     write_idx %= cli->buffer_count; | 
|   | 
|     /* Fill in dump buffer and its metadata. */ | 
|     buffer = &cli->dump_buffers[write_idx * cli->dump_size]; | 
|     meta   = &cli->dump_buffers_meta[write_idx]; | 
|     meta->timestamp  = timestamp; | 
|     meta->event_id   = event_id; | 
|     meta->buffer_idx = write_idx; | 
|     memcpy(buffer, cli->accum_buffer, cli->dump_size); | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_fill_dump_buffer_legacy - copy accumulated counters to buffer | 
|  *                                         allocated in userspace | 
|  * @cli: requesting client | 
|  * | 
|  * Return: zero on success | 
|  * | 
|  * This is part of legacy ioctl interface. | 
|  */ | 
| static int kbasep_vinstr_fill_dump_buffer_legacy( | 
|         struct kbase_vinstr_client *cli) | 
| { | 
|     void __user  *buffer = cli->legacy_buffer; | 
|     int          rcode; | 
|   | 
|     /* Copy data to user buffer. */ | 
|     rcode = copy_to_user(buffer, cli->accum_buffer, cli->dump_size); | 
|     if (rcode) | 
|         pr_warn("error while copying buffer to user\n"); | 
|     return rcode; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_fill_dump_buffer_kernel - copy accumulated counters to buffer | 
|  *                                         allocated in kernel space | 
|  * @cli: requesting client | 
|  * | 
|  * Return: zero on success | 
|  * | 
|  * This is part of the kernel client interface. | 
|  */ | 
| static int kbasep_vinstr_fill_dump_buffer_kernel( | 
|         struct kbase_vinstr_client *cli) | 
| { | 
|     memcpy(cli->kernel_buffer, cli->accum_buffer, cli->dump_size); | 
|   | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_reprogram - reprogram hwcnt set collected by inst | 
|  * @vinstr_ctx: vinstr context | 
|  */ | 
| static void kbasep_vinstr_reprogram( | 
|         struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     unsigned long flags; | 
|     bool suspended = false; | 
|   | 
|     /* Don't enable hardware counters if vinstr is suspended. */ | 
|     spin_lock_irqsave(&vinstr_ctx->state_lock, flags); | 
|     if (VINSTR_IDLE != vinstr_ctx->state) | 
|         suspended = true; | 
|     spin_unlock_irqrestore(&vinstr_ctx->state_lock, flags); | 
|     if (suspended) | 
|         return; | 
|   | 
|     /* Change to suspended state is done while holding vinstr context | 
|      * lock. Below code will then no re-enable the instrumentation. */ | 
|   | 
|     if (vinstr_ctx->reprogram) { | 
|         struct kbase_vinstr_client *iter; | 
|   | 
|         if (!reprogram_hwcnt(vinstr_ctx)) { | 
|             vinstr_ctx->reprogram = false; | 
|             list_for_each_entry( | 
|                     iter, | 
|                     &vinstr_ctx->idle_clients, | 
|                     list) | 
|                 iter->pending = false; | 
|             list_for_each_entry( | 
|                     iter, | 
|                     &vinstr_ctx->waiting_clients, | 
|                     list) | 
|                 iter->pending = false; | 
|         } | 
|     } | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_update_client - copy accumulated counters to user readable | 
|  *                               buffer and notify the user | 
|  * @cli:       requesting client | 
|  * @timestamp: timestamp when counters were collected | 
|  * @event_id:  id of event that caused triggered counters collection | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static int kbasep_vinstr_update_client( | 
|         struct kbase_vinstr_client *cli, u64 timestamp, | 
|         enum base_hwcnt_reader_event event_id) | 
| { | 
|     int rcode = 0; | 
|   | 
|     /* Copy collected counters to user readable buffer. */ | 
|     if (cli->buffer_count) | 
|         rcode = kbasep_vinstr_fill_dump_buffer( | 
|                 cli, timestamp, event_id); | 
|     else if (cli->kernel_buffer) | 
|         rcode = kbasep_vinstr_fill_dump_buffer_kernel(cli); | 
|     else | 
|         rcode = kbasep_vinstr_fill_dump_buffer_legacy(cli); | 
|   | 
|     if (rcode) | 
|         goto exit; | 
|   | 
|   | 
|     /* Notify client. Make sure all changes to memory are visible. */ | 
|     wmb(); | 
|     atomic_inc(&cli->write_idx); | 
|     wake_up_interruptible(&cli->waitq); | 
|   | 
|     /* Prepare for next request. */ | 
|     memset(cli->accum_buffer, 0, cli->dump_size); | 
|   | 
| exit: | 
|     return rcode; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_wake_up_callback - vinstr wake up timer wake up function | 
|  * | 
|  * @hrtimer: high resolution timer | 
|  * | 
|  * Return: High resolution timer restart enum. | 
|  */ | 
| static enum hrtimer_restart kbasep_vinstr_wake_up_callback( | 
|         struct hrtimer *hrtimer) | 
| { | 
|     struct kbasep_vinstr_wake_up_timer *timer = | 
|         container_of( | 
|             hrtimer, | 
|             struct kbasep_vinstr_wake_up_timer, | 
|             hrtimer); | 
|   | 
|     KBASE_DEBUG_ASSERT(timer); | 
|   | 
|     atomic_set(&timer->vinstr_ctx->request_pending, 1); | 
|     wake_up_all(&timer->vinstr_ctx->waitq); | 
|   | 
|     return HRTIMER_NORESTART; | 
| } | 
|   | 
| #ifdef CONFIG_DEBUG_OBJECT_TIMERS | 
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)) | 
| /** | 
|  * kbase_destroy_hrtimer_on_stack - kernel's destroy_hrtimer_on_stack(), | 
|  *                                  rewritten | 
|  * | 
|  * @timer: high resolution timer | 
|  * | 
|  * destroy_hrtimer_on_stack() was exported only for 4.7.0 kernel so for | 
|  * earlier kernel versions it is not possible to call it explicitly. | 
|  * Since this function must accompany hrtimer_init_on_stack(), which | 
|  * has to be used for hrtimer initialization if CONFIG_DEBUG_OBJECT_TIMERS | 
|  * is defined in order to avoid the warning about object on stack not being | 
|  * annotated, we rewrite it here to be used for earlier kernel versions. | 
|  */ | 
| static void kbase_destroy_hrtimer_on_stack(struct hrtimer *timer) | 
| { | 
|     debug_object_free(timer, &hrtimer_debug_descr); | 
| } | 
| #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0) */ | 
| #endif /* CONFIG_DEBUG_OBJECT_TIMERS */ | 
|   | 
| /** | 
|  * kbasep_vinstr_service_task - HWC dumping service thread | 
|  * | 
|  * @data: Pointer to vinstr context structure. | 
|  * | 
|  * Return: Always returns zero. | 
|  */ | 
| static int kbasep_vinstr_service_task(void *data) | 
| { | 
|     struct kbase_vinstr_context        *vinstr_ctx = data; | 
|     struct kbasep_vinstr_wake_up_timer timer; | 
|   | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     hrtimer_init_on_stack(&timer.hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); | 
|   | 
|     timer.hrtimer.function = kbasep_vinstr_wake_up_callback; | 
|     timer.vinstr_ctx       = vinstr_ctx; | 
|   | 
|     while (!kthread_should_stop()) { | 
|         struct kbase_vinstr_client *cli = NULL; | 
|         struct kbase_vinstr_client *tmp; | 
|         int                        rcode; | 
|   | 
|         u64              timestamp = kbasep_vinstr_get_timestamp(); | 
|         u64              dump_time = 0; | 
|         struct list_head expired_requests; | 
|   | 
|         /* Hold lock while performing operations on lists of clients. */ | 
|         mutex_lock(&vinstr_ctx->lock); | 
|   | 
|         /* Closing thread must not interact with client requests. */ | 
|         if (current == vinstr_ctx->thread) { | 
|             atomic_set(&vinstr_ctx->request_pending, 0); | 
|   | 
|             if (!list_empty(&vinstr_ctx->waiting_clients)) { | 
|                 cli = list_first_entry( | 
|                         &vinstr_ctx->waiting_clients, | 
|                         struct kbase_vinstr_client, | 
|                         list); | 
|                 dump_time = cli->dump_time; | 
|             } | 
|         } | 
|   | 
|         if (!cli || ((s64)timestamp - (s64)dump_time < 0ll)) { | 
|             mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|             /* Sleep until next dumping event or service request. */ | 
|             if (cli) { | 
|                 u64 diff = dump_time - timestamp; | 
|   | 
|                 hrtimer_start( | 
|                         &timer.hrtimer, | 
|                         ns_to_ktime(diff), | 
|                         HRTIMER_MODE_REL); | 
|             } | 
|             wait_event( | 
|                     vinstr_ctx->waitq, | 
|                     atomic_read( | 
|                         &vinstr_ctx->request_pending) || | 
|                     kthread_should_stop()); | 
|             hrtimer_cancel(&timer.hrtimer); | 
|             continue; | 
|         } | 
|   | 
|         rcode = kbasep_vinstr_collect_and_accumulate(vinstr_ctx, | 
|                 ×tamp); | 
|   | 
|         INIT_LIST_HEAD(&expired_requests); | 
|   | 
|         /* Find all expired requests. */ | 
|         list_for_each_entry_safe( | 
|                 cli, | 
|                 tmp, | 
|                 &vinstr_ctx->waiting_clients, | 
|                 list) { | 
|             s64 tdiff = | 
|                 (s64)(timestamp + DUMPING_RESOLUTION) - | 
|                 (s64)cli->dump_time; | 
|             if (tdiff >= 0ll) { | 
|                 list_del(&cli->list); | 
|                 list_add(&cli->list, &expired_requests); | 
|             } else { | 
|                 break; | 
|             } | 
|         } | 
|   | 
|         /* Fill data for each request found. */ | 
|         list_for_each_entry_safe(cli, tmp, &expired_requests, list) { | 
|             /* Ensure that legacy buffer will not be used from | 
|              * this kthread context. */ | 
|             BUG_ON(0 == cli->buffer_count); | 
|             /* Expect only periodically sampled clients. */ | 
|             BUG_ON(0 == cli->dump_interval); | 
|   | 
|             if (!rcode) | 
|                 kbasep_vinstr_update_client( | 
|                         cli, | 
|                         timestamp, | 
|                         BASE_HWCNT_READER_EVENT_PERIODIC); | 
|   | 
|             /* Set new dumping time. Drop missed probing times. */ | 
|             do { | 
|                 cli->dump_time += cli->dump_interval; | 
|             } while (cli->dump_time < timestamp); | 
|   | 
|             list_del(&cli->list); | 
|             kbasep_vinstr_add_dump_request( | 
|                     cli, | 
|                     &vinstr_ctx->waiting_clients); | 
|         } | 
|   | 
|         /* Reprogram counters set if required. */ | 
|         kbasep_vinstr_reprogram(vinstr_ctx); | 
|   | 
|         mutex_unlock(&vinstr_ctx->lock); | 
|     } | 
|   | 
| #ifdef CONFIG_DEBUG_OBJECTS_TIMERS | 
| #if (LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)) | 
|     kbase_destroy_hrtimer_on_stack(&timer.hrtimer); | 
| #else | 
|     destroy_hrtimer_on_stack(&timer.hrtimer); | 
| #endif /* (LINUX_VERSION_CODE < KERNEL_VERSION(4, 7, 0)) */ | 
| #endif /* CONFIG_DEBUG_OBJECTS_TIMERS */ | 
|   | 
|     return 0; | 
| } | 
|   | 
| /*****************************************************************************/ | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_buffer_ready - check if client has ready buffers | 
|  * @cli: pointer to vinstr client structure | 
|  * | 
|  * Return: non-zero if client has at least one dumping buffer filled that was | 
|  *         not notified to user yet | 
|  */ | 
| static int kbasep_vinstr_hwcnt_reader_buffer_ready( | 
|         struct kbase_vinstr_client *cli) | 
| { | 
|     KBASE_DEBUG_ASSERT(cli); | 
|     return atomic_read(&cli->write_idx) != atomic_read(&cli->meta_idx); | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_ioctl_get_buffer - hwcnt reader's ioctl command | 
|  * @cli:    pointer to vinstr client structure | 
|  * @buffer: pointer to userspace buffer | 
|  * @size:   size of buffer | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static long kbasep_vinstr_hwcnt_reader_ioctl_get_buffer( | 
|         struct kbase_vinstr_client *cli, void __user *buffer, | 
|         size_t size) | 
| { | 
|     unsigned int meta_idx = atomic_read(&cli->meta_idx); | 
|     unsigned int idx = meta_idx % cli->buffer_count; | 
|   | 
|     struct kbase_hwcnt_reader_metadata *meta = &cli->dump_buffers_meta[idx]; | 
|   | 
|     /* Metadata sanity check. */ | 
|     KBASE_DEBUG_ASSERT(idx == meta->buffer_idx); | 
|   | 
|     if (sizeof(struct kbase_hwcnt_reader_metadata) != size) | 
|         return -EINVAL; | 
|   | 
|     /* Check if there is any buffer available. */ | 
|     if (atomic_read(&cli->write_idx) == meta_idx) | 
|         return -EAGAIN; | 
|   | 
|     /* Check if previously taken buffer was put back. */ | 
|     if (atomic_read(&cli->read_idx) != meta_idx) | 
|         return -EBUSY; | 
|   | 
|     /* Copy next available buffer's metadata to user. */ | 
|     if (copy_to_user(buffer, meta, size)) | 
|         return -EFAULT; | 
|   | 
|     atomic_inc(&cli->meta_idx); | 
|   | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_ioctl_put_buffer - hwcnt reader's ioctl command | 
|  * @cli:    pointer to vinstr client structure | 
|  * @buffer: pointer to userspace buffer | 
|  * @size:   size of buffer | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static long kbasep_vinstr_hwcnt_reader_ioctl_put_buffer( | 
|         struct kbase_vinstr_client *cli, void __user *buffer, | 
|         size_t size) | 
| { | 
|     unsigned int read_idx = atomic_read(&cli->read_idx); | 
|     unsigned int idx = read_idx % cli->buffer_count; | 
|   | 
|     struct kbase_hwcnt_reader_metadata meta; | 
|   | 
|     if (sizeof(struct kbase_hwcnt_reader_metadata) != size) | 
|         return -EINVAL; | 
|   | 
|     /* Check if any buffer was taken. */ | 
|     if (atomic_read(&cli->meta_idx) == read_idx) | 
|         return -EPERM; | 
|   | 
|     /* Check if correct buffer is put back. */ | 
|     if (copy_from_user(&meta, buffer, size)) | 
|         return -EFAULT; | 
|     if (idx != meta.buffer_idx) | 
|         return -EINVAL; | 
|   | 
|     atomic_inc(&cli->read_idx); | 
|   | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_ioctl_set_interval - hwcnt reader's ioctl command | 
|  * @cli:      pointer to vinstr client structure | 
|  * @interval: periodic dumping interval (disable periodic dumping if zero) | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static long kbasep_vinstr_hwcnt_reader_ioctl_set_interval( | 
|         struct kbase_vinstr_client *cli, u32 interval) | 
| { | 
|     struct kbase_vinstr_context *vinstr_ctx = cli->vinstr_ctx; | 
|   | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     mutex_lock(&vinstr_ctx->lock); | 
|   | 
|     list_del(&cli->list); | 
|   | 
|     cli->dump_interval = interval; | 
|   | 
|     /* If interval is non-zero, enable periodic dumping for this client. */ | 
|     if (cli->dump_interval) { | 
|         if (DUMPING_RESOLUTION > cli->dump_interval) | 
|             cli->dump_interval = DUMPING_RESOLUTION; | 
|         cli->dump_time = | 
|             kbasep_vinstr_get_timestamp() + cli->dump_interval; | 
|   | 
|         kbasep_vinstr_add_dump_request( | 
|                 cli, &vinstr_ctx->waiting_clients); | 
|   | 
|         atomic_set(&vinstr_ctx->request_pending, 1); | 
|         wake_up_all(&vinstr_ctx->waitq); | 
|     } else { | 
|         list_add(&cli->list, &vinstr_ctx->idle_clients); | 
|     } | 
|   | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_event_mask - return event mask for event id | 
|  * @event_id: id of event | 
|  * Return: event_mask or zero if event is not supported or maskable | 
|  */ | 
| static u32 kbasep_vinstr_hwcnt_reader_event_mask( | 
|         enum base_hwcnt_reader_event event_id) | 
| { | 
|     u32 event_mask = 0; | 
|   | 
|     switch (event_id) { | 
|     case BASE_HWCNT_READER_EVENT_PREJOB: | 
|     case BASE_HWCNT_READER_EVENT_POSTJOB: | 
|         /* These event are maskable. */ | 
|         event_mask = (1 << event_id); | 
|         break; | 
|   | 
|     case BASE_HWCNT_READER_EVENT_MANUAL: | 
|     case BASE_HWCNT_READER_EVENT_PERIODIC: | 
|         /* These event are non-maskable. */ | 
|     default: | 
|         /* These event are not supported. */ | 
|         break; | 
|     } | 
|   | 
|     return event_mask; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_ioctl_enable_event - hwcnt reader's ioctl command | 
|  * @cli:      pointer to vinstr client structure | 
|  * @event_id: id of event to enable | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static long kbasep_vinstr_hwcnt_reader_ioctl_enable_event( | 
|         struct kbase_vinstr_client *cli, | 
|         enum base_hwcnt_reader_event event_id) | 
| { | 
|     struct kbase_vinstr_context *vinstr_ctx = cli->vinstr_ctx; | 
|     u32                         event_mask; | 
|   | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     event_mask = kbasep_vinstr_hwcnt_reader_event_mask(event_id); | 
|     if (!event_mask) | 
|         return -EINVAL; | 
|   | 
|     mutex_lock(&vinstr_ctx->lock); | 
|     cli->event_mask |= event_mask; | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_ioctl_disable_event - hwcnt reader's ioctl command | 
|  * @cli:      pointer to vinstr client structure | 
|  * @event_id: id of event to disable | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static long kbasep_vinstr_hwcnt_reader_ioctl_disable_event( | 
|         struct kbase_vinstr_client *cli, | 
|         enum base_hwcnt_reader_event event_id) | 
| { | 
|     struct kbase_vinstr_context *vinstr_ctx = cli->vinstr_ctx; | 
|     u32                         event_mask; | 
|   | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     event_mask = kbasep_vinstr_hwcnt_reader_event_mask(event_id); | 
|     if (!event_mask) | 
|         return -EINVAL; | 
|   | 
|     mutex_lock(&vinstr_ctx->lock); | 
|     cli->event_mask &= ~event_mask; | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_ioctl_get_hwver - hwcnt reader's ioctl command | 
|  * @cli:   pointer to vinstr client structure | 
|  * @hwver: pointer to user buffer where hw version will be stored | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static long kbasep_vinstr_hwcnt_reader_ioctl_get_hwver( | 
|         struct kbase_vinstr_client *cli, u32 __user *hwver) | 
| { | 
| #ifndef CONFIG_MALI_NO_MALI | 
|     struct kbase_vinstr_context *vinstr_ctx = cli->vinstr_ctx; | 
| #endif | 
|   | 
|     u32                         ver = 5; | 
|   | 
| #ifndef CONFIG_MALI_NO_MALI | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|     if (kbase_hw_has_feature(vinstr_ctx->kbdev, BASE_HW_FEATURE_V4)) | 
|         ver = 4; | 
| #endif | 
|   | 
|     return put_user(ver, hwver); | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_ioctl - hwcnt reader's ioctl | 
|  * @filp:   pointer to file structure | 
|  * @cmd:    user command | 
|  * @arg:    command's argument | 
|  * | 
|  * Return: zero on success | 
|  */ | 
| static long kbasep_vinstr_hwcnt_reader_ioctl(struct file *filp, | 
|         unsigned int cmd, unsigned long arg) | 
| { | 
|     long                       rcode = 0; | 
|     struct kbase_vinstr_client *cli; | 
|   | 
|     KBASE_DEBUG_ASSERT(filp); | 
|   | 
|     cli = filp->private_data; | 
|     KBASE_DEBUG_ASSERT(cli); | 
|   | 
|     if (unlikely(KBASE_HWCNT_READER != _IOC_TYPE(cmd))) | 
|         return -EINVAL; | 
|   | 
|     switch (cmd) { | 
|     case KBASE_HWCNT_READER_GET_API_VERSION: | 
|         rcode = put_user(HWCNT_READER_API, (u32 __user *)arg); | 
|         break; | 
|     case KBASE_HWCNT_READER_GET_HWVER: | 
|         rcode = kbasep_vinstr_hwcnt_reader_ioctl_get_hwver( | 
|                 cli, (u32 __user *)arg); | 
|         break; | 
|     case KBASE_HWCNT_READER_GET_BUFFER_SIZE: | 
|         KBASE_DEBUG_ASSERT(cli->vinstr_ctx); | 
|         rcode = put_user( | 
|                 (u32)cli->vinstr_ctx->dump_size, | 
|                 (u32 __user *)arg); | 
|         break; | 
|     case KBASE_HWCNT_READER_DUMP: | 
|         rcode = kbase_vinstr_hwc_dump( | 
|                 cli, BASE_HWCNT_READER_EVENT_MANUAL); | 
|         break; | 
|     case KBASE_HWCNT_READER_CLEAR: | 
|         rcode = kbase_vinstr_hwc_clear(cli); | 
|         break; | 
|     case KBASE_HWCNT_READER_GET_BUFFER: | 
|         rcode = kbasep_vinstr_hwcnt_reader_ioctl_get_buffer( | 
|                 cli, (void __user *)arg, _IOC_SIZE(cmd)); | 
|         break; | 
|     case KBASE_HWCNT_READER_PUT_BUFFER: | 
|         rcode = kbasep_vinstr_hwcnt_reader_ioctl_put_buffer( | 
|                 cli, (void __user *)arg, _IOC_SIZE(cmd)); | 
|         break; | 
|     case KBASE_HWCNT_READER_SET_INTERVAL: | 
|         rcode = kbasep_vinstr_hwcnt_reader_ioctl_set_interval( | 
|                 cli, (u32)arg); | 
|         break; | 
|     case KBASE_HWCNT_READER_ENABLE_EVENT: | 
|         rcode = kbasep_vinstr_hwcnt_reader_ioctl_enable_event( | 
|                 cli, (enum base_hwcnt_reader_event)arg); | 
|         break; | 
|     case KBASE_HWCNT_READER_DISABLE_EVENT: | 
|         rcode = kbasep_vinstr_hwcnt_reader_ioctl_disable_event( | 
|                 cli, (enum base_hwcnt_reader_event)arg); | 
|         break; | 
|     default: | 
|         rcode = -EINVAL; | 
|         break; | 
|     } | 
|   | 
|     return rcode; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_poll - hwcnt reader's poll | 
|  * @filp: pointer to file structure | 
|  * @wait: pointer to poll table | 
|  * Return: POLLIN if data can be read without blocking, otherwise zero | 
|  */ | 
| static unsigned int kbasep_vinstr_hwcnt_reader_poll(struct file *filp, | 
|         poll_table *wait) | 
| { | 
|     struct kbase_vinstr_client *cli; | 
|   | 
|     KBASE_DEBUG_ASSERT(filp); | 
|     KBASE_DEBUG_ASSERT(wait); | 
|   | 
|     cli = filp->private_data; | 
|     KBASE_DEBUG_ASSERT(cli); | 
|   | 
|     poll_wait(filp, &cli->waitq, wait); | 
|     if (kbasep_vinstr_hwcnt_reader_buffer_ready(cli)) | 
|         return POLLIN; | 
|     return 0; | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_mmap - hwcnt reader's mmap | 
|  * @filp: pointer to file structure | 
|  * @vma:  pointer to vma structure | 
|  * Return: zero on success | 
|  */ | 
| static int kbasep_vinstr_hwcnt_reader_mmap(struct file *filp, | 
|         struct vm_area_struct *vma) | 
| { | 
|     struct kbase_vinstr_client *cli; | 
|     unsigned long size, addr, pfn, offset; | 
|     unsigned long vm_size = vma->vm_end - vma->vm_start; | 
|   | 
|     KBASE_DEBUG_ASSERT(filp); | 
|     KBASE_DEBUG_ASSERT(vma); | 
|   | 
|     cli = filp->private_data; | 
|     KBASE_DEBUG_ASSERT(cli); | 
|   | 
|     size = cli->buffer_count * cli->dump_size; | 
|   | 
|     if (vma->vm_pgoff > (size >> PAGE_SHIFT)) | 
|         return -EINVAL; | 
|   | 
|     offset = vma->vm_pgoff << PAGE_SHIFT; | 
|     if (vm_size > size - offset) | 
|         return -EINVAL; | 
|   | 
|     addr = __pa((unsigned long)cli->dump_buffers + offset); | 
|     pfn = addr >> PAGE_SHIFT; | 
|   | 
|     return remap_pfn_range( | 
|             vma, | 
|             vma->vm_start, | 
|             pfn, | 
|             vm_size, | 
|             vma->vm_page_prot); | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_hwcnt_reader_release - hwcnt reader's release | 
|  * @inode: pointer to inode structure | 
|  * @filp:  pointer to file structure | 
|  * Return always return zero | 
|  */ | 
| static int kbasep_vinstr_hwcnt_reader_release(struct inode *inode, | 
|         struct file *filp) | 
| { | 
|     struct kbase_vinstr_client *cli; | 
|   | 
|     KBASE_DEBUG_ASSERT(inode); | 
|     KBASE_DEBUG_ASSERT(filp); | 
|   | 
|     cli = filp->private_data; | 
|     KBASE_DEBUG_ASSERT(cli); | 
|   | 
|     kbase_vinstr_detach_client(cli); | 
|     return 0; | 
| } | 
|   | 
| /*****************************************************************************/ | 
|   | 
| /** | 
|  * kbasep_vinstr_kick_scheduler - trigger scheduler cycle | 
|  * @kbdev: pointer to kbase device structure | 
|  */ | 
| static void kbasep_vinstr_kick_scheduler(struct kbase_device *kbdev) | 
| { | 
|     struct kbasep_js_device_data *js_devdata = &kbdev->js_data; | 
|     unsigned long flags; | 
|   | 
|     down(&js_devdata->schedule_sem); | 
|     spin_lock_irqsave(&kbdev->hwaccess_lock, flags); | 
|     kbase_backend_slot_update(kbdev); | 
|     spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); | 
|     up(&js_devdata->schedule_sem); | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_suspend_worker - worker suspending vinstr module | 
|  * @data: pointer to work structure | 
|  */ | 
| static void kbasep_vinstr_suspend_worker(struct work_struct *data) | 
| { | 
|     struct kbase_vinstr_context *vinstr_ctx; | 
|     unsigned long flags; | 
|   | 
|     vinstr_ctx = container_of(data, struct kbase_vinstr_context, | 
|             suspend_work); | 
|   | 
|     mutex_lock(&vinstr_ctx->lock); | 
|   | 
|     if (vinstr_ctx->kctx) | 
|         disable_hwcnt(vinstr_ctx); | 
|   | 
|     spin_lock_irqsave(&vinstr_ctx->state_lock, flags); | 
|     vinstr_ctx->state = VINSTR_SUSPENDED; | 
|     wake_up_all(&vinstr_ctx->suspend_waitq); | 
|     spin_unlock_irqrestore(&vinstr_ctx->state_lock, flags); | 
|   | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     /* Kick GPU scheduler to allow entering protected mode. | 
|      * This must happen after vinstr was suspended. */ | 
|     kbasep_vinstr_kick_scheduler(vinstr_ctx->kbdev); | 
| } | 
|   | 
| /** | 
|  * kbasep_vinstr_suspend_worker - worker resuming vinstr module | 
|  * @data: pointer to work structure | 
|  */ | 
| static void kbasep_vinstr_resume_worker(struct work_struct *data) | 
| { | 
|     struct kbase_vinstr_context *vinstr_ctx; | 
|     unsigned long flags; | 
|   | 
|     vinstr_ctx = container_of(data, struct kbase_vinstr_context, | 
|             resume_work); | 
|   | 
|     mutex_lock(&vinstr_ctx->lock); | 
|   | 
|     if (vinstr_ctx->kctx) | 
|         enable_hwcnt(vinstr_ctx); | 
|   | 
|     spin_lock_irqsave(&vinstr_ctx->state_lock, flags); | 
|     vinstr_ctx->state = VINSTR_IDLE; | 
|     wake_up_all(&vinstr_ctx->suspend_waitq); | 
|     spin_unlock_irqrestore(&vinstr_ctx->state_lock, flags); | 
|   | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     /* Kick GPU scheduler to allow entering protected mode. | 
|      * Note that scheduler state machine might requested re-entry to | 
|      * protected mode before vinstr was resumed. | 
|      * This must happen after vinstr was release. */ | 
|     kbasep_vinstr_kick_scheduler(vinstr_ctx->kbdev); | 
| } | 
|   | 
| /*****************************************************************************/ | 
|   | 
| struct kbase_vinstr_context *kbase_vinstr_init(struct kbase_device *kbdev) | 
| { | 
|     struct kbase_vinstr_context *vinstr_ctx; | 
|   | 
|     vinstr_ctx = kzalloc(sizeof(*vinstr_ctx), GFP_KERNEL); | 
|     if (!vinstr_ctx) | 
|         return NULL; | 
|   | 
|     INIT_LIST_HEAD(&vinstr_ctx->idle_clients); | 
|     INIT_LIST_HEAD(&vinstr_ctx->waiting_clients); | 
|     mutex_init(&vinstr_ctx->lock); | 
|     spin_lock_init(&vinstr_ctx->state_lock); | 
|     vinstr_ctx->kbdev = kbdev; | 
|     vinstr_ctx->thread = NULL; | 
|     vinstr_ctx->state = VINSTR_IDLE; | 
|     vinstr_ctx->suspend_cnt = 0; | 
|     INIT_WORK(&vinstr_ctx->suspend_work, kbasep_vinstr_suspend_worker); | 
|     INIT_WORK(&vinstr_ctx->resume_work, kbasep_vinstr_resume_worker); | 
|     init_waitqueue_head(&vinstr_ctx->suspend_waitq); | 
|   | 
|     atomic_set(&vinstr_ctx->request_pending, 0); | 
|     init_waitqueue_head(&vinstr_ctx->waitq); | 
|   | 
|     return vinstr_ctx; | 
| } | 
|   | 
| void kbase_vinstr_term(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     struct kbase_vinstr_client *cli; | 
|   | 
|     /* Stop service thread first. */ | 
|     if (vinstr_ctx->thread) | 
|         kthread_stop(vinstr_ctx->thread); | 
|   | 
|     /* Wait for workers. */ | 
|     flush_work(&vinstr_ctx->suspend_work); | 
|     flush_work(&vinstr_ctx->resume_work); | 
|   | 
|     while (1) { | 
|         struct list_head *list = &vinstr_ctx->idle_clients; | 
|   | 
|         if (list_empty(list)) { | 
|             list = &vinstr_ctx->waiting_clients; | 
|             if (list_empty(list)) | 
|                 break; | 
|         } | 
|   | 
|         cli = list_first_entry(list, struct kbase_vinstr_client, list); | 
|         list_del(&cli->list); | 
|         kfree(cli->accum_buffer); | 
|         kfree(cli); | 
|         vinstr_ctx->nclients--; | 
|     } | 
|     KBASE_DEBUG_ASSERT(!vinstr_ctx->nclients); | 
|     if (vinstr_ctx->kctx) | 
|         kbasep_vinstr_destroy_kctx(vinstr_ctx); | 
|     kfree(vinstr_ctx); | 
| } | 
|   | 
| int kbase_vinstr_hwcnt_reader_setup(struct kbase_vinstr_context *vinstr_ctx, | 
|         struct kbase_uk_hwcnt_reader_setup *setup) | 
| { | 
|     struct kbase_vinstr_client  *cli; | 
|     u32                         bitmap[4]; | 
|   | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|     KBASE_DEBUG_ASSERT(setup); | 
|     KBASE_DEBUG_ASSERT(setup->buffer_count); | 
|   | 
|     bitmap[SHADER_HWCNT_BM] = setup->shader_bm; | 
|     bitmap[TILER_HWCNT_BM]  = setup->tiler_bm; | 
|     bitmap[MMU_L2_HWCNT_BM] = setup->mmu_l2_bm; | 
|     bitmap[JM_HWCNT_BM]     = setup->jm_bm; | 
|   | 
|     cli = kbasep_vinstr_attach_client( | 
|             vinstr_ctx, | 
|             setup->buffer_count, | 
|             bitmap, | 
|             &setup->fd, | 
|             NULL); | 
|   | 
|     if (!cli) | 
|         return -ENOMEM; | 
|   | 
|     return 0; | 
| } | 
|   | 
| int kbase_vinstr_legacy_hwc_setup( | 
|         struct kbase_vinstr_context *vinstr_ctx, | 
|         struct kbase_vinstr_client  **cli, | 
|         struct kbase_uk_hwcnt_setup *setup) | 
| { | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|     KBASE_DEBUG_ASSERT(setup); | 
|     KBASE_DEBUG_ASSERT(cli); | 
|   | 
|     if (setup->dump_buffer) { | 
|         u32 bitmap[4]; | 
|   | 
|         bitmap[SHADER_HWCNT_BM] = setup->shader_bm; | 
|         bitmap[TILER_HWCNT_BM]  = setup->tiler_bm; | 
|         bitmap[MMU_L2_HWCNT_BM] = setup->mmu_l2_bm; | 
|         bitmap[JM_HWCNT_BM]     = setup->jm_bm; | 
|   | 
|         if (*cli) | 
|             return -EBUSY; | 
|   | 
|         *cli = kbasep_vinstr_attach_client( | 
|                 vinstr_ctx, | 
|                 0, | 
|                 bitmap, | 
|                 (void *)(long)setup->dump_buffer, | 
|                 NULL); | 
|   | 
|         if (!(*cli)) | 
|             return -ENOMEM; | 
|     } else { | 
|         if (!*cli) | 
|             return -EINVAL; | 
|   | 
|         kbase_vinstr_detach_client(*cli); | 
|         *cli = NULL; | 
|     } | 
|   | 
|     return 0; | 
| } | 
|   | 
| struct kbase_vinstr_client *kbase_vinstr_hwcnt_kernel_setup( | 
|         struct kbase_vinstr_context *vinstr_ctx, | 
|         struct kbase_uk_hwcnt_reader_setup *setup, | 
|         void *kernel_buffer) | 
| { | 
|     u32 bitmap[4]; | 
|   | 
|     if (!vinstr_ctx || !setup || !kernel_buffer) | 
|         return NULL; | 
|   | 
|     bitmap[SHADER_HWCNT_BM] = setup->shader_bm; | 
|     bitmap[TILER_HWCNT_BM]  = setup->tiler_bm; | 
|     bitmap[MMU_L2_HWCNT_BM] = setup->mmu_l2_bm; | 
|     bitmap[JM_HWCNT_BM]     = setup->jm_bm; | 
|   | 
|     return kbasep_vinstr_attach_client( | 
|             vinstr_ctx, | 
|             0, | 
|             bitmap, | 
|             NULL, | 
|             kernel_buffer); | 
| } | 
| KBASE_EXPORT_TEST_API(kbase_vinstr_hwcnt_kernel_setup); | 
|   | 
| int kbase_vinstr_hwc_dump(struct kbase_vinstr_client *cli, | 
|         enum base_hwcnt_reader_event event_id) | 
| { | 
|     int                         rcode = 0; | 
|     struct kbase_vinstr_context *vinstr_ctx; | 
|     u64                         timestamp; | 
|     u32                         event_mask; | 
|   | 
|     if (!cli) | 
|         return -EINVAL; | 
|   | 
|     vinstr_ctx = cli->vinstr_ctx; | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     KBASE_DEBUG_ASSERT(event_id < BASE_HWCNT_READER_EVENT_COUNT); | 
|     event_mask = 1 << event_id; | 
|   | 
|     mutex_lock(&vinstr_ctx->lock); | 
|   | 
|     if (event_mask & cli->event_mask) { | 
|         rcode = kbasep_vinstr_collect_and_accumulate( | 
|                 vinstr_ctx, | 
|                 ×tamp); | 
|         if (rcode) | 
|             goto exit; | 
|   | 
|         rcode = kbasep_vinstr_update_client(cli, timestamp, event_id); | 
|         if (rcode) | 
|             goto exit; | 
|   | 
|         kbasep_vinstr_reprogram(vinstr_ctx); | 
|     } | 
|   | 
| exit: | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     return rcode; | 
| } | 
| KBASE_EXPORT_TEST_API(kbase_vinstr_hwc_dump); | 
|   | 
| int kbase_vinstr_hwc_clear(struct kbase_vinstr_client *cli) | 
| { | 
|     struct kbase_vinstr_context *vinstr_ctx; | 
|     int                         rcode; | 
|     u64                         unused; | 
|   | 
|     if (!cli) | 
|         return -EINVAL; | 
|   | 
|     vinstr_ctx = cli->vinstr_ctx; | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     mutex_lock(&vinstr_ctx->lock); | 
|   | 
|     rcode = kbasep_vinstr_collect_and_accumulate(vinstr_ctx, &unused); | 
|     if (rcode) | 
|         goto exit; | 
|     rcode = kbase_instr_hwcnt_clear(vinstr_ctx->kctx); | 
|     if (rcode) | 
|         goto exit; | 
|     memset(cli->accum_buffer, 0, cli->dump_size); | 
|   | 
|     kbasep_vinstr_reprogram(vinstr_ctx); | 
|   | 
| exit: | 
|     mutex_unlock(&vinstr_ctx->lock); | 
|   | 
|     return rcode; | 
| } | 
|   | 
| int kbase_vinstr_try_suspend(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     unsigned long flags; | 
|     int ret = -EAGAIN; | 
|   | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     spin_lock_irqsave(&vinstr_ctx->state_lock, flags); | 
|     switch (vinstr_ctx->state) { | 
|     case VINSTR_SUSPENDED: | 
|         vinstr_ctx->suspend_cnt++; | 
|         /* overflow shall not happen */ | 
|         BUG_ON(0 == vinstr_ctx->suspend_cnt); | 
|         ret = 0; | 
|         break; | 
|   | 
|     case VINSTR_IDLE: | 
|         vinstr_ctx->state = VINSTR_SUSPENDING; | 
|         schedule_work(&vinstr_ctx->suspend_work); | 
|         break; | 
|   | 
|     case VINSTR_DUMPING: | 
|         vinstr_ctx->state = VINSTR_SUSPENDING; | 
|         break; | 
|   | 
|     case VINSTR_SUSPENDING: | 
|         /* fall through */ | 
|     case VINSTR_RESUMING: | 
|         break; | 
|   | 
|     default: | 
|         BUG(); | 
|         break; | 
|     } | 
|     spin_unlock_irqrestore(&vinstr_ctx->state_lock, flags); | 
|   | 
|     return ret; | 
| } | 
|   | 
| void kbase_vinstr_suspend(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     wait_event(vinstr_ctx->suspend_waitq, | 
|             (0 == kbase_vinstr_try_suspend(vinstr_ctx))); | 
| } | 
|   | 
| void kbase_vinstr_resume(struct kbase_vinstr_context *vinstr_ctx) | 
| { | 
|     unsigned long flags; | 
|   | 
|     KBASE_DEBUG_ASSERT(vinstr_ctx); | 
|   | 
|     spin_lock_irqsave(&vinstr_ctx->state_lock, flags); | 
|     BUG_ON(VINSTR_SUSPENDING == vinstr_ctx->state); | 
|     if (VINSTR_SUSPENDED == vinstr_ctx->state) { | 
|         BUG_ON(0 == vinstr_ctx->suspend_cnt); | 
|         vinstr_ctx->suspend_cnt--; | 
|         if (0 == vinstr_ctx->suspend_cnt) { | 
|             vinstr_ctx->state = VINSTR_RESUMING; | 
|             schedule_work(&vinstr_ctx->resume_work); | 
|         } | 
|     } | 
|     spin_unlock_irqrestore(&vinstr_ctx->state_lock, flags); | 
| } |