aboutsummaryrefslogtreecommitdiffstats
path: root/arch/arm/mach-exynos/subsystem_notif.c
blob: f7db54c1b39308e74f6aa1b9e1e3c564a6513df0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
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(&notif_lock);
	list_for_each_entry(subsys, &subsystem_list, list)
		if (!strncmp(subsys->name, subsys_name,
				ARRAY_SIZE(subsys->name))) {
			mutex_unlock(&notif_lock);
			return subsys;
		}
	mutex_unlock(&notif_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(&notif_add_lock);

	subsys = _notif_find_subsys(subsys_name);

	if (subsys) {
		mutex_unlock(&notif_add_lock);
		goto done;
	}

	subsys = kmalloc(sizeof(struct subsys_notif_info), GFP_KERNEL);

	if (!subsys) {
		mutex_unlock(&notif_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(&notif_lock);
	list_add_tail(&subsys->list, &subsystem_list);
	mutex_unlock(&notif_lock);

	#if defined(SUBSYS_RESTART_DEBUG)
	subsys_notif_reg_test_notifier(subsys->name);
	#endif

	mutex_unlock(&notif_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");