| // SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note |
| /* |
| * |
| * (C) COPYRIGHT 2015-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_timeline.h" |
| #include "mali_kbase_timeline_priv.h" |
| #include "mali_kbase_tracepoints.h" |
| |
| #include <mali_kbase.h> |
| #include <mali_kbase_jm.h> |
| |
| #include <linux/anon_inodes.h> |
| #include <linux/atomic.h> |
| #include <linux/file.h> |
| #include <linux/mutex.h> |
| #include <linux/spinlock.h> |
| #include <linux/string.h> |
| #include <linux/stringify.h> |
| #include <linux/timer.h> |
| #include <linux/wait.h> |
| |
| |
| /* The period of autoflush checker execution in milliseconds. */ |
| #define AUTOFLUSH_INTERVAL 1000 /* ms */ |
| |
| /*****************************************************************************/ |
| |
| /* These values are used in mali_kbase_tracepoints.h |
| * to retrieve the streams from a kbase_timeline instance. |
| */ |
| const size_t __obj_stream_offset = |
| offsetof(struct kbase_timeline, streams) |
| + sizeof(struct kbase_tlstream) * TL_STREAM_TYPE_OBJ; |
| |
| const size_t __aux_stream_offset = |
| offsetof(struct kbase_timeline, streams) |
| + sizeof(struct kbase_tlstream) * TL_STREAM_TYPE_AUX; |
| |
| /** |
| * kbasep_timeline_autoflush_timer_callback - autoflush timer callback |
| * @timer: Timer list |
| * |
| * Timer is executed periodically to check if any of the stream contains |
| * buffer ready to be submitted to user space. |
| */ |
| static void kbasep_timeline_autoflush_timer_callback(struct timer_list *timer) |
| { |
| enum tl_stream_type stype; |
| int rcode; |
| struct kbase_timeline *timeline = |
| container_of(timer, struct kbase_timeline, autoflush_timer); |
| |
| CSTD_UNUSED(timer); |
| |
| for (stype = (enum tl_stream_type)0; stype < TL_STREAM_TYPE_COUNT; |
| stype++) { |
| struct kbase_tlstream *stream = &timeline->streams[stype]; |
| |
| int af_cnt = atomic_read(&stream->autoflush_counter); |
| |
| /* Check if stream contain unflushed data. */ |
| if (af_cnt < 0) |
| continue; |
| |
| /* Check if stream should be flushed now. */ |
| if (af_cnt != atomic_cmpxchg( |
| &stream->autoflush_counter, |
| af_cnt, |
| af_cnt + 1)) |
| continue; |
| if (!af_cnt) |
| continue; |
| |
| /* Autoflush this stream. */ |
| kbase_tlstream_flush_stream(stream); |
| } |
| |
| if (atomic_read(&timeline->autoflush_timer_active)) |
| rcode = mod_timer( |
| &timeline->autoflush_timer, |
| jiffies + msecs_to_jiffies(AUTOFLUSH_INTERVAL)); |
| CSTD_UNUSED(rcode); |
| } |
| |
| |
| |
| /*****************************************************************************/ |
| |
| int kbase_timeline_init(struct kbase_timeline **timeline, |
| atomic_t *timeline_flags) |
| { |
| enum tl_stream_type i; |
| struct kbase_timeline *result; |
| #if MALI_USE_CSF |
| struct kbase_tlstream *csffw_stream; |
| #endif |
| |
| if (!timeline || !timeline_flags) |
| return -EINVAL; |
| |
| result = vzalloc(sizeof(*result)); |
| if (!result) |
| return -ENOMEM; |
| |
| mutex_init(&result->reader_lock); |
| init_waitqueue_head(&result->event_queue); |
| |
| /* Prepare stream structures. */ |
| for (i = 0; i < TL_STREAM_TYPE_COUNT; i++) |
| kbase_tlstream_init(&result->streams[i], i, |
| &result->event_queue); |
| |
| /* Initialize the kctx list */ |
| mutex_init(&result->tl_kctx_list_lock); |
| INIT_LIST_HEAD(&result->tl_kctx_list); |
| |
| /* Initialize autoflush timer. */ |
| atomic_set(&result->autoflush_timer_active, 0); |
| kbase_timer_setup(&result->autoflush_timer, |
| kbasep_timeline_autoflush_timer_callback); |
| result->timeline_flags = timeline_flags; |
| |
| #if MALI_USE_CSF |
| csffw_stream = &result->streams[TL_STREAM_TYPE_CSFFW]; |
| kbase_csf_tl_reader_init(&result->csf_tl_reader, csffw_stream); |
| #endif |
| |
| *timeline = result; |
| return 0; |
| } |
| |
| void kbase_timeline_term(struct kbase_timeline *timeline) |
| { |
| enum tl_stream_type i; |
| |
| if (!timeline) |
| return; |
| |
| #if MALI_USE_CSF |
| kbase_csf_tl_reader_term(&timeline->csf_tl_reader); |
| #endif |
| |
| WARN_ON(!list_empty(&timeline->tl_kctx_list)); |
| |
| for (i = (enum tl_stream_type)0; i < TL_STREAM_TYPE_COUNT; i++) |
| kbase_tlstream_term(&timeline->streams[i]); |
| |
| vfree(timeline); |
| } |
| |
| #ifdef CONFIG_MALI_DEVFREQ |
| static void kbase_tlstream_current_devfreq_target(struct kbase_device *kbdev) |
| { |
| struct devfreq *devfreq = kbdev->devfreq; |
| |
| /* Devfreq initialization failure isn't a fatal error, so devfreq might |
| * be null. |
| */ |
| if (devfreq) { |
| unsigned long cur_freq = 0; |
| |
| mutex_lock(&devfreq->lock); |
| cur_freq = devfreq->last_status.current_frequency; |
| KBASE_TLSTREAM_AUX_DEVFREQ_TARGET(kbdev, (u64)cur_freq); |
| mutex_unlock(&devfreq->lock); |
| } |
| } |
| #endif /* CONFIG_MALI_DEVFREQ */ |
| |
| int kbase_timeline_io_acquire(struct kbase_device *kbdev, u32 flags) |
| { |
| int ret = 0; |
| u32 timeline_flags = TLSTREAM_ENABLED | flags; |
| struct kbase_timeline *timeline = kbdev->timeline; |
| |
| if (!atomic_cmpxchg(timeline->timeline_flags, 0, timeline_flags)) { |
| int rcode; |
| |
| #if MALI_USE_CSF |
| if (flags & BASE_TLSTREAM_ENABLE_CSFFW_TRACEPOINTS) { |
| ret = kbase_csf_tl_reader_start( |
| &timeline->csf_tl_reader, kbdev); |
| if (ret) |
| { |
| atomic_set(timeline->timeline_flags, 0); |
| return ret; |
| } |
| } |
| #endif |
| ret = anon_inode_getfd( |
| "[mali_tlstream]", |
| &kbasep_tlstream_fops, |
| timeline, |
| O_RDONLY | O_CLOEXEC); |
| if (ret < 0) { |
| atomic_set(timeline->timeline_flags, 0); |
| #if MALI_USE_CSF |
| kbase_csf_tl_reader_stop(&timeline->csf_tl_reader); |
| #endif |
| return ret; |
| } |
| |
| /* Reset and initialize header streams. */ |
| kbase_tlstream_reset( |
| &timeline->streams[TL_STREAM_TYPE_OBJ_SUMMARY]); |
| |
| timeline->obj_header_btc = obj_desc_header_size; |
| timeline->aux_header_btc = aux_desc_header_size; |
| |
| /* Start autoflush timer. */ |
| atomic_set(&timeline->autoflush_timer_active, 1); |
| rcode = mod_timer( |
| &timeline->autoflush_timer, |
| jiffies + msecs_to_jiffies(AUTOFLUSH_INTERVAL)); |
| CSTD_UNUSED(rcode); |
| |
| #if !MALI_USE_CSF |
| /* If job dumping is enabled, readjust the software event's |
| * timeout as the default value of 3 seconds is often |
| * insufficient. |
| */ |
| if (flags & BASE_TLSTREAM_JOB_DUMPING_ENABLED) { |
| dev_info(kbdev->dev, |
| "Job dumping is enabled, readjusting the software event's timeout\n"); |
| atomic_set(&kbdev->js_data.soft_job_timeout_ms, |
| 1800000); |
| } |
| #endif /* !MALI_USE_CSF */ |
| |
| /* Summary stream was cleared during acquire. |
| * Create static timeline objects that will be |
| * read by client. |
| */ |
| kbase_create_timeline_objects(kbdev); |
| |
| #ifdef CONFIG_MALI_DEVFREQ |
| /* Devfreq target tracepoints are only fired when the target |
| * changes, so we won't know the current target unless we |
| * send it now. |
| */ |
| kbase_tlstream_current_devfreq_target(kbdev); |
| #endif /* CONFIG_MALI_DEVFREQ */ |
| |
| } else { |
| ret = -EBUSY; |
| } |
| |
| if (ret >= 0) |
| timeline->last_acquire_time = ktime_get(); |
| |
| return ret; |
| } |
| |
| int kbase_timeline_streams_flush(struct kbase_timeline *timeline) |
| { |
| enum tl_stream_type stype; |
| bool has_bytes = false; |
| size_t nbytes = 0; |
| #if MALI_USE_CSF |
| int ret = kbase_csf_tl_reader_flush_buffer(&timeline->csf_tl_reader); |
| |
| if (ret > 0) |
| has_bytes = true; |
| #endif |
| |
| for (stype = 0; stype < TL_STREAM_TYPE_COUNT; stype++) { |
| nbytes = kbase_tlstream_flush_stream(&timeline->streams[stype]); |
| if (nbytes > 0) |
| has_bytes = true; |
| } |
| return has_bytes ? 0 : -EIO; |
| } |
| |
| void kbase_timeline_streams_body_reset(struct kbase_timeline *timeline) |
| { |
| kbase_tlstream_reset( |
| &timeline->streams[TL_STREAM_TYPE_OBJ]); |
| kbase_tlstream_reset( |
| &timeline->streams[TL_STREAM_TYPE_AUX]); |
| #if MALI_USE_CSF |
| kbase_tlstream_reset( |
| &timeline->streams[TL_STREAM_TYPE_CSFFW]); |
| #endif |
| } |
| |
| void kbase_timeline_pre_kbase_context_destroy(struct kbase_context *kctx) |
| { |
| struct kbase_device *const kbdev = kctx->kbdev; |
| struct kbase_timeline *timeline = kbdev->timeline; |
| |
| /* Remove the context from the list to ensure we don't try and |
| * summarize a context that is being destroyed. |
| * |
| * It's unsafe to try and summarize a context being destroyed as the |
| * locks we might normally attempt to acquire, and the data structures |
| * we would normally attempt to traverse could already be destroyed. |
| * |
| * In the case where the tlstream is acquired between this pre destroy |
| * call and the post destroy call, we will get a context destroy |
| * tracepoint without the corresponding context create tracepoint, |
| * but this will not affect the correctness of the object model. |
| */ |
| mutex_lock(&timeline->tl_kctx_list_lock); |
| list_del_init(&kctx->tl_kctx_list_node); |
| mutex_unlock(&timeline->tl_kctx_list_lock); |
| } |
| |
| void kbase_timeline_post_kbase_context_create(struct kbase_context *kctx) |
| { |
| struct kbase_device *const kbdev = kctx->kbdev; |
| struct kbase_timeline *timeline = kbdev->timeline; |
| |
| /* On context create, add the context to the list to ensure it is |
| * summarized when timeline is acquired |
| */ |
| mutex_lock(&timeline->tl_kctx_list_lock); |
| |
| list_add(&kctx->tl_kctx_list_node, &timeline->tl_kctx_list); |
| |
| /* Fire the tracepoints with the lock held to ensure the tracepoints |
| * are either fired before or after the summarization, |
| * never in parallel with it. If fired in parallel, we could get |
| * duplicate creation tracepoints. |
| */ |
| #if MALI_USE_CSF |
| KBASE_TLSTREAM_TL_KBASE_NEW_CTX( |
| kbdev, kctx->id, kbdev->gpu_props.props.raw_props.gpu_id); |
| #endif |
| /* Trace with the AOM tracepoint even in CSF for dumping */ |
| KBASE_TLSTREAM_TL_NEW_CTX(kbdev, kctx, kctx->id, 0); |
| |
| mutex_unlock(&timeline->tl_kctx_list_lock); |
| } |
| |
| void kbase_timeline_post_kbase_context_destroy(struct kbase_context *kctx) |
| { |
| struct kbase_device *const kbdev = kctx->kbdev; |
| |
| /* Trace with the AOM tracepoint even in CSF for dumping */ |
| KBASE_TLSTREAM_TL_DEL_CTX(kbdev, kctx); |
| #if MALI_USE_CSF |
| KBASE_TLSTREAM_TL_KBASE_DEL_CTX(kbdev, kctx->id); |
| #endif |
| |
| /* Flush the timeline stream, so the user can see the termination |
| * tracepoints being fired. |
| * The "if" statement below is for optimization. It is safe to call |
| * kbase_timeline_streams_flush when timeline is disabled. |
| */ |
| if (atomic_read(&kbdev->timeline_flags) != 0) |
| kbase_timeline_streams_flush(kbdev->timeline); |
| } |
| |
| #if MALI_UNIT_TEST |
| void kbase_timeline_stats(struct kbase_timeline *timeline, |
| u32 *bytes_collected, u32 *bytes_generated) |
| { |
| enum tl_stream_type stype; |
| |
| KBASE_DEBUG_ASSERT(bytes_collected); |
| |
| /* Accumulate bytes generated per stream */ |
| *bytes_generated = 0; |
| for (stype = (enum tl_stream_type)0; stype < TL_STREAM_TYPE_COUNT; |
| stype++) |
| *bytes_generated += atomic_read( |
| &timeline->streams[stype].bytes_generated); |
| |
| *bytes_collected = atomic_read(&timeline->bytes_collected); |
| } |
| #endif /* MALI_UNIT_TEST */ |