| /* Copyright (c) 2011, 2013, 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. |
| * |
| * |
| * Subsystem Notifier -- Provides notifications |
| * of subsys events. |
| * |
| * Use subsys_notif_register_notifier to register for notifications |
| * and subsys_notif_queue_notification to send notifications. |
| * |
| */ |
| |
| #include <linux/notifier.h> |
| #include <linux/init.h> |
| #include <linux/debugfs.h> |
| #include <linux/module.h> |
| #include <linux/workqueue.h> |
| #include <linux/stringify.h> |
| #include <linux/delay.h> |
| #include <linux/slab.h> |
| |
| #include <mach/subsystem_notif.h> |
| |
| struct subsys_notif_info { |
| char name[50]; |
| struct srcu_notifier_head subsys_notif_rcvr_list; |
| struct list_head list; |
| }; |
| |
| static LIST_HEAD(subsystem_list); |
| static DEFINE_MUTEX(notif_lock); |
| static DEFINE_MUTEX(notif_add_lock); |
| |
| #if defined(SUBSYS_RESTART_DEBUG) |
| static void subsys_notif_reg_test_notifier(const char *); |
| #endif |
| |
| static struct subsys_notif_info *_notif_find_subsys(const char *subsys_name) |
| { |
| struct subsys_notif_info *subsys; |
| |
| mutex_lock(¬if_lock); |
| list_for_each_entry(subsys, &subsystem_list, list) |
| if (!strncmp(subsys->name, subsys_name, |
| ARRAY_SIZE(subsys->name))) { |
| mutex_unlock(¬if_lock); |
| return subsys; |
| } |
| mutex_unlock(¬if_lock); |
| |
| return NULL; |
| } |
| |
| void *subsys_notif_register_notifier( |
| const char *subsys_name, struct notifier_block *nb) |
| { |
| int ret; |
| struct subsys_notif_info *subsys = _notif_find_subsys(subsys_name); |
| |
| if (!subsys) { |
| |
| /* Possible first time reference to this subsystem. Add it. */ |
| subsys = (struct subsys_notif_info *) |
| subsys_notif_add_subsys(subsys_name); |
| |
| if (!subsys) |
| return ERR_PTR(-EINVAL); |
| } |
| |
| ret = srcu_notifier_chain_register( |
| &subsys->subsys_notif_rcvr_list, nb); |
| |
| if (ret < 0) |
| return ERR_PTR(ret); |
| |
| return subsys; |
| } |
| EXPORT_SYMBOL(subsys_notif_register_notifier); |
| |
| int subsys_notif_unregister_notifier(void *subsys_handle, |
| struct notifier_block *nb) |
| { |
| int ret; |
| struct subsys_notif_info *subsys = |
| (struct subsys_notif_info *)subsys_handle; |
| |
| if (!subsys) |
| return -EINVAL; |
| |
| ret = srcu_notifier_chain_unregister( |
| &subsys->subsys_notif_rcvr_list, nb); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(subsys_notif_unregister_notifier); |
| |
| void *subsys_notif_add_subsys(const char *subsys_name) |
| { |
| struct subsys_notif_info *subsys = NULL; |
| |
| if (!subsys_name) |
| goto done; |
| |
| mutex_lock(¬if_add_lock); |
| |
| subsys = _notif_find_subsys(subsys_name); |
| |
| if (subsys) { |
| mutex_unlock(¬if_add_lock); |
| goto done; |
| } |
| |
| subsys = kmalloc(sizeof(struct subsys_notif_info), GFP_KERNEL); |
| |
| if (!subsys) { |
| mutex_unlock(¬if_add_lock); |
| return ERR_PTR(-EINVAL); |
| } |
| |
| strlcpy(subsys->name, subsys_name, ARRAY_SIZE(subsys->name)); |
| |
| srcu_init_notifier_head(&subsys->subsys_notif_rcvr_list); |
| |
| INIT_LIST_HEAD(&subsys->list); |
| |
| mutex_lock(¬if_lock); |
| list_add_tail(&subsys->list, &subsystem_list); |
| mutex_unlock(¬if_lock); |
| |
| #if defined(SUBSYS_RESTART_DEBUG) |
| subsys_notif_reg_test_notifier(subsys->name); |
| #endif |
| |
| mutex_unlock(¬if_add_lock); |
| |
| done: |
| return subsys; |
| } |
| EXPORT_SYMBOL(subsys_notif_add_subsys); |
| |
| int subsys_notif_queue_notification(void *subsys_handle, |
| enum subsys_notif_type notif_type, |
| void *data) |
| { |
| int ret = 0; |
| struct subsys_notif_info *subsys = |
| (struct subsys_notif_info *) subsys_handle; |
| |
| if (!subsys) |
| return -EINVAL; |
| |
| if (notif_type < 0 || notif_type >= SUBSYS_NOTIF_TYPE_COUNT) |
| return -EINVAL; |
| |
| ret = srcu_notifier_call_chain( |
| &subsys->subsys_notif_rcvr_list, notif_type, |
| data); |
| return ret; |
| } |
| EXPORT_SYMBOL(subsys_notif_queue_notification); |
| |
| #if defined(SUBSYS_RESTART_DEBUG) |
| static const char *notif_to_string(enum subsys_notif_type notif_type) |
| { |
| switch (notif_type) { |
| |
| case SUBSYS_BEFORE_SHUTDOWN: |
| return __stringify(SUBSYS_BEFORE_SHUTDOWN); |
| |
| case SUBSYS_AFTER_SHUTDOWN: |
| return __stringify(SUBSYS_AFTER_SHUTDOWN); |
| |
| case SUBSYS_BEFORE_POWERUP: |
| return __stringify(SUBSYS_BEFORE_POWERUP); |
| |
| case SUBSYS_AFTER_POWERUP: |
| return __stringify(SUBSYS_AFTER_POWERUP); |
| |
| default: |
| return "unknown"; |
| } |
| } |
| |
| static int subsys_notifier_test_call(struct notifier_block *this, |
| unsigned long code, |
| void *data) |
| { |
| switch (code) { |
| |
| default: |
| printk(KERN_WARNING "%s: Notification %s from subsystem %p\n", |
| __func__, notif_to_string(code), data); |
| break; |
| |
| } |
| |
| return NOTIFY_DONE; |
| } |
| |
| static struct notifier_block nb = { |
| .notifier_call = subsys_notifier_test_call, |
| }; |
| |
| static void subsys_notif_reg_test_notifier(const char *subsys_name) |
| { |
| void *handle = subsys_notif_register_notifier(subsys_name, &nb); |
| printk(KERN_WARNING "%s: Registered test notifier, handle=%p", |
| __func__, handle); |
| } |
| #endif |
| |
| MODULE_DESCRIPTION("Subsystem Restart Notifier"); |
| MODULE_VERSION("1.0"); |
| MODULE_LICENSE("GPL v2"); |