diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2008-05-01 11:26:56 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2008-05-01 11:26:56 -0400 |
commit | 03fc922f4085a0139f313831fe2dc6fed467cd2d (patch) | |
tree | 1404c800acaf02eab054eb3448803db9d9b89262 /kernel | |
parent | a0be7522b25f17ac2c3964a24b88b5fe7c9404b8 (diff) | |
parent | df4b565e1fbc777bb6e274378a41fa8ff7485680 (diff) |
Merge git://git.kernel.org/pub/scm/linux/kernel/git/rusty/linux-2.6-for-linus
* git://git.kernel.org/pub/scm/linux/kernel/git/rusty/linux-2.6-for-linus:
module: add MODULE_STATE_GOING notifier call
module: Enhance verify_export_symbols
module: set unused_gpl_crcs instead of overwriting unused_crcs
module: neaten __find_symbol, rename to find_symbol
module: reduce module image and resident size
module: make module_sect_attrs private to kernel/module.c
Diffstat (limited to 'kernel')
-rw-r--r-- | kernel/module.c | 319 |
1 files changed, 171 insertions, 148 deletions
diff --git a/kernel/module.c b/kernel/module.c index 8d6cccc6c3cf..8674a390a2e8 100644 --- a/kernel/module.c +++ b/kernel/module.c | |||
@@ -164,131 +164,140 @@ static const struct kernel_symbol *lookup_symbol(const char *name, | |||
164 | return NULL; | 164 | return NULL; |
165 | } | 165 | } |
166 | 166 | ||
167 | static void printk_unused_warning(const char *name) | 167 | static bool always_ok(bool gplok, bool warn, const char *name) |
168 | { | 168 | { |
169 | printk(KERN_WARNING "Symbol %s is marked as UNUSED, " | 169 | return true; |
170 | "however this module is using it.\n", name); | ||
171 | printk(KERN_WARNING "This symbol will go away in the future.\n"); | ||
172 | printk(KERN_WARNING "Please evalute if this is the right api to use, " | ||
173 | "and if it really is, submit a report the linux kernel " | ||
174 | "mailinglist together with submitting your code for " | ||
175 | "inclusion.\n"); | ||
176 | } | 170 | } |
177 | 171 | ||
178 | /* Find a symbol, return value, crc and module which owns it */ | 172 | static bool printk_unused_warning(bool gplok, bool warn, const char *name) |
179 | static unsigned long __find_symbol(const char *name, | ||
180 | struct module **owner, | ||
181 | const unsigned long **crc, | ||
182 | int gplok) | ||
183 | { | 173 | { |
184 | struct module *mod; | 174 | if (warn) { |
185 | const struct kernel_symbol *ks; | 175 | printk(KERN_WARNING "Symbol %s is marked as UNUSED, " |
186 | 176 | "however this module is using it.\n", name); | |
187 | /* Core kernel first. */ | 177 | printk(KERN_WARNING |
188 | *owner = NULL; | 178 | "This symbol will go away in the future.\n"); |
189 | ks = lookup_symbol(name, __start___ksymtab, __stop___ksymtab); | 179 | printk(KERN_WARNING |
190 | if (ks) { | 180 | "Please evalute if this is the right api to use and if " |
191 | *crc = symversion(__start___kcrctab, (ks - __start___ksymtab)); | 181 | "it really is, submit a report the linux kernel " |
192 | return ks->value; | 182 | "mailinglist together with submitting your code for " |
183 | "inclusion.\n"); | ||
193 | } | 184 | } |
194 | if (gplok) { | 185 | return true; |
195 | ks = lookup_symbol(name, __start___ksymtab_gpl, | 186 | } |
196 | __stop___ksymtab_gpl); | 187 | |
197 | if (ks) { | 188 | static bool gpl_only_unused_warning(bool gplok, bool warn, const char *name) |
198 | *crc = symversion(__start___kcrctab_gpl, | 189 | { |
199 | (ks - __start___ksymtab_gpl)); | 190 | if (!gplok) |
200 | return ks->value; | 191 | return false; |
201 | } | 192 | return printk_unused_warning(gplok, warn, name); |
202 | } | 193 | } |
203 | ks = lookup_symbol(name, __start___ksymtab_gpl_future, | 194 | |
204 | __stop___ksymtab_gpl_future); | 195 | static bool gpl_only(bool gplok, bool warn, const char *name) |
205 | if (ks) { | 196 | { |
206 | if (!gplok) { | 197 | return gplok; |
207 | printk(KERN_WARNING "Symbol %s is being used " | 198 | } |
208 | "by a non-GPL module, which will not " | 199 | |
209 | "be allowed in the future\n", name); | 200 | static bool warn_if_not_gpl(bool gplok, bool warn, const char *name) |
210 | printk(KERN_WARNING "Please see the file " | 201 | { |
211 | "Documentation/feature-removal-schedule.txt " | 202 | if (!gplok && warn) { |
212 | "in the kernel source tree for more " | 203 | printk(KERN_WARNING "Symbol %s is being used " |
213 | "details.\n"); | 204 | "by a non-GPL module, which will not " |
214 | } | 205 | "be allowed in the future\n", name); |
215 | *crc = symversion(__start___kcrctab_gpl_future, | 206 | printk(KERN_WARNING "Please see the file " |
216 | (ks - __start___ksymtab_gpl_future)); | 207 | "Documentation/feature-removal-schedule.txt " |
217 | return ks->value; | 208 | "in the kernel source tree for more details.\n"); |
218 | } | 209 | } |
210 | return true; | ||
211 | } | ||
219 | 212 | ||
220 | ks = lookup_symbol(name, __start___ksymtab_unused, | 213 | struct symsearch { |
221 | __stop___ksymtab_unused); | 214 | const struct kernel_symbol *start, *stop; |
222 | if (ks) { | 215 | const unsigned long *crcs; |
223 | printk_unused_warning(name); | 216 | bool (*check)(bool gplok, bool warn, const char *name); |
224 | *crc = symversion(__start___kcrctab_unused, | 217 | }; |
225 | (ks - __start___ksymtab_unused)); | 218 | |
226 | return ks->value; | 219 | /* Look through this array of symbol tables for a symbol match which |
220 | * passes the check function. */ | ||
221 | static const struct kernel_symbol *search_symarrays(const struct symsearch *arr, | ||
222 | unsigned int num, | ||
223 | const char *name, | ||
224 | bool gplok, | ||
225 | bool warn, | ||
226 | const unsigned long **crc) | ||
227 | { | ||
228 | unsigned int i; | ||
229 | const struct kernel_symbol *ks; | ||
230 | |||
231 | for (i = 0; i < num; i++) { | ||
232 | ks = lookup_symbol(name, arr[i].start, arr[i].stop); | ||
233 | if (!ks || !arr[i].check(gplok, warn, name)) | ||
234 | continue; | ||
235 | |||
236 | if (crc) | ||
237 | *crc = symversion(arr[i].crcs, ks - arr[i].start); | ||
238 | return ks; | ||
227 | } | 239 | } |
240 | return NULL; | ||
241 | } | ||
242 | |||
243 | /* Find a symbol, return value, (optional) crc and (optional) module | ||
244 | * which owns it */ | ||
245 | static unsigned long find_symbol(const char *name, | ||
246 | struct module **owner, | ||
247 | const unsigned long **crc, | ||
248 | bool gplok, | ||
249 | bool warn) | ||
250 | { | ||
251 | struct module *mod; | ||
252 | const struct kernel_symbol *ks; | ||
253 | const struct symsearch arr[] = { | ||
254 | { __start___ksymtab, __stop___ksymtab, __start___kcrctab, | ||
255 | always_ok }, | ||
256 | { __start___ksymtab_gpl, __stop___ksymtab_gpl, | ||
257 | __start___kcrctab_gpl, gpl_only }, | ||
258 | { __start___ksymtab_gpl_future, __stop___ksymtab_gpl_future, | ||
259 | __start___kcrctab_gpl_future, warn_if_not_gpl }, | ||
260 | { __start___ksymtab_unused, __stop___ksymtab_unused, | ||
261 | __start___kcrctab_unused, printk_unused_warning }, | ||
262 | { __start___ksymtab_unused_gpl, __stop___ksymtab_unused_gpl, | ||
263 | __start___kcrctab_unused_gpl, gpl_only_unused_warning }, | ||
264 | }; | ||
228 | 265 | ||
229 | if (gplok) | 266 | /* Core kernel first. */ |
230 | ks = lookup_symbol(name, __start___ksymtab_unused_gpl, | 267 | ks = search_symarrays(arr, ARRAY_SIZE(arr), name, gplok, warn, crc); |
231 | __stop___ksymtab_unused_gpl); | ||
232 | if (ks) { | 268 | if (ks) { |
233 | printk_unused_warning(name); | 269 | if (owner) |
234 | *crc = symversion(__start___kcrctab_unused_gpl, | 270 | *owner = NULL; |
235 | (ks - __start___ksymtab_unused_gpl)); | ||
236 | return ks->value; | 271 | return ks->value; |
237 | } | 272 | } |
238 | 273 | ||
239 | /* Now try modules. */ | 274 | /* Now try modules. */ |
240 | list_for_each_entry(mod, &modules, list) { | 275 | list_for_each_entry(mod, &modules, list) { |
241 | *owner = mod; | 276 | struct symsearch arr[] = { |
242 | ks = lookup_symbol(name, mod->syms, mod->syms + mod->num_syms); | 277 | { mod->syms, mod->syms + mod->num_syms, mod->crcs, |
243 | if (ks) { | 278 | always_ok }, |
244 | *crc = symversion(mod->crcs, (ks - mod->syms)); | 279 | { mod->gpl_syms, mod->gpl_syms + mod->num_gpl_syms, |
245 | return ks->value; | 280 | mod->gpl_crcs, gpl_only }, |
246 | } | 281 | { mod->gpl_future_syms, |
247 | 282 | mod->gpl_future_syms + mod->num_gpl_future_syms, | |
248 | if (gplok) { | 283 | mod->gpl_future_crcs, warn_if_not_gpl }, |
249 | ks = lookup_symbol(name, mod->gpl_syms, | 284 | { mod->unused_syms, |
250 | mod->gpl_syms + mod->num_gpl_syms); | 285 | mod->unused_syms + mod->num_unused_syms, |
251 | if (ks) { | 286 | mod->unused_crcs, printk_unused_warning }, |
252 | *crc = symversion(mod->gpl_crcs, | 287 | { mod->unused_gpl_syms, |
253 | (ks - mod->gpl_syms)); | 288 | mod->unused_gpl_syms + mod->num_unused_gpl_syms, |
254 | return ks->value; | 289 | mod->unused_gpl_crcs, gpl_only_unused_warning }, |
255 | } | 290 | }; |
256 | } | 291 | |
257 | ks = lookup_symbol(name, mod->unused_syms, mod->unused_syms + mod->num_unused_syms); | 292 | ks = search_symarrays(arr, ARRAY_SIZE(arr), |
293 | name, gplok, warn, crc); | ||
258 | if (ks) { | 294 | if (ks) { |
259 | printk_unused_warning(name); | 295 | if (owner) |
260 | *crc = symversion(mod->unused_crcs, (ks - mod->unused_syms)); | 296 | *owner = mod; |
261 | return ks->value; | ||
262 | } | ||
263 | |||
264 | if (gplok) { | ||
265 | ks = lookup_symbol(name, mod->unused_gpl_syms, | ||
266 | mod->unused_gpl_syms + mod->num_unused_gpl_syms); | ||
267 | if (ks) { | ||
268 | printk_unused_warning(name); | ||
269 | *crc = symversion(mod->unused_gpl_crcs, | ||
270 | (ks - mod->unused_gpl_syms)); | ||
271 | return ks->value; | ||
272 | } | ||
273 | } | ||
274 | ks = lookup_symbol(name, mod->gpl_future_syms, | ||
275 | (mod->gpl_future_syms + | ||
276 | mod->num_gpl_future_syms)); | ||
277 | if (ks) { | ||
278 | if (!gplok) { | ||
279 | printk(KERN_WARNING "Symbol %s is being used " | ||
280 | "by a non-GPL module, which will not " | ||
281 | "be allowed in the future\n", name); | ||
282 | printk(KERN_WARNING "Please see the file " | ||
283 | "Documentation/feature-removal-schedule.txt " | ||
284 | "in the kernel source tree for more " | ||
285 | "details.\n"); | ||
286 | } | ||
287 | *crc = symversion(mod->gpl_future_crcs, | ||
288 | (ks - mod->gpl_future_syms)); | ||
289 | return ks->value; | 297 | return ks->value; |
290 | } | 298 | } |
291 | } | 299 | } |
300 | |||
292 | DEBUGP("Failed to find symbol %s\n", name); | 301 | DEBUGP("Failed to find symbol %s\n", name); |
293 | return -ENOENT; | 302 | return -ENOENT; |
294 | } | 303 | } |
@@ -736,12 +745,13 @@ sys_delete_module(const char __user *name_user, unsigned int flags) | |||
736 | if (!forced && module_refcount(mod) != 0) | 745 | if (!forced && module_refcount(mod) != 0) |
737 | wait_for_zero_refcount(mod); | 746 | wait_for_zero_refcount(mod); |
738 | 747 | ||
748 | mutex_unlock(&module_mutex); | ||
739 | /* Final destruction now noone is using it. */ | 749 | /* Final destruction now noone is using it. */ |
740 | if (mod->exit != NULL) { | 750 | if (mod->exit != NULL) |
741 | mutex_unlock(&module_mutex); | ||
742 | mod->exit(); | 751 | mod->exit(); |
743 | mutex_lock(&module_mutex); | 752 | blocking_notifier_call_chain(&module_notify_list, |
744 | } | 753 | MODULE_STATE_GOING, mod); |
754 | mutex_lock(&module_mutex); | ||
745 | /* Store the name of the last unloaded module for diagnostic purposes */ | 755 | /* Store the name of the last unloaded module for diagnostic purposes */ |
746 | strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module)); | 756 | strlcpy(last_unloaded_module, mod->name, sizeof(last_unloaded_module)); |
747 | free_module(mod); | 757 | free_module(mod); |
@@ -777,10 +787,9 @@ static void print_unload_info(struct seq_file *m, struct module *mod) | |||
777 | void __symbol_put(const char *symbol) | 787 | void __symbol_put(const char *symbol) |
778 | { | 788 | { |
779 | struct module *owner; | 789 | struct module *owner; |
780 | const unsigned long *crc; | ||
781 | 790 | ||
782 | preempt_disable(); | 791 | preempt_disable(); |
783 | if (IS_ERR_VALUE(__find_symbol(symbol, &owner, &crc, 1))) | 792 | if (IS_ERR_VALUE(find_symbol(symbol, &owner, NULL, true, false))) |
784 | BUG(); | 793 | BUG(); |
785 | module_put(owner); | 794 | module_put(owner); |
786 | preempt_enable(); | 795 | preempt_enable(); |
@@ -924,13 +933,10 @@ static inline int check_modstruct_version(Elf_Shdr *sechdrs, | |||
924 | struct module *mod) | 933 | struct module *mod) |
925 | { | 934 | { |
926 | const unsigned long *crc; | 935 | const unsigned long *crc; |
927 | struct module *owner; | ||
928 | 936 | ||
929 | if (IS_ERR_VALUE(__find_symbol("struct_module", | 937 | if (IS_ERR_VALUE(find_symbol("struct_module", NULL, &crc, true, false))) |
930 | &owner, &crc, 1))) | ||
931 | BUG(); | 938 | BUG(); |
932 | return check_version(sechdrs, versindex, "struct_module", mod, | 939 | return check_version(sechdrs, versindex, "struct_module", mod, crc); |
933 | crc); | ||
934 | } | 940 | } |
935 | 941 | ||
936 | /* First part is kernel version, which we ignore. */ | 942 | /* First part is kernel version, which we ignore. */ |
@@ -974,8 +980,8 @@ static unsigned long resolve_symbol(Elf_Shdr *sechdrs, | |||
974 | unsigned long ret; | 980 | unsigned long ret; |
975 | const unsigned long *crc; | 981 | const unsigned long *crc; |
976 | 982 | ||
977 | ret = __find_symbol(name, &owner, &crc, | 983 | ret = find_symbol(name, &owner, &crc, |
978 | !(mod->taints & TAINT_PROPRIETARY_MODULE)); | 984 | !(mod->taints & TAINT_PROPRIETARY_MODULE), true); |
979 | if (!IS_ERR_VALUE(ret)) { | 985 | if (!IS_ERR_VALUE(ret)) { |
980 | /* use_module can fail due to OOM, | 986 | /* use_module can fail due to OOM, |
981 | or module initialization or unloading */ | 987 | or module initialization or unloading */ |
@@ -991,6 +997,20 @@ static unsigned long resolve_symbol(Elf_Shdr *sechdrs, | |||
991 | * J. Corbet <corbet@lwn.net> | 997 | * J. Corbet <corbet@lwn.net> |
992 | */ | 998 | */ |
993 | #if defined(CONFIG_KALLSYMS) && defined(CONFIG_SYSFS) | 999 | #if defined(CONFIG_KALLSYMS) && defined(CONFIG_SYSFS) |
1000 | struct module_sect_attr | ||
1001 | { | ||
1002 | struct module_attribute mattr; | ||
1003 | char *name; | ||
1004 | unsigned long address; | ||
1005 | }; | ||
1006 | |||
1007 | struct module_sect_attrs | ||
1008 | { | ||
1009 | struct attribute_group grp; | ||
1010 | unsigned int nsections; | ||
1011 | struct module_sect_attr attrs[0]; | ||
1012 | }; | ||
1013 | |||
994 | static ssize_t module_sect_show(struct module_attribute *mattr, | 1014 | static ssize_t module_sect_show(struct module_attribute *mattr, |
995 | struct module *mod, char *buf) | 1015 | struct module *mod, char *buf) |
996 | { | 1016 | { |
@@ -1001,7 +1021,7 @@ static ssize_t module_sect_show(struct module_attribute *mattr, | |||
1001 | 1021 | ||
1002 | static void free_sect_attrs(struct module_sect_attrs *sect_attrs) | 1022 | static void free_sect_attrs(struct module_sect_attrs *sect_attrs) |
1003 | { | 1023 | { |
1004 | int section; | 1024 | unsigned int section; |
1005 | 1025 | ||
1006 | for (section = 0; section < sect_attrs->nsections; section++) | 1026 | for (section = 0; section < sect_attrs->nsections; section++) |
1007 | kfree(sect_attrs->attrs[section].name); | 1027 | kfree(sect_attrs->attrs[section].name); |
@@ -1362,10 +1382,9 @@ void *__symbol_get(const char *symbol) | |||
1362 | { | 1382 | { |
1363 | struct module *owner; | 1383 | struct module *owner; |
1364 | unsigned long value; | 1384 | unsigned long value; |
1365 | const unsigned long *crc; | ||
1366 | 1385 | ||
1367 | preempt_disable(); | 1386 | preempt_disable(); |
1368 | value = __find_symbol(symbol, &owner, &crc, 1); | 1387 | value = find_symbol(symbol, &owner, NULL, true, true); |
1369 | if (IS_ERR_VALUE(value)) | 1388 | if (IS_ERR_VALUE(value)) |
1370 | value = 0; | 1389 | value = 0; |
1371 | else if (strong_try_module_get(owner)) | 1390 | else if (strong_try_module_get(owner)) |
@@ -1382,33 +1401,33 @@ EXPORT_SYMBOL_GPL(__symbol_get); | |||
1382 | */ | 1401 | */ |
1383 | static int verify_export_symbols(struct module *mod) | 1402 | static int verify_export_symbols(struct module *mod) |
1384 | { | 1403 | { |
1385 | const char *name = NULL; | 1404 | unsigned int i; |
1386 | unsigned long i, ret = 0; | ||
1387 | struct module *owner; | 1405 | struct module *owner; |
1388 | const unsigned long *crc; | 1406 | const struct kernel_symbol *s; |
1389 | 1407 | struct { | |
1390 | for (i = 0; i < mod->num_syms; i++) | 1408 | const struct kernel_symbol *sym; |
1391 | if (!IS_ERR_VALUE(__find_symbol(mod->syms[i].name, | 1409 | unsigned int num; |
1392 | &owner, &crc, 1))) { | 1410 | } arr[] = { |
1393 | name = mod->syms[i].name; | 1411 | { mod->syms, mod->num_syms }, |
1394 | ret = -ENOEXEC; | 1412 | { mod->gpl_syms, mod->num_gpl_syms }, |
1395 | goto dup; | 1413 | { mod->gpl_future_syms, mod->num_gpl_future_syms }, |
1396 | } | 1414 | { mod->unused_syms, mod->num_unused_syms }, |
1415 | { mod->unused_gpl_syms, mod->num_unused_gpl_syms }, | ||
1416 | }; | ||
1397 | 1417 | ||
1398 | for (i = 0; i < mod->num_gpl_syms; i++) | 1418 | for (i = 0; i < ARRAY_SIZE(arr); i++) { |
1399 | if (!IS_ERR_VALUE(__find_symbol(mod->gpl_syms[i].name, | 1419 | for (s = arr[i].sym; s < arr[i].sym + arr[i].num; s++) { |
1400 | &owner, &crc, 1))) { | 1420 | if (!IS_ERR_VALUE(find_symbol(s->name, &owner, |
1401 | name = mod->gpl_syms[i].name; | 1421 | NULL, true, false))) { |
1402 | ret = -ENOEXEC; | 1422 | printk(KERN_ERR |
1403 | goto dup; | 1423 | "%s: exports duplicate symbol %s" |
1424 | " (owned by %s)\n", | ||
1425 | mod->name, s->name, module_name(owner)); | ||
1426 | return -ENOEXEC; | ||
1427 | } | ||
1404 | } | 1428 | } |
1405 | 1429 | } | |
1406 | dup: | 1430 | return 0; |
1407 | if (ret) | ||
1408 | printk(KERN_ERR "%s: exports duplicate symbol %s (owned by %s)\n", | ||
1409 | mod->name, name, module_name(owner)); | ||
1410 | |||
1411 | return ret; | ||
1412 | } | 1431 | } |
1413 | 1432 | ||
1414 | /* Change all symbols so that st_value encodes the pointer directly. */ | 1433 | /* Change all symbols so that st_value encodes the pointer directly. */ |
@@ -1814,8 +1833,9 @@ static struct module *load_module(void __user *umod, | |||
1814 | unwindex = find_sec(hdr, sechdrs, secstrings, ARCH_UNWIND_SECTION_NAME); | 1833 | unwindex = find_sec(hdr, sechdrs, secstrings, ARCH_UNWIND_SECTION_NAME); |
1815 | #endif | 1834 | #endif |
1816 | 1835 | ||
1817 | /* Don't keep modinfo section */ | 1836 | /* Don't keep modinfo and version sections. */ |
1818 | sechdrs[infoindex].sh_flags &= ~(unsigned long)SHF_ALLOC; | 1837 | sechdrs[infoindex].sh_flags &= ~(unsigned long)SHF_ALLOC; |
1838 | sechdrs[versindex].sh_flags &= ~(unsigned long)SHF_ALLOC; | ||
1819 | #ifdef CONFIG_KALLSYMS | 1839 | #ifdef CONFIG_KALLSYMS |
1820 | /* Keep symbol and string tables for decoding later. */ | 1840 | /* Keep symbol and string tables for decoding later. */ |
1821 | sechdrs[symindex].sh_flags |= SHF_ALLOC; | 1841 | sechdrs[symindex].sh_flags |= SHF_ALLOC; |
@@ -1977,7 +1997,8 @@ static struct module *load_module(void __user *umod, | |||
1977 | mod->unused_crcs = (void *)sechdrs[unusedcrcindex].sh_addr; | 1997 | mod->unused_crcs = (void *)sechdrs[unusedcrcindex].sh_addr; |
1978 | mod->unused_gpl_syms = (void *)sechdrs[unusedgplindex].sh_addr; | 1998 | mod->unused_gpl_syms = (void *)sechdrs[unusedgplindex].sh_addr; |
1979 | if (unusedgplcrcindex) | 1999 | if (unusedgplcrcindex) |
1980 | mod->unused_crcs = (void *)sechdrs[unusedgplcrcindex].sh_addr; | 2000 | mod->unused_gpl_crcs |
2001 | = (void *)sechdrs[unusedgplcrcindex].sh_addr; | ||
1981 | 2002 | ||
1982 | #ifdef CONFIG_MODVERSIONS | 2003 | #ifdef CONFIG_MODVERSIONS |
1983 | if ((mod->num_syms && !crcindex) || | 2004 | if ((mod->num_syms && !crcindex) || |
@@ -2171,6 +2192,8 @@ sys_init_module(void __user *umod, | |||
2171 | mod->state = MODULE_STATE_GOING; | 2192 | mod->state = MODULE_STATE_GOING; |
2172 | synchronize_sched(); | 2193 | synchronize_sched(); |
2173 | module_put(mod); | 2194 | module_put(mod); |
2195 | blocking_notifier_call_chain(&module_notify_list, | ||
2196 | MODULE_STATE_GOING, mod); | ||
2174 | mutex_lock(&module_mutex); | 2197 | mutex_lock(&module_mutex); |
2175 | free_module(mod); | 2198 | free_module(mod); |
2176 | mutex_unlock(&module_mutex); | 2199 | mutex_unlock(&module_mutex); |