/* * tegra-soc-hwpm-ioctl.c: * This file adds IOCTL handlers for the Tegra SOC HWPM driver. * * Copyright (c) 2021, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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, see . */ #include #include #include #include #include /* FIXME: Is this include needed for struct resource? */ #if 0 #include #endif #include #include #include #include "tegra-soc-hwpm.h" #include "tegra-soc-hwpm-io.h" struct tegra_soc_hwpm_ioctl { const char *const name; const size_t struct_size; int (*handler)(struct tegra_soc_hwpm *, void *); }; static int device_info_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct); static int timer_relation_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct); static int reserve_resource_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct); static int alloc_pma_stream_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct); static int bind_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct); static int query_whitelist_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct); static int exec_reg_ops_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct); static int update_get_put_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct); static const struct tegra_soc_hwpm_ioctl ioctls[] = { [TEGRA_SOC_HWPM_IOCTL_DEVICE_INFO] = { .name = "device_info", .struct_size = sizeof(struct tegra_soc_hwpm_device_info), .handler = device_info_ioctl, }, [TEGRA_SOC_HWPM_IOCTL_GET_GPU_CPU_TIME_CORRELATION_INFO] = { .name = "timer_relation", .struct_size = sizeof(struct tegra_soc_hwpm_timer_relation), .handler = timer_relation_ioctl, }, [TEGRA_SOC_HWPM_IOCTL_RESERVE_RESOURCE] = { .name = "reserve_resource", .struct_size = sizeof(struct tegra_soc_hwpm_reserve_resource), .handler = reserve_resource_ioctl, }, [TEGRA_SOC_HWPM_IOCTL_ALLOC_PMA_STREAM] = { .name = "alloc_pma_stream", .struct_size = sizeof(struct tegra_soc_hwpm_alloc_pma_stream), .handler = alloc_pma_stream_ioctl, }, [TEGRA_SOC_HWPM_IOCTL_BIND] = { .name = "bind", .struct_size = 0, .handler = bind_ioctl, }, [TEGRA_SOC_HWPM_IOCTL_QUERY_WHITELIST] = { .name = "query_whitelist", .struct_size = sizeof(struct tegra_soc_hwpm_query_whitelist), .handler = query_whitelist_ioctl, }, [TEGRA_SOC_HWPM_IOCTL_EXEC_REG_OPS] = { .name = "exec_reg_ops", .struct_size = sizeof(struct tegra_soc_hwpm_exec_reg_ops), .handler = exec_reg_ops_ioctl, }, [TEGRA_SOC_HWPM_IOCTL_UPDATE_GET_PUT] = { .name = "update_get_put", .struct_size = sizeof(struct tegra_soc_hwpm_update_get_put), .handler = update_get_put_ioctl, }, }; static int device_info_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct) { /* FIXME: Implement IOCTL */ #if 0 struct tegra_soc_hwpm_device_info *device_info = (struct tegra_soc_hwpm_device_info *)ioctl_struct; #endif tegra_soc_hwpm_err("The DEVICE_INFO IOCTL is currently not implemented"); return -ENXIO; } static int timer_relation_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct) { /* FIXME: Implement IOCTL */ #if 0 struct tegra_soc_hwpm_timer_relation *timer_relation = (struct tegra_soc_hwpm_timer_relation *)ioctl_struct; #endif tegra_soc_hwpm_err("The GET_GPU_CPU_TIME_CORRELATION_INFO IOCTL is" " currently not implemented"); return -ENXIO; } static u32 **get_mc_fake_regs(struct tegra_soc_hwpm *hwpm, struct hwpm_resource_aperture *aperture) { if (!hwpm->fake_registers_enabled) return NULL; if (!aperture) { tegra_soc_hwpm_err("aperture is NULL"); return NULL; } switch (aperture->start_pa) { case NV_ADDRESS_MAP_MC0_BASE: return &mc_fake_regs[0]; case NV_ADDRESS_MAP_MC1_BASE: return &mc_fake_regs[1]; case NV_ADDRESS_MAP_MC2_BASE: return &mc_fake_regs[2]; case NV_ADDRESS_MAP_MC3_BASE: return &mc_fake_regs[3]; case NV_ADDRESS_MAP_MC4_BASE: return &mc_fake_regs[4]; case NV_ADDRESS_MAP_MC5_BASE: return &mc_fake_regs[5]; case NV_ADDRESS_MAP_MC6_BASE: return &mc_fake_regs[6]; case NV_ADDRESS_MAP_MC7_BASE: return &mc_fake_regs[7]; case NV_ADDRESS_MAP_MC8_BASE: return &mc_fake_regs[8]; case NV_ADDRESS_MAP_MC9_BASE: return &mc_fake_regs[9]; case NV_ADDRESS_MAP_MC10_BASE: return &mc_fake_regs[10]; case NV_ADDRESS_MAP_MC11_BASE: return &mc_fake_regs[11]; case NV_ADDRESS_MAP_MC12_BASE: return &mc_fake_regs[12]; case NV_ADDRESS_MAP_MC13_BASE: return &mc_fake_regs[13]; case NV_ADDRESS_MAP_MC14_BASE: return &mc_fake_regs[14]; case NV_ADDRESS_MAP_MC15_BASE: return &mc_fake_regs[15]; default: return NULL; } } static void set_mc_fake_regs(struct tegra_soc_hwpm *hwpm, struct hwpm_resource_aperture *aperture, bool set_null) { u32 *fake_regs = NULL; if (!aperture) { tegra_soc_hwpm_err("aperture is NULL"); return; } switch (aperture->start_pa) { case NV_ADDRESS_MAP_MC0_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[0]; mss_channel_map[0].fake_registers = fake_regs; mss_iso_niso_hub_map[0].fake_registers = fake_regs; mss_mcf_map[0].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC1_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[1]; mss_channel_map[1].fake_registers = fake_regs; mss_iso_niso_hub_map[1].fake_registers = fake_regs; mss_mcf_map[1].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC2_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[2]; mss_channel_map[2].fake_registers = fake_regs; mss_iso_niso_hub_map[2].fake_registers = fake_regs; mss_mcf_map[2].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC3_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[3]; mss_channel_map[3].fake_registers = fake_regs; mss_iso_niso_hub_map[3].fake_registers = fake_regs; mss_mcf_map[3].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC4_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[4]; mss_channel_map[4].fake_registers = fake_regs; mss_iso_niso_hub_map[4].fake_registers = fake_regs; mss_mcf_map[4].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC5_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[5]; mss_channel_map[5].fake_registers = fake_regs; mss_iso_niso_hub_map[5].fake_registers = fake_regs; mss_mcf_map[5].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC6_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[6]; mss_channel_map[6].fake_registers = fake_regs; mss_iso_niso_hub_map[6].fake_registers = fake_regs; mss_mcf_map[6].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC7_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[7]; mss_channel_map[7].fake_registers = fake_regs; mss_iso_niso_hub_map[7].fake_registers = fake_regs; mss_mcf_map[7].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC8_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[8]; mss_channel_map[8].fake_registers = fake_regs; mss_iso_niso_hub_map[8].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC9_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[9]; mss_channel_map[9].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC10_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[10]; mss_channel_map[10].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC11_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[11]; mss_channel_map[11].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC12_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[12]; mss_channel_map[12].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC13_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[13]; mss_channel_map[13].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC14_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[14]; mss_channel_map[14].fake_registers = fake_regs; break; case NV_ADDRESS_MAP_MC15_BASE: fake_regs = (!hwpm->fake_registers_enabled || set_null) ? NULL : mc_fake_regs[15]; mss_channel_map[15].fake_registers = fake_regs; break; default: break; } } static int reserve_resource_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct) { int ret = 0; struct tegra_soc_hwpm_reserve_resource *reserve_resource = (struct tegra_soc_hwpm_reserve_resource *)ioctl_struct; u32 resource = reserve_resource->resource; struct hwpm_resource_aperture *aperture = NULL; int aprt_idx = 0; if (hwpm->bind_completed) { tegra_soc_hwpm_err("The RESERVE_RESOURCE IOCTL can only be" " called before the BIND IOCTL."); return -EPERM; } /* * FIXME: Tell IPs which are being profiled to power up IP and * disable power management */ /* Map reserved apertures and allocate fake register arrays if needed */ for (aprt_idx = 0; aprt_idx < hwpm_resources[resource].map_size; aprt_idx++) { aperture = &(hwpm_resources[resource].map[aprt_idx]); if ((aperture->dt_aperture == TEGRA_SOC_HWPM_PMA_DT) || (aperture->dt_aperture == TEGRA_SOC_HWPM_RTR_DT)) { /* PMA and RTR apertures are handled in open(fd) */ continue; } else if (IS_PERFMON(aperture->dt_aperture)) { struct resource *res = NULL; u64 num_regs = 0; tegra_soc_hwpm_dbg("Found PERFMON(0x%llx - 0x%llx)", aperture->start_pa, aperture->end_pa); hwpm->dt_apertures[aperture->dt_aperture] = of_iomap(hwpm->np, aperture->dt_aperture); if (!hwpm->dt_apertures[aperture->dt_aperture]) { tegra_soc_hwpm_err("Couldn't map PERFMON(%d)", aperture->dt_aperture); ret = -ENOMEM; goto fail; } res = platform_get_resource(hwpm->pdev, IORESOURCE_MEM, aperture->dt_aperture); if ((!res) || (res->start == 0) || (res->end == 0)) { tegra_soc_hwpm_err("Invalid resource for PERFMON(%d)", aperture->dt_aperture); ret = -ENOMEM; goto fail; } aperture->start_pa = res->start; aperture->end_pa = res->end; if (hwpm->fake_registers_enabled) { num_regs = (aperture->end_pa + 1 - aperture->start_pa) / sizeof(*aperture->fake_registers); aperture->fake_registers = (u32 *)kzalloc(sizeof(*aperture->fake_registers) * num_regs, GFP_KERNEL); if (!aperture->fake_registers) { tegra_soc_hwpm_err("Aperture(0x%llx - 0x%llx):" " Couldn't allocate memory for fake" " registers", aperture->start_pa, aperture->end_pa); ret = -ENOMEM; goto fail; } } } else { /* IP apertures */ if (hwpm->fake_registers_enabled) { u64 num_regs = 0; u32 **fake_regs = get_mc_fake_regs(hwpm, aperture); if (!fake_regs) fake_regs = &aperture->fake_registers; num_regs = (aperture->end_pa + 1 - aperture->start_pa) / sizeof(*(*fake_regs)); *fake_regs = (u32 *)kzalloc(sizeof(*(*fake_regs)) * num_regs, GFP_KERNEL); if (!(*fake_regs)) { tegra_soc_hwpm_err("Aperture(0x%llx - 0x%llx):" " Couldn't allocate memory for fake" " registers", aperture->start_pa, aperture->end_pa); ret = -ENOMEM; goto fail; } set_mc_fake_regs(hwpm, aperture, false); } } } hwpm_resources[resource].reserved = true; goto success; fail: for (aprt_idx = 0; aprt_idx < hwpm_resources[resource].map_size; aprt_idx++) { aperture = &(hwpm_resources[resource].map[aprt_idx]); if ((aperture->dt_aperture == TEGRA_SOC_HWPM_PMA_DT) || (aperture->dt_aperture == TEGRA_SOC_HWPM_RTR_DT)) { /* PMA and RTR apertures are handled in open(fd) */ continue; } else if (IS_PERFMON(aperture->dt_aperture)) { if (hwpm->dt_apertures[aperture->dt_aperture]) { iounmap(hwpm->dt_apertures[aperture->dt_aperture]); hwpm->dt_apertures[aperture->dt_aperture] = NULL; } aperture->start_pa = 0; aperture->end_pa = 0; if (aperture->fake_registers) { kfree(aperture->fake_registers); aperture->fake_registers = NULL; } } else { /* IP apertures */ if (aperture->fake_registers) { kfree(aperture->fake_registers); aperture->fake_registers = NULL; set_mc_fake_regs(hwpm, aperture, true); } } } hwpm_resources[resource].reserved = false; success: return ret; } static int alloc_pma_stream_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct) { int ret = 0; u32 reg_val = 0; u32 outbase_lo = 0; u32 outbase_hi = 0; u32 outsize = 0; u32 mem_bytes_addr = 0; struct tegra_soc_hwpm_alloc_pma_stream *alloc_pma_stream = (struct tegra_soc_hwpm_alloc_pma_stream *)ioctl_struct; if (hwpm->bind_completed) { tegra_soc_hwpm_err("The ALLOC_PMA_STREAM IOCTL can only be" " called before the BIND IOCTL."); return -EPERM; } if (alloc_pma_stream->stream_buf_size == 0) { tegra_soc_hwpm_err("stream_buf_size is 0"); return -EINVAL; } if (alloc_pma_stream->stream_buf_fd == 0) { tegra_soc_hwpm_err("Invalid stream_buf_fd"); return -EINVAL; } if (alloc_pma_stream->mem_bytes_buf_fd == 0) { tegra_soc_hwpm_err("Invalid mem_bytes_buf_fd"); return -EINVAL; } /* Memory map stream buffer */ hwpm->stream_dma_buf = dma_buf_get(alloc_pma_stream->stream_buf_fd); if (IS_ERR(hwpm->stream_dma_buf)) { tegra_soc_hwpm_err("Unable to get stream dma_buf"); ret = PTR_ERR(hwpm->stream_dma_buf); goto fail; } hwpm->stream_attach = dma_buf_attach(hwpm->stream_dma_buf, hwpm->dev); if (IS_ERR(hwpm->stream_attach)) { tegra_soc_hwpm_err("Unable to attach stream dma_buf"); ret = PTR_ERR(hwpm->stream_attach); goto fail; } hwpm->stream_sgt = dma_buf_map_attachment(hwpm->stream_attach, DMA_FROM_DEVICE); if (IS_ERR(hwpm->stream_sgt)) { tegra_soc_hwpm_err("Unable to map stream attachment"); ret = PTR_ERR(hwpm->stream_sgt); goto fail; } alloc_pma_stream->stream_buf_pma_va = sg_dma_address(hwpm->stream_sgt->sgl); if (alloc_pma_stream->stream_buf_pma_va == 0) { tegra_soc_hwpm_err("Invalid stream buffer SMMU IOVA"); ret = -ENXIO; goto fail; } tegra_soc_hwpm_dbg("stream_buf_pma_va = 0x%llx", alloc_pma_stream->stream_buf_pma_va); /* Memory map mem bytes buffer */ hwpm->mem_bytes_dma_buf = dma_buf_get(alloc_pma_stream->mem_bytes_buf_fd); if (IS_ERR(hwpm->mem_bytes_dma_buf)) { tegra_soc_hwpm_err("Unable to get mem bytes dma_buf"); ret = PTR_ERR(hwpm->mem_bytes_dma_buf); goto fail; } hwpm->mem_bytes_attach = dma_buf_attach(hwpm->mem_bytes_dma_buf, hwpm->dev); if (IS_ERR(hwpm->mem_bytes_attach)) { tegra_soc_hwpm_err("Unable to attach mem bytes dma_buf"); ret = PTR_ERR(hwpm->mem_bytes_attach); goto fail; } hwpm->mem_bytes_sgt = dma_buf_map_attachment(hwpm->mem_bytes_attach, DMA_FROM_DEVICE); if (IS_ERR(hwpm->mem_bytes_sgt)) { tegra_soc_hwpm_err("Unable to map mem bytes attachment"); ret = PTR_ERR(hwpm->mem_bytes_sgt); goto fail; } hwpm->mem_bytes_kernel = dma_buf_vmap(hwpm->mem_bytes_dma_buf); if (!hwpm->mem_bytes_kernel) { tegra_soc_hwpm_err("Unable to map mem_bytes buffer into kernel VA space"); ret = -ENOMEM; goto fail; } memset(hwpm->mem_bytes_kernel, 0, 32); outbase_lo = alloc_pma_stream->stream_buf_pma_va & 0xffffffffULL; outbase_lo >>= NV_PERF_PMASYS_CHANNEL_OUTBASE_PTR_SHIFT; reg_val = HWPM_REG_F(NV_PERF_PMASYS_CHANNEL_OUTBASE_PTR, outbase_lo); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_OUTBASE_CH0, reg_val); tegra_soc_hwpm_dbg("OUTBASE = 0x%x", reg_val); outbase_hi = (alloc_pma_stream->stream_buf_pma_va >> 32) & 0xff; outbase_hi >>= NV_PERF_PMASYS_CHANNEL_OUTBASEUPPER_PTR_SHIFT; reg_val = HWPM_REG_F(NV_PERF_PMASYS_CHANNEL_OUTBASEUPPER_PTR, outbase_hi); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_OUTBASEUPPER_CH0, reg_val); tegra_soc_hwpm_dbg("OUTBASEUPPER = 0x%x", reg_val); outsize = alloc_pma_stream->stream_buf_size >> NV_PERF_PMASYS_CHANNEL_OUTSIZE_NUMBYTES_SHIFT; reg_val = HWPM_REG_F(NV_PERF_PMASYS_CHANNEL_OUTSIZE_NUMBYTES, outsize); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_OUTSIZE_CH0, reg_val); tegra_soc_hwpm_dbg("OUTSIZE = 0x%x", reg_val); mem_bytes_addr = sg_dma_address(hwpm->mem_bytes_sgt->sgl) & 0xffffffffULL; mem_bytes_addr >>= NV_PERF_PMASYS_CHANNEL_MEM_BYTES_ADDR_PTR_SHIFT; reg_val = HWPM_REG_F(NV_PERF_PMASYS_CHANNEL_MEM_BYTES_ADDR_PTR, mem_bytes_addr); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_MEM_BYTES_ADDR_CH0, reg_val); tegra_soc_hwpm_dbg("MEM_BYTES_ADDR = 0x%x", reg_val); reg_val = HWPM_REG_F(NV_PERF_PMASYS_CHANNEL_MEM_BLOCK_VALID, NV_PERF_PMASYS_CHANNEL_MEM_BLOCK_VALID_TRUE); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_MEM_BLOCK_CH0, reg_val); return 0; fail: reg_val = HWPM_REG_F(NV_PERF_PMASYS_CHANNEL_MEM_BLOCK_VALID, NV_PERF_PMASYS_CHANNEL_MEM_BLOCK_VALID_FALSE); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_MEM_BLOCK_CH0, reg_val); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_OUTBASE_CH0, 0); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_OUTBASEUPPER_CH0, 0); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_OUTSIZE_CH0, 0); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_MEM_BYTES_ADDR_CH0, 0); alloc_pma_stream->stream_buf_pma_va = 0; if (hwpm->stream_sgt && (!IS_ERR(hwpm->stream_sgt))) { dma_buf_unmap_attachment(hwpm->stream_attach, hwpm->stream_sgt, DMA_FROM_DEVICE); } hwpm->stream_sgt = NULL; if (hwpm->stream_attach && (!IS_ERR(hwpm->stream_attach))) { dma_buf_detach(hwpm->stream_dma_buf, hwpm->stream_attach); } hwpm->stream_attach = NULL; if (hwpm->stream_dma_buf && (!IS_ERR(hwpm->stream_dma_buf))) { dma_buf_put(hwpm->stream_dma_buf); } hwpm->stream_dma_buf = NULL; if (hwpm->mem_bytes_kernel) { dma_buf_vunmap(hwpm->mem_bytes_dma_buf, hwpm->mem_bytes_kernel); hwpm->mem_bytes_kernel = NULL; } if (hwpm->mem_bytes_sgt && (!IS_ERR(hwpm->mem_bytes_sgt))) { dma_buf_unmap_attachment(hwpm->mem_bytes_attach, hwpm->mem_bytes_sgt, DMA_FROM_DEVICE); } hwpm->mem_bytes_sgt = NULL; if (hwpm->mem_bytes_attach && (!IS_ERR(hwpm->mem_bytes_attach))) { dma_buf_detach(hwpm->mem_bytes_dma_buf, hwpm->mem_bytes_attach); } hwpm->mem_bytes_attach = NULL; if (hwpm->mem_bytes_dma_buf && (!IS_ERR(hwpm->mem_bytes_dma_buf))) { dma_buf_put(hwpm->mem_bytes_dma_buf); } hwpm->mem_bytes_dma_buf = NULL; return ret; } static int bind_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct) { int ret = 0; int res_idx = 0; int aprt_idx = 0; u32 wlist_idx = 0; struct hwpm_resource_aperture *aperture = NULL; for (res_idx = 0; res_idx < TERGA_SOC_HWPM_NUM_RESOURCES; res_idx++) { if (!hwpm_resources[res_idx].reserved) continue; tegra_soc_hwpm_dbg("Found reserved IP(%d)", res_idx); for (aprt_idx = 0; aprt_idx < hwpm_resources[res_idx].map_size; aprt_idx++) { aperture = &(hwpm_resources[res_idx].map[aprt_idx]); /* Zero out necessary registers */ if (aperture->wlist) { for (wlist_idx = 0; wlist_idx < aperture->wlist_size; wlist_idx++) { if (aperture->wlist[wlist_idx].zero_in_init) { ioctl_writel(hwpm, aperture, aperture->start_pa + aperture->wlist[wlist_idx].reg, 0); } } } else { tegra_soc_hwpm_err("NULL whitelist in aperture(0x%llx - 0x%llx)", aperture->start_pa, aperture->end_pa); } /* * Enable reporting of PERFMON status to * NV_PERF_PMMSYS_SYS0ROUTER_PERFMONSTATUS_MERGED */ if (IS_PERFMON(aperture->dt_aperture)) { tegra_soc_hwpm_dbg("Found PERFMON(0x%llx - 0x%llx)", aperture->start_pa, aperture->end_pa); ret = DRIVER_REG_RMW(hwpm, aperture->dt_aperture, NV_PERF_PMMSYS_SYS0_ENGINESTATUS, NV_PERF_PMMSYS_SYS0_ENGINESTATUS_ENABLE, NV_PERF_PMMSYS_SYS0_ENGINESTATUS_ENABLE_OUT, false); if (ret < 0) { tegra_soc_hwpm_err("Unable to set PMM ENGINESTATUS_ENABLE" " for PERFMON(0x%llx - 0x%llx)", aperture->start_pa, aperture->end_pa); return -EIO; } } } } hwpm->bind_completed = true; return 0; } static int query_whitelist_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct) { int ret = 0; int res_idx = 0; int aprt_idx = 0; struct hwpm_resource_aperture *aperture = NULL; struct tegra_soc_hwpm_query_whitelist *query_whitelist = (struct tegra_soc_hwpm_query_whitelist *)ioctl_struct; if (!hwpm->bind_completed) { tegra_soc_hwpm_err("The QUERY_WHITELIST IOCTL can only be called" " after the BIND IOCTL."); return -EPERM; } if (!query_whitelist->whitelist) { /* Return whitelist_size */ if (hwpm->full_wlist_size >= 0) { query_whitelist->whitelist_size = hwpm->full_wlist_size; return 0; } hwpm->full_wlist_size = 0; for (res_idx = 0; res_idx < TERGA_SOC_HWPM_NUM_RESOURCES; res_idx++) { if (!(hwpm_resources[res_idx].reserved)) continue; tegra_soc_hwpm_dbg("Found reserved IP(%d)", res_idx); for (aprt_idx = 0; aprt_idx < hwpm_resources[res_idx].map_size; aprt_idx++) { aperture = &(hwpm_resources[res_idx].map[aprt_idx]); if (aperture->wlist) { hwpm->full_wlist_size += aperture->wlist_size; } else { tegra_soc_hwpm_err("NULL whitelist in aperture(0x%llx - 0x%llx)", aperture->start_pa, aperture->end_pa); } } } query_whitelist->whitelist_size = hwpm->full_wlist_size; } else { /* Fill in whitelist array */ unsigned long user_va = (unsigned long)(query_whitelist->whitelist); unsigned long offset = user_va & ~PAGE_MASK; u64 wlist_buf_size = 0; u64 num_pages = 0; long pinned_pages = 0; struct page **pages = NULL; long page_idx = 0; void *full_wlist = NULL; u64 *full_wlist_u64 = NULL; u32 full_wlist_idx = 0; u32 aprt_wlist_idx = 0; if (hwpm->full_wlist_size < 0) { tegra_soc_hwpm_err("Invalid whitelist size"); return -EINVAL; } wlist_buf_size = hwpm->full_wlist_size * sizeof(*(query_whitelist->whitelist)); /* Memory map user buffer into kernel address space */ num_pages = DIV_ROUND_UP(offset + wlist_buf_size, PAGE_SIZE); pages = (struct page **)kzalloc(sizeof(*pages) * num_pages, GFP_KERNEL); if (!pages) { tegra_soc_hwpm_err("Couldn't allocate memory for pages array"); ret = -ENOMEM; goto wlist_unmap; } pinned_pages = get_user_pages(user_va & PAGE_MASK, num_pages, 0, pages, NULL); if (pinned_pages != num_pages) { tegra_soc_hwpm_err("Requested %llu pages / Got %ld pages", num_pages, pinned_pages); ret = -ENOMEM; goto wlist_unmap; } full_wlist = vmap(pages, num_pages, VM_MAP, PAGE_KERNEL); if (!full_wlist) { tegra_soc_hwpm_err("Couldn't map whitelist buffer into" " kernel address space"); ret = -ENOMEM; goto wlist_unmap; } full_wlist_u64 = (u64 *)(full_wlist + offset); /* Fill in whitelist buffer */ for (res_idx = 0, full_wlist_idx = 0; res_idx < TERGA_SOC_HWPM_NUM_RESOURCES; res_idx++) { if (!(hwpm_resources[res_idx].reserved)) continue; tegra_soc_hwpm_dbg("Found reserved IP(%d)", res_idx); for (aprt_idx = 0; aprt_idx < hwpm_resources[res_idx].map_size; aprt_idx++) { aperture = &(hwpm_resources[res_idx].map[aprt_idx]); if (aperture->wlist) { for (aprt_wlist_idx = 0; aprt_wlist_idx < aperture->wlist_size; aprt_wlist_idx++, full_wlist_idx++) { full_wlist_u64[full_wlist_idx] = aperture->start_pa + aperture->wlist[aprt_wlist_idx].reg; } } else { tegra_soc_hwpm_err("NULL whitelist in aperture(0x%llx - 0x%llx)", aperture->start_pa, aperture->end_pa); } } } wlist_unmap: if (full_wlist) vunmap(full_wlist); if (pinned_pages > 0) { for (page_idx = 0; page_idx < pinned_pages; page_idx++) { set_page_dirty(pages[page_idx]); put_page(pages[page_idx]); } } if (pages) kfree(pages); } return ret; } static int exec_reg_ops_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct) { int ret = 0; struct tegra_soc_hwpm_exec_reg_ops *exec_reg_ops = (struct tegra_soc_hwpm_exec_reg_ops *)ioctl_struct; struct hwpm_resource_aperture *aperture = NULL; int op_idx = 0; struct tegra_soc_hwpm_reg_op *reg_op = NULL; if (!hwpm->bind_completed) { tegra_soc_hwpm_err("The EXEC_REG_OPS IOCTL can only be called" " after the BIND IOCTL."); return -EPERM; } switch (exec_reg_ops->mode) { case TEGRA_SOC_HWPM_REG_OP_MODE_FAIL_ON_FIRST: case TEGRA_SOC_HWPM_REG_OP_MODE_CONT_ON_ERR: break; default: tegra_soc_hwpm_err("Invalid reg ops mode(%u)", exec_reg_ops->mode); return -EINVAL; } for (op_idx = 0; op_idx < exec_reg_ops->op_count; op_idx++) { #define REG_OP_FAIL(op_status, msg, ...) \ do { \ tegra_soc_hwpm_err(msg, ##__VA_ARGS__); \ reg_op->status = \ TEGRA_SOC_HWPM_REG_OP_STATUS_ ## op_status; \ exec_reg_ops->b_all_reg_ops_passed = false; \ if (exec_reg_ops->mode == \ TEGRA_SOC_HWPM_REG_OP_MODE_FAIL_ON_FIRST) { \ return -EINVAL; \ } \ } while (0) tegra_soc_hwpm_dbg("reg op: idx(%d), phys(0x%llx), cmd(%u)", op_idx, reg_op->phys_addr, reg_op->cmd); reg_op = &(exec_reg_ops->ops[op_idx]); /* The whitelist check is done here */ aperture = find_hwpm_aperture(hwpm, reg_op->phys_addr, true); if (!aperture) { REG_OP_FAIL(INSUFFICIENT_PERMISSIONS, "Invalid register address(0x%llx)", reg_op->phys_addr); continue; } switch (reg_op->cmd) { case TEGRA_SOC_HWPM_REG_OP_CMD_RD32: reg_op->reg_val_lo = ioctl_readl(hwpm, aperture, reg_op->phys_addr); reg_op->status = TEGRA_SOC_HWPM_REG_OP_STATUS_SUCCESS; break; case TEGRA_SOC_HWPM_REG_OP_CMD_RD64: reg_op->reg_val_lo = ioctl_readl(hwpm, aperture, reg_op->phys_addr); reg_op->reg_val_hi = ioctl_readl(hwpm, aperture, reg_op->phys_addr + 4); reg_op->status = TEGRA_SOC_HWPM_REG_OP_STATUS_SUCCESS; break; /* Read Modify Write operation */ case TEGRA_SOC_HWPM_REG_OP_CMD_WR32: ret = IOCTL_REG_RMW(hwpm, aperture, reg_op->phys_addr, reg_op->mask_lo, reg_op->reg_val_lo); if (ret < 0) { REG_OP_FAIL(INVALID, "WR32 REGOP failed for register(0x%llx)", reg_op->phys_addr); } else { reg_op->status = TEGRA_SOC_HWPM_REG_OP_STATUS_SUCCESS; } break; /* Read Modify Write operation */ case TEGRA_SOC_HWPM_REG_OP_CMD_WR64: /* Lower 32 bits */ ret = IOCTL_REG_RMW(hwpm, aperture, reg_op->phys_addr, reg_op->mask_lo, reg_op->reg_val_lo); if (ret < 0) { REG_OP_FAIL(INVALID, "WR64 REGOP failed for register(0x%llx)", reg_op->phys_addr); continue; } /* Upper 32 bits */ ret = IOCTL_REG_RMW(hwpm, aperture, reg_op->phys_addr + 4, reg_op->mask_hi, reg_op->reg_val_hi); if (ret < 0) { REG_OP_FAIL(INVALID, "WR64 REGOP failed for register(0x%llx)", reg_op->phys_addr + 4); } else { reg_op->status = TEGRA_SOC_HWPM_REG_OP_STATUS_SUCCESS; } break; default: REG_OP_FAIL(INVALID_CMD, "Invalid reg op command(%u)", reg_op->cmd); break; } } exec_reg_ops->b_all_reg_ops_passed = true; return 0; } static int update_get_put_ioctl(struct tegra_soc_hwpm *hwpm, void *ioctl_struct) { int ret = 0; u32 reg_val = 0; u32 field_val = 0; u32 *mem_bytes_kernel_u32 = NULL; struct tegra_soc_hwpm_update_get_put *update_get_put = (struct tegra_soc_hwpm_update_get_put *)ioctl_struct; if (!hwpm->bind_completed) { tegra_soc_hwpm_err("The UPDATE_GET_PUT IOCTL can only be called" " after the BIND IOCTL."); return -EPERM; } if (!hwpm->mem_bytes_kernel) { tegra_soc_hwpm_err("mem_bytes buffer is not mapped in the driver"); return -ENXIO; } /* Update SW get pointer */ hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_MEM_BUMP_CH0, update_get_put->mem_bump); /* Stream MEM_BYTES value to MEM_BYTES buffer */ if (update_get_put->b_stream_mem_bytes) { mem_bytes_kernel_u32 = (u32 *)(hwpm->mem_bytes_kernel); *mem_bytes_kernel_u32 = TEGRA_SOC_HWPM_MEM_BYTES_INVALID; ret = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_CONTROL_USER_CH0, NV_PERF_PMASYS_CHANNEL_CONTROL_USER_UPDATE_BYTES, NV_PERF_PMASYS_CHANNEL_CONTROL_USER_UPDATE_BYTES_DOIT, false); if (ret < 0) { tegra_soc_hwpm_err("Failed to stream mem_bytes to buffer"); return -EIO; } } /* Read HW put pointer */ if (update_get_put->b_read_mem_head) { update_get_put->mem_head = hwpm_readl(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_MEM_HEAD_CH0); tegra_soc_hwpm_dbg("MEM_HEAD = 0x%llx", update_get_put->mem_head); } /* Check overflow error status */ if (update_get_put->b_check_overflow) { reg_val = hwpm_readl(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_STATUS_SECURE_CH0); field_val = HWPM_REG_V(NV_PERF_PMASYS_CHANNEL_STATUS_SECURE_MEMBUF_STATUS, reg_val); update_get_put->b_overflowed = (field_val == NV_PERF_PMASYS_CHANNEL_STATUS_SECURE_MEMBUF_STATUS_OVERFLOWED); tegra_soc_hwpm_dbg("OVERFLOWED = %u", update_get_put->b_overflowed); } return 0; } static long tegra_soc_hwpm_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; enum tegra_soc_hwpm_ioctl_num ioctl_num = _IOC_NR(cmd); u32 ioc_dir = _IOC_DIR(cmd); u32 arg_size = _IOC_SIZE(cmd); struct tegra_soc_hwpm *hwpm = NULL; void *arg_copy = NULL; if (!file) { tegra_soc_hwpm_err("Invalid file"); ret = -ENODEV; goto fail; } if ((_IOC_TYPE(cmd) != TEGRA_SOC_HWPM_IOC_MAGIC) || (ioctl_num < 0) || (ioctl_num >= TERGA_SOC_HWPM_NUM_IOCTLS)) { tegra_soc_hwpm_err("Unsupported IOCTL call"); ret = -EINVAL; goto fail; } if (arg_size != ioctls[ioctl_num].struct_size) { tegra_soc_hwpm_err("Invalid userspace struct"); ret = -EINVAL; goto fail; } hwpm = file->private_data; if (!hwpm) { tegra_soc_hwpm_err("Invalid hwpm struct"); ret = -ENODEV; goto fail; } /* Only allocate a buffer if the IOCTL needs a buffer */ if (!(ioc_dir & _IOC_NONE)) { arg_copy = kzalloc(arg_size, GFP_KERNEL); if (!arg_copy) { tegra_soc_hwpm_err("Can't allocate memory for kernel struct"); ret = -ENOMEM; goto fail; } } if (ioc_dir & _IOC_WRITE) { if (copy_from_user(arg_copy, (void __user *)arg, arg_size)) { tegra_soc_hwpm_err("Failed to copy data from userspace" " struct into kernel struct"); ret = -EFAULT; goto fail; } } /* * We don't goto fail here because even if the IOCTL fails, we have to * call copy_to_user() to pass back any valid output params to * userspace. */ ret = ioctls[ioctl_num].handler(hwpm, arg_copy); if (ioc_dir & _IOC_READ) { if (copy_to_user((void __user *)arg, arg_copy, arg_size)) { tegra_soc_hwpm_err("Failed to copy data from kernel" " struct into userspace struct"); ret = -EFAULT; goto fail; } } if (ret < 0) goto fail; tegra_soc_hwpm_dbg("The %s IOCTL completed successfully!", ioctls[ioctl_num].name); goto cleanup; fail: tegra_soc_hwpm_err("The %s IOCTL failed(%d)!", ioctls[ioctl_num].name, ret); cleanup: if (arg_copy) kfree(arg_copy); return ret; } static int tegra_soc_hwpm_open(struct inode *inode, struct file *filp) { int ret = 0; unsigned int minor = iminor(inode); struct tegra_soc_hwpm *hwpm = NULL; struct resource *res = NULL; u64 num_regs = 0; if (!inode) { tegra_soc_hwpm_err("Invalid inode"); return -EINVAL; } if (!filp) { tegra_soc_hwpm_err("Invalid file"); return -EINVAL; } if (minor > 0) { tegra_soc_hwpm_err("Incorrect minor number"); return -EBADFD; } hwpm = container_of(inode->i_cdev, struct tegra_soc_hwpm, cdev); if (!hwpm) { tegra_soc_hwpm_err("Invalid hwpm struct"); return -EINVAL; } filp->private_data = hwpm; /* FIXME: Enable clock and reset programming */ #if 0 ret = reset_control_assert(hwpm->hwpm_rst); if (ret < 0) { tegra_soc_hwpm_err("hwpm reset assert failed"); ret = -ENODEV; goto fail; } ret = reset_control_assert(hwpm->la_rst); if (ret < 0) { tegra_soc_hwpm_err("la reset assert failed"); ret = -ENODEV; goto fail; } ret = clk_prepare_enable(hwpm->la_clk); if (ret < 0) { tegra_soc_hwpm_err("la clock enable failed"); ret = -ENODEV; goto fail; } ret = reset_control_deassert(hwpm->la_rst); if (ret < 0) { tegra_soc_hwpm_err("la reset deassert failed"); ret = -ENODEV; goto fail; } ret = reset_control_deassert(hwpm->hwpm_rst); if (ret < 0) { tegra_soc_hwpm_err("hwpm reset deassert failed"); ret = -ENODEV; goto fail; } #endif /* Map PMA and RTR apertures */ hwpm->dt_apertures[TEGRA_SOC_HWPM_PMA_DT] = of_iomap(hwpm->np, TEGRA_SOC_HWPM_PMA_DT); if (!hwpm->dt_apertures[TEGRA_SOC_HWPM_PMA_DT]) { tegra_soc_hwpm_err("Couldn't map the PMA aperture"); ret = -ENOMEM; goto fail; } res = platform_get_resource(hwpm->pdev, IORESOURCE_MEM, TEGRA_SOC_HWPM_PMA_DT); if ((!res) || (res->start == 0) || (res->end == 0)) { tegra_soc_hwpm_err("Invalid resource for PMA"); ret = -ENOMEM; goto fail; } pma_map[1].start_pa = res->start; pma_map[1].end_pa = res->end; cmd_slice_rtr_map[0].start_pa = res->start; cmd_slice_rtr_map[0].end_pa = res->end; if (hwpm->fake_registers_enabled) { num_regs = (res->end + 1 - res->start) / sizeof(*pma_fake_regs); pma_fake_regs = (u32 *)kzalloc(sizeof(*pma_fake_regs) * num_regs, GFP_KERNEL); if (!pma_fake_regs) { tegra_soc_hwpm_err("Couldn't allocate memory for PMA" " fake registers"); ret = -ENOMEM; goto fail; } pma_map[1].fake_registers = pma_fake_regs; cmd_slice_rtr_map[0].fake_registers = pma_fake_regs; } hwpm->dt_apertures[TEGRA_SOC_HWPM_RTR_DT] = of_iomap(hwpm->np, TEGRA_SOC_HWPM_RTR_DT); if (!hwpm->dt_apertures[TEGRA_SOC_HWPM_RTR_DT]) { tegra_soc_hwpm_err("Couldn't map the RTR aperture"); ret = -ENOMEM; goto fail; } res = platform_get_resource(hwpm->pdev, IORESOURCE_MEM, TEGRA_SOC_HWPM_RTR_DT); if ((!res) || (res->start == 0) || (res->end == 0)) { tegra_soc_hwpm_err("Invalid resource for RTR"); ret = -ENOMEM; goto fail; } cmd_slice_rtr_map[1].start_pa = res->start; cmd_slice_rtr_map[1].end_pa = res->end; if (hwpm->fake_registers_enabled) { num_regs = (res->end + 1 - res->start) / sizeof(*cmd_slice_rtr_map[1].fake_registers); cmd_slice_rtr_map[1].fake_registers = (u32 *)kzalloc(sizeof(*cmd_slice_rtr_map[1].fake_registers) * num_regs, GFP_KERNEL); if (!cmd_slice_rtr_map[1].fake_registers) { tegra_soc_hwpm_err("Couldn't allocate memory for RTR" " fake registers"); ret = -ENOMEM; goto fail; } } /* FIXME: Remove after verification */ /* Disable SLCG */ ret = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CG2, NV_PERF_PMASYS_CG2_SLCG, NV_PERF_PMASYS_CG2_SLCG_DISABLED, false); if (ret < 0) { tegra_soc_hwpm_err("Unable to disable PMA SLCG"); ret = -EIO; goto fail; } ret = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_RTR_DT, NV_PERF_PMMSYS_SYS0ROUTER_CG2, NV_PERF_PMMSYS_SYS0ROUTER_CG2_SLCG, NV_PERF_PMMSYS_SYS0ROUTER_CG2_SLCG_DISABLED, false); if (ret < 0) { tegra_soc_hwpm_err("Unable to disable ROUTER SLCG"); ret = -EIO; goto fail; } /* Program PROD values */ ret = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CONTROLB, NV_PERF_PMASYS_CONTROLB_COALESCE_TIMEOUT_CYCLES, NV_PERF_PMASYS_CONTROLB_COALESCE_TIMEOUT_CYCLES__PROD, false); if (ret < 0) { tegra_soc_hwpm_err("Unable to program PROD value"); ret = -EIO; goto fail; } ret = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_CONFIG_USER_CH0, NV_PERF_PMASYS_CHANNEL_CONFIG_USER_COALESCE_TIMEOUT_CYCLES, NV_PERF_PMASYS_CHANNEL_CONFIG_USER_COALESCE_TIMEOUT_CYCLES__PROD, false); if (ret < 0) { tegra_soc_hwpm_err("Unable to program PROD value"); ret = -EIO; goto fail; } /* Initialize SW state */ hwpm->bind_completed = false; hwpm->full_wlist_size = -1; fail: if (hwpm->dt_apertures[TEGRA_SOC_HWPM_PMA_DT]) { iounmap(hwpm->dt_apertures[TEGRA_SOC_HWPM_PMA_DT]); hwpm->dt_apertures[TEGRA_SOC_HWPM_PMA_DT] = NULL; } pma_map[1].start_pa = 0; pma_map[1].end_pa = 0; cmd_slice_rtr_map[0].start_pa = 0; cmd_slice_rtr_map[0].end_pa = 0; if (pma_fake_regs) { kfree(pma_fake_regs); pma_fake_regs = NULL; pma_map[1].fake_registers = NULL; cmd_slice_rtr_map[0].fake_registers = NULL; } if (hwpm->dt_apertures[TEGRA_SOC_HWPM_RTR_DT]) { iounmap(hwpm->dt_apertures[TEGRA_SOC_HWPM_RTR_DT]); hwpm->dt_apertures[TEGRA_SOC_HWPM_RTR_DT] = NULL; } cmd_slice_rtr_map[1].start_pa = 0; cmd_slice_rtr_map[1].end_pa = 0; if (cmd_slice_rtr_map[1].fake_registers) { kfree(cmd_slice_rtr_map[1].fake_registers); cmd_slice_rtr_map[1].fake_registers = NULL; } return ret; } static ssize_t tegra_soc_hwpm_read(struct file *file, char __user *ubuf, size_t count, loff_t *offp) { return 0; } /* FIXME: Fix double release bug */ static int tegra_soc_hwpm_release(struct inode *inode, struct file *filp) { int err = 0; int ret = 0; bool timeout = false; int res_idx = 0; int aprt_idx = 0; u32 field_mask = 0; u32 field_val = 0; u32 *mem_bytes_kernel_u32 = NULL; struct tegra_soc_hwpm *hwpm = NULL; struct hwpm_resource_aperture *aperture = NULL; #define RELEASE_FAIL(msg, ...) \ do { \ if (err < 0) { \ tegra_soc_hwpm_err(msg, ##__VA_ARGS__); \ if (ret == 0) \ ret = err; \ } \ } while (0) if (!inode) { tegra_soc_hwpm_err("Invalid inode"); return -EINVAL; } if (!filp) { tegra_soc_hwpm_err("Invalid file"); return -EINVAL; } hwpm = container_of(inode->i_cdev, struct tegra_soc_hwpm, cdev); if (!hwpm) { tegra_soc_hwpm_err("Invalid hwpm struct"); return -EINVAL; } /* Disable PMA triggers */ err = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_TRIGGER_CONFIG_USER_CH0, NV_PERF_PMASYS_TRIGGER_CONFIG_USER_PMA_PULSE, NV_PERF_PMASYS_TRIGGER_CONFIG_USER_PMA_PULSE_DISABLE, false); RELEASE_FAIL("Unable to disable PMA triggers"); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_SYS_TRIGGER_START_MASK, 0); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_SYS_TRIGGER_START_MASKB, 0); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_SYS_TRIGGER_STOP_MASK, 0); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_SYS_TRIGGER_STOP_MASKB, 0); /* Wait for PERFMONs, ROUTER, and PMA to idle */ timeout = HWPM_TIMEOUT(HWPM_REG_CHECK_F(hwpm_readl(hwpm, TEGRA_SOC_HWPM_RTR_DT, NV_PERF_PMMSYS_SYS0ROUTER_PERFMONSTATUS), NV_PERF_PMMSYS_SYS0ROUTER_PERFMONSTATUS_MERGED, NV_PERF_PMMSYS_SYS0ROUTER_PERFMONSTATUS_MERGED_EMPTY), "NV_PERF_PMMSYS_SYS0ROUTER_PERFMONSTATUS_MERGED_EMPTY"); if (timeout && ret == 0) { ret = -EIO; } timeout = HWPM_TIMEOUT(HWPM_REG_CHECK_F(hwpm_readl(hwpm, TEGRA_SOC_HWPM_RTR_DT, NV_PERF_PMMSYS_SYS0ROUTER_ENGINESTATUS), NV_PERF_PMMSYS_SYS0ROUTER_ENGINESTATUS_STATUS, NV_PERF_PMMSYS_SYS0ROUTER_ENGINESTATUS_STATUS_EMPTY), "NV_PERF_PMMSYS_SYS0ROUTER_ENGINESTATUS_STATUS_EMPTY"); if (timeout && ret == 0) { ret = -EIO; } field_mask = NV_PERF_PMASYS_ENGINESTATUS_STATUS_MASK | NV_PERF_PMASYS_ENGINESTATUS_RBUFEMPTY_MASK; field_val = HWPM_REG_F(NV_PERF_PMASYS_ENGINESTATUS_STATUS, NV_PERF_PMASYS_ENGINESTATUS_STATUS_EMPTY); field_val |= HWPM_REG_F(NV_PERF_PMASYS_ENGINESTATUS_RBUFEMPTY, NV_PERF_PMASYS_ENGINESTATUS_RBUFEMPTY_EMPTY); timeout = HWPM_TIMEOUT(HWPM_REG_CHECK(hwpm_readl(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_ENGINESTATUS), field_mask, field_val), "NV_PERF_PMASYS_ENGINESTATUS"); if (timeout && ret == 0) { ret = -EIO; } /* Disable all PERFMONs */ tegra_soc_hwpm_dbg("Disabling PERFMONs"); for (res_idx = 0; res_idx < TERGA_SOC_HWPM_NUM_RESOURCES; res_idx++) { if (!hwpm_resources[res_idx].reserved) continue; tegra_soc_hwpm_dbg("Found reserved IP(%d)", res_idx); for (aprt_idx = 0; aprt_idx < hwpm_resources[res_idx].map_size; aprt_idx++) { aperture = &(hwpm_resources[res_idx].map[aprt_idx]); if (IS_PERFMON(aperture->dt_aperture)) { tegra_soc_hwpm_dbg("Found PERFMON(0x%llx - 0x%llx)", aperture->start_pa, aperture->end_pa); err = DRIVER_REG_RMW(hwpm, aperture->dt_aperture, NV_PERF_PMMSYS_CONTROL, NV_PERF_PMMSYS_CONTROL_MODE, NV_PERF_PMMSYS_CONTROL_MODE_DISABLE, false); RELEASE_FAIL("Unable to disable PERFMON(0x%llx - 0x%llx)", aperture->start_pa, aperture->end_pa); } } } /* Stream MEM_BYTES to clear pipeline */ if (hwpm->mem_bytes_kernel) { mem_bytes_kernel_u32 = (u32 *)(hwpm->mem_bytes_kernel); *mem_bytes_kernel_u32 = TEGRA_SOC_HWPM_MEM_BYTES_INVALID; err = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_CONTROL_USER_CH0, NV_PERF_PMASYS_CHANNEL_CONTROL_USER_UPDATE_BYTES, NV_PERF_PMASYS_CHANNEL_CONTROL_USER_UPDATE_BYTES_DOIT, false); RELEASE_FAIL("Unable to stream MEM_BYTES"); timeout = HWPM_TIMEOUT(*mem_bytes_kernel_u32 != TEGRA_SOC_HWPM_MEM_BYTES_INVALID, "MEM_BYTES streaming"); if (timeout && ret == 0) ret = -EIO; } /* Disable PMA streaming */ err = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_TRIGGER_CONFIG_USER_CH0, NV_PERF_PMASYS_TRIGGER_CONFIG_USER_RECORD_STREAM, NV_PERF_PMASYS_TRIGGER_CONFIG_USER_RECORD_STREAM_DISABLE, false); RELEASE_FAIL("Unable to disable PMA streaming"); err = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_CONTROL_USER_CH0, NV_PERF_PMASYS_CHANNEL_CONTROL_USER_STREAM, NV_PERF_PMASYS_CHANNEL_CONTROL_USER_STREAM_DISABLE, false); RELEASE_FAIL("Unable to disable PMA streaming"); /* Memory Management */ hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_OUTBASE_CH0, 0); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_OUTBASEUPPER_CH0, 0); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_OUTSIZE_CH0, 0); hwpm_writel(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CHANNEL_MEM_BYTES_ADDR_CH0, 0); if (hwpm->stream_sgt && (!IS_ERR(hwpm->stream_sgt))) { dma_buf_unmap_attachment(hwpm->stream_attach, hwpm->stream_sgt, DMA_FROM_DEVICE); } hwpm->stream_sgt = NULL; if (hwpm->stream_attach && (!IS_ERR(hwpm->stream_attach))) { dma_buf_detach(hwpm->stream_dma_buf, hwpm->stream_attach); } hwpm->stream_attach = NULL; if (hwpm->stream_dma_buf && (!IS_ERR(hwpm->stream_dma_buf))) { dma_buf_put(hwpm->stream_dma_buf); } hwpm->stream_dma_buf = NULL; if (hwpm->mem_bytes_kernel) { dma_buf_vunmap(hwpm->mem_bytes_dma_buf, hwpm->mem_bytes_kernel); hwpm->mem_bytes_kernel = NULL; } if (hwpm->mem_bytes_sgt && (!IS_ERR(hwpm->mem_bytes_sgt))) { dma_buf_unmap_attachment(hwpm->mem_bytes_attach, hwpm->mem_bytes_sgt, DMA_FROM_DEVICE); } hwpm->mem_bytes_sgt = NULL; if (hwpm->mem_bytes_attach && (!IS_ERR(hwpm->mem_bytes_attach))) { dma_buf_detach(hwpm->mem_bytes_dma_buf, hwpm->mem_bytes_attach); } hwpm->mem_bytes_attach = NULL; if (hwpm->mem_bytes_dma_buf && (!IS_ERR(hwpm->mem_bytes_dma_buf))) { dma_buf_put(hwpm->mem_bytes_dma_buf); } hwpm->mem_bytes_dma_buf = NULL; /* FIXME: Enable clock and reset programming */ /* FIXME: Tell IPs which are being profiled to re-enable power management */ #if 0 err = reset_control_assert(hwpm->hwpm_rst); RELEASE_FAIL("hwpm reset assert failed"); err = reset_control_assert(hwpm->la_rst); RELEASE_FAIL("la reset assert failed"); clk_disable_unprepare(hwpm->la_clk); #endif /* FIXME: Remove after verification */ /* Enable SLCG */ err = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_PMA_DT, NV_PERF_PMASYS_CG2, NV_PERF_PMASYS_CG2_SLCG, NV_PERF_PMASYS_CG2_SLCG_ENABLED, false); RELEASE_FAIL("Unable to enable PMA SLCG"); err = DRIVER_REG_RMW(hwpm, TEGRA_SOC_HWPM_RTR_DT, NV_PERF_PMMSYS_SYS0ROUTER_CG2, NV_PERF_PMMSYS_SYS0ROUTER_CG2_SLCG, NV_PERF_PMMSYS_SYS0ROUTER_CG2_SLCG_ENABLED, false); RELEASE_FAIL("Unable to enable ROUTER SLCG"); /* Unmap PMA and RTR apertures */ tegra_soc_hwpm_dbg("Unmapping apertures"); if (hwpm->dt_apertures[TEGRA_SOC_HWPM_PMA_DT]) { iounmap(hwpm->dt_apertures[TEGRA_SOC_HWPM_PMA_DT]); hwpm->dt_apertures[TEGRA_SOC_HWPM_PMA_DT] = NULL; } pma_map[1].start_pa = 0; pma_map[1].end_pa = 0; cmd_slice_rtr_map[0].start_pa = 0; cmd_slice_rtr_map[0].end_pa = 0; if (pma_fake_regs) { kfree(pma_fake_regs); pma_fake_regs = NULL; pma_map[1].fake_registers = NULL; cmd_slice_rtr_map[0].fake_registers = NULL; } if (hwpm->dt_apertures[TEGRA_SOC_HWPM_RTR_DT]) { iounmap(hwpm->dt_apertures[TEGRA_SOC_HWPM_RTR_DT]); hwpm->dt_apertures[TEGRA_SOC_HWPM_RTR_DT] = NULL; } cmd_slice_rtr_map[1].start_pa = 0; cmd_slice_rtr_map[1].end_pa = 0; if (cmd_slice_rtr_map[1].fake_registers) { kfree(cmd_slice_rtr_map[1].fake_registers); cmd_slice_rtr_map[1].fake_registers = NULL; } /* Reset resource and aperture state */ for (res_idx = 0; res_idx < TERGA_SOC_HWPM_NUM_RESOURCES; res_idx++) { if (!hwpm_resources[res_idx].reserved) continue; tegra_soc_hwpm_dbg("Found reserved IP(%d)", res_idx); hwpm_resources[res_idx].reserved = false; for (aprt_idx = 0; aprt_idx < hwpm_resources[res_idx].map_size; aprt_idx++) { aperture = &(hwpm_resources[res_idx].map[aprt_idx]); if ((aperture->dt_aperture == TEGRA_SOC_HWPM_PMA_DT) || (aperture->dt_aperture == TEGRA_SOC_HWPM_RTR_DT)) { /* PMA and RTR apertures are handled separately */ continue; } else if (IS_PERFMON(aperture->dt_aperture)) { if (hwpm->dt_apertures[aperture->dt_aperture]) { iounmap(hwpm->dt_apertures[aperture->dt_aperture]); hwpm->dt_apertures[aperture->dt_aperture] = NULL; } aperture->start_pa = 0; aperture->end_pa = 0; if (aperture->fake_registers) { kfree(aperture->fake_registers); aperture->fake_registers = NULL; } } else { /* IP apertures */ if (aperture->fake_registers) { kfree(aperture->fake_registers); aperture->fake_registers = NULL; set_mc_fake_regs(hwpm, aperture, true); } } } } return ret; } /* File ops for device node */ const struct file_operations tegra_soc_hwpm_ops = { .owner = THIS_MODULE, .open = tegra_soc_hwpm_open, .read = tegra_soc_hwpm_read, .release = tegra_soc_hwpm_release, .unlocked_ioctl = tegra_soc_hwpm_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = tegra_soc_hwpm_ioctl, #endif };