Commit | Line | Data |
---|---|---|
9d9baadd KC |
1 | /* periodic_work.c |
2 | * | |
f6d0c1e6 | 3 | * Copyright (C) 2010 - 2013 UNISYS CORPORATION |
9d9baadd KC |
4 | * All rights reserved. |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation; either version 2 of the License, or (at | |
9 | * your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but | |
12 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or | |
14 | * NON INFRINGEMENT. See the GNU General Public License for more | |
15 | * details. | |
16 | */ | |
17 | ||
18 | /* | |
19 | * Helper functions to schedule periodic work in Linux kernel mode. | |
20 | */ | |
d5b3f1dc | 21 | #include <linux/sched.h> |
9d9baadd | 22 | |
9d9baadd KC |
23 | #include "timskmod.h" |
24 | #include "periodic_work.h" | |
25 | ||
26 | #define MYDRVNAME "periodic_work" | |
27 | ||
2c5653b7 | 28 | struct periodic_work { |
9d9baadd KC |
29 | rwlock_t lock; |
30 | struct delayed_work work; | |
31 | void (*workfunc)(void *); | |
32 | void *workfuncarg; | |
779d0752 PB |
33 | bool is_scheduled; |
34 | bool want_to_stop; | |
9d9baadd KC |
35 | ulong jiffy_interval; |
36 | struct workqueue_struct *workqueue; | |
37 | const char *devnam; | |
38 | }; | |
39 | ||
9d9baadd KC |
40 | static void periodic_work_func(struct work_struct *work) |
41 | { | |
2c5653b7 BR |
42 | struct periodic_work *pw; |
43 | ||
44 | pw = container_of(work, struct periodic_work, work.work); | |
45 | (*pw->workfunc)(pw->workfuncarg); | |
9d9baadd KC |
46 | } |
47 | ||
2c5653b7 BR |
48 | struct periodic_work *visor_periodic_work_create(ulong jiffy_interval, |
49 | struct workqueue_struct *workqueue, | |
50 | void (*workfunc)(void *), | |
51 | void *workfuncarg, | |
52 | const char *devnam) | |
9d9baadd | 53 | { |
2c5653b7 BR |
54 | struct periodic_work *pw; |
55 | ||
56 | pw = kzalloc(sizeof(*pw), GFP_KERNEL | __GFP_NORETRY); | |
57 | if (!pw) | |
9d9baadd | 58 | return NULL; |
2c5653b7 BR |
59 | |
60 | rwlock_init(&pw->lock); | |
61 | pw->jiffy_interval = jiffy_interval; | |
62 | pw->workqueue = workqueue; | |
63 | pw->workfunc = workfunc; | |
64 | pw->workfuncarg = workfuncarg; | |
65 | pw->devnam = devnam; | |
66 | return pw; | |
9d9baadd | 67 | } |
927c7927 | 68 | EXPORT_SYMBOL_GPL(visor_periodic_work_create); |
9d9baadd | 69 | |
2c5653b7 | 70 | void visor_periodic_work_destroy(struct periodic_work *pw) |
9d9baadd | 71 | { |
2c5653b7 | 72 | kfree(pw); |
9d9baadd | 73 | } |
927c7927 | 74 | EXPORT_SYMBOL_GPL(visor_periodic_work_destroy); |
9d9baadd | 75 | |
9d9baadd KC |
76 | /** Call this from your periodic work worker function to schedule the next |
77 | * call. | |
779d0752 | 78 | * If this function returns false, there was a failure and the |
9d9baadd KC |
79 | * periodic work is no longer scheduled |
80 | */ | |
779d0752 | 81 | bool visor_periodic_work_nextperiod(struct periodic_work *pw) |
9d9baadd | 82 | { |
779d0752 | 83 | bool rc = false; |
e03e1e39 | 84 | |
2c5653b7 BR |
85 | write_lock(&pw->lock); |
86 | if (pw->want_to_stop) { | |
779d0752 PB |
87 | pw->is_scheduled = false; |
88 | pw->want_to_stop = false; | |
89 | rc = true; /* yes, true; see visor_periodic_work_stop() */ | |
2c5653b7 BR |
90 | goto unlock; |
91 | } else if (queue_delayed_work(pw->workqueue, &pw->work, | |
92 | pw->jiffy_interval) < 0) { | |
779d0752 PB |
93 | pw->is_scheduled = false; |
94 | rc = false; | |
2c5653b7 | 95 | goto unlock; |
9d9baadd | 96 | } |
779d0752 | 97 | rc = true; |
2c5653b7 BR |
98 | unlock: |
99 | write_unlock(&pw->lock); | |
9d9baadd KC |
100 | return rc; |
101 | } | |
927c7927 | 102 | EXPORT_SYMBOL_GPL(visor_periodic_work_nextperiod); |
9d9baadd | 103 | |
779d0752 PB |
104 | /** This function returns true iff new periodic work was actually started. |
105 | * If this function returns false, then no work was started | |
9d9baadd KC |
106 | * (either because it was already started, or because of a failure). |
107 | */ | |
779d0752 | 108 | bool visor_periodic_work_start(struct periodic_work *pw) |
9d9baadd | 109 | { |
779d0752 | 110 | bool rc = false; |
9d9baadd | 111 | |
2c5653b7 BR |
112 | write_lock(&pw->lock); |
113 | if (pw->is_scheduled) { | |
779d0752 | 114 | rc = false; |
2c5653b7 | 115 | goto unlock; |
61e03b43 | 116 | } |
2c5653b7 | 117 | if (pw->want_to_stop) { |
779d0752 | 118 | rc = false; |
2c5653b7 | 119 | goto unlock; |
9d9baadd | 120 | } |
2c5653b7 BR |
121 | INIT_DELAYED_WORK(&pw->work, &periodic_work_func); |
122 | if (queue_delayed_work(pw->workqueue, &pw->work, | |
123 | pw->jiffy_interval) < 0) { | |
779d0752 | 124 | rc = false; |
2c5653b7 | 125 | goto unlock; |
9d9baadd | 126 | } |
779d0752 PB |
127 | pw->is_scheduled = true; |
128 | rc = true; | |
2c5653b7 BR |
129 | unlock: |
130 | write_unlock(&pw->lock); | |
9d9baadd | 131 | return rc; |
9d9baadd | 132 | } |
927c7927 | 133 | EXPORT_SYMBOL_GPL(visor_periodic_work_start); |
9d9baadd | 134 | |
779d0752 | 135 | /** This function returns true iff your call actually stopped the periodic |
9d9baadd KC |
136 | * work. |
137 | * | |
138 | * -- PAY ATTENTION... this is important -- | |
139 | * | |
140 | * NO NO #1 | |
141 | * | |
142 | * Do NOT call this function from some function that is running on the | |
143 | * same workqueue as the work you are trying to stop might be running | |
927c7927 KC |
144 | * on! If you violate this rule, visor_periodic_work_stop() MIGHT work, |
145 | * but it also MIGHT get hung up in an infinite loop saying | |
9d9baadd KC |
146 | * "waiting for delayed work...". This will happen if the delayed work |
147 | * you are trying to cancel has been put in the workqueue list, but can't | |
148 | * run yet because we are running that same workqueue thread right now. | |
149 | * | |
927c7927 KC |
150 | * Bottom line: If you need to call visor_periodic_work_stop() from a |
151 | * workitem, be sure the workitem is on a DIFFERENT workqueue than the | |
152 | * workitem that you are trying to cancel. | |
9d9baadd KC |
153 | * |
154 | * If I could figure out some way to check for this "no no" condition in | |
155 | * the code, I would. It would have saved me the trouble of writing this | |
156 | * long comment. And also, don't think this is some "theoretical" race | |
157 | * condition. It is REAL, as I have spent the day chasing it. | |
158 | * | |
159 | * NO NO #2 | |
160 | * | |
161 | * Take close note of the locks that you own when you call this function. | |
162 | * You must NOT own any locks that are needed by the periodic work | |
163 | * function that is currently installed. If you DO, a deadlock may result, | |
164 | * because stopping the periodic work often involves waiting for the last | |
165 | * iteration of the periodic work function to complete. Again, if you hit | |
166 | * this deadlock, you will get hung up in an infinite loop saying | |
167 | * "waiting for delayed work...". | |
168 | */ | |
779d0752 | 169 | bool visor_periodic_work_stop(struct periodic_work *pw) |
9d9baadd | 170 | { |
779d0752 | 171 | bool stopped_something = false; |
9d9baadd | 172 | |
2c5653b7 BR |
173 | write_lock(&pw->lock); |
174 | stopped_something = pw->is_scheduled && (!pw->want_to_stop); | |
175 | while (pw->is_scheduled) { | |
779d0752 | 176 | pw->want_to_stop = true; |
2c5653b7 | 177 | if (cancel_delayed_work(&pw->work)) { |
9d9baadd KC |
178 | /* We get here if the delayed work was pending as |
179 | * delayed work, but was NOT run. | |
180 | */ | |
1a84fec1 | 181 | WARN_ON(!pw->is_scheduled); |
779d0752 | 182 | pw->is_scheduled = false; |
9d9baadd KC |
183 | } else { |
184 | /* If we get here, either the delayed work: | |
185 | * - was run, OR, | |
186 | * - is running RIGHT NOW on another processor, OR, | |
187 | * - wasn't even scheduled (there is a miniscule | |
188 | * timing window where this could be the case) | |
189 | * flush_workqueue() would make sure it is finished | |
190 | * executing, but that still isn't very useful, which | |
191 | * explains the loop... | |
192 | */ | |
193 | } | |
2c5653b7 BR |
194 | if (pw->is_scheduled) { |
195 | write_unlock(&pw->lock); | |
d5b3f1dc EA |
196 | __set_current_state(TASK_INTERRUPTIBLE); |
197 | schedule_timeout(10); | |
2c5653b7 | 198 | write_lock(&pw->lock); |
f0b5c6d3 | 199 | } else { |
779d0752 | 200 | pw->want_to_stop = false; |
f0b5c6d3 | 201 | } |
9d9baadd | 202 | } |
2c5653b7 | 203 | write_unlock(&pw->lock); |
9d9baadd KC |
204 | return stopped_something; |
205 | } | |
927c7927 | 206 | EXPORT_SYMBOL_GPL(visor_periodic_work_stop); |