675 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			675 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /*
 | |
|  *
 | |
|  * (C) COPYRIGHT 2010-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.
 | |
|  *
 | |
|  */
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| 
 | |
| /*
 | |
|  * Base kernel device APIs
 | |
|  */
 | |
| 
 | |
| #include <linux/debugfs.h>
 | |
| #include <linux/dma-mapping.h>
 | |
| #include <linux/seq_file.h>
 | |
| #include <linux/kernel.h>
 | |
| #include <linux/module.h>
 | |
| #include <linux/of_platform.h>
 | |
| 
 | |
| #include <mali_kbase.h>
 | |
| #include <mali_kbase_defs.h>
 | |
| #include <mali_kbase_hwaccess_instr.h>
 | |
| #include <mali_kbase_hw.h>
 | |
| #include <mali_kbase_config_defaults.h>
 | |
| 
 | |
| #include <mali_kbase_profiling_gator_api.h>
 | |
| 
 | |
| /* NOTE: Magic - 0x45435254 (TRCE in ASCII).
 | |
|  * Supports tracing feature provided in the base module.
 | |
|  * Please keep it in sync with the value of base module.
 | |
|  */
 | |
| #define TRACE_BUFFER_HEADER_SPECIAL 0x45435254
 | |
| 
 | |
| #if KBASE_TRACE_ENABLE
 | |
| static const char *kbasep_trace_code_string[] = {
 | |
| 	/* IMPORTANT: USE OF SPECIAL #INCLUDE OF NON-STANDARD HEADER FILE
 | |
| 	 * THIS MUST BE USED AT THE START OF THE ARRAY */
 | |
| #define KBASE_TRACE_CODE_MAKE_CODE(X) # X
 | |
| #include "mali_kbase_trace_defs.h"
 | |
| #undef  KBASE_TRACE_CODE_MAKE_CODE
 | |
| };
 | |
| #endif
 | |
| 
 | |
| #define DEBUG_MESSAGE_SIZE 256
 | |
| 
 | |
| static int kbasep_trace_init(struct kbase_device *kbdev);
 | |
| static void kbasep_trace_term(struct kbase_device *kbdev);
 | |
| static void kbasep_trace_hook_wrapper(void *param);
 | |
| 
 | |
| struct kbase_device *kbase_device_alloc(void)
 | |
| {
 | |
| 	return kzalloc(sizeof(struct kbase_device), GFP_KERNEL);
 | |
| }
 | |
| 
 | |
| static int kbase_device_as_init(struct kbase_device *kbdev, int i)
 | |
| {
 | |
| 	const char format[] = "mali_mmu%d";
 | |
| 	char name[sizeof(format)];
 | |
| 	const char poke_format[] = "mali_mmu%d_poker";
 | |
| 	char poke_name[sizeof(poke_format)];
 | |
| 
 | |
| 	if (kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_8316))
 | |
| 		snprintf(poke_name, sizeof(poke_name), poke_format, i);
 | |
| 
 | |
| 	snprintf(name, sizeof(name), format, i);
 | |
| 
 | |
| 	kbdev->as[i].number = i;
 | |
| 	kbdev->as[i].fault_addr = 0ULL;
 | |
| 
 | |
| 	kbdev->as[i].pf_wq = alloc_workqueue(name, 0, 1);
 | |
| 	if (!kbdev->as[i].pf_wq)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	INIT_WORK(&kbdev->as[i].work_pagefault, page_fault_worker);
 | |
| 	INIT_WORK(&kbdev->as[i].work_busfault, bus_fault_worker);
 | |
| 
 | |
| 	if (kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_8316)) {
 | |
| 		struct hrtimer *poke_timer = &kbdev->as[i].poke_timer;
 | |
| 		struct work_struct *poke_work = &kbdev->as[i].poke_work;
 | |
| 
 | |
| 		kbdev->as[i].poke_wq = alloc_workqueue(poke_name, 0, 1);
 | |
| 		if (!kbdev->as[i].poke_wq) {
 | |
| 			destroy_workqueue(kbdev->as[i].pf_wq);
 | |
| 			return -EINVAL;
 | |
| 		}
 | |
| 		KBASE_DEBUG_ASSERT(!object_is_on_stack(poke_work));
 | |
| 		INIT_WORK(poke_work, kbasep_as_do_poke);
 | |
| 
 | |
| 		hrtimer_init(poke_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
 | |
| 
 | |
| 		poke_timer->function = kbasep_as_poke_timer_callback;
 | |
| 
 | |
| 		kbdev->as[i].poke_refcount = 0;
 | |
| 		kbdev->as[i].poke_state = 0u;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void kbase_device_as_term(struct kbase_device *kbdev, int i)
 | |
| {
 | |
| 	destroy_workqueue(kbdev->as[i].pf_wq);
 | |
| 	if (kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_8316))
 | |
| 		destroy_workqueue(kbdev->as[i].poke_wq);
 | |
| }
 | |
| 
 | |
| static int kbase_device_all_as_init(struct kbase_device *kbdev)
 | |
| {
 | |
| 	int i, err;
 | |
| 
 | |
| 	for (i = 0; i < kbdev->nr_hw_address_spaces; i++) {
 | |
| 		err = kbase_device_as_init(kbdev, i);
 | |
| 		if (err)
 | |
| 			goto free_workqs;
 | |
| 	}
 | |
| 
 | |
| 	return 0;
 | |
| 
 | |
| free_workqs:
 | |
| 	for (; i > 0; i--)
 | |
| 		kbase_device_as_term(kbdev, i);
 | |
| 
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| static void kbase_device_all_as_term(struct kbase_device *kbdev)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; i < kbdev->nr_hw_address_spaces; i++)
 | |
| 		kbase_device_as_term(kbdev, i);
 | |
| }
 | |
| 
 | |
| int kbase_device_init(struct kbase_device * const kbdev)
 | |
| {
 | |
| 	int i, err;
 | |
| #ifdef CONFIG_ARM64
 | |
| 	struct device_node *np = NULL;
 | |
| #endif /* CONFIG_ARM64 */
 | |
| 
 | |
| 	spin_lock_init(&kbdev->mmu_mask_change);
 | |
| 	mutex_init(&kbdev->mmu_hw_mutex);
 | |
| #ifdef CONFIG_ARM64
 | |
| 	kbdev->cci_snoop_enabled = false;
 | |
| 	np = kbdev->dev->of_node;
 | |
| 	if (np != NULL) {
 | |
| 		if (of_property_read_u32(np, "snoop_enable_smc",
 | |
| 					&kbdev->snoop_enable_smc))
 | |
| 			kbdev->snoop_enable_smc = 0;
 | |
| 		if (of_property_read_u32(np, "snoop_disable_smc",
 | |
| 					&kbdev->snoop_disable_smc))
 | |
| 			kbdev->snoop_disable_smc = 0;
 | |
| 		/* Either both or none of the calls should be provided. */
 | |
| 		if (!((kbdev->snoop_disable_smc == 0
 | |
| 			&& kbdev->snoop_enable_smc == 0)
 | |
| 			|| (kbdev->snoop_disable_smc != 0
 | |
| 			&& kbdev->snoop_enable_smc != 0))) {
 | |
| 			WARN_ON(1);
 | |
| 			err = -EINVAL;
 | |
| 			goto fail;
 | |
| 		}
 | |
| 	}
 | |
| #endif /* CONFIG_ARM64 */
 | |
| 	/* Get the list of workarounds for issues on the current HW
 | |
| 	 * (identified by the GPU_ID register)
 | |
| 	 */
 | |
| 	err = kbase_hw_set_issues_mask(kbdev);
 | |
| 	if (err)
 | |
| 		goto fail;
 | |
| 
 | |
| 	/* Set the list of features available on the current HW
 | |
| 	 * (identified by the GPU_ID register)
 | |
| 	 */
 | |
| 	kbase_hw_set_features_mask(kbdev);
 | |
| 
 | |
| 	kbase_gpuprops_set_features(kbdev);
 | |
| 
 | |
| 	/* On Linux 4.0+, dma coherency is determined from device tree */
 | |
| #if defined(CONFIG_ARM64) && LINUX_VERSION_CODE < KERNEL_VERSION(4, 0, 0)
 | |
| 	set_dma_ops(kbdev->dev, &noncoherent_swiotlb_dma_ops);
 | |
| #endif
 | |
| 
 | |
| 	/* Workaround a pre-3.13 Linux issue, where dma_mask is NULL when our
 | |
| 	 * device structure was created by device-tree
 | |
| 	 */
 | |
| 	if (!kbdev->dev->dma_mask)
 | |
| 		kbdev->dev->dma_mask = &kbdev->dev->coherent_dma_mask;
 | |
| 
 | |
| 	err = dma_set_mask(kbdev->dev,
 | |
| 			DMA_BIT_MASK(kbdev->gpu_props.mmu.pa_bits));
 | |
| 	if (err)
 | |
| 		goto dma_set_mask_failed;
 | |
| 
 | |
| 	err = dma_set_coherent_mask(kbdev->dev,
 | |
| 			DMA_BIT_MASK(kbdev->gpu_props.mmu.pa_bits));
 | |
| 	if (err)
 | |
| 		goto dma_set_mask_failed;
 | |
| 
 | |
| 	kbdev->nr_hw_address_spaces = kbdev->gpu_props.num_address_spaces;
 | |
| 
 | |
| 	err = kbase_device_all_as_init(kbdev);
 | |
| 	if (err)
 | |
| 		goto as_init_failed;
 | |
| 
 | |
| 	spin_lock_init(&kbdev->hwcnt.lock);
 | |
| 
 | |
| 	err = kbasep_trace_init(kbdev);
 | |
| 	if (err)
 | |
| 		goto term_as;
 | |
| 
 | |
| 	mutex_init(&kbdev->cacheclean_lock);
 | |
| 
 | |
| #ifdef CONFIG_MALI_TRACE_TIMELINE
 | |
| 	for (i = 0; i < BASE_JM_MAX_NR_SLOTS; ++i)
 | |
| 		kbdev->timeline.slot_atoms_submitted[i] = 0;
 | |
| 
 | |
| 	for (i = 0; i <= KBASEP_TIMELINE_PM_EVENT_LAST; ++i)
 | |
| 		atomic_set(&kbdev->timeline.pm_event_uid[i], 0);
 | |
| #endif /* CONFIG_MALI_TRACE_TIMELINE */
 | |
| 
 | |
| 	/* fbdump profiling controls set to 0 - fbdump not enabled until changed by gator */
 | |
| 	for (i = 0; i < FBDUMP_CONTROL_MAX; i++)
 | |
| 		kbdev->kbase_profiling_controls[i] = 0;
 | |
| 
 | |
| 	kbase_debug_assert_register_hook(&kbasep_trace_hook_wrapper, kbdev);
 | |
| 
 | |
| 	atomic_set(&kbdev->ctx_num, 0);
 | |
| 
 | |
| 	err = kbase_instr_backend_init(kbdev);
 | |
| 	if (err)
 | |
| 		goto term_trace;
 | |
| 
 | |
| 	kbdev->pm.dvfs_period = DEFAULT_PM_DVFS_PERIOD;
 | |
| 
 | |
| 	kbdev->reset_timeout_ms = DEFAULT_RESET_TIMEOUT_MS;
 | |
| 
 | |
| 	if (kbase_hw_has_feature(kbdev, BASE_HW_FEATURE_AARCH64_MMU))
 | |
| 		kbdev->mmu_mode = kbase_mmu_mode_get_aarch64();
 | |
| 	else
 | |
| 		kbdev->mmu_mode = kbase_mmu_mode_get_lpae();
 | |
| 
 | |
| #ifdef CONFIG_MALI_DEBUG
 | |
| 	init_waitqueue_head(&kbdev->driver_inactive_wait);
 | |
| #endif /* CONFIG_MALI_DEBUG */
 | |
| 
 | |
| 	return 0;
 | |
| term_trace:
 | |
| 	kbasep_trace_term(kbdev);
 | |
| term_as:
 | |
| 	kbase_device_all_as_term(kbdev);
 | |
| as_init_failed:
 | |
| dma_set_mask_failed:
 | |
| fail:
 | |
| 	return err;
 | |
| }
 | |
| 
 | |
| void kbase_device_term(struct kbase_device *kbdev)
 | |
| {
 | |
| 	KBASE_DEBUG_ASSERT(kbdev);
 | |
| 
 | |
| #if KBASE_TRACE_ENABLE
 | |
| 	kbase_debug_assert_register_hook(NULL, NULL);
 | |
| #endif
 | |
| 
 | |
| 	kbase_instr_backend_term(kbdev);
 | |
| 
 | |
| 	kbasep_trace_term(kbdev);
 | |
| 
 | |
| 	kbase_device_all_as_term(kbdev);
 | |
| }
 | |
| 
 | |
| void kbase_device_free(struct kbase_device *kbdev)
 | |
| {
 | |
| 	kfree(kbdev);
 | |
| }
 | |
| 
 | |
| int kbase_device_trace_buffer_install(
 | |
| 		struct kbase_context *kctx, u32 *tb, size_t size)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(kctx);
 | |
| 	KBASE_DEBUG_ASSERT(tb);
 | |
| 
 | |
| 	/* Interface uses 16-bit value to track last accessed entry. Each entry
 | |
| 	 * is composed of two 32-bit words.
 | |
| 	 * This limits the size that can be handled without an overflow. */
 | |
| 	if (0xFFFF * (2 * sizeof(u32)) < size)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	/* set up the header */
 | |
| 	/* magic number in the first 4 bytes */
 | |
| 	tb[0] = TRACE_BUFFER_HEADER_SPECIAL;
 | |
| 	/* Store (write offset = 0, wrap counter = 0, transaction active = no)
 | |
| 	 * write offset 0 means never written.
 | |
| 	 * Offsets 1 to (wrap_offset - 1) used to store values when trace started
 | |
| 	 */
 | |
| 	tb[1] = 0;
 | |
| 
 | |
| 	/* install trace buffer */
 | |
| 	spin_lock_irqsave(&kctx->jctx.tb_lock, flags);
 | |
| 	kctx->jctx.tb_wrap_offset = size / 8;
 | |
| 	kctx->jctx.tb = tb;
 | |
| 	spin_unlock_irqrestore(&kctx->jctx.tb_lock, flags);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| void kbase_device_trace_buffer_uninstall(struct kbase_context *kctx)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	KBASE_DEBUG_ASSERT(kctx);
 | |
| 	spin_lock_irqsave(&kctx->jctx.tb_lock, flags);
 | |
| 	kctx->jctx.tb = NULL;
 | |
| 	kctx->jctx.tb_wrap_offset = 0;
 | |
| 	spin_unlock_irqrestore(&kctx->jctx.tb_lock, flags);
 | |
| }
 | |
| 
 | |
| void kbase_device_trace_register_access(struct kbase_context *kctx, enum kbase_reg_access_type type, u16 reg_offset, u32 reg_value)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	spin_lock_irqsave(&kctx->jctx.tb_lock, flags);
 | |
| 	if (kctx->jctx.tb) {
 | |
| 		u16 wrap_count;
 | |
| 		u16 write_offset;
 | |
| 		u32 *tb = kctx->jctx.tb;
 | |
| 		u32 header_word;
 | |
| 
 | |
| 		header_word = tb[1];
 | |
| 		KBASE_DEBUG_ASSERT(0 == (header_word & 0x1));
 | |
| 
 | |
| 		wrap_count = (header_word >> 1) & 0x7FFF;
 | |
| 		write_offset = (header_word >> 16) & 0xFFFF;
 | |
| 
 | |
| 		/* mark as transaction in progress */
 | |
| 		tb[1] |= 0x1;
 | |
| 		mb();
 | |
| 
 | |
| 		/* calculate new offset */
 | |
| 		write_offset++;
 | |
| 		if (write_offset == kctx->jctx.tb_wrap_offset) {
 | |
| 			/* wrap */
 | |
| 			write_offset = 1;
 | |
| 			wrap_count++;
 | |
| 			wrap_count &= 0x7FFF;	/* 15bit wrap counter */
 | |
| 		}
 | |
| 
 | |
| 		/* store the trace entry at the selected offset */
 | |
| 		tb[write_offset * 2 + 0] = (reg_offset & ~0x3) | ((type == REG_WRITE) ? 0x1 : 0x0);
 | |
| 		tb[write_offset * 2 + 1] = reg_value;
 | |
| 		mb();
 | |
| 
 | |
| 		/* new header word */
 | |
| 		header_word = (write_offset << 16) | (wrap_count << 1) | 0x0;	/* transaction complete */
 | |
| 		tb[1] = header_word;
 | |
| 	}
 | |
| 	spin_unlock_irqrestore(&kctx->jctx.tb_lock, flags);
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Device trace functions
 | |
|  */
 | |
| #if KBASE_TRACE_ENABLE
 | |
| 
 | |
| static int kbasep_trace_init(struct kbase_device *kbdev)
 | |
| {
 | |
| 	struct kbase_trace *rbuf;
 | |
| 
 | |
| 	rbuf = kmalloc_array(KBASE_TRACE_SIZE, sizeof(*rbuf), GFP_KERNEL);
 | |
| 
 | |
| 	if (!rbuf)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	kbdev->trace_rbuf = rbuf;
 | |
| 	spin_lock_init(&kbdev->trace_lock);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void kbasep_trace_term(struct kbase_device *kbdev)
 | |
| {
 | |
| 	kfree(kbdev->trace_rbuf);
 | |
| }
 | |
| 
 | |
| static void kbasep_trace_format_msg(struct kbase_trace *trace_msg, char *buffer, int len)
 | |
| {
 | |
| 	s32 written = 0;
 | |
| 
 | |
| 	/* Initial part of message */
 | |
| 	written += MAX(snprintf(buffer + written, MAX(len - written, 0), "%d.%.6d,%d,%d,%s,%p,", (int)trace_msg->timestamp.tv_sec, (int)(trace_msg->timestamp.tv_nsec / 1000), trace_msg->thread_id, trace_msg->cpu, kbasep_trace_code_string[trace_msg->code], trace_msg->ctx), 0);
 | |
| 
 | |
| 	if (trace_msg->katom)
 | |
| 		written += MAX(snprintf(buffer + written, MAX(len - written, 0), "atom %d (ud: 0x%llx 0x%llx)", trace_msg->atom_number, trace_msg->atom_udata[0], trace_msg->atom_udata[1]), 0);
 | |
| 
 | |
| 	written += MAX(snprintf(buffer + written, MAX(len - written, 0), ",%.8llx,", trace_msg->gpu_addr), 0);
 | |
| 
 | |
| 	/* NOTE: Could add function callbacks to handle different message types */
 | |
| 	/* Jobslot present */
 | |
| 	if (trace_msg->flags & KBASE_TRACE_FLAG_JOBSLOT)
 | |
| 		written += MAX(snprintf(buffer + written, MAX(len - written, 0), "%d", trace_msg->jobslot), 0);
 | |
| 
 | |
| 	written += MAX(snprintf(buffer + written, MAX(len - written, 0), ","), 0);
 | |
| 
 | |
| 	/* Refcount present */
 | |
| 	if (trace_msg->flags & KBASE_TRACE_FLAG_REFCOUNT)
 | |
| 		written += MAX(snprintf(buffer + written, MAX(len - written, 0), "%d", trace_msg->refcount), 0);
 | |
| 
 | |
| 	written += MAX(snprintf(buffer + written, MAX(len - written, 0), ","), 0);
 | |
| 
 | |
| 	/* Rest of message */
 | |
| 	written += MAX(snprintf(buffer + written, MAX(len - written, 0), "0x%.8lx", trace_msg->info_val), 0);
 | |
| }
 | |
| 
 | |
| static void kbasep_trace_dump_msg(struct kbase_device *kbdev, struct kbase_trace *trace_msg)
 | |
| {
 | |
| 	char buffer[DEBUG_MESSAGE_SIZE];
 | |
| 
 | |
| 	kbasep_trace_format_msg(trace_msg, buffer, DEBUG_MESSAGE_SIZE);
 | |
| 	dev_dbg(kbdev->dev, "%s", buffer);
 | |
| }
 | |
| 
 | |
| void kbasep_trace_add(struct kbase_device *kbdev, enum kbase_trace_code code, void *ctx, struct kbase_jd_atom *katom, u64 gpu_addr, u8 flags, int refcount, int jobslot, unsigned long info_val)
 | |
| {
 | |
| 	unsigned long irqflags;
 | |
| 	struct kbase_trace *trace_msg;
 | |
| 
 | |
| 	spin_lock_irqsave(&kbdev->trace_lock, irqflags);
 | |
| 
 | |
| 	trace_msg = &kbdev->trace_rbuf[kbdev->trace_next_in];
 | |
| 
 | |
| 	/* Fill the message */
 | |
| 	trace_msg->thread_id = task_pid_nr(current);
 | |
| 	trace_msg->cpu = task_cpu(current);
 | |
| 
 | |
| 	ktime_get_real_ts64(&trace_msg->timestamp);
 | |
| 
 | |
| 	trace_msg->code = code;
 | |
| 	trace_msg->ctx = ctx;
 | |
| 
 | |
| 	if (NULL == katom) {
 | |
| 		trace_msg->katom = false;
 | |
| 	} else {
 | |
| 		trace_msg->katom = true;
 | |
| 		trace_msg->atom_number = kbase_jd_atom_id(katom->kctx, katom);
 | |
| 		trace_msg->atom_udata[0] = katom->udata.blob[0];
 | |
| 		trace_msg->atom_udata[1] = katom->udata.blob[1];
 | |
| 	}
 | |
| 
 | |
| 	trace_msg->gpu_addr = gpu_addr;
 | |
| 	trace_msg->jobslot = jobslot;
 | |
| 	trace_msg->refcount = MIN((unsigned int)refcount, 0xFF);
 | |
| 	trace_msg->info_val = info_val;
 | |
| 	trace_msg->flags = flags;
 | |
| 
 | |
| 	/* Update the ringbuffer indices */
 | |
| 	kbdev->trace_next_in = (kbdev->trace_next_in + 1) & KBASE_TRACE_MASK;
 | |
| 	if (kbdev->trace_next_in == kbdev->trace_first_out)
 | |
| 		kbdev->trace_first_out = (kbdev->trace_first_out + 1) & KBASE_TRACE_MASK;
 | |
| 
 | |
| 	/* Done */
 | |
| 
 | |
| 	spin_unlock_irqrestore(&kbdev->trace_lock, irqflags);
 | |
| }
 | |
| 
 | |
| void kbasep_trace_clear(struct kbase_device *kbdev)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	spin_lock_irqsave(&kbdev->trace_lock, flags);
 | |
| 	kbdev->trace_first_out = kbdev->trace_next_in;
 | |
| 	spin_unlock_irqrestore(&kbdev->trace_lock, flags);
 | |
| }
 | |
| 
 | |
| void kbasep_trace_dump(struct kbase_device *kbdev)
 | |
| {
 | |
| 	unsigned long flags;
 | |
| 	u32 start;
 | |
| 	u32 end;
 | |
| 
 | |
| 	dev_dbg(kbdev->dev, "Dumping trace:\nsecs,nthread,cpu,code,ctx,katom,gpu_addr,jobslot,refcount,info_val");
 | |
| 	spin_lock_irqsave(&kbdev->trace_lock, flags);
 | |
| 	start = kbdev->trace_first_out;
 | |
| 	end = kbdev->trace_next_in;
 | |
| 
 | |
| 	while (start != end) {
 | |
| 		struct kbase_trace *trace_msg = &kbdev->trace_rbuf[start];
 | |
| 
 | |
| 		kbasep_trace_dump_msg(kbdev, trace_msg);
 | |
| 
 | |
| 		start = (start + 1) & KBASE_TRACE_MASK;
 | |
| 	}
 | |
| 	dev_dbg(kbdev->dev, "TRACE_END");
 | |
| 
 | |
| 	spin_unlock_irqrestore(&kbdev->trace_lock, flags);
 | |
| 
 | |
| 	KBASE_TRACE_CLEAR(kbdev);
 | |
| }
 | |
| 
 | |
| static void kbasep_trace_hook_wrapper(void *param)
 | |
| {
 | |
| 	struct kbase_device *kbdev = (struct kbase_device *)param;
 | |
| 
 | |
| 	kbasep_trace_dump(kbdev);
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_DEBUG_FS
 | |
| struct trace_seq_state {
 | |
| 	struct kbase_trace trace_buf[KBASE_TRACE_SIZE];
 | |
| 	u32 start;
 | |
| 	u32 end;
 | |
| };
 | |
| 
 | |
| static void *kbasep_trace_seq_start(struct seq_file *s, loff_t *pos)
 | |
| {
 | |
| 	struct trace_seq_state *state = s->private;
 | |
| 	int i;
 | |
| 
 | |
| 	if (*pos > KBASE_TRACE_SIZE)
 | |
| 		return NULL;
 | |
| 	i = state->start + *pos;
 | |
| 	if ((state->end >= state->start && i >= state->end) ||
 | |
| 			i >= state->end + KBASE_TRACE_SIZE)
 | |
| 		return NULL;
 | |
| 
 | |
| 	i &= KBASE_TRACE_MASK;
 | |
| 
 | |
| 	return &state->trace_buf[i];
 | |
| }
 | |
| 
 | |
| static void kbasep_trace_seq_stop(struct seq_file *s, void *data)
 | |
| {
 | |
| }
 | |
| 
 | |
| static void *kbasep_trace_seq_next(struct seq_file *s, void *data, loff_t *pos)
 | |
| {
 | |
| 	struct trace_seq_state *state = s->private;
 | |
| 	int i;
 | |
| 
 | |
| 	(*pos)++;
 | |
| 
 | |
| 	i = (state->start + *pos) & KBASE_TRACE_MASK;
 | |
| 	if (i == state->end)
 | |
| 		return NULL;
 | |
| 
 | |
| 	return &state->trace_buf[i];
 | |
| }
 | |
| 
 | |
| static int kbasep_trace_seq_show(struct seq_file *s, void *data)
 | |
| {
 | |
| 	struct kbase_trace *trace_msg = data;
 | |
| 	char buffer[DEBUG_MESSAGE_SIZE];
 | |
| 
 | |
| 	kbasep_trace_format_msg(trace_msg, buffer, DEBUG_MESSAGE_SIZE);
 | |
| 	seq_printf(s, "%s\n", buffer);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct seq_operations kbasep_trace_seq_ops = {
 | |
| 	.start = kbasep_trace_seq_start,
 | |
| 	.next = kbasep_trace_seq_next,
 | |
| 	.stop = kbasep_trace_seq_stop,
 | |
| 	.show = kbasep_trace_seq_show,
 | |
| };
 | |
| 
 | |
| static int kbasep_trace_debugfs_open(struct inode *inode, struct file *file)
 | |
| {
 | |
| 	struct kbase_device *kbdev = inode->i_private;
 | |
| 	unsigned long flags;
 | |
| 
 | |
| 	struct trace_seq_state *state;
 | |
| 
 | |
| 	state = __seq_open_private(file, &kbasep_trace_seq_ops, sizeof(*state));
 | |
| 	if (!state)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	spin_lock_irqsave(&kbdev->trace_lock, flags);
 | |
| 	state->start = kbdev->trace_first_out;
 | |
| 	state->end = kbdev->trace_next_in;
 | |
| 	memcpy(state->trace_buf, kbdev->trace_rbuf, sizeof(state->trace_buf));
 | |
| 	spin_unlock_irqrestore(&kbdev->trace_lock, flags);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static const struct file_operations kbasep_trace_debugfs_fops = {
 | |
| 	.open = kbasep_trace_debugfs_open,
 | |
| 	.read = seq_read,
 | |
| 	.llseek = seq_lseek,
 | |
| 	.release = seq_release_private,
 | |
| };
 | |
| 
 | |
| void kbasep_trace_debugfs_init(struct kbase_device *kbdev)
 | |
| {
 | |
| 	debugfs_create_file("mali_trace", S_IRUGO,
 | |
| 			kbdev->mali_debugfs_directory, kbdev,
 | |
| 			&kbasep_trace_debugfs_fops);
 | |
| }
 | |
| 
 | |
| #else
 | |
| void kbasep_trace_debugfs_init(struct kbase_device *kbdev)
 | |
| {
 | |
| }
 | |
| #endif				/* CONFIG_DEBUG_FS */
 | |
| 
 | |
| #else				/* KBASE_TRACE_ENABLE  */
 | |
| static int kbasep_trace_init(struct kbase_device *kbdev)
 | |
| {
 | |
| 	CSTD_UNUSED(kbdev);
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| static void kbasep_trace_term(struct kbase_device *kbdev)
 | |
| {
 | |
| 	CSTD_UNUSED(kbdev);
 | |
| }
 | |
| 
 | |
| static void kbasep_trace_hook_wrapper(void *param)
 | |
| {
 | |
| 	CSTD_UNUSED(param);
 | |
| }
 | |
| 
 | |
| void kbasep_trace_dump(struct kbase_device *kbdev)
 | |
| {
 | |
| 	CSTD_UNUSED(kbdev);
 | |
| }
 | |
| #endif				/* KBASE_TRACE_ENABLE  */
 | |
| 
 | |
| void kbase_set_profiling_control(struct kbase_device *kbdev, u32 control, u32 value)
 | |
| {
 | |
| 	switch (control) {
 | |
| 	case FBDUMP_CONTROL_ENABLE:
 | |
| 		/* fall through */
 | |
| 	case FBDUMP_CONTROL_RATE:
 | |
| 		/* fall through */
 | |
| 	case SW_COUNTER_ENABLE:
 | |
| 		/* fall through */
 | |
| 	case FBDUMP_CONTROL_RESIZE_FACTOR:
 | |
| 		kbdev->kbase_profiling_controls[control] = value;
 | |
| 		break;
 | |
| 	default:
 | |
| 		dev_err(kbdev->dev, "Profiling control %d not found\n", control);
 | |
| 		break;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /*
 | |
|  * Called by gator to control the production of
 | |
|  * profiling information at runtime
 | |
|  * */
 | |
| 
 | |
| void _mali_profiling_control(u32 action, u32 value)
 | |
| {
 | |
| 	struct kbase_device *kbdev = NULL;
 | |
| 
 | |
| 	/* find the first i.e. call with -1 */
 | |
| 	kbdev = kbase_find_device(-1);
 | |
| 
 | |
| 	if (NULL != kbdev)
 | |
| 		kbase_set_profiling_control(kbdev, action, value);
 | |
| }
 | |
| KBASE_EXPORT_SYMBOL(_mali_profiling_control);
 | |
| 
 |