Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

net: wwan: core: explicit WWAN device reference counting

We need information about existing WWAN device children since we remove
the device after removing the last child. Previously, we tracked users
implicitly by checking whether ops was registered and existence of a
child device of the wwan_class class. Upcoming GNSS (NMEA) port type
support breaks this approach by introducing a child device of the
gnss_class class.

And a modem driver can easily trigger a kernel Oops by removing regular
(e.g., MBIM, AT) ports first and then removing a GNSS port. The WWAN
device will be unregistered on removal of a last regular WWAN port. And
subsequent GNSS port removal will cause NULL pointer dereference in
simple_recursive_removal().

In order to support ports of classes other than wwan_class, switch to
explicit references counting. Introduce a dedicated counter to the WWAN
device struct, increment it on every wwan_create_dev() call, decrement
on wwan_remove_dev(), and actually unregister the WWAN device when there
are no more references.

Run tested with wwan_hwsim with NMEA support patches applied and
different port removing sequences.

Reported-by: Daniele Palmas <dnlplm@gmail.com>
Closes: https://lore.kernel.org/netdev/CAGRyCJE28yf-rrfkFbzu44ygLEvoUM7fecK1vnrghjG_e9UaRA@mail.gmail.com/
Suggested-by: Loic Poulain <loic.poulain@oss.qualcomm.com>
Signed-off-by: Sergey Ryazanov <ryazanov.s.a@gmail.com>
Link: https://patch.msgid.link/20260126062158.308598-3-slark_xiao@163.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Sergey Ryazanov and committed by
Jakub Kicinski
b9879ba7 d95c5aa4

+15 -19
+15 -19
drivers/net/wwan/wwan_core.c
··· 42 42 * struct wwan_device - The structure that defines a WWAN device 43 43 * 44 44 * @id: WWAN device unique ID. 45 + * @refcount: Reference count of this WWAN device. When this refcount reaches 46 + * zero, the device is deleted. NB: access is protected by global 47 + * wwan_register_lock mutex. 45 48 * @dev: Underlying device. 46 49 * @ops: wwan device ops 47 50 * @ops_ctxt: context to pass to ops ··· 52 49 */ 53 50 struct wwan_device { 54 51 unsigned int id; 52 + int refcount; 55 53 struct device dev; 56 54 const struct wwan_ops *ops; 57 55 void *ops_ctxt; ··· 226 222 227 223 /* If wwandev already exists, return it */ 228 224 wwandev = wwan_dev_get_by_parent(parent); 229 - if (!IS_ERR(wwandev)) 225 + if (!IS_ERR(wwandev)) { 226 + wwandev->refcount++; 230 227 goto done_unlock; 228 + } 231 229 232 230 id = ida_alloc(&wwan_dev_ids, GFP_KERNEL); 233 231 if (id < 0) { ··· 248 242 wwandev->dev.class = &wwan_class; 249 243 wwandev->dev.type = &wwan_dev_type; 250 244 wwandev->id = id; 245 + wwandev->refcount = 1; 251 246 dev_set_name(&wwandev->dev, "wwan%d", wwandev->id); 252 247 253 248 err = device_register(&wwandev->dev); ··· 270 263 return wwandev; 271 264 } 272 265 273 - static int is_wwan_child(struct device *dev, void *data) 274 - { 275 - return dev->class == &wwan_class; 276 - } 277 - 278 266 static void wwan_remove_dev(struct wwan_device *wwandev) 279 267 { 280 - int ret; 281 - 282 268 /* Prevent concurrent picking from wwan_create_dev */ 283 269 mutex_lock(&wwan_register_lock); 284 270 285 - /* WWAN device is created and registered (get+add) along with its first 286 - * child port, and subsequent port registrations only grab a reference 287 - * (get). The WWAN device must then be unregistered (del+put) along with 288 - * its last port, and reference simply dropped (put) otherwise. In the 289 - * same fashion, we must not unregister it when the ops are still there. 290 - */ 291 - if (wwandev->ops) 292 - ret = 1; 293 - else 294 - ret = device_for_each_child(&wwandev->dev, NULL, is_wwan_child); 271 + if (--wwandev->refcount <= 0) { 272 + struct device *child = device_find_any_child(&wwandev->dev); 295 273 296 - if (!ret) { 274 + put_device(child); 275 + if (WARN_ON(wwandev->ops || child)) /* Paranoid */ 276 + goto out_unlock; 277 + 297 278 #ifdef CONFIG_WWAN_DEBUGFS 298 279 debugfs_remove_recursive(wwandev->debugfs_dir); 299 280 #endif ··· 290 295 put_device(&wwandev->dev); 291 296 } 292 297 298 + out_unlock: 293 299 mutex_unlock(&wwan_register_lock); 294 300 } 295 301