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.

arm_mpam: Register and enable IRQs

Register and enable error IRQs. All the MPAM error interrupts indicate a
software bug, e.g. out of range partid. If the error interrupt is ever
signalled, attempt to disable MPAM.

Only the irq handler accesses the MPAMF_ESR register, so no locking is
needed. The work to disable MPAM after an error needs to happen at process
context as it takes mutex. It also unregisters the interrupts, meaning
it can't be done from the threaded part of a threaded interrupt.
Instead, mpam_disable() gets scheduled.

Enabling the IRQs in the MSC may involve cross calling to a CPU that
can access the MSC.

Once the IRQ is requested, the mpam_disable() path can be called
asynchronously, which will walk structures sized by max_partid. Ensure
this size is fixed before the interrupt is requested.

CC: Rohit Mathew <rohit.mathew@arm.com>
Reviewed-by: Jonathan Cameron <jonathan.cameron@huawei.com>
Reviewed-by: Gavin Shan <gshan@redhat.com>
Reviewed-by: Shaopeng Tan <tan.shaopeng@jp.fujitsu.com>
Reviewed-by: Fenghua Yu <fenghuay@nvidia.com>
Tested-by: Rohit Mathew <rohit.mathew@arm.com>
Tested-by: Fenghua Yu <fenghuay@nvidia.com>
Tested-by: Shaopeng Tan <tan.shaopeng@jp.fujitsu.com>
Tested-by: Peter Newman <peternewman@google.com>
Tested-by: Carl Worth <carl@os.amperecomputing.com>
Tested-by: Gavin Shan <gshan@redhat.com>
Tested-by: Zeng Heng <zengheng4@huawei.com>
Tested-by: Hanjun Guo <guohanjun@huawei.com>
Signed-off-by: James Morse <james.morse@arm.com>
Signed-off-by: Ben Horgan <ben.horgan@arm.com>
Signed-off-by: Catalin Marinas <catalin.marinas@arm.com>

authored by

James Morse and committed by
Catalin Marinas
49aa621c 3bd04fe7

+293
+280
drivers/resctrl/mpam_devices.c
··· 14 14 #include <linux/device.h> 15 15 #include <linux/errno.h> 16 16 #include <linux/gfp.h> 17 + #include <linux/interrupt.h> 18 + #include <linux/irq.h> 19 + #include <linux/irqdesc.h> 17 20 #include <linux/list.h> 18 21 #include <linux/lockdep.h> 19 22 #include <linux/mutex.h> ··· 201 198 idr_high = mpam_read_partsel_reg(msc, IDR + 4); 202 199 203 200 return (idr_high << 32) | idr_low; 201 + } 202 + 203 + static void mpam_msc_clear_esr(struct mpam_msc *msc) 204 + { 205 + u64 esr_low = __mpam_read_reg(msc, MPAMF_ESR); 206 + 207 + if (!esr_low) 208 + return; 209 + 210 + /* 211 + * Clearing the high/low bits of MPAMF_ESR can not be atomic. 212 + * Clear the top half first, so that the pending error bits in the 213 + * lower half prevent hardware from updating either half of the 214 + * register. 215 + */ 216 + if (msc->has_extd_esr) 217 + __mpam_write_reg(msc, MPAMF_ESR + 4, 0); 218 + __mpam_write_reg(msc, MPAMF_ESR, 0); 219 + } 220 + 221 + static u64 mpam_msc_read_esr(struct mpam_msc *msc) 222 + { 223 + u64 esr_high = 0, esr_low; 224 + 225 + esr_low = __mpam_read_reg(msc, MPAMF_ESR); 226 + if (msc->has_extd_esr) 227 + esr_high = __mpam_read_reg(msc, MPAMF_ESR + 4); 228 + 229 + return (esr_high << 32) | esr_low; 204 230 } 205 231 206 232 static void __mpam_part_sel_raw(u32 partsel, struct mpam_msc *msc) ··· 761 729 pmg_max = FIELD_GET(MPAMF_IDR_PMG_MAX, idr); 762 730 msc->partid_max = min(msc->partid_max, partid_max); 763 731 msc->pmg_max = min(msc->pmg_max, pmg_max); 732 + msc->has_extd_esr = FIELD_GET(MPAMF_IDR_HAS_EXTD_ESR, idr); 764 733 765 734 mutex_lock(&mpam_list_lock); 766 735 ris = mpam_get_or_create_ris(msc, ris_idx); ··· 775 742 mpam_ris_hw_probe(ris); 776 743 mutex_unlock(&msc->part_sel_lock); 777 744 } 745 + 746 + /* Clear any stale errors */ 747 + mpam_msc_clear_esr(msc); 778 748 779 749 spin_lock(&partid_max_lock); 780 750 mpam_partid_max = min(mpam_partid_max, msc->partid_max); ··· 902 866 } 903 867 } 904 868 869 + static void _enable_percpu_irq(void *_irq) 870 + { 871 + int *irq = _irq; 872 + 873 + enable_percpu_irq(*irq, IRQ_TYPE_NONE); 874 + } 875 + 905 876 static int mpam_cpu_online(unsigned int cpu) 906 877 { 907 878 struct mpam_msc *msc; ··· 918 875 srcu_read_lock_held(&mpam_srcu)) { 919 876 if (!cpumask_test_cpu(cpu, &msc->accessibility)) 920 877 continue; 878 + 879 + if (msc->reenable_error_ppi) 880 + _enable_percpu_irq(&msc->reenable_error_ppi); 921 881 922 882 if (atomic_fetch_inc(&msc->online_refs) == 0) 923 883 mpam_reset_msc(msc, true); ··· 972 926 if (!cpumask_test_cpu(cpu, &msc->accessibility)) 973 927 continue; 974 928 929 + if (msc->reenable_error_ppi) 930 + disable_percpu_irq(msc->reenable_error_ppi); 931 + 975 932 if (atomic_dec_and_test(&msc->online_refs)) 976 933 mpam_reset_msc(msc, false); 977 934 } ··· 999 950 mpam_cpuhp_state = 0; 1000 951 } 1001 952 mutex_unlock(&mpam_cpuhp_state_lock); 953 + } 954 + 955 + static int __setup_ppi(struct mpam_msc *msc) 956 + { 957 + int cpu; 958 + 959 + msc->error_dev_id = alloc_percpu(struct mpam_msc *); 960 + if (!msc->error_dev_id) 961 + return -ENOMEM; 962 + 963 + for_each_cpu(cpu, &msc->accessibility) 964 + *per_cpu_ptr(msc->error_dev_id, cpu) = msc; 965 + 966 + return 0; 967 + } 968 + 969 + static int mpam_msc_setup_error_irq(struct mpam_msc *msc) 970 + { 971 + int irq; 972 + 973 + irq = platform_get_irq_byname_optional(msc->pdev, "error"); 974 + if (irq <= 0) 975 + return 0; 976 + 977 + /* Allocate and initialise the percpu device pointer for PPI */ 978 + if (irq_is_percpu(irq)) 979 + return __setup_ppi(msc); 980 + 981 + /* sanity check: shared interrupts can be routed anywhere? */ 982 + if (!cpumask_equal(&msc->accessibility, cpu_possible_mask)) { 983 + pr_err_once("msc:%u is a private resource with a shared error interrupt", 984 + msc->id); 985 + return -EINVAL; 986 + } 987 + 988 + return 0; 1002 989 } 1003 990 1004 991 /* ··· 1113 1028 if (err) 1114 1029 return ERR_PTR(err); 1115 1030 1031 + err = devm_mutex_init(dev, &msc->error_irq_lock); 1032 + if (err) 1033 + return ERR_PTR(err); 1116 1034 mpam_mon_sel_lock_init(msc); 1117 1035 msc->id = pdev->id; 1118 1036 msc->pdev = pdev; ··· 1127 1039 dev_err_once(dev, "MSC is not accessible from any CPU!"); 1128 1040 return ERR_PTR(-EINVAL); 1129 1041 } 1042 + 1043 + err = mpam_msc_setup_error_irq(msc); 1044 + if (err) 1045 + return ERR_PTR(err); 1130 1046 1131 1047 if (device_property_read_u32(&pdev->dev, "pcc-channel", &tmp)) 1132 1048 msc->iface = MPAM_IFACE_MMIO; ··· 1405 1313 } 1406 1314 } 1407 1315 1316 + static char *mpam_errcode_names[16] = { 1317 + [MPAM_ERRCODE_NONE] = "No error", 1318 + [MPAM_ERRCODE_PARTID_SEL_RANGE] = "PARTID_SEL_Range", 1319 + [MPAM_ERRCODE_REQ_PARTID_RANGE] = "Req_PARTID_Range", 1320 + [MPAM_ERRCODE_MSMONCFG_ID_RANGE] = "MSMONCFG_ID_RANGE", 1321 + [MPAM_ERRCODE_REQ_PMG_RANGE] = "Req_PMG_Range", 1322 + [MPAM_ERRCODE_MONITOR_RANGE] = "Monitor_Range", 1323 + [MPAM_ERRCODE_INTPARTID_RANGE] = "intPARTID_Range", 1324 + [MPAM_ERRCODE_UNEXPECTED_INTERNAL] = "Unexpected_INTERNAL", 1325 + [MPAM_ERRCODE_UNDEFINED_RIS_PART_SEL] = "Undefined_RIS_PART_SEL", 1326 + [MPAM_ERRCODE_RIS_NO_CONTROL] = "RIS_No_Control", 1327 + [MPAM_ERRCODE_UNDEFINED_RIS_MON_SEL] = "Undefined_RIS_MON_SEL", 1328 + [MPAM_ERRCODE_RIS_NO_MONITOR] = "RIS_No_Monitor", 1329 + [12 ... 15] = "Reserved" 1330 + }; 1331 + 1332 + static int mpam_enable_msc_ecr(void *_msc) 1333 + { 1334 + struct mpam_msc *msc = _msc; 1335 + 1336 + __mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN); 1337 + 1338 + return 0; 1339 + } 1340 + 1341 + /* This can run in mpam_disable(), and the interrupt handler on the same CPU */ 1342 + static int mpam_disable_msc_ecr(void *_msc) 1343 + { 1344 + struct mpam_msc *msc = _msc; 1345 + 1346 + __mpam_write_reg(msc, MPAMF_ECR, 0); 1347 + 1348 + return 0; 1349 + } 1350 + 1351 + static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc) 1352 + { 1353 + u64 reg; 1354 + u16 partid; 1355 + u8 errcode, pmg, ris; 1356 + 1357 + if (WARN_ON_ONCE(!msc) || 1358 + WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(), 1359 + &msc->accessibility))) 1360 + return IRQ_NONE; 1361 + 1362 + reg = mpam_msc_read_esr(msc); 1363 + 1364 + errcode = FIELD_GET(MPAMF_ESR_ERRCODE, reg); 1365 + if (!errcode) 1366 + return IRQ_NONE; 1367 + 1368 + /* Clear level triggered irq */ 1369 + mpam_msc_clear_esr(msc); 1370 + 1371 + partid = FIELD_GET(MPAMF_ESR_PARTID_MON, reg); 1372 + pmg = FIELD_GET(MPAMF_ESR_PMG, reg); 1373 + ris = FIELD_GET(MPAMF_ESR_RIS, reg); 1374 + 1375 + pr_err_ratelimited("error irq from msc:%u '%s', partid:%u, pmg: %u, ris: %u\n", 1376 + msc->id, mpam_errcode_names[errcode], partid, pmg, 1377 + ris); 1378 + 1379 + /* Disable this interrupt. */ 1380 + mpam_disable_msc_ecr(msc); 1381 + 1382 + /* 1383 + * Schedule the teardown work. Don't use a threaded IRQ as we can't 1384 + * unregister the interrupt from the threaded part of the handler. 1385 + */ 1386 + mpam_disable_reason = "hardware error interrupt"; 1387 + schedule_work(&mpam_broken_work); 1388 + 1389 + return IRQ_HANDLED; 1390 + } 1391 + 1392 + static irqreturn_t mpam_ppi_handler(int irq, void *dev_id) 1393 + { 1394 + struct mpam_msc *msc = *(struct mpam_msc **)dev_id; 1395 + 1396 + return __mpam_irq_handler(irq, msc); 1397 + } 1398 + 1399 + static irqreturn_t mpam_spi_handler(int irq, void *dev_id) 1400 + { 1401 + struct mpam_msc *msc = dev_id; 1402 + 1403 + return __mpam_irq_handler(irq, msc); 1404 + } 1405 + 1406 + static int mpam_register_irqs(void) 1407 + { 1408 + int err, irq; 1409 + struct mpam_msc *msc; 1410 + 1411 + lockdep_assert_cpus_held(); 1412 + 1413 + guard(srcu)(&mpam_srcu); 1414 + list_for_each_entry_srcu(msc, &mpam_all_msc, all_msc_list, 1415 + srcu_read_lock_held(&mpam_srcu)) { 1416 + irq = platform_get_irq_byname_optional(msc->pdev, "error"); 1417 + if (irq <= 0) 1418 + continue; 1419 + 1420 + /* The MPAM spec says the interrupt can be SPI, PPI or LPI */ 1421 + /* We anticipate sharing the interrupt with other MSCs */ 1422 + if (irq_is_percpu(irq)) { 1423 + err = request_percpu_irq(irq, &mpam_ppi_handler, 1424 + "mpam:msc:error", 1425 + msc->error_dev_id); 1426 + if (err) 1427 + return err; 1428 + 1429 + msc->reenable_error_ppi = irq; 1430 + smp_call_function_many(&msc->accessibility, 1431 + &_enable_percpu_irq, &irq, 1432 + true); 1433 + } else { 1434 + err = devm_request_irq(&msc->pdev->dev, irq, 1435 + &mpam_spi_handler, IRQF_SHARED, 1436 + "mpam:msc:error", msc); 1437 + if (err) 1438 + return err; 1439 + } 1440 + 1441 + mutex_lock(&msc->error_irq_lock); 1442 + msc->error_irq_req = true; 1443 + mpam_touch_msc(msc, mpam_enable_msc_ecr, msc); 1444 + msc->error_irq_hw_enabled = true; 1445 + mutex_unlock(&msc->error_irq_lock); 1446 + } 1447 + 1448 + return 0; 1449 + } 1450 + 1451 + static void mpam_unregister_irqs(void) 1452 + { 1453 + int irq; 1454 + struct mpam_msc *msc; 1455 + 1456 + guard(cpus_read_lock)(); 1457 + guard(srcu)(&mpam_srcu); 1458 + list_for_each_entry_srcu(msc, &mpam_all_msc, all_msc_list, 1459 + srcu_read_lock_held(&mpam_srcu)) { 1460 + irq = platform_get_irq_byname_optional(msc->pdev, "error"); 1461 + if (irq <= 0) 1462 + continue; 1463 + 1464 + mutex_lock(&msc->error_irq_lock); 1465 + if (msc->error_irq_hw_enabled) { 1466 + mpam_touch_msc(msc, mpam_disable_msc_ecr, msc); 1467 + msc->error_irq_hw_enabled = false; 1468 + } 1469 + 1470 + if (msc->error_irq_req) { 1471 + if (irq_is_percpu(irq)) { 1472 + msc->reenable_error_ppi = 0; 1473 + free_percpu_irq(irq, msc->error_dev_id); 1474 + } else { 1475 + devm_free_irq(&msc->pdev->dev, irq, msc); 1476 + } 1477 + msc->error_irq_req = false; 1478 + } 1479 + mutex_unlock(&msc->error_irq_lock); 1480 + } 1481 + } 1482 + 1408 1483 static void mpam_enable_once(void) 1409 1484 { 1485 + int err; 1486 + 1410 1487 /* 1411 1488 * Once the cpuhp callbacks have been changed, mpam_partid_max can no 1412 1489 * longer change. ··· 1584 1323 partid_max_published = true; 1585 1324 spin_unlock(&partid_max_lock); 1586 1325 1326 + /* 1327 + * If all the MSC have been probed, enabling the IRQs happens next. 1328 + * That involves cross-calling to a CPU that can reach the MSC, and 1329 + * the locks must be taken in this order: 1330 + */ 1331 + cpus_read_lock(); 1587 1332 mutex_lock(&mpam_list_lock); 1588 1333 mpam_enable_merge_features(&mpam_classes); 1334 + 1335 + err = mpam_register_irqs(); 1336 + 1589 1337 mutex_unlock(&mpam_list_lock); 1338 + cpus_read_unlock(); 1339 + 1340 + if (err) { 1341 + pr_warn("Failed to register irqs: %d\n", err); 1342 + mpam_disable_reason = "Failed to enable."; 1343 + schedule_work(&mpam_broken_work); 1344 + return; 1345 + } 1590 1346 1591 1347 mpam_register_cpuhp_callbacks(mpam_cpu_online, mpam_cpu_offline, 1592 1348 "mpam:online"); ··· 1670 1392 mpam_cpuhp_state = 0; 1671 1393 } 1672 1394 mutex_unlock(&mpam_cpuhp_state_lock); 1395 + 1396 + mpam_unregister_irqs(); 1673 1397 1674 1398 idx = srcu_read_lock(&mpam_srcu); 1675 1399 list_for_each_entry_srcu(class, &mpam_classes, classes_list,
+13
drivers/resctrl/mpam_internal.h
··· 46 46 enum mpam_msc_iface iface; 47 47 u32 nrdy_usec; 48 48 cpumask_t accessibility; 49 + bool has_extd_esr; 50 + 51 + int reenable_error_ppi; 52 + struct mpam_msc * __percpu *error_dev_id; 53 + 49 54 atomic_t online_refs; 50 55 51 56 /* ··· 63 58 u8 pmg_max; 64 59 unsigned long ris_idxs; 65 60 u32 ris_max; 61 + 62 + /* 63 + * error_irq_lock is taken when registering/unregistering the error 64 + * interrupt and maniupulating the below flags. 65 + */ 66 + struct mutex error_irq_lock; 67 + bool error_irq_req; 68 + bool error_irq_hw_enabled; 66 69 67 70 /* mpam_msc_ris of this component */ 68 71 struct list_head ris;