diff options
Diffstat (limited to 'arch/arm/mach-exynos/subsystem_notif.c')
-rw-r--r-- | arch/arm/mach-exynos/subsystem_notif.c | 222 |
1 files changed, 222 insertions, 0 deletions
diff --git a/arch/arm/mach-exynos/subsystem_notif.c b/arch/arm/mach-exynos/subsystem_notif.c new file mode 100644 index 0000000..f7db54c --- /dev/null +++ b/arch/arm/mach-exynos/subsystem_notif.c @@ -0,0 +1,222 @@ +/* Copyright (c) 2011, 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. + * + * + * 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) +{ + 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, + (void *)subsys); + + 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"); |