Commit | Line | Data |
---|---|---|
2874c5fd | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
ff84136c VZ |
2 | /* |
3 | * Copyright (C) 2015-2016 Mentor Graphics | |
ff84136c VZ |
4 | */ |
5 | ||
6 | #include <linux/list.h> | |
7 | #include <linux/slab.h> | |
8 | #include <linux/spinlock.h> | |
53f96cee | 9 | #include <linux/string.h> |
ff84136c VZ |
10 | #include <linux/watchdog.h> |
11 | ||
7b7d2fdc | 12 | #include "watchdog_core.h" |
ff84136c VZ |
13 | #include "watchdog_pretimeout.h" |
14 | ||
15 | /* Default watchdog pretimeout governor */ | |
16 | static struct watchdog_governor *default_gov; | |
17 | ||
18 | /* The spinlock protects default_gov, wdd->gov and pretimeout_list */ | |
19 | static DEFINE_SPINLOCK(pretimeout_lock); | |
20 | ||
21 | /* List of watchdog devices, which can generate a pretimeout event */ | |
22 | static LIST_HEAD(pretimeout_list); | |
23 | ||
24 | struct watchdog_pretimeout { | |
25 | struct watchdog_device *wdd; | |
26 | struct list_head entry; | |
27 | }; | |
28 | ||
53f96cee VZ |
29 | /* The mutex protects governor list and serializes external interfaces */ |
30 | static DEFINE_MUTEX(governor_lock); | |
31 | ||
32 | /* List of the registered watchdog pretimeout governors */ | |
33 | static LIST_HEAD(governor_list); | |
34 | ||
35 | struct governor_priv { | |
36 | struct watchdog_governor *gov; | |
37 | struct list_head entry; | |
38 | }; | |
39 | ||
40 | static struct governor_priv *find_governor_by_name(const char *gov_name) | |
41 | { | |
42 | struct governor_priv *priv; | |
43 | ||
44 | list_for_each_entry(priv, &governor_list, entry) | |
45 | if (sysfs_streq(gov_name, priv->gov->name)) | |
46 | return priv; | |
47 | ||
48 | return NULL; | |
49 | } | |
50 | ||
89873a71 VZ |
51 | int watchdog_pretimeout_available_governors_get(char *buf) |
52 | { | |
53 | struct governor_priv *priv; | |
54 | int count = 0; | |
55 | ||
56 | mutex_lock(&governor_lock); | |
57 | ||
58 | list_for_each_entry(priv, &governor_list, entry) | |
3bb21781 | 59 | count += sysfs_emit_at(buf, count, "%s\n", priv->gov->name); |
89873a71 VZ |
60 | |
61 | mutex_unlock(&governor_lock); | |
62 | ||
63 | return count; | |
64 | } | |
65 | ||
ff84136c VZ |
66 | int watchdog_pretimeout_governor_get(struct watchdog_device *wdd, char *buf) |
67 | { | |
68 | int count = 0; | |
69 | ||
70 | spin_lock_irq(&pretimeout_lock); | |
71 | if (wdd->gov) | |
3bb21781 | 72 | count = sysfs_emit(buf, "%s\n", wdd->gov->name); |
ff84136c VZ |
73 | spin_unlock_irq(&pretimeout_lock); |
74 | ||
75 | return count; | |
76 | } | |
77 | ||
53f96cee VZ |
78 | int watchdog_pretimeout_governor_set(struct watchdog_device *wdd, |
79 | const char *buf) | |
80 | { | |
81 | struct governor_priv *priv; | |
82 | ||
83 | mutex_lock(&governor_lock); | |
84 | ||
85 | priv = find_governor_by_name(buf); | |
86 | if (!priv) { | |
87 | mutex_unlock(&governor_lock); | |
88 | return -EINVAL; | |
89 | } | |
90 | ||
91 | spin_lock_irq(&pretimeout_lock); | |
92 | wdd->gov = priv->gov; | |
93 | spin_unlock_irq(&pretimeout_lock); | |
94 | ||
95 | mutex_unlock(&governor_lock); | |
96 | ||
97 | return 0; | |
98 | } | |
99 | ||
ff84136c VZ |
100 | void watchdog_notify_pretimeout(struct watchdog_device *wdd) |
101 | { | |
102 | unsigned long flags; | |
103 | ||
104 | spin_lock_irqsave(&pretimeout_lock, flags); | |
105 | if (!wdd->gov) { | |
106 | spin_unlock_irqrestore(&pretimeout_lock, flags); | |
107 | return; | |
108 | } | |
109 | ||
110 | wdd->gov->pretimeout(wdd); | |
111 | spin_unlock_irqrestore(&pretimeout_lock, flags); | |
112 | } | |
113 | EXPORT_SYMBOL_GPL(watchdog_notify_pretimeout); | |
114 | ||
115 | int watchdog_register_governor(struct watchdog_governor *gov) | |
116 | { | |
117 | struct watchdog_pretimeout *p; | |
53f96cee VZ |
118 | struct governor_priv *priv; |
119 | ||
120 | priv = kzalloc(sizeof(*priv), GFP_KERNEL); | |
121 | if (!priv) | |
122 | return -ENOMEM; | |
123 | ||
124 | mutex_lock(&governor_lock); | |
125 | ||
126 | if (find_governor_by_name(gov->name)) { | |
127 | mutex_unlock(&governor_lock); | |
128 | kfree(priv); | |
129 | return -EBUSY; | |
130 | } | |
131 | ||
132 | priv->gov = gov; | |
133 | list_add(&priv->entry, &governor_list); | |
ff84136c | 134 | |
da0d12ff VZ |
135 | if (!strncmp(gov->name, WATCHDOG_PRETIMEOUT_DEFAULT_GOV, |
136 | WATCHDOG_GOV_NAME_MAXLEN)) { | |
ff84136c VZ |
137 | spin_lock_irq(&pretimeout_lock); |
138 | default_gov = gov; | |
139 | ||
140 | list_for_each_entry(p, &pretimeout_list, entry) | |
141 | if (!p->wdd->gov) | |
142 | p->wdd->gov = default_gov; | |
143 | spin_unlock_irq(&pretimeout_lock); | |
144 | } | |
145 | ||
53f96cee VZ |
146 | mutex_unlock(&governor_lock); |
147 | ||
ff84136c VZ |
148 | return 0; |
149 | } | |
150 | EXPORT_SYMBOL(watchdog_register_governor); | |
151 | ||
152 | void watchdog_unregister_governor(struct watchdog_governor *gov) | |
153 | { | |
154 | struct watchdog_pretimeout *p; | |
53f96cee VZ |
155 | struct governor_priv *priv, *t; |
156 | ||
157 | mutex_lock(&governor_lock); | |
158 | ||
159 | list_for_each_entry_safe(priv, t, &governor_list, entry) { | |
160 | if (priv->gov == gov) { | |
161 | list_del(&priv->entry); | |
162 | kfree(priv); | |
163 | break; | |
164 | } | |
165 | } | |
ff84136c VZ |
166 | |
167 | spin_lock_irq(&pretimeout_lock); | |
ff84136c VZ |
168 | list_for_each_entry(p, &pretimeout_list, entry) |
169 | if (p->wdd->gov == gov) | |
170 | p->wdd->gov = default_gov; | |
171 | spin_unlock_irq(&pretimeout_lock); | |
53f96cee VZ |
172 | |
173 | mutex_unlock(&governor_lock); | |
ff84136c VZ |
174 | } |
175 | EXPORT_SYMBOL(watchdog_unregister_governor); | |
176 | ||
177 | int watchdog_register_pretimeout(struct watchdog_device *wdd) | |
178 | { | |
179 | struct watchdog_pretimeout *p; | |
180 | ||
7b7d2fdc | 181 | if (!watchdog_have_pretimeout(wdd)) |
ff84136c VZ |
182 | return 0; |
183 | ||
184 | p = kzalloc(sizeof(*p), GFP_KERNEL); | |
185 | if (!p) | |
186 | return -ENOMEM; | |
187 | ||
188 | spin_lock_irq(&pretimeout_lock); | |
189 | list_add(&p->entry, &pretimeout_list); | |
190 | p->wdd = wdd; | |
191 | wdd->gov = default_gov; | |
192 | spin_unlock_irq(&pretimeout_lock); | |
193 | ||
194 | return 0; | |
195 | } | |
196 | ||
197 | void watchdog_unregister_pretimeout(struct watchdog_device *wdd) | |
198 | { | |
199 | struct watchdog_pretimeout *p, *t; | |
200 | ||
7b7d2fdc | 201 | if (!watchdog_have_pretimeout(wdd)) |
ff84136c VZ |
202 | return; |
203 | ||
204 | spin_lock_irq(&pretimeout_lock); | |
205 | wdd->gov = NULL; | |
206 | ||
207 | list_for_each_entry_safe(p, t, &pretimeout_list, entry) { | |
208 | if (p->wdd == wdd) { | |
209 | list_del(&p->entry); | |
210 | break; | |
211 | } | |
212 | } | |
213 | spin_unlock_irq(&pretimeout_lock); | |
214 | ||
215 | kfree(p); | |
216 | } |