/* Copyright (c) 2013-2014, 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 #include #include #include #include #include #include #include #include #include #include struct msm_thermal_ioctl_dev { struct semaphore sem; struct cdev char_dev; }; static int msm_thermal_major; static struct class *thermal_class; static struct msm_thermal_ioctl_dev *msm_thermal_dev; static unsigned int freq_table_len[NR_CPUS], freq_table_set[NR_CPUS]; static unsigned int *freq_table_ptr[NR_CPUS]; static DEFINE_MUTEX(ioctl_access_mutex); static int msm_thermal_ioctl_open(struct inode *node, struct file *filep) { int ret = 0; struct msm_thermal_ioctl_dev *dev; dev = container_of(node->i_cdev, struct msm_thermal_ioctl_dev, char_dev); filep->private_data = dev; return ret; } static int msm_thermal_ioctl_release(struct inode *node, struct file *filep) { pr_debug("%s: IOCTL: release\n", KBUILD_MODNAME); return 0; } static long validate_and_copy(unsigned int *cmd, unsigned long *arg, struct msm_thermal_ioctl *query) { long ret = 0, err_val = 0; if ((_IOC_TYPE(*cmd) != MSM_THERMAL_MAGIC_NUM) || (_IOC_NR(*cmd) >= MSM_CMD_MAX_NR)) { ret = -ENOTTY; goto validate_exit; } if (_IOC_DIR(*cmd) & _IOC_READ) { err_val = !access_ok(VERIFY_WRITE, (void __user *)*arg, _IOC_SIZE(*cmd)); } else if (_IOC_DIR(*cmd) & _IOC_WRITE) { err_val = !access_ok(VERIFY_READ, (void __user *)*arg, _IOC_SIZE(*cmd)); } if (err_val) { ret = -EFAULT; goto validate_exit; } if (copy_from_user(query, (void __user *)(*arg), sizeof(struct msm_thermal_ioctl))) { ret = -EACCES; goto validate_exit; } if (query->size != sizeof(struct msm_thermal_ioctl)) { pr_err("%s: Invalid input argument size\n", __func__); ret = -EINVAL; goto validate_exit; } switch (*cmd) { case MSM_THERMAL_SET_CPU_MAX_FREQUENCY: case MSM_THERMAL_SET_CPU_MIN_FREQUENCY: if (query->cpu_freq.cpu_num >= num_possible_cpus()) { pr_err("%s: Invalid CPU number: %u\n", __func__, query->cpu_freq.cpu_num); ret = -EINVAL; goto validate_exit; } break; default: break; } validate_exit: return ret; } static long msm_thermal_process_freq_table_req(struct msm_thermal_ioctl *query, unsigned long *arg) { long ret = 0; uint32_t table_idx, idx = 0, cluster_id = query->clock_freq.cluster_num; struct clock_plan_arg *clock_freq = &(query->clock_freq); if (cluster_id >= num_possible_cpus()) return -EINVAL; if (!freq_table_len[cluster_id]) { ret = msm_thermal_get_freq_plan_size(cluster_id, &freq_table_len[cluster_id]); if (ret) { pr_err("%s: Cluster%d freq table length get err:%ld\n", KBUILD_MODNAME, cluster_id, ret); goto process_freq_exit; } if (!freq_table_len[cluster_id]) { pr_err("%s: Cluster%d freq table empty\n", KBUILD_MODNAME, cluster_id); ret = -EAGAIN; goto process_freq_exit; } freq_table_set[cluster_id] = freq_table_len[cluster_id] / MSM_IOCTL_FREQ_SIZE; if (freq_table_len[cluster_id] % MSM_IOCTL_FREQ_SIZE) freq_table_set[cluster_id]++; if (!freq_table_ptr[cluster_id]) { freq_table_ptr[cluster_id] = kzalloc( sizeof(unsigned int) * freq_table_len[cluster_id], GFP_KERNEL); if (!freq_table_ptr[cluster_id]) { pr_err("%s: memory alloc failed\n", KBUILD_MODNAME); freq_table_len[cluster_id] = 0; ret = -ENOMEM; goto process_freq_exit; } } ret = msm_thermal_get_cluster_freq_plan(cluster_id, freq_table_ptr[cluster_id]); if (ret) { pr_err("%s: Error getting frequency table. err:%ld\n", KBUILD_MODNAME, ret); freq_table_len[cluster_id] = 0; freq_table_set[cluster_id] = 0; kfree(freq_table_ptr[cluster_id]); freq_table_ptr[cluster_id] = NULL; goto process_freq_exit; } } if (!clock_freq->freq_table_len) { clock_freq->freq_table_len = freq_table_len[cluster_id]; goto copy_and_return; } if (clock_freq->set_idx >= freq_table_set[cluster_id]) { pr_err("%s: Invalid freq table set%d for cluster%d\n", KBUILD_MODNAME, clock_freq->set_idx, cluster_id); ret = -EINVAL; goto process_freq_exit; } table_idx = MSM_IOCTL_FREQ_SIZE * clock_freq->set_idx; for (; table_idx < freq_table_len[cluster_id] && idx < MSM_IOCTL_FREQ_SIZE; idx++, table_idx++) { clock_freq->freq_table[idx] = freq_table_ptr[cluster_id][table_idx]; } clock_freq->freq_table_len = idx; copy_and_return: ret = copy_to_user((void __user *)(*arg), query, sizeof(struct msm_thermal_ioctl)); if (ret) { pr_err("%s: copy_to_user error:%ld.\n", KBUILD_MODNAME, ret); goto process_freq_exit; } process_freq_exit: return ret; } static long msm_thermal_ioctl_process(struct file *filep, unsigned int cmd, unsigned long arg) { long ret = 0; struct msm_thermal_ioctl query; pr_debug("%s: IOCTL: processing cmd:%u\n", KBUILD_MODNAME, cmd); ret = validate_and_copy(&cmd, &arg, &query); if (ret) return ret; mutex_lock(&ioctl_access_mutex); switch (cmd) { case MSM_THERMAL_SET_CPU_MAX_FREQUENCY: ret = msm_thermal_set_frequency(query.cpu_freq.cpu_num, query.cpu_freq.freq_req, true); break; case MSM_THERMAL_SET_CPU_MIN_FREQUENCY: ret = msm_thermal_set_frequency(query.cpu_freq.cpu_num, query.cpu_freq.freq_req, false); break; case MSM_THERMAL_SET_CLUSTER_MAX_FREQUENCY: ret = msm_thermal_set_cluster_freq(query.cpu_freq.cpu_num, query.cpu_freq.freq_req, true); break; case MSM_THERMAL_SET_CLUSTER_MIN_FREQUENCY: ret = msm_thermal_set_cluster_freq(query.cpu_freq.cpu_num, query.cpu_freq.freq_req, false); break; case MSM_THERMAL_GET_CLUSTER_FREQUENCY_PLAN: ret = msm_thermal_process_freq_table_req(&query, &arg); break; default: ret = -ENOTTY; goto process_exit; } process_exit: mutex_unlock(&ioctl_access_mutex); return ret; } #ifdef CONFIG_COMPAT static long msm_thermal_compat_ioctl_process(struct file *filep, unsigned int cmd, unsigned long arg) { arg = (unsigned long)compat_ptr(arg); return msm_thermal_ioctl_process(filep, cmd, arg); } #endif /* CONFIG_COMPAT */ static const struct file_operations msm_thermal_fops = { .owner = THIS_MODULE, .open = msm_thermal_ioctl_open, .unlocked_ioctl = msm_thermal_ioctl_process, #ifdef CONFIG_COMPAT .compat_ioctl = msm_thermal_compat_ioctl_process, #endif /* CONFIG_COMPAT */ .release = msm_thermal_ioctl_release, }; int msm_thermal_ioctl_init() { int ret = 0; dev_t thermal_dev; struct device *therm_device; ret = alloc_chrdev_region(&thermal_dev, 0, 1, MSM_THERMAL_IOCTL_NAME); if (ret < 0) { pr_err("%s: Error in allocating char device region. Err:%d\n", KBUILD_MODNAME, ret); goto ioctl_init_exit; } msm_thermal_major = MAJOR(thermal_dev); thermal_class = class_create(THIS_MODULE, "msm_thermal"); if (IS_ERR(thermal_class)) { pr_err("%s: Error in creating class\n", KBUILD_MODNAME); ret = PTR_ERR(thermal_class); goto ioctl_class_fail; } therm_device = device_create(thermal_class, NULL, thermal_dev, NULL, MSM_THERMAL_IOCTL_NAME); if (IS_ERR(therm_device)) { pr_err("%s: Error in creating character device\n", KBUILD_MODNAME); ret = PTR_ERR(therm_device); goto ioctl_dev_fail; } msm_thermal_dev = kmalloc(sizeof(struct msm_thermal_ioctl_dev), GFP_KERNEL); if (!msm_thermal_dev) { pr_err("%s: Error allocating memory\n", KBUILD_MODNAME); ret = -ENOMEM; goto ioctl_clean_all; } memset(msm_thermal_dev, 0, sizeof(struct msm_thermal_ioctl_dev)); sema_init(&msm_thermal_dev->sem, 1); cdev_init(&msm_thermal_dev->char_dev, &msm_thermal_fops); ret = cdev_add(&msm_thermal_dev->char_dev, thermal_dev, 1); if (ret < 0) { pr_err("%s: Error in adding character device\n", KBUILD_MODNAME); goto ioctl_clean_all; } return ret; ioctl_clean_all: device_destroy(thermal_class, thermal_dev); ioctl_dev_fail: class_destroy(thermal_class); ioctl_class_fail: unregister_chrdev_region(thermal_dev, 1); ioctl_init_exit: return ret; } void msm_thermal_ioctl_cleanup() { uint32_t idx = 0; dev_t thermal_dev = MKDEV(msm_thermal_major, 0); if (!msm_thermal_dev) { pr_err("%s: Thermal IOCTL cleanup already done\n", KBUILD_MODNAME); return; } for (; idx < num_possible_cpus(); idx++) kfree(freq_table_ptr[idx]); device_destroy(thermal_class, thermal_dev); class_destroy(thermal_class); cdev_del(&msm_thermal_dev->char_dev); unregister_chrdev_region(thermal_dev, 1); kfree(msm_thermal_dev); msm_thermal_dev = NULL; thermal_class = NULL; }