cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

irq-mvebu-icu.c (11494B)


      1/*
      2 * Copyright (C) 2017 Marvell
      3 *
      4 * Hanna Hawa <hannah@marvell.com>
      5 * Thomas Petazzoni <thomas.petazzoni@free-electrons.com>
      6 *
      7 * This file is licensed under the terms of the GNU General Public
      8 * License version 2. This program is licensed "as is" without any
      9 * warranty of any kind, whether express or implied.
     10 */
     11
     12#include <linux/interrupt.h>
     13#include <linux/irq.h>
     14#include <linux/irqchip.h>
     15#include <linux/irqdomain.h>
     16#include <linux/jump_label.h>
     17#include <linux/kernel.h>
     18#include <linux/msi.h>
     19#include <linux/of_irq.h>
     20#include <linux/of_platform.h>
     21#include <linux/platform_device.h>
     22
     23#include <dt-bindings/interrupt-controller/mvebu-icu.h>
     24
     25/* ICU registers */
     26#define ICU_SETSPI_NSR_AL	0x10
     27#define ICU_SETSPI_NSR_AH	0x14
     28#define ICU_CLRSPI_NSR_AL	0x18
     29#define ICU_CLRSPI_NSR_AH	0x1c
     30#define ICU_SET_SEI_AL		0x50
     31#define ICU_SET_SEI_AH		0x54
     32#define ICU_CLR_SEI_AL		0x58
     33#define ICU_CLR_SEI_AH		0x5C
     34#define ICU_INT_CFG(x)          (0x100 + 4 * (x))
     35#define   ICU_INT_ENABLE	BIT(24)
     36#define   ICU_IS_EDGE		BIT(28)
     37#define   ICU_GROUP_SHIFT	29
     38
     39/* ICU definitions */
     40#define ICU_MAX_IRQS		207
     41#define ICU_SATA0_ICU_ID	109
     42#define ICU_SATA1_ICU_ID	107
     43
     44struct mvebu_icu_subset_data {
     45	unsigned int icu_group;
     46	unsigned int offset_set_ah;
     47	unsigned int offset_set_al;
     48	unsigned int offset_clr_ah;
     49	unsigned int offset_clr_al;
     50};
     51
     52struct mvebu_icu {
     53	void __iomem *base;
     54	struct device *dev;
     55};
     56
     57struct mvebu_icu_msi_data {
     58	struct mvebu_icu *icu;
     59	atomic_t initialized;
     60	const struct mvebu_icu_subset_data *subset_data;
     61};
     62
     63struct mvebu_icu_irq_data {
     64	struct mvebu_icu *icu;
     65	unsigned int icu_group;
     66	unsigned int type;
     67};
     68
     69static DEFINE_STATIC_KEY_FALSE(legacy_bindings);
     70
     71static void mvebu_icu_init(struct mvebu_icu *icu,
     72			   struct mvebu_icu_msi_data *msi_data,
     73			   struct msi_msg *msg)
     74{
     75	const struct mvebu_icu_subset_data *subset = msi_data->subset_data;
     76
     77	if (atomic_cmpxchg(&msi_data->initialized, false, true))
     78		return;
     79
     80	/* Set 'SET' ICU SPI message address in AP */
     81	writel_relaxed(msg[0].address_hi, icu->base + subset->offset_set_ah);
     82	writel_relaxed(msg[0].address_lo, icu->base + subset->offset_set_al);
     83
     84	if (subset->icu_group != ICU_GRP_NSR)
     85		return;
     86
     87	/* Set 'CLEAR' ICU SPI message address in AP (level-MSI only) */
     88	writel_relaxed(msg[1].address_hi, icu->base + subset->offset_clr_ah);
     89	writel_relaxed(msg[1].address_lo, icu->base + subset->offset_clr_al);
     90}
     91
     92static void mvebu_icu_write_msg(struct msi_desc *desc, struct msi_msg *msg)
     93{
     94	struct irq_data *d = irq_get_irq_data(desc->irq);
     95	struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d->domain);
     96	struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
     97	struct mvebu_icu *icu = icu_irqd->icu;
     98	unsigned int icu_int;
     99
    100	if (msg->address_lo || msg->address_hi) {
    101		/* One off initialization per domain */
    102		mvebu_icu_init(icu, msi_data, msg);
    103		/* Configure the ICU with irq number & type */
    104		icu_int = msg->data | ICU_INT_ENABLE;
    105		if (icu_irqd->type & IRQ_TYPE_EDGE_RISING)
    106			icu_int |= ICU_IS_EDGE;
    107		icu_int |= icu_irqd->icu_group << ICU_GROUP_SHIFT;
    108	} else {
    109		/* De-configure the ICU */
    110		icu_int = 0;
    111	}
    112
    113	writel_relaxed(icu_int, icu->base + ICU_INT_CFG(d->hwirq));
    114
    115	/*
    116	 * The SATA unit has 2 ports, and a dedicated ICU entry per
    117	 * port. The ahci sata driver supports only one irq interrupt
    118	 * per SATA unit. To solve this conflict, we configure the 2
    119	 * SATA wired interrupts in the south bridge into 1 GIC
    120	 * interrupt in the north bridge. Even if only a single port
    121	 * is enabled, if sata node is enabled, both interrupts are
    122	 * configured (regardless of which port is actually in use).
    123	 */
    124	if (d->hwirq == ICU_SATA0_ICU_ID || d->hwirq == ICU_SATA1_ICU_ID) {
    125		writel_relaxed(icu_int,
    126			       icu->base + ICU_INT_CFG(ICU_SATA0_ICU_ID));
    127		writel_relaxed(icu_int,
    128			       icu->base + ICU_INT_CFG(ICU_SATA1_ICU_ID));
    129	}
    130}
    131
    132static struct irq_chip mvebu_icu_nsr_chip = {
    133	.name			= "ICU-NSR",
    134	.irq_mask		= irq_chip_mask_parent,
    135	.irq_unmask		= irq_chip_unmask_parent,
    136	.irq_eoi		= irq_chip_eoi_parent,
    137	.irq_set_type		= irq_chip_set_type_parent,
    138	.irq_set_affinity	= irq_chip_set_affinity_parent,
    139};
    140
    141static struct irq_chip mvebu_icu_sei_chip = {
    142	.name			= "ICU-SEI",
    143	.irq_ack		= irq_chip_ack_parent,
    144	.irq_mask		= irq_chip_mask_parent,
    145	.irq_unmask		= irq_chip_unmask_parent,
    146	.irq_set_type		= irq_chip_set_type_parent,
    147	.irq_set_affinity	= irq_chip_set_affinity_parent,
    148};
    149
    150static int
    151mvebu_icu_irq_domain_translate(struct irq_domain *d, struct irq_fwspec *fwspec,
    152			       unsigned long *hwirq, unsigned int *type)
    153{
    154	struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(d);
    155	struct mvebu_icu *icu = platform_msi_get_host_data(d);
    156	unsigned int param_count = static_branch_unlikely(&legacy_bindings) ? 3 : 2;
    157
    158	/* Check the count of the parameters in dt */
    159	if (WARN_ON(fwspec->param_count != param_count)) {
    160		dev_err(icu->dev, "wrong ICU parameter count %d\n",
    161			fwspec->param_count);
    162		return -EINVAL;
    163	}
    164
    165	if (static_branch_unlikely(&legacy_bindings)) {
    166		*hwirq = fwspec->param[1];
    167		*type = fwspec->param[2] & IRQ_TYPE_SENSE_MASK;
    168		if (fwspec->param[0] != ICU_GRP_NSR) {
    169			dev_err(icu->dev, "wrong ICU group type %x\n",
    170				fwspec->param[0]);
    171			return -EINVAL;
    172		}
    173	} else {
    174		*hwirq = fwspec->param[0];
    175		*type = fwspec->param[1] & IRQ_TYPE_SENSE_MASK;
    176
    177		/*
    178		 * The ICU receives level interrupts. While the NSR are also
    179		 * level interrupts, SEI are edge interrupts. Force the type
    180		 * here in this case. Please note that this makes the interrupt
    181		 * handling unreliable.
    182		 */
    183		if (msi_data->subset_data->icu_group == ICU_GRP_SEI)
    184			*type = IRQ_TYPE_EDGE_RISING;
    185	}
    186
    187	if (*hwirq >= ICU_MAX_IRQS) {
    188		dev_err(icu->dev, "invalid interrupt number %ld\n", *hwirq);
    189		return -EINVAL;
    190	}
    191
    192	return 0;
    193}
    194
    195static int
    196mvebu_icu_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
    197			   unsigned int nr_irqs, void *args)
    198{
    199	int err;
    200	unsigned long hwirq;
    201	struct irq_fwspec *fwspec = args;
    202	struct mvebu_icu_msi_data *msi_data = platform_msi_get_host_data(domain);
    203	struct mvebu_icu *icu = msi_data->icu;
    204	struct mvebu_icu_irq_data *icu_irqd;
    205	struct irq_chip *chip = &mvebu_icu_nsr_chip;
    206
    207	icu_irqd = kmalloc(sizeof(*icu_irqd), GFP_KERNEL);
    208	if (!icu_irqd)
    209		return -ENOMEM;
    210
    211	err = mvebu_icu_irq_domain_translate(domain, fwspec, &hwirq,
    212					     &icu_irqd->type);
    213	if (err) {
    214		dev_err(icu->dev, "failed to translate ICU parameters\n");
    215		goto free_irqd;
    216	}
    217
    218	if (static_branch_unlikely(&legacy_bindings))
    219		icu_irqd->icu_group = fwspec->param[0];
    220	else
    221		icu_irqd->icu_group = msi_data->subset_data->icu_group;
    222	icu_irqd->icu = icu;
    223
    224	err = platform_msi_device_domain_alloc(domain, virq, nr_irqs);
    225	if (err) {
    226		dev_err(icu->dev, "failed to allocate ICU interrupt in parent domain\n");
    227		goto free_irqd;
    228	}
    229
    230	/* Make sure there is no interrupt left pending by the firmware */
    231	err = irq_set_irqchip_state(virq, IRQCHIP_STATE_PENDING, false);
    232	if (err)
    233		goto free_msi;
    234
    235	if (icu_irqd->icu_group == ICU_GRP_SEI)
    236		chip = &mvebu_icu_sei_chip;
    237
    238	err = irq_domain_set_hwirq_and_chip(domain, virq, hwirq,
    239					    chip, icu_irqd);
    240	if (err) {
    241		dev_err(icu->dev, "failed to set the data to IRQ domain\n");
    242		goto free_msi;
    243	}
    244
    245	return 0;
    246
    247free_msi:
    248	platform_msi_device_domain_free(domain, virq, nr_irqs);
    249free_irqd:
    250	kfree(icu_irqd);
    251	return err;
    252}
    253
    254static void
    255mvebu_icu_irq_domain_free(struct irq_domain *domain, unsigned int virq,
    256			  unsigned int nr_irqs)
    257{
    258	struct irq_data *d = irq_get_irq_data(virq);
    259	struct mvebu_icu_irq_data *icu_irqd = d->chip_data;
    260
    261	kfree(icu_irqd);
    262
    263	platform_msi_device_domain_free(domain, virq, nr_irqs);
    264}
    265
    266static const struct irq_domain_ops mvebu_icu_domain_ops = {
    267	.translate = mvebu_icu_irq_domain_translate,
    268	.alloc     = mvebu_icu_irq_domain_alloc,
    269	.free      = mvebu_icu_irq_domain_free,
    270};
    271
    272static const struct mvebu_icu_subset_data mvebu_icu_nsr_subset_data = {
    273	.icu_group = ICU_GRP_NSR,
    274	.offset_set_ah = ICU_SETSPI_NSR_AH,
    275	.offset_set_al = ICU_SETSPI_NSR_AL,
    276	.offset_clr_ah = ICU_CLRSPI_NSR_AH,
    277	.offset_clr_al = ICU_CLRSPI_NSR_AL,
    278};
    279
    280static const struct mvebu_icu_subset_data mvebu_icu_sei_subset_data = {
    281	.icu_group = ICU_GRP_SEI,
    282	.offset_set_ah = ICU_SET_SEI_AH,
    283	.offset_set_al = ICU_SET_SEI_AL,
    284};
    285
    286static const struct of_device_id mvebu_icu_subset_of_match[] = {
    287	{
    288		.compatible = "marvell,cp110-icu-nsr",
    289		.data = &mvebu_icu_nsr_subset_data,
    290	},
    291	{
    292		.compatible = "marvell,cp110-icu-sei",
    293		.data = &mvebu_icu_sei_subset_data,
    294	},
    295	{},
    296};
    297
    298static int mvebu_icu_subset_probe(struct platform_device *pdev)
    299{
    300	struct mvebu_icu_msi_data *msi_data;
    301	struct device_node *msi_parent_dn;
    302	struct device *dev = &pdev->dev;
    303	struct irq_domain *irq_domain;
    304
    305	msi_data = devm_kzalloc(dev, sizeof(*msi_data), GFP_KERNEL);
    306	if (!msi_data)
    307		return -ENOMEM;
    308
    309	if (static_branch_unlikely(&legacy_bindings)) {
    310		msi_data->icu = dev_get_drvdata(dev);
    311		msi_data->subset_data = &mvebu_icu_nsr_subset_data;
    312	} else {
    313		msi_data->icu = dev_get_drvdata(dev->parent);
    314		msi_data->subset_data = of_device_get_match_data(dev);
    315	}
    316
    317	dev->msi.domain = of_msi_get_domain(dev, dev->of_node,
    318					    DOMAIN_BUS_PLATFORM_MSI);
    319	if (!dev->msi.domain)
    320		return -EPROBE_DEFER;
    321
    322	msi_parent_dn = irq_domain_get_of_node(dev->msi.domain);
    323	if (!msi_parent_dn)
    324		return -ENODEV;
    325
    326	irq_domain = platform_msi_create_device_tree_domain(dev, ICU_MAX_IRQS,
    327							    mvebu_icu_write_msg,
    328							    &mvebu_icu_domain_ops,
    329							    msi_data);
    330	if (!irq_domain) {
    331		dev_err(dev, "Failed to create ICU MSI domain\n");
    332		return -ENOMEM;
    333	}
    334
    335	return 0;
    336}
    337
    338static struct platform_driver mvebu_icu_subset_driver = {
    339	.probe  = mvebu_icu_subset_probe,
    340	.driver = {
    341		.name = "mvebu-icu-subset",
    342		.of_match_table = mvebu_icu_subset_of_match,
    343	},
    344};
    345builtin_platform_driver(mvebu_icu_subset_driver);
    346
    347static int mvebu_icu_probe(struct platform_device *pdev)
    348{
    349	struct mvebu_icu *icu;
    350	int i;
    351
    352	icu = devm_kzalloc(&pdev->dev, sizeof(struct mvebu_icu),
    353			   GFP_KERNEL);
    354	if (!icu)
    355		return -ENOMEM;
    356
    357	icu->dev = &pdev->dev;
    358
    359	icu->base = devm_platform_ioremap_resource(pdev, 0);
    360	if (IS_ERR(icu->base))
    361		return PTR_ERR(icu->base);
    362
    363	/*
    364	 * Legacy bindings: ICU is one node with one MSI parent: force manually
    365	 *                  the probe of the NSR interrupts side.
    366	 * New bindings: ICU node has children, one per interrupt controller
    367	 *               having its own MSI parent: call platform_populate().
    368	 * All ICU instances should use the same bindings.
    369	 */
    370	if (!of_get_child_count(pdev->dev.of_node))
    371		static_branch_enable(&legacy_bindings);
    372
    373	/*
    374	 * Clean all ICU interrupts of type NSR and SEI, required to
    375	 * avoid unpredictable SPI assignments done by firmware.
    376	 */
    377	for (i = 0 ; i < ICU_MAX_IRQS ; i++) {
    378		u32 icu_int, icu_grp;
    379
    380		icu_int = readl_relaxed(icu->base + ICU_INT_CFG(i));
    381		icu_grp = icu_int >> ICU_GROUP_SHIFT;
    382
    383		if (icu_grp == ICU_GRP_NSR ||
    384		    (icu_grp == ICU_GRP_SEI &&
    385		     !static_branch_unlikely(&legacy_bindings)))
    386			writel_relaxed(0x0, icu->base + ICU_INT_CFG(i));
    387	}
    388
    389	platform_set_drvdata(pdev, icu);
    390
    391	if (static_branch_unlikely(&legacy_bindings))
    392		return mvebu_icu_subset_probe(pdev);
    393	else
    394		return devm_of_platform_populate(&pdev->dev);
    395}
    396
    397static const struct of_device_id mvebu_icu_of_match[] = {
    398	{ .compatible = "marvell,cp110-icu", },
    399	{},
    400};
    401
    402static struct platform_driver mvebu_icu_driver = {
    403	.probe  = mvebu_icu_probe,
    404	.driver = {
    405		.name = "mvebu-icu",
    406		.of_match_table = mvebu_icu_of_match,
    407	},
    408};
    409builtin_platform_driver(mvebu_icu_driver);