162306a36Sopenharmony_ci/*
262306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public
362306a36Sopenharmony_ci * License.  See the file "COPYING" in the main directory of this archive
462306a36Sopenharmony_ci * for more details.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * SGI UV IRQ functions
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Copyright (C) 2008 Silicon Graphics, Inc. All rights reserved.
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/export.h>
1262306a36Sopenharmony_ci#include <linux/rbtree.h>
1362306a36Sopenharmony_ci#include <linux/slab.h>
1462306a36Sopenharmony_ci#include <linux/irq.h>
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci#include <asm/irqdomain.h>
1762306a36Sopenharmony_ci#include <asm/apic.h>
1862306a36Sopenharmony_ci#include <asm/uv/uv_irq.h>
1962306a36Sopenharmony_ci#include <asm/uv/uv_hub.h>
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ci/* MMR offset and pnode of hub sourcing interrupts for a given irq */
2262306a36Sopenharmony_cistruct uv_irq_2_mmr_pnode {
2362306a36Sopenharmony_ci	unsigned long		offset;
2462306a36Sopenharmony_ci	int			pnode;
2562306a36Sopenharmony_ci};
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_cistatic void uv_program_mmr(struct irq_cfg *cfg, struct uv_irq_2_mmr_pnode *info)
2862306a36Sopenharmony_ci{
2962306a36Sopenharmony_ci	unsigned long mmr_value;
3062306a36Sopenharmony_ci	struct uv_IO_APIC_route_entry *entry;
3162306a36Sopenharmony_ci
3262306a36Sopenharmony_ci	BUILD_BUG_ON(sizeof(struct uv_IO_APIC_route_entry) !=
3362306a36Sopenharmony_ci		     sizeof(unsigned long));
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci	mmr_value = 0;
3662306a36Sopenharmony_ci	entry = (struct uv_IO_APIC_route_entry *)&mmr_value;
3762306a36Sopenharmony_ci	entry->vector		= cfg->vector;
3862306a36Sopenharmony_ci	entry->delivery_mode	= apic->delivery_mode;
3962306a36Sopenharmony_ci	entry->dest_mode	= apic->dest_mode_logical;
4062306a36Sopenharmony_ci	entry->polarity		= 0;
4162306a36Sopenharmony_ci	entry->trigger		= 0;
4262306a36Sopenharmony_ci	entry->mask		= 0;
4362306a36Sopenharmony_ci	entry->dest		= cfg->dest_apicid;
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_ci	uv_write_global_mmr64(info->pnode, info->offset, mmr_value);
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistatic void uv_noop(struct irq_data *data) { }
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic int
5162306a36Sopenharmony_ciuv_set_irq_affinity(struct irq_data *data, const struct cpumask *mask,
5262306a36Sopenharmony_ci		    bool force)
5362306a36Sopenharmony_ci{
5462306a36Sopenharmony_ci	struct irq_data *parent = data->parent_data;
5562306a36Sopenharmony_ci	struct irq_cfg *cfg = irqd_cfg(data);
5662306a36Sopenharmony_ci	int ret;
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci	ret = parent->chip->irq_set_affinity(parent, mask, force);
5962306a36Sopenharmony_ci	if (ret >= 0) {
6062306a36Sopenharmony_ci		uv_program_mmr(cfg, data->chip_data);
6162306a36Sopenharmony_ci		vector_schedule_cleanup(cfg);
6262306a36Sopenharmony_ci	}
6362306a36Sopenharmony_ci
6462306a36Sopenharmony_ci	return ret;
6562306a36Sopenharmony_ci}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic struct irq_chip uv_irq_chip = {
6862306a36Sopenharmony_ci	.name			= "UV-CORE",
6962306a36Sopenharmony_ci	.irq_mask		= uv_noop,
7062306a36Sopenharmony_ci	.irq_unmask		= uv_noop,
7162306a36Sopenharmony_ci	.irq_eoi		= apic_ack_irq,
7262306a36Sopenharmony_ci	.irq_set_affinity	= uv_set_irq_affinity,
7362306a36Sopenharmony_ci};
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_cistatic int uv_domain_alloc(struct irq_domain *domain, unsigned int virq,
7662306a36Sopenharmony_ci			   unsigned int nr_irqs, void *arg)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	struct uv_irq_2_mmr_pnode *chip_data;
7962306a36Sopenharmony_ci	struct irq_alloc_info *info = arg;
8062306a36Sopenharmony_ci	struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);
8162306a36Sopenharmony_ci	int ret;
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_ci	if (nr_irqs > 1 || !info || info->type != X86_IRQ_ALLOC_TYPE_UV)
8462306a36Sopenharmony_ci		return -EINVAL;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	chip_data = kmalloc_node(sizeof(*chip_data), GFP_KERNEL,
8762306a36Sopenharmony_ci				 irq_data_get_node(irq_data));
8862306a36Sopenharmony_ci	if (!chip_data)
8962306a36Sopenharmony_ci		return -ENOMEM;
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci	ret = irq_domain_alloc_irqs_parent(domain, virq, nr_irqs, arg);
9262306a36Sopenharmony_ci	if (ret >= 0) {
9362306a36Sopenharmony_ci		if (info->uv.limit == UV_AFFINITY_CPU)
9462306a36Sopenharmony_ci			irq_set_status_flags(virq, IRQ_NO_BALANCING);
9562306a36Sopenharmony_ci		else
9662306a36Sopenharmony_ci			irq_set_status_flags(virq, IRQ_MOVE_PCNTXT);
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci		chip_data->pnode = uv_blade_to_pnode(info->uv.blade);
9962306a36Sopenharmony_ci		chip_data->offset = info->uv.offset;
10062306a36Sopenharmony_ci		irq_domain_set_info(domain, virq, virq, &uv_irq_chip, chip_data,
10162306a36Sopenharmony_ci				    handle_percpu_irq, NULL, info->uv.name);
10262306a36Sopenharmony_ci	} else {
10362306a36Sopenharmony_ci		kfree(chip_data);
10462306a36Sopenharmony_ci	}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_ci	return ret;
10762306a36Sopenharmony_ci}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_cistatic void uv_domain_free(struct irq_domain *domain, unsigned int virq,
11062306a36Sopenharmony_ci			   unsigned int nr_irqs)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct irq_data *irq_data = irq_domain_get_irq_data(domain, virq);
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	BUG_ON(nr_irqs != 1);
11562306a36Sopenharmony_ci	kfree(irq_data->chip_data);
11662306a36Sopenharmony_ci	irq_clear_status_flags(virq, IRQ_MOVE_PCNTXT);
11762306a36Sopenharmony_ci	irq_clear_status_flags(virq, IRQ_NO_BALANCING);
11862306a36Sopenharmony_ci	irq_domain_free_irqs_top(domain, virq, nr_irqs);
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci/*
12262306a36Sopenharmony_ci * Re-target the irq to the specified CPU and enable the specified MMR located
12362306a36Sopenharmony_ci * on the specified blade to allow the sending of MSIs to the specified CPU.
12462306a36Sopenharmony_ci */
12562306a36Sopenharmony_cistatic int uv_domain_activate(struct irq_domain *domain,
12662306a36Sopenharmony_ci			      struct irq_data *irq_data, bool reserve)
12762306a36Sopenharmony_ci{
12862306a36Sopenharmony_ci	uv_program_mmr(irqd_cfg(irq_data), irq_data->chip_data);
12962306a36Sopenharmony_ci	return 0;
13062306a36Sopenharmony_ci}
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci/*
13362306a36Sopenharmony_ci * Disable the specified MMR located on the specified blade so that MSIs are
13462306a36Sopenharmony_ci * longer allowed to be sent.
13562306a36Sopenharmony_ci */
13662306a36Sopenharmony_cistatic void uv_domain_deactivate(struct irq_domain *domain,
13762306a36Sopenharmony_ci				 struct irq_data *irq_data)
13862306a36Sopenharmony_ci{
13962306a36Sopenharmony_ci	unsigned long mmr_value;
14062306a36Sopenharmony_ci	struct uv_IO_APIC_route_entry *entry;
14162306a36Sopenharmony_ci
14262306a36Sopenharmony_ci	mmr_value = 0;
14362306a36Sopenharmony_ci	entry = (struct uv_IO_APIC_route_entry *)&mmr_value;
14462306a36Sopenharmony_ci	entry->mask = 1;
14562306a36Sopenharmony_ci	uv_program_mmr(irqd_cfg(irq_data), irq_data->chip_data);
14662306a36Sopenharmony_ci}
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_cistatic const struct irq_domain_ops uv_domain_ops = {
14962306a36Sopenharmony_ci	.alloc		= uv_domain_alloc,
15062306a36Sopenharmony_ci	.free		= uv_domain_free,
15162306a36Sopenharmony_ci	.activate	= uv_domain_activate,
15262306a36Sopenharmony_ci	.deactivate	= uv_domain_deactivate,
15362306a36Sopenharmony_ci};
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_cistatic struct irq_domain *uv_get_irq_domain(void)
15662306a36Sopenharmony_ci{
15762306a36Sopenharmony_ci	static struct irq_domain *uv_domain;
15862306a36Sopenharmony_ci	static DEFINE_MUTEX(uv_lock);
15962306a36Sopenharmony_ci	struct fwnode_handle *fn;
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	mutex_lock(&uv_lock);
16262306a36Sopenharmony_ci	if (uv_domain)
16362306a36Sopenharmony_ci		goto out;
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	fn = irq_domain_alloc_named_fwnode("UV-CORE");
16662306a36Sopenharmony_ci	if (!fn)
16762306a36Sopenharmony_ci		goto out;
16862306a36Sopenharmony_ci
16962306a36Sopenharmony_ci	uv_domain = irq_domain_create_hierarchy(x86_vector_domain, 0, 0, fn,
17062306a36Sopenharmony_ci						&uv_domain_ops, NULL);
17162306a36Sopenharmony_ci	if (!uv_domain)
17262306a36Sopenharmony_ci		irq_domain_free_fwnode(fn);
17362306a36Sopenharmony_ciout:
17462306a36Sopenharmony_ci	mutex_unlock(&uv_lock);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	return uv_domain;
17762306a36Sopenharmony_ci}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci/*
18062306a36Sopenharmony_ci * Set up a mapping of an available irq and vector, and enable the specified
18162306a36Sopenharmony_ci * MMR that defines the MSI that is to be sent to the specified CPU when an
18262306a36Sopenharmony_ci * interrupt is raised.
18362306a36Sopenharmony_ci */
18462306a36Sopenharmony_ciint uv_setup_irq(char *irq_name, int cpu, int mmr_blade,
18562306a36Sopenharmony_ci		 unsigned long mmr_offset, int limit)
18662306a36Sopenharmony_ci{
18762306a36Sopenharmony_ci	struct irq_alloc_info info;
18862306a36Sopenharmony_ci	struct irq_domain *domain = uv_get_irq_domain();
18962306a36Sopenharmony_ci
19062306a36Sopenharmony_ci	if (!domain)
19162306a36Sopenharmony_ci		return -ENOMEM;
19262306a36Sopenharmony_ci
19362306a36Sopenharmony_ci	init_irq_alloc_info(&info, cpumask_of(cpu));
19462306a36Sopenharmony_ci	info.type = X86_IRQ_ALLOC_TYPE_UV;
19562306a36Sopenharmony_ci	info.uv.limit = limit;
19662306a36Sopenharmony_ci	info.uv.blade = mmr_blade;
19762306a36Sopenharmony_ci	info.uv.offset = mmr_offset;
19862306a36Sopenharmony_ci	info.uv.name = irq_name;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci	return irq_domain_alloc_irqs(domain, 1,
20162306a36Sopenharmony_ci				     uv_blade_to_memory_nid(mmr_blade), &info);
20262306a36Sopenharmony_ci}
20362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(uv_setup_irq);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci/*
20662306a36Sopenharmony_ci * Tear down a mapping of an irq and vector, and disable the specified MMR that
20762306a36Sopenharmony_ci * defined the MSI that was to be sent to the specified CPU when an interrupt
20862306a36Sopenharmony_ci * was raised.
20962306a36Sopenharmony_ci *
21062306a36Sopenharmony_ci * Set mmr_blade and mmr_offset to what was passed in on uv_setup_irq().
21162306a36Sopenharmony_ci */
21262306a36Sopenharmony_civoid uv_teardown_irq(unsigned int irq)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	irq_domain_free_irqs(irq, 1);
21562306a36Sopenharmony_ci}
21662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(uv_teardown_irq);
217