/* Copyright (c) 2014-2015, 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. */ #define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ #include #include #include #include #include #include #include #include #include #include #include #include #include "lmh_interface.h" #include #include #define LMH_MON_NAME "lmh_monitor" #define LMH_ISR_POLL_DELAY "interrupt_poll_delay_msec" #define LMH_TRACE_ENABLE "hw_trace_enable" #define LMH_TRACE_INTERVAL "hw_trace_interval" #define LMH_DBGFS_DIR "debug" #define LMH_DBGFS_READ "data" #define LMH_DBGFS_CONFIG_READ "config" #define LMH_DBGFS_READ_TYPES "data_types" #define LMH_DBGFS_CONFIG_TYPES "config_types" #define LMH_TRACE_INTERVAL_XO_TICKS 250 #define LMH_POLLING_MSEC 30 struct lmh_mon_threshold { long value; bool active; }; struct lmh_device_data { char device_name[LMH_NAME_MAX]; struct lmh_device_ops *device_ops; uint32_t max_level; int curr_level; int *levels; struct dentry *dev_parent; struct dentry *max_lvl_fs; struct dentry *curr_lvl_fs; struct dentry *avail_lvl_fs; struct list_head list_ptr; struct rw_semaphore lock; struct device dev; }; struct lmh_mon_sensor_data { struct list_head list_ptr; char sensor_name[LMH_NAME_MAX]; struct lmh_sensor_ops *sensor_ops; struct rw_semaphore lock; struct lmh_mon_threshold trip[LMH_TRIP_MAX]; struct thermal_zone_device *tzdev; enum thermal_device_mode mode; }; struct lmh_mon_driver_data { struct dentry *debugfs_parent; struct dentry *poll_fs; struct dentry *enable_hw_log; struct dentry *hw_log_delay; uint32_t hw_log_enable; uint64_t hw_log_interval; struct dentry *debug_dir; struct dentry *debug_read; struct dentry *debug_config; struct dentry *debug_read_type; struct dentry *debug_config_type; struct lmh_debug_ops *debug_ops; }; enum lmh_read_type { LMH_DEBUG_READ_TYPE, LMH_DEBUG_CONFIG_TYPE, LMH_PROFILES, }; static struct lmh_mon_driver_data *lmh_mon_data; static struct class lmh_class_info = { .name = "msm_limits", }; static int lmh_poll_interval = LMH_POLLING_MSEC; static DECLARE_RWSEM(lmh_mon_access_lock); static LIST_HEAD(lmh_sensor_list); static DECLARE_RWSEM(lmh_dev_access_lock); static LIST_HEAD(lmh_device_list); #define LMH_CREATE_DEBUGFS_FILE(_node, _name, _mode, _parent, _data, _ops, \ _ret) do { \ _node = debugfs_create_file(_name, _mode, _parent, \ _data, _ops); \ if (IS_ERR(_node)) { \ _ret = PTR_ERR(_node); \ pr_err("Error creating debugfs file:%s. err:%d\n", \ _name, _ret); \ } \ } while (0) #define LMH_CREATE_DEBUGFS_DIR(_node, _name, _parent, _ret) \ do { \ _node = debugfs_create_dir(_name, _parent); \ if (IS_ERR(_node)) { \ _ret = PTR_ERR(_node); \ pr_err("Error creating debugfs dir:%s. err:%d\n", \ _name, _ret); \ } \ } while (0) #define LMH_HW_LOG_FS(_name) \ static int _name##_get(void *data, u64 *val) \ { \ *val = lmh_mon_data->_name; \ return 0; \ } \ static int _name##_set(void *data, u64 val) \ { \ struct lmh_mon_sensor_data *lmh_sensor = data; \ int ret = 0; \ lmh_mon_data->_name = val; \ if (lmh_mon_data->hw_log_enable) \ ret = lmh_sensor->sensor_ops->enable_hw_log( \ lmh_mon_data->hw_log_interval \ , lmh_mon_data->hw_log_enable); \ else \ ret = lmh_sensor->sensor_ops->disable_hw_log(); \ return ret; \ } \ DEFINE_SIMPLE_ATTRIBUTE(_name##_fops, _name##_get, _name##_set, \ "%llu\n"); #define LMH_DEV_GET(_name) \ static ssize_t _name##_get(struct device *dev, \ struct device_attribute *attr, char *buf) \ { \ struct lmh_device_data *lmh_dev = container_of(dev, \ struct lmh_device_data, dev); \ return snprintf(buf, LMH_NAME_MAX, "%d", lmh_dev->_name); \ } \ LMH_HW_LOG_FS(hw_log_enable); LMH_HW_LOG_FS(hw_log_interval); LMH_DEV_GET(max_level); LMH_DEV_GET(curr_level); int lmh_get_poll_interval(void) { return lmh_poll_interval; } static ssize_t curr_level_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { struct lmh_device_data *lmh_dev = container_of(dev, struct lmh_device_data, dev); int val = 0, ret = 0; ret = kstrtouint(buf, 0, &val); if (ret < 0) { pr_err("Invalid input [%s]. err:%d\n", buf, ret); return ret; } return lmh_set_dev_level(lmh_dev->device_name, val); } static ssize_t avail_level_get(struct device *dev, struct device_attribute *attr, char *buf) { struct lmh_device_data *lmh_dev = container_of(dev, struct lmh_device_data, dev); uint32_t *type_list = NULL; int ret = 0, count = 0, lvl_buf_count = 0, idx = 0; char *lvl_buf = NULL; if (!lmh_dev || !lmh_dev->levels || !lmh_dev->max_level) { pr_err("Invalid input\n"); return -EINVAL; } type_list = lmh_dev->levels; lvl_buf = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!lvl_buf) { pr_err("Error allocating memory\n"); return -ENOMEM; } for (idx = 0; (idx < lmh_dev->max_level) && (lvl_buf_count < PAGE_SIZE) ; idx++) { count = snprintf(lvl_buf + lvl_buf_count, PAGE_SIZE - lvl_buf_count, "%d ", type_list[idx]); if (count + lvl_buf_count >= PAGE_SIZE) { pr_err("overflow.\n"); break; } else if (count < 0) { pr_err("Error writing to buffer. err:%d\n", count); ret = count; goto lvl_get_exit; } lvl_buf_count += count; } count = snprintf(lvl_buf + lvl_buf_count, PAGE_SIZE - lvl_buf_count, "\n"); if (count < 0) pr_err("Error writing new line to buffer. err:%d\n", count); else if (count + lvl_buf_count < PAGE_SIZE) lvl_buf_count += count; count = snprintf(buf, lvl_buf_count + 1, lvl_buf); if (count > PAGE_SIZE || count < 0) { pr_err("copy to user buffer failed\n"); ret = -EFAULT; goto lvl_get_exit; } lvl_get_exit: kfree(lvl_buf); return (ret) ? ret : count; } static int lmh_create_dev_sysfs(struct lmh_device_data *lmh_dev) { int ret = 0; static DEVICE_ATTR(level, 0600, curr_level_get, curr_level_set); static DEVICE_ATTR(available_levels, 0400, avail_level_get, NULL); static DEVICE_ATTR(total_levels, 0400, max_level_get, NULL); lmh_dev->dev.class = &lmh_class_info; dev_set_name(&lmh_dev->dev, "%s", lmh_dev->device_name); ret = device_register(&lmh_dev->dev); if (ret) { pr_err("Error registering profile device. err:%d\n", ret); return ret; } ret = device_create_file(&lmh_dev->dev, &dev_attr_level); if (ret) { pr_err("Error creating profile level sysfs node. err:%d\n", ret); goto dev_sysfs_exit; } ret = device_create_file(&lmh_dev->dev, &dev_attr_total_levels); if (ret) { pr_err("Error creating total level sysfs node. err:%d\n", ret); goto dev_sysfs_exit; } ret = device_create_file(&lmh_dev->dev, &dev_attr_available_levels); if (ret) { pr_err("Error creating available level sysfs node. err:%d\n", ret); goto dev_sysfs_exit; } dev_sysfs_exit: if (ret) device_unregister(&lmh_dev->dev); return ret; } static int lmh_create_debugfs_nodes(struct lmh_mon_sensor_data *lmh_sensor) { int ret = 0; lmh_mon_data->hw_log_enable = 0; lmh_mon_data->hw_log_interval = LMH_TRACE_INTERVAL_XO_TICKS; LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->enable_hw_log, LMH_TRACE_ENABLE, 0600, lmh_mon_data->debugfs_parent, (void *)lmh_sensor, &hw_log_enable_fops, ret); if (ret) goto create_debugfs_exit; LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->hw_log_delay, LMH_TRACE_INTERVAL, 0600, lmh_mon_data->debugfs_parent, (void *)lmh_sensor, &hw_log_interval_fops, ret); if (ret) goto create_debugfs_exit; create_debugfs_exit: if (ret) debugfs_remove_recursive(lmh_mon_data->debugfs_parent); return ret; } static struct lmh_mon_sensor_data *lmh_match_sensor_ops( struct lmh_sensor_ops *ops) { struct lmh_mon_sensor_data *lmh_sensor = NULL; list_for_each_entry(lmh_sensor, &lmh_sensor_list, list_ptr) { if (lmh_sensor->sensor_ops == ops) return lmh_sensor; } return NULL; } static struct lmh_mon_sensor_data *lmh_match_sensor_name(char *sensor_name) { struct lmh_mon_sensor_data *lmh_sensor = NULL; list_for_each_entry(lmh_sensor, &lmh_sensor_list, list_ptr) { if (!strnicmp(lmh_sensor->sensor_name, sensor_name, LMH_NAME_MAX)) return lmh_sensor; } return NULL; } static void lmh_evaluate_and_notify(struct lmh_mon_sensor_data *lmh_sensor, long val) { int idx = 0, trip = 0; bool cond = false; for (idx = 0; idx < LMH_TRIP_MAX; idx++) { if (!lmh_sensor->trip[idx].active) continue; if (idx == LMH_HIGH_TRIP) { trip = THERMAL_TRIP_CONFIGURABLE_HI; cond = (val >= lmh_sensor->trip[idx].value); } else { trip = THERMAL_TRIP_CONFIGURABLE_LOW; cond = (val <= lmh_sensor->trip[idx].value); } if (cond) { lmh_sensor->trip[idx].active = false; thermal_sensor_trip(lmh_sensor->tzdev, trip, val); } } } void lmh_update_reading(struct lmh_sensor_ops *ops, long trip_val) { struct lmh_mon_sensor_data *lmh_sensor = NULL; if (!ops) { pr_err("Invalid input\n"); return; } down_read(&lmh_mon_access_lock); lmh_sensor = lmh_match_sensor_ops(ops); if (!lmh_sensor) { pr_err("Invalid ops\n"); goto interrupt_exit; } down_write(&lmh_sensor->lock); pr_debug("Sensor:[%s] intensity:%ld\n", lmh_sensor->sensor_name, trip_val); lmh_evaluate_and_notify(lmh_sensor, trip_val); interrupt_exit: if (lmh_sensor) up_write(&lmh_sensor->lock); up_read(&lmh_mon_access_lock); return; } static int lmh_sensor_read(struct thermal_zone_device *dev, unsigned long *val) { int ret = 0; struct lmh_mon_sensor_data *lmh_sensor; if (!val || !dev || !dev->devdata) { pr_err("Invalid input\n"); return -EINVAL; } lmh_sensor = dev->devdata; down_read(&lmh_mon_access_lock); down_read(&lmh_sensor->lock); ret = lmh_sensor->sensor_ops->read(lmh_sensor->sensor_ops, val); if (ret) { pr_err("Error reading sensor:%s. err:%d\n", lmh_sensor->sensor_name, ret); goto unlock_and_exit; } unlock_and_exit: up_read(&lmh_sensor->lock); up_read(&lmh_mon_access_lock); return ret; } static int lmh_get_mode(struct thermal_zone_device *dev, enum thermal_device_mode *mode) { struct lmh_mon_sensor_data *lmh_sensor; if (!dev || !dev->devdata || !mode) { pr_err("Invalid input\n"); return -EINVAL; } lmh_sensor = dev->devdata; *mode = lmh_sensor->mode; return 0; } static int lmh_get_trip_type(struct thermal_zone_device *dev, int trip, enum thermal_trip_type *type) { if (!type || !dev || !dev->devdata || trip < 0 || trip >= LMH_TRIP_MAX) { pr_err("Invalid input\n"); return -EINVAL; } switch (trip) { case LMH_HIGH_TRIP: *type = THERMAL_TRIP_CONFIGURABLE_HI; break; case LMH_LOW_TRIP: *type = THERMAL_TRIP_CONFIGURABLE_LOW; break; default: return -EINVAL; } return 0; } static int lmh_activate_trip(struct thermal_zone_device *dev, int trip, enum thermal_trip_activation_mode mode) { struct lmh_mon_sensor_data *lmh_sensor; if (!dev || !dev->devdata || trip < 0 || trip >= LMH_TRIP_MAX) { pr_err("Invalid input\n"); return -EINVAL; } lmh_sensor = dev->devdata; down_read(&lmh_mon_access_lock); down_write(&lmh_sensor->lock); lmh_sensor->trip[trip].active = (mode == THERMAL_TRIP_ACTIVATION_ENABLED); up_write(&lmh_sensor->lock); up_read(&lmh_mon_access_lock); return 0; } static int lmh_get_trip_value(struct thermal_zone_device *dev, int trip, unsigned long *value) { struct lmh_mon_sensor_data *lmh_sensor; if (!dev || !dev->devdata || trip < 0 || trip >= LMH_TRIP_MAX || !value) { pr_err("Invalid input\n"); return -EINVAL; } lmh_sensor = dev->devdata; down_read(&lmh_mon_access_lock); down_read(&lmh_sensor->lock); *value = lmh_sensor->trip[trip].value; up_read(&lmh_sensor->lock); up_read(&lmh_mon_access_lock); return 0; } static int lmh_set_trip_value(struct thermal_zone_device *dev, int trip, unsigned long value) { struct lmh_mon_sensor_data *lmh_sensor; if (!dev || !dev->devdata || trip < 0 || trip >= LMH_TRIP_MAX) { pr_err("Invalid input\n"); return -EINVAL; } lmh_sensor = dev->devdata; down_read(&lmh_mon_access_lock); down_write(&lmh_sensor->lock); lmh_sensor->trip[trip].value = value; up_write(&lmh_sensor->lock); up_read(&lmh_mon_access_lock); return 0; } static struct thermal_zone_device_ops lmh_sens_ops = { .get_temp = lmh_sensor_read, .get_mode = lmh_get_mode, .get_trip_type = lmh_get_trip_type, .activate_trip_type = lmh_activate_trip, .get_trip_temp = lmh_get_trip_value, .set_trip_temp = lmh_set_trip_value, }; static int lmh_register_sensor(struct lmh_mon_sensor_data *lmh_sensor) { int ret = 0; lmh_sensor->tzdev = thermal_zone_device_register( lmh_sensor->sensor_name, LMH_TRIP_MAX, (1 << LMH_TRIP_MAX) - 1, lmh_sensor, &lmh_sens_ops, NULL, 0 , 0); if (IS_ERR_OR_NULL(lmh_sensor->tzdev)) { ret = PTR_ERR(lmh_sensor->tzdev); pr_err("Error registering sensor:[%s] with thermal. err:%d\n", lmh_sensor->sensor_name, ret); return ret; } return ret; } static int lmh_sensor_init(struct lmh_mon_sensor_data *lmh_sensor, char *sensor_name, struct lmh_sensor_ops *ops) { int idx = 0, ret = 0; strlcpy(lmh_sensor->sensor_name, sensor_name, LMH_NAME_MAX); lmh_sensor->sensor_ops = ops; ops->new_value_notify = lmh_update_reading; for (idx = 0; idx < LMH_TRIP_MAX; idx++) { lmh_sensor->trip[idx].value = 0; lmh_sensor->trip[idx].active = false; } init_rwsem(&lmh_sensor->lock); if (list_empty(&lmh_sensor_list) && !lmh_mon_data->enable_hw_log) lmh_create_debugfs_nodes(lmh_sensor); list_add_tail(&lmh_sensor->list_ptr, &lmh_sensor_list); return ret; } int lmh_sensor_register(char *sensor_name, struct lmh_sensor_ops *ops) { int ret = 0; struct lmh_mon_sensor_data *lmh_sensor = NULL; if (!sensor_name || !ops) { pr_err("Invalid input\n"); return -EINVAL; } if (!ops->read || !ops->enable_hw_log || !ops->disable_hw_log) { pr_err("Invalid ops input for sensor:%s\n", sensor_name); return -EINVAL; } down_write(&lmh_mon_access_lock); if (lmh_match_sensor_name(sensor_name) || lmh_match_sensor_ops(ops)) { ret = -EEXIST; pr_err("Sensor[%s] exists\n", sensor_name); goto register_exit; } lmh_sensor = kzalloc(sizeof(struct lmh_mon_sensor_data), GFP_KERNEL); if (!lmh_sensor) { pr_err("kzalloc failed\n"); ret = -ENOMEM; goto register_exit; } ret = lmh_sensor_init(lmh_sensor, sensor_name, ops); if (ret) { pr_err("Error registering sensor:%s. err:%d\n", sensor_name, ret); kfree(lmh_sensor); goto register_exit; } pr_debug("Registered Sensor:[%s]\n", sensor_name); register_exit: up_write(&lmh_mon_access_lock); if (ret) return ret; ret = lmh_register_sensor(lmh_sensor); if (ret) { pr_err("Thermal Zone register failed for Sensor:[%s]\n" , sensor_name); return ret; } pr_debug("Registered Sensor:[%s]\n", sensor_name); return ret; } static void lmh_sensor_remove(struct lmh_sensor_ops *ops) { struct lmh_mon_sensor_data *lmh_sensor = NULL; lmh_sensor = lmh_match_sensor_ops(ops); if (!lmh_sensor) { pr_err("No match for the sensor\n"); goto deregister_exit; } down_write(&lmh_sensor->lock); thermal_zone_device_unregister(lmh_sensor->tzdev); list_del(&lmh_sensor->list_ptr); up_write(&lmh_sensor->lock); pr_debug("Deregistered sensor:[%s]\n", lmh_sensor->sensor_name); kfree(lmh_sensor); deregister_exit: return; } void lmh_sensor_deregister(struct lmh_sensor_ops *ops) { if (!ops) { pr_err("Invalid input\n"); return; } down_write(&lmh_mon_access_lock); lmh_sensor_remove(ops); up_write(&lmh_mon_access_lock); return; } static struct lmh_device_data *lmh_match_device_name(char *device_name) { struct lmh_device_data *lmh_device = NULL; list_for_each_entry(lmh_device, &lmh_device_list, list_ptr) { if (!strnicmp(lmh_device->device_name, device_name, LMH_NAME_MAX)) return lmh_device; } return NULL; } static struct lmh_device_data *lmh_match_device_ops(struct lmh_device_ops *ops) { struct lmh_device_data *lmh_device = NULL; list_for_each_entry(lmh_device, &lmh_device_list, list_ptr) { if (lmh_device->device_ops == ops) return lmh_device; } return NULL; } static int lmh_device_init(struct lmh_device_data *lmh_device, char *device_name, struct lmh_device_ops *ops) { int ret = 0; ret = ops->get_curr_level(ops, &lmh_device->curr_level); if (ret) { pr_err("Error getting curr level for Device:[%s]. err:%d\n", device_name, ret); goto dev_init_exit; } ret = ops->get_available_levels(ops, NULL); if (ret <= 0) { pr_err("Error getting max level for Device:[%s]. err:%d\n", device_name, ret); ret = (!ret) ? -EINVAL : ret; goto dev_init_exit; } lmh_device->max_level = ret; lmh_device->levels = kzalloc(lmh_device->max_level * sizeof(int), GFP_KERNEL); if (!lmh_device->levels) { pr_err("No memory\n"); ret = -ENOMEM; goto dev_init_exit; } ret = ops->get_available_levels(ops, lmh_device->levels); if (ret) { pr_err("Error getting device:[%s] levels. err:%d\n", device_name, ret); goto dev_init_exit; } init_rwsem(&lmh_device->lock); lmh_device->device_ops = ops; strlcpy(lmh_device->device_name, device_name, LMH_NAME_MAX); list_add_tail(&lmh_device->list_ptr, &lmh_device_list); lmh_create_dev_sysfs(lmh_device); dev_init_exit: if (ret) kfree(lmh_device->levels); return ret; } int lmh_get_all_dev_levels(char *device_name, int *val) { int ret = 0; struct lmh_device_data *lmh_device = NULL; if (!device_name) { pr_err("Invalid input\n"); return -EINVAL; } down_read(&lmh_dev_access_lock); lmh_device = lmh_match_device_name(device_name); if (!lmh_device) { pr_err("Invalid device:%s\n", device_name); ret = -EINVAL; goto get_all_lvl_exit; } down_read(&lmh_device->lock); if (!val) { ret = lmh_device->max_level; goto get_all_lvl_exit; } memcpy(val, lmh_device->levels, sizeof(int) * lmh_device->max_level); get_all_lvl_exit: if (lmh_device) up_read(&lmh_device->lock); up_read(&lmh_dev_access_lock); return ret; } int lmh_set_dev_level(char *device_name, int curr_lvl) { int ret = 0; struct lmh_device_data *lmh_device = NULL; if (!device_name) { pr_err("Invalid input\n"); return -EINVAL; } down_read(&lmh_dev_access_lock); lmh_device = lmh_match_device_name(device_name); if (!lmh_device) { pr_err("Invalid device:%s\n", device_name); ret = -EINVAL; goto set_dev_exit; } down_write(&lmh_device->lock); curr_lvl = min(curr_lvl, lmh_device->levels[lmh_device->max_level - 1]); curr_lvl = max(curr_lvl, lmh_device->levels[0]); if (curr_lvl == lmh_device->curr_level) goto set_dev_exit; ret = lmh_device->device_ops->set_level(lmh_device->device_ops, curr_lvl); if (ret) { pr_err("Error setting current level%d for device[%s]. err:%d\n", curr_lvl, device_name, ret); goto set_dev_exit; } pr_debug("Device:[%s] configured to level %d\n", device_name, curr_lvl); lmh_device->curr_level = curr_lvl; set_dev_exit: if (lmh_device) up_write(&lmh_device->lock); up_read(&lmh_dev_access_lock); return ret; } int lmh_get_curr_level(char *device_name, int *val) { int ret = 0; struct lmh_device_data *lmh_device = NULL; if (!device_name || !val) { pr_err("Invalid input\n"); return -EINVAL; } down_read(&lmh_dev_access_lock); lmh_device = lmh_match_device_name(device_name); if (!lmh_device) { pr_err("Invalid device:%s\n", device_name); ret = -EINVAL; goto get_curr_level; } down_read(&lmh_device->lock); ret = lmh_device->device_ops->get_curr_level(lmh_device->device_ops, &lmh_device->curr_level); if (ret) { pr_err("Error getting device[%s] current level. err:%d\n", device_name, ret); goto get_curr_level; } *val = lmh_device->curr_level; pr_debug("Device:%s current level:%d\n", device_name, *val); get_curr_level: if (lmh_device) up_read(&lmh_device->lock); up_read(&lmh_dev_access_lock); return ret; } int lmh_device_register(char *device_name, struct lmh_device_ops *ops) { int ret = 0; struct lmh_device_data *lmh_device = NULL; if (!device_name || !ops) { pr_err("Invalid input\n"); return -EINVAL; } if (!ops->get_available_levels || !ops->get_curr_level || !ops->set_level) { pr_err("Invalid ops input for device:%s\n", device_name); return -EINVAL; } down_write(&lmh_dev_access_lock); if (lmh_match_device_name(device_name) || lmh_match_device_ops(ops)) { ret = -EEXIST; pr_err("Device[%s] allready exists\n", device_name); goto register_exit; } lmh_device = kzalloc(sizeof(struct lmh_device_data), GFP_KERNEL); if (!lmh_device) { pr_err("kzalloc failed\n"); ret = -ENOMEM; goto register_exit; } ret = lmh_device_init(lmh_device, device_name, ops); if (ret) { pr_err("Error registering device:%s. err:%d\n", device_name, ret); kfree(lmh_device); goto register_exit; } pr_debug("Registered Device:[%s] with %d levels\n", device_name, lmh_device->max_level); register_exit: up_write(&lmh_dev_access_lock); return ret; } static void lmh_device_remove(struct lmh_device_ops *ops) { struct lmh_device_data *lmh_device = NULL; lmh_device = lmh_match_device_ops(ops); if (!lmh_device) { pr_err("No match for the device\n"); goto deregister_exit; } down_write(&lmh_device->lock); list_del(&lmh_device->list_ptr); pr_debug("Deregistered device:[%s]\n", lmh_device->device_name); kfree(lmh_device->levels); up_write(&lmh_device->lock); kfree(lmh_device); deregister_exit: return; } void lmh_device_deregister(struct lmh_device_ops *ops) { if (!ops) { pr_err("Invalid input\n"); return; } down_write(&lmh_dev_access_lock); lmh_device_remove(ops); up_write(&lmh_dev_access_lock); return; } static int lmh_parse_and_extract(const char __user *user_buf, size_t count, enum lmh_read_type type) { char *local_buf = NULL, *token = NULL, *curr_ptr = NULL, *token1 = NULL; char *next_line = NULL; int ret = 0, data_ct = 0, i = 0, size = 0; uint32_t *config_buf = NULL; /* Allocate two extra space to add ';' character and NULL terminate */ local_buf = kzalloc(count + 2, GFP_KERNEL); if (!local_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } if (copy_from_user(local_buf, user_buf, count)) { pr_err("user buf error\n"); ret = -EFAULT; goto dfs_cfg_write_exit; } size = count + (strnchr(local_buf, count, '\n') ? 1 : 2); local_buf[size - 2] = ';'; local_buf[size - 1] = '\0'; curr_ptr = next_line = local_buf; while ((token1 = strnchr(next_line, local_buf + size - next_line, ';')) != NULL) { data_ct = 0; *token1 = '\0'; curr_ptr = next_line; next_line = token1 + 1; for (token = (char *)curr_ptr; token && ((token = strnchr(token, next_line - token, ' ')) != NULL); token++) data_ct++; if (data_ct < 2) { pr_err("Invalid format string:[%s]\n", curr_ptr); ret = -EINVAL; goto dfs_cfg_write_exit; } config_buf = kzalloc((++data_ct) * sizeof(uint32_t), GFP_KERNEL); if (!config_buf) { ret = -ENOMEM; goto dfs_cfg_write_exit; } pr_debug("Input:%s data_ct:%d\n", curr_ptr, data_ct); for (i = 0, token = (char *)curr_ptr; token && (i < data_ct); i++) { token = strnchr(token, next_line - token, ' '); if (token) *token = '\0'; ret = kstrtouint(curr_ptr, 0, &config_buf[i]); if (ret < 0) { pr_err("Data[%s] scan error. err:%d\n", curr_ptr, ret); kfree(config_buf); goto dfs_cfg_write_exit; } if (token) curr_ptr = ++token; } switch (type) { case LMH_DEBUG_READ_TYPE: ret = lmh_mon_data->debug_ops->debug_config_read( lmh_mon_data->debug_ops, config_buf, data_ct); break; case LMH_DEBUG_CONFIG_TYPE: ret = lmh_mon_data->debug_ops->debug_config_lmh( lmh_mon_data->debug_ops, config_buf, data_ct); break; default: ret = -EINVAL; break; } kfree(config_buf); if (ret) { pr_err("Config error. type:%d err:%d\n", type, ret); goto dfs_cfg_write_exit; } } dfs_cfg_write_exit: kfree(local_buf); return ret; } static ssize_t lmh_dbgfs_config_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_DEBUG_CONFIG_TYPE); return count; } static int lmh_dbgfs_data_read(struct seq_file *seq_fp, void *data) { static uint32_t *read_buf; static int read_buf_size; int idx = 0, ret = 0, print_ret = 0; if (!read_buf_size) { ret = lmh_mon_data->debug_ops->debug_read( lmh_mon_data->debug_ops, &read_buf); if (ret <= 0) goto dfs_read_exit; if (!read_buf || ret < sizeof(uint32_t)) { ret = -EINVAL; goto dfs_read_exit; } read_buf_size = ret; ret = 0; } do { print_ret = seq_printf(seq_fp, "0x%x ", read_buf[idx]); if (print_ret) { pr_err("Seq print error. idx:%d err:%d\n", idx, print_ret); goto dfs_read_exit; } idx++; if ((idx % LMH_READ_LINE_LENGTH) == 0) { print_ret = seq_puts(seq_fp, "\n"); if (print_ret) { pr_err("Seq print error. err:%d\n", print_ret); goto dfs_read_exit; } } } while (idx < (read_buf_size / sizeof(uint32_t))); read_buf_size = 0; read_buf = NULL; dfs_read_exit: return ret; } static ssize_t lmh_dbgfs_data_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos) { lmh_parse_and_extract(user_buf, count, LMH_DEBUG_READ_TYPE); return count; } static int lmh_dbgfs_data_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_data_read, inode->i_private); } static int lmh_get_types(struct seq_file *seq_fp, enum lmh_read_type type) { int ret = 0, idx = 0, size = 0; uint32_t *type_list = NULL; switch (type) { case LMH_DEBUG_READ_TYPE: ret = lmh_mon_data->debug_ops->debug_get_types( lmh_mon_data->debug_ops, true, &type_list); break; case LMH_DEBUG_CONFIG_TYPE: ret = lmh_mon_data->debug_ops->debug_get_types( lmh_mon_data->debug_ops, false, &type_list); break; default: return -EINVAL; } if (ret <= 0 || !type_list) { pr_err("No device information. err:%d\n", ret); return -ENODEV; } size = ret; for (idx = 0; idx < size; idx++) seq_printf(seq_fp, "0x%x ", type_list[idx]); seq_puts(seq_fp, "\n"); return 0; } static int lmh_dbgfs_read_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_DEBUG_READ_TYPE); } static int lmh_dbgfs_read_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_read_type, inode->i_private); } static int lmh_dbgfs_config_type(struct seq_file *seq_fp, void *data) { return lmh_get_types(seq_fp, LMH_DEBUG_CONFIG_TYPE); } static int lmh_dbgfs_config_type_open(struct inode *inode, struct file *file) { return single_open(file, lmh_dbgfs_config_type, inode->i_private); } static const struct file_operations lmh_dbgfs_config_fops = { .write = lmh_dbgfs_config_write, }; static const struct file_operations lmh_dbgfs_read_fops = { .open = lmh_dbgfs_data_open, .read = seq_read, .write = lmh_dbgfs_data_write, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_read_type_fops = { .open = lmh_dbgfs_read_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; static const struct file_operations lmh_dbgfs_config_type_fops = { .open = lmh_dbgfs_config_type_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; int lmh_debug_register(struct lmh_debug_ops *ops) { int ret = 0; if (!ops || !ops->debug_read || !ops->debug_config_read || !ops->debug_get_types) { pr_err("Invalid input"); ret = -EINVAL; goto dbg_reg_exit; } lmh_mon_data->debug_ops = ops; LMH_CREATE_DEBUGFS_DIR(lmh_mon_data->debug_dir, LMH_DBGFS_DIR, lmh_mon_data->debugfs_parent, ret); if (ret) goto dbg_reg_exit; LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_read, LMH_DBGFS_READ, 0600, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_read_fops, ret); if (!lmh_mon_data->debug_read) { pr_err("Error creating" LMH_DBGFS_READ "entry.\n"); ret = -ENODEV; goto dbg_reg_exit; } LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_config, LMH_DBGFS_CONFIG_READ, 0200, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_config_fops, ret); if (!lmh_mon_data->debug_config) { pr_err("Error creating" LMH_DBGFS_CONFIG_READ "entry\n"); ret = -ENODEV; goto dbg_reg_exit; } LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_read_type, LMH_DBGFS_READ_TYPES, 0400, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_read_type_fops, ret); if (!lmh_mon_data->debug_read_type) { pr_err("Error creating" LMH_DBGFS_READ_TYPES "entry\n"); ret = -ENODEV; goto dbg_reg_exit; } LMH_CREATE_DEBUGFS_FILE(lmh_mon_data->debug_config_type, LMH_DBGFS_CONFIG_TYPES, 0400, lmh_mon_data->debug_dir, NULL, &lmh_dbgfs_config_type_fops, ret); if (!lmh_mon_data->debug_config_type) { pr_err("Error creating" LMH_DBGFS_CONFIG_TYPES "entry\n"); ret = -ENODEV; goto dbg_reg_exit; } dbg_reg_exit: if (ret) { /*Clean up all the dbg nodes*/ debugfs_remove_recursive(lmh_mon_data->debug_dir); lmh_mon_data->debug_ops = NULL; } return ret; } static int lmh_mon_init_driver(void) { int ret = 0; lmh_mon_data = kzalloc(sizeof(struct lmh_mon_driver_data), GFP_KERNEL); if (!lmh_mon_data) { pr_err("No memory\n"); return -ENOMEM; } LMH_CREATE_DEBUGFS_DIR(lmh_mon_data->debugfs_parent, LMH_MON_NAME, NULL, ret); if (ret) goto init_exit; lmh_mon_data->poll_fs = debugfs_create_u32(LMH_ISR_POLL_DELAY, 0600, lmh_mon_data->debugfs_parent, &lmh_poll_interval); if (IS_ERR(lmh_mon_data->poll_fs)) pr_err("Error creating debugfs:[%s]. err:%ld\n", LMH_ISR_POLL_DELAY, PTR_ERR(lmh_mon_data->poll_fs)); init_exit: if (ret == -ENODEV) ret = 0; return ret; } static int __init lmh_mon_init_call(void) { int ret = 0; ret = lmh_mon_init_driver(); if (ret) { pr_err("Error initializing the debugfs. err:%d\n", ret); goto lmh_init_exit; } ret = class_register(&lmh_class_info); if (ret) goto lmh_init_exit; lmh_init_exit: if (ret) class_unregister(&lmh_class_info); return ret; } static void lmh_mon_cleanup(void) { down_write(&lmh_mon_access_lock); while (!list_empty(&lmh_sensor_list)) { lmh_sensor_remove(list_first_entry(&lmh_sensor_list, struct lmh_mon_sensor_data, list_ptr)->sensor_ops); } up_write(&lmh_mon_access_lock); debugfs_remove_recursive(lmh_mon_data->debugfs_parent); kfree(lmh_mon_data); } static void lmh_device_cleanup(void) { down_write(&lmh_dev_access_lock); while (!list_empty(&lmh_device_list)) { lmh_device_remove(list_first_entry(&lmh_device_list, struct lmh_device_data, list_ptr)->device_ops); } up_write(&lmh_dev_access_lock); } static void lmh_debug_cleanup(void) { if (lmh_mon_data->debug_ops) { debugfs_remove_recursive(lmh_mon_data->debug_dir); lmh_mon_data->debug_ops = NULL; } } static void __exit lmh_mon_exit(void) { lmh_mon_cleanup(); lmh_device_cleanup(); lmh_debug_cleanup(); class_unregister(&lmh_class_info); } module_init(lmh_mon_init_call); module_exit(lmh_mon_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("LMH monitor driver"); MODULE_ALIAS("platform:" LMH_MON_NAME);