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.

Merge tag 'cxl-fixes-6.8-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl

Pull cxl fixes from Dan Williams:
"A collection of significant fixes for the CXL subsystem.

The largest change in this set, that bordered on "new development", is
the fix for the fact that the location of the new qos_class attribute
did not match the Documentation. The fix ends up deleting more code
than it added, and it has a new unit test to backstop basic errors in
this interface going forward. So the "red-diff" and unit test saved
the "rip it out and try again" response.

In contrast, the new notification path for firmware reported CXL
errors (CXL CPER notifications) has a locking context bug that can not
be fixed with a red-diff. Given where the release cycle stands, it is
not comfortable to squeeze in that fix in these waning days. So, that
receives the "back it out and try again later" treatment.

There is a regression fix in the code that establishes memory NUMA
nodes for platform CXL regions. That has an ack from x86 folks. There
are a couple more fixups for Linux to understand (reassemble) CXL
regions instantiated by platform firmware. The policy around platforms
that do not match host-physical-address with system-physical-address
(i.e. systems that have an address translation mechanism between the
address range reported in the ACPI CEDT.CFMWS and endpoint decoders)
has been softened to abort driver load rather than teardown the memory
range (can cause system hangs). Lastly, there is a robustness /
regression fix for cases where the driver would previously continue in
the face of error, and a fixup for PCI error notification handling.

Summary:

- Fix NUMA initialization from ACPI CEDT.CFMWS

- Fix region assembly failures due to async init order

- Fix / simplify export of qos_class information

- Fix cxl_acpi initialization vs single-window-init failures

- Fix handling of repeated 'pci_channel_io_frozen' notifications

- Workaround platforms that violate host-physical-address ==
system-physical address assumptions

- Defer CXL CPER notification handling to v6.9"

* tag 'cxl-fixes-6.8-rc6' of git://git.kernel.org/pub/scm/linux/kernel/git/cxl/cxl:
cxl/acpi: Fix load failures due to single window creation failure
acpi/ghes: Remove CXL CPER notifications
cxl/pci: Fix disabling memory if DVSEC CXL Range does not match a CFMWS window
cxl/test: Add support for qos_class checking
cxl: Fix sysfs export of qos_class for memdev
cxl: Remove unnecessary type cast in cxl_qos_class_verify()
cxl: Change 'struct cxl_memdev_state' *_perf_list to single 'struct cxl_dpa_perf'
cxl/region: Allow out of order assembly of autodiscovered regions
cxl/region: Handle endpoint decoders in cxl_region_find_decoder()
x86/numa: Fix the sort compare func used in numa_fill_memblks()
x86/numa: Fix the address overlap check in numa_fill_memblks()
cxl/pci: Skip to handle RAS errors if CXL.mem device is detached

+289 -334
+8 -13
arch/x86/mm/numa.c
··· 934 934 const struct numa_memblk *ma = *(const struct numa_memblk **)a; 935 935 const struct numa_memblk *mb = *(const struct numa_memblk **)b; 936 936 937 - return ma->start - mb->start; 937 + return (ma->start > mb->start) - (ma->start < mb->start); 938 938 } 939 939 940 940 static struct numa_memblk *numa_memblk_list[NR_NODE_MEMBLKS] __initdata; ··· 944 944 * @start: address to begin fill 945 945 * @end: address to end fill 946 946 * 947 - * Find and extend numa_meminfo memblks to cover the @start-@end 948 - * physical address range, such that the first memblk includes 949 - * @start, the last memblk includes @end, and any gaps in between 950 - * are filled. 947 + * Find and extend numa_meminfo memblks to cover the physical 948 + * address range @start-@end 951 949 * 952 950 * RETURNS: 953 951 * 0 : Success 954 - * NUMA_NO_MEMBLK : No memblk exists in @start-@end range 952 + * NUMA_NO_MEMBLK : No memblks exist in address range @start-@end 955 953 */ 956 954 957 955 int __init numa_fill_memblks(u64 start, u64 end) ··· 961 963 962 964 /* 963 965 * Create a list of pointers to numa_meminfo memblks that 964 - * overlap start, end. Exclude (start == bi->end) since 965 - * end addresses in both a CFMWS range and a memblk range 966 - * are exclusive. 967 - * 968 - * This list of pointers is used to make in-place changes 969 - * that fill out the numa_meminfo memblks. 966 + * overlap start, end. The list is used to make in-place 967 + * changes that fill out the numa_meminfo memblks. 970 968 */ 971 969 for (int i = 0; i < mi->nr_blks; i++) { 972 970 struct numa_memblk *bi = &mi->blk[i]; 973 971 974 - if (start < bi->end && end >= bi->start) { 972 + if (memblock_addrs_overlap(start, end - start, bi->start, 973 + bi->end - bi->start)) { 975 974 blk[count] = &mi->blk[i]; 976 975 count++; 977 976 }
-63
drivers/acpi/apei/ghes.c
··· 26 26 #include <linux/interrupt.h> 27 27 #include <linux/timer.h> 28 28 #include <linux/cper.h> 29 - #include <linux/cxl-event.h> 30 29 #include <linux/platform_device.h> 31 30 #include <linux/mutex.h> 32 31 #include <linux/ratelimit.h> ··· 673 674 schedule_work(&entry->work); 674 675 } 675 676 676 - /* 677 - * Only a single callback can be registered for CXL CPER events. 678 - */ 679 - static DECLARE_RWSEM(cxl_cper_rw_sem); 680 - static cxl_cper_callback cper_callback; 681 - 682 - static void cxl_cper_post_event(enum cxl_event_type event_type, 683 - struct cxl_cper_event_rec *rec) 684 - { 685 - if (rec->hdr.length <= sizeof(rec->hdr) || 686 - rec->hdr.length > sizeof(*rec)) { 687 - pr_err(FW_WARN "CXL CPER Invalid section length (%u)\n", 688 - rec->hdr.length); 689 - return; 690 - } 691 - 692 - if (!(rec->hdr.validation_bits & CPER_CXL_COMP_EVENT_LOG_VALID)) { 693 - pr_err(FW_WARN "CXL CPER invalid event\n"); 694 - return; 695 - } 696 - 697 - guard(rwsem_read)(&cxl_cper_rw_sem); 698 - if (cper_callback) 699 - cper_callback(event_type, rec); 700 - } 701 - 702 - int cxl_cper_register_callback(cxl_cper_callback callback) 703 - { 704 - guard(rwsem_write)(&cxl_cper_rw_sem); 705 - if (cper_callback) 706 - return -EINVAL; 707 - cper_callback = callback; 708 - return 0; 709 - } 710 - EXPORT_SYMBOL_NS_GPL(cxl_cper_register_callback, CXL); 711 - 712 - int cxl_cper_unregister_callback(cxl_cper_callback callback) 713 - { 714 - guard(rwsem_write)(&cxl_cper_rw_sem); 715 - if (callback != cper_callback) 716 - return -EINVAL; 717 - cper_callback = NULL; 718 - return 0; 719 - } 720 - EXPORT_SYMBOL_NS_GPL(cxl_cper_unregister_callback, CXL); 721 - 722 677 static bool ghes_do_proc(struct ghes *ghes, 723 678 const struct acpi_hest_generic_status *estatus) 724 679 { ··· 707 754 } 708 755 else if (guid_equal(sec_type, &CPER_SEC_PROC_ARM)) { 709 756 queued = ghes_handle_arm_hw_error(gdata, sev, sync); 710 - } else if (guid_equal(sec_type, &CPER_SEC_CXL_GEN_MEDIA_GUID)) { 711 - struct cxl_cper_event_rec *rec = 712 - acpi_hest_get_payload(gdata); 713 - 714 - cxl_cper_post_event(CXL_CPER_EVENT_GEN_MEDIA, rec); 715 - } else if (guid_equal(sec_type, &CPER_SEC_CXL_DRAM_GUID)) { 716 - struct cxl_cper_event_rec *rec = 717 - acpi_hest_get_payload(gdata); 718 - 719 - cxl_cper_post_event(CXL_CPER_EVENT_DRAM, rec); 720 - } else if (guid_equal(sec_type, 721 - &CPER_SEC_CXL_MEM_MODULE_GUID)) { 722 - struct cxl_cper_event_rec *rec = 723 - acpi_hest_get_payload(gdata); 724 - 725 - cxl_cper_post_event(CXL_CPER_EVENT_MEM_MODULE, rec); 726 757 } else { 727 758 void *err = acpi_hest_get_payload(gdata); 728 759
+28 -18
drivers/cxl/acpi.c
··· 316 316 .qos_class = cxl_acpi_qos_class, 317 317 }; 318 318 319 - static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg, 320 - const unsigned long end) 319 + static int __cxl_parse_cfmws(struct acpi_cedt_cfmws *cfmws, 320 + struct cxl_cfmws_context *ctx) 321 321 { 322 322 int target_map[CXL_DECODER_MAX_INTERLEAVE]; 323 - struct cxl_cfmws_context *ctx = arg; 324 323 struct cxl_port *root_port = ctx->root_port; 325 324 struct resource *cxl_res = ctx->cxl_res; 326 325 struct cxl_cxims_context cxims_ctx; 327 326 struct cxl_root_decoder *cxlrd; 328 327 struct device *dev = ctx->dev; 329 - struct acpi_cedt_cfmws *cfmws; 330 328 cxl_calc_hb_fn cxl_calc_hb; 331 329 struct cxl_decoder *cxld; 332 330 unsigned int ways, i, ig; 333 331 struct resource *res; 334 332 int rc; 335 333 336 - cfmws = (struct acpi_cedt_cfmws *) header; 337 - 338 334 rc = cxl_acpi_cfmws_verify(dev, cfmws); 339 335 if (rc) { 340 336 dev_err(dev, "CFMWS range %#llx-%#llx not registered\n", 341 337 cfmws->base_hpa, 342 338 cfmws->base_hpa + cfmws->window_size - 1); 343 - return 0; 339 + return rc; 344 340 } 345 341 346 342 rc = eiw_to_ways(cfmws->interleave_ways, &ways); ··· 372 376 373 377 cxlrd = cxl_root_decoder_alloc(root_port, ways, cxl_calc_hb); 374 378 if (IS_ERR(cxlrd)) 375 - return 0; 379 + return PTR_ERR(cxlrd); 376 380 377 381 cxld = &cxlrd->cxlsd.cxld; 378 382 cxld->flags = cfmws_to_decoder_flags(cfmws->restrictions); ··· 416 420 put_device(&cxld->dev); 417 421 else 418 422 rc = cxl_decoder_autoremove(dev, cxld); 419 - if (rc) { 420 - dev_err(dev, "Failed to add decode range: %pr", res); 421 - return rc; 422 - } 423 - dev_dbg(dev, "add: %s node: %d range [%#llx - %#llx]\n", 424 - dev_name(&cxld->dev), 425 - phys_to_target_node(cxld->hpa_range.start), 426 - cxld->hpa_range.start, cxld->hpa_range.end); 427 - 428 - return 0; 423 + return rc; 429 424 430 425 err_insert: 431 426 kfree(res->name); 432 427 err_name: 433 428 kfree(res); 434 429 return -ENOMEM; 430 + } 431 + 432 + static int cxl_parse_cfmws(union acpi_subtable_headers *header, void *arg, 433 + const unsigned long end) 434 + { 435 + struct acpi_cedt_cfmws *cfmws = (struct acpi_cedt_cfmws *)header; 436 + struct cxl_cfmws_context *ctx = arg; 437 + struct device *dev = ctx->dev; 438 + int rc; 439 + 440 + rc = __cxl_parse_cfmws(cfmws, ctx); 441 + if (rc) 442 + dev_err(dev, 443 + "Failed to add decode range: [%#llx - %#llx] (%d)\n", 444 + cfmws->base_hpa, 445 + cfmws->base_hpa + cfmws->window_size - 1, rc); 446 + else 447 + dev_dbg(dev, "decode range: node: %d range [%#llx - %#llx]\n", 448 + phys_to_target_node(cfmws->base_hpa), cfmws->base_hpa, 449 + cfmws->base_hpa + cfmws->window_size - 1); 450 + 451 + /* never fail cxl_acpi load for a single window failure */ 452 + return 0; 435 453 } 436 454 437 455 __mock struct acpi_device *to_cxl_host_bridge(struct device *host,
+26 -60
drivers/cxl/core/cdat.c
··· 210 210 return 0; 211 211 } 212 212 213 - static void add_perf_entry(struct device *dev, struct dsmas_entry *dent, 214 - struct list_head *list) 213 + static void update_perf_entry(struct device *dev, struct dsmas_entry *dent, 214 + struct cxl_dpa_perf *dpa_perf) 215 215 { 216 - struct cxl_dpa_perf *dpa_perf; 217 - 218 - dpa_perf = kzalloc(sizeof(*dpa_perf), GFP_KERNEL); 219 - if (!dpa_perf) 220 - return; 221 - 222 216 dpa_perf->dpa_range = dent->dpa_range; 223 217 dpa_perf->coord = dent->coord; 224 218 dpa_perf->qos_class = dent->qos_class; 225 - list_add_tail(&dpa_perf->list, list); 226 219 dev_dbg(dev, 227 220 "DSMAS: dpa: %#llx qos: %d read_bw: %d write_bw %d read_lat: %d write_lat: %d\n", 228 221 dent->dpa_range.start, dpa_perf->qos_class, 229 222 dent->coord.read_bandwidth, dent->coord.write_bandwidth, 230 223 dent->coord.read_latency, dent->coord.write_latency); 231 - } 232 - 233 - static void free_perf_ents(void *data) 234 - { 235 - struct cxl_memdev_state *mds = data; 236 - struct cxl_dpa_perf *dpa_perf, *n; 237 - LIST_HEAD(discard); 238 - 239 - list_splice_tail_init(&mds->ram_perf_list, &discard); 240 - list_splice_tail_init(&mds->pmem_perf_list, &discard); 241 - list_for_each_entry_safe(dpa_perf, n, &discard, list) { 242 - list_del(&dpa_perf->list); 243 - kfree(dpa_perf); 244 - } 245 224 } 246 225 247 226 static void cxl_memdev_set_qos_class(struct cxl_dev_state *cxlds, ··· 242 263 xa_for_each(dsmas_xa, index, dent) { 243 264 if (resource_size(&cxlds->ram_res) && 244 265 range_contains(&ram_range, &dent->dpa_range)) 245 - add_perf_entry(dev, dent, &mds->ram_perf_list); 266 + update_perf_entry(dev, dent, &mds->ram_perf); 246 267 else if (resource_size(&cxlds->pmem_res) && 247 268 range_contains(&pmem_range, &dent->dpa_range)) 248 - add_perf_entry(dev, dent, &mds->pmem_perf_list); 269 + update_perf_entry(dev, dent, &mds->pmem_perf); 249 270 else 250 271 dev_dbg(dev, "no partition for dsmas dpa: %#llx\n", 251 272 dent->dpa_range.start); 252 273 } 253 - 254 - devm_add_action_or_reset(&cxlds->cxlmd->dev, free_perf_ents, mds); 255 274 } 256 275 257 276 static int match_cxlrd_qos_class(struct device *dev, void *data) ··· 270 293 return 0; 271 294 } 272 295 273 - static void cxl_qos_match(struct cxl_port *root_port, 274 - struct list_head *work_list, 275 - struct list_head *discard_list) 296 + static void reset_dpa_perf(struct cxl_dpa_perf *dpa_perf) 276 297 { 277 - struct cxl_dpa_perf *dpa_perf, *n; 298 + *dpa_perf = (struct cxl_dpa_perf) { 299 + .qos_class = CXL_QOS_CLASS_INVALID, 300 + }; 301 + } 278 302 279 - list_for_each_entry_safe(dpa_perf, n, work_list, list) { 280 - int rc; 303 + static bool cxl_qos_match(struct cxl_port *root_port, 304 + struct cxl_dpa_perf *dpa_perf) 305 + { 306 + if (dpa_perf->qos_class == CXL_QOS_CLASS_INVALID) 307 + return false; 281 308 282 - if (dpa_perf->qos_class == CXL_QOS_CLASS_INVALID) 283 - return; 309 + if (!device_for_each_child(&root_port->dev, &dpa_perf->qos_class, 310 + match_cxlrd_qos_class)) 311 + return false; 284 312 285 - rc = device_for_each_child(&root_port->dev, 286 - (void *)&dpa_perf->qos_class, 287 - match_cxlrd_qos_class); 288 - if (!rc) 289 - list_move_tail(&dpa_perf->list, discard_list); 290 - } 313 + return true; 291 314 } 292 315 293 316 static int match_cxlrd_hb(struct device *dev, void *data) ··· 311 334 return 0; 312 335 } 313 336 314 - static void discard_dpa_perf(struct list_head *list) 315 - { 316 - struct cxl_dpa_perf *dpa_perf, *n; 317 - 318 - list_for_each_entry_safe(dpa_perf, n, list, list) { 319 - list_del(&dpa_perf->list); 320 - kfree(dpa_perf); 321 - } 322 - } 323 - DEFINE_FREE(dpa_perf, struct list_head *, if (!list_empty(_T)) discard_dpa_perf(_T)) 324 - 325 337 static int cxl_qos_class_verify(struct cxl_memdev *cxlmd) 326 338 { 327 339 struct cxl_dev_state *cxlds = cxlmd->cxlds; 328 340 struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds); 329 - LIST_HEAD(__discard); 330 - struct list_head *discard __free(dpa_perf) = &__discard; 331 341 struct cxl_port *root_port; 332 342 int rc; 333 343 ··· 327 363 root_port = &cxl_root->port; 328 364 329 365 /* Check that the QTG IDs are all sane between end device and root decoders */ 330 - cxl_qos_match(root_port, &mds->ram_perf_list, discard); 331 - cxl_qos_match(root_port, &mds->pmem_perf_list, discard); 366 + if (!cxl_qos_match(root_port, &mds->ram_perf)) 367 + reset_dpa_perf(&mds->ram_perf); 368 + if (!cxl_qos_match(root_port, &mds->pmem_perf)) 369 + reset_dpa_perf(&mds->pmem_perf); 332 370 333 371 /* Check to make sure that the device's host bridge is under a root decoder */ 334 372 rc = device_for_each_child(&root_port->dev, 335 - (void *)cxlmd->endpoint->host_bridge, 336 - match_cxlrd_hb); 373 + cxlmd->endpoint->host_bridge, match_cxlrd_hb); 337 374 if (!rc) { 338 - list_splice_tail_init(&mds->ram_perf_list, discard); 339 - list_splice_tail_init(&mds->pmem_perf_list, discard); 375 + reset_dpa_perf(&mds->ram_perf); 376 + reset_dpa_perf(&mds->pmem_perf); 340 377 } 341 378 342 379 return rc; ··· 382 417 383 418 cxl_memdev_set_qos_class(cxlds, dsmas_xa); 384 419 cxl_qos_class_verify(cxlmd); 420 + cxl_memdev_update_perf(cxlmd); 385 421 } 386 422 EXPORT_SYMBOL_NS_GPL(cxl_endpoint_parse_cdat, CXL); 387 423
+2 -2
drivers/cxl/core/mbox.c
··· 1391 1391 mds->cxlds.reg_map.host = dev; 1392 1392 mds->cxlds.reg_map.resource = CXL_RESOURCE_NONE; 1393 1393 mds->cxlds.type = CXL_DEVTYPE_CLASSMEM; 1394 - INIT_LIST_HEAD(&mds->ram_perf_list); 1395 - INIT_LIST_HEAD(&mds->pmem_perf_list); 1394 + mds->ram_perf.qos_class = CXL_QOS_CLASS_INVALID; 1395 + mds->pmem_perf.qos_class = CXL_QOS_CLASS_INVALID; 1396 1396 1397 1397 return mds; 1398 1398 }
+63
drivers/cxl/core/memdev.c
··· 447 447 NULL, 448 448 }; 449 449 450 + static ssize_t pmem_qos_class_show(struct device *dev, 451 + struct device_attribute *attr, char *buf) 452 + { 453 + struct cxl_memdev *cxlmd = to_cxl_memdev(dev); 454 + struct cxl_dev_state *cxlds = cxlmd->cxlds; 455 + struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds); 456 + 457 + return sysfs_emit(buf, "%d\n", mds->pmem_perf.qos_class); 458 + } 459 + 460 + static struct device_attribute dev_attr_pmem_qos_class = 461 + __ATTR(qos_class, 0444, pmem_qos_class_show, NULL); 462 + 450 463 static struct attribute *cxl_memdev_pmem_attributes[] = { 451 464 &dev_attr_pmem_size.attr, 465 + &dev_attr_pmem_qos_class.attr, 452 466 NULL, 453 467 }; 454 468 469 + static ssize_t ram_qos_class_show(struct device *dev, 470 + struct device_attribute *attr, char *buf) 471 + { 472 + struct cxl_memdev *cxlmd = to_cxl_memdev(dev); 473 + struct cxl_dev_state *cxlds = cxlmd->cxlds; 474 + struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds); 475 + 476 + return sysfs_emit(buf, "%d\n", mds->ram_perf.qos_class); 477 + } 478 + 479 + static struct device_attribute dev_attr_ram_qos_class = 480 + __ATTR(qos_class, 0444, ram_qos_class_show, NULL); 481 + 455 482 static struct attribute *cxl_memdev_ram_attributes[] = { 456 483 &dev_attr_ram_size.attr, 484 + &dev_attr_ram_qos_class.attr, 457 485 NULL, 458 486 }; 459 487 ··· 505 477 .is_visible = cxl_memdev_visible, 506 478 }; 507 479 480 + static umode_t cxl_ram_visible(struct kobject *kobj, struct attribute *a, int n) 481 + { 482 + struct device *dev = kobj_to_dev(kobj); 483 + struct cxl_memdev *cxlmd = to_cxl_memdev(dev); 484 + struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds); 485 + 486 + if (a == &dev_attr_ram_qos_class.attr) 487 + if (mds->ram_perf.qos_class == CXL_QOS_CLASS_INVALID) 488 + return 0; 489 + 490 + return a->mode; 491 + } 492 + 508 493 static struct attribute_group cxl_memdev_ram_attribute_group = { 509 494 .name = "ram", 510 495 .attrs = cxl_memdev_ram_attributes, 496 + .is_visible = cxl_ram_visible, 511 497 }; 498 + 499 + static umode_t cxl_pmem_visible(struct kobject *kobj, struct attribute *a, int n) 500 + { 501 + struct device *dev = kobj_to_dev(kobj); 502 + struct cxl_memdev *cxlmd = to_cxl_memdev(dev); 503 + struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlmd->cxlds); 504 + 505 + if (a == &dev_attr_pmem_qos_class.attr) 506 + if (mds->pmem_perf.qos_class == CXL_QOS_CLASS_INVALID) 507 + return 0; 508 + 509 + return a->mode; 510 + } 512 511 513 512 static struct attribute_group cxl_memdev_pmem_attribute_group = { 514 513 .name = "pmem", 515 514 .attrs = cxl_memdev_pmem_attributes, 515 + .is_visible = cxl_pmem_visible, 516 516 }; 517 517 518 518 static umode_t cxl_memdev_security_visible(struct kobject *kobj, ··· 574 518 &cxl_memdev_security_attribute_group, 575 519 NULL, 576 520 }; 521 + 522 + void cxl_memdev_update_perf(struct cxl_memdev *cxlmd) 523 + { 524 + sysfs_update_group(&cxlmd->dev.kobj, &cxl_memdev_ram_attribute_group); 525 + sysfs_update_group(&cxlmd->dev.kobj, &cxl_memdev_pmem_attribute_group); 526 + } 527 + EXPORT_SYMBOL_NS_GPL(cxl_memdev_update_perf, CXL); 577 528 578 529 static const struct device_type cxl_memdev_type = { 579 530 .name = "cxl_memdev",
+34 -15
drivers/cxl/core/pci.c
··· 477 477 allowed++; 478 478 } 479 479 480 - if (!allowed) { 481 - cxl_set_mem_enable(cxlds, 0); 482 - info->mem_enabled = 0; 480 + if (!allowed && info->mem_enabled) { 481 + dev_err(dev, "Range register decodes outside platform defined CXL ranges.\n"); 482 + return -ENXIO; 483 483 } 484 484 485 485 /* ··· 932 932 void cxl_cor_error_detected(struct pci_dev *pdev) 933 933 { 934 934 struct cxl_dev_state *cxlds = pci_get_drvdata(pdev); 935 + struct device *dev = &cxlds->cxlmd->dev; 935 936 936 - if (cxlds->rcd) 937 - cxl_handle_rdport_errors(cxlds); 937 + scoped_guard(device, dev) { 938 + if (!dev->driver) { 939 + dev_warn(&pdev->dev, 940 + "%s: memdev disabled, abort error handling\n", 941 + dev_name(dev)); 942 + return; 943 + } 938 944 939 - cxl_handle_endpoint_cor_ras(cxlds); 945 + if (cxlds->rcd) 946 + cxl_handle_rdport_errors(cxlds); 947 + 948 + cxl_handle_endpoint_cor_ras(cxlds); 949 + } 940 950 } 941 951 EXPORT_SYMBOL_NS_GPL(cxl_cor_error_detected, CXL); 942 952 ··· 958 948 struct device *dev = &cxlmd->dev; 959 949 bool ue; 960 950 961 - if (cxlds->rcd) 962 - cxl_handle_rdport_errors(cxlds); 951 + scoped_guard(device, dev) { 952 + if (!dev->driver) { 953 + dev_warn(&pdev->dev, 954 + "%s: memdev disabled, abort error handling\n", 955 + dev_name(dev)); 956 + return PCI_ERS_RESULT_DISCONNECT; 957 + } 963 958 964 - /* 965 - * A frozen channel indicates an impending reset which is fatal to 966 - * CXL.mem operation, and will likely crash the system. On the off 967 - * chance the situation is recoverable dump the status of the RAS 968 - * capability registers and bounce the active state of the memdev. 969 - */ 970 - ue = cxl_handle_endpoint_ras(cxlds); 959 + if (cxlds->rcd) 960 + cxl_handle_rdport_errors(cxlds); 961 + /* 962 + * A frozen channel indicates an impending reset which is fatal to 963 + * CXL.mem operation, and will likely crash the system. On the off 964 + * chance the situation is recoverable dump the status of the RAS 965 + * capability registers and bounce the active state of the memdev. 966 + */ 967 + ue = cxl_handle_endpoint_ras(cxlds); 968 + } 969 + 971 970 972 971 switch (state) { 973 972 case pci_channel_io_normal:
+46 -16
drivers/cxl/core/region.c
··· 730 730 return 0; 731 731 } 732 732 733 - static struct cxl_decoder *cxl_region_find_decoder(struct cxl_port *port, 734 - struct cxl_region *cxlr) 733 + static struct cxl_decoder * 734 + cxl_region_find_decoder(struct cxl_port *port, 735 + struct cxl_endpoint_decoder *cxled, 736 + struct cxl_region *cxlr) 735 737 { 736 738 struct device *dev; 737 739 int id = 0; 740 + 741 + if (port == cxled_to_port(cxled)) 742 + return &cxled->cxld; 738 743 739 744 if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) 740 745 dev = device_find_child(&port->dev, &cxlr->params, ··· 758 753 return to_cxl_decoder(dev); 759 754 } 760 755 761 - static struct cxl_region_ref *alloc_region_ref(struct cxl_port *port, 762 - struct cxl_region *cxlr) 756 + static bool auto_order_ok(struct cxl_port *port, struct cxl_region *cxlr_iter, 757 + struct cxl_decoder *cxld) 758 + { 759 + struct cxl_region_ref *rr = cxl_rr_load(port, cxlr_iter); 760 + struct cxl_decoder *cxld_iter = rr->decoder; 761 + 762 + /* 763 + * Allow the out of order assembly of auto-discovered regions. 764 + * Per CXL Spec 3.1 8.2.4.20.12 software must commit decoders 765 + * in HPA order. Confirm that the decoder with the lesser HPA 766 + * starting address has the lesser id. 767 + */ 768 + dev_dbg(&cxld->dev, "check for HPA violation %s:%d < %s:%d\n", 769 + dev_name(&cxld->dev), cxld->id, 770 + dev_name(&cxld_iter->dev), cxld_iter->id); 771 + 772 + if (cxld_iter->id > cxld->id) 773 + return true; 774 + 775 + return false; 776 + } 777 + 778 + static struct cxl_region_ref * 779 + alloc_region_ref(struct cxl_port *port, struct cxl_region *cxlr, 780 + struct cxl_endpoint_decoder *cxled) 763 781 { 764 782 struct cxl_region_params *p = &cxlr->params; 765 783 struct cxl_region_ref *cxl_rr, *iter; ··· 792 764 xa_for_each(&port->regions, index, iter) { 793 765 struct cxl_region_params *ip = &iter->region->params; 794 766 795 - if (!ip->res) 767 + if (!ip->res || ip->res->start < p->res->start) 796 768 continue; 797 769 798 - if (ip->res->start > p->res->start) { 799 - dev_dbg(&cxlr->dev, 800 - "%s: HPA order violation %s:%pr vs %pr\n", 801 - dev_name(&port->dev), 802 - dev_name(&iter->region->dev), ip->res, p->res); 803 - return ERR_PTR(-EBUSY); 770 + if (test_bit(CXL_REGION_F_AUTO, &cxlr->flags)) { 771 + struct cxl_decoder *cxld; 772 + 773 + cxld = cxl_region_find_decoder(port, cxled, cxlr); 774 + if (auto_order_ok(port, iter->region, cxld)) 775 + continue; 804 776 } 777 + dev_dbg(&cxlr->dev, "%s: HPA order violation %s:%pr vs %pr\n", 778 + dev_name(&port->dev), 779 + dev_name(&iter->region->dev), ip->res, p->res); 780 + 781 + return ERR_PTR(-EBUSY); 805 782 } 806 783 807 784 cxl_rr = kzalloc(sizeof(*cxl_rr), GFP_KERNEL); ··· 886 853 { 887 854 struct cxl_decoder *cxld; 888 855 889 - if (port == cxled_to_port(cxled)) 890 - cxld = &cxled->cxld; 891 - else 892 - cxld = cxl_region_find_decoder(port, cxlr); 856 + cxld = cxl_region_find_decoder(port, cxled, cxlr); 893 857 if (!cxld) { 894 858 dev_dbg(&cxlr->dev, "%s: no decoder available\n", 895 859 dev_name(&port->dev)); ··· 983 953 nr_targets_inc = true; 984 954 } 985 955 } else { 986 - cxl_rr = alloc_region_ref(port, cxlr); 956 + cxl_rr = alloc_region_ref(port, cxlr, cxled); 987 957 if (IS_ERR(cxl_rr)) { 988 958 dev_dbg(&cxlr->dev, 989 959 "%s: failed to allocate region reference\n",
+2
drivers/cxl/cxl.h
··· 880 880 int cxl_endpoint_get_perf_coordinates(struct cxl_port *port, 881 881 struct access_coordinate *coord); 882 882 883 + void cxl_memdev_update_perf(struct cxl_memdev *cxlmd); 884 + 883 885 /* 884 886 * Unit test builds overrides this to __weak, find the 'strong' version 885 887 * of these symbols in tools/testing/cxl/.
+4 -6
drivers/cxl/cxlmem.h
··· 395 395 396 396 /** 397 397 * struct cxl_dpa_perf - DPA performance property entry 398 - * @list - list entry 399 398 * @dpa_range - range for DPA address 400 399 * @coord - QoS performance data (i.e. latency, bandwidth) 401 400 * @qos_class - QoS Class cookies 402 401 */ 403 402 struct cxl_dpa_perf { 404 - struct list_head list; 405 403 struct range dpa_range; 406 404 struct access_coordinate coord; 407 405 int qos_class; ··· 469 471 * @security: security driver state info 470 472 * @fw: firmware upload / activation state 471 473 * @mbox_send: @dev specific transport for transmitting mailbox commands 472 - * @ram_perf_list: performance data entries matched to RAM 473 - * @pmem_perf_list: performance data entries matched to PMEM 474 + * @ram_perf: performance data entry matched to RAM partition 475 + * @pmem_perf: performance data entry matched to PMEM partition 474 476 * 475 477 * See CXL 3.0 8.2.9.8.2 Capacity Configuration and Label Storage for 476 478 * details on capacity parameters. ··· 492 494 u64 next_volatile_bytes; 493 495 u64 next_persistent_bytes; 494 496 495 - struct list_head ram_perf_list; 496 - struct list_head pmem_perf_list; 497 + struct cxl_dpa_perf ram_perf; 498 + struct cxl_dpa_perf pmem_perf; 497 499 498 500 struct cxl_event_state event; 499 501 struct cxl_poison_state poison;
-56
drivers/cxl/mem.c
··· 215 215 } 216 216 static DEVICE_ATTR_WO(trigger_poison_list); 217 217 218 - static ssize_t ram_qos_class_show(struct device *dev, 219 - struct device_attribute *attr, char *buf) 220 - { 221 - struct cxl_memdev *cxlmd = to_cxl_memdev(dev); 222 - struct cxl_dev_state *cxlds = cxlmd->cxlds; 223 - struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds); 224 - struct cxl_dpa_perf *dpa_perf; 225 - 226 - if (!dev->driver) 227 - return -ENOENT; 228 - 229 - if (list_empty(&mds->ram_perf_list)) 230 - return -ENOENT; 231 - 232 - dpa_perf = list_first_entry(&mds->ram_perf_list, struct cxl_dpa_perf, 233 - list); 234 - 235 - return sysfs_emit(buf, "%d\n", dpa_perf->qos_class); 236 - } 237 - 238 - static struct device_attribute dev_attr_ram_qos_class = 239 - __ATTR(qos_class, 0444, ram_qos_class_show, NULL); 240 - 241 - static ssize_t pmem_qos_class_show(struct device *dev, 242 - struct device_attribute *attr, char *buf) 243 - { 244 - struct cxl_memdev *cxlmd = to_cxl_memdev(dev); 245 - struct cxl_dev_state *cxlds = cxlmd->cxlds; 246 - struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds); 247 - struct cxl_dpa_perf *dpa_perf; 248 - 249 - if (!dev->driver) 250 - return -ENOENT; 251 - 252 - if (list_empty(&mds->pmem_perf_list)) 253 - return -ENOENT; 254 - 255 - dpa_perf = list_first_entry(&mds->pmem_perf_list, struct cxl_dpa_perf, 256 - list); 257 - 258 - return sysfs_emit(buf, "%d\n", dpa_perf->qos_class); 259 - } 260 - 261 - static struct device_attribute dev_attr_pmem_qos_class = 262 - __ATTR(qos_class, 0444, pmem_qos_class_show, NULL); 263 - 264 218 static umode_t cxl_mem_visible(struct kobject *kobj, struct attribute *a, int n) 265 219 { 266 220 struct device *dev = kobj_to_dev(kobj); ··· 226 272 mds->poison.enabled_cmds)) 227 273 return 0; 228 274 229 - if (a == &dev_attr_pmem_qos_class.attr) 230 - if (list_empty(&mds->pmem_perf_list)) 231 - return 0; 232 - 233 - if (a == &dev_attr_ram_qos_class.attr) 234 - if (list_empty(&mds->ram_perf_list)) 235 - return 0; 236 - 237 275 return a->mode; 238 276 } 239 277 240 278 static struct attribute *cxl_mem_attrs[] = { 241 279 &dev_attr_trigger_poison_list.attr, 242 - &dev_attr_ram_qos_class.attr, 243 - &dev_attr_pmem_qos_class.attr, 244 280 NULL 245 281 }; 246 282
+1 -56
drivers/cxl/pci.c
··· 974 974 }, 975 975 }; 976 976 977 - #define CXL_EVENT_HDR_FLAGS_REC_SEVERITY GENMASK(1, 0) 978 - static void cxl_cper_event_call(enum cxl_event_type ev_type, 979 - struct cxl_cper_event_rec *rec) 980 - { 981 - struct cper_cxl_event_devid *device_id = &rec->hdr.device_id; 982 - struct pci_dev *pdev __free(pci_dev_put) = NULL; 983 - enum cxl_event_log_type log_type; 984 - struct cxl_dev_state *cxlds; 985 - unsigned int devfn; 986 - u32 hdr_flags; 987 - 988 - devfn = PCI_DEVFN(device_id->device_num, device_id->func_num); 989 - pdev = pci_get_domain_bus_and_slot(device_id->segment_num, 990 - device_id->bus_num, devfn); 991 - if (!pdev) 992 - return; 993 - 994 - guard(pci_dev)(pdev); 995 - if (pdev->driver != &cxl_pci_driver) 996 - return; 997 - 998 - cxlds = pci_get_drvdata(pdev); 999 - if (!cxlds) 1000 - return; 1001 - 1002 - /* Fabricate a log type */ 1003 - hdr_flags = get_unaligned_le24(rec->event.generic.hdr.flags); 1004 - log_type = FIELD_GET(CXL_EVENT_HDR_FLAGS_REC_SEVERITY, hdr_flags); 1005 - 1006 - cxl_event_trace_record(cxlds->cxlmd, log_type, ev_type, 1007 - &uuid_null, &rec->event); 1008 - } 1009 - 1010 - static int __init cxl_pci_driver_init(void) 1011 - { 1012 - int rc; 1013 - 1014 - rc = cxl_cper_register_callback(cxl_cper_event_call); 1015 - if (rc) 1016 - return rc; 1017 - 1018 - rc = pci_register_driver(&cxl_pci_driver); 1019 - if (rc) 1020 - cxl_cper_unregister_callback(cxl_cper_event_call); 1021 - 1022 - return rc; 1023 - } 1024 - 1025 - static void __exit cxl_pci_driver_exit(void) 1026 - { 1027 - pci_unregister_driver(&cxl_pci_driver); 1028 - cxl_cper_unregister_callback(cxl_cper_event_call); 1029 - } 1030 - 1031 - module_init(cxl_pci_driver_init); 1032 - module_exit(cxl_pci_driver_exit); 977 + module_pci_driver(cxl_pci_driver); 1033 978 MODULE_LICENSE("GPL v2"); 1034 979 MODULE_IMPORT_NS(CXL);
-18
include/linux/cxl-event.h
··· 140 140 union cxl_event event; 141 141 } __packed; 142 142 143 - typedef void (*cxl_cper_callback)(enum cxl_event_type type, 144 - struct cxl_cper_event_rec *rec); 145 - 146 - #ifdef CONFIG_ACPI_APEI_GHES 147 - int cxl_cper_register_callback(cxl_cper_callback callback); 148 - int cxl_cper_unregister_callback(cxl_cper_callback callback); 149 - #else 150 - static inline int cxl_cper_register_callback(cxl_cper_callback callback) 151 - { 152 - return 0; 153 - } 154 - 155 - static inline int cxl_cper_unregister_callback(cxl_cper_callback callback) 156 - { 157 - return 0; 158 - } 159 - #endif 160 - 161 143 #endif /* _LINUX_CXL_EVENT_H */
+2
include/linux/memblock.h
··· 121 121 int memblock_physmem_add(phys_addr_t base, phys_addr_t size); 122 122 #endif 123 123 void memblock_trim_memory(phys_addr_t align); 124 + unsigned long memblock_addrs_overlap(phys_addr_t base1, phys_addr_t size1, 125 + phys_addr_t base2, phys_addr_t size2); 124 126 bool memblock_overlaps_region(struct memblock_type *type, 125 127 phys_addr_t base, phys_addr_t size); 126 128 bool memblock_validate_numa_coverage(unsigned long threshold_bytes);
+3 -2
mm/memblock.c
··· 180 180 /* 181 181 * Address comparison utilities 182 182 */ 183 - static unsigned long __init_memblock memblock_addrs_overlap(phys_addr_t base1, phys_addr_t size1, 184 - phys_addr_t base2, phys_addr_t size2) 183 + unsigned long __init_memblock 184 + memblock_addrs_overlap(phys_addr_t base1, phys_addr_t size1, phys_addr_t base2, 185 + phys_addr_t size2) 185 186 { 186 187 return ((base1 < (base2 + size2)) && (base2 < (base1 + size1))); 187 188 }
+1
tools/testing/cxl/Kbuild
··· 13 13 ldflags-y += --wrap=cxl_dvsec_rr_decode 14 14 ldflags-y += --wrap=devm_cxl_add_rch_dport 15 15 ldflags-y += --wrap=cxl_rcd_component_reg_phys 16 + ldflags-y += --wrap=cxl_endpoint_parse_cdat 16 17 17 18 DRIVERS := ../../../drivers 18 19 CXL_SRC := $(DRIVERS)/cxl
+54 -9
tools/testing/cxl/test/cxl.c
··· 15 15 16 16 static int interleave_arithmetic; 17 17 18 + #define FAKE_QTG_ID 42 19 + 18 20 #define NR_CXL_HOST_BRIDGES 2 19 21 #define NR_CXL_SINGLE_HOST 1 20 22 #define NR_CXL_RCH 1 ··· 211 209 .granularity = 4, 212 210 .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | 213 211 ACPI_CEDT_CFMWS_RESTRICT_VOLATILE, 214 - .qtg_id = 0, 212 + .qtg_id = FAKE_QTG_ID, 215 213 .window_size = SZ_256M * 4UL, 216 214 }, 217 215 .target = { 0 }, ··· 226 224 .granularity = 4, 227 225 .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | 228 226 ACPI_CEDT_CFMWS_RESTRICT_VOLATILE, 229 - .qtg_id = 1, 227 + .qtg_id = FAKE_QTG_ID, 230 228 .window_size = SZ_256M * 8UL, 231 229 }, 232 230 .target = { 0, 1, }, ··· 241 239 .granularity = 4, 242 240 .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | 243 241 ACPI_CEDT_CFMWS_RESTRICT_PMEM, 244 - .qtg_id = 2, 242 + .qtg_id = FAKE_QTG_ID, 245 243 .window_size = SZ_256M * 4UL, 246 244 }, 247 245 .target = { 0 }, ··· 256 254 .granularity = 4, 257 255 .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | 258 256 ACPI_CEDT_CFMWS_RESTRICT_PMEM, 259 - .qtg_id = 3, 257 + .qtg_id = FAKE_QTG_ID, 260 258 .window_size = SZ_256M * 8UL, 261 259 }, 262 260 .target = { 0, 1, }, ··· 271 269 .granularity = 4, 272 270 .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | 273 271 ACPI_CEDT_CFMWS_RESTRICT_PMEM, 274 - .qtg_id = 4, 272 + .qtg_id = FAKE_QTG_ID, 275 273 .window_size = SZ_256M * 4UL, 276 274 }, 277 275 .target = { 2 }, ··· 286 284 .granularity = 4, 287 285 .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | 288 286 ACPI_CEDT_CFMWS_RESTRICT_VOLATILE, 289 - .qtg_id = 5, 287 + .qtg_id = FAKE_QTG_ID, 290 288 .window_size = SZ_256M, 291 289 }, 292 290 .target = { 3 }, ··· 303 301 .granularity = 4, 304 302 .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | 305 303 ACPI_CEDT_CFMWS_RESTRICT_PMEM, 306 - .qtg_id = 0, 304 + .qtg_id = FAKE_QTG_ID, 307 305 .window_size = SZ_256M * 8UL, 308 306 }, 309 307 .target = { 0, }, ··· 319 317 .granularity = 0, 320 318 .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | 321 319 ACPI_CEDT_CFMWS_RESTRICT_PMEM, 322 - .qtg_id = 1, 320 + .qtg_id = FAKE_QTG_ID, 323 321 .window_size = SZ_256M * 8UL, 324 322 }, 325 323 .target = { 0, 1, }, ··· 335 333 .granularity = 0, 336 334 .restrictions = ACPI_CEDT_CFMWS_RESTRICT_TYPE3 | 337 335 ACPI_CEDT_CFMWS_RESTRICT_PMEM, 338 - .qtg_id = 0, 336 + .qtg_id = FAKE_QTG_ID, 339 337 .window_size = SZ_256M * 16UL, 340 338 }, 341 339 .target = { 0, 1, 0, 1, }, ··· 978 976 return 0; 979 977 } 980 978 979 + /* 980 + * Faking the cxl_dpa_perf for the memdev when appropriate. 981 + */ 982 + static void dpa_perf_setup(struct cxl_port *endpoint, struct range *range, 983 + struct cxl_dpa_perf *dpa_perf) 984 + { 985 + dpa_perf->qos_class = FAKE_QTG_ID; 986 + dpa_perf->dpa_range = *range; 987 + dpa_perf->coord.read_latency = 500; 988 + dpa_perf->coord.write_latency = 500; 989 + dpa_perf->coord.read_bandwidth = 1000; 990 + dpa_perf->coord.write_bandwidth = 1000; 991 + } 992 + 993 + static void mock_cxl_endpoint_parse_cdat(struct cxl_port *port) 994 + { 995 + struct cxl_root *cxl_root __free(put_cxl_root) = 996 + find_cxl_root(port); 997 + struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev); 998 + struct cxl_dev_state *cxlds = cxlmd->cxlds; 999 + struct cxl_memdev_state *mds = to_cxl_memdev_state(cxlds); 1000 + struct range pmem_range = { 1001 + .start = cxlds->pmem_res.start, 1002 + .end = cxlds->pmem_res.end, 1003 + }; 1004 + struct range ram_range = { 1005 + .start = cxlds->ram_res.start, 1006 + .end = cxlds->ram_res.end, 1007 + }; 1008 + 1009 + if (!cxl_root) 1010 + return; 1011 + 1012 + if (range_len(&ram_range)) 1013 + dpa_perf_setup(port, &ram_range, &mds->ram_perf); 1014 + 1015 + if (range_len(&pmem_range)) 1016 + dpa_perf_setup(port, &pmem_range, &mds->pmem_perf); 1017 + 1018 + cxl_memdev_update_perf(cxlmd); 1019 + } 1020 + 981 1021 static struct cxl_mock_ops cxl_mock_ops = { 982 1022 .is_mock_adev = is_mock_adev, 983 1023 .is_mock_bridge = is_mock_bridge, ··· 1033 989 .devm_cxl_setup_hdm = mock_cxl_setup_hdm, 1034 990 .devm_cxl_add_passthrough_decoder = mock_cxl_add_passthrough_decoder, 1035 991 .devm_cxl_enumerate_decoders = mock_cxl_enumerate_decoders, 992 + .cxl_endpoint_parse_cdat = mock_cxl_endpoint_parse_cdat, 1036 993 .list = LIST_HEAD_INIT(cxl_mock_ops.list), 1037 994 }; 1038 995
+14
tools/testing/cxl/test/mock.c
··· 285 285 } 286 286 EXPORT_SYMBOL_NS_GPL(__wrap_cxl_rcd_component_reg_phys, CXL); 287 287 288 + void __wrap_cxl_endpoint_parse_cdat(struct cxl_port *port) 289 + { 290 + int index; 291 + struct cxl_mock_ops *ops = get_cxl_mock_ops(&index); 292 + struct cxl_memdev *cxlmd = to_cxl_memdev(port->uport_dev); 293 + 294 + if (ops && ops->is_mock_dev(cxlmd->dev.parent)) 295 + ops->cxl_endpoint_parse_cdat(port); 296 + else 297 + cxl_endpoint_parse_cdat(port); 298 + put_cxl_mock_ops(index); 299 + } 300 + EXPORT_SYMBOL_NS_GPL(__wrap_cxl_endpoint_parse_cdat, CXL); 301 + 288 302 MODULE_LICENSE("GPL v2"); 289 303 MODULE_IMPORT_NS(ACPI); 290 304 MODULE_IMPORT_NS(CXL);
+1
tools/testing/cxl/test/mock.h
··· 25 25 int (*devm_cxl_add_passthrough_decoder)(struct cxl_port *port); 26 26 int (*devm_cxl_enumerate_decoders)( 27 27 struct cxl_hdm *hdm, struct cxl_endpoint_dvsec_info *info); 28 + void (*cxl_endpoint_parse_cdat)(struct cxl_port *port); 28 29 }; 29 30 30 31 void register_cxl_mock_ops(struct cxl_mock_ops *ops);