| /* Copyright (c) 2008-2010, 2012, Code Aurora Forum. 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. | 
 |  * | 
 |  */ | 
 | /* | 
 |  * Modem Restart Notifier -- Provides notification | 
 |  *			     of modem restart events. | 
 |  */ | 
 |  | 
 | #include <linux/notifier.h> | 
 | #include <linux/init.h> | 
 | #include <linux/debugfs.h> | 
 | #include <linux/module.h> | 
 | #include <linux/workqueue.h> | 
 |  | 
 | #include "modem_notifier.h" | 
 |  | 
 | #define DEBUG | 
 |  | 
 | static struct srcu_notifier_head modem_notifier_list; | 
 | static struct workqueue_struct *modem_notifier_wq; | 
 |  | 
 | static void notify_work_smsm_init(struct work_struct *work) | 
 | { | 
 | 	modem_notify(0, MODEM_NOTIFIER_SMSM_INIT); | 
 | } | 
 | static DECLARE_WORK(modem_notifier_smsm_init_work, ¬ify_work_smsm_init); | 
 |  | 
 | void modem_queue_smsm_init_notify(void) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = queue_work(modem_notifier_wq, &modem_notifier_smsm_init_work); | 
 |  | 
 | 	if (!ret) | 
 | 		printk(KERN_ERR "%s\n", __func__); | 
 | } | 
 | EXPORT_SYMBOL(modem_queue_smsm_init_notify); | 
 |  | 
 | static void notify_work_start_reset(struct work_struct *work) | 
 | { | 
 | 	modem_notify(0, MODEM_NOTIFIER_START_RESET); | 
 | } | 
 | static DECLARE_WORK(modem_notifier_start_reset_work, ¬ify_work_start_reset); | 
 |  | 
 | void modem_queue_start_reset_notify(void) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = queue_work(modem_notifier_wq, &modem_notifier_start_reset_work); | 
 |  | 
 | 	if (!ret) | 
 | 		printk(KERN_ERR "%s\n", __func__); | 
 | } | 
 | EXPORT_SYMBOL(modem_queue_start_reset_notify); | 
 |  | 
 | static void notify_work_end_reset(struct work_struct *work) | 
 | { | 
 | 	modem_notify(0, MODEM_NOTIFIER_END_RESET); | 
 | } | 
 | static DECLARE_WORK(modem_notifier_end_reset_work, ¬ify_work_end_reset); | 
 |  | 
 | void modem_queue_end_reset_notify(void) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = queue_work(modem_notifier_wq, &modem_notifier_end_reset_work); | 
 |  | 
 | 	if (!ret) | 
 | 		printk(KERN_ERR "%s\n", __func__); | 
 | } | 
 | EXPORT_SYMBOL(modem_queue_end_reset_notify); | 
 |  | 
 | int modem_register_notifier(struct notifier_block *nb) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = srcu_notifier_chain_register( | 
 | 		&modem_notifier_list, nb); | 
 |  | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL(modem_register_notifier); | 
 |  | 
 | int modem_unregister_notifier(struct notifier_block *nb) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = srcu_notifier_chain_unregister( | 
 | 		&modem_notifier_list, nb); | 
 |  | 
 | 	return ret; | 
 | } | 
 | EXPORT_SYMBOL(modem_unregister_notifier); | 
 |  | 
 | void modem_notify(void *data, unsigned int state) | 
 | { | 
 | 	srcu_notifier_call_chain(&modem_notifier_list, state, data); | 
 | } | 
 | EXPORT_SYMBOL(modem_notify); | 
 |  | 
 | #if defined(CONFIG_DEBUG_FS) | 
 | static int debug_reset_start(const char __user *buf, int count) | 
 | { | 
 | 	modem_queue_start_reset_notify(); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int debug_reset_end(const char __user *buf, int count) | 
 | { | 
 | 	modem_queue_end_reset_notify(); | 
 | 	return 0; | 
 | } | 
 |  | 
 | static ssize_t debug_write(struct file *file, const char __user *buf, | 
 | 			   size_t count, loff_t *ppos) | 
 | { | 
 | 	int (*fling)(const char __user *buf, int max) = file->private_data; | 
 | 	fling(buf, count); | 
 | 	return count; | 
 | } | 
 |  | 
 | static int debug_open(struct inode *inode, struct file *file) | 
 | { | 
 | 	file->private_data = inode->i_private; | 
 | 	return 0; | 
 | } | 
 |  | 
 | static const struct file_operations debug_ops = { | 
 | 	.write = debug_write, | 
 | 	.open = debug_open, | 
 | }; | 
 |  | 
 | static void debug_create(const char *name, mode_t mode, | 
 | 			 struct dentry *dent, | 
 | 			 int (*fling)(const char __user *buf, int max)) | 
 | { | 
 | 	debugfs_create_file(name, mode, dent, fling, &debug_ops); | 
 | } | 
 |  | 
 | static void modem_notifier_debugfs_init(void) | 
 | { | 
 | 	struct dentry *dent; | 
 |  | 
 | 	dent = debugfs_create_dir("modem_notifier", 0); | 
 | 	if (IS_ERR(dent)) | 
 | 		return; | 
 |  | 
 | 	debug_create("reset_start", 0444, dent, debug_reset_start); | 
 | 	debug_create("reset_end", 0444, dent, debug_reset_end); | 
 | } | 
 | #else | 
 | static void modem_notifier_debugfs_init(void) {} | 
 | #endif | 
 |  | 
 | #if defined(DEBUG) | 
 | static int modem_notifier_test_call(struct notifier_block *this, | 
 | 				  unsigned long code, | 
 | 				  void *_cmd) | 
 | { | 
 | 	switch (code) { | 
 | 	case MODEM_NOTIFIER_START_RESET: | 
 | 		printk(KERN_ERR "Notify: start reset\n"); | 
 | 		break; | 
 | 	case MODEM_NOTIFIER_END_RESET: | 
 | 		printk(KERN_ERR "Notify: end reset\n"); | 
 | 		break; | 
 | 	case MODEM_NOTIFIER_SMSM_INIT: | 
 | 		printk(KERN_ERR "Notify: smsm init\n"); | 
 | 		break; | 
 | 	default: | 
 | 		printk(KERN_ERR "Notify: general\n"); | 
 | 		break; | 
 | 	} | 
 | 	return NOTIFY_DONE; | 
 | } | 
 |  | 
 | static struct notifier_block nb = { | 
 | 	.notifier_call = modem_notifier_test_call, | 
 | }; | 
 |  | 
 | static void register_test_notifier(void) | 
 | { | 
 | 	modem_register_notifier(&nb); | 
 | } | 
 | #endif | 
 |  | 
 | int __init msm_init_modem_notifier_list(void) | 
 | { | 
 | 	static bool registered; | 
 |  | 
 | 	if (registered) | 
 | 		return 0; | 
 |  | 
 | 	registered = true; | 
 |  | 
 | 	srcu_init_notifier_head(&modem_notifier_list); | 
 | 	modem_notifier_debugfs_init(); | 
 | #if defined(DEBUG) | 
 | 	register_test_notifier(); | 
 | #endif | 
 |  | 
 | 	/* Create the workqueue */ | 
 | 	modem_notifier_wq = create_singlethread_workqueue("modem_notifier"); | 
 | 	if (!modem_notifier_wq) { | 
 | 		srcu_cleanup_notifier_head(&modem_notifier_list); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 | module_init(msm_init_modem_notifier_list); |