/* * drivers/thermal/userspace_alert.c * * Userspace thermal alert cooling device. * * Copyright (C) 2016-2017 NVIDIA Corporation. 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 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 /* Define cooling device alert state */ #define NO_ALERT 0 #define ALERT 1 /* Maximum throttle states supported */ #define CDEV_MAX_STATE ALERT struct usr_alrt_strct { unsigned long cur_state; struct thermal_cooling_device *cdev; uint32_t therm_alert; uint32_t alert_timeout_ms; wait_queue_head_t alert_wait_queue; struct mutex alert_timeout_lock; struct mutex alert_lock; bool syfs_created; bool syfs_symlink_created; }; static int userspace_alert_send_uevent(struct thermal_cooling_device *tcd) { char trip[25]; char *envp[] = { trip, NULL }; int ret = 0; snprintf(trip, sizeof(trip), "USERSPACE_ALERT=%s", "TRIPPED"); ret = kobject_uevent_env(&tcd->device.kobj, KOBJ_CHANGE, envp); if (ret < 0) pr_err("userspace_alert_send_uevent: kobj_uevent error!\n"); return ret; } /* Get maximum throttle state supported by cooling device. */ static int userspace_alert_cdev_max_state(struct thermal_cooling_device *tcd, unsigned long *state) { struct usr_alrt_strct *alert_data = tcd->devdata; if (!alert_data) return -EINVAL; *state = CDEV_MAX_STATE; return 0; } /* Get current throttle state of the cooling device. */ static int userspace_alert_cdev_cur_state(struct thermal_cooling_device *tcd, unsigned long *state) { struct usr_alrt_strct *alert_data = tcd->devdata; if (!alert_data) return -EINVAL; *state = alert_data->cur_state; return 0; } /* Set current throttle state of the cooling device. */ static int userspace_alert_cdev_set_state(struct thermal_cooling_device *tcd, unsigned long state) { struct usr_alrt_strct *alert_data = tcd->devdata; if (!alert_data) return -EINVAL; if (state == alert_data->cur_state) return 0; mutex_lock(&alert_data->alert_lock); alert_data->cur_state = state; if (state > 0) { /* Send 'uevent' notification to userspace */ userspace_alert_send_uevent(tcd); alert_data->therm_alert = ALERT; /* * Write barrier for other processes to see the therm_alert * updated value. */ smp_wmb(); /* Wakeup the processes waiting for the alert to occur */ if (waitqueue_active(&alert_data->alert_wait_queue)) wake_up_all(&alert_data->alert_wait_queue); pr_debug("thermal alert: USERSPACE_ALERT_TRIPPED!\n"); } else { alert_data->therm_alert = NO_ALERT; /* * Write barrier for other processes to see the therm_alert * updated value. */ smp_wmb(); } mutex_unlock(&alert_data->alert_lock); return 0; } /* * Blocks the read call until an alert occurs or the specified duration * times out. When alert occurs or if alert is preset, then read return * with value 1 */ static ssize_t thermal_alert_block_show(struct device *dev, struct device_attribute *attr, char *buf) { uint32_t alert_timeout_ms; struct usr_alrt_strct *alert_data = dev_get_drvdata(dev); if (!alert_data) return -EINVAL; alert_timeout_ms = alert_data->alert_timeout_ms; if (alert_timeout_ms > 0) wait_event_interruptible_timeout(alert_data->alert_wait_queue, alert_data->therm_alert == ALERT, msecs_to_jiffies(alert_timeout_ms)); else wait_event_interruptible( alert_data->alert_wait_queue, alert_data->therm_alert == ALERT); return sprintf(buf, "%d\n", alert_data->therm_alert); } static ssize_t thermal_alert_show(struct device *dev, struct device_attribute *attr, char *buf) { struct usr_alrt_strct *alert_data = dev_get_drvdata(dev); if (!alert_data) return -EINVAL; return sprintf(buf, "%d\n", alert_data->therm_alert); } /* * Set the time out duration(in ms) to wait when alert is not preset. * When alert occurs or if alert is already preset, this value has * no significance */ static ssize_t thermal_alert_block_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { long val; struct usr_alrt_strct *alert_data = dev_get_drvdata(dev); if (!alert_data) return -EINVAL; if (kstrtol(buf, 0, &val)) return -EINVAL; mutex_lock(&alert_data->alert_timeout_lock); alert_data->alert_timeout_ms = val; mutex_unlock(&alert_data->alert_timeout_lock); return count; } /* * Cooling device operations. */ static struct thermal_cooling_device_ops userspace_alert_cdev_ops = { .get_max_state = userspace_alert_cdev_max_state, .get_cur_state = userspace_alert_cdev_cur_state, .set_cur_state = userspace_alert_cdev_set_state, }; static DEVICE_ATTR(thermal_alert_block, S_IRUGO | S_IWUSR, thermal_alert_block_show, thermal_alert_block_store); static DEVICE_ATTR(thermal_alert, S_IRUGO, thermal_alert_show, NULL); static const struct attribute *userspace_cdev_attr[] = { &dev_attr_thermal_alert_block.attr, &dev_attr_thermal_alert.attr, NULL, }; static int userspace_alert_therm_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; const char *str; char *cdev_type; struct usr_alrt_strct *alert_data; struct device_node *np = dev->of_node; alert_data = devm_kzalloc(dev, sizeof(struct usr_alrt_strct), GFP_KERNEL); if (!alert_data) return -ENOMEM; if (of_property_read_string(np, "cdev-type", &str) == 0) { cdev_type = (char *)str; } else { dev_err(dev, "missing cdev-type\n"); return -EINVAL; } alert_data->cur_state = NO_ALERT; alert_data->therm_alert = 0; alert_data->alert_timeout_ms = 0; alert_data->syfs_created = 0; alert_data->syfs_symlink_created = 0; mutex_init(&alert_data->alert_lock); mutex_init(&alert_data->alert_timeout_lock); init_waitqueue_head(&alert_data->alert_wait_queue); dev_set_drvdata(dev, alert_data); alert_data->cdev = thermal_of_cooling_device_register(np, cdev_type, alert_data, &userspace_alert_cdev_ops); if (IS_ERR(alert_data->cdev)) return PTR_ERR(alert_data->cdev); if (sysfs_create_files(&dev->kobj, userspace_cdev_attr) < 0) pr_warn("\n userspace alert cdev: failed to create sysfs files\n"); else alert_data->syfs_created = 1; if (sysfs_create_link(&alert_data->cdev->device.kobj, &dev->kobj, "userspace_alert") < 0) pr_warn("\n userspace alert cdev: failed to create sysfs symlink\n"); else alert_data->syfs_symlink_created = 1; pr_info("%s cooling device registered.\n", cdev_type); return 0; } static int userspace_alert_therm_remove(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct usr_alrt_strct *alert_data = dev_get_drvdata(dev); struct thermal_cooling_device *cdev; if (alert_data) { cdev = alert_data->cdev; if ((cdev != NULL) && (alert_data->syfs_symlink_created)) sysfs_remove_link(&cdev->device.kobj, "userspace_alert"); if (alert_data->syfs_created) sysfs_remove_files(&dev->kobj, userspace_cdev_attr); mutex_destroy(&alert_data->alert_lock); mutex_destroy(&alert_data->alert_timeout_lock); thermal_cooling_device_unregister(alert_data->cdev); } return 0; } static const struct of_device_id userspace_therm_of_match[] = { { .compatible = "userspace-therm-alert", }, { }, }; static struct platform_driver userspace_therm_driver = { .driver = { .name = "userspace-therm-alert", .owner = THIS_MODULE, .of_match_table = userspace_therm_of_match, }, .probe = userspace_alert_therm_probe, .remove = userspace_alert_therm_remove, }; module_platform_driver(userspace_therm_driver); MODULE_DESCRIPTION("Userspace thermal alert cooling device module"); MODULE_AUTHOR("NVIDIA"); MODULE_LICENSE("GPL v2");