Commit 67acd8b4b7a3f1b183ae358e1dfdb8a80e170736
Merge git://git.kernel.org/pub/scm/linux/kernel/git/arjan/linux-2.6-async
* git://git.kernel.org/pub/scm/linux/kernel/git/arjan/linux-2.6-async: async: don't do the initcall stuff post boot bootchart: improve output based on Dave Jones' feedback async: make the final inode deletion an asynchronous event fastboot: Make libata initialization even more async fastboot: make the libata port scan asynchronous fastboot: make scsi probes asynchronous async: Asynchronous function calls to speed up kernel boot
Showing 13 changed files Side-by-side Diff
drivers/ata/libata-core.c
... | ... | @@ -56,6 +56,7 @@ |
56 | 56 | #include <linux/workqueue.h> |
57 | 57 | #include <linux/scatterlist.h> |
58 | 58 | #include <linux/io.h> |
59 | +#include <linux/async.h> | |
59 | 60 | #include <scsi/scsi.h> |
60 | 61 | #include <scsi/scsi_cmnd.h> |
61 | 62 | #include <scsi/scsi_host.h> |
... | ... | @@ -5909,6 +5910,54 @@ |
5909 | 5910 | host->ops = ops; |
5910 | 5911 | } |
5911 | 5912 | |
5913 | + | |
5914 | +static void async_port_probe(void *data, async_cookie_t cookie) | |
5915 | +{ | |
5916 | + int rc; | |
5917 | + struct ata_port *ap = data; | |
5918 | + /* probe */ | |
5919 | + if (ap->ops->error_handler) { | |
5920 | + struct ata_eh_info *ehi = &ap->link.eh_info; | |
5921 | + unsigned long flags; | |
5922 | + | |
5923 | + ata_port_probe(ap); | |
5924 | + | |
5925 | + /* kick EH for boot probing */ | |
5926 | + spin_lock_irqsave(ap->lock, flags); | |
5927 | + | |
5928 | + ehi->probe_mask |= ATA_ALL_DEVICES; | |
5929 | + ehi->action |= ATA_EH_RESET | ATA_EH_LPM; | |
5930 | + ehi->flags |= ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET; | |
5931 | + | |
5932 | + ap->pflags &= ~ATA_PFLAG_INITIALIZING; | |
5933 | + ap->pflags |= ATA_PFLAG_LOADING; | |
5934 | + ata_port_schedule_eh(ap); | |
5935 | + | |
5936 | + spin_unlock_irqrestore(ap->lock, flags); | |
5937 | + | |
5938 | + /* wait for EH to finish */ | |
5939 | + ata_port_wait_eh(ap); | |
5940 | + } else { | |
5941 | + DPRINTK("ata%u: bus probe begin\n", ap->print_id); | |
5942 | + rc = ata_bus_probe(ap); | |
5943 | + DPRINTK("ata%u: bus probe end\n", ap->print_id); | |
5944 | + | |
5945 | + if (rc) { | |
5946 | + /* FIXME: do something useful here? | |
5947 | + * Current libata behavior will | |
5948 | + * tear down everything when | |
5949 | + * the module is removed | |
5950 | + * or the h/w is unplugged. | |
5951 | + */ | |
5952 | + } | |
5953 | + } | |
5954 | + | |
5955 | + /* in order to keep device order, we need to synchronize at this point */ | |
5956 | + async_synchronize_cookie(cookie); | |
5957 | + | |
5958 | + ata_scsi_scan_host(ap, 1); | |
5959 | + | |
5960 | +} | |
5912 | 5961 | /** |
5913 | 5962 | * ata_host_register - register initialized ATA host |
5914 | 5963 | * @host: ATA host to register |
5915 | 5964 | |
... | ... | @@ -5988,52 +6037,9 @@ |
5988 | 6037 | DPRINTK("probe begin\n"); |
5989 | 6038 | for (i = 0; i < host->n_ports; i++) { |
5990 | 6039 | struct ata_port *ap = host->ports[i]; |
5991 | - | |
5992 | - /* probe */ | |
5993 | - if (ap->ops->error_handler) { | |
5994 | - struct ata_eh_info *ehi = &ap->link.eh_info; | |
5995 | - unsigned long flags; | |
5996 | - | |
5997 | - ata_port_probe(ap); | |
5998 | - | |
5999 | - /* kick EH for boot probing */ | |
6000 | - spin_lock_irqsave(ap->lock, flags); | |
6001 | - | |
6002 | - ehi->probe_mask |= ATA_ALL_DEVICES; | |
6003 | - ehi->action |= ATA_EH_RESET | ATA_EH_LPM; | |
6004 | - ehi->flags |= ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET; | |
6005 | - | |
6006 | - ap->pflags &= ~ATA_PFLAG_INITIALIZING; | |
6007 | - ap->pflags |= ATA_PFLAG_LOADING; | |
6008 | - ata_port_schedule_eh(ap); | |
6009 | - | |
6010 | - spin_unlock_irqrestore(ap->lock, flags); | |
6011 | - | |
6012 | - /* wait for EH to finish */ | |
6013 | - ata_port_wait_eh(ap); | |
6014 | - } else { | |
6015 | - DPRINTK("ata%u: bus probe begin\n", ap->print_id); | |
6016 | - rc = ata_bus_probe(ap); | |
6017 | - DPRINTK("ata%u: bus probe end\n", ap->print_id); | |
6018 | - | |
6019 | - if (rc) { | |
6020 | - /* FIXME: do something useful here? | |
6021 | - * Current libata behavior will | |
6022 | - * tear down everything when | |
6023 | - * the module is removed | |
6024 | - * or the h/w is unplugged. | |
6025 | - */ | |
6026 | - } | |
6027 | - } | |
6040 | + async_schedule(async_port_probe, ap); | |
6028 | 6041 | } |
6029 | - | |
6030 | - /* probes are done, now scan each port's disk(s) */ | |
6031 | - DPRINTK("host probe begin\n"); | |
6032 | - for (i = 0; i < host->n_ports; i++) { | |
6033 | - struct ata_port *ap = host->ports[i]; | |
6034 | - | |
6035 | - ata_scsi_scan_host(ap, 1); | |
6036 | - } | |
6042 | + DPRINTK("probe end\n"); | |
6037 | 6043 | |
6038 | 6044 | return 0; |
6039 | 6045 | } |
drivers/scsi/scsi_scan.c
... | ... | @@ -32,6 +32,7 @@ |
32 | 32 | #include <linux/delay.h> |
33 | 33 | #include <linux/kthread.h> |
34 | 34 | #include <linux/spinlock.h> |
35 | +#include <linux/async.h> | |
35 | 36 | |
36 | 37 | #include <scsi/scsi.h> |
37 | 38 | #include <scsi/scsi_cmnd.h> |
... | ... | @@ -179,6 +180,8 @@ |
179 | 180 | spin_unlock(&async_scan_lock); |
180 | 181 | |
181 | 182 | kfree(data); |
183 | + /* Synchronize async operations globally */ | |
184 | + async_synchronize_full(); | |
182 | 185 | return 0; |
183 | 186 | } |
184 | 187 |
drivers/scsi/sd.c
... | ... | @@ -48,6 +48,7 @@ |
48 | 48 | #include <linux/delay.h> |
49 | 49 | #include <linux/mutex.h> |
50 | 50 | #include <linux/string_helpers.h> |
51 | +#include <linux/async.h> | |
51 | 52 | #include <asm/uaccess.h> |
52 | 53 | |
53 | 54 | #include <scsi/scsi.h> |
... | ... | @@ -1802,6 +1803,71 @@ |
1802 | 1803 | return 0; |
1803 | 1804 | } |
1804 | 1805 | |
1806 | +/* | |
1807 | + * The asynchronous part of sd_probe | |
1808 | + */ | |
1809 | +static void sd_probe_async(void *data, async_cookie_t cookie) | |
1810 | +{ | |
1811 | + struct scsi_disk *sdkp = data; | |
1812 | + struct scsi_device *sdp; | |
1813 | + struct gendisk *gd; | |
1814 | + u32 index; | |
1815 | + struct device *dev; | |
1816 | + | |
1817 | + sdp = sdkp->device; | |
1818 | + gd = sdkp->disk; | |
1819 | + index = sdkp->index; | |
1820 | + dev = &sdp->sdev_gendev; | |
1821 | + | |
1822 | + if (!sdp->request_queue->rq_timeout) { | |
1823 | + if (sdp->type != TYPE_MOD) | |
1824 | + blk_queue_rq_timeout(sdp->request_queue, SD_TIMEOUT); | |
1825 | + else | |
1826 | + blk_queue_rq_timeout(sdp->request_queue, | |
1827 | + SD_MOD_TIMEOUT); | |
1828 | + } | |
1829 | + | |
1830 | + device_initialize(&sdkp->dev); | |
1831 | + sdkp->dev.parent = &sdp->sdev_gendev; | |
1832 | + sdkp->dev.class = &sd_disk_class; | |
1833 | + strncpy(sdkp->dev.bus_id, sdp->sdev_gendev.bus_id, BUS_ID_SIZE); | |
1834 | + | |
1835 | + if (device_add(&sdkp->dev)) | |
1836 | + goto out_free_index; | |
1837 | + | |
1838 | + get_device(&sdp->sdev_gendev); | |
1839 | + | |
1840 | + if (index < SD_MAX_DISKS) { | |
1841 | + gd->major = sd_major((index & 0xf0) >> 4); | |
1842 | + gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00); | |
1843 | + gd->minors = SD_MINORS; | |
1844 | + } | |
1845 | + gd->fops = &sd_fops; | |
1846 | + gd->private_data = &sdkp->driver; | |
1847 | + gd->queue = sdkp->device->request_queue; | |
1848 | + | |
1849 | + sd_revalidate_disk(gd); | |
1850 | + | |
1851 | + blk_queue_prep_rq(sdp->request_queue, sd_prep_fn); | |
1852 | + | |
1853 | + gd->driverfs_dev = &sdp->sdev_gendev; | |
1854 | + gd->flags = GENHD_FL_EXT_DEVT | GENHD_FL_DRIVERFS; | |
1855 | + if (sdp->removable) | |
1856 | + gd->flags |= GENHD_FL_REMOVABLE; | |
1857 | + | |
1858 | + dev_set_drvdata(dev, sdkp); | |
1859 | + add_disk(gd); | |
1860 | + sd_dif_config_host(sdkp); | |
1861 | + | |
1862 | + sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n", | |
1863 | + sdp->removable ? "removable " : ""); | |
1864 | + | |
1865 | + return; | |
1866 | + | |
1867 | + out_free_index: | |
1868 | + ida_remove(&sd_index_ida, index); | |
1869 | +} | |
1870 | + | |
1805 | 1871 | /** |
1806 | 1872 | * sd_probe - called during driver initialization and whenever a |
1807 | 1873 | * new scsi device is attached to the system. It is called once |
... | ... | @@ -1865,48 +1931,7 @@ |
1865 | 1931 | sdkp->openers = 0; |
1866 | 1932 | sdkp->previous_state = 1; |
1867 | 1933 | |
1868 | - if (!sdp->request_queue->rq_timeout) { | |
1869 | - if (sdp->type != TYPE_MOD) | |
1870 | - blk_queue_rq_timeout(sdp->request_queue, SD_TIMEOUT); | |
1871 | - else | |
1872 | - blk_queue_rq_timeout(sdp->request_queue, | |
1873 | - SD_MOD_TIMEOUT); | |
1874 | - } | |
1875 | - | |
1876 | - device_initialize(&sdkp->dev); | |
1877 | - sdkp->dev.parent = &sdp->sdev_gendev; | |
1878 | - sdkp->dev.class = &sd_disk_class; | |
1879 | - strncpy(sdkp->dev.bus_id, sdp->sdev_gendev.bus_id, BUS_ID_SIZE); | |
1880 | - | |
1881 | - if (device_add(&sdkp->dev)) | |
1882 | - goto out_free_index; | |
1883 | - | |
1884 | - get_device(&sdp->sdev_gendev); | |
1885 | - | |
1886 | - if (index < SD_MAX_DISKS) { | |
1887 | - gd->major = sd_major((index & 0xf0) >> 4); | |
1888 | - gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00); | |
1889 | - gd->minors = SD_MINORS; | |
1890 | - } | |
1891 | - gd->fops = &sd_fops; | |
1892 | - gd->private_data = &sdkp->driver; | |
1893 | - gd->queue = sdkp->device->request_queue; | |
1894 | - | |
1895 | - sd_revalidate_disk(gd); | |
1896 | - | |
1897 | - blk_queue_prep_rq(sdp->request_queue, sd_prep_fn); | |
1898 | - | |
1899 | - gd->driverfs_dev = &sdp->sdev_gendev; | |
1900 | - gd->flags = GENHD_FL_EXT_DEVT | GENHD_FL_DRIVERFS; | |
1901 | - if (sdp->removable) | |
1902 | - gd->flags |= GENHD_FL_REMOVABLE; | |
1903 | - | |
1904 | - dev_set_drvdata(dev, sdkp); | |
1905 | - add_disk(gd); | |
1906 | - sd_dif_config_host(sdkp); | |
1907 | - | |
1908 | - sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n", | |
1909 | - sdp->removable ? "removable " : ""); | |
1934 | + async_schedule(sd_probe_async, sdkp); | |
1910 | 1935 | |
1911 | 1936 | return 0; |
1912 | 1937 |
fs/inode.c
... | ... | @@ -22,6 +22,7 @@ |
22 | 22 | #include <linux/bootmem.h> |
23 | 23 | #include <linux/inotify.h> |
24 | 24 | #include <linux/mount.h> |
25 | +#include <linux/async.h> | |
25 | 26 | |
26 | 27 | /* |
27 | 28 | * This is needed for the following functions: |
28 | 29 | |
29 | 30 | |
... | ... | @@ -1138,16 +1139,11 @@ |
1138 | 1139 | * I_FREEING is set so that no-one will take a new reference to the inode while |
1139 | 1140 | * it is being deleted. |
1140 | 1141 | */ |
1141 | -void generic_delete_inode(struct inode *inode) | |
1142 | +static void generic_delete_inode_async(void *data, async_cookie_t cookie) | |
1142 | 1143 | { |
1144 | + struct inode *inode = data; | |
1143 | 1145 | const struct super_operations *op = inode->i_sb->s_op; |
1144 | 1146 | |
1145 | - list_del_init(&inode->i_list); | |
1146 | - list_del_init(&inode->i_sb_list); | |
1147 | - inode->i_state |= I_FREEING; | |
1148 | - inodes_stat.nr_inodes--; | |
1149 | - spin_unlock(&inode_lock); | |
1150 | - | |
1151 | 1147 | security_inode_delete(inode); |
1152 | 1148 | |
1153 | 1149 | if (op->delete_inode) { |
... | ... | @@ -1169,6 +1165,16 @@ |
1169 | 1165 | wake_up_inode(inode); |
1170 | 1166 | BUG_ON(inode->i_state != I_CLEAR); |
1171 | 1167 | destroy_inode(inode); |
1168 | +} | |
1169 | + | |
1170 | +void generic_delete_inode(struct inode *inode) | |
1171 | +{ | |
1172 | + list_del_init(&inode->i_list); | |
1173 | + list_del_init(&inode->i_sb_list); | |
1174 | + inode->i_state |= I_FREEING; | |
1175 | + inodes_stat.nr_inodes--; | |
1176 | + spin_unlock(&inode_lock); | |
1177 | + async_schedule_special(generic_delete_inode_async, inode, &inode->i_sb->s_async_list); | |
1172 | 1178 | } |
1173 | 1179 | |
1174 | 1180 | EXPORT_SYMBOL(generic_delete_inode); |
fs/super.c
... | ... | @@ -38,6 +38,7 @@ |
38 | 38 | #include <linux/kobject.h> |
39 | 39 | #include <linux/mutex.h> |
40 | 40 | #include <linux/file.h> |
41 | +#include <linux/async.h> | |
41 | 42 | #include <asm/uaccess.h> |
42 | 43 | #include "internal.h" |
43 | 44 | |
... | ... | @@ -71,6 +72,7 @@ |
71 | 72 | INIT_HLIST_HEAD(&s->s_anon); |
72 | 73 | INIT_LIST_HEAD(&s->s_inodes); |
73 | 74 | INIT_LIST_HEAD(&s->s_dentry_lru); |
75 | + INIT_LIST_HEAD(&s->s_async_list); | |
74 | 76 | init_rwsem(&s->s_umount); |
75 | 77 | mutex_init(&s->s_lock); |
76 | 78 | lockdep_set_class(&s->s_umount, &type->s_umount_key); |
77 | 79 | |
... | ... | @@ -289,11 +291,18 @@ |
289 | 291 | { |
290 | 292 | const struct super_operations *sop = sb->s_op; |
291 | 293 | |
294 | + | |
292 | 295 | if (sb->s_root) { |
293 | 296 | shrink_dcache_for_umount(sb); |
294 | 297 | fsync_super(sb); |
295 | 298 | lock_super(sb); |
296 | 299 | sb->s_flags &= ~MS_ACTIVE; |
300 | + | |
301 | + /* | |
302 | + * wait for asynchronous fs operations to finish before going further | |
303 | + */ | |
304 | + async_synchronize_full_special(&sb->s_async_list); | |
305 | + | |
297 | 306 | /* bad name - it should be evict_inodes() */ |
298 | 307 | invalidate_inodes(sb); |
299 | 308 | lock_kernel(); |
... | ... | @@ -449,6 +458,7 @@ |
449 | 458 | if (sb->s_flags & MS_RDONLY) |
450 | 459 | continue; |
451 | 460 | sb->s_need_sync_fs = 1; |
461 | + async_synchronize_full_special(&sb->s_async_list); | |
452 | 462 | } |
453 | 463 | |
454 | 464 | restart: |
include/linux/async.h
1 | +/* | |
2 | + * async.h: 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 | +#include <linux/types.h> | |
14 | +#include <linux/list.h> | |
15 | + | |
16 | +typedef u64 async_cookie_t; | |
17 | +typedef void (async_func_ptr) (void *data, async_cookie_t cookie); | |
18 | + | |
19 | +extern async_cookie_t async_schedule(async_func_ptr *ptr, void *data); | |
20 | +extern async_cookie_t async_schedule_special(async_func_ptr *ptr, void *data, struct list_head *list); | |
21 | +extern void async_synchronize_full(void); | |
22 | +extern void async_synchronize_full_special(struct list_head *list); | |
23 | +extern void async_synchronize_cookie(async_cookie_t cookie); | |
24 | +extern void async_synchronize_cookie_special(async_cookie_t cookie, struct list_head *list); |
include/linux/fs.h
... | ... | @@ -1184,6 +1184,11 @@ |
1184 | 1184 | * generic_show_options() |
1185 | 1185 | */ |
1186 | 1186 | char *s_options; |
1187 | + | |
1188 | + /* | |
1189 | + * storage for asynchronous operations | |
1190 | + */ | |
1191 | + struct list_head s_async_list; | |
1187 | 1192 | }; |
1188 | 1193 | |
1189 | 1194 | extern struct timespec current_fs_time(struct super_block *sb); |
init/do_mounts.c
... | ... | @@ -13,6 +13,7 @@ |
13 | 13 | #include <linux/init.h> |
14 | 14 | #include <linux/fs.h> |
15 | 15 | #include <linux/initrd.h> |
16 | +#include <linux/async.h> | |
16 | 17 | |
17 | 18 | #include <linux/nfs_fs.h> |
18 | 19 | #include <linux/nfs_fs_sb.h> |
... | ... | @@ -372,6 +373,7 @@ |
372 | 373 | /* wait for the known devices to complete their probing */ |
373 | 374 | while (driver_probe_done() != 0) |
374 | 375 | msleep(100); |
376 | + async_synchronize_full(); | |
375 | 377 | |
376 | 378 | md_run_setup(); |
377 | 379 |
init/main.c
... | ... | @@ -62,6 +62,7 @@ |
62 | 62 | #include <linux/signal.h> |
63 | 63 | #include <linux/idr.h> |
64 | 64 | #include <linux/ftrace.h> |
65 | +#include <linux/async.h> | |
65 | 66 | #include <trace/boot.h> |
66 | 67 | |
67 | 68 | #include <asm/io.h> |
... | ... | @@ -685,7 +686,7 @@ |
685 | 686 | rest_init(); |
686 | 687 | } |
687 | 688 | |
688 | -static int initcall_debug; | |
689 | +int initcall_debug; | |
689 | 690 | core_param(initcall_debug, initcall_debug, bool, 0644); |
690 | 691 | |
691 | 692 | int do_one_initcall(initcall_t fn) |
... | ... | @@ -786,6 +787,8 @@ |
786 | 787 | */ |
787 | 788 | static noinline int init_post(void) |
788 | 789 | { |
790 | + /* need to finish all async __init code before freeing the memory */ | |
791 | + async_synchronize_full(); | |
789 | 792 | free_initmem(); |
790 | 793 | unlock_kernel(); |
791 | 794 | mark_rodata_ro(); |
kernel/Makefile
... | ... | @@ -9,7 +9,8 @@ |
9 | 9 | rcupdate.o extable.o params.o posix-timers.o \ |
10 | 10 | kthread.o wait.o kfifo.o sys_ni.o posix-cpu-timers.o mutex.o \ |
11 | 11 | hrtimer.o rwsem.o nsproxy.o srcu.o semaphore.o \ |
12 | - notifier.o ksysfs.o pm_qos_params.o sched_clock.o cred.o | |
12 | + notifier.o ksysfs.o pm_qos_params.o sched_clock.o cred.o \ | |
13 | + async.o | |
13 | 14 | |
14 | 15 | ifdef CONFIG_FUNCTION_TRACER |
15 | 16 | # Do not trace debug files and internal ftrace files |
kernel/async.c
1 | +/* | |
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 | + | |
16 | +Goals and Theory of Operation | |
17 | + | |
18 | +The primary goal of this feature is to reduce the kernel boot time, | |
19 | +by doing various independent hardware delays and discovery operations | |
20 | +decoupled and not strictly serialized. | |
21 | + | |
22 | +More specifically, the asynchronous function call concept allows | |
23 | +certain operations (primarily during system boot) to happen | |
24 | +asynchronously, out of order, while these operations still | |
25 | +have their externally visible parts happen sequentially and in-order. | |
26 | +(not unlike how out-of-order CPUs retire their instructions in order) | |
27 | + | |
28 | +Key to the asynchronous function call implementation is the concept of | |
29 | +a "sequence cookie" (which, although it has an abstracted type, can be | |
30 | +thought of as a monotonically incrementing number). | |
31 | + | |
32 | +The async core will assign each scheduled event such a sequence cookie and | |
33 | +pass this to the called functions. | |
34 | + | |
35 | +The asynchronously called function should before doing a globally visible | |
36 | +operation, such as registering device numbers, call the | |
37 | +async_synchronize_cookie() function and pass in its own cookie. The | |
38 | +async_synchronize_cookie() function will make sure that all asynchronous | |
39 | +operations that were scheduled prior to the operation corresponding with the | |
40 | +cookie have completed. | |
41 | + | |
42 | +Subsystem/driver initialization code that scheduled asynchronous probe | |
43 | +functions, but which shares global resources with other drivers/subsystems | |
44 | +that do not use the asynchronous call feature, need to do a full | |
45 | +synchronization with the async_synchronize_full() function, before returning | |
46 | +from their init function. This is to maintain strict ordering between the | |
47 | +asynchronous and synchronous parts of the kernel. | |
48 | + | |
49 | +*/ | |
50 | + | |
51 | +#include <linux/async.h> | |
52 | +#include <linux/module.h> | |
53 | +#include <linux/wait.h> | |
54 | +#include <linux/sched.h> | |
55 | +#include <linux/init.h> | |
56 | +#include <linux/kthread.h> | |
57 | +#include <asm/atomic.h> | |
58 | + | |
59 | +static async_cookie_t next_cookie = 1; | |
60 | + | |
61 | +#define MAX_THREADS 256 | |
62 | +#define MAX_WORK 32768 | |
63 | + | |
64 | +static LIST_HEAD(async_pending); | |
65 | +static LIST_HEAD(async_running); | |
66 | +static DEFINE_SPINLOCK(async_lock); | |
67 | + | |
68 | +struct async_entry { | |
69 | + struct list_head list; | |
70 | + async_cookie_t cookie; | |
71 | + async_func_ptr *func; | |
72 | + void *data; | |
73 | + struct list_head *running; | |
74 | +}; | |
75 | + | |
76 | +static DECLARE_WAIT_QUEUE_HEAD(async_done); | |
77 | +static DECLARE_WAIT_QUEUE_HEAD(async_new); | |
78 | + | |
79 | +static atomic_t entry_count; | |
80 | +static atomic_t thread_count; | |
81 | + | |
82 | +extern int initcall_debug; | |
83 | + | |
84 | + | |
85 | +/* | |
86 | + * MUST be called with the lock held! | |
87 | + */ | |
88 | +static async_cookie_t __lowest_in_progress(struct list_head *running) | |
89 | +{ | |
90 | + struct async_entry *entry; | |
91 | + if (!list_empty(&async_pending)) { | |
92 | + entry = list_first_entry(&async_pending, | |
93 | + struct async_entry, list); | |
94 | + return entry->cookie; | |
95 | + } else if (!list_empty(running)) { | |
96 | + entry = list_first_entry(running, | |
97 | + struct async_entry, list); | |
98 | + return entry->cookie; | |
99 | + } else { | |
100 | + /* nothing in progress... next_cookie is "infinity" */ | |
101 | + return next_cookie; | |
102 | + } | |
103 | + | |
104 | +} | |
105 | +/* | |
106 | + * pick the first pending entry and run it | |
107 | + */ | |
108 | +static void run_one_entry(void) | |
109 | +{ | |
110 | + unsigned long flags; | |
111 | + struct async_entry *entry; | |
112 | + ktime_t calltime, delta, rettime; | |
113 | + | |
114 | + /* 1) pick one task from the pending queue */ | |
115 | + | |
116 | + spin_lock_irqsave(&async_lock, flags); | |
117 | + if (list_empty(&async_pending)) | |
118 | + goto out; | |
119 | + entry = list_first_entry(&async_pending, struct async_entry, list); | |
120 | + | |
121 | + /* 2) move it to the running queue */ | |
122 | + list_del(&entry->list); | |
123 | + list_add_tail(&entry->list, &async_running); | |
124 | + spin_unlock_irqrestore(&async_lock, flags); | |
125 | + | |
126 | + /* 3) run it (and print duration)*/ | |
127 | + if (initcall_debug && system_state == SYSTEM_BOOTING) { | |
128 | + printk("calling %lli_%pF @ %i\n", entry->cookie, entry->func, task_pid_nr(current)); | |
129 | + calltime = ktime_get(); | |
130 | + } | |
131 | + entry->func(entry->data, entry->cookie); | |
132 | + if (initcall_debug && system_state == SYSTEM_BOOTING) { | |
133 | + rettime = ktime_get(); | |
134 | + delta = ktime_sub(rettime, calltime); | |
135 | + printk("initcall %lli_%pF returned 0 after %lld usecs\n", entry->cookie, | |
136 | + entry->func, ktime_to_ns(delta) >> 10); | |
137 | + } | |
138 | + | |
139 | + /* 4) remove it from the running queue */ | |
140 | + spin_lock_irqsave(&async_lock, flags); | |
141 | + list_del(&entry->list); | |
142 | + | |
143 | + /* 5) free the entry */ | |
144 | + kfree(entry); | |
145 | + atomic_dec(&entry_count); | |
146 | + | |
147 | + spin_unlock_irqrestore(&async_lock, flags); | |
148 | + | |
149 | + /* 6) wake up any waiters. */ | |
150 | + wake_up(&async_done); | |
151 | + return; | |
152 | + | |
153 | +out: | |
154 | + spin_unlock_irqrestore(&async_lock, flags); | |
155 | +} | |
156 | + | |
157 | + | |
158 | +static async_cookie_t __async_schedule(async_func_ptr *ptr, void *data, struct list_head *running) | |
159 | +{ | |
160 | + struct async_entry *entry; | |
161 | + unsigned long flags; | |
162 | + async_cookie_t newcookie; | |
163 | + | |
164 | + | |
165 | + /* allow irq-off callers */ | |
166 | + entry = kzalloc(sizeof(struct async_entry), GFP_ATOMIC); | |
167 | + | |
168 | + /* | |
169 | + * If we're out of memory or if there's too much work | |
170 | + * pending already, we execute synchronously. | |
171 | + */ | |
172 | + if (!entry || atomic_read(&entry_count) > MAX_WORK) { | |
173 | + kfree(entry); | |
174 | + spin_lock_irqsave(&async_lock, flags); | |
175 | + newcookie = next_cookie++; | |
176 | + spin_unlock_irqrestore(&async_lock, flags); | |
177 | + | |
178 | + /* low on memory.. run synchronously */ | |
179 | + ptr(data, newcookie); | |
180 | + return newcookie; | |
181 | + } | |
182 | + entry->func = ptr; | |
183 | + entry->data = data; | |
184 | + entry->running = running; | |
185 | + | |
186 | + spin_lock_irqsave(&async_lock, flags); | |
187 | + newcookie = entry->cookie = next_cookie++; | |
188 | + list_add_tail(&entry->list, &async_pending); | |
189 | + atomic_inc(&entry_count); | |
190 | + spin_unlock_irqrestore(&async_lock, flags); | |
191 | + wake_up(&async_new); | |
192 | + return newcookie; | |
193 | +} | |
194 | + | |
195 | +async_cookie_t async_schedule(async_func_ptr *ptr, void *data) | |
196 | +{ | |
197 | + return __async_schedule(ptr, data, &async_pending); | |
198 | +} | |
199 | +EXPORT_SYMBOL_GPL(async_schedule); | |
200 | + | |
201 | +async_cookie_t async_schedule_special(async_func_ptr *ptr, void *data, struct list_head *running) | |
202 | +{ | |
203 | + return __async_schedule(ptr, data, running); | |
204 | +} | |
205 | +EXPORT_SYMBOL_GPL(async_schedule_special); | |
206 | + | |
207 | +void async_synchronize_full(void) | |
208 | +{ | |
209 | + async_synchronize_cookie(next_cookie); | |
210 | +} | |
211 | +EXPORT_SYMBOL_GPL(async_synchronize_full); | |
212 | + | |
213 | +void async_synchronize_full_special(struct list_head *list) | |
214 | +{ | |
215 | + async_synchronize_cookie_special(next_cookie, list); | |
216 | +} | |
217 | +EXPORT_SYMBOL_GPL(async_synchronize_full_special); | |
218 | + | |
219 | +void async_synchronize_cookie_special(async_cookie_t cookie, struct list_head *running) | |
220 | +{ | |
221 | + ktime_t starttime, delta, endtime; | |
222 | + | |
223 | + if (initcall_debug && system_state == SYSTEM_BOOTING) { | |
224 | + printk("async_waiting @ %i\n", task_pid_nr(current)); | |
225 | + starttime = ktime_get(); | |
226 | + } | |
227 | + | |
228 | + wait_event(async_done, __lowest_in_progress(running) >= cookie); | |
229 | + | |
230 | + if (initcall_debug && system_state == SYSTEM_BOOTING) { | |
231 | + endtime = ktime_get(); | |
232 | + delta = ktime_sub(endtime, starttime); | |
233 | + | |
234 | + printk("async_continuing @ %i after %lli usec\n", | |
235 | + task_pid_nr(current), ktime_to_ns(delta) >> 10); | |
236 | + } | |
237 | +} | |
238 | +EXPORT_SYMBOL_GPL(async_synchronize_cookie_special); | |
239 | + | |
240 | +void async_synchronize_cookie(async_cookie_t cookie) | |
241 | +{ | |
242 | + async_synchronize_cookie_special(cookie, &async_running); | |
243 | +} | |
244 | +EXPORT_SYMBOL_GPL(async_synchronize_cookie); | |
245 | + | |
246 | + | |
247 | +static int async_thread(void *unused) | |
248 | +{ | |
249 | + DECLARE_WAITQUEUE(wq, current); | |
250 | + add_wait_queue(&async_new, &wq); | |
251 | + | |
252 | + while (!kthread_should_stop()) { | |
253 | + int ret = HZ; | |
254 | + set_current_state(TASK_INTERRUPTIBLE); | |
255 | + /* | |
256 | + * check the list head without lock.. false positives | |
257 | + * are dealt with inside run_one_entry() while holding | |
258 | + * the lock. | |
259 | + */ | |
260 | + rmb(); | |
261 | + if (!list_empty(&async_pending)) | |
262 | + run_one_entry(); | |
263 | + else | |
264 | + ret = schedule_timeout(HZ); | |
265 | + | |
266 | + if (ret == 0) { | |
267 | + /* | |
268 | + * we timed out, this means we as thread are redundant. | |
269 | + * we sign off and die, but we to avoid any races there | |
270 | + * is a last-straw check to see if work snuck in. | |
271 | + */ | |
272 | + atomic_dec(&thread_count); | |
273 | + wmb(); /* manager must see our departure first */ | |
274 | + if (list_empty(&async_pending)) | |
275 | + break; | |
276 | + /* | |
277 | + * woops work came in between us timing out and us | |
278 | + * signing off; we need to stay alive and keep working. | |
279 | + */ | |
280 | + atomic_inc(&thread_count); | |
281 | + } | |
282 | + } | |
283 | + remove_wait_queue(&async_new, &wq); | |
284 | + | |
285 | + return 0; | |
286 | +} | |
287 | + | |
288 | +static int async_manager_thread(void *unused) | |
289 | +{ | |
290 | + DECLARE_WAITQUEUE(wq, current); | |
291 | + add_wait_queue(&async_new, &wq); | |
292 | + | |
293 | + while (!kthread_should_stop()) { | |
294 | + int tc, ec; | |
295 | + | |
296 | + set_current_state(TASK_INTERRUPTIBLE); | |
297 | + | |
298 | + tc = atomic_read(&thread_count); | |
299 | + rmb(); | |
300 | + ec = atomic_read(&entry_count); | |
301 | + | |
302 | + while (tc < ec && tc < MAX_THREADS) { | |
303 | + kthread_run(async_thread, NULL, "async/%i", tc); | |
304 | + atomic_inc(&thread_count); | |
305 | + tc++; | |
306 | + } | |
307 | + | |
308 | + schedule(); | |
309 | + } | |
310 | + remove_wait_queue(&async_new, &wq); | |
311 | + | |
312 | + return 0; | |
313 | +} | |
314 | + | |
315 | +static int __init async_init(void) | |
316 | +{ | |
317 | + kthread_run(async_manager_thread, NULL, "async/mgr"); | |
318 | + return 0; | |
319 | +} | |
320 | + | |
321 | +core_initcall(async_init); |
kernel/irq/autoprobe.c
... | ... | @@ -10,6 +10,7 @@ |
10 | 10 | #include <linux/module.h> |
11 | 11 | #include <linux/interrupt.h> |
12 | 12 | #include <linux/delay.h> |
13 | +#include <linux/async.h> | |
13 | 14 | |
14 | 15 | #include "internals.h" |
15 | 16 | |
... | ... | @@ -34,6 +35,10 @@ |
34 | 35 | unsigned int status; |
35 | 36 | int i; |
36 | 37 | |
38 | + /* | |
39 | + * quiesce the kernel, or at least the asynchronous portion | |
40 | + */ | |
41 | + async_synchronize_full(); | |
37 | 42 | mutex_lock(&probing_active); |
38 | 43 | /* |
39 | 44 | * something may have generated an irq long ago and we want to |
kernel/module.c
... | ... | @@ -50,6 +50,7 @@ |
50 | 50 | #include <asm/sections.h> |
51 | 51 | #include <linux/tracepoint.h> |
52 | 52 | #include <linux/ftrace.h> |
53 | +#include <linux/async.h> | |
53 | 54 | |
54 | 55 | #if 0 |
55 | 56 | #define DEBUGP printk |
... | ... | @@ -816,6 +817,7 @@ |
816 | 817 | mod->exit(); |
817 | 818 | blocking_notifier_call_chain(&module_notify_list, |
818 | 819 | MODULE_STATE_GOING, mod); |
820 | + async_synchronize_full(); | |
819 | 821 | mutex_lock(&module_mutex); |
820 | 822 | /* Store the name of the last unloaded module for diagnostic purposes */ |
821 | 823 | strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module)); |