162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2016 ARM Limited, All Rights Reserved. 462306a36Sopenharmony_ci * Author: Marc Zyngier <marc.zyngier@arm.com> 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <linux/bitops.h> 862306a36Sopenharmony_ci#include <linux/interrupt.h> 962306a36Sopenharmony_ci#include <linux/irqchip.h> 1062306a36Sopenharmony_ci#include <linux/irqchip/chained_irq.h> 1162306a36Sopenharmony_ci#include <linux/irqchip/irq-partition-percpu.h> 1262306a36Sopenharmony_ci#include <linux/irqdomain.h> 1362306a36Sopenharmony_ci#include <linux/seq_file.h> 1462306a36Sopenharmony_ci#include <linux/slab.h> 1562306a36Sopenharmony_ci 1662306a36Sopenharmony_cistruct partition_desc { 1762306a36Sopenharmony_ci int nr_parts; 1862306a36Sopenharmony_ci struct partition_affinity *parts; 1962306a36Sopenharmony_ci struct irq_domain *domain; 2062306a36Sopenharmony_ci struct irq_desc *chained_desc; 2162306a36Sopenharmony_ci unsigned long *bitmap; 2262306a36Sopenharmony_ci struct irq_domain_ops ops; 2362306a36Sopenharmony_ci}; 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_cistatic bool partition_check_cpu(struct partition_desc *part, 2662306a36Sopenharmony_ci unsigned int cpu, unsigned int hwirq) 2762306a36Sopenharmony_ci{ 2862306a36Sopenharmony_ci return cpumask_test_cpu(cpu, &part->parts[hwirq].mask); 2962306a36Sopenharmony_ci} 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_cistatic void partition_irq_mask(struct irq_data *d) 3262306a36Sopenharmony_ci{ 3362306a36Sopenharmony_ci struct partition_desc *part = irq_data_get_irq_chip_data(d); 3462306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 3562306a36Sopenharmony_ci struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 3662306a36Sopenharmony_ci 3762306a36Sopenharmony_ci if (partition_check_cpu(part, smp_processor_id(), d->hwirq) && 3862306a36Sopenharmony_ci chip->irq_mask) 3962306a36Sopenharmony_ci chip->irq_mask(data); 4062306a36Sopenharmony_ci} 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_cistatic void partition_irq_unmask(struct irq_data *d) 4362306a36Sopenharmony_ci{ 4462306a36Sopenharmony_ci struct partition_desc *part = irq_data_get_irq_chip_data(d); 4562306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 4662306a36Sopenharmony_ci struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 4762306a36Sopenharmony_ci 4862306a36Sopenharmony_ci if (partition_check_cpu(part, smp_processor_id(), d->hwirq) && 4962306a36Sopenharmony_ci chip->irq_unmask) 5062306a36Sopenharmony_ci chip->irq_unmask(data); 5162306a36Sopenharmony_ci} 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_cistatic int partition_irq_set_irqchip_state(struct irq_data *d, 5462306a36Sopenharmony_ci enum irqchip_irq_state which, 5562306a36Sopenharmony_ci bool val) 5662306a36Sopenharmony_ci{ 5762306a36Sopenharmony_ci struct partition_desc *part = irq_data_get_irq_chip_data(d); 5862306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 5962306a36Sopenharmony_ci struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_ci if (partition_check_cpu(part, smp_processor_id(), d->hwirq) && 6262306a36Sopenharmony_ci chip->irq_set_irqchip_state) 6362306a36Sopenharmony_ci return chip->irq_set_irqchip_state(data, which, val); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci return -EINVAL; 6662306a36Sopenharmony_ci} 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_cistatic int partition_irq_get_irqchip_state(struct irq_data *d, 6962306a36Sopenharmony_ci enum irqchip_irq_state which, 7062306a36Sopenharmony_ci bool *val) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci struct partition_desc *part = irq_data_get_irq_chip_data(d); 7362306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 7462306a36Sopenharmony_ci struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 7562306a36Sopenharmony_ci 7662306a36Sopenharmony_ci if (partition_check_cpu(part, smp_processor_id(), d->hwirq) && 7762306a36Sopenharmony_ci chip->irq_get_irqchip_state) 7862306a36Sopenharmony_ci return chip->irq_get_irqchip_state(data, which, val); 7962306a36Sopenharmony_ci 8062306a36Sopenharmony_ci return -EINVAL; 8162306a36Sopenharmony_ci} 8262306a36Sopenharmony_ci 8362306a36Sopenharmony_cistatic int partition_irq_set_type(struct irq_data *d, unsigned int type) 8462306a36Sopenharmony_ci{ 8562306a36Sopenharmony_ci struct partition_desc *part = irq_data_get_irq_chip_data(d); 8662306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 8762306a36Sopenharmony_ci struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci if (chip->irq_set_type) 9062306a36Sopenharmony_ci return chip->irq_set_type(data, type); 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci return -EINVAL; 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic void partition_irq_print_chip(struct irq_data *d, struct seq_file *p) 9662306a36Sopenharmony_ci{ 9762306a36Sopenharmony_ci struct partition_desc *part = irq_data_get_irq_chip_data(d); 9862306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(part->chained_desc); 9962306a36Sopenharmony_ci struct irq_data *data = irq_desc_get_irq_data(part->chained_desc); 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci seq_printf(p, " %5s-%lu", chip->name, data->hwirq); 10262306a36Sopenharmony_ci} 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_cistatic struct irq_chip partition_irq_chip = { 10562306a36Sopenharmony_ci .irq_mask = partition_irq_mask, 10662306a36Sopenharmony_ci .irq_unmask = partition_irq_unmask, 10762306a36Sopenharmony_ci .irq_set_type = partition_irq_set_type, 10862306a36Sopenharmony_ci .irq_get_irqchip_state = partition_irq_get_irqchip_state, 10962306a36Sopenharmony_ci .irq_set_irqchip_state = partition_irq_set_irqchip_state, 11062306a36Sopenharmony_ci .irq_print_chip = partition_irq_print_chip, 11162306a36Sopenharmony_ci}; 11262306a36Sopenharmony_ci 11362306a36Sopenharmony_cistatic void partition_handle_irq(struct irq_desc *desc) 11462306a36Sopenharmony_ci{ 11562306a36Sopenharmony_ci struct partition_desc *part = irq_desc_get_handler_data(desc); 11662306a36Sopenharmony_ci struct irq_chip *chip = irq_desc_get_chip(desc); 11762306a36Sopenharmony_ci int cpu = smp_processor_id(); 11862306a36Sopenharmony_ci int hwirq; 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci chained_irq_enter(chip, desc); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci for_each_set_bit(hwirq, part->bitmap, part->nr_parts) { 12362306a36Sopenharmony_ci if (partition_check_cpu(part, cpu, hwirq)) 12462306a36Sopenharmony_ci break; 12562306a36Sopenharmony_ci } 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci if (unlikely(hwirq == part->nr_parts)) 12862306a36Sopenharmony_ci handle_bad_irq(desc); 12962306a36Sopenharmony_ci else 13062306a36Sopenharmony_ci generic_handle_domain_irq(part->domain, hwirq); 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci chained_irq_exit(chip, desc); 13362306a36Sopenharmony_ci} 13462306a36Sopenharmony_ci 13562306a36Sopenharmony_cistatic int partition_domain_alloc(struct irq_domain *domain, unsigned int virq, 13662306a36Sopenharmony_ci unsigned int nr_irqs, void *arg) 13762306a36Sopenharmony_ci{ 13862306a36Sopenharmony_ci int ret; 13962306a36Sopenharmony_ci irq_hw_number_t hwirq; 14062306a36Sopenharmony_ci unsigned int type; 14162306a36Sopenharmony_ci struct irq_fwspec *fwspec = arg; 14262306a36Sopenharmony_ci struct partition_desc *part; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci BUG_ON(nr_irqs != 1); 14562306a36Sopenharmony_ci ret = domain->ops->translate(domain, fwspec, &hwirq, &type); 14662306a36Sopenharmony_ci if (ret) 14762306a36Sopenharmony_ci return ret; 14862306a36Sopenharmony_ci 14962306a36Sopenharmony_ci part = domain->host_data; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci set_bit(hwirq, part->bitmap); 15262306a36Sopenharmony_ci irq_set_chained_handler_and_data(irq_desc_get_irq(part->chained_desc), 15362306a36Sopenharmony_ci partition_handle_irq, part); 15462306a36Sopenharmony_ci irq_set_percpu_devid_partition(virq, &part->parts[hwirq].mask); 15562306a36Sopenharmony_ci irq_domain_set_info(domain, virq, hwirq, &partition_irq_chip, part, 15662306a36Sopenharmony_ci handle_percpu_devid_irq, NULL, NULL); 15762306a36Sopenharmony_ci irq_set_status_flags(virq, IRQ_NOAUTOEN); 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci return 0; 16062306a36Sopenharmony_ci} 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_cistatic void partition_domain_free(struct irq_domain *domain, unsigned int virq, 16362306a36Sopenharmony_ci unsigned int nr_irqs) 16462306a36Sopenharmony_ci{ 16562306a36Sopenharmony_ci struct irq_data *d; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci BUG_ON(nr_irqs != 1); 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci d = irq_domain_get_irq_data(domain, virq); 17062306a36Sopenharmony_ci irq_set_handler(virq, NULL); 17162306a36Sopenharmony_ci irq_domain_reset_irq_data(d); 17262306a36Sopenharmony_ci} 17362306a36Sopenharmony_ci 17462306a36Sopenharmony_ciint partition_translate_id(struct partition_desc *desc, void *partition_id) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct partition_affinity *part = NULL; 17762306a36Sopenharmony_ci int i; 17862306a36Sopenharmony_ci 17962306a36Sopenharmony_ci for (i = 0; i < desc->nr_parts; i++) { 18062306a36Sopenharmony_ci if (desc->parts[i].partition_id == partition_id) { 18162306a36Sopenharmony_ci part = &desc->parts[i]; 18262306a36Sopenharmony_ci break; 18362306a36Sopenharmony_ci } 18462306a36Sopenharmony_ci } 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci if (WARN_ON(!part)) { 18762306a36Sopenharmony_ci pr_err("Failed to find partition\n"); 18862306a36Sopenharmony_ci return -EINVAL; 18962306a36Sopenharmony_ci } 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci return i; 19262306a36Sopenharmony_ci} 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_cistruct partition_desc *partition_create_desc(struct fwnode_handle *fwnode, 19562306a36Sopenharmony_ci struct partition_affinity *parts, 19662306a36Sopenharmony_ci int nr_parts, 19762306a36Sopenharmony_ci int chained_irq, 19862306a36Sopenharmony_ci const struct irq_domain_ops *ops) 19962306a36Sopenharmony_ci{ 20062306a36Sopenharmony_ci struct partition_desc *desc; 20162306a36Sopenharmony_ci struct irq_domain *d; 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ci BUG_ON(!ops->select || !ops->translate); 20462306a36Sopenharmony_ci 20562306a36Sopenharmony_ci desc = kzalloc(sizeof(*desc), GFP_KERNEL); 20662306a36Sopenharmony_ci if (!desc) 20762306a36Sopenharmony_ci return NULL; 20862306a36Sopenharmony_ci 20962306a36Sopenharmony_ci desc->ops = *ops; 21062306a36Sopenharmony_ci desc->ops.free = partition_domain_free; 21162306a36Sopenharmony_ci desc->ops.alloc = partition_domain_alloc; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci d = irq_domain_create_linear(fwnode, nr_parts, &desc->ops, desc); 21462306a36Sopenharmony_ci if (!d) 21562306a36Sopenharmony_ci goto out; 21662306a36Sopenharmony_ci desc->domain = d; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci desc->bitmap = bitmap_zalloc(nr_parts, GFP_KERNEL); 21962306a36Sopenharmony_ci if (WARN_ON(!desc->bitmap)) 22062306a36Sopenharmony_ci goto out; 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_ci desc->chained_desc = irq_to_desc(chained_irq); 22362306a36Sopenharmony_ci desc->nr_parts = nr_parts; 22462306a36Sopenharmony_ci desc->parts = parts; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci return desc; 22762306a36Sopenharmony_ciout: 22862306a36Sopenharmony_ci if (d) 22962306a36Sopenharmony_ci irq_domain_remove(d); 23062306a36Sopenharmony_ci kfree(desc); 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci return NULL; 23362306a36Sopenharmony_ci} 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_cistruct irq_domain *partition_get_domain(struct partition_desc *dsc) 23662306a36Sopenharmony_ci{ 23762306a36Sopenharmony_ci if (dsc) 23862306a36Sopenharmony_ci return dsc->domain; 23962306a36Sopenharmony_ci 24062306a36Sopenharmony_ci return NULL; 24162306a36Sopenharmony_ci} 242