diff options
Diffstat (limited to 'drivers/firmware')
| -rw-r--r-- | drivers/firmware/Kconfig | 4 | ||||
| -rw-r--r-- | drivers/firmware/Makefile | 1 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/Makefile | 2 | ||||
| -rw-r--r-- | drivers/firmware/efi/libstub/arm64-stub.c | 15 | ||||
| -rw-r--r-- | drivers/firmware/psci.c | 130 | ||||
| -rw-r--r-- | drivers/firmware/qcom/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/firmware/qcom/Makefile | 1 | ||||
| -rw-r--r-- | drivers/firmware/qcom/tz_log.c | 1211 |
8 files changed, 1366 insertions, 5 deletions
diff --git a/drivers/firmware/Kconfig b/drivers/firmware/Kconfig index cf478fe6b335..68489ef7422f 100644 --- a/drivers/firmware/Kconfig +++ b/drivers/firmware/Kconfig @@ -173,8 +173,12 @@ config QCOM_SCM_64 def_bool y depends on QCOM_SCM && ARM64 +config HAVE_ARM_SMCCC + bool + source "drivers/firmware/broadcom/Kconfig" source "drivers/firmware/google/Kconfig" source "drivers/firmware/efi/Kconfig" +source "drivers/firmware/qcom/Kconfig" endmenu diff --git a/drivers/firmware/Makefile b/drivers/firmware/Makefile index 48dd4175297e..5a9e2816d722 100644 --- a/drivers/firmware/Makefile +++ b/drivers/firmware/Makefile @@ -23,3 +23,4 @@ obj-y += broadcom/ obj-$(CONFIG_GOOGLE_FIRMWARE) += google/ obj-$(CONFIG_EFI) += efi/ obj-$(CONFIG_UEFI_CPER) += efi/ +obj-$(CONFIG_MSM_TZ_LOG) += qcom/
\ No newline at end of file diff --git a/drivers/firmware/efi/libstub/Makefile b/drivers/firmware/efi/libstub/Makefile index 68bdd9f23e13..d775e2bfc017 100644 --- a/drivers/firmware/efi/libstub/Makefile +++ b/drivers/firmware/efi/libstub/Makefile @@ -22,6 +22,7 @@ KBUILD_CFLAGS := $(cflags-y) -DDISABLE_BRANCH_PROFILING \ GCOV_PROFILE := n KASAN_SANITIZE := n +UBSAN_SANITIZE := n lib-y := efi-stub-helper.o @@ -34,6 +35,7 @@ $(obj)/lib-%.o: $(srctree)/lib/%.c FORCE lib-$(CONFIG_EFI_ARMSTUB) += arm-stub.o fdt.o string.o \ $(patsubst %.c,lib-%.o,$(arm-deps)) +lib-$(CONFIG_ARM) += arm32-stub.o lib-$(CONFIG_ARM64) += arm64-stub.o random.o CFLAGS_arm64-stub.o := -DTEXT_OFFSET=$(TEXT_OFFSET) diff --git a/drivers/firmware/efi/libstub/arm64-stub.c b/drivers/firmware/efi/libstub/arm64-stub.c index e0e6b74fef8f..377d935a3380 100644 --- a/drivers/firmware/efi/libstub/arm64-stub.c +++ b/drivers/firmware/efi/libstub/arm64-stub.c @@ -61,15 +61,24 @@ efi_status_t __init handle_kernel_image(efi_system_table_t *sys_table_arg, if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && phys_seed != 0) { /* + * If CONFIG_DEBUG_ALIGN_RODATA is not set, produce a + * displacement in the interval [0, MIN_KIMG_ALIGN) that + * is a multiple of the minimal segment alignment (SZ_64K) + */ + u32 mask = (MIN_KIMG_ALIGN - 1) & ~(SZ_64K - 1); + u32 offset = !IS_ENABLED(CONFIG_DEBUG_ALIGN_RODATA) ? + (phys_seed >> 32) & mask : TEXT_OFFSET; + + /* * If KASLR is enabled, and we have some randomness available, * locate the kernel at a randomized offset in physical memory. */ - *reserve_size = kernel_memsize + TEXT_OFFSET; + *reserve_size = kernel_memsize + offset; status = efi_random_alloc(sys_table_arg, *reserve_size, MIN_KIMG_ALIGN, reserve_addr, - phys_seed); + (u32)phys_seed); - *image_addr = *reserve_addr + TEXT_OFFSET; + *image_addr = *reserve_addr + offset; } else { /* * Else, try a straight allocation at the preferred offset. diff --git a/drivers/firmware/psci.c b/drivers/firmware/psci.c index d24f35d74b27..d652f3b53635 100644 --- a/drivers/firmware/psci.c +++ b/drivers/firmware/psci.c @@ -13,6 +13,8 @@ #define pr_fmt(fmt) "psci: " fmt +#include <linux/arm-smccc.h> +#include <linux/cpuidle.h> #include <linux/errno.h> #include <linux/linkage.h> #include <linux/of.h> @@ -20,10 +22,12 @@ #include <linux/printk.h> #include <linux/psci.h> #include <linux/reboot.h> +#include <linux/slab.h> #include <linux/suspend.h> #include <uapi/linux/psci.h> +#include <asm/cpuidle.h> #include <asm/cputype.h> #include <asm/system_misc.h> #include <asm/smp_plat.h> @@ -58,8 +62,6 @@ struct psci_operations psci_ops; typedef unsigned long (psci_fn)(unsigned long, unsigned long, unsigned long, unsigned long); -asmlinkage psci_fn __invoke_psci_fn_hvc; -asmlinkage psci_fn __invoke_psci_fn_smc; static psci_fn *invoke_psci_fn; enum psci_function { @@ -225,6 +227,130 @@ static int __init psci_features(u32 psci_func_id) psci_func_id, 0, 0); } +#ifdef CONFIG_CPU_IDLE +static DEFINE_PER_CPU_READ_MOSTLY(u32 *, psci_power_state); + +#ifdef CONFIG_ARM_PSCI +static int psci_cpu_init(struct device_node *cpu_node, int cpu) +{ + return 0; +} +#endif + +static int psci_dt_cpu_init_idle(struct device_node *cpu_node, int cpu) +{ + int i, ret, count = 0; + u32 *psci_states; + struct device_node *state_node; + + /* + * If the PSCI cpu_suspend function hook has not been initialized + * idle states must not be enabled, so bail out + */ + if (!psci_ops.cpu_suspend) + return -EOPNOTSUPP; + + /* Count idle states */ + while ((state_node = of_parse_phandle(cpu_node, "cpu-idle-states", + count))) { + count++; + of_node_put(state_node); + } + + if (!count) + return -ENODEV; + + psci_states = kcalloc(count, sizeof(*psci_states), GFP_KERNEL); + if (!psci_states) + return -ENOMEM; + + for (i = 0; i < count; i++) { + u32 state; + + state_node = of_parse_phandle(cpu_node, "cpu-idle-states", i); + + ret = of_property_read_u32(state_node, + "arm,psci-suspend-param", + &state); + if (ret) { + pr_warn(" * %s missing arm,psci-suspend-param property\n", + state_node->full_name); + of_node_put(state_node); + goto free_mem; + } + + of_node_put(state_node); + pr_debug("psci-power-state %#x index %d\n", state, i); + if (!psci_power_state_is_valid(state)) { + pr_warn("Invalid PSCI power state %#x\n", state); + ret = -EINVAL; + goto free_mem; + } + psci_states[i] = state; + } + /* Idle states parsed correctly, initialize per-cpu pointer */ + per_cpu(psci_power_state, cpu) = psci_states; + return 0; + +free_mem: + kfree(psci_states); + return ret; +} + +int psci_cpu_init_idle(unsigned int cpu) +{ + struct device_node *cpu_node; + int ret; + + cpu_node = of_get_cpu_node(cpu, NULL); + if (!cpu_node) + return -ENODEV; + + ret = psci_dt_cpu_init_idle(cpu_node, cpu); + + of_node_put(cpu_node); + + return ret; +} + +static int psci_suspend_finisher(unsigned long state_id) +{ + return psci_ops.cpu_suspend(state_id, virt_to_phys(cpu_resume)); +} + +int psci_cpu_suspend_enter(unsigned long state_id) +{ + int ret; + /* + * idle state_id 0 corresponds to wfi, should never be called + * from the cpu_suspend operations + */ + if (WARN_ON_ONCE(!state_id)) + return -EINVAL; + + if (!psci_power_state_loses_context(state_id)) + ret = psci_ops.cpu_suspend(state_id, 0); + else + ret = cpu_suspend(state_id, psci_suspend_finisher); + + return ret; +} + +/* ARM specific CPU idle operations */ +#ifdef CONFIG_ARM +static struct cpuidle_ops psci_cpuidle_ops __initdata = { + .suspend = psci_cpu_suspend_enter, +#ifdef CONFIG_ARM_PSCI + .init = psci_cpu_init, +#else + .init = psci_dt_cpu_init_idle, +#endif +}; + +CPUIDLE_METHOD_OF_DECLARE(psci, "psci", &psci_cpuidle_ops); +#endif +#endif + static int psci_system_suspend(unsigned long unused) { return invoke_psci_fn(PSCI_FN_NATIVE(1_0, SYSTEM_SUSPEND), diff --git a/drivers/firmware/qcom/Kconfig b/drivers/firmware/qcom/Kconfig new file mode 100644 index 000000000000..61c797430bd6 --- /dev/null +++ b/drivers/firmware/qcom/Kconfig @@ -0,0 +1,7 @@ +config MSM_TZ_LOG + tristate "MSM Trust Zone (TZ) Log Driver" + depends on DEBUG_FS + help + This option enables a driver with a debugfs interface for messages + produced by the Secure code (Trust zone). These messages provide + diagnostic information about TZ operation. diff --git a/drivers/firmware/qcom/Makefile b/drivers/firmware/qcom/Makefile new file mode 100644 index 000000000000..635f60c1fc16 --- /dev/null +++ b/drivers/firmware/qcom/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_MSM_TZ_LOG) += tz_log.o diff --git a/drivers/firmware/qcom/tz_log.c b/drivers/firmware/qcom/tz_log.c new file mode 100644 index 000000000000..c893681f3bf3 --- /dev/null +++ b/drivers/firmware/qcom/tz_log.c @@ -0,0 +1,1211 @@ +/* Copyright (c) 2011-2015,2017 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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. + * + */ +#include <linux/debugfs.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/msm_ion.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/string.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <linux/of.h> + +#include <soc/qcom/scm.h> +#include <soc/qcom/qseecomi.h> + +/* QSEE_LOG_BUF_SIZE = 32K */ +#define QSEE_LOG_BUF_SIZE 0x8000 + + +/* TZ Diagnostic Area legacy version number */ +#define TZBSP_DIAG_MAJOR_VERSION_LEGACY 2 +/* + * Preprocessor Definitions and Constants + */ +#define TZBSP_MAX_CPU_COUNT 0x08 +/* + * Number of VMID Tables + */ +#define TZBSP_DIAG_NUM_OF_VMID 16 +/* + * VMID Description length + */ +#define TZBSP_DIAG_VMID_DESC_LEN 7 +/* + * Number of Interrupts + */ +#define TZBSP_DIAG_INT_NUM 32 +/* + * Length of descriptive name associated with Interrupt + */ +#define TZBSP_MAX_INT_DESC 16 +/* + * TZ 3.X version info + */ +#define QSEE_VERSION_TZ_3_X 0x800000 +/* + * TZ 4.X version info + */ +#define QSEE_VERSION_TZ_4_X 0x1000000 + +#define TZBSP_AES_256_ENCRYPTED_KEY_SIZE 256 +#define TZBSP_NONCE_LEN 12 +#define TZBSP_TAG_LEN 16 + +/* + * VMID Table + */ +struct tzdbg_vmid_t { + uint8_t vmid; /* Virtual Machine Identifier */ + uint8_t desc[TZBSP_DIAG_VMID_DESC_LEN]; /* ASCII Text */ +}; +/* + * Boot Info Table + */ +struct tzdbg_boot_info_t { + uint32_t wb_entry_cnt; /* Warmboot entry CPU Counter */ + uint32_t wb_exit_cnt; /* Warmboot exit CPU Counter */ + uint32_t pc_entry_cnt; /* Power Collapse entry CPU Counter */ + uint32_t pc_exit_cnt; /* Power Collapse exit CPU counter */ + uint32_t warm_jmp_addr; /* Last Warmboot Jump Address */ + uint32_t spare; /* Reserved for future use. */ +}; +/* + * Boot Info Table for 64-bit + */ +struct tzdbg_boot_info64_t { + uint32_t wb_entry_cnt; /* Warmboot entry CPU Counter */ + uint32_t wb_exit_cnt; /* Warmboot exit CPU Counter */ + uint32_t pc_entry_cnt; /* Power Collapse entry CPU Counter */ + uint32_t pc_exit_cnt; /* Power Collapse exit CPU counter */ + uint32_t psci_entry_cnt;/* PSCI syscall entry CPU Counter */ + uint32_t psci_exit_cnt; /* PSCI syscall exit CPU Counter */ + uint64_t warm_jmp_addr; /* Last Warmboot Jump Address */ + uint32_t warm_jmp_instr; /* Last Warmboot Jump Address Instruction */ +}; +/* + * Reset Info Table + */ +struct tzdbg_reset_info_t { + uint32_t reset_type; /* Reset Reason */ + uint32_t reset_cnt; /* Number of resets occured/CPU */ +}; +/* + * Interrupt Info Table + */ +struct tzdbg_int_t { + /* + * Type of Interrupt/exception + */ + uint16_t int_info; + /* + * Availability of the slot + */ + uint8_t avail; + /* + * Reserved for future use + */ + uint8_t spare; + /* + * Interrupt # for IRQ and FIQ + */ + uint32_t int_num; + /* + * ASCII text describing type of interrupt e.g: + * Secure Timer, EBI XPU. This string is always null terminated, + * supporting at most TZBSP_MAX_INT_DESC characters. + * Any additional characters are truncated. + */ + uint8_t int_desc[TZBSP_MAX_INT_DESC]; + uint64_t int_count[TZBSP_MAX_CPU_COUNT]; /* # of times seen per CPU */ +}; + +/* + * Interrupt Info Table used in tz version >=4.X + */ +struct tzdbg_int_t_tz40 { + uint16_t int_info; + uint8_t avail; + uint8_t spare; + uint32_t int_num; + uint8_t int_desc[TZBSP_MAX_INT_DESC]; + uint32_t int_count[TZBSP_MAX_CPU_COUNT]; /* uint32_t in TZ ver >= 4.x*/ +}; + +/* warm boot reason for cores */ +struct tzbsp_diag_wakeup_info_t { + /* Wake source info : APCS_GICC_HPPIR */ + uint32_t HPPIR; + /* Wake source info : APCS_GICC_AHPPIR */ + uint32_t AHPPIR; +}; + +/* + * Log ring buffer position + */ +struct tzdbg_log_pos_t { + uint16_t wrap; + uint16_t offset; +}; + + /* + * Log ring buffer + */ +struct tzdbg_log_t { + struct tzdbg_log_pos_t log_pos; + /* open ended array to the end of the 4K IMEM buffer */ + uint8_t log_buf[]; +}; + +/* + * Diagnostic Table + * Note: This is the reference data structure for tz diagnostic table + * supporting TZBSP_MAX_CPU_COUNT, the real diagnostic data is directly + * copied into buffer from i/o memory. + */ +struct tzdbg_t { + uint32_t magic_num; + uint32_t version; + /* + * Number of CPU's + */ + uint32_t cpu_count; + /* + * Offset of VMID Table + */ + uint32_t vmid_info_off; + /* + * Offset of Boot Table + */ + uint32_t boot_info_off; + /* + * Offset of Reset info Table + */ + uint32_t reset_info_off; + /* + * Offset of Interrupt info Table + */ + uint32_t int_info_off; + /* + * Ring Buffer Offset + */ + uint32_t ring_off; + /* + * Ring Buffer Length + */ + uint32_t ring_len; + + /* Offset for Wakeup info */ + uint32_t wakeup_info_off; + + /* + * VMID to EE Mapping + */ + struct tzdbg_vmid_t vmid_info[TZBSP_DIAG_NUM_OF_VMID]; + /* + * Boot Info + */ + struct tzdbg_boot_info_t boot_info[TZBSP_MAX_CPU_COUNT]; + /* + * Reset Info + */ + struct tzdbg_reset_info_t reset_info[TZBSP_MAX_CPU_COUNT]; + uint32_t num_interrupts; + struct tzdbg_int_t int_info[TZBSP_DIAG_INT_NUM]; + + /* Wake up info */ + struct tzbsp_diag_wakeup_info_t wakeup_info[TZBSP_MAX_CPU_COUNT]; + + uint8_t key[TZBSP_AES_256_ENCRYPTED_KEY_SIZE]; + + uint8_t nonce[TZBSP_NONCE_LEN]; + + uint8_t tag[TZBSP_TAG_LEN]; + + /* + * We need at least 2K for the ring buffer + */ + struct tzdbg_log_t ring_buffer; /* TZ Ring Buffer */ +}; + +struct hypdbg_log_pos_t { + uint16_t wrap; + uint16_t offset; +}; + +struct hypdbg_boot_info_t { + uint32_t warm_entry_cnt; + uint32_t warm_exit_cnt; +}; + +struct hypdbg_t { + /* Magic Number */ + uint32_t magic_num; + + /* Number of CPU's */ + uint32_t cpu_count; + + /* Ring Buffer Offset */ + uint32_t ring_off; + + /* Ring buffer position mgmt */ + struct hypdbg_log_pos_t log_pos; + uint32_t log_len; + + /* S2 fault numbers */ + uint32_t s2_fault_counter; + + /* Boot Info */ + struct hypdbg_boot_info_t boot_info[TZBSP_MAX_CPU_COUNT]; + + /* Ring buffer pointer */ + uint8_t log_buf_p[]; +}; + +/* + * Enumeration order for VMID's + */ +enum tzdbg_stats_type { + TZDBG_BOOT = 0, + TZDBG_RESET, + TZDBG_INTERRUPT, + TZDBG_VMID, + TZDBG_GENERAL, + TZDBG_LOG, + TZDBG_QSEE_LOG, + TZDBG_HYP_GENERAL, + TZDBG_HYP_LOG, + TZDBG_STATS_MAX +}; + +struct tzdbg_stat { + char *name; + char *data; +}; + +struct tzdbg { + void __iomem *virt_iobase; + void __iomem *hyp_virt_iobase; + struct tzdbg_t *diag_buf; + struct hypdbg_t *hyp_diag_buf; + char *disp_buf; + int debug_tz[TZDBG_STATS_MAX]; + struct tzdbg_stat stat[TZDBG_STATS_MAX]; + uint32_t hyp_debug_rw_buf_size; + bool is_hyplog_enabled; + uint32_t tz_version; +}; + +static struct tzdbg tzdbg = { + .stat[TZDBG_BOOT].name = "boot", + .stat[TZDBG_RESET].name = "reset", + .stat[TZDBG_INTERRUPT].name = "interrupt", + .stat[TZDBG_VMID].name = "vmid", + .stat[TZDBG_GENERAL].name = "general", + .stat[TZDBG_LOG].name = "log", + .stat[TZDBG_QSEE_LOG].name = "qsee_log", + .stat[TZDBG_HYP_GENERAL].name = "hyp_general", + .stat[TZDBG_HYP_LOG].name = "hyp_log", +}; + +static struct tzdbg_log_t *g_qsee_log; +static uint32_t debug_rw_buf_size; + +/* + * Debugfs data structure and functions + */ + +static int _disp_tz_general_stats(void) +{ + int len = 0; + + len += snprintf(tzdbg.disp_buf + len, debug_rw_buf_size - 1, + " Version : 0x%x\n" + " Magic Number : 0x%x\n" + " Number of CPU : %d\n", + tzdbg.diag_buf->version, + tzdbg.diag_buf->magic_num, + tzdbg.diag_buf->cpu_count); + tzdbg.stat[TZDBG_GENERAL].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_vmid_stats(void) +{ + int i, num_vmid; + int len = 0; + struct tzdbg_vmid_t *ptr; + + ptr = (struct tzdbg_vmid_t *)((unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->vmid_info_off); + num_vmid = ((tzdbg.diag_buf->boot_info_off - + tzdbg.diag_buf->vmid_info_off)/ + (sizeof(struct tzdbg_vmid_t))); + + for (i = 0; i < num_vmid; i++) { + if (ptr->vmid < 0xFF) { + len += snprintf(tzdbg.disp_buf + len, + (debug_rw_buf_size - 1) - len, + " 0x%x %s\n", + (uint32_t)ptr->vmid, (uint8_t *)ptr->desc); + } + if (len > (debug_rw_buf_size - 1)) { + pr_warn("%s: Cannot fit all info into the buffer\n", + __func__); + break; + } + ptr++; + } + + tzdbg.stat[TZDBG_VMID].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_boot_stats(void) +{ + int i; + int len = 0; + struct tzdbg_boot_info_t *ptr = NULL; + struct tzdbg_boot_info64_t *ptr_64 = NULL; + + pr_info("qsee_version = 0x%x\n", tzdbg.tz_version); + if (tzdbg.tz_version >= QSEE_VERSION_TZ_3_X) { + ptr_64 = (struct tzdbg_boot_info64_t *)((unsigned char *) + tzdbg.diag_buf + tzdbg.diag_buf->boot_info_off); + } else { + ptr = (struct tzdbg_boot_info_t *)((unsigned char *) + tzdbg.diag_buf + tzdbg.diag_buf->boot_info_off); + } + + for (i = 0; i < tzdbg.diag_buf->cpu_count; i++) { + if (tzdbg.tz_version >= QSEE_VERSION_TZ_3_X) { + len += snprintf(tzdbg.disp_buf + len, + (debug_rw_buf_size - 1) - len, + " CPU #: %d\n" + " Warmboot jump address : 0x%llx\n" + " Warmboot entry CPU counter : 0x%x\n" + " Warmboot exit CPU counter : 0x%x\n" + " Power Collapse entry CPU counter : 0x%x\n" + " Power Collapse exit CPU counter : 0x%x\n" + " Psci entry CPU counter : 0x%x\n" + " Psci exit CPU counter : 0x%x\n" + " Warmboot Jump Address Instruction : 0x%x\n", + i, (uint64_t)ptr_64->warm_jmp_addr, + ptr_64->wb_entry_cnt, + ptr_64->wb_exit_cnt, + ptr_64->pc_entry_cnt, + ptr_64->pc_exit_cnt, + ptr_64->psci_entry_cnt, + ptr_64->psci_exit_cnt, + ptr_64->warm_jmp_instr); + + if (len > (debug_rw_buf_size - 1)) { + pr_warn("%s: Cannot fit all info into the buffer\n", + __func__); + break; + } + ptr_64++; + } else { + len += snprintf(tzdbg.disp_buf + len, + (debug_rw_buf_size - 1) - len, + " CPU #: %d\n" + " Warmboot jump address : 0x%x\n" + " Warmboot entry CPU counter: 0x%x\n" + " Warmboot exit CPU counter : 0x%x\n" + " Power Collapse entry CPU counter: 0x%x\n" + " Power Collapse exit CPU counter : 0x%x\n", + i, ptr->warm_jmp_addr, + ptr->wb_entry_cnt, + ptr->wb_exit_cnt, + ptr->pc_entry_cnt, + ptr->pc_exit_cnt); + + if (len > (debug_rw_buf_size - 1)) { + pr_warn("%s: Cannot fit all info into the buffer\n", + __func__); + break; + } + ptr++; + } + } + tzdbg.stat[TZDBG_BOOT].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_reset_stats(void) +{ + int i; + int len = 0; + struct tzdbg_reset_info_t *ptr; + + ptr = (struct tzdbg_reset_info_t *)((unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->reset_info_off); + + for (i = 0; i < tzdbg.diag_buf->cpu_count; i++) { + len += snprintf(tzdbg.disp_buf + len, + (debug_rw_buf_size - 1) - len, + " CPU #: %d\n" + " Reset Type (reason) : 0x%x\n" + " Reset counter : 0x%x\n", + i, ptr->reset_type, ptr->reset_cnt); + + if (len > (debug_rw_buf_size - 1)) { + pr_warn("%s: Cannot fit all info into the buffer\n", + __func__); + break; + } + + ptr++; + } + tzdbg.stat[TZDBG_RESET].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_interrupt_stats(void) +{ + int i, j; + int len = 0; + int *num_int; + void *ptr; + struct tzdbg_int_t *tzdbg_ptr; + struct tzdbg_int_t_tz40 *tzdbg_ptr_tz40; + + num_int = (uint32_t *)((unsigned char *)tzdbg.diag_buf + + (tzdbg.diag_buf->int_info_off - sizeof(uint32_t))); + ptr = ((unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->int_info_off); + + pr_info("qsee_version = 0x%x\n", tzdbg.tz_version); + + if (tzdbg.tz_version < QSEE_VERSION_TZ_4_X) { + tzdbg_ptr = ptr; + for (i = 0; i < (*num_int); i++) { + len += snprintf(tzdbg.disp_buf + len, + (debug_rw_buf_size - 1) - len, + " Interrupt Number : 0x%x\n" + " Type of Interrupt : 0x%x\n" + " Description of interrupt : %s\n", + tzdbg_ptr->int_num, + (uint32_t)tzdbg_ptr->int_info, + (uint8_t *)tzdbg_ptr->int_desc); + for (j = 0; j < tzdbg.diag_buf->cpu_count; j++) { + len += snprintf(tzdbg.disp_buf + len, + (debug_rw_buf_size - 1) - len, + " int_count on CPU # %d : %u\n", + (uint32_t)j, + (uint32_t)tzdbg_ptr->int_count[j]); + } + len += snprintf(tzdbg.disp_buf + len, + debug_rw_buf_size - 1, "\n"); + + if (len > (debug_rw_buf_size - 1)) { + pr_warn("%s: Cannot fit all info into buf\n", + __func__); + break; + } + tzdbg_ptr++; + } + } else { + tzdbg_ptr_tz40 = ptr; + for (i = 0; i < (*num_int); i++) { + len += snprintf(tzdbg.disp_buf + len, + (debug_rw_buf_size - 1) - len, + " Interrupt Number : 0x%x\n" + " Type of Interrupt : 0x%x\n" + " Description of interrupt : %s\n", + tzdbg_ptr_tz40->int_num, + (uint32_t)tzdbg_ptr_tz40->int_info, + (uint8_t *)tzdbg_ptr_tz40->int_desc); + for (j = 0; j < tzdbg.diag_buf->cpu_count; j++) { + len += snprintf(tzdbg.disp_buf + len, + (debug_rw_buf_size - 1) - len, + " int_count on CPU # %d : %u\n", + (uint32_t)j, + (uint32_t)tzdbg_ptr_tz40->int_count[j]); + } + len += snprintf(tzdbg.disp_buf + len, + debug_rw_buf_size - 1, "\n"); + + if (len > (debug_rw_buf_size - 1)) { + pr_warn("%s: Cannot fit all info into buf\n", + __func__); + break; + } + tzdbg_ptr_tz40++; + } + } + + tzdbg.stat[TZDBG_INTERRUPT].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_log_stats_legacy(void) +{ + int len = 0; + unsigned char *ptr; + + ptr = (unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->ring_off; + len += snprintf(tzdbg.disp_buf, (debug_rw_buf_size - 1) - len, + "%s\n", ptr); + + tzdbg.stat[TZDBG_LOG].data = tzdbg.disp_buf; + return len; +} + +static int _disp_log_stats(struct tzdbg_log_t *log, + struct tzdbg_log_pos_t *log_start, uint32_t log_len, + size_t count, uint32_t buf_idx) +{ + uint32_t wrap_start; + uint32_t wrap_end; + uint32_t wrap_cnt; + int max_len; + int len = 0; + int i = 0; + + wrap_start = log_start->wrap; + wrap_end = log->log_pos.wrap; + + /* Calculate difference in # of buffer wrap-arounds */ + if (wrap_end >= wrap_start) { + wrap_cnt = wrap_end - wrap_start; + } else { + /* wrap counter has wrapped around, invalidate start position */ + wrap_cnt = 2; + } + + if (wrap_cnt > 1) { + /* end position has wrapped around more than once, */ + /* current start no longer valid */ + log_start->wrap = log->log_pos.wrap - 1; + log_start->offset = (log->log_pos.offset + 1) % log_len; + } else if ((wrap_cnt == 1) && + (log->log_pos.offset > log_start->offset)) { + /* end position has overwritten start */ + log_start->offset = (log->log_pos.offset + 1) % log_len; + } + + while (log_start->offset == log->log_pos.offset) { + /* + * No data in ring buffer, + * so we'll hang around until something happens + */ + unsigned long t = msleep_interruptible(50); + if (t != 0) { + /* Some event woke us up, so let's quit */ + return 0; + } + + if (buf_idx == TZDBG_LOG) + memcpy_fromio((void *)tzdbg.diag_buf, tzdbg.virt_iobase, + debug_rw_buf_size); + + } + + max_len = (count > debug_rw_buf_size) ? debug_rw_buf_size : count; + + /* + * Read from ring buff while there is data and space in return buff + */ + while ((log_start->offset != log->log_pos.offset) && (len < max_len)) { + tzdbg.disp_buf[i++] = log->log_buf[log_start->offset]; + log_start->offset = (log_start->offset + 1) % log_len; + if (0 == log_start->offset) + ++log_start->wrap; + ++len; + } + + /* + * return buffer to caller + */ + tzdbg.stat[buf_idx].data = tzdbg.disp_buf; + return len; +} + +static int __disp_hyp_log_stats(uint8_t *log, + struct hypdbg_log_pos_t *log_start, uint32_t log_len, + size_t count, uint32_t buf_idx) +{ + struct hypdbg_t *hyp = tzdbg.hyp_diag_buf; + unsigned long t = 0; + uint32_t wrap_start; + uint32_t wrap_end; + uint32_t wrap_cnt; + int max_len; + int len = 0; + int i = 0; + + wrap_start = log_start->wrap; + wrap_end = hyp->log_pos.wrap; + + /* Calculate difference in # of buffer wrap-arounds */ + if (wrap_end >= wrap_start) { + wrap_cnt = wrap_end - wrap_start; + } else { + /* wrap counter has wrapped around, invalidate start position */ + wrap_cnt = 2; + } + + if (wrap_cnt > 1) { + /* end position has wrapped around more than once, */ + /* current start no longer valid */ + log_start->wrap = hyp->log_pos.wrap - 1; + log_start->offset = (hyp->log_pos.offset + 1) % log_len; + } else if ((wrap_cnt == 1) && + (hyp->log_pos.offset > log_start->offset)) { + /* end position has overwritten start */ + log_start->offset = (hyp->log_pos.offset + 1) % log_len; + } + + while (log_start->offset == hyp->log_pos.offset) { + /* + * No data in ring buffer, + * so we'll hang around until something happens + */ + t = msleep_interruptible(50); + if (t != 0) { + /* Some event woke us up, so let's quit */ + return 0; + } + + /* TZDBG_HYP_LOG */ + memcpy_fromio((void *)tzdbg.hyp_diag_buf, tzdbg.hyp_virt_iobase, + tzdbg.hyp_debug_rw_buf_size); + } + + max_len = (count > tzdbg.hyp_debug_rw_buf_size) ? + tzdbg.hyp_debug_rw_buf_size : count; + + /* + * Read from ring buff while there is data and space in return buff + */ + while ((log_start->offset != hyp->log_pos.offset) && (len < max_len)) { + tzdbg.disp_buf[i++] = log[log_start->offset]; + log_start->offset = (log_start->offset + 1) % log_len; + if (0 == log_start->offset) + ++log_start->wrap; + ++len; + } + + /* + * return buffer to caller + */ + tzdbg.stat[buf_idx].data = tzdbg.disp_buf; + return len; +} + +static int _disp_tz_log_stats(size_t count) +{ + static struct tzdbg_log_pos_t log_start = {0}; + struct tzdbg_log_t *log_ptr; + log_ptr = (struct tzdbg_log_t *)((unsigned char *)tzdbg.diag_buf + + tzdbg.diag_buf->ring_off - + offsetof(struct tzdbg_log_t, log_buf)); + + return _disp_log_stats(log_ptr, &log_start, + tzdbg.diag_buf->ring_len, count, TZDBG_LOG); +} + +static int _disp_hyp_log_stats(size_t count) +{ + static struct hypdbg_log_pos_t log_start = {0}; + uint8_t *log_ptr; + + log_ptr = (uint8_t *)((unsigned char *)tzdbg.hyp_diag_buf + + tzdbg.hyp_diag_buf->ring_off); + + return __disp_hyp_log_stats(log_ptr, &log_start, + tzdbg.hyp_debug_rw_buf_size, count, TZDBG_HYP_LOG); +} + +static int _disp_qsee_log_stats(size_t count) +{ + static struct tzdbg_log_pos_t log_start = {0}; + + return _disp_log_stats(g_qsee_log, &log_start, + QSEE_LOG_BUF_SIZE - sizeof(struct tzdbg_log_pos_t), + count, TZDBG_QSEE_LOG); +} + +static int _disp_hyp_general_stats(size_t count) +{ + int len = 0; + int i; + struct hypdbg_boot_info_t *ptr = NULL; + + len += snprintf((unsigned char *)tzdbg.disp_buf + len, + tzdbg.hyp_debug_rw_buf_size - 1, + " Magic Number : 0x%x\n" + " CPU Count : 0x%x\n" + " S2 Fault Counter: 0x%x\n", + tzdbg.hyp_diag_buf->magic_num, + tzdbg.hyp_diag_buf->cpu_count, + tzdbg.hyp_diag_buf->s2_fault_counter); + + ptr = tzdbg.hyp_diag_buf->boot_info; + for (i = 0; i < tzdbg.hyp_diag_buf->cpu_count; i++) { + len += snprintf((unsigned char *)tzdbg.disp_buf + len, + (tzdbg.hyp_debug_rw_buf_size - 1) - len, + " CPU #: %d\n" + " Warmboot entry CPU counter: 0x%x\n" + " Warmboot exit CPU counter : 0x%x\n", + i, ptr->warm_entry_cnt, ptr->warm_exit_cnt); + + if (len > (tzdbg.hyp_debug_rw_buf_size - 1)) { + pr_warn("%s: Cannot fit all info into the buffer\n", + __func__); + break; + } + ptr++; + } + + tzdbg.stat[TZDBG_HYP_GENERAL].data = (char *)tzdbg.disp_buf; + return len; +} + +static ssize_t tzdbgfs_read(struct file *file, char __user *buf, + size_t count, loff_t *offp) +{ + int len = 0; + int *tz_id = file->private_data; + + if (*tz_id == TZDBG_BOOT || *tz_id == TZDBG_RESET || + *tz_id == TZDBG_INTERRUPT || *tz_id == TZDBG_GENERAL || + *tz_id == TZDBG_VMID || *tz_id == TZDBG_LOG) + memcpy_fromio((void *)tzdbg.diag_buf, tzdbg.virt_iobase, + debug_rw_buf_size); + + if (*tz_id == TZDBG_HYP_GENERAL || *tz_id == TZDBG_HYP_LOG) + memcpy_fromio((void *)tzdbg.hyp_diag_buf, tzdbg.hyp_virt_iobase, + tzdbg.hyp_debug_rw_buf_size); + + switch (*tz_id) { + case TZDBG_BOOT: + len = _disp_tz_boot_stats(); + break; + case TZDBG_RESET: + len = _disp_tz_reset_stats(); + break; + case TZDBG_INTERRUPT: + len = _disp_tz_interrupt_stats(); + break; + case TZDBG_GENERAL: + len = _disp_tz_general_stats(); + break; + case TZDBG_VMID: + len = _disp_tz_vmid_stats(); + break; + case TZDBG_LOG: + if (TZBSP_DIAG_MAJOR_VERSION_LEGACY < + (tzdbg.diag_buf->version >> 16)) { + len = _disp_tz_log_stats(count); + *offp = 0; + } else { + len = _disp_tz_log_stats_legacy(); + } + break; + case TZDBG_QSEE_LOG: + len = _disp_qsee_log_stats(count); + *offp = 0; + break; + case TZDBG_HYP_GENERAL: + len = _disp_hyp_general_stats(count); + break; + case TZDBG_HYP_LOG: + len = _disp_hyp_log_stats(count); + *offp = 0; + break; + default: + break; + } + + if (len > count) + len = count; + + return simple_read_from_buffer(buf, len, offp, + tzdbg.stat[(*tz_id)].data, len); +} + +static int tzdbgfs_open(struct inode *inode, struct file *pfile) +{ + pfile->private_data = inode->i_private; + return 0; +} + +const struct file_operations tzdbg_fops = { + .owner = THIS_MODULE, + .read = tzdbgfs_read, + .open = tzdbgfs_open, +}; + +static struct ion_client *g_ion_clnt; +static struct ion_handle *g_ihandle; + +/* + * Allocates log buffer from ION, registers the buffer at TZ + */ +static void tzdbg_register_qsee_log_buf(void) +{ + /* register log buffer scm request */ + struct qseecom_reg_log_buf_ireq req; + + /* scm response */ + struct qseecom_command_scm_resp resp = {}; + ion_phys_addr_t pa = 0; + size_t len; + int ret = 0; + + /* Create ION msm client */ + g_ion_clnt = msm_ion_client_create("qsee_log"); + if (g_ion_clnt == NULL) { + pr_err("%s: Ion client cannot be created\n", __func__); + return; + } + + g_ihandle = ion_alloc(g_ion_clnt, QSEE_LOG_BUF_SIZE, + 4096, ION_HEAP(ION_QSECOM_HEAP_ID), 0); + if (IS_ERR_OR_NULL(g_ihandle)) { + pr_err("%s: Ion client could not retrieve the handle\n", + __func__); + goto err1; + } + + ret = ion_phys(g_ion_clnt, g_ihandle, &pa, &len); + if (ret) { + pr_err("%s: Ion conversion to physical address failed\n", + __func__); + goto err2; + } + + req.qsee_cmd_id = QSEOS_REGISTER_LOG_BUF_COMMAND; + req.phy_addr = (uint32_t)pa; + req.len = len; + + if (!is_scm_armv8()) { + /* SCM_CALL to register the log buffer */ + ret = scm_call(SCM_SVC_TZSCHEDULER, 1, &req, sizeof(req), + &resp, sizeof(resp)); + } else { + struct scm_desc desc = {0}; + desc.args[0] = pa; + desc.args[1] = len; + desc.arginfo = 0x22; + ret = scm_call2(SCM_QSEEOS_FNID(1, 6), &desc); + resp.result = desc.ret[0]; + } + + if (ret) { + pr_err("%s: scm_call to register log buffer failed\n", + __func__); + goto err2; + } + + if (resp.result != QSEOS_RESULT_SUCCESS) { + pr_err( + "%s: scm_call to register log buf failed, resp result =%d\n", + __func__, resp.result); + goto err2; + } + + g_qsee_log = + (struct tzdbg_log_t *)ion_map_kernel(g_ion_clnt, g_ihandle); + + if (IS_ERR(g_qsee_log)) { + pr_err("%s: Couldn't map ion buffer to kernel\n", + __func__); + goto err2; + } + + g_qsee_log->log_pos.wrap = g_qsee_log->log_pos.offset = 0; + return; + +err2: + ion_free(g_ion_clnt, g_ihandle); + g_ihandle = NULL; +err1: + ion_client_destroy(g_ion_clnt); + g_ion_clnt = NULL; +} + +static int tzdbgfs_init(struct platform_device *pdev) +{ + int rc = 0; + int i; + struct dentry *dent_dir; + struct dentry *dent; + + dent_dir = debugfs_create_dir("tzdbg", NULL); + if (dent_dir == NULL) { + dev_err(&pdev->dev, "tzdbg debugfs_create_dir failed\n"); + return -ENOMEM; + } + + for (i = 0; i < TZDBG_STATS_MAX; i++) { + tzdbg.debug_tz[i] = i; + dent = debugfs_create_file(tzdbg.stat[i].name, + S_IRUGO, dent_dir, + &tzdbg.debug_tz[i], &tzdbg_fops); + if (dent == NULL) { + dev_err(&pdev->dev, "TZ debugfs_create_file failed\n"); + rc = -ENOMEM; + goto err; + } + } + tzdbg.disp_buf = kzalloc(max(debug_rw_buf_size, + tzdbg.hyp_debug_rw_buf_size), GFP_KERNEL); + if (tzdbg.disp_buf == NULL) { + pr_err("%s: Can't Allocate memory for tzdbg.disp_buf\n", + __func__); + + goto err; + } + platform_set_drvdata(pdev, dent_dir); + return 0; +err: + debugfs_remove_recursive(dent_dir); + + return rc; +} + +static void tzdbgfs_exit(struct platform_device *pdev) +{ + struct dentry *dent_dir; + + kzfree(tzdbg.disp_buf); + dent_dir = platform_get_drvdata(pdev); + debugfs_remove_recursive(dent_dir); + if (g_ion_clnt != NULL) { + if (!IS_ERR_OR_NULL(g_ihandle)) { + ion_unmap_kernel(g_ion_clnt, g_ihandle); + ion_free(g_ion_clnt, g_ihandle); + } + ion_client_destroy(g_ion_clnt); + } +} + +static int __update_hypdbg_base(struct platform_device *pdev, + void __iomem *virt_iobase) +{ + phys_addr_t hypdiag_phy_iobase; + uint32_t hyp_address_offset; + uint32_t hyp_size_offset; + struct hypdbg_t *hyp; + uint32_t *ptr = NULL; + + if (of_property_read_u32((&pdev->dev)->of_node, "hyplog-address-offset", + &hyp_address_offset)) { + dev_err(&pdev->dev, "hyplog address offset is not defined\n"); + return -EINVAL; + } + if (of_property_read_u32((&pdev->dev)->of_node, "hyplog-size-offset", + &hyp_size_offset)) { + dev_err(&pdev->dev, "hyplog size offset is not defined\n"); + return -EINVAL; + } + + hypdiag_phy_iobase = readl_relaxed(virt_iobase + hyp_address_offset); + tzdbg.hyp_debug_rw_buf_size = readl_relaxed(virt_iobase + + hyp_size_offset); + + tzdbg.hyp_virt_iobase = devm_ioremap_nocache(&pdev->dev, + hypdiag_phy_iobase, + tzdbg.hyp_debug_rw_buf_size); + if (!tzdbg.hyp_virt_iobase) { + dev_err(&pdev->dev, "ERROR could not ioremap: start=%pr, len=%u\n", + &hypdiag_phy_iobase, tzdbg.hyp_debug_rw_buf_size); + return -ENXIO; + } + + ptr = kzalloc(tzdbg.hyp_debug_rw_buf_size, GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + tzdbg.hyp_diag_buf = (struct hypdbg_t *)ptr; + hyp = tzdbg.hyp_diag_buf; + hyp->log_pos.wrap = hyp->log_pos.offset = 0; + return 0; +} + +static void tzdbg_get_tz_version(void) +{ + uint32_t smc_id = 0; + uint32_t feature = 10; + struct qseecom_command_scm_resp resp = {0}; + struct scm_desc desc = {0}; + int ret = 0; + + if (!is_scm_armv8()) { + ret = scm_call(SCM_SVC_INFO, SCM_SVC_UTIL, &feature, + sizeof(feature), &resp, sizeof(resp)); + } else { + smc_id = TZ_INFO_GET_FEATURE_VERSION_ID; + desc.arginfo = TZ_INFO_GET_FEATURE_VERSION_ID_PARAM_ID; + desc.args[0] = feature; + ret = scm_call2(smc_id, &desc); + resp.result = desc.ret[0]; + } + + if (ret) + pr_err("%s: scm_call to get tz version failed\n", + __func__); + else + tzdbg.tz_version = resp.result; + +} + +/* + * Driver functions + */ +static int tz_log_probe(struct platform_device *pdev) +{ + struct resource *resource; + void __iomem *virt_iobase; + phys_addr_t tzdiag_phy_iobase; + uint32_t *ptr = NULL; + int ret = 0; + + /* + * Get address that stores the physical location diagnostic data + */ + resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!resource) { + dev_err(&pdev->dev, + "%s: ERROR Missing MEM resource\n", __func__); + return -ENXIO; + }; + + /* + * Get the debug buffer size + */ + debug_rw_buf_size = resource->end - resource->start + 1; + + /* + * Map address that stores the physical location diagnostic data + */ + virt_iobase = devm_ioremap_nocache(&pdev->dev, resource->start, + debug_rw_buf_size); + if (!virt_iobase) { + dev_err(&pdev->dev, + "%s: ERROR could not ioremap: start=%pr, len=%u\n", + __func__, &resource->start, + (unsigned int)(debug_rw_buf_size)); + return -ENXIO; + } + + if (pdev->dev.of_node) { + tzdbg.is_hyplog_enabled = of_property_read_bool( + (&pdev->dev)->of_node, "qcom,hyplog-enabled"); + if (tzdbg.is_hyplog_enabled) { + ret = __update_hypdbg_base(pdev, virt_iobase); + if (ret) { + dev_err(&pdev->dev, "%s() failed to get device tree data ret = %d\n", + __func__, ret); + return -EINVAL; + } + } else { + dev_info(&pdev->dev, "Hyp log service is not supported\n"); + } + } else { + dev_dbg(&pdev->dev, "Device tree data is not found\n"); + } + + /* + * Retrieve the address of diagnostic data + */ + tzdiag_phy_iobase = readl_relaxed(virt_iobase); + + /* + * Map the diagnostic information area + */ + tzdbg.virt_iobase = devm_ioremap_nocache(&pdev->dev, + tzdiag_phy_iobase, debug_rw_buf_size); + + if (!tzdbg.virt_iobase) { + dev_err(&pdev->dev, + "%s: ERROR could not ioremap: start=%pr, len=%u\n", + __func__, &tzdiag_phy_iobase, + debug_rw_buf_size); + return -ENXIO; + } + + ptr = kzalloc(debug_rw_buf_size, GFP_KERNEL); + if (ptr == NULL) { + pr_err("%s: Can't Allocate memory: ptr\n", + __func__); + return -ENXIO; + } + + tzdbg.diag_buf = (struct tzdbg_t *)ptr; + + if (tzdbgfs_init(pdev)) + goto err; + + tzdbg_register_qsee_log_buf(); + + tzdbg_get_tz_version(); + + return 0; +err: + kfree(tzdbg.diag_buf); + return -ENXIO; +} + + +static int tz_log_remove(struct platform_device *pdev) +{ + kzfree(tzdbg.diag_buf); + if (tzdbg.hyp_diag_buf) + kzfree(tzdbg.hyp_diag_buf); + tzdbgfs_exit(pdev); + + return 0; +} + +static struct of_device_id tzlog_match[] = { + { .compatible = "qcom,tz-log", + }, + {} +}; + +static struct platform_driver tz_log_driver = { + .probe = tz_log_probe, + .remove = tz_log_remove, + .driver = { + .name = "tz_log", + .owner = THIS_MODULE, + .of_match_table = tzlog_match, + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, +}; + +static int __init tz_log_init(void) +{ + return platform_driver_register(&tz_log_driver); +} + +static void __exit tz_log_exit(void) +{ + platform_driver_unregister(&tz_log_driver); +} + +module_init(tz_log_init); +module_exit(tz_log_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("TZ Log driver"); +MODULE_ALIAS("platform:tz_log"); |
