blob: 4c2843c0043ea205d358d7c65b086f290e09cd68 [file] [log] [blame]
Arjan van de Ven22a9d642009-01-07 08:45:46 -08001/*
2 * async.c: Asynchronous function calls for boot performance
3 *
4 * (C) Copyright 2009 Intel Corporation
5 * Author: Arjan van de Ven <arjan@linux.intel.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; version 2
10 * of the License.
11 */
12
13
14/*
15
16Goals and Theory of Operation
17
18The primary goal of this feature is to reduce the kernel boot time,
19by doing various independent hardware delays and discovery operations
20decoupled and not strictly serialized.
21
22More specifically, the asynchronous function call concept allows
23certain operations (primarily during system boot) to happen
24asynchronously, out of order, while these operations still
25have their externally visible parts happen sequentially and in-order.
26(not unlike how out-of-order CPUs retire their instructions in order)
27
28Key to the asynchronous function call implementation is the concept of
29a "sequence cookie" (which, although it has an abstracted type, can be
30thought of as a monotonically incrementing number).
31
32The async core will assign each scheduled event such a sequence cookie and
33pass this to the called functions.
34
35The asynchronously called function should before doing a globally visible
36operation, such as registering device numbers, call the
37async_synchronize_cookie() function and pass in its own cookie. The
38async_synchronize_cookie() function will make sure that all asynchronous
39operations that were scheduled prior to the operation corresponding with the
40cookie have completed.
41
42Subsystem/driver initialization code that scheduled asynchronous probe
43functions, but which shares global resources with other drivers/subsystems
44that do not use the asynchronous call feature, need to do a full
45synchronization with the async_synchronize_full() function, before returning
46from their init function. This is to maintain strict ordering between the
47asynchronous and synchronous parts of the kernel.
48
49*/
50
51#include <linux/async.h>
Paul McQuade84c15022011-05-31 20:51:55 +010052#include <linux/atomic.h>
53#include <linux/ktime.h>
Arjan van de Ven22a9d642009-01-07 08:45:46 -080054#include <linux/module.h>
55#include <linux/wait.h>
56#include <linux/sched.h>
Tejun Heo5a0e3ad2010-03-24 17:04:11 +090057#include <linux/slab.h>
Tejun Heo083b8042010-07-02 10:03:52 +020058#include <linux/workqueue.h>
Arjan van de Ven22a9d642009-01-07 08:45:46 -080059
60static async_cookie_t next_cookie = 1;
61
Arjan van de Ven22a9d642009-01-07 08:45:46 -080062#define MAX_WORK 32768
63
64static LIST_HEAD(async_pending);
65static LIST_HEAD(async_running);
66static DEFINE_SPINLOCK(async_lock);
67
68struct async_entry {
Tejun Heo083b8042010-07-02 10:03:52 +020069 struct list_head list;
70 struct work_struct work;
71 async_cookie_t cookie;
72 async_func_ptr *func;
73 void *data;
74 struct list_head *running;
Arjan van de Ven22a9d642009-01-07 08:45:46 -080075};
76
77static DECLARE_WAIT_QUEUE_HEAD(async_done);
Arjan van de Ven22a9d642009-01-07 08:45:46 -080078
79static atomic_t entry_count;
Arjan van de Ven22a9d642009-01-07 08:45:46 -080080
81extern int initcall_debug;
82
83
84/*
85 * MUST be called with the lock held!
86 */
87static async_cookie_t __lowest_in_progress(struct list_head *running)
88{
89 struct async_entry *entry;
James Bottomleyd5a877e2009-05-24 13:03:43 -070090
Arjan van de Ven37a76bd2009-01-11 15:35:01 +000091 if (!list_empty(running)) {
92 entry = list_first_entry(running,
Arjan van de Ven22a9d642009-01-07 08:45:46 -080093 struct async_entry, list);
Linus Torvalds3af968e2009-06-08 12:31:53 -070094 return entry->cookie;
Arjan van de Ven22a9d642009-01-07 08:45:46 -080095 }
96
Linus Torvalds3af968e2009-06-08 12:31:53 -070097 list_for_each_entry(entry, &async_pending, list)
98 if (entry->running == running)
99 return entry->cookie;
James Bottomleyd5a877e2009-05-24 13:03:43 -0700100
Linus Torvalds3af968e2009-06-08 12:31:53 -0700101 return next_cookie; /* "infinity" value */
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800102}
Arjan van de Ven37a76bd2009-01-11 15:35:01 +0000103
104static async_cookie_t lowest_in_progress(struct list_head *running)
105{
106 unsigned long flags;
107 async_cookie_t ret;
108
109 spin_lock_irqsave(&async_lock, flags);
110 ret = __lowest_in_progress(running);
111 spin_unlock_irqrestore(&async_lock, flags);
112 return ret;
113}
Tejun Heo083b8042010-07-02 10:03:52 +0200114
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800115/*
116 * pick the first pending entry and run it
117 */
Tejun Heo083b8042010-07-02 10:03:52 +0200118static void async_run_entry_fn(struct work_struct *work)
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800119{
Tejun Heo083b8042010-07-02 10:03:52 +0200120 struct async_entry *entry =
121 container_of(work, struct async_entry, work);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800122 unsigned long flags;
Vitaliy Ivanov124ff4e2011-07-07 14:10:40 +0300123 ktime_t uninitialized_var(calltime), delta, rettime;
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800124
Tejun Heo083b8042010-07-02 10:03:52 +0200125 /* 1) move self to the running queue */
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800126 spin_lock_irqsave(&async_lock, flags);
Stefan Richterf7de7621f2009-02-02 13:24:34 +0100127 list_move_tail(&entry->list, entry->running);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800128 spin_unlock_irqrestore(&async_lock, flags);
129
Tejun Heo083b8042010-07-02 10:03:52 +0200130 /* 2) run (and print duration) */
Arjan van de Venad160d22009-01-07 09:28:53 -0800131 if (initcall_debug && system_state == SYSTEM_BOOTING) {
Paul McQuade84c15022011-05-31 20:51:55 +0100132 printk(KERN_DEBUG "calling %lli_%pF @ %i\n",
133 (long long)entry->cookie,
Andrew Morton58763a22009-02-04 15:11:58 -0800134 entry->func, task_pid_nr(current));
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800135 calltime = ktime_get();
136 }
137 entry->func(entry->data, entry->cookie);
Arjan van de Venad160d22009-01-07 09:28:53 -0800138 if (initcall_debug && system_state == SYSTEM_BOOTING) {
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800139 rettime = ktime_get();
140 delta = ktime_sub(rettime, calltime);
Paul McQuade84c15022011-05-31 20:51:55 +0100141 printk(KERN_DEBUG "initcall %lli_%pF returned 0 after %lld usecs\n",
Andrew Morton58763a22009-02-04 15:11:58 -0800142 (long long)entry->cookie,
143 entry->func,
144 (long long)ktime_to_ns(delta) >> 10);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800145 }
146
Tejun Heo083b8042010-07-02 10:03:52 +0200147 /* 3) remove self from the running queue */
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800148 spin_lock_irqsave(&async_lock, flags);
149 list_del(&entry->list);
150
Tejun Heo083b8042010-07-02 10:03:52 +0200151 /* 4) free the entry */
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800152 kfree(entry);
153 atomic_dec(&entry_count);
154
155 spin_unlock_irqrestore(&async_lock, flags);
156
Tejun Heo083b8042010-07-02 10:03:52 +0200157 /* 5) wake up any waiters */
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800158 wake_up(&async_done);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800159}
160
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800161static async_cookie_t __async_schedule(async_func_ptr *ptr, void *data, struct list_head *running)
162{
163 struct async_entry *entry;
164 unsigned long flags;
165 async_cookie_t newcookie;
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800166
167 /* allow irq-off callers */
168 entry = kzalloc(sizeof(struct async_entry), GFP_ATOMIC);
169
170 /*
171 * If we're out of memory or if there's too much work
172 * pending already, we execute synchronously.
173 */
Tejun Heo083b8042010-07-02 10:03:52 +0200174 if (!entry || atomic_read(&entry_count) > MAX_WORK) {
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800175 kfree(entry);
176 spin_lock_irqsave(&async_lock, flags);
177 newcookie = next_cookie++;
178 spin_unlock_irqrestore(&async_lock, flags);
179
180 /* low on memory.. run synchronously */
181 ptr(data, newcookie);
182 return newcookie;
183 }
Tejun Heo083b8042010-07-02 10:03:52 +0200184 INIT_WORK(&entry->work, async_run_entry_fn);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800185 entry->func = ptr;
186 entry->data = data;
187 entry->running = running;
188
189 spin_lock_irqsave(&async_lock, flags);
190 newcookie = entry->cookie = next_cookie++;
191 list_add_tail(&entry->list, &async_pending);
192 atomic_inc(&entry_count);
193 spin_unlock_irqrestore(&async_lock, flags);
Tejun Heo083b8042010-07-02 10:03:52 +0200194
195 /* schedule for execution */
196 queue_work(system_unbound_wq, &entry->work);
197
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800198 return newcookie;
199}
200
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100201/**
202 * async_schedule - schedule a function for asynchronous execution
203 * @ptr: function to execute asynchronously
204 * @data: data pointer to pass to the function
205 *
206 * Returns an async_cookie_t that may be used for checkpointing later.
207 * Note: This function may be called from atomic or non-atomic contexts.
208 */
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800209async_cookie_t async_schedule(async_func_ptr *ptr, void *data)
210{
Cornelia Huck7a89bbc2009-01-19 13:45:28 +0100211 return __async_schedule(ptr, data, &async_running);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800212}
213EXPORT_SYMBOL_GPL(async_schedule);
214
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100215/**
Cornelia Huck766ccb92009-01-20 15:31:31 +0100216 * async_schedule_domain - schedule a function for asynchronous execution within a certain domain
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100217 * @ptr: function to execute asynchronously
218 * @data: data pointer to pass to the function
Cornelia Huck766ccb92009-01-20 15:31:31 +0100219 * @running: running list for the domain
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100220 *
221 * Returns an async_cookie_t that may be used for checkpointing later.
Cornelia Huck766ccb92009-01-20 15:31:31 +0100222 * @running may be used in the async_synchronize_*_domain() functions
223 * to wait within a certain synchronization domain rather than globally.
224 * A synchronization domain is specified via the running queue @running to use.
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100225 * Note: This function may be called from atomic or non-atomic contexts.
226 */
Cornelia Huck766ccb92009-01-20 15:31:31 +0100227async_cookie_t async_schedule_domain(async_func_ptr *ptr, void *data,
228 struct list_head *running)
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800229{
230 return __async_schedule(ptr, data, running);
231}
Cornelia Huck766ccb92009-01-20 15:31:31 +0100232EXPORT_SYMBOL_GPL(async_schedule_domain);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800233
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100234/**
235 * async_synchronize_full - synchronize all asynchronous function calls
236 *
237 * This function waits until all asynchronous function calls have been done.
238 */
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800239void async_synchronize_full(void)
240{
Arjan van de Ven33b04b92009-01-08 12:35:11 -0800241 do {
242 async_synchronize_cookie(next_cookie);
243 } while (!list_empty(&async_running) || !list_empty(&async_pending));
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800244}
245EXPORT_SYMBOL_GPL(async_synchronize_full);
246
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100247/**
Cornelia Huck766ccb92009-01-20 15:31:31 +0100248 * async_synchronize_full_domain - synchronize all asynchronous function within a certain domain
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100249 * @list: running list to synchronize on
250 *
Cornelia Huck766ccb92009-01-20 15:31:31 +0100251 * This function waits until all asynchronous function calls for the
252 * synchronization domain specified by the running list @list have been done.
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100253 */
Cornelia Huck766ccb92009-01-20 15:31:31 +0100254void async_synchronize_full_domain(struct list_head *list)
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800255{
Cornelia Huck766ccb92009-01-20 15:31:31 +0100256 async_synchronize_cookie_domain(next_cookie, list);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800257}
Cornelia Huck766ccb92009-01-20 15:31:31 +0100258EXPORT_SYMBOL_GPL(async_synchronize_full_domain);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800259
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100260/**
Cornelia Huck766ccb92009-01-20 15:31:31 +0100261 * async_synchronize_cookie_domain - synchronize asynchronous function calls within a certain domain with cookie checkpointing
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100262 * @cookie: async_cookie_t to use as checkpoint
263 * @running: running list to synchronize on
264 *
Cornelia Huck766ccb92009-01-20 15:31:31 +0100265 * This function waits until all asynchronous function calls for the
266 * synchronization domain specified by the running list @list submitted
267 * prior to @cookie have been done.
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100268 */
Cornelia Huck766ccb92009-01-20 15:31:31 +0100269void async_synchronize_cookie_domain(async_cookie_t cookie,
270 struct list_head *running)
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800271{
Vitaliy Ivanov124ff4e2011-07-07 14:10:40 +0300272 ktime_t uninitialized_var(starttime), delta, endtime;
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800273
Arjan van de Venad160d22009-01-07 09:28:53 -0800274 if (initcall_debug && system_state == SYSTEM_BOOTING) {
Paul McQuade84c15022011-05-31 20:51:55 +0100275 printk(KERN_DEBUG "async_waiting @ %i\n", task_pid_nr(current));
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800276 starttime = ktime_get();
277 }
278
Arjan van de Ven37a76bd2009-01-11 15:35:01 +0000279 wait_event(async_done, lowest_in_progress(running) >= cookie);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800280
Arjan van de Venad160d22009-01-07 09:28:53 -0800281 if (initcall_debug && system_state == SYSTEM_BOOTING) {
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800282 endtime = ktime_get();
283 delta = ktime_sub(endtime, starttime);
284
Paul McQuade84c15022011-05-31 20:51:55 +0100285 printk(KERN_DEBUG "async_continuing @ %i after %lli usec\n",
Andrew Morton58763a22009-02-04 15:11:58 -0800286 task_pid_nr(current),
287 (long long)ktime_to_ns(delta) >> 10);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800288 }
289}
Cornelia Huck766ccb92009-01-20 15:31:31 +0100290EXPORT_SYMBOL_GPL(async_synchronize_cookie_domain);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800291
Cornelia Huckf30d5b32009-01-19 13:45:33 +0100292/**
293 * async_synchronize_cookie - synchronize asynchronous function calls with cookie checkpointing
294 * @cookie: async_cookie_t to use as checkpoint
295 *
296 * This function waits until all asynchronous function calls prior to @cookie
297 * have been done.
298 */
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800299void async_synchronize_cookie(async_cookie_t cookie)
300{
Cornelia Huck766ccb92009-01-20 15:31:31 +0100301 async_synchronize_cookie_domain(cookie, &async_running);
Arjan van de Ven22a9d642009-01-07 08:45:46 -0800302}
303EXPORT_SYMBOL_GPL(async_synchronize_cookie);