/* * Copyright (c) 2017-2018, 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. */ #define pr_fmt(fmt) "%s(): " fmt, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MASK GENMASK(15, 12) #define T19x_CACHE_STR "l3_cache" #define CCPLEX_CC_GPU_ONLY_BITS_SHIFT 8 #define L3_WAYS_MASK GENMASK(4, 0) #define PROFILE_DCACHE_FLUSH 0 #ifdef CONFIG_DEBUG_FS static struct dentry *t19x_l3cache_root; #endif struct cache_drv_data { struct platform_device *pdev; struct miscdevice dev_user; struct tegra_l3_ioctl_data ioctl_data; }; static struct cache_drv_data *cache_data; int t19x_flush_cache_all(void) { u64 id_afr0; u64 ret; u64 retry = 10; asm volatile ("mrs %0, ID_AFR0_EL1" : "=r"(id_afr0)); /* check if cache flush through mts is supported */ if (!likely(id_afr0 & MASK)) { pr_warn("SCF cache flush all is not supported in MTS\n"); return -ENOTSUPP; } do { asm volatile ("mrs %0, s3_0_c15_c3_7" : "=r" (ret)); } while (!ret && retry--); asm volatile ("dsb sy"); if (!ret) { WARN_ONCE(!ret, "%s failed\n", __func__); pr_err("SCF cache flush all: instruction error\n"); return -EINVAL; } return 0; } #if PROFILE_DCACHE_FLUSH struct flush_error { u64 run_time; u64 ret; }; static int __t19x_flush_dcache_all(void) { #define FLUSH_TO_IN_MS 1000 #define RETRY 10 static struct flush_error fe[RETRY] = { { 0 } }; ktime_t kt, tt = { 0 }; u64 id_afr0; u64 i = 0; u64 ret; asm volatile ("mrs %0, ID_AFR0_EL1" : "=r"(id_afr0)); /* check if dcache flush through mts is supported */ if (!likely(id_afr0 & MASK)) { pr_warn("SCF dcache flush is not supported in MTS\n"); return -ENOTSUPP; } do { kt = ktime_get(); asm volatile ("mrs %0, s3_0_c15_c3_6" : "=r" (ret)); kt = ktime_sub(ktime_get(), kt); fe[i].run_time = ktime_to_ns(kt); fe[i].ret = ret; i++; tt = ktime_add(tt, kt); if (ktime_to_ms(tt) >= FLUSH_TO_IN_MS) break; udelay(1); } while (!ret); asm volatile ("dsb sy"); i = 0; if (!ret) { pr_err("SCF dcache flush: instruction error\n"); while (fe[i].run_time) { pr_err("flush_dcache_all_exec_time[%llu]:%lluns\n", i, fe[i].run_time); pr_err("flush_dcache_all_return[%llu]:%llu\n", i, fe[i].ret); i++; } WARN_ONCE(!ret, "%s failed\n", __func__); return -EINVAL; } return 0; } #endif int t19x_flush_dcache_all(void) { #if PROFILE_DCACHE_FLUSH return __t19x_flush_dcache_all(); #else u64 id_afr0; u64 ret; u64 retry = 10; asm volatile ("mrs %0, ID_AFR0_EL1" : "=r"(id_afr0)); /* check if dcache flush through mts is supported */ if (!likely(id_afr0 & MASK)) { pr_warn("SCF dcache flush is not supported in MTS\n"); return -ENOTSUPP; } do { asm volatile ("mrs %0, s3_0_c15_c3_6" : "=r" (ret)); udelay(1); } while (!ret && retry--); asm volatile ("dsb sy"); if (!ret) { WARN_ONCE(!ret, "%s failed\n", __func__); pr_err("SCF dcache flush: instruction error\n"); return -EINVAL; } return 0; #endif } int t19x_clean_dcache_all(void) { u64 id_afr0; u64 ret; u64 retry = 10; asm volatile ("mrs %0, ID_AFR0_EL1" : "=r"(id_afr0)); /* check if dcache clean through mts is supported */ if (!likely(id_afr0 & MASK)) { pr_err("SCF dcache clean is not supported in MTS\n"); return -ENOTSUPP; } do { asm volatile ("mrs %0, s3_0_c15_c3_5" : "=r" (ret)); } while (!ret && retry--); asm volatile ("dsb sy"); if (!ret) { WARN_ONCE(!ret, "%s failed\n", __func__); pr_err("SCF dcache clean: instruction error\n"); return -EINVAL; } return 0; } static int t19x_extract_l3_cache_ways(struct device *dev) { u64 nvg_data; u32 *gpu_cpu_ways = &cache_data->ioctl_data.igpu_cpu_ways; u32 *gpu_only_ways = &cache_data->ioctl_data.igpu_only_ways; u32 dt_gpu_cpu_ways = *gpu_cpu_ways; u32 dt_gpu_only_ways = *gpu_only_ways; if (tegra_mce_read_l3_cache_ways(&nvg_data)) { dev_err(dev, "Reading L3 cache ways is not supported in this platform\n"); return -ENOTSUPP; } *gpu_cpu_ways = nvg_data & L3_WAYS_MASK; *gpu_only_ways = (nvg_data >> CCPLEX_CC_GPU_ONLY_BITS_SHIFT) & L3_WAYS_MASK; if ((dt_gpu_cpu_ways != *gpu_cpu_ways) || (dt_gpu_only_ways != *gpu_only_ways)) { dev_info(dev, "L3 ways information mismatch " "between HW and DT!\n" "DT: %d %d HW: %d %d\n", dt_gpu_cpu_ways, dt_gpu_only_ways, *gpu_cpu_ways, *gpu_only_ways); } return 0; } static int t19x_set_l3_cache_ways(u32 gpu_cpu_ways, u32 gpu_only_ways) { struct device *dev = &cache_data->pdev->dev; u64 nvg_data; u64 ret; if ((gpu_only_ways > cache_data->ioctl_data.total_ways) || (gpu_cpu_ways > cache_data->ioctl_data.total_ways)) { dev_err(dev, "gpu_cpu_ways:%u or gpu_only_ways:%u" "exceeds total_ways:%u!!\n", gpu_cpu_ways, gpu_only_ways, cache_data->ioctl_data.total_ways); return -EINVAL; } if (gpu_cpu_ways < gpu_only_ways) { dev_err(dev, "gpu_cpu_ways:%u is smaller than gpu_only_ways:%u\n", gpu_cpu_ways, gpu_only_ways); return -EINVAL; } gpu_only_ways <<= CCPLEX_CC_GPU_ONLY_BITS_SHIFT; nvg_data = gpu_cpu_ways | gpu_only_ways; if (tegra_mce_write_l3_cache_ways(nvg_data, &ret)) { dev_err(dev, "Writing L3 cache ways is not supported in this platform\n"); return -ENOTSUPP; } if (ret != nvg_data) { dev_err(dev, "CCPLEX_CACHE_CONTROL contents are not updated!!\n"); return -ENODEV; } return 0; } static ssize_t gpu_only_ways_show(struct device *device, struct device_attribute *attr, char *buf) { struct cache_drv_data *pdata; t19x_extract_l3_cache_ways(device); pdata = dev_get_drvdata(device); return sprintf(buf, "%u\n", pdata->ioctl_data.igpu_only_ways); } static ssize_t gpu_only_ways_store(struct device *device, struct device_attribute *attr, const char *buf, size_t count) { struct cache_drv_data *pdata; u32 new_gpu_only_ways; u32 gpu_cpu_ways; if (is_tegra_hypervisor_mode()) return -EPERM; if (kstrtou32(buf, 0, &new_gpu_only_ways) < 0) return -EINVAL; t19x_extract_l3_cache_ways(device); pdata = dev_get_drvdata(device); gpu_cpu_ways = pdata->ioctl_data.igpu_cpu_ways; t19x_set_l3_cache_ways(gpu_cpu_ways, new_gpu_only_ways); return count; } static ssize_t gpu_cpu_ways_show(struct device *device, struct device_attribute *attr, char *buf) { struct cache_drv_data *pdata; t19x_extract_l3_cache_ways(device); pdata = dev_get_drvdata(device); return sprintf(buf, "%u\n", pdata->ioctl_data.igpu_cpu_ways); } static ssize_t gpu_cpu_ways_store(struct device *device, struct device_attribute *attr, const char *buf, size_t count) { struct cache_drv_data *pdata; u32 new_gpu_cpu_ways; u32 gpu_only_ways; if (is_tegra_hypervisor_mode()) return -EPERM; if (kstrtou32(buf, 0, &new_gpu_cpu_ways) < 0) return -EINVAL; t19x_extract_l3_cache_ways(device); pdata = dev_get_drvdata(device); gpu_only_ways = pdata->ioctl_data.igpu_only_ways; t19x_set_l3_cache_ways(new_gpu_cpu_ways, gpu_only_ways); return count; } static DEVICE_ATTR(l3_gpu_only_ways, 0644, gpu_only_ways_show, gpu_only_ways_store); static DEVICE_ATTR(l3_gpu_cpu_ways, 0644, gpu_cpu_ways_show, gpu_cpu_ways_store); static int t19x_cache_sysfs_create(struct device *dev) { int err; err = device_create_file(dev, &dev_attr_l3_gpu_only_ways); if (err) { dev_warn(dev, "Couldn't create gpu_only_ways attribute err%d\n", err); return err; } err = device_create_file(dev, &dev_attr_l3_gpu_cpu_ways); if (err) { dev_warn(dev, "Couldn't create gpu_cpu_ways attribute err%d\n", err); device_remove_file(dev, &dev_attr_l3_gpu_only_ways); return err; } return 0; } static void t19x_cache_sysfs_remove(struct device *dev) { device_remove_file(dev, &dev_attr_l3_gpu_only_ways); device_remove_file(dev, &dev_attr_l3_gpu_cpu_ways); } static int t19x_parse_dt(void) { struct device *dev = &cache_data->pdev->dev; struct device_node *np = dev->of_node; struct tegra_l3_ioctl_data *idata = &cache_data->ioctl_data; int ret = 0; if (!np) { dev_err(dev, "Did not find device node\n"); return -EINVAL; } if (!of_device_is_available(np)) { dev_err(dev, "device is disabled\n"); return -ENODEV; } ret = of_property_read_u32_array(np, "l3-gpu-cpu-ways", &idata->igpu_cpu_ways, 1); if (ret) { dev_err(dev, "Did not find l3-gpu-cpu-ways property\n"); return ret; } ret = of_property_read_u32_array(np, "l3-gpu-only-ways", &idata->igpu_only_ways, 1); if (ret) { dev_err(dev, "Did not find l3-gpu-only-ways property\n"); return ret; } ret = of_property_read_u32_array(np, "l3-total-ways", &idata->total_ways, 1); if (ret) { dev_err(dev, "Did not find l3-total-ways property\n"); return ret; } ret = of_property_read_u64(np, "l3-size", &idata->size); if (ret) { dev_err(dev, "Did not find l3-size property\n"); return ret; } return ret; } static int t19x_cache_open(struct inode *inode, struct file *filp) { int err = 0; err = nonseekable_open(inode, filp); if (unlikely(err)) return err; return err; } static int t19x_cache_release(struct inode *inode, struct file *filp) { return 0; } static int get_l3_data(void *arg) { struct tegra_l3_ioctl_data *in_data = (struct tegra_l3_ioctl_data *)arg; memcpy(in_data, &cache_data->ioctl_data, sizeof(struct tegra_l3_ioctl_data)); return 0; } static long t19x_cache_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { u8 buf[TEGRA_L3_CACHE_IOCTL_MAX_ARG_SIZE] __aligned(sizeof(u64)); struct device *dev = &cache_data->pdev->dev; void __user *uarg = (void __user *)arg; int err = 0; /* check for valid IOCTL cmd */ if ((_IOC_TYPE(cmd) != TEGRA_L3_CACHE_IOC_MAGIC) || (_IOC_NR(cmd) == _IOC_NR(0)) || (_IOC_NR(cmd) > TEGRA_L3_CACHE_IOCTL_IOC_MAXNR) || (_IOC_SIZE(cmd) > TEGRA_L3_CACHE_IOCTL_MAX_ARG_SIZE)) { return -ENOIOCTLCMD; } if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, uarg, _IOC_SIZE(cmd)); if (!err && (_IOC_DIR(cmd) & _IOC_WRITE)) err = !access_ok(VERIFY_READ, uarg, _IOC_SIZE(cmd)); if (err) return -EFAULT; if (_IOC_DIR(cmd) & _IOC_WRITE) { if (copy_from_user(buf, (void __user *)arg, _IOC_SIZE(cmd))) return -EFAULT; } switch (cmd) { case TEGRA_L3_CACHE_GET_IOCTL_DATA: err = get_l3_data(buf); break; default: dev_err(dev, "Unknown NVMAP_IOC = 0x%x\n", cmd); return -ENOIOCTLCMD; } if ((err == 0) && (_IOC_DIR(cmd) & _IOC_READ)) err = copy_to_user((void __user *)arg, buf, _IOC_SIZE(cmd)); return err; } #ifdef CONFIG_DEBUG_FS #define RW_MODE (S_IWUSR | S_IRUGO) #define RO_MODE (S_IRUGO) static int get_gpu_cpu_ways(void *data, u64 *val) { *val = cache_data->ioctl_data.igpu_cpu_ways; return 0; } static int set_gpu_cpu_ways(void *data, u64 val) { u32 igpu_cpu_ways = (u32)val; int ret = 0; if (is_tegra_hypervisor_mode()) return -EPERM; if (igpu_cpu_ways != cache_data->ioctl_data.igpu_cpu_ways) { ret = t19x_set_l3_cache_ways(igpu_cpu_ways, cache_data->ioctl_data.igpu_only_ways); if (!ret) cache_data->ioctl_data.igpu_cpu_ways = igpu_cpu_ways; } return ret; } DEFINE_SIMPLE_ATTRIBUTE(gpu_cpu_ways_ops, get_gpu_cpu_ways, set_gpu_cpu_ways, "%llu\n"); static int get_gpu_only_ways(void *data, u64 *val) { *val = cache_data->ioctl_data.igpu_only_ways; return 0; } static int set_gpu_only_ways(void *data, u64 val) { u32 igpu_only_ways = (u32)val; int ret = 0; if (is_tegra_hypervisor_mode()) return -EPERM; if (igpu_only_ways != cache_data->ioctl_data.igpu_only_ways) { ret = t19x_set_l3_cache_ways( cache_data->ioctl_data.igpu_cpu_ways, igpu_only_ways); if (!ret) cache_data->ioctl_data.igpu_only_ways = igpu_only_ways; } return ret; } DEFINE_SIMPLE_ATTRIBUTE(gpu_only_ways_ops, get_gpu_only_ways, set_gpu_only_ways, "%llu\n"); static int get_total_ways(void *data, u64 *val) { *val = cache_data->ioctl_data.total_ways; return 0; } DEFINE_SIMPLE_ATTRIBUTE(total_ways_ops, get_total_ways, NULL, "%llu\n"); static int get_l3_cache_size(void *data, u64 *val) { *val = cache_data->ioctl_data.size; return 0; } DEFINE_SIMPLE_ATTRIBUTE(size_ops, get_l3_cache_size, NULL, "%llu\n"); static int __init t19x_cache_debug_init(void) { struct dentry *dir; uint8_t buff[15]; snprintf(buff, sizeof(buff), T19x_CACHE_STR); dir = debugfs_create_dir(buff, t19x_l3cache_root); if (!dir) goto err_out; if (!debugfs_create_file("gpu_cpu_ways", RW_MODE, dir, NULL, &gpu_cpu_ways_ops)) goto err_out; if (!debugfs_create_file("gpu_only_ways", RW_MODE, dir, NULL, &gpu_only_ways_ops)) goto err_out; if (!debugfs_create_file("total_ways", RO_MODE, dir, NULL, &total_ways_ops)) goto err_out; if (!debugfs_create_file("size", RO_MODE, dir, NULL, &size_ops)) goto err_out; return 0; err_out: debugfs_remove_recursive(t19x_l3cache_root); return -EINVAL; } static void t19x_cache_debug_exit(void) { debugfs_remove_recursive(t19x_l3cache_root); } #endif /* End of debug fs */ static const struct file_operations t19x_cache_user_fops = { .owner = THIS_MODULE, .open = t19x_cache_open, .release = t19x_cache_release, .unlocked_ioctl = t19x_cache_ioctl, #ifdef CONFIG_COMPAT .compat_ioctl = t19x_cache_ioctl, #endif }; static int __init t19x_cache_probe(struct platform_device *pdev) { struct cache_drv_data *drv_data; struct device *dev = &pdev->dev; int ret = 0; if (!tegra_platform_is_silicon()) { dev_err(dev, "is not supported on this platform\n"); return -EPERM; } dev_dbg(dev, "in probe()...\n"); drv_data = devm_kzalloc(dev, sizeof(*drv_data), GFP_KERNEL); if (!drv_data) return -ENOMEM; platform_set_drvdata(pdev, drv_data); drv_data->pdev = pdev; drv_data->dev_user.minor = MISC_DYNAMIC_MINOR; drv_data->dev_user.name = "l3cache"; drv_data->dev_user.fops = &t19x_cache_user_fops; drv_data->dev_user.parent = &pdev->dev; cache_data = drv_data; ret = misc_register(&drv_data->dev_user); if (ret) { dev_err(dev, "unable to register miscdevice %s\n", drv_data->dev_user.name); goto err_out; } ret = t19x_parse_dt(); if (ret) goto err_out; if (is_tegra_hypervisor_mode()) { ret = t19x_extract_l3_cache_ways(dev); if (ret) { dev_err(dev, "Could not extract gpu_cpu_ways from L3\n"); goto err_out; } } else { ret = t19x_set_l3_cache_ways(cache_data->ioctl_data.igpu_cpu_ways, cache_data->ioctl_data.igpu_only_ways); if (ret) { dev_err(dev, "Could not set gpu_cpu_ways in L3\n"); goto err_out; } } ret = t19x_cache_sysfs_create(dev); if (ret) { dev_err(dev, "Failed to create sysfs attributes!\n"); goto err_out; } #ifdef CONFIG_DEBUG_FS ret = t19x_cache_debug_init(); if (ret) { dev_err(dev, "Could not init debugfs\n"); goto err_out; } #endif dev_notice(dev, "probed\n"); return 0; err_out: dev_info(dev, "failed\n"); if (drv_data->dev_user.minor != MISC_DYNAMIC_MINOR) misc_deregister(&drv_data->dev_user); devm_kfree(dev, drv_data); return ret; } static int __exit t19x_cache_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; t19x_cache_sysfs_remove(dev); misc_deregister(&cache_data->dev_user); devm_kfree(dev, cache_data); #ifdef CONFIG_DEBUG_FS t19x_cache_debug_exit(); #endif return 0; } #ifdef CONFIG_OF static const struct of_device_id cache_of_match[] = { { .compatible = "nvidia,t19x-cache", .data = NULL, }, {}, }; #endif static struct platform_driver t19x_cache_driver __refdata = { .driver = { .name = "t19x_cache", .owner = THIS_MODULE, .of_match_table = of_match_ptr(cache_of_match), }, .probe = t19x_cache_probe, .remove = t19x_cache_remove, }; module_platform_driver(t19x_cache_driver); MODULE_DESCRIPTION("T19x Cache operations registration"); MODULE_AUTHOR("Sri Krishna chowdary K "); MODULE_AUTHOR("Puneet Saxena "); MODULE_LICENSE("GPL v2");