/* Copyright (c) 2011, 2013, 2016 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 #include #include #include #include #include #include #include #include struct subsys_notif_info { char name[50]; struct srcu_notifier_head subsys_notif_rcvr_list; struct atomic_notifier_head atomic_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); int subsys_notif_register_atomic_notifier(void *subsys_handle, struct notifier_block *nb) { struct subsys_notif_info *subsys = (struct subsys_notif_info *)subsys_handle; if (!subsys) return -EINVAL; return atomic_notifier_chain_register( &subsys->atomic_subsys_notif_rcvr_list, nb); } EXPORT_SYMBOL(subsys_notif_register_atomic_notifier); int subsys_notif_unregister_atomic_notifier(void *subsys_handle, struct notifier_block *nb) { struct subsys_notif_info *subsys = (struct subsys_notif_info *)subsys_handle; if (!subsys) return -EINVAL; return atomic_notifier_chain_unregister( &subsys->atomic_subsys_notif_rcvr_list, nb); } EXPORT_SYMBOL(subsys_notif_unregister_atomic_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); ATOMIC_INIT_NOTIFIER_HEAD(&subsys->atomic_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; if (notif_type == SUBSYS_PREPARE_FOR_FATAL_SHUTDOWN) { ret = atomic_notifier_call_chain( &subsys->atomic_subsys_notif_rcvr_list, notif_type, data); } else { 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: pr_warn("%s: Notification %s from subsystem %pK\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); pr_warn("%s: Registered test notifier, handle=%pK", __func__, handle); } #endif MODULE_DESCRIPTION("Subsystem Restart Notifier"); MODULE_VERSION("1.0"); MODULE_LICENSE("GPL v2");