diff --git a/sys/kern/kern_linker.c b/sys/kern/kern_linker.c --- a/sys/kern/kern_linker.c +++ b/sys/kern/kern_linker.c @@ -106,6 +106,7 @@ linker_file_t linker_kernel_file; static struct sx kld_sx; /* kernel linker lock */ +static bool kld_busy; /* * Load counter used by clients to determine if a linker file has been @@ -1050,6 +1051,40 @@ M_WAITOK)); } +int +linker_set_busy(bool unlock, bool interruptible) +{ + int error; + + sx_xlock(&kld_sx); + while (kld_busy) { + error = sx_sleep(&kld_busy, &kld_sx, interruptible ? PCATCH : + 0, "kldser1", 0); + if (error != 0) { + if (unlock) + sx_xunlock(&kld_sx); + return (error); + } + } + kld_busy = true; + if (unlock) + sx_xunlock(&kld_sx); + return (0); +} + +void +linker_set_unbusy(bool locked) +{ + if (locked) + sx_assert(&kld_sx, SA_XLOCKED); + else + sx_xlock(&kld_sx); + MPASS(kld_busy); + kld_busy = false; + wakeup(&kld_busy); + sx_xunlock(&kld_sx); +} + /* * Syscalls. */ @@ -1066,12 +1101,6 @@ if ((error = priv_check(td, PRIV_KLD_LOAD)) != 0) return (error); - /* - * It is possible that kldloaded module will attach a new ifnet, - * so vnet context must be set when this ocurs. - */ - CURVNET_SET(TD_TO_VNET(td)); - /* * If file does not contain a qualified name or any dot in it * (kldname.ko, or kldname.ver.ko) treat it as an interface @@ -1085,19 +1114,27 @@ modname = file; } - sx_xlock(&kld_sx); - error = linker_load_module(kldname, modname, NULL, NULL, &lf); - if (error) { + error = linker_set_busy(false, true); + if (error != 0) { sx_xunlock(&kld_sx); - goto done; + return (error); } - lf->userrefs++; - if (fileid != NULL) - *fileid = lf->id; - sx_xunlock(&kld_sx); -done: + /* + * It is possible that kldloaded module will attach a new ifnet, + * so vnet context must be set when this ocurs. + */ + CURVNET_SET(TD_TO_VNET(td)); + + error = linker_load_module(kldname, modname, NULL, NULL, &lf); CURVNET_RESTORE(); + + if (error == 0) { + lf->userrefs++; + if (fileid != NULL) + *fileid = lf->id; + } + linker_set_unbusy(true); return (error); } @@ -1132,8 +1169,13 @@ if ((error = priv_check(td, PRIV_KLD_UNLOAD)) != 0) return (error); + error = linker_set_busy(false, true); + if (error != 0) { + sx_xunlock(&kld_sx); + return (error); + } + CURVNET_SET(TD_TO_VNET(td)); - sx_xlock(&kld_sx); lf = linker_find_file_by_id(fileid); if (lf) { KLD_DPF(FILE, ("kldunload: lf->userrefs=%d\n", lf->userrefs)); @@ -1153,9 +1195,8 @@ } } else error = ENOENT; - sx_xunlock(&kld_sx); - CURVNET_RESTORE(); + linker_set_unbusy(true); return (error); } diff --git a/sys/kern/subr_firmware.c b/sys/kern/subr_firmware.c --- a/sys/kern/subr_firmware.c +++ b/sys/kern/subr_firmware.c @@ -251,6 +251,8 @@ linker_file_t result; int error; + error = linker_set_busy(true, false); + MPASS(error == 0); error = linker_reference_module(fwli->imagename, NULL, &result); if (error != 0) { if (bootverbose || (fwli->flags & FIRMWARE_GET_NOWARN) == 0) @@ -275,6 +277,7 @@ done: wakeup_one(arg); mtx_unlock(&firmware_mtx); + linker_set_unbusy(false); } /* diff --git a/sys/sys/linker.h b/sys/sys/linker.h --- a/sys/sys/linker.h +++ b/sys/sys/linker.h @@ -196,6 +196,10 @@ /* HWPMC helper */ void *linker_hwpmc_list_objects(void); +/* kldload/kldunload syscalls blocking */ +int linker_set_busy(bool unlock, bool interruptible); +void linker_set_unbusy(bool locked); + #endif /* _KERNEL */ /*