222 lines
5 KiB
C
222 lines
5 KiB
C
/* 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 <soc/qcom/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");
|