Blame view

kernel/async.c 10.9 KB
22a9d6456   Arjan van de Ven   async: Asynchrono...
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
  /*
   * async.c: Asynchronous function calls for boot performance
   *
   * (C) Copyright 2009 Intel Corporation
   * Author: Arjan van de Ven <arjan@linux.intel.com>
   *
   * This program is free software; you can redistribute it and/or
   * modify it under the terms of the GNU General Public License
   * as published by the Free Software Foundation; version 2
   * of the License.
   */
  
  
  /*
  
  Goals and Theory of Operation
  
  The primary goal of this feature is to reduce the kernel boot time,
  by doing various independent hardware delays and discovery operations
  decoupled and not strictly serialized.
  
  More specifically, the asynchronous function call concept allows
  certain operations (primarily during system boot) to happen
  asynchronously, out of order, while these operations still
  have their externally visible parts happen sequentially and in-order.
  (not unlike how out-of-order CPUs retire their instructions in order)
  
  Key to the asynchronous function call implementation is the concept of
  a "sequence cookie" (which, although it has an abstracted type, can be
  thought of as a monotonically incrementing number).
  
  The async core will assign each scheduled event such a sequence cookie and
  pass this to the called functions.
  
  The asynchronously called function should before doing a globally visible
  operation, such as registering device numbers, call the
  async_synchronize_cookie() function and pass in its own cookie. The
  async_synchronize_cookie() function will make sure that all asynchronous
  operations that were scheduled prior to the operation corresponding with the
  cookie have completed.
  
  Subsystem/driver initialization code that scheduled asynchronous probe
  functions, but which shares global resources with other drivers/subsystems
  that do not use the asynchronous call feature, need to do a full
  synchronization with the async_synchronize_full() function, before returning
  from their init function. This is to maintain strict ordering between the
  asynchronous and synchronous parts of the kernel.
  
  */
  
  #include <linux/async.h>
971079438   Arjan van de Ven   async: remove the...
52
  #include <linux/bug.h>
22a9d6456   Arjan van de Ven   async: Asynchrono...
53
54
55
56
57
  #include <linux/module.h>
  #include <linux/wait.h>
  #include <linux/sched.h>
  #include <linux/init.h>
  #include <linux/kthread.h>
86532d8b1   Cornelia Huck   async: Handle kth...
58
  #include <linux/delay.h>
5a0e3ad6a   Tejun Heo   include cleanup: ...
59
  #include <linux/slab.h>
22a9d6456   Arjan van de Ven   async: Asynchrono...
60
61
62
63
64
65
66
67
68
69
  #include <asm/atomic.h>
  
  static async_cookie_t next_cookie = 1;
  
  #define MAX_THREADS	256
  #define MAX_WORK	32768
  
  static LIST_HEAD(async_pending);
  static LIST_HEAD(async_running);
  static DEFINE_SPINLOCK(async_lock);
cdb80f630   Arjan van de Ven   async: make async...
70
  static int async_enabled = 0;
22a9d6456   Arjan van de Ven   async: Asynchrono...
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
  struct async_entry {
  	struct list_head list;
  	async_cookie_t   cookie;
  	async_func_ptr	 *func;
  	void             *data;
  	struct list_head *running;
  };
  
  static DECLARE_WAIT_QUEUE_HEAD(async_done);
  static DECLARE_WAIT_QUEUE_HEAD(async_new);
  
  static atomic_t entry_count;
  static atomic_t thread_count;
  
  extern int initcall_debug;
  
  
  /*
   * MUST be called with the lock held!
   */
  static async_cookie_t  __lowest_in_progress(struct list_head *running)
  {
  	struct async_entry *entry;
d5a877e8d   James Bottomley   async: make sure ...
94

37a76bd4f   Arjan van de Ven   async: fix __lowe...
95
96
  	if (!list_empty(running)) {
  		entry = list_first_entry(running,
22a9d6456   Arjan van de Ven   async: Asynchrono...
97
  			struct async_entry, list);
3af968e06   Linus Torvalds   async: Fix lack o...
98
  		return entry->cookie;
22a9d6456   Arjan van de Ven   async: Asynchrono...
99
  	}
3af968e06   Linus Torvalds   async: Fix lack o...
100
101
102
  	list_for_each_entry(entry, &async_pending, list)
  		if (entry->running == running)
  			return entry->cookie;
d5a877e8d   James Bottomley   async: make sure ...
103

3af968e06   Linus Torvalds   async: Fix lack o...
104
  	return next_cookie;	/* "infinity" value */
22a9d6456   Arjan van de Ven   async: Asynchrono...
105
  }
37a76bd4f   Arjan van de Ven   async: fix __lowe...
106
107
108
109
110
111
112
113
114
115
116
  
  static async_cookie_t  lowest_in_progress(struct list_head *running)
  {
  	unsigned long flags;
  	async_cookie_t ret;
  
  	spin_lock_irqsave(&async_lock, flags);
  	ret = __lowest_in_progress(running);
  	spin_unlock_irqrestore(&async_lock, flags);
  	return ret;
  }
22a9d6456   Arjan van de Ven   async: Asynchrono...
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
  /*
   * pick the first pending entry and run it
   */
  static void run_one_entry(void)
  {
  	unsigned long flags;
  	struct async_entry *entry;
  	ktime_t calltime, delta, rettime;
  
  	/* 1) pick one task from the pending queue */
  
  	spin_lock_irqsave(&async_lock, flags);
  	if (list_empty(&async_pending))
  		goto out;
  	entry = list_first_entry(&async_pending, struct async_entry, list);
  
  	/* 2) move it to the running queue */
f7de7621f   Stefan Richter   async: use list_m...
134
  	list_move_tail(&entry->list, entry->running);
22a9d6456   Arjan van de Ven   async: Asynchrono...
135
136
137
  	spin_unlock_irqrestore(&async_lock, flags);
  
  	/* 3) run it (and print duration)*/
ad160d231   Arjan van de Ven   async: don't do t...
138
  	if (initcall_debug && system_state == SYSTEM_BOOTING) {
58763a297   Andrew Morton   kernel/async.c: f...
139
140
141
  		printk("calling  %lli_%pF @ %i
  ", (long long)entry->cookie,
  			entry->func, task_pid_nr(current));
22a9d6456   Arjan van de Ven   async: Asynchrono...
142
143
144
  		calltime = ktime_get();
  	}
  	entry->func(entry->data, entry->cookie);
ad160d231   Arjan van de Ven   async: don't do t...
145
  	if (initcall_debug && system_state == SYSTEM_BOOTING) {
22a9d6456   Arjan van de Ven   async: Asynchrono...
146
147
  		rettime = ktime_get();
  		delta = ktime_sub(rettime, calltime);
58763a297   Andrew Morton   kernel/async.c: f...
148
149
150
151
152
  		printk("initcall %lli_%pF returned 0 after %lld usecs
  ",
  			(long long)entry->cookie,
  			entry->func,
  			(long long)ktime_to_ns(delta) >> 10);
22a9d6456   Arjan van de Ven   async: Asynchrono...
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
  	}
  
  	/* 4) remove it from the running queue */
  	spin_lock_irqsave(&async_lock, flags);
  	list_del(&entry->list);
  
  	/* 5) free the entry  */
  	kfree(entry);
  	atomic_dec(&entry_count);
  
  	spin_unlock_irqrestore(&async_lock, flags);
  
  	/* 6) wake up any waiters. */
  	wake_up(&async_done);
  	return;
  
  out:
  	spin_unlock_irqrestore(&async_lock, flags);
  }
  
  
  static async_cookie_t __async_schedule(async_func_ptr *ptr, void *data, struct list_head *running)
  {
  	struct async_entry *entry;
  	unsigned long flags;
  	async_cookie_t newcookie;
  	
  
  	/* allow irq-off callers */
  	entry = kzalloc(sizeof(struct async_entry), GFP_ATOMIC);
  
  	/*
  	 * If we're out of memory or if there's too much work
  	 * pending already, we execute synchronously.
  	 */
cdb80f630   Arjan van de Ven   async: make async...
188
  	if (!async_enabled || !entry || atomic_read(&entry_count) > MAX_WORK) {
22a9d6456   Arjan van de Ven   async: Asynchrono...
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
  		kfree(entry);
  		spin_lock_irqsave(&async_lock, flags);
  		newcookie = next_cookie++;
  		spin_unlock_irqrestore(&async_lock, flags);
  
  		/* low on memory.. run synchronously */
  		ptr(data, newcookie);
  		return newcookie;
  	}
  	entry->func = ptr;
  	entry->data = data;
  	entry->running = running;
  
  	spin_lock_irqsave(&async_lock, flags);
  	newcookie = entry->cookie = next_cookie++;
  	list_add_tail(&entry->list, &async_pending);
  	atomic_inc(&entry_count);
  	spin_unlock_irqrestore(&async_lock, flags);
  	wake_up(&async_new);
  	return newcookie;
  }
f30d5b307   Cornelia Huck   async: Add some d...
210
211
212
213
214
215
216
217
  /**
   * async_schedule - schedule a function for asynchronous execution
   * @ptr: function to execute asynchronously
   * @data: data pointer to pass to the function
   *
   * Returns an async_cookie_t that may be used for checkpointing later.
   * Note: This function may be called from atomic or non-atomic contexts.
   */
22a9d6456   Arjan van de Ven   async: Asynchrono...
218
219
  async_cookie_t async_schedule(async_func_ptr *ptr, void *data)
  {
7a89bbc74   Cornelia Huck   async: Fix runnin...
220
  	return __async_schedule(ptr, data, &async_running);
22a9d6456   Arjan van de Ven   async: Asynchrono...
221
222
  }
  EXPORT_SYMBOL_GPL(async_schedule);
f30d5b307   Cornelia Huck   async: Add some d...
223
  /**
766ccb9ed   Cornelia Huck   async: Rename _sp...
224
   * async_schedule_domain - schedule a function for asynchronous execution within a certain domain
f30d5b307   Cornelia Huck   async: Add some d...
225
226
   * @ptr: function to execute asynchronously
   * @data: data pointer to pass to the function
766ccb9ed   Cornelia Huck   async: Rename _sp...
227
   * @running: running list for the domain
f30d5b307   Cornelia Huck   async: Add some d...
228
229
   *
   * Returns an async_cookie_t that may be used for checkpointing later.
766ccb9ed   Cornelia Huck   async: Rename _sp...
230
231
232
   * @running may be used in the async_synchronize_*_domain() functions
   * to wait within a certain synchronization domain rather than globally.
   * A synchronization domain is specified via the running queue @running to use.
f30d5b307   Cornelia Huck   async: Add some d...
233
234
   * Note: This function may be called from atomic or non-atomic contexts.
   */
766ccb9ed   Cornelia Huck   async: Rename _sp...
235
236
  async_cookie_t async_schedule_domain(async_func_ptr *ptr, void *data,
  				     struct list_head *running)
22a9d6456   Arjan van de Ven   async: Asynchrono...
237
238
239
  {
  	return __async_schedule(ptr, data, running);
  }
766ccb9ed   Cornelia Huck   async: Rename _sp...
240
  EXPORT_SYMBOL_GPL(async_schedule_domain);
22a9d6456   Arjan van de Ven   async: Asynchrono...
241

f30d5b307   Cornelia Huck   async: Add some d...
242
243
244
245
246
  /**
   * async_synchronize_full - synchronize all asynchronous function calls
   *
   * This function waits until all asynchronous function calls have been done.
   */
22a9d6456   Arjan van de Ven   async: Asynchrono...
247
248
  void async_synchronize_full(void)
  {
33b04b930   Arjan van de Ven   async: make async...
249
250
251
  	do {
  		async_synchronize_cookie(next_cookie);
  	} while (!list_empty(&async_running) || !list_empty(&async_pending));
22a9d6456   Arjan van de Ven   async: Asynchrono...
252
253
  }
  EXPORT_SYMBOL_GPL(async_synchronize_full);
f30d5b307   Cornelia Huck   async: Add some d...
254
  /**
766ccb9ed   Cornelia Huck   async: Rename _sp...
255
   * async_synchronize_full_domain - synchronize all asynchronous function within a certain domain
f30d5b307   Cornelia Huck   async: Add some d...
256
257
   * @list: running list to synchronize on
   *
766ccb9ed   Cornelia Huck   async: Rename _sp...
258
259
   * This function waits until all asynchronous function calls for the
   * synchronization domain specified by the running list @list have been done.
f30d5b307   Cornelia Huck   async: Add some d...
260
   */
766ccb9ed   Cornelia Huck   async: Rename _sp...
261
  void async_synchronize_full_domain(struct list_head *list)
22a9d6456   Arjan van de Ven   async: Asynchrono...
262
  {
766ccb9ed   Cornelia Huck   async: Rename _sp...
263
  	async_synchronize_cookie_domain(next_cookie, list);
22a9d6456   Arjan van de Ven   async: Asynchrono...
264
  }
766ccb9ed   Cornelia Huck   async: Rename _sp...
265
  EXPORT_SYMBOL_GPL(async_synchronize_full_domain);
22a9d6456   Arjan van de Ven   async: Asynchrono...
266

f30d5b307   Cornelia Huck   async: Add some d...
267
  /**
766ccb9ed   Cornelia Huck   async: Rename _sp...
268
   * async_synchronize_cookie_domain - synchronize asynchronous function calls within a certain domain with cookie checkpointing
f30d5b307   Cornelia Huck   async: Add some d...
269
270
271
   * @cookie: async_cookie_t to use as checkpoint
   * @running: running list to synchronize on
   *
766ccb9ed   Cornelia Huck   async: Rename _sp...
272
273
274
   * This function waits until all asynchronous function calls for the
   * synchronization domain specified by the running list @list submitted
   * prior to @cookie have been done.
f30d5b307   Cornelia Huck   async: Add some d...
275
   */
766ccb9ed   Cornelia Huck   async: Rename _sp...
276
277
  void async_synchronize_cookie_domain(async_cookie_t cookie,
  				     struct list_head *running)
22a9d6456   Arjan van de Ven   async: Asynchrono...
278
279
  {
  	ktime_t starttime, delta, endtime;
ad160d231   Arjan van de Ven   async: don't do t...
280
  	if (initcall_debug && system_state == SYSTEM_BOOTING) {
22a9d6456   Arjan van de Ven   async: Asynchrono...
281
282
283
284
  		printk("async_waiting @ %i
  ", task_pid_nr(current));
  		starttime = ktime_get();
  	}
37a76bd4f   Arjan van de Ven   async: fix __lowe...
285
  	wait_event(async_done, lowest_in_progress(running) >= cookie);
22a9d6456   Arjan van de Ven   async: Asynchrono...
286

ad160d231   Arjan van de Ven   async: don't do t...
287
  	if (initcall_debug && system_state == SYSTEM_BOOTING) {
22a9d6456   Arjan van de Ven   async: Asynchrono...
288
289
290
291
292
  		endtime = ktime_get();
  		delta = ktime_sub(endtime, starttime);
  
  		printk("async_continuing @ %i after %lli usec
  ",
58763a297   Andrew Morton   kernel/async.c: f...
293
294
  			task_pid_nr(current),
  			(long long)ktime_to_ns(delta) >> 10);
22a9d6456   Arjan van de Ven   async: Asynchrono...
295
296
  	}
  }
766ccb9ed   Cornelia Huck   async: Rename _sp...
297
  EXPORT_SYMBOL_GPL(async_synchronize_cookie_domain);
22a9d6456   Arjan van de Ven   async: Asynchrono...
298

f30d5b307   Cornelia Huck   async: Add some d...
299
300
301
302
303
304
305
  /**
   * async_synchronize_cookie - synchronize asynchronous function calls with cookie checkpointing
   * @cookie: async_cookie_t to use as checkpoint
   *
   * This function waits until all asynchronous function calls prior to @cookie
   * have been done.
   */
22a9d6456   Arjan van de Ven   async: Asynchrono...
306
307
  void async_synchronize_cookie(async_cookie_t cookie)
  {
766ccb9ed   Cornelia Huck   async: Rename _sp...
308
  	async_synchronize_cookie_domain(cookie, &async_running);
22a9d6456   Arjan van de Ven   async: Asynchrono...
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
  }
  EXPORT_SYMBOL_GPL(async_synchronize_cookie);
  
  
  static int async_thread(void *unused)
  {
  	DECLARE_WAITQUEUE(wq, current);
  	add_wait_queue(&async_new, &wq);
  
  	while (!kthread_should_stop()) {
  		int ret = HZ;
  		set_current_state(TASK_INTERRUPTIBLE);
  		/*
  		 * check the list head without lock.. false positives
  		 * are dealt with inside run_one_entry() while holding
  		 * the lock.
  		 */
  		rmb();
  		if (!list_empty(&async_pending))
  			run_one_entry();
  		else
  			ret = schedule_timeout(HZ);
  
  		if (ret == 0) {
  			/*
  			 * we timed out, this means we as thread are redundant.
  			 * we sign off and die, but we to avoid any races there
  			 * is a last-straw check to see if work snuck in.
  			 */
  			atomic_dec(&thread_count);
  			wmb(); /* manager must see our departure first */
  			if (list_empty(&async_pending))
  				break;
  			/*
  			 * woops work came in between us timing out and us
  			 * signing off; we need to stay alive and keep working.
  			 */
  			atomic_inc(&thread_count);
  		}
  	}
  	remove_wait_queue(&async_new, &wq);
  
  	return 0;
  }
  
  static int async_manager_thread(void *unused)
  {
  	DECLARE_WAITQUEUE(wq, current);
  	add_wait_queue(&async_new, &wq);
  
  	while (!kthread_should_stop()) {
  		int tc, ec;
  
  		set_current_state(TASK_INTERRUPTIBLE);
  
  		tc = atomic_read(&thread_count);
  		rmb();
  		ec = atomic_read(&entry_count);
  
  		while (tc < ec && tc < MAX_THREADS) {
86532d8b1   Cornelia Huck   async: Handle kth...
369
370
371
372
373
  			if (IS_ERR(kthread_run(async_thread, NULL, "async/%i",
  					       tc))) {
  				msleep(100);
  				continue;
  			}
22a9d6456   Arjan van de Ven   async: Asynchrono...
374
375
376
377
378
379
380
381
382
383
384
385
386
  			atomic_inc(&thread_count);
  			tc++;
  		}
  
  		schedule();
  	}
  	remove_wait_queue(&async_new, &wq);
  
  	return 0;
  }
  
  static int __init async_init(void)
  {
971079438   Arjan van de Ven   async: remove the...
387
388
  	async_enabled =
  		!IS_ERR(kthread_run(async_manager_thread, NULL, "async/mgr"));
22a9d6456   Arjan van de Ven   async: Asynchrono...
389

971079438   Arjan van de Ven   async: remove the...
390
391
  	WARN_ON(!async_enabled);
  	return 0;
cdb80f630   Arjan van de Ven   async: make async...
392
  }
22a9d6456   Arjan van de Ven   async: Asynchrono...
393
  core_initcall(async_init);