| // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note |
| /* |
| * |
| * (C) COPYRIGHT 2021 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 license. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, you can access it online at |
| * http://www.gnu.org/licenses/gpl-2.0.html. |
| * |
| */ |
| |
| #include "mali_kbase_hwcnt_backend_csf.h" |
| #include "mali_kbase_hwcnt_gpu.h" |
| #include "mali_kbase_hwcnt_types.h" |
| |
| #include <linux/log2.h> |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/spinlock.h> |
| #include <linux/wait.h> |
| #include <linux/workqueue.h> |
| #include <linux/completion.h> |
| |
| #ifndef BASE_MAX_NR_CLOCKS_REGULATORS |
| #define BASE_MAX_NR_CLOCKS_REGULATORS 2 |
| #endif |
| |
| /** |
| * enum kbase_hwcnt_backend_csf_dump_state - HWC CSF backend dumping states. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE: Initial state, or the state if there is |
| * an error. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_DUMP_REQUESTED: A dump has been requested and we are |
| * waiting for an ACK, this ACK could come from either PRFCNT_ACK, |
| * PROTMODE_ENTER_ACK, or if an error occurs. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_DUMP_QUERYING_INSERT: Checking the insert |
| * immediately after receiving the ACK, so we know which index corresponds to |
| * the buffer we requested. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_DUMP_WORKER_LAUNCHED: The insert has been saved and |
| * now we have kicked off the worker. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_DUMP_ACCUMULATING: The insert has been saved and now |
| * we have kicked off the worker to accumulate up to that insert and then copy |
| * the delta to the user buffer to prepare for dump_get(). |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_DUMP_COMPLETED: The dump completed successfully. |
| * |
| * Valid state transitions: |
| * IDLE -> REQUESTED (on dump request) |
| * REQUESTED -> QUERYING_INSERT (on dump ack) |
| * QUERYING_INSERT -> WORKER_LAUNCHED (on worker submission) |
| * WORKER_LAUNCHED -> ACCUMULATING (while the worker is accumulating) |
| * ACCUMULATING -> COMPLETED (on accumulation completion) |
| * COMPLETED -> REQUESTED (on dump request) |
| * COMPLETED -> IDLE (on disable) |
| * ANY -> IDLE (on error) |
| */ |
| enum kbase_hwcnt_backend_csf_dump_state { |
| KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE, |
| KBASE_HWCNT_BACKEND_CSF_DUMP_REQUESTED, |
| KBASE_HWCNT_BACKEND_CSF_DUMP_QUERYING_INSERT, |
| KBASE_HWCNT_BACKEND_CSF_DUMP_WORKER_LAUNCHED, |
| KBASE_HWCNT_BACKEND_CSF_DUMP_ACCUMULATING, |
| KBASE_HWCNT_BACKEND_CSF_DUMP_COMPLETED, |
| }; |
| |
| /** |
| * enum kbase_hwcnt_backend_csf_enable_state - HWC CSF backend enable states. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_DISABLED: Initial state, and the state when backend |
| * is disabled. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_ENABLED: Enable request is in |
| * progress, waiting for firmware acknowledgment. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_ENABLED: Enable request has been acknowledged, |
| * enable is done. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_DISABLED: Disable request is in |
| * progress, waiting for firmware acknowledgment. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_DISABLED_WAIT_FOR_WORKER: Disable request has been |
| * acknowledged, waiting for dump workers to be finished. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR_WAIT_FOR_WORKER: An |
| * unrecoverable error happened, waiting for dump workers to be finished. |
| * |
| * @KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR: An unrecoverable error |
| * happened, and dump workers have finished, waiting for reset. |
| * |
| * Valid state transitions: |
| * DISABLED -> TRANSITIONING_TO_ENABLED (on enable) |
| * TRANSITIONING_TO_ENABLED -> ENABLED (on enable ack) |
| * ENABLED -> TRANSITIONING_TO_DISABLED (on disable) |
| * TRANSITIONING_TO_DISABLED -> DISABLED_WAIT_FOR_WORKER (on disable ack) |
| * DISABLED_WAIT_FOR_WORKER -> DISABLED (after workers are flushed) |
| * DISABLED -> UNRECOVERABLE_ERROR (on unrecoverable error) |
| * ANY but DISABLED -> UNRECOVERABLE_ERROR_WAIT_FOR_WORKER (on unrecoverable |
| * error) |
| * UNRECOVERABLE_ERROR -> DISABLED (on before reset) |
| */ |
| enum kbase_hwcnt_backend_csf_enable_state { |
| KBASE_HWCNT_BACKEND_CSF_DISABLED, |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_ENABLED, |
| KBASE_HWCNT_BACKEND_CSF_ENABLED, |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_DISABLED, |
| KBASE_HWCNT_BACKEND_CSF_DISABLED_WAIT_FOR_WORKER, |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR_WAIT_FOR_WORKER, |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR, |
| }; |
| |
| /** |
| * struct kbase_hwcnt_backend_csf_info - Information used to create an instance |
| * of a CSF hardware counter backend. |
| * @backend: Pointer to access CSF backend. |
| * @fw_in_protected_mode: True if FW is running in protected mode, else |
| * false. |
| * @unrecoverable_error_happened: True if an recoverable error happened, else |
| * false. |
| * @csf_if: CSF interface object pointer. |
| * @ring_buf_cnt: Dump buffer count in the ring buffer. |
| * @counter_set: The performance counter set to use. |
| * @metadata: Hardware counter metadata. |
| * @prfcnt_info: Performance counter information. |
| */ |
| struct kbase_hwcnt_backend_csf_info { |
| struct kbase_hwcnt_backend_csf *backend; |
| bool fw_in_protected_mode; |
| bool unrecoverable_error_happened; |
| struct kbase_hwcnt_backend_csf_if *csf_if; |
| u32 ring_buf_cnt; |
| enum kbase_hwcnt_set counter_set; |
| const struct kbase_hwcnt_metadata *metadata; |
| struct kbase_hwcnt_backend_csf_if_prfcnt_info prfcnt_info; |
| }; |
| |
| /** |
| * struct kbase_hwcnt_csf_physical_layout - HWC sample memory physical layout |
| * information. |
| * @fe_cnt: Front end block count. |
| * @tiler_cnt: Tiler block count. |
| * @mmu_l2_cnt: Memory system(MMU and L2 cache) block count. |
| * @shader_cnt: Shader Core block count. |
| * @block_cnt: Total block count (sum of all other block counts). |
| * @shader_avail_mask: Bitmap of all shader cores in the system. |
| * @offset_enable_mask: Offset of enable mask in the block. |
| * @headers_per_block: Header size per block. |
| * @counters_per_block: Counters size per block. |
| * @values_per_block: Total size per block. |
| */ |
| struct kbase_hwcnt_csf_physical_layout { |
| size_t fe_cnt; |
| size_t tiler_cnt; |
| size_t mmu_l2_cnt; |
| size_t shader_cnt; |
| size_t block_cnt; |
| u64 shader_avail_mask; |
| size_t offset_enable_mask; |
| size_t headers_per_block; |
| size_t counters_per_block; |
| size_t values_per_block; |
| }; |
| |
| /** |
| * struct kbase_hwcnt_backend_csf - Instance of a CSF hardware counter backend. |
| * @info: CSF Info used to create the backend. |
| * @dump_state: The dumping state of the backend. |
| * @enable_state: The CSF backend internal enabled state. |
| * @insert_index_to_accumulate: The insert index in the ring buffer which need |
| * to accumulate up to. |
| * @enable_state_waitq: Wait queue object used to notify the enable |
| * changing flag is done. |
| * @to_user_buf: HWC sample buffer for client user. |
| * @accum_buf: HWC sample buffer used as an internal |
| * accumulator. |
| * @old_sample_buf: HWC sample buffer to save the previous values |
| * for delta calculation. |
| * @ring_buf: Opaque pointer for ring buffer object. |
| * @ring_buf_cpu_base: CPU base address of the allocated ring buffer. |
| * @clk_enable_map: The enable map specifying enabled clock domains. |
| * @cycle_count_elapsed: Cycle count elapsed for a given sample period. |
| * @prev_cycle_count: Previous cycle count to calculate the cycle |
| * count for sample period. |
| * @phys_layout: Physical memory layout information of HWC |
| * sample buffer. |
| * @dump_completed: Completion signaled by the dump worker when |
| * it is completed accumulating up to the |
| * insert_index_to_accumulate. |
| * Should be initialized to the "complete" state. |
| * @hwc_dump_workq: Single threaded work queue for HWC workers |
| * execution. |
| * @hwc_dump_work: Worker to accumulate samples. |
| * @hwc_threshold_work: Worker for consuming available samples when |
| * threshold interrupt raised. |
| */ |
| struct kbase_hwcnt_backend_csf { |
| struct kbase_hwcnt_backend_csf_info *info; |
| enum kbase_hwcnt_backend_csf_dump_state dump_state; |
| enum kbase_hwcnt_backend_csf_enable_state enable_state; |
| u32 insert_index_to_accumulate; |
| wait_queue_head_t enable_state_waitq; |
| u32 *to_user_buf; |
| u32 *accum_buf; |
| u32 *old_sample_buf; |
| struct kbase_hwcnt_backend_csf_if_ring_buf *ring_buf; |
| void *ring_buf_cpu_base; |
| u64 clk_enable_map; |
| u64 cycle_count_elapsed[BASE_MAX_NR_CLOCKS_REGULATORS]; |
| u64 prev_cycle_count[BASE_MAX_NR_CLOCKS_REGULATORS]; |
| struct kbase_hwcnt_csf_physical_layout phys_layout; |
| struct completion dump_completed; |
| struct workqueue_struct *hwc_dump_workq; |
| struct work_struct hwc_dump_work; |
| struct work_struct hwc_threshold_work; |
| }; |
| |
| static bool kbasep_hwcnt_backend_csf_backend_exists( |
| struct kbase_hwcnt_backend_csf_info *csf_info) |
| { |
| WARN_ON(!csf_info); |
| csf_info->csf_if->assert_lock_held(csf_info->csf_if->ctx); |
| return (csf_info->backend != NULL); |
| } |
| |
| /** |
| * kbasep_hwcnt_backend_csf_cc_initial_sample() - Initialize cycle count |
| * tracking. |
| * |
| * @backend_csf: Non-NULL pointer to backend. |
| * @enable_map: Non-NULL pointer to enable map specifying enabled counters. |
| */ |
| static void kbasep_hwcnt_backend_csf_cc_initial_sample( |
| struct kbase_hwcnt_backend_csf *backend_csf, |
| const struct kbase_hwcnt_enable_map *enable_map) |
| { |
| u64 clk_enable_map = enable_map->clk_enable_map; |
| u64 cycle_counts[BASE_MAX_NR_CLOCKS_REGULATORS]; |
| size_t clk; |
| |
| /* Read cycle count from CSF interface for both clock domains. */ |
| backend_csf->info->csf_if->get_gpu_cycle_count( |
| backend_csf->info->csf_if->ctx, cycle_counts, clk_enable_map); |
| |
| kbase_hwcnt_metadata_for_each_clock(enable_map->metadata, clk) { |
| if (kbase_hwcnt_clk_enable_map_enabled(clk_enable_map, clk)) |
| backend_csf->prev_cycle_count[clk] = cycle_counts[clk]; |
| } |
| |
| /* Keep clk_enable_map for dump_request. */ |
| backend_csf->clk_enable_map = clk_enable_map; |
| } |
| |
| static void |
| kbasep_hwcnt_backend_csf_cc_update(struct kbase_hwcnt_backend_csf *backend_csf) |
| { |
| u64 cycle_counts[BASE_MAX_NR_CLOCKS_REGULATORS]; |
| size_t clk; |
| |
| backend_csf->info->csf_if->assert_lock_held( |
| backend_csf->info->csf_if->ctx); |
| |
| backend_csf->info->csf_if->get_gpu_cycle_count( |
| backend_csf->info->csf_if->ctx, cycle_counts, |
| backend_csf->clk_enable_map); |
| |
| kbase_hwcnt_metadata_for_each_clock(backend_csf->info->metadata, clk) { |
| if (kbase_hwcnt_clk_enable_map_enabled( |
| backend_csf->clk_enable_map, clk)) { |
| backend_csf->cycle_count_elapsed[clk] = |
| cycle_counts[clk] - |
| backend_csf->prev_cycle_count[clk]; |
| backend_csf->prev_cycle_count[clk] = cycle_counts[clk]; |
| } |
| } |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_timestamp_ns_fn */ |
| static u64 |
| kbasep_hwcnt_backend_csf_timestamp_ns(struct kbase_hwcnt_backend *backend) |
| { |
| struct kbase_hwcnt_backend_csf *backend_csf = |
| (struct kbase_hwcnt_backend_csf *)backend; |
| |
| if (!backend_csf || !backend_csf->info || !backend_csf->info->csf_if) |
| return 0; |
| |
| return backend_csf->info->csf_if->timestamp_ns( |
| backend_csf->info->csf_if->ctx); |
| } |
| |
| /** kbasep_hwcnt_backend_csf_process_enable_map() - Process the enable_map to |
| * guarantee headers are |
| * enabled if any counter is |
| * required. |
| *@phys_enable_map: HWC physical enable map to be processed. |
| */ |
| static void kbasep_hwcnt_backend_csf_process_enable_map( |
| struct kbase_hwcnt_physical_enable_map *phys_enable_map) |
| { |
| WARN_ON(!phys_enable_map); |
| |
| /* Enable header if any counter is required from user, the header is |
| * controlled by bit 0 of the enable mask. |
| */ |
| if (phys_enable_map->fe_bm) |
| phys_enable_map->fe_bm |= 1; |
| |
| if (phys_enable_map->tiler_bm) |
| phys_enable_map->tiler_bm |= 1; |
| |
| if (phys_enable_map->mmu_l2_bm) |
| phys_enable_map->mmu_l2_bm |= 1; |
| |
| if (phys_enable_map->shader_bm) |
| phys_enable_map->shader_bm |= 1; |
| } |
| |
| static void kbasep_hwcnt_backend_csf_init_layout( |
| const struct kbase_hwcnt_backend_csf_if_prfcnt_info *prfcnt_info, |
| struct kbase_hwcnt_csf_physical_layout *phys_layout) |
| { |
| WARN_ON(!prfcnt_info); |
| WARN_ON(!phys_layout); |
| |
| phys_layout->fe_cnt = 1; |
| phys_layout->tiler_cnt = 1; |
| phys_layout->mmu_l2_cnt = prfcnt_info->l2_count; |
| phys_layout->shader_cnt = fls64(prfcnt_info->core_mask); |
| phys_layout->block_cnt = phys_layout->fe_cnt + phys_layout->tiler_cnt + |
| phys_layout->mmu_l2_cnt + |
| phys_layout->shader_cnt; |
| |
| phys_layout->shader_avail_mask = prfcnt_info->core_mask; |
| |
| phys_layout->headers_per_block = KBASE_HWCNT_V5_HEADERS_PER_BLOCK; |
| phys_layout->values_per_block = |
| prfcnt_info->prfcnt_block_size / KBASE_HWCNT_VALUE_BYTES; |
| phys_layout->counters_per_block = |
| phys_layout->values_per_block - phys_layout->headers_per_block; |
| phys_layout->offset_enable_mask = KBASE_HWCNT_V5_PRFCNT_EN_HEADER; |
| } |
| |
| static void kbasep_hwcnt_backend_csf_reset_internal_buffers( |
| struct kbase_hwcnt_backend_csf *backend_csf) |
| { |
| memset(backend_csf->to_user_buf, 0, |
| backend_csf->info->prfcnt_info.dump_bytes); |
| memset(backend_csf->accum_buf, 0, |
| backend_csf->info->prfcnt_info.dump_bytes); |
| memset(backend_csf->old_sample_buf, 0, |
| backend_csf->info->prfcnt_info.dump_bytes); |
| } |
| |
| static void kbasep_hwcnt_backend_csf_zero_sample_prfcnt_en_header( |
| struct kbase_hwcnt_backend_csf *backend_csf, u32 *sample) |
| { |
| u32 block_idx; |
| const struct kbase_hwcnt_csf_physical_layout *phys_layout; |
| u32 *block_buf; |
| |
| phys_layout = &backend_csf->phys_layout; |
| |
| for (block_idx = 0; block_idx < phys_layout->block_cnt; block_idx++) { |
| block_buf = sample + block_idx * phys_layout->values_per_block; |
| block_buf[phys_layout->offset_enable_mask] = 0; |
| } |
| } |
| |
| static void kbasep_hwcnt_backend_csf_zero_all_prfcnt_en_header( |
| struct kbase_hwcnt_backend_csf *backend_csf) |
| { |
| u32 idx; |
| u32 *sample; |
| char *cpu_dump_base; |
| size_t dump_bytes = backend_csf->info->prfcnt_info.dump_bytes; |
| |
| cpu_dump_base = (char *)backend_csf->ring_buf_cpu_base; |
| |
| for (idx = 0; idx < backend_csf->info->ring_buf_cnt; idx++) { |
| sample = (u32 *)&cpu_dump_base[idx * dump_bytes]; |
| kbasep_hwcnt_backend_csf_zero_sample_prfcnt_en_header( |
| backend_csf, sample); |
| } |
| } |
| |
| static void kbasep_hwcnt_backend_csf_update_user_sample( |
| struct kbase_hwcnt_backend_csf *backend_csf) |
| { |
| /* Copy the data into the sample and wait for the user to get it. */ |
| memcpy(backend_csf->to_user_buf, backend_csf->accum_buf, |
| backend_csf->info->prfcnt_info.dump_bytes); |
| |
| /* After copied data into user sample, clear the accumulator values to |
| * prepare for the next accumulator, such as the next request or |
| * threshold. |
| */ |
| memset(backend_csf->accum_buf, 0, |
| backend_csf->info->prfcnt_info.dump_bytes); |
| } |
| |
| static void kbasep_hwcnt_backend_csf_accumulate_sample( |
| const struct kbase_hwcnt_csf_physical_layout *phys_layout, |
| size_t dump_bytes, u32 *accum_buf, const u32 *old_sample_buf, |
| const u32 *new_sample_buf, bool clearing_samples) |
| { |
| size_t block_idx, ctr_idx; |
| const u32 *old_block = old_sample_buf; |
| const u32 *new_block = new_sample_buf; |
| u32 *acc_block = accum_buf; |
| |
| for (block_idx = 0; block_idx < phys_layout->block_cnt; block_idx++) { |
| const u32 old_enable_mask = |
| old_block[phys_layout->offset_enable_mask]; |
| const u32 new_enable_mask = |
| new_block[phys_layout->offset_enable_mask]; |
| |
| if (new_enable_mask == 0) { |
| /* Hardware block was unavailable or we didn't turn on |
| * any counters. Do nothing. |
| */ |
| } else { |
| /* Hardware block was available and it had some counters |
| * enabled. We need to update the accumulation buffer. |
| */ |
| |
| /* Unconditionally copy the headers. */ |
| memcpy(acc_block, new_block, |
| phys_layout->headers_per_block * |
| KBASE_HWCNT_VALUE_BYTES); |
| |
| /* Accumulate counter samples |
| * |
| * When accumulating samples we need to take into |
| * account whether the counter sampling method involves |
| * clearing counters back to zero after each sample is |
| * taken. |
| * |
| * The intention for CSF was that all HW should use |
| * counters which wrap to zero when their maximum value |
| * is reached. This, combined with non-clearing |
| * sampling, enables multiple concurrent users to |
| * request samples without interfering with each other. |
| * |
| * However some early HW may not support wrapping |
| * counters, for these GPUs counters must be cleared on |
| * sample to avoid loss of data due to counters |
| * saturating at their maximum value. |
| */ |
| if (!clearing_samples) { |
| if (old_enable_mask == 0) { |
| /* Hardware block was previously |
| * unavailable. Accumulate the new |
| * counters only, as we know previous |
| * values are zeroes. |
| */ |
| for (ctr_idx = |
| phys_layout |
| ->headers_per_block; |
| ctr_idx < |
| phys_layout->values_per_block; |
| ctr_idx++) { |
| acc_block[ctr_idx] += |
| new_block[ctr_idx]; |
| } |
| } else { |
| /* Hardware block was previously |
| * available. Accumulate the delta |
| * between old and new counter values. |
| */ |
| for (ctr_idx = |
| phys_layout |
| ->headers_per_block; |
| ctr_idx < |
| phys_layout->values_per_block; |
| ctr_idx++) { |
| acc_block[ctr_idx] += |
| new_block[ctr_idx] - |
| old_block[ctr_idx]; |
| } |
| } |
| } else { |
| for (ctr_idx = phys_layout->headers_per_block; |
| ctr_idx < phys_layout->values_per_block; |
| ctr_idx++) { |
| acc_block[ctr_idx] += |
| new_block[ctr_idx]; |
| } |
| } |
| } |
| old_block += phys_layout->values_per_block; |
| new_block += phys_layout->values_per_block; |
| acc_block += phys_layout->values_per_block; |
| } |
| |
| WARN_ON(old_block != |
| old_sample_buf + dump_bytes / KBASE_HWCNT_VALUE_BYTES); |
| WARN_ON(new_block != |
| new_sample_buf + dump_bytes / KBASE_HWCNT_VALUE_BYTES); |
| WARN_ON(acc_block != accum_buf + dump_bytes / KBASE_HWCNT_VALUE_BYTES); |
| (void)dump_bytes; |
| } |
| |
| static void kbasep_hwcnt_backend_csf_accumulate_samples( |
| struct kbase_hwcnt_backend_csf *backend_csf, u32 extract_index_to_start, |
| u32 insert_index_to_stop) |
| { |
| u32 raw_idx; |
| unsigned long flags; |
| u8 *cpu_dump_base = (u8 *)backend_csf->ring_buf_cpu_base; |
| const size_t ring_buf_cnt = backend_csf->info->ring_buf_cnt; |
| const size_t buf_dump_bytes = backend_csf->info->prfcnt_info.dump_bytes; |
| bool clearing_samples = backend_csf->info->prfcnt_info.clearing_samples; |
| u32 *old_sample_buf = backend_csf->old_sample_buf; |
| u32 *new_sample_buf; |
| |
| if (extract_index_to_start == insert_index_to_stop) |
| /* No samples to accumulate. Early out. */ |
| return; |
| |
| /* Sync all the buffers to CPU side before read the data. */ |
| backend_csf->info->csf_if->ring_buf_sync(backend_csf->info->csf_if->ctx, |
| backend_csf->ring_buf, |
| extract_index_to_start, |
| insert_index_to_stop, true); |
| |
| /* Consider u32 wrap case, '!=' is used here instead of '<' operator */ |
| for (raw_idx = extract_index_to_start; raw_idx != insert_index_to_stop; |
| raw_idx++) { |
| /* The logical "&" acts as a modulo operation since buf_count |
| * must be a power of two. |
| */ |
| const u32 buf_idx = raw_idx & (ring_buf_cnt - 1); |
| |
| new_sample_buf = |
| (u32 *)&cpu_dump_base[buf_idx * buf_dump_bytes]; |
| |
| kbasep_hwcnt_backend_csf_accumulate_sample( |
| &backend_csf->phys_layout, buf_dump_bytes, |
| backend_csf->accum_buf, old_sample_buf, new_sample_buf, |
| clearing_samples); |
| |
| old_sample_buf = new_sample_buf; |
| } |
| |
| /* Save the newest buffer as the old buffer for next time. */ |
| memcpy(backend_csf->old_sample_buf, new_sample_buf, buf_dump_bytes); |
| |
| /* Reset the prfcnt_en header on each sample before releasing them. */ |
| for (raw_idx = extract_index_to_start; raw_idx != insert_index_to_stop; |
| raw_idx++) { |
| const u32 buf_idx = raw_idx & (ring_buf_cnt - 1); |
| u32 *sample = (u32 *)&cpu_dump_base[buf_idx * buf_dump_bytes]; |
| |
| kbasep_hwcnt_backend_csf_zero_sample_prfcnt_en_header( |
| backend_csf, sample); |
| } |
| |
| /* Sync zeroed buffers to avoid coherency issues on future use. */ |
| backend_csf->info->csf_if->ring_buf_sync(backend_csf->info->csf_if->ctx, |
| backend_csf->ring_buf, |
| extract_index_to_start, |
| insert_index_to_stop, false); |
| |
| /* After consuming all samples between extract_idx and insert_idx, |
| * set the raw extract index to insert_idx so that the sample buffers |
| * can be released back to the ring buffer pool. |
| */ |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| backend_csf->info->csf_if->set_extract_index( |
| backend_csf->info->csf_if->ctx, insert_index_to_stop); |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| } |
| |
| static void kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| struct kbase_hwcnt_backend_csf *backend_csf, |
| enum kbase_hwcnt_backend_csf_enable_state new_state) |
| { |
| backend_csf->info->csf_if->assert_lock_held( |
| backend_csf->info->csf_if->ctx); |
| |
| if (backend_csf->enable_state != new_state) { |
| backend_csf->enable_state = new_state; |
| |
| wake_up(&backend_csf->enable_state_waitq); |
| } |
| } |
| |
| /** |
| * kbasep_hwcnt_backend_csf_dump_worker() - HWC dump worker. |
| * @work: Work structure. |
| * |
| * To accumulate all available samples in the ring buffer when a request has |
| * been done. |
| * |
| */ |
| static void kbasep_hwcnt_backend_csf_dump_worker(struct work_struct *work) |
| { |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf *backend_csf; |
| u32 insert_index_to_acc; |
| u32 extract_index; |
| u32 insert_index; |
| |
| WARN_ON(!work); |
| backend_csf = container_of(work, struct kbase_hwcnt_backend_csf, |
| hwc_dump_work); |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| /* Assert the backend is not destroyed. */ |
| WARN_ON(backend_csf != backend_csf->info->backend); |
| |
| /* The backend was disabled or had an error while the worker was being |
| * launched. |
| */ |
| if (backend_csf->enable_state != KBASE_HWCNT_BACKEND_CSF_ENABLED) { |
| WARN_ON(backend_csf->dump_state != |
| KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE); |
| WARN_ON(!completion_done(&backend_csf->dump_completed)); |
| backend_csf->info->csf_if->unlock( |
| backend_csf->info->csf_if->ctx, flags); |
| return; |
| } |
| |
| WARN_ON(backend_csf->dump_state != |
| KBASE_HWCNT_BACKEND_CSF_DUMP_WORKER_LAUNCHED); |
| |
| backend_csf->dump_state = KBASE_HWCNT_BACKEND_CSF_DUMP_ACCUMULATING; |
| insert_index_to_acc = backend_csf->insert_index_to_accumulate; |
| |
| /* Read the raw extract and insert indexes from the CSF interface. */ |
| backend_csf->info->csf_if->get_indexes(backend_csf->info->csf_if->ctx, |
| &extract_index, &insert_index); |
| |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| |
| /* Accumulate up to the insert we grabbed at the prfcnt request |
| * interrupt. |
| */ |
| kbasep_hwcnt_backend_csf_accumulate_samples(backend_csf, extract_index, |
| insert_index_to_acc); |
| |
| /* Copy to the user buffer so if a threshold interrupt fires |
| * between now and get(), the accumulations are untouched. |
| */ |
| kbasep_hwcnt_backend_csf_update_user_sample(backend_csf); |
| |
| /* Dump done, set state back to COMPLETED for next request. */ |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| /* Assert the backend is not destroyed. */ |
| WARN_ON(backend_csf != backend_csf->info->backend); |
| |
| /* The backend was disabled or had an error while we were accumulating. |
| */ |
| if (backend_csf->enable_state != KBASE_HWCNT_BACKEND_CSF_ENABLED) { |
| WARN_ON(backend_csf->dump_state != |
| KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE); |
| WARN_ON(!completion_done(&backend_csf->dump_completed)); |
| backend_csf->info->csf_if->unlock( |
| backend_csf->info->csf_if->ctx, flags); |
| return; |
| } |
| |
| WARN_ON(backend_csf->dump_state != |
| KBASE_HWCNT_BACKEND_CSF_DUMP_ACCUMULATING); |
| |
| /* Our work here is done - set the wait object and unblock waiters. */ |
| backend_csf->dump_state = KBASE_HWCNT_BACKEND_CSF_DUMP_COMPLETED; |
| complete_all(&backend_csf->dump_completed); |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| } |
| |
| /** |
| * kbasep_hwcnt_backend_csf_threshold_worker() - Threshold worker. |
| * |
| * @work: Work structure. |
| * |
| * Called when a HWC threshold interrupt raised to consume all available samples |
| * in the ring buffer. |
| */ |
| static void kbasep_hwcnt_backend_csf_threshold_worker(struct work_struct *work) |
| { |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf *backend_csf; |
| u32 extract_index; |
| u32 insert_index; |
| |
| WARN_ON(!work); |
| |
| backend_csf = container_of(work, struct kbase_hwcnt_backend_csf, |
| hwc_threshold_work); |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| |
| /* Assert the backend is not destroyed. */ |
| WARN_ON(backend_csf != backend_csf->info->backend); |
| |
| /* Read the raw extract and insert indexes from the CSF interface. */ |
| backend_csf->info->csf_if->get_indexes(backend_csf->info->csf_if->ctx, |
| &extract_index, &insert_index); |
| |
| /* The backend was disabled or had an error while the worker was being |
| * launched. |
| */ |
| if (backend_csf->enable_state != KBASE_HWCNT_BACKEND_CSF_ENABLED) { |
| backend_csf->info->csf_if->unlock( |
| backend_csf->info->csf_if->ctx, flags); |
| return; |
| } |
| |
| /* Early out if we are not in the IDLE state or COMPLETED state, as this |
| * means a concurrent dump is in progress and we don't want to |
| * interfere. |
| */ |
| if ((backend_csf->dump_state != KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE) && |
| (backend_csf->dump_state != |
| KBASE_HWCNT_BACKEND_CSF_DUMP_COMPLETED)) { |
| backend_csf->info->csf_if->unlock( |
| backend_csf->info->csf_if->ctx, flags); |
| return; |
| } |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| |
| /* Accumulate everything we possibly can. We grabbed the insert index |
| * immediately after we acquired the lock but before we checked whether |
| * a concurrent dump was triggered. This ensures that if a concurrent |
| * dump was triggered between releasing the lock and now, we know for a |
| * fact that our insert will not exceed the concurrent dump's |
| * insert_to_accumulate, so we don't risk accumulating too much data. |
| */ |
| kbasep_hwcnt_backend_csf_accumulate_samples(backend_csf, extract_index, |
| insert_index); |
| |
| /* No need to wake up anything since it is not a user dump request. */ |
| } |
| |
| static void kbase_hwcnt_backend_csf_submit_dump_worker( |
| struct kbase_hwcnt_backend_csf_info *csf_info) |
| { |
| u32 extract_index; |
| |
| WARN_ON(!csf_info); |
| csf_info->csf_if->assert_lock_held(csf_info->csf_if->ctx); |
| |
| WARN_ON(!kbasep_hwcnt_backend_csf_backend_exists(csf_info)); |
| WARN_ON(csf_info->backend->enable_state != |
| KBASE_HWCNT_BACKEND_CSF_ENABLED); |
| WARN_ON(csf_info->backend->dump_state != |
| KBASE_HWCNT_BACKEND_CSF_DUMP_QUERYING_INSERT); |
| |
| /* Save insert index now so that the dump worker only accumulates the |
| * HWC data associated with this request. Extract index is not stored |
| * as that needs to be checked when accumulating to prevent re-reading |
| * buffers that have already been read and returned to the GPU. |
| */ |
| csf_info->csf_if->get_indexes( |
| csf_info->csf_if->ctx, &extract_index, |
| &csf_info->backend->insert_index_to_accumulate); |
| csf_info->backend->dump_state = |
| KBASE_HWCNT_BACKEND_CSF_DUMP_WORKER_LAUNCHED; |
| |
| /* Submit the accumulator task into the work queue. */ |
| queue_work(csf_info->backend->hwc_dump_workq, |
| &csf_info->backend->hwc_dump_work); |
| } |
| |
| static void kbasep_hwcnt_backend_csf_get_physical_enable( |
| struct kbase_hwcnt_backend_csf *backend_csf, |
| const struct kbase_hwcnt_enable_map *enable_map, |
| struct kbase_hwcnt_backend_csf_if_enable *enable) |
| { |
| enum kbase_hwcnt_physical_set phys_counter_set; |
| struct kbase_hwcnt_physical_enable_map phys_enable_map; |
| |
| kbase_hwcnt_gpu_enable_map_to_physical(&phys_enable_map, enable_map); |
| |
| /* process the enable_map to guarantee the block header is enabled which |
| * is needed for delta calculation. |
| */ |
| kbasep_hwcnt_backend_csf_process_enable_map(&phys_enable_map); |
| |
| kbase_hwcnt_gpu_set_to_physical(&phys_counter_set, |
| backend_csf->info->counter_set); |
| |
| /* Use processed enable_map to enable HWC in HW level. */ |
| enable->fe_bm = phys_enable_map.fe_bm; |
| enable->shader_bm = phys_enable_map.shader_bm; |
| enable->tiler_bm = phys_enable_map.tiler_bm; |
| enable->mmu_l2_bm = phys_enable_map.mmu_l2_bm; |
| enable->counter_set = phys_counter_set; |
| enable->clk_enable_map = enable_map->clk_enable_map; |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_dump_enable_nolock_fn */ |
| static int kbasep_hwcnt_backend_csf_dump_enable_nolock( |
| struct kbase_hwcnt_backend *backend, |
| const struct kbase_hwcnt_enable_map *enable_map) |
| { |
| struct kbase_hwcnt_backend_csf *backend_csf = |
| (struct kbase_hwcnt_backend_csf *)backend; |
| struct kbase_hwcnt_backend_csf_if_enable enable; |
| |
| if (!backend_csf || !enable_map || |
| (enable_map->metadata != backend_csf->info->metadata)) |
| return -EINVAL; |
| |
| backend_csf->info->csf_if->assert_lock_held( |
| backend_csf->info->csf_if->ctx); |
| |
| kbasep_hwcnt_backend_csf_get_physical_enable(backend_csf, enable_map, |
| &enable); |
| |
| /* enable_state should be DISABLED before we transfer it to enabled */ |
| if (backend_csf->enable_state != KBASE_HWCNT_BACKEND_CSF_DISABLED) |
| return -EIO; |
| |
| backend_csf->dump_state = KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE; |
| WARN_ON(!completion_done(&backend_csf->dump_completed)); |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_ENABLED); |
| |
| backend_csf->info->csf_if->dump_enable(backend_csf->info->csf_if->ctx, |
| backend_csf->ring_buf, &enable); |
| |
| kbasep_hwcnt_backend_csf_cc_initial_sample(backend_csf, enable_map); |
| |
| return 0; |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_dump_enable_fn */ |
| static int kbasep_hwcnt_backend_csf_dump_enable( |
| struct kbase_hwcnt_backend *backend, |
| const struct kbase_hwcnt_enable_map *enable_map) |
| { |
| int errcode; |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf *backend_csf = |
| (struct kbase_hwcnt_backend_csf *)backend; |
| |
| if (!backend_csf) |
| return -EINVAL; |
| |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| errcode = kbasep_hwcnt_backend_csf_dump_enable_nolock(backend, |
| enable_map); |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| return errcode; |
| } |
| |
| static void kbasep_hwcnt_backend_csf_wait_enable_transition_complete( |
| struct kbase_hwcnt_backend_csf *backend_csf, unsigned long *lock_flags) |
| { |
| backend_csf->info->csf_if->assert_lock_held( |
| backend_csf->info->csf_if->ctx); |
| |
| while ((backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_ENABLED) || |
| (backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_DISABLED)) { |
| backend_csf->info->csf_if->unlock( |
| backend_csf->info->csf_if->ctx, *lock_flags); |
| |
| wait_event( |
| backend_csf->enable_state_waitq, |
| (backend_csf->enable_state != |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_ENABLED) && |
| (backend_csf->enable_state != |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_DISABLED)); |
| |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, |
| lock_flags); |
| } |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_dump_disable_fn */ |
| static void |
| kbasep_hwcnt_backend_csf_dump_disable(struct kbase_hwcnt_backend *backend) |
| { |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf *backend_csf = |
| (struct kbase_hwcnt_backend_csf *)backend; |
| bool do_disable = false; |
| |
| WARN_ON(!backend_csf); |
| |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| |
| /* Make sure we wait until any previous enable or disable have completed |
| * before doing anything. |
| */ |
| kbasep_hwcnt_backend_csf_wait_enable_transition_complete(backend_csf, |
| &flags); |
| |
| if (backend_csf->enable_state == KBASE_HWCNT_BACKEND_CSF_DISABLED || |
| backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR) { |
| /* If we are already disabled or in an unrecoverable error |
| * state, there is nothing for us to do. |
| */ |
| backend_csf->info->csf_if->unlock( |
| backend_csf->info->csf_if->ctx, flags); |
| return; |
| } |
| |
| if (backend_csf->enable_state == KBASE_HWCNT_BACKEND_CSF_ENABLED) { |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_DISABLED); |
| backend_csf->dump_state = KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE; |
| complete_all(&backend_csf->dump_completed); |
| /* Only disable if we were previously enabled - in all other |
| * cases the call to disable will have already been made. |
| */ |
| do_disable = true; |
| } |
| |
| WARN_ON(backend_csf->dump_state != KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE); |
| WARN_ON(!completion_done(&backend_csf->dump_completed)); |
| |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| |
| /* Block until any async work has completed. We have transitioned out of |
| * the ENABLED state so we can guarantee no new work will concurrently |
| * be submitted. |
| */ |
| flush_workqueue(backend_csf->hwc_dump_workq); |
| |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| |
| if (do_disable) |
| backend_csf->info->csf_if->dump_disable( |
| backend_csf->info->csf_if->ctx); |
| |
| kbasep_hwcnt_backend_csf_wait_enable_transition_complete(backend_csf, |
| &flags); |
| |
| switch (backend_csf->enable_state) { |
| case KBASE_HWCNT_BACKEND_CSF_DISABLED_WAIT_FOR_WORKER: |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, KBASE_HWCNT_BACKEND_CSF_DISABLED); |
| break; |
| case KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR_WAIT_FOR_WORKER: |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR); |
| break; |
| default: |
| WARN_ON(true); |
| break; |
| } |
| |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| |
| /* After disable, zero the header of all buffers in the ring buffer back |
| * to 0 to prepare for the next enable. |
| */ |
| kbasep_hwcnt_backend_csf_zero_all_prfcnt_en_header(backend_csf); |
| |
| /* Sync zeroed buffers to avoid coherency issues on future use. */ |
| backend_csf->info->csf_if->ring_buf_sync( |
| backend_csf->info->csf_if->ctx, backend_csf->ring_buf, 0, |
| backend_csf->info->ring_buf_cnt, false); |
| |
| /* Reset accumulator, old_sample_buf and user_sample to all-0 to prepare |
| * for next enable. |
| */ |
| kbasep_hwcnt_backend_csf_reset_internal_buffers(backend_csf); |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_dump_request_fn */ |
| static int |
| kbasep_hwcnt_backend_csf_dump_request(struct kbase_hwcnt_backend *backend, |
| u64 *dump_time_ns) |
| { |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf *backend_csf = |
| (struct kbase_hwcnt_backend_csf *)backend; |
| bool do_request = false; |
| |
| if (!backend_csf) |
| return -EINVAL; |
| |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| |
| /* If we're transitioning to enabled there's nothing to accumulate, and |
| * the user dump buffer is already zeroed. We can just short circuit to |
| * the DUMP_COMPLETED state. |
| */ |
| if (backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_ENABLED) { |
| backend_csf->dump_state = |
| KBASE_HWCNT_BACKEND_CSF_DUMP_COMPLETED; |
| *dump_time_ns = kbasep_hwcnt_backend_csf_timestamp_ns(backend); |
| kbasep_hwcnt_backend_csf_cc_update(backend_csf); |
| backend_csf->info->csf_if->unlock( |
| backend_csf->info->csf_if->ctx, flags); |
| return 0; |
| } |
| |
| /* Otherwise, make sure we're already enabled. */ |
| if (backend_csf->enable_state != KBASE_HWCNT_BACKEND_CSF_ENABLED) { |
| backend_csf->info->csf_if->unlock( |
| backend_csf->info->csf_if->ctx, flags); |
| return -EIO; |
| } |
| |
| /* Make sure that this is either the first request since enable or the |
| * previous dump has completed, so we can avoid midway through a dump. |
| */ |
| if ((backend_csf->dump_state != KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE) && |
| (backend_csf->dump_state != |
| KBASE_HWCNT_BACKEND_CSF_DUMP_COMPLETED)) { |
| backend_csf->info->csf_if->unlock( |
| backend_csf->info->csf_if->ctx, flags); |
| /* HWC is disabled or another dump is ongoing, or we are on |
| * fault. |
| */ |
| return -EIO; |
| } |
| |
| /* Reset the completion so dump_wait() has something to wait on. */ |
| reinit_completion(&backend_csf->dump_completed); |
| |
| if ((backend_csf->enable_state == KBASE_HWCNT_BACKEND_CSF_ENABLED) && |
| !backend_csf->info->fw_in_protected_mode) { |
| /* Only do the request if we are fully enabled and not in |
| * protected mode. |
| */ |
| backend_csf->dump_state = |
| KBASE_HWCNT_BACKEND_CSF_DUMP_REQUESTED; |
| do_request = true; |
| } else { |
| /* Skip the request and waiting for ack and go straight to |
| * checking the insert and kicking off the worker to do the dump |
| */ |
| backend_csf->dump_state = |
| KBASE_HWCNT_BACKEND_CSF_DUMP_QUERYING_INSERT; |
| } |
| |
| /* CSF firmware might enter protected mode now, but still call request. |
| * That is fine, as we changed state while holding the lock, so the |
| * protected mode enter function will query the insert and launch the |
| * dumping worker. |
| * At some point we will get the dump request ACK saying a dump is done, |
| * but we can ignore it if we are not in the REQUESTED state and process |
| * it in next round dumping worker. |
| */ |
| |
| *dump_time_ns = kbasep_hwcnt_backend_csf_timestamp_ns(backend); |
| kbasep_hwcnt_backend_csf_cc_update(backend_csf); |
| |
| if (do_request) |
| backend_csf->info->csf_if->dump_request( |
| backend_csf->info->csf_if->ctx); |
| else |
| kbase_hwcnt_backend_csf_submit_dump_worker(backend_csf->info); |
| |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| return 0; |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_dump_wait_fn */ |
| static int |
| kbasep_hwcnt_backend_csf_dump_wait(struct kbase_hwcnt_backend *backend) |
| { |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf *backend_csf = |
| (struct kbase_hwcnt_backend_csf *)backend; |
| int errcode; |
| |
| if (!backend_csf) |
| return -EINVAL; |
| |
| wait_for_completion(&backend_csf->dump_completed); |
| |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| /* Make sure the last dump actually succeeded. */ |
| errcode = (backend_csf->dump_state == |
| KBASE_HWCNT_BACKEND_CSF_DUMP_COMPLETED) ? |
| 0 : |
| -EIO; |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| |
| return errcode; |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_dump_clear_fn */ |
| static int |
| kbasep_hwcnt_backend_csf_dump_clear(struct kbase_hwcnt_backend *backend) |
| { |
| struct kbase_hwcnt_backend_csf *backend_csf = |
| (struct kbase_hwcnt_backend_csf *)backend; |
| int errcode; |
| u64 ts; |
| |
| if (!backend_csf) |
| return -EINVAL; |
| |
| /* Request a dump so we can clear all current counters. */ |
| errcode = kbasep_hwcnt_backend_csf_dump_request(backend, &ts); |
| if (!errcode) |
| /* Wait for the manual dump or auto dump to be done and |
| * accumulator to be updated. |
| */ |
| errcode = kbasep_hwcnt_backend_csf_dump_wait(backend); |
| |
| return errcode; |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_dump_get_fn */ |
| static int kbasep_hwcnt_backend_csf_dump_get( |
| struct kbase_hwcnt_backend *backend, |
| struct kbase_hwcnt_dump_buffer *dst, |
| const struct kbase_hwcnt_enable_map *dst_enable_map, bool accumulate) |
| { |
| struct kbase_hwcnt_backend_csf *backend_csf = |
| (struct kbase_hwcnt_backend_csf *)backend; |
| int ret; |
| size_t clk; |
| |
| if (!backend_csf || !dst || !dst_enable_map || |
| (backend_csf->info->metadata != dst->metadata) || |
| (dst_enable_map->metadata != dst->metadata)) |
| return -EINVAL; |
| |
| kbase_hwcnt_metadata_for_each_clock(dst_enable_map->metadata, clk) { |
| if (!kbase_hwcnt_clk_enable_map_enabled( |
| dst_enable_map->clk_enable_map, clk)) |
| continue; |
| |
| /* Extract elapsed cycle count for each clock domain. */ |
| dst->clk_cnt_buf[clk] = backend_csf->cycle_count_elapsed[clk]; |
| } |
| |
| /* We just return the user buffer without checking the current state, |
| * as it is undefined to call this function without a prior succeeding |
| * one to dump_wait(). |
| */ |
| ret = kbase_hwcnt_csf_dump_get(dst, backend_csf->to_user_buf, |
| dst_enable_map, accumulate); |
| |
| return ret; |
| } |
| |
| /** |
| * kbasep_hwcnt_backend_csf_destroy() - Destroy CSF backend. |
| * @backend_csf: Pointer to CSF backend to destroy. |
| * |
| * Can be safely called on a backend in any state of partial construction. |
| * |
| */ |
| static void |
| kbasep_hwcnt_backend_csf_destroy(struct kbase_hwcnt_backend_csf *backend_csf) |
| { |
| if (!backend_csf) |
| return; |
| |
| destroy_workqueue(backend_csf->hwc_dump_workq); |
| |
| backend_csf->info->csf_if->ring_buf_free(backend_csf->info->csf_if->ctx, |
| backend_csf->ring_buf); |
| |
| kfree(backend_csf->accum_buf); |
| backend_csf->accum_buf = NULL; |
| |
| kfree(backend_csf->old_sample_buf); |
| backend_csf->old_sample_buf = NULL; |
| |
| kfree(backend_csf->to_user_buf); |
| backend_csf->to_user_buf = NULL; |
| |
| kfree(backend_csf); |
| } |
| |
| /** |
| * kbasep_hwcnt_backend_csf_create() - Create a CSF backend instance. |
| * |
| * @csf_info: Non-NULL pointer to backend info. |
| * @out_backend: Non-NULL pointer to where backend is stored on success. |
| * Return: 0 on success, else error code. |
| */ |
| static int |
| kbasep_hwcnt_backend_csf_create(struct kbase_hwcnt_backend_csf_info *csf_info, |
| struct kbase_hwcnt_backend_csf **out_backend) |
| { |
| struct kbase_hwcnt_backend_csf *backend_csf = NULL; |
| int errcode = -ENOMEM; |
| |
| WARN_ON(!csf_info); |
| WARN_ON(!out_backend); |
| |
| backend_csf = kzalloc(sizeof(*backend_csf), GFP_KERNEL); |
| if (!backend_csf) |
| goto alloc_error; |
| |
| backend_csf->info = csf_info; |
| kbasep_hwcnt_backend_csf_init_layout(&csf_info->prfcnt_info, |
| &backend_csf->phys_layout); |
| |
| backend_csf->accum_buf = |
| kzalloc(csf_info->prfcnt_info.dump_bytes, GFP_KERNEL); |
| if (!backend_csf->accum_buf) |
| goto err_alloc_acc_buf; |
| |
| backend_csf->old_sample_buf = |
| kzalloc(csf_info->prfcnt_info.dump_bytes, GFP_KERNEL); |
| if (!backend_csf->old_sample_buf) |
| goto err_alloc_pre_sample_buf; |
| |
| backend_csf->to_user_buf = |
| kzalloc(csf_info->prfcnt_info.dump_bytes, GFP_KERNEL); |
| if (!backend_csf->to_user_buf) |
| goto err_alloc_user_sample_buf; |
| |
| errcode = csf_info->csf_if->ring_buf_alloc( |
| csf_info->csf_if->ctx, csf_info->ring_buf_cnt, |
| &backend_csf->ring_buf_cpu_base, &backend_csf->ring_buf); |
| if (errcode) |
| goto err_ring_buf_alloc; |
| |
| /* Zero all performance enable header to prepare for first enable. */ |
| kbasep_hwcnt_backend_csf_zero_all_prfcnt_en_header(backend_csf); |
| |
| /* Sync zeroed buffers to avoid coherency issues on use. */ |
| backend_csf->info->csf_if->ring_buf_sync( |
| backend_csf->info->csf_if->ctx, backend_csf->ring_buf, 0, |
| backend_csf->info->ring_buf_cnt, false); |
| |
| init_completion(&backend_csf->dump_completed); |
| |
| init_waitqueue_head(&backend_csf->enable_state_waitq); |
| |
| /* Allocate a single threaded work queue for dump worker and threshold |
| * worker. |
| */ |
| backend_csf->hwc_dump_workq = |
| alloc_workqueue("mali_hwc_dump_wq", WQ_HIGHPRI | WQ_UNBOUND, 1); |
| if (!backend_csf->hwc_dump_workq) |
| goto err_alloc_workqueue; |
| |
| INIT_WORK(&backend_csf->hwc_dump_work, |
| kbasep_hwcnt_backend_csf_dump_worker); |
| INIT_WORK(&backend_csf->hwc_threshold_work, |
| kbasep_hwcnt_backend_csf_threshold_worker); |
| |
| backend_csf->enable_state = KBASE_HWCNT_BACKEND_CSF_DISABLED; |
| backend_csf->dump_state = KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE; |
| complete_all(&backend_csf->dump_completed); |
| |
| *out_backend = backend_csf; |
| return 0; |
| |
| destroy_workqueue(backend_csf->hwc_dump_workq); |
| err_alloc_workqueue: |
| backend_csf->info->csf_if->ring_buf_free(backend_csf->info->csf_if->ctx, |
| backend_csf->ring_buf); |
| err_ring_buf_alloc: |
| kfree(backend_csf->to_user_buf); |
| backend_csf->to_user_buf = NULL; |
| err_alloc_user_sample_buf: |
| kfree(backend_csf->old_sample_buf); |
| backend_csf->old_sample_buf = NULL; |
| err_alloc_pre_sample_buf: |
| kfree(backend_csf->accum_buf); |
| backend_csf->accum_buf = NULL; |
| err_alloc_acc_buf: |
| kfree(backend_csf); |
| alloc_error: |
| return errcode; |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_init_fn */ |
| static int |
| kbasep_hwcnt_backend_csf_init(const struct kbase_hwcnt_backend_info *info, |
| struct kbase_hwcnt_backend **out_backend) |
| { |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf *backend_csf = NULL; |
| struct kbase_hwcnt_backend_csf_info *csf_info = |
| (struct kbase_hwcnt_backend_csf_info *)info; |
| int errcode; |
| bool success = false; |
| |
| if (!info || !out_backend) |
| return -EINVAL; |
| |
| /* Create the backend. */ |
| errcode = kbasep_hwcnt_backend_csf_create(csf_info, &backend_csf); |
| if (errcode) |
| return errcode; |
| |
| /* If it was not created before, attach it to csf_info. |
| * Use spin lock to avoid concurrent initialization. |
| */ |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| if (csf_info->backend == NULL) { |
| csf_info->backend = backend_csf; |
| *out_backend = (struct kbase_hwcnt_backend *)backend_csf; |
| success = true; |
| if (csf_info->unrecoverable_error_happened) |
| backend_csf->enable_state = |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR; |
| } |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| |
| /* Destroy the new created backend if the backend has already created |
| * before. In normal case, this won't happen if the client call init() |
| * function properly. |
| */ |
| if (!success) { |
| kbasep_hwcnt_backend_csf_destroy(backend_csf); |
| return -EBUSY; |
| } |
| |
| return 0; |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_term_fn */ |
| static void kbasep_hwcnt_backend_csf_term(struct kbase_hwcnt_backend *backend) |
| { |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf *backend_csf = |
| (struct kbase_hwcnt_backend_csf *)backend; |
| |
| if (!backend) |
| return; |
| |
| kbasep_hwcnt_backend_csf_dump_disable(backend); |
| |
| /* Set the backend in csf_info to NULL so we won't handle any external |
| * notification anymore since we are terminating. |
| */ |
| backend_csf->info->csf_if->lock(backend_csf->info->csf_if->ctx, &flags); |
| backend_csf->info->backend = NULL; |
| backend_csf->info->csf_if->unlock(backend_csf->info->csf_if->ctx, |
| flags); |
| |
| kbasep_hwcnt_backend_csf_destroy(backend_csf); |
| } |
| |
| /** |
| * kbasep_hwcnt_backend_csf_info_destroy() - Destroy a CSF backend info. |
| * @info: Pointer to info to destroy. |
| * |
| * Can be safely called on a backend info in any state of partial construction. |
| * |
| */ |
| static void kbasep_hwcnt_backend_csf_info_destroy( |
| const struct kbase_hwcnt_backend_csf_info *info) |
| { |
| if (!info) |
| return; |
| |
| /* The backend should be destroyed before the info object destroy. */ |
| WARN_ON(info->backend != NULL); |
| |
| /* The metadata should be destroyed before the info object destroy. */ |
| WARN_ON(info->metadata != NULL); |
| |
| kfree(info); |
| } |
| |
| /** |
| * kbasep_hwcnt_backend_csf_info_create() - Create a CSF backend info. |
| * |
| * @csf_if: Non-NULL pointer to a hwcnt backend CSF interface structure |
| * used to create backend interface. |
| * @ring_buf_cnt: The buffer count of the CSF hwcnt backend ring buffer. |
| * MUST be power of 2. |
| * @out_info: Non-NULL pointer to where info is stored on success. |
| * @return 0 on success, else error code. |
| */ |
| static int kbasep_hwcnt_backend_csf_info_create( |
| struct kbase_hwcnt_backend_csf_if *csf_if, u32 ring_buf_cnt, |
| const struct kbase_hwcnt_backend_csf_info **out_info) |
| { |
| struct kbase_hwcnt_backend_csf_info *info = NULL; |
| |
| WARN_ON(!csf_if); |
| WARN_ON(!out_info); |
| WARN_ON(!is_power_of_2(ring_buf_cnt)); |
| |
| info = kzalloc(sizeof(*info), GFP_KERNEL); |
| if (!info) |
| return -ENOMEM; |
| |
| #if defined(CONFIG_MALI_PRFCNT_SET_SECONDARY) |
| info->counter_set = KBASE_HWCNT_SET_SECONDARY; |
| #elif defined(CONFIG_MALI_PRFCNT_SET_TERTIARY) |
| info->counter_set = KBASE_HWCNT_SET_TERTIARY; |
| #else |
| /* Default to primary */ |
| info->counter_set = KBASE_HWCNT_SET_PRIMARY; |
| #endif |
| |
| info->backend = NULL; |
| info->csf_if = csf_if; |
| info->ring_buf_cnt = ring_buf_cnt; |
| info->fw_in_protected_mode = false; |
| info->unrecoverable_error_happened = false; |
| |
| *out_info = info; |
| |
| return 0; |
| } |
| |
| /* CSF backend implementation of kbase_hwcnt_backend_metadata_fn */ |
| static const struct kbase_hwcnt_metadata * |
| kbasep_hwcnt_backend_csf_metadata(const struct kbase_hwcnt_backend_info *info) |
| { |
| if (!info) |
| return NULL; |
| |
| WARN_ON(!((const struct kbase_hwcnt_backend_csf_info *)info)->metadata); |
| |
| return ((const struct kbase_hwcnt_backend_csf_info *)info)->metadata; |
| } |
| |
| static void kbasep_hwcnt_backend_csf_handle_unrecoverable_error( |
| struct kbase_hwcnt_backend_csf *backend_csf) |
| { |
| bool do_disable = false; |
| |
| backend_csf->info->csf_if->assert_lock_held( |
| backend_csf->info->csf_if->ctx); |
| |
| /* We are already in or transitioning to the unrecoverable error state. |
| * Early out. |
| */ |
| if ((backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR) || |
| (backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR_WAIT_FOR_WORKER)) |
| return; |
| |
| /* If we are disabled, we know we have no pending workers, so skip the |
| * waiting state. |
| */ |
| if (backend_csf->enable_state == KBASE_HWCNT_BACKEND_CSF_DISABLED) { |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR); |
| return; |
| } |
| |
| /* Trigger a disable only if we are not already transitioning to |
| * disabled, we don't want to disable twice if an unrecoverable error |
| * happens while we are disabling. |
| */ |
| do_disable = (backend_csf->enable_state != |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_DISABLED); |
| |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR_WAIT_FOR_WORKER); |
| |
| /* Transition the dump to the IDLE state and unblock any waiters. The |
| * IDLE state signifies an error. |
| */ |
| backend_csf->dump_state = KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE; |
| complete_all(&backend_csf->dump_completed); |
| |
| /* Trigger a disable only if we are not already transitioning to |
| * disabled, - we don't want to disable twice if an unrecoverable error |
| * happens while we are disabling. |
| */ |
| if (do_disable) |
| backend_csf->info->csf_if->dump_disable( |
| backend_csf->info->csf_if->ctx); |
| } |
| |
| static void kbasep_hwcnt_backend_csf_handle_recoverable_error( |
| struct kbase_hwcnt_backend_csf *backend_csf) |
| { |
| backend_csf->info->csf_if->assert_lock_held( |
| backend_csf->info->csf_if->ctx); |
| |
| switch (backend_csf->enable_state) { |
| case KBASE_HWCNT_BACKEND_CSF_DISABLED: |
| case KBASE_HWCNT_BACKEND_CSF_DISABLED_WAIT_FOR_WORKER: |
| case KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_DISABLED: |
| case KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR: |
| case KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR_WAIT_FOR_WORKER: |
| /* Already disabled or disabling, or in an unrecoverable error. |
| * Nothing to be done to handle the error. |
| */ |
| return; |
| case KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_ENABLED: |
| /* A seemingly recoverable error that occurs while we are |
| * transitioning to enabled is probably unrecoverable. |
| */ |
| kbasep_hwcnt_backend_csf_handle_unrecoverable_error( |
| backend_csf); |
| return; |
| case KBASE_HWCNT_BACKEND_CSF_ENABLED: |
| /* Start transitioning to the disabled state. We can't wait for |
| * it as this recoverable error might be triggered from an |
| * interrupt. The wait will be done in the eventual call to |
| * disable(). |
| */ |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_DISABLED); |
| /* Transition the dump to the IDLE state and unblock any |
| * waiters. The IDLE state signifies an error. |
| */ |
| backend_csf->dump_state = KBASE_HWCNT_BACKEND_CSF_DUMP_IDLE; |
| complete_all(&backend_csf->dump_completed); |
| |
| backend_csf->info->csf_if->dump_disable( |
| backend_csf->info->csf_if->ctx); |
| return; |
| } |
| } |
| |
| void kbase_hwcnt_backend_csf_protm_entered( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| struct kbase_hwcnt_backend_csf_info *csf_info = |
| (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| |
| csf_info->csf_if->assert_lock_held(csf_info->csf_if->ctx); |
| csf_info->fw_in_protected_mode = true; |
| |
| /* Call on_prfcnt_sample() to trigger collection of the protected mode |
| * entry auto-sample if there is currently a pending dump request. |
| */ |
| kbase_hwcnt_backend_csf_on_prfcnt_sample(iface); |
| } |
| |
| void kbase_hwcnt_backend_csf_protm_exited( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| |
| csf_info->csf_if->assert_lock_held(csf_info->csf_if->ctx); |
| csf_info->fw_in_protected_mode = false; |
| } |
| |
| void kbase_hwcnt_backend_csf_on_unrecoverable_error( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| |
| csf_info->csf_if->lock(csf_info->csf_if->ctx, &flags); |
| csf_info->unrecoverable_error_happened = true; |
| /* Early out if the backend does not exist. */ |
| if (!kbasep_hwcnt_backend_csf_backend_exists(csf_info)) { |
| csf_info->csf_if->unlock(csf_info->csf_if->ctx, flags); |
| return; |
| } |
| |
| kbasep_hwcnt_backend_csf_handle_unrecoverable_error(csf_info->backend); |
| |
| csf_info->csf_if->unlock(csf_info->csf_if->ctx, flags); |
| } |
| |
| void kbase_hwcnt_backend_csf_on_before_reset( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| unsigned long flags; |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| struct kbase_hwcnt_backend_csf *backend_csf; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| |
| csf_info->csf_if->lock(csf_info->csf_if->ctx, &flags); |
| csf_info->unrecoverable_error_happened = false; |
| /* Early out if the backend does not exist. */ |
| if (!kbasep_hwcnt_backend_csf_backend_exists(csf_info)) { |
| csf_info->csf_if->unlock(csf_info->csf_if->ctx, flags); |
| return; |
| } |
| backend_csf = csf_info->backend; |
| |
| if ((backend_csf->enable_state != KBASE_HWCNT_BACKEND_CSF_DISABLED) && |
| (backend_csf->enable_state != |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR)) { |
| /* Before a reset occurs, we must either have been disabled |
| * (else we lose data) or we should have encountered an |
| * unrecoverable error. Either way, we will have disabled the |
| * interface and waited for any workers that might have still |
| * been in flight. |
| * If not in these states, fire off one more disable to make |
| * sure everything is turned off before the power is pulled. |
| * We can't wait for this disable to complete, but it doesn't |
| * really matter, the power is being pulled. |
| */ |
| kbasep_hwcnt_backend_csf_handle_unrecoverable_error( |
| csf_info->backend); |
| } |
| |
| /* A reset is the only way to exit the unrecoverable error state */ |
| if (backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_UNRECOVERABLE_ERROR) { |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, KBASE_HWCNT_BACKEND_CSF_DISABLED); |
| } |
| |
| csf_info->csf_if->unlock(csf_info->csf_if->ctx, flags); |
| } |
| |
| void kbase_hwcnt_backend_csf_on_prfcnt_sample( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| struct kbase_hwcnt_backend_csf *backend_csf; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| csf_info->csf_if->assert_lock_held(csf_info->csf_if->ctx); |
| |
| /* Early out if the backend does not exist. */ |
| if (!kbasep_hwcnt_backend_csf_backend_exists(csf_info)) |
| return; |
| backend_csf = csf_info->backend; |
| |
| /* If the current state is not REQUESTED, this HWC sample will be |
| * skipped and processed in next dump_request. |
| */ |
| if (backend_csf->dump_state != KBASE_HWCNT_BACKEND_CSF_DUMP_REQUESTED) |
| return; |
| backend_csf->dump_state = KBASE_HWCNT_BACKEND_CSF_DUMP_QUERYING_INSERT; |
| |
| kbase_hwcnt_backend_csf_submit_dump_worker(csf_info); |
| } |
| |
| void kbase_hwcnt_backend_csf_on_prfcnt_threshold( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| struct kbase_hwcnt_backend_csf *backend_csf; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| csf_info->csf_if->assert_lock_held(csf_info->csf_if->ctx); |
| |
| /* Early out if the backend does not exist. */ |
| if (!kbasep_hwcnt_backend_csf_backend_exists(csf_info)) |
| return; |
| backend_csf = csf_info->backend; |
| |
| if (backend_csf->enable_state == KBASE_HWCNT_BACKEND_CSF_ENABLED) |
| /* Submit the threshold work into the work queue to consume the |
| * available samples. |
| */ |
| queue_work(backend_csf->hwc_dump_workq, |
| &backend_csf->hwc_threshold_work); |
| } |
| |
| void kbase_hwcnt_backend_csf_on_prfcnt_overflow( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| csf_info->csf_if->assert_lock_held(csf_info->csf_if->ctx); |
| |
| /* Early out if the backend does not exist. */ |
| if (!kbasep_hwcnt_backend_csf_backend_exists(csf_info)) |
| return; |
| |
| /* Called when an overflow occurs. We treat this as a recoverable error, |
| * so we start transitioning to the disabled state. |
| * We could try and handle it while enabled, but in a real system we |
| * never expect an overflow to occur so there is no point implementing |
| * complex recovery code when we can just turn ourselves off instead for |
| * a while. |
| */ |
| kbasep_hwcnt_backend_csf_handle_recoverable_error(csf_info->backend); |
| } |
| |
| void kbase_hwcnt_backend_csf_on_prfcnt_enable( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| struct kbase_hwcnt_backend_csf *backend_csf; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| csf_info->csf_if->assert_lock_held(csf_info->csf_if->ctx); |
| |
| /* Early out if the backend does not exist. */ |
| if (!kbasep_hwcnt_backend_csf_backend_exists(csf_info)) |
| return; |
| backend_csf = csf_info->backend; |
| |
| if (backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_ENABLED) { |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, KBASE_HWCNT_BACKEND_CSF_ENABLED); |
| } else if (backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_ENABLED) { |
| /* Unexpected, but we are already in the right state so just |
| * ignore it. |
| */ |
| } else { |
| /* Unexpected state change, assume everything is broken until |
| * we reset. |
| */ |
| kbasep_hwcnt_backend_csf_handle_unrecoverable_error( |
| csf_info->backend); |
| } |
| } |
| |
| void kbase_hwcnt_backend_csf_on_prfcnt_disable( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| struct kbase_hwcnt_backend_csf *backend_csf; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| csf_info->csf_if->assert_lock_held(csf_info->csf_if->ctx); |
| |
| /* Early out if the backend does not exist. */ |
| if (!kbasep_hwcnt_backend_csf_backend_exists(csf_info)) |
| return; |
| backend_csf = csf_info->backend; |
| |
| if (backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_TRANSITIONING_TO_DISABLED) { |
| kbasep_hwcnt_backend_csf_change_es_and_wake_waiters( |
| backend_csf, |
| KBASE_HWCNT_BACKEND_CSF_DISABLED_WAIT_FOR_WORKER); |
| } else if (backend_csf->enable_state == |
| KBASE_HWCNT_BACKEND_CSF_DISABLED) { |
| /* Unexpected, but we are already in the right state so just |
| * ignore it. |
| */ |
| } else { |
| /* Unexpected state change, assume everything is broken until |
| * we reset. |
| */ |
| kbasep_hwcnt_backend_csf_handle_unrecoverable_error( |
| csf_info->backend); |
| } |
| } |
| |
| int kbase_hwcnt_backend_csf_metadata_init( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| int errcode; |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| struct kbase_hwcnt_gpu_info gpu_info; |
| |
| if (!iface) |
| return -EINVAL; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| |
| WARN_ON(!csf_info->csf_if->get_prfcnt_info); |
| |
| csf_info->csf_if->get_prfcnt_info(csf_info->csf_if->ctx, |
| &csf_info->prfcnt_info); |
| |
| /* The clock domain counts should not exceed the number of maximum |
| * number of clock regulators. |
| */ |
| if (csf_info->prfcnt_info.clk_cnt > BASE_MAX_NR_CLOCKS_REGULATORS) |
| return -EIO; |
| |
| gpu_info.l2_count = csf_info->prfcnt_info.l2_count; |
| gpu_info.core_mask = csf_info->prfcnt_info.core_mask; |
| gpu_info.clk_cnt = csf_info->prfcnt_info.clk_cnt; |
| gpu_info.prfcnt_values_per_block = |
| csf_info->prfcnt_info.prfcnt_block_size / |
| KBASE_HWCNT_VALUE_BYTES; |
| errcode = kbase_hwcnt_csf_metadata_create( |
| &gpu_info, csf_info->counter_set, &csf_info->metadata); |
| if (errcode) |
| return errcode; |
| |
| /* |
| * Dump abstraction size should be exactly the same size and layout as |
| * the physical dump size, for backwards compatibility. |
| */ |
| WARN_ON(csf_info->prfcnt_info.dump_bytes != |
| csf_info->metadata->dump_buf_bytes); |
| |
| return 0; |
| } |
| |
| void kbase_hwcnt_backend_csf_metadata_term( |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| struct kbase_hwcnt_backend_csf_info *csf_info; |
| |
| if (!iface) |
| return; |
| |
| csf_info = (struct kbase_hwcnt_backend_csf_info *)iface->info; |
| if (csf_info->metadata) { |
| kbase_hwcnt_csf_metadata_destroy(csf_info->metadata); |
| csf_info->metadata = NULL; |
| } |
| } |
| |
| int kbase_hwcnt_backend_csf_create(struct kbase_hwcnt_backend_csf_if *csf_if, |
| u32 ring_buf_cnt, |
| struct kbase_hwcnt_backend_interface *iface) |
| { |
| int errcode; |
| const struct kbase_hwcnt_backend_csf_info *info = NULL; |
| |
| if (!iface || !csf_if) |
| return -EINVAL; |
| |
| /* The buffer count must be power of 2 */ |
| if (!is_power_of_2(ring_buf_cnt)) |
| return -EINVAL; |
| |
| errcode = kbasep_hwcnt_backend_csf_info_create(csf_if, ring_buf_cnt, |
| &info); |
| if (errcode) |
| return errcode; |
| |
| iface->info = (struct kbase_hwcnt_backend_info *)info; |
| iface->metadata = kbasep_hwcnt_backend_csf_metadata; |
| iface->init = kbasep_hwcnt_backend_csf_init; |
| iface->term = kbasep_hwcnt_backend_csf_term; |
| iface->timestamp_ns = kbasep_hwcnt_backend_csf_timestamp_ns; |
| iface->dump_enable = kbasep_hwcnt_backend_csf_dump_enable; |
| iface->dump_enable_nolock = kbasep_hwcnt_backend_csf_dump_enable_nolock; |
| iface->dump_disable = kbasep_hwcnt_backend_csf_dump_disable; |
| iface->dump_clear = kbasep_hwcnt_backend_csf_dump_clear; |
| iface->dump_request = kbasep_hwcnt_backend_csf_dump_request; |
| iface->dump_wait = kbasep_hwcnt_backend_csf_dump_wait; |
| iface->dump_get = kbasep_hwcnt_backend_csf_dump_get; |
| |
| return 0; |
| } |
| |
| void kbase_hwcnt_backend_csf_destroy(struct kbase_hwcnt_backend_interface *iface) |
| { |
| if (!iface) |
| return; |
| |
| kbasep_hwcnt_backend_csf_info_destroy( |
| (const struct kbase_hwcnt_backend_csf_info *)iface->info); |
| memset(iface, 0, sizeof(*iface)); |
| } |