Module Name: src Committed By: riastradh Date: Mon May 22 14:58:22 UTC 2023
Modified Files: src/sys/kern: subr_autoconf.c src/sys/sys: device.h Log Message: autoconf(9): New functions for referenced attach/detach. New functions: - config_found_acquire(dev, aux, print, cfargs) - config_attach_acquire(parent, cf, aux, print, cfargs) - config_attach_pseudo_acquire(cf, aux) - config_detach_release(dev, flags) - device_acquire(dev) The config_*_acquire functions are like the non-acquire versions, but they return a referenced device_t, which is guaranteed to be safe to use until released. The device's detach function may run while it is referenced, but the device_t will not be freed and the parent's .ca_childdetached routine will not be called. => config_attach_pseudo_acquire additionally lets you pass an aux argument to the device's .ca_attach routine, unlike config_attach_pseudo which always passes NULL. => Eventually, config_found, config_attach, and config_attach_pseudo should be made to return void, because use of the device_t they return is unsafe without the kernel lock and difficult to use safely even with the kernel lock or in a UP system. For now, they require the caller to hold the kernel lock, while config_*_acquire do not. config_detach_release is like device_release and then config_detach, but avoids the race inherent with that sequence. => Eventually, config_detach should be eliminated, because getting at the device_t it needs is unsafe without the kernel lock and difficult to use safely even with the kernel lock or in a UP system. For now, it requires the caller to hold the kernel lock, while config_detach_release does not. device_acquire acquires a reference to a device. It never fails and can be used in thread context (but not interrupt context, hard or soft). Caller is responsible for ensuring that the device_t cannot be freed; in other words, the device_t must be made unavailable to any device_acquire callers before the .ca_detach function returns. Typically device_acquire will be used in a read section (mutex, rwlock, pserialize, &c.) in a data structure lookup, with corresponding logic in the .ca_detach function to remove the device from the data structure and wait for all read sections to complete. Proposed on tech-kern: https://mail-index.netbsd.org/tech-kern/2023/05/10/msg028889.html To generate a diff of this commit: cvs rdiff -u -r1.310 -r1.311 src/sys/kern/subr_autoconf.c cvs rdiff -u -r1.185 -r1.186 src/sys/sys/device.h Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/kern/subr_autoconf.c diff -u src/sys/kern/subr_autoconf.c:1.310 src/sys/kern/subr_autoconf.c:1.311 --- src/sys/kern/subr_autoconf.c:1.310 Fri Apr 21 17:35:43 2023 +++ src/sys/kern/subr_autoconf.c Mon May 22 14:58:22 2023 @@ -1,4 +1,4 @@ -/* $NetBSD: subr_autoconf.c,v 1.310 2023/04/21 17:35:43 riastradh Exp $ */ +/* $NetBSD: subr_autoconf.c,v 1.311 2023/05/22 14:58:22 riastradh Exp $ */ /* * Copyright (c) 1996, 2000 Christopher G. Demetriou @@ -77,7 +77,7 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: subr_autoconf.c,v 1.310 2023/04/21 17:35:43 riastradh Exp $"); +__KERNEL_RCSID(0, "$NetBSD: subr_autoconf.c,v 1.311 2023/05/22 14:58:22 riastradh Exp $"); #ifdef _KERNEL_OPT #include "opt_ddb.h" @@ -1259,17 +1259,21 @@ static const char * const msgs[] = { * not configured, call the given `print' function and return NULL. */ device_t -config_found(device_t parent, void *aux, cfprint_t print, +config_found_acquire(device_t parent, void *aux, cfprint_t print, const struct cfargs * const cfargs) { cfdata_t cf; struct cfargs_internal store; const struct cfargs_internal * const args = cfargs_canonicalize(cfargs, &store); + device_t dev; + + KERNEL_LOCK(1, NULL); cf = config_search_internal(parent, aux, args); if (cf != NULL) { - return config_attach_internal(parent, cf, aux, print, args); + dev = config_attach_internal(parent, cf, aux, print, args); + goto out; } if (print) { @@ -1283,7 +1287,39 @@ config_found(device_t parent, void *aux, aprint_normal("%s", msgs[pret]); } - return NULL; + dev = NULL; + +out: KERNEL_UNLOCK_ONE(NULL); + return dev; +} + +/* + * config_found(parent, aux, print, cfargs) + * + * Legacy entry point for callers whose use of the returned + * device_t is not delimited by device_release. + * + * The caller is required to hold the kernel lock as a fragile + * defence against races. + * + * Callers should ignore the return value or be converted to + * config_found_acquire with a matching device_release once they + * have finished with the returned device_t. + */ +device_t +config_found(device_t parent, void *aux, cfprint_t print, + const struct cfargs * const cfargs) +{ + device_t dev; + + KASSERT(KERNEL_LOCKED_P()); + + dev = config_found_acquire(parent, aux, print, cfargs); + if (dev == NULL) + return NULL; + device_release(dev); + + return dev; } /* @@ -1708,6 +1744,8 @@ config_add_attrib_dict(device_t dev) /* * Attach a found device. + * + * Returns the device referenced, to be released with device_release. */ static device_t config_attach_internal(device_t parent, cfdata_t cf, void *aux, cfprint_t print, @@ -1778,6 +1816,12 @@ config_attach_internal(device_t parent, */ config_pending_incr(dev); + /* + * Prevent concurrent detach from destroying the device_t until + * the caller has released the device. + */ + device_acquire(dev); + /* Call the driver's attach function. */ (*dev->dv_cfattach->ca_attach)(parent, dev, aux); @@ -1813,15 +1857,47 @@ config_attach_internal(device_t parent, } device_t -config_attach(device_t parent, cfdata_t cf, void *aux, cfprint_t print, +config_attach_acquire(device_t parent, cfdata_t cf, void *aux, cfprint_t print, const struct cfargs *cfargs) { struct cfargs_internal store; + device_t dev; + + KERNEL_LOCK(1, NULL); + dev = config_attach_internal(parent, cf, aux, print, + cfargs_canonicalize(cfargs, &store)); + KERNEL_UNLOCK_ONE(NULL); + + return dev; +} + +/* + * config_attach(parent, cf, aux, print, cfargs) + * + * Legacy entry point for callers whose use of the returned + * device_t is not delimited by device_release. + * + * The caller is required to hold the kernel lock as a fragile + * defence against races. + * + * Callers should ignore the return value or be converted to + * config_attach_acquire with a matching device_release once they + * have finished with the returned device_t. + */ +device_t +config_attach(device_t parent, cfdata_t cf, void *aux, cfprint_t print, + const struct cfargs *cfargs) +{ + device_t dev; KASSERT(KERNEL_LOCKED_P()); - return config_attach_internal(parent, cf, aux, print, - cfargs_canonicalize(cfargs, &store)); + dev = config_attach_acquire(parent, cf, aux, print, cfargs); + if (dev == NULL) + return NULL; + device_release(dev); + + return dev; } /* @@ -1834,7 +1910,7 @@ config_attach(device_t parent, cfdata_t * name by the attach routine. */ device_t -config_attach_pseudo(cfdata_t cf) +config_attach_pseudo_acquire(cfdata_t cf, void *aux) { device_t dev; @@ -1867,8 +1943,14 @@ config_attach_pseudo(cfdata_t cf) */ config_pending_incr(dev); + /* + * Prevent concurrent detach from destroying the device_t until + * the caller has released the device. + */ + device_acquire(dev); + /* Call the driver's attach function. */ - (*dev->dv_cfattach->ca_attach)(ROOT, dev, NULL); + (*dev->dv_cfattach->ca_attach)(ROOT, dev, aux); /* * Allow other threads to acquire references to the device now @@ -1893,6 +1975,36 @@ out: KERNEL_UNLOCK_ONE(NULL); } /* + * config_attach_pseudo(cf) + * + * Legacy entry point for callers whose use of the returned + * device_t is not delimited by device_release. + * + * The caller is required to hold the kernel lock as a fragile + * defence against races. + * + * Callers should ignore the return value or be converted to + * config_attach_pseudo_acquire with a matching device_release + * once they have finished with the returned device_t. As a + * bonus, config_attach_pseudo_acquire can pass a non-null aux + * argument into the driver's attach routine. + */ +device_t +config_attach_pseudo(cfdata_t cf) +{ + device_t dev; + + KASSERT(KERNEL_LOCKED_P()); + + dev = config_attach_pseudo_acquire(cf, NULL); + if (dev == NULL) + return dev; + device_release(dev); + + return dev; +} + +/* * Caller must hold alldevs_lock. */ static void @@ -2000,9 +2112,12 @@ config_detach_exit(device_t dev) * Note that this code wants to be run from a process context, so * that the detach can sleep to allow processes which have a device * open to run and unwind their stacks. + * + * Caller must hold a reference with device_acquire or + * device_lookup_acquire. */ int -config_detach(device_t dev, int flags) +config_detach_release(device_t dev, int flags) { struct alldevs_foray af; struct cftable *ct; @@ -2031,6 +2146,7 @@ config_detach(device_t dev, int flags) * attached. */ rv = config_detach_enter(dev); + device_release(dev); if (rv) { KERNEL_UNLOCK_ONE(NULL); return rv; @@ -2186,6 +2302,32 @@ out: } /* + * config_detach(dev, flags) + * + * Legacy entry point for callers that have not acquired a + * reference to dev. + * + * The caller is required to hold the kernel lock as a fragile + * defence against races. + * + * Callers should be converted to use device_acquire under a lock + * taken also by .ca_childdetached to synchronize access to the + * device_t, and then config_detach_release ouside the lock. + * Alternatively, most drivers detach children only in their own + * detach routines, which can be done with config_detach_children + * instead. + */ +int +config_detach(device_t dev, int flags) +{ + + KASSERT(KERNEL_LOCKED_P()); + + device_acquire(dev); + return config_detach_release(dev, flags); +} + +/* * config_detach_commit(dev) * * Issued by a driver's .ca_detach routine to notify anyone @@ -2740,7 +2882,7 @@ retry: if (unit < 0 || unit >= cd->cd_nd mutex_enter(&alldevs_lock); goto retry; } - localcount_acquire(dv->dv_localcount); + device_acquire(dv); } mutex_exit(&alldevs_lock); mutex_exit(&config_misc_lock); @@ -2749,9 +2891,30 @@ retry: if (unit < 0 || unit >= cd->cd_nd } /* + * device_acquire: + * + * Acquire a reference to a device. It is the caller's + * responsibility to ensure that the device's .ca_detach routine + * cannot return before calling this. Caller must release the + * reference with device_release or config_detach_release. + */ +void +device_acquire(device_t dv) +{ + + /* + * No lock because the caller has promised that this can't + * change concurrently with device_acquire. + */ + KASSERTMSG(!dv->dv_detach_done, "%s", + dv == NULL ? "(null)" : device_xname(dv)); + localcount_acquire(dv->dv_localcount); +} + +/* * device_release: * - * Release a reference to a device acquired with + * Release a reference to a device acquired with device_acquire or * device_lookup_acquire. */ void Index: src/sys/sys/device.h diff -u src/sys/sys/device.h:1.185 src/sys/sys/device.h:1.186 --- src/sys/sys/device.h:1.185 Wed Aug 24 11:19:25 2022 +++ src/sys/sys/device.h Mon May 22 14:58:22 2023 @@ -1,4 +1,4 @@ -/* $NetBSD: device.h,v 1.185 2022/08/24 11:19:25 riastradh Exp $ */ +/* $NetBSD: device.h,v 1.186 2023/05/22 14:58:22 riastradh Exp $ */ /* * Copyright (c) 2021 The NetBSD Foundation, Inc. @@ -554,14 +554,20 @@ device_t config_found(device_t, void *, device_t config_rootfound(const char *, void *); device_t config_attach(device_t, cfdata_t, void *, cfprint_t, const struct cfargs *); +device_t config_found_acquire(device_t, void *, cfprint_t, + const struct cfargs *); +device_t config_attach_acquire(device_t, cfdata_t, void *, cfprint_t, + const struct cfargs *); int config_match(device_t, cfdata_t, void *); int config_probe(device_t, cfdata_t, void *); bool ifattr_match(const char *, const char *); device_t config_attach_pseudo(cfdata_t); +device_t config_attach_pseudo_acquire(cfdata_t, void *); int config_detach(device_t, int); +int config_detach_release(device_t, int); int config_detach_children(device_t, int flags); void config_detach_commit(device_t); bool config_detach_all(int); @@ -588,6 +594,7 @@ device_t device_lookup(cfdriver_t, int); void *device_lookup_private(cfdriver_t, int); device_t device_lookup_acquire(cfdriver_t, int); +void device_acquire(device_t); void device_release(device_t); void device_register(device_t, void *);