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

cxl: Prove CXL locking

When CONFIG_PROVE_LOCKING is enabled the 'struct device' definition gets
an additional mutex that is not clobbered by
lockdep_set_novalidate_class() like the typical device_lock(). This
allows for local annotation of subsystem locks with mutex_lock_nested()
per the subsystem's object/lock hierarchy. For CXL, this primarily needs
the ability to lock ports by depth and child objects of ports by their
parent parent-port lock.

Reviewed-by: Jonathan Cameron <Jonathan.Cameron@huawei.com>
Reviewed-by: Ben Widawsky <ben.widawsky@intel.com>
Link: https://lore.kernel.org/r/164365853422.99383.1052399160445197427.stgit@dwillia2-desk3.amr.corp.intel.com
Signed-off-by: Dan Williams <dan.j.williams@intel.com>

+154 -25
+5 -5
drivers/cxl/acpi.c
··· 176 176 { 177 177 struct cxl_dport *dport; 178 178 179 - device_lock(&port->dev); 179 + cxl_device_lock(&port->dev); 180 180 list_for_each_entry(dport, &port->dports, list) 181 181 if (dport->dport == dev) { 182 - device_unlock(&port->dev); 182 + cxl_device_unlock(&port->dev); 183 183 return dport; 184 184 } 185 185 186 - device_unlock(&port->dev); 186 + cxl_device_unlock(&port->dev); 187 187 return NULL; 188 188 } 189 189 ··· 264 264 if (IS_ERR(cxld)) 265 265 return PTR_ERR(cxld); 266 266 267 - device_lock(&port->dev); 267 + cxl_device_lock(&port->dev); 268 268 dport = list_first_entry(&port->dports, typeof(*dport), list); 269 - device_unlock(&port->dev); 269 + cxl_device_unlock(&port->dev); 270 270 271 271 single_port_map[0] = dport->port_id; 272 272
+2 -2
drivers/cxl/core/pmem.c
··· 115 115 * work to flush. Once the state has been changed to 'dead' then no new 116 116 * work can be queued by user-triggered bind. 117 117 */ 118 - device_lock(&cxl_nvb->dev); 118 + cxl_device_lock(&cxl_nvb->dev); 119 119 flush = cxl_nvb->state != CXL_NVB_NEW; 120 120 cxl_nvb->state = CXL_NVB_DEAD; 121 - device_unlock(&cxl_nvb->dev); 121 + cxl_device_unlock(&cxl_nvb->dev); 122 122 123 123 /* 124 124 * Even though the device core will trigger device_release_driver()
+36 -11
drivers/cxl/core/port.c
··· 111 111 ssize_t offset = 0; 112 112 int i, rc = 0; 113 113 114 - device_lock(dev); 114 + cxl_device_lock(dev); 115 115 for (i = 0; i < cxld->interleave_ways; i++) { 116 116 struct cxl_dport *dport = cxld->target[i]; 117 117 struct cxl_dport *next = NULL; ··· 127 127 break; 128 128 offset += rc; 129 129 } 130 - device_unlock(dev); 130 + cxl_device_unlock(dev); 131 131 132 132 if (rc < 0) 133 133 return rc; ··· 214 214 } 215 215 EXPORT_SYMBOL_NS_GPL(is_root_decoder, CXL); 216 216 217 + bool is_cxl_decoder(struct device *dev) 218 + { 219 + return dev->type->release == cxl_decoder_release; 220 + } 221 + EXPORT_SYMBOL_NS_GPL(is_cxl_decoder, CXL); 222 + 217 223 struct cxl_decoder *to_cxl_decoder(struct device *dev) 218 224 { 219 225 if (dev_WARN_ONCE(dev, dev->type->release != cxl_decoder_release, ··· 241 235 struct cxl_port *port = to_cxl_port(dev); 242 236 struct cxl_dport *dport, *_d; 243 237 244 - device_lock(dev); 238 + cxl_device_lock(dev); 245 239 list_for_each_entry_safe(dport, _d, &port->dports, list) 246 240 cxl_dport_release(dport); 247 - device_unlock(dev); 241 + cxl_device_unlock(dev); 248 242 ida_free(&cxl_port_ida, port->id); 249 243 kfree(port); 250 244 } ··· 260 254 .groups = cxl_port_attribute_groups, 261 255 }; 262 256 257 + bool is_cxl_port(struct device *dev) 258 + { 259 + return dev->type == &cxl_port_type; 260 + } 261 + EXPORT_SYMBOL_NS_GPL(is_cxl_port, CXL); 262 + 263 263 struct cxl_port *to_cxl_port(struct device *dev) 264 264 { 265 265 if (dev_WARN_ONCE(dev, dev->type != &cxl_port_type, ··· 273 261 return NULL; 274 262 return container_of(dev, struct cxl_port, dev); 275 263 } 264 + EXPORT_SYMBOL_NS_GPL(to_cxl_port, CXL); 276 265 277 266 static void unregister_port(void *_port) 278 267 { 279 268 struct cxl_port *port = _port; 280 269 struct cxl_dport *dport; 281 270 282 - device_lock(&port->dev); 271 + cxl_device_lock(&port->dev); 283 272 list_for_each_entry(dport, &port->dports, list) { 284 273 char link_name[CXL_TARGET_STRLEN]; 285 274 ··· 289 276 continue; 290 277 sysfs_remove_link(&port->dev.kobj, link_name); 291 278 } 292 - device_unlock(&port->dev); 279 + cxl_device_unlock(&port->dev); 293 280 device_unregister(&port->dev); 294 281 } 295 282 ··· 420 407 { 421 408 struct cxl_dport *dup; 422 409 423 - device_lock(&port->dev); 410 + cxl_device_lock(&port->dev); 424 411 dup = find_dport(port, new->port_id); 425 412 if (dup) 426 413 dev_err(&port->dev, ··· 429 416 dev_name(dup->dport)); 430 417 else 431 418 list_add_tail(&new->list, &port->dports); 432 - device_unlock(&port->dev); 419 + cxl_device_unlock(&port->dev); 433 420 434 421 return dup ? -EEXIST : 0; 435 422 } ··· 488 475 if (!target_map) 489 476 return 0; 490 477 491 - device_lock(&port->dev); 478 + cxl_device_lock(&port->dev); 492 479 if (list_empty(&port->dports)) { 493 480 rc = -EINVAL; 494 481 goto out_unlock; ··· 505 492 } 506 493 507 494 out_unlock: 508 - device_unlock(&port->dev); 495 + cxl_device_unlock(&port->dev); 509 496 510 497 return rc; 511 498 } ··· 730 717 731 718 static int cxl_bus_probe(struct device *dev) 732 719 { 733 - return to_cxl_drv(dev->driver)->probe(dev); 720 + int rc; 721 + 722 + /* 723 + * Take the CXL nested lock since the driver core only holds 724 + * @dev->mutex and not @dev->lockdep_mutex. 725 + */ 726 + cxl_nested_lock(dev); 727 + rc = to_cxl_drv(dev->driver)->probe(dev); 728 + cxl_nested_unlock(dev); 729 + 730 + return rc; 734 731 } 735 732 736 733 static void cxl_bus_remove(struct device *dev) 737 734 { 738 735 struct cxl_driver *cxl_drv = to_cxl_drv(dev->driver); 739 736 737 + cxl_nested_lock(dev); 740 738 if (cxl_drv->remove) 741 739 cxl_drv->remove(dev); 740 + cxl_nested_unlock(dev); 742 741 } 743 742 744 743 struct bus_type cxl_bus_type = {
+81
drivers/cxl/cxl.h
··· 291 291 return port->uport == port->dev.parent; 292 292 } 293 293 294 + bool is_cxl_port(struct device *dev); 294 295 struct cxl_port *to_cxl_port(struct device *dev); 295 296 struct cxl_port *devm_cxl_add_port(struct device *host, struct device *uport, 296 297 resource_size_t component_reg_phys, ··· 302 301 303 302 struct cxl_decoder *to_cxl_decoder(struct device *dev); 304 303 bool is_root_decoder(struct device *dev); 304 + bool is_cxl_decoder(struct device *dev); 305 305 struct cxl_decoder *cxl_root_decoder_alloc(struct cxl_port *port, 306 306 unsigned int nr_targets); 307 307 struct cxl_decoder *cxl_switch_decoder_alloc(struct cxl_port *port, ··· 354 352 */ 355 353 #ifndef __mock 356 354 #define __mock static 355 + #endif 356 + 357 + #ifdef CONFIG_PROVE_CXL_LOCKING 358 + enum cxl_lock_class { 359 + CXL_ANON_LOCK, 360 + CXL_NVDIMM_LOCK, 361 + CXL_NVDIMM_BRIDGE_LOCK, 362 + CXL_PORT_LOCK, 363 + /* 364 + * Be careful to add new lock classes here, CXL_PORT_LOCK is 365 + * extended by the port depth, so a maximum CXL port topology 366 + * depth would need to be defined first. 367 + */ 368 + }; 369 + 370 + static inline void cxl_nested_lock(struct device *dev) 371 + { 372 + if (is_cxl_port(dev)) { 373 + struct cxl_port *port = to_cxl_port(dev); 374 + 375 + mutex_lock_nested(&dev->lockdep_mutex, 376 + CXL_PORT_LOCK + port->depth); 377 + } else if (is_cxl_decoder(dev)) { 378 + struct cxl_port *port = to_cxl_port(dev->parent); 379 + 380 + /* 381 + * A decoder is the immediate child of a port, so set 382 + * its lock class equal to other child device siblings. 383 + */ 384 + mutex_lock_nested(&dev->lockdep_mutex, 385 + CXL_PORT_LOCK + port->depth + 1); 386 + } else if (is_cxl_nvdimm_bridge(dev)) 387 + mutex_lock_nested(&dev->lockdep_mutex, CXL_NVDIMM_BRIDGE_LOCK); 388 + else if (is_cxl_nvdimm(dev)) 389 + mutex_lock_nested(&dev->lockdep_mutex, CXL_NVDIMM_LOCK); 390 + else 391 + mutex_lock_nested(&dev->lockdep_mutex, CXL_ANON_LOCK); 392 + } 393 + 394 + static inline void cxl_nested_unlock(struct device *dev) 395 + { 396 + mutex_unlock(&dev->lockdep_mutex); 397 + } 398 + 399 + static inline void cxl_device_lock(struct device *dev) 400 + { 401 + /* 402 + * For double lock errors the lockup will happen before lockdep 403 + * warns at cxl_nested_lock(), so assert explicitly. 404 + */ 405 + lockdep_assert_not_held(&dev->lockdep_mutex); 406 + 407 + device_lock(dev); 408 + cxl_nested_lock(dev); 409 + } 410 + 411 + static inline void cxl_device_unlock(struct device *dev) 412 + { 413 + cxl_nested_unlock(dev); 414 + device_unlock(dev); 415 + } 416 + #else 417 + static inline void cxl_nested_lock(struct device *dev) 418 + { 419 + } 420 + 421 + static inline void cxl_nested_unlock(struct device *dev) 422 + { 423 + } 424 + 425 + static inline void cxl_device_lock(struct device *dev) 426 + { 427 + device_lock(dev); 428 + } 429 + 430 + static inline void cxl_device_unlock(struct device *dev) 431 + { 432 + device_unlock(dev); 433 + } 357 434 #endif 358 435 #endif /* __CXL_H__ */
+6 -6
drivers/cxl/pmem.c
··· 43 43 if (!cxl_nvb) 44 44 return -ENXIO; 45 45 46 - device_lock(&cxl_nvb->dev); 46 + cxl_device_lock(&cxl_nvb->dev); 47 47 if (!cxl_nvb->nvdimm_bus) { 48 48 rc = -ENXIO; 49 49 goto out; ··· 68 68 dev_set_drvdata(dev, nvdimm); 69 69 rc = devm_add_action_or_reset(dev, unregister_nvdimm, nvdimm); 70 70 out: 71 - device_unlock(&cxl_nvb->dev); 71 + cxl_device_unlock(&cxl_nvb->dev); 72 72 put_device(&cxl_nvb->dev); 73 73 74 74 return rc; ··· 233 233 struct nvdimm_bus *victim_bus = NULL; 234 234 bool release = false, rescan = false; 235 235 236 - device_lock(&cxl_nvb->dev); 236 + cxl_device_lock(&cxl_nvb->dev); 237 237 switch (cxl_nvb->state) { 238 238 case CXL_NVB_ONLINE: 239 239 if (!online_nvdimm_bus(cxl_nvb)) { ··· 251 251 default: 252 252 break; 253 253 } 254 - device_unlock(&cxl_nvb->dev); 254 + cxl_device_unlock(&cxl_nvb->dev); 255 255 256 256 if (release) 257 257 device_release_driver(&cxl_nvb->dev); ··· 327 327 return 0; 328 328 329 329 cxl_nvb = to_cxl_nvdimm_bridge(dev); 330 - device_lock(dev); 330 + cxl_device_lock(dev); 331 331 cxl_nvb->state = CXL_NVB_NEW; 332 - device_unlock(dev); 332 + cxl_device_unlock(dev); 333 333 334 334 return 0; 335 335 }
+1 -1
drivers/nvdimm/nd-core.h
··· 185 185 } 186 186 #endif 187 187 188 - #ifdef CONFIG_PROVE_LOCKING 188 + #ifdef CONFIG_PROVE_NVDIMM_LOCKING 189 189 extern struct class *nd_class; 190 190 191 191 enum {
+23
lib/Kconfig.debug
··· 1516 1516 include the IPI handler function currently executing (if any) 1517 1517 and relevant stack traces. 1518 1518 1519 + choice 1520 + prompt "Lock debugging: prove subsystem device_lock() correctness" 1521 + depends on PROVE_LOCKING 1522 + help 1523 + For subsystems that have instrumented their usage of the device_lock() 1524 + with nested annotations, enable lock dependency checking. The locking 1525 + hierarchy 'subclass' identifiers are not compatible across 1526 + sub-systems, so only one can be enabled at a time. 1527 + 1528 + config PROVE_NVDIMM_LOCKING 1529 + bool "NVDIMM" 1530 + depends on LIBNVDIMM 1531 + help 1532 + Enable lockdep to validate nd_device_lock() usage. 1533 + 1534 + config PROVE_CXL_LOCKING 1535 + bool "CXL" 1536 + depends on CXL_BUS 1537 + help 1538 + Enable lockdep to validate cxl_device_lock() usage. 1539 + 1540 + endchoice 1541 + 1519 1542 endmenu # lock debugging 1520 1543 1521 1544 config TRACE_IRQFLAGS