Skip to content

Commit 49aa621

Browse files
James Morsectmarinas
authored andcommitted
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>
1 parent 3bd04fe commit 49aa621

2 files changed

Lines changed: 293 additions & 0 deletions

File tree

drivers/resctrl/mpam_devices.c

Lines changed: 280 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#include <linux/device.h>
1515
#include <linux/errno.h>
1616
#include <linux/gfp.h>
17+
#include <linux/interrupt.h>
18+
#include <linux/irq.h>
19+
#include <linux/irqdesc.h>
1720
#include <linux/list.h>
1821
#include <linux/lockdep.h>
1922
#include <linux/mutex.h>
@@ -200,6 +203,35 @@ static u64 mpam_msc_read_idr(struct mpam_msc *msc)
200203
return (idr_high << 32) | idr_low;
201204
}
202205

206+
static void mpam_msc_clear_esr(struct mpam_msc *msc)
207+
{
208+
u64 esr_low = __mpam_read_reg(msc, MPAMF_ESR);
209+
210+
if (!esr_low)
211+
return;
212+
213+
/*
214+
* Clearing the high/low bits of MPAMF_ESR can not be atomic.
215+
* Clear the top half first, so that the pending error bits in the
216+
* lower half prevent hardware from updating either half of the
217+
* register.
218+
*/
219+
if (msc->has_extd_esr)
220+
__mpam_write_reg(msc, MPAMF_ESR + 4, 0);
221+
__mpam_write_reg(msc, MPAMF_ESR, 0);
222+
}
223+
224+
static u64 mpam_msc_read_esr(struct mpam_msc *msc)
225+
{
226+
u64 esr_high = 0, esr_low;
227+
228+
esr_low = __mpam_read_reg(msc, MPAMF_ESR);
229+
if (msc->has_extd_esr)
230+
esr_high = __mpam_read_reg(msc, MPAMF_ESR + 4);
231+
232+
return (esr_high << 32) | esr_low;
233+
}
234+
203235
static void __mpam_part_sel_raw(u32 partsel, struct mpam_msc *msc)
204236
{
205237
lockdep_assert_held(&msc->part_sel_lock);
@@ -729,6 +761,7 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc)
729761
pmg_max = FIELD_GET(MPAMF_IDR_PMG_MAX, idr);
730762
msc->partid_max = min(msc->partid_max, partid_max);
731763
msc->pmg_max = min(msc->pmg_max, pmg_max);
764+
msc->has_extd_esr = FIELD_GET(MPAMF_IDR_HAS_EXTD_ESR, idr);
732765

733766
mutex_lock(&mpam_list_lock);
734767
ris = mpam_get_or_create_ris(msc, ris_idx);
@@ -743,6 +776,9 @@ static int mpam_msc_hw_probe(struct mpam_msc *msc)
743776
mutex_unlock(&msc->part_sel_lock);
744777
}
745778

779+
/* Clear any stale errors */
780+
mpam_msc_clear_esr(msc);
781+
746782
spin_lock(&partid_max_lock);
747783
mpam_partid_max = min(mpam_partid_max, msc->partid_max);
748784
mpam_pmg_max = min(mpam_pmg_max, msc->pmg_max);
@@ -866,6 +902,13 @@ static void mpam_reset_msc(struct mpam_msc *msc, bool online)
866902
}
867903
}
868904

905+
static void _enable_percpu_irq(void *_irq)
906+
{
907+
int *irq = _irq;
908+
909+
enable_percpu_irq(*irq, IRQ_TYPE_NONE);
910+
}
911+
869912
static int mpam_cpu_online(unsigned int cpu)
870913
{
871914
struct mpam_msc *msc;
@@ -876,6 +919,9 @@ static int mpam_cpu_online(unsigned int cpu)
876919
if (!cpumask_test_cpu(cpu, &msc->accessibility))
877920
continue;
878921

922+
if (msc->reenable_error_ppi)
923+
_enable_percpu_irq(&msc->reenable_error_ppi);
924+
879925
if (atomic_fetch_inc(&msc->online_refs) == 0)
880926
mpam_reset_msc(msc, true);
881927
}
@@ -926,6 +972,9 @@ static int mpam_cpu_offline(unsigned int cpu)
926972
if (!cpumask_test_cpu(cpu, &msc->accessibility))
927973
continue;
928974

975+
if (msc->reenable_error_ppi)
976+
disable_percpu_irq(msc->reenable_error_ppi);
977+
929978
if (atomic_dec_and_test(&msc->online_refs))
930979
mpam_reset_msc(msc, false);
931980
}
@@ -952,6 +1001,42 @@ static void mpam_register_cpuhp_callbacks(int (*online)(unsigned int online),
9521001
mutex_unlock(&mpam_cpuhp_state_lock);
9531002
}
9541003

1004+
static int __setup_ppi(struct mpam_msc *msc)
1005+
{
1006+
int cpu;
1007+
1008+
msc->error_dev_id = alloc_percpu(struct mpam_msc *);
1009+
if (!msc->error_dev_id)
1010+
return -ENOMEM;
1011+
1012+
for_each_cpu(cpu, &msc->accessibility)
1013+
*per_cpu_ptr(msc->error_dev_id, cpu) = msc;
1014+
1015+
return 0;
1016+
}
1017+
1018+
static int mpam_msc_setup_error_irq(struct mpam_msc *msc)
1019+
{
1020+
int irq;
1021+
1022+
irq = platform_get_irq_byname_optional(msc->pdev, "error");
1023+
if (irq <= 0)
1024+
return 0;
1025+
1026+
/* Allocate and initialise the percpu device pointer for PPI */
1027+
if (irq_is_percpu(irq))
1028+
return __setup_ppi(msc);
1029+
1030+
/* sanity check: shared interrupts can be routed anywhere? */
1031+
if (!cpumask_equal(&msc->accessibility, cpu_possible_mask)) {
1032+
pr_err_once("msc:%u is a private resource with a shared error interrupt",
1033+
msc->id);
1034+
return -EINVAL;
1035+
}
1036+
1037+
return 0;
1038+
}
1039+
9551040
/*
9561041
* An MSC can control traffic from a set of CPUs, but may only be accessible
9571042
* from a (hopefully wider) set of CPUs. The common reason for this is power
@@ -1028,6 +1113,9 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev)
10281113
if (err)
10291114
return ERR_PTR(err);
10301115

1116+
err = devm_mutex_init(dev, &msc->error_irq_lock);
1117+
if (err)
1118+
return ERR_PTR(err);
10311119
mpam_mon_sel_lock_init(msc);
10321120
msc->id = pdev->id;
10331121
msc->pdev = pdev;
@@ -1040,6 +1128,10 @@ static struct mpam_msc *do_mpam_msc_drv_probe(struct platform_device *pdev)
10401128
return ERR_PTR(-EINVAL);
10411129
}
10421130

1131+
err = mpam_msc_setup_error_irq(msc);
1132+
if (err)
1133+
return ERR_PTR(err);
1134+
10431135
if (device_property_read_u32(&pdev->dev, "pcc-channel", &tmp))
10441136
msc->iface = MPAM_IFACE_MMIO;
10451137
else
@@ -1313,8 +1405,177 @@ static void mpam_enable_merge_features(struct list_head *all_classes_list)
13131405
}
13141406
}
13151407

1408+
static char *mpam_errcode_names[16] = {
1409+
[MPAM_ERRCODE_NONE] = "No error",
1410+
[MPAM_ERRCODE_PARTID_SEL_RANGE] = "PARTID_SEL_Range",
1411+
[MPAM_ERRCODE_REQ_PARTID_RANGE] = "Req_PARTID_Range",
1412+
[MPAM_ERRCODE_MSMONCFG_ID_RANGE] = "MSMONCFG_ID_RANGE",
1413+
[MPAM_ERRCODE_REQ_PMG_RANGE] = "Req_PMG_Range",
1414+
[MPAM_ERRCODE_MONITOR_RANGE] = "Monitor_Range",
1415+
[MPAM_ERRCODE_INTPARTID_RANGE] = "intPARTID_Range",
1416+
[MPAM_ERRCODE_UNEXPECTED_INTERNAL] = "Unexpected_INTERNAL",
1417+
[MPAM_ERRCODE_UNDEFINED_RIS_PART_SEL] = "Undefined_RIS_PART_SEL",
1418+
[MPAM_ERRCODE_RIS_NO_CONTROL] = "RIS_No_Control",
1419+
[MPAM_ERRCODE_UNDEFINED_RIS_MON_SEL] = "Undefined_RIS_MON_SEL",
1420+
[MPAM_ERRCODE_RIS_NO_MONITOR] = "RIS_No_Monitor",
1421+
[12 ... 15] = "Reserved"
1422+
};
1423+
1424+
static int mpam_enable_msc_ecr(void *_msc)
1425+
{
1426+
struct mpam_msc *msc = _msc;
1427+
1428+
__mpam_write_reg(msc, MPAMF_ECR, MPAMF_ECR_INTEN);
1429+
1430+
return 0;
1431+
}
1432+
1433+
/* This can run in mpam_disable(), and the interrupt handler on the same CPU */
1434+
static int mpam_disable_msc_ecr(void *_msc)
1435+
{
1436+
struct mpam_msc *msc = _msc;
1437+
1438+
__mpam_write_reg(msc, MPAMF_ECR, 0);
1439+
1440+
return 0;
1441+
}
1442+
1443+
static irqreturn_t __mpam_irq_handler(int irq, struct mpam_msc *msc)
1444+
{
1445+
u64 reg;
1446+
u16 partid;
1447+
u8 errcode, pmg, ris;
1448+
1449+
if (WARN_ON_ONCE(!msc) ||
1450+
WARN_ON_ONCE(!cpumask_test_cpu(smp_processor_id(),
1451+
&msc->accessibility)))
1452+
return IRQ_NONE;
1453+
1454+
reg = mpam_msc_read_esr(msc);
1455+
1456+
errcode = FIELD_GET(MPAMF_ESR_ERRCODE, reg);
1457+
if (!errcode)
1458+
return IRQ_NONE;
1459+
1460+
/* Clear level triggered irq */
1461+
mpam_msc_clear_esr(msc);
1462+
1463+
partid = FIELD_GET(MPAMF_ESR_PARTID_MON, reg);
1464+
pmg = FIELD_GET(MPAMF_ESR_PMG, reg);
1465+
ris = FIELD_GET(MPAMF_ESR_RIS, reg);
1466+
1467+
pr_err_ratelimited("error irq from msc:%u '%s', partid:%u, pmg: %u, ris: %u\n",
1468+
msc->id, mpam_errcode_names[errcode], partid, pmg,
1469+
ris);
1470+
1471+
/* Disable this interrupt. */
1472+
mpam_disable_msc_ecr(msc);
1473+
1474+
/*
1475+
* Schedule the teardown work. Don't use a threaded IRQ as we can't
1476+
* unregister the interrupt from the threaded part of the handler.
1477+
*/
1478+
mpam_disable_reason = "hardware error interrupt";
1479+
schedule_work(&mpam_broken_work);
1480+
1481+
return IRQ_HANDLED;
1482+
}
1483+
1484+
static irqreturn_t mpam_ppi_handler(int irq, void *dev_id)
1485+
{
1486+
struct mpam_msc *msc = *(struct mpam_msc **)dev_id;
1487+
1488+
return __mpam_irq_handler(irq, msc);
1489+
}
1490+
1491+
static irqreturn_t mpam_spi_handler(int irq, void *dev_id)
1492+
{
1493+
struct mpam_msc *msc = dev_id;
1494+
1495+
return __mpam_irq_handler(irq, msc);
1496+
}
1497+
1498+
static int mpam_register_irqs(void)
1499+
{
1500+
int err, irq;
1501+
struct mpam_msc *msc;
1502+
1503+
lockdep_assert_cpus_held();
1504+
1505+
guard(srcu)(&mpam_srcu);
1506+
list_for_each_entry_srcu(msc, &mpam_all_msc, all_msc_list,
1507+
srcu_read_lock_held(&mpam_srcu)) {
1508+
irq = platform_get_irq_byname_optional(msc->pdev, "error");
1509+
if (irq <= 0)
1510+
continue;
1511+
1512+
/* The MPAM spec says the interrupt can be SPI, PPI or LPI */
1513+
/* We anticipate sharing the interrupt with other MSCs */
1514+
if (irq_is_percpu(irq)) {
1515+
err = request_percpu_irq(irq, &mpam_ppi_handler,
1516+
"mpam:msc:error",
1517+
msc->error_dev_id);
1518+
if (err)
1519+
return err;
1520+
1521+
msc->reenable_error_ppi = irq;
1522+
smp_call_function_many(&msc->accessibility,
1523+
&_enable_percpu_irq, &irq,
1524+
true);
1525+
} else {
1526+
err = devm_request_irq(&msc->pdev->dev, irq,
1527+
&mpam_spi_handler, IRQF_SHARED,
1528+
"mpam:msc:error", msc);
1529+
if (err)
1530+
return err;
1531+
}
1532+
1533+
mutex_lock(&msc->error_irq_lock);
1534+
msc->error_irq_req = true;
1535+
mpam_touch_msc(msc, mpam_enable_msc_ecr, msc);
1536+
msc->error_irq_hw_enabled = true;
1537+
mutex_unlock(&msc->error_irq_lock);
1538+
}
1539+
1540+
return 0;
1541+
}
1542+
1543+
static void mpam_unregister_irqs(void)
1544+
{
1545+
int irq;
1546+
struct mpam_msc *msc;
1547+
1548+
guard(cpus_read_lock)();
1549+
guard(srcu)(&mpam_srcu);
1550+
list_for_each_entry_srcu(msc, &mpam_all_msc, all_msc_list,
1551+
srcu_read_lock_held(&mpam_srcu)) {
1552+
irq = platform_get_irq_byname_optional(msc->pdev, "error");
1553+
if (irq <= 0)
1554+
continue;
1555+
1556+
mutex_lock(&msc->error_irq_lock);
1557+
if (msc->error_irq_hw_enabled) {
1558+
mpam_touch_msc(msc, mpam_disable_msc_ecr, msc);
1559+
msc->error_irq_hw_enabled = false;
1560+
}
1561+
1562+
if (msc->error_irq_req) {
1563+
if (irq_is_percpu(irq)) {
1564+
msc->reenable_error_ppi = 0;
1565+
free_percpu_irq(irq, msc->error_dev_id);
1566+
} else {
1567+
devm_free_irq(&msc->pdev->dev, irq, msc);
1568+
}
1569+
msc->error_irq_req = false;
1570+
}
1571+
mutex_unlock(&msc->error_irq_lock);
1572+
}
1573+
}
1574+
13161575
static void mpam_enable_once(void)
13171576
{
1577+
int err;
1578+
13181579
/*
13191580
* Once the cpuhp callbacks have been changed, mpam_partid_max can no
13201581
* longer change.
@@ -1323,9 +1584,26 @@ static void mpam_enable_once(void)
13231584
partid_max_published = true;
13241585
spin_unlock(&partid_max_lock);
13251586

1587+
/*
1588+
* If all the MSC have been probed, enabling the IRQs happens next.
1589+
* That involves cross-calling to a CPU that can reach the MSC, and
1590+
* the locks must be taken in this order:
1591+
*/
1592+
cpus_read_lock();
13261593
mutex_lock(&mpam_list_lock);
13271594
mpam_enable_merge_features(&mpam_classes);
1595+
1596+
err = mpam_register_irqs();
1597+
13281598
mutex_unlock(&mpam_list_lock);
1599+
cpus_read_unlock();
1600+
1601+
if (err) {
1602+
pr_warn("Failed to register irqs: %d\n", err);
1603+
mpam_disable_reason = "Failed to enable.";
1604+
schedule_work(&mpam_broken_work);
1605+
return;
1606+
}
13291607

13301608
mpam_register_cpuhp_callbacks(mpam_cpu_online, mpam_cpu_offline,
13311609
"mpam:online");
@@ -1393,6 +1671,8 @@ void mpam_disable(struct work_struct *ignored)
13931671
}
13941672
mutex_unlock(&mpam_cpuhp_state_lock);
13951673

1674+
mpam_unregister_irqs();
1675+
13961676
idx = srcu_read_lock(&mpam_srcu);
13971677
list_for_each_entry_srcu(class, &mpam_classes, classes_list,
13981678
srcu_read_lock_held(&mpam_srcu))

0 commit comments

Comments
 (0)