| /* Copyright (c) 2008-2010, 2012, 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. |
| * |
| */ |
| /* |
| * 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); |