162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Support for adapter interruptions 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright IBM Corp. 1999, 2007 662306a36Sopenharmony_ci * Author(s): Ingo Adlung <adlung@de.ibm.com> 762306a36Sopenharmony_ci * Cornelia Huck <cornelia.huck@de.ibm.com> 862306a36Sopenharmony_ci * Arnd Bergmann <arndb@de.ibm.com> 962306a36Sopenharmony_ci * Peter Oberparleiter <peter.oberparleiter@de.ibm.com> 1062306a36Sopenharmony_ci */ 1162306a36Sopenharmony_ci 1262306a36Sopenharmony_ci#include <linux/init.h> 1362306a36Sopenharmony_ci#include <linux/irq.h> 1462306a36Sopenharmony_ci#include <linux/kernel_stat.h> 1562306a36Sopenharmony_ci#include <linux/module.h> 1662306a36Sopenharmony_ci#include <linux/mutex.h> 1762306a36Sopenharmony_ci#include <linux/rculist.h> 1862306a36Sopenharmony_ci#include <linux/slab.h> 1962306a36Sopenharmony_ci#include <linux/dmapool.h> 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_ci#include <asm/airq.h> 2262306a36Sopenharmony_ci#include <asm/isc.h> 2362306a36Sopenharmony_ci#include <asm/cio.h> 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci#include "cio.h" 2662306a36Sopenharmony_ci#include "cio_debug.h" 2762306a36Sopenharmony_ci#include "ioasm.h" 2862306a36Sopenharmony_ci 2962306a36Sopenharmony_cistatic DEFINE_SPINLOCK(airq_lists_lock); 3062306a36Sopenharmony_cistatic struct hlist_head airq_lists[MAX_ISC+1]; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic struct dma_pool *airq_iv_cache; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci/** 3562306a36Sopenharmony_ci * register_adapter_interrupt() - register adapter interrupt handler 3662306a36Sopenharmony_ci * @airq: pointer to adapter interrupt descriptor 3762306a36Sopenharmony_ci * 3862306a36Sopenharmony_ci * Returns 0 on success, or -EINVAL. 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_ciint register_adapter_interrupt(struct airq_struct *airq) 4162306a36Sopenharmony_ci{ 4262306a36Sopenharmony_ci char dbf_txt[32]; 4362306a36Sopenharmony_ci 4462306a36Sopenharmony_ci if (!airq->handler || airq->isc > MAX_ISC) 4562306a36Sopenharmony_ci return -EINVAL; 4662306a36Sopenharmony_ci if (!airq->lsi_ptr) { 4762306a36Sopenharmony_ci airq->lsi_ptr = cio_dma_zalloc(1); 4862306a36Sopenharmony_ci if (!airq->lsi_ptr) 4962306a36Sopenharmony_ci return -ENOMEM; 5062306a36Sopenharmony_ci airq->flags |= AIRQ_PTR_ALLOCATED; 5162306a36Sopenharmony_ci } 5262306a36Sopenharmony_ci snprintf(dbf_txt, sizeof(dbf_txt), "rairq:%p", airq); 5362306a36Sopenharmony_ci CIO_TRACE_EVENT(4, dbf_txt); 5462306a36Sopenharmony_ci isc_register(airq->isc); 5562306a36Sopenharmony_ci spin_lock(&airq_lists_lock); 5662306a36Sopenharmony_ci hlist_add_head_rcu(&airq->list, &airq_lists[airq->isc]); 5762306a36Sopenharmony_ci spin_unlock(&airq_lists_lock); 5862306a36Sopenharmony_ci return 0; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ciEXPORT_SYMBOL(register_adapter_interrupt); 6162306a36Sopenharmony_ci 6262306a36Sopenharmony_ci/** 6362306a36Sopenharmony_ci * unregister_adapter_interrupt - unregister adapter interrupt handler 6462306a36Sopenharmony_ci * @airq: pointer to adapter interrupt descriptor 6562306a36Sopenharmony_ci */ 6662306a36Sopenharmony_civoid unregister_adapter_interrupt(struct airq_struct *airq) 6762306a36Sopenharmony_ci{ 6862306a36Sopenharmony_ci char dbf_txt[32]; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci if (hlist_unhashed(&airq->list)) 7162306a36Sopenharmony_ci return; 7262306a36Sopenharmony_ci snprintf(dbf_txt, sizeof(dbf_txt), "urairq:%p", airq); 7362306a36Sopenharmony_ci CIO_TRACE_EVENT(4, dbf_txt); 7462306a36Sopenharmony_ci spin_lock(&airq_lists_lock); 7562306a36Sopenharmony_ci hlist_del_rcu(&airq->list); 7662306a36Sopenharmony_ci spin_unlock(&airq_lists_lock); 7762306a36Sopenharmony_ci synchronize_rcu(); 7862306a36Sopenharmony_ci isc_unregister(airq->isc); 7962306a36Sopenharmony_ci if (airq->flags & AIRQ_PTR_ALLOCATED) { 8062306a36Sopenharmony_ci cio_dma_free(airq->lsi_ptr, 1); 8162306a36Sopenharmony_ci airq->lsi_ptr = NULL; 8262306a36Sopenharmony_ci airq->flags &= ~AIRQ_PTR_ALLOCATED; 8362306a36Sopenharmony_ci } 8462306a36Sopenharmony_ci} 8562306a36Sopenharmony_ciEXPORT_SYMBOL(unregister_adapter_interrupt); 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic irqreturn_t do_airq_interrupt(int irq, void *dummy) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci struct tpi_info *tpi_info; 9062306a36Sopenharmony_ci struct airq_struct *airq; 9162306a36Sopenharmony_ci struct hlist_head *head; 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci set_cpu_flag(CIF_NOHZ_DELAY); 9462306a36Sopenharmony_ci tpi_info = &get_irq_regs()->tpi_info; 9562306a36Sopenharmony_ci trace_s390_cio_adapter_int(tpi_info); 9662306a36Sopenharmony_ci head = &airq_lists[tpi_info->isc]; 9762306a36Sopenharmony_ci rcu_read_lock(); 9862306a36Sopenharmony_ci hlist_for_each_entry_rcu(airq, head, list) 9962306a36Sopenharmony_ci if (*airq->lsi_ptr != 0) 10062306a36Sopenharmony_ci airq->handler(airq, tpi_info); 10162306a36Sopenharmony_ci rcu_read_unlock(); 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci return IRQ_HANDLED; 10462306a36Sopenharmony_ci} 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_civoid __init init_airq_interrupts(void) 10762306a36Sopenharmony_ci{ 10862306a36Sopenharmony_ci irq_set_chip_and_handler(THIN_INTERRUPT, 10962306a36Sopenharmony_ci &dummy_irq_chip, handle_percpu_irq); 11062306a36Sopenharmony_ci if (request_irq(THIN_INTERRUPT, do_airq_interrupt, 0, "AIO", NULL)) 11162306a36Sopenharmony_ci panic("Failed to register AIO interrupt\n"); 11262306a36Sopenharmony_ci} 11362306a36Sopenharmony_ci 11462306a36Sopenharmony_cistatic inline unsigned long iv_size(unsigned long bits) 11562306a36Sopenharmony_ci{ 11662306a36Sopenharmony_ci return BITS_TO_LONGS(bits) * sizeof(unsigned long); 11762306a36Sopenharmony_ci} 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci/** 12062306a36Sopenharmony_ci * airq_iv_create - create an interrupt vector 12162306a36Sopenharmony_ci * @bits: number of bits in the interrupt vector 12262306a36Sopenharmony_ci * @flags: allocation flags 12362306a36Sopenharmony_ci * @vec: pointer to pinned guest memory if AIRQ_IV_GUESTVEC 12462306a36Sopenharmony_ci * 12562306a36Sopenharmony_ci * Returns a pointer to an interrupt vector structure 12662306a36Sopenharmony_ci */ 12762306a36Sopenharmony_cistruct airq_iv *airq_iv_create(unsigned long bits, unsigned long flags, 12862306a36Sopenharmony_ci unsigned long *vec) 12962306a36Sopenharmony_ci{ 13062306a36Sopenharmony_ci struct airq_iv *iv; 13162306a36Sopenharmony_ci unsigned long size; 13262306a36Sopenharmony_ci 13362306a36Sopenharmony_ci iv = kzalloc(sizeof(*iv), GFP_KERNEL); 13462306a36Sopenharmony_ci if (!iv) 13562306a36Sopenharmony_ci goto out; 13662306a36Sopenharmony_ci iv->bits = bits; 13762306a36Sopenharmony_ci iv->flags = flags; 13862306a36Sopenharmony_ci size = iv_size(bits); 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_ci if (flags & AIRQ_IV_CACHELINE) { 14162306a36Sopenharmony_ci if ((cache_line_size() * BITS_PER_BYTE) < bits 14262306a36Sopenharmony_ci || !airq_iv_cache) 14362306a36Sopenharmony_ci goto out_free; 14462306a36Sopenharmony_ci 14562306a36Sopenharmony_ci iv->vector = dma_pool_zalloc(airq_iv_cache, GFP_KERNEL, 14662306a36Sopenharmony_ci &iv->vector_dma); 14762306a36Sopenharmony_ci if (!iv->vector) 14862306a36Sopenharmony_ci goto out_free; 14962306a36Sopenharmony_ci } else if (flags & AIRQ_IV_GUESTVEC) { 15062306a36Sopenharmony_ci iv->vector = vec; 15162306a36Sopenharmony_ci } else { 15262306a36Sopenharmony_ci iv->vector = cio_dma_zalloc(size); 15362306a36Sopenharmony_ci if (!iv->vector) 15462306a36Sopenharmony_ci goto out_free; 15562306a36Sopenharmony_ci } 15662306a36Sopenharmony_ci if (flags & AIRQ_IV_ALLOC) { 15762306a36Sopenharmony_ci iv->avail = kmalloc(size, GFP_KERNEL); 15862306a36Sopenharmony_ci if (!iv->avail) 15962306a36Sopenharmony_ci goto out_free; 16062306a36Sopenharmony_ci memset(iv->avail, 0xff, size); 16162306a36Sopenharmony_ci iv->end = 0; 16262306a36Sopenharmony_ci } else 16362306a36Sopenharmony_ci iv->end = bits; 16462306a36Sopenharmony_ci if (flags & AIRQ_IV_BITLOCK) { 16562306a36Sopenharmony_ci iv->bitlock = kzalloc(size, GFP_KERNEL); 16662306a36Sopenharmony_ci if (!iv->bitlock) 16762306a36Sopenharmony_ci goto out_free; 16862306a36Sopenharmony_ci } 16962306a36Sopenharmony_ci if (flags & AIRQ_IV_PTR) { 17062306a36Sopenharmony_ci size = bits * sizeof(unsigned long); 17162306a36Sopenharmony_ci iv->ptr = kzalloc(size, GFP_KERNEL); 17262306a36Sopenharmony_ci if (!iv->ptr) 17362306a36Sopenharmony_ci goto out_free; 17462306a36Sopenharmony_ci } 17562306a36Sopenharmony_ci if (flags & AIRQ_IV_DATA) { 17662306a36Sopenharmony_ci size = bits * sizeof(unsigned int); 17762306a36Sopenharmony_ci iv->data = kzalloc(size, GFP_KERNEL); 17862306a36Sopenharmony_ci if (!iv->data) 17962306a36Sopenharmony_ci goto out_free; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci spin_lock_init(&iv->lock); 18262306a36Sopenharmony_ci return iv; 18362306a36Sopenharmony_ci 18462306a36Sopenharmony_ciout_free: 18562306a36Sopenharmony_ci kfree(iv->ptr); 18662306a36Sopenharmony_ci kfree(iv->bitlock); 18762306a36Sopenharmony_ci kfree(iv->avail); 18862306a36Sopenharmony_ci if (iv->flags & AIRQ_IV_CACHELINE && iv->vector) 18962306a36Sopenharmony_ci dma_pool_free(airq_iv_cache, iv->vector, iv->vector_dma); 19062306a36Sopenharmony_ci else if (!(iv->flags & AIRQ_IV_GUESTVEC)) 19162306a36Sopenharmony_ci cio_dma_free(iv->vector, size); 19262306a36Sopenharmony_ci kfree(iv); 19362306a36Sopenharmony_ciout: 19462306a36Sopenharmony_ci return NULL; 19562306a36Sopenharmony_ci} 19662306a36Sopenharmony_ciEXPORT_SYMBOL(airq_iv_create); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci/** 19962306a36Sopenharmony_ci * airq_iv_release - release an interrupt vector 20062306a36Sopenharmony_ci * @iv: pointer to interrupt vector structure 20162306a36Sopenharmony_ci */ 20262306a36Sopenharmony_civoid airq_iv_release(struct airq_iv *iv) 20362306a36Sopenharmony_ci{ 20462306a36Sopenharmony_ci kfree(iv->data); 20562306a36Sopenharmony_ci kfree(iv->ptr); 20662306a36Sopenharmony_ci kfree(iv->bitlock); 20762306a36Sopenharmony_ci if (iv->flags & AIRQ_IV_CACHELINE) 20862306a36Sopenharmony_ci dma_pool_free(airq_iv_cache, iv->vector, iv->vector_dma); 20962306a36Sopenharmony_ci else if (!(iv->flags & AIRQ_IV_GUESTVEC)) 21062306a36Sopenharmony_ci cio_dma_free(iv->vector, iv_size(iv->bits)); 21162306a36Sopenharmony_ci kfree(iv->avail); 21262306a36Sopenharmony_ci kfree(iv); 21362306a36Sopenharmony_ci} 21462306a36Sopenharmony_ciEXPORT_SYMBOL(airq_iv_release); 21562306a36Sopenharmony_ci 21662306a36Sopenharmony_ci/** 21762306a36Sopenharmony_ci * airq_iv_alloc - allocate irq bits from an interrupt vector 21862306a36Sopenharmony_ci * @iv: pointer to an interrupt vector structure 21962306a36Sopenharmony_ci * @num: number of consecutive irq bits to allocate 22062306a36Sopenharmony_ci * 22162306a36Sopenharmony_ci * Returns the bit number of the first irq in the allocated block of irqs, 22262306a36Sopenharmony_ci * or -1UL if no bit is available or the AIRQ_IV_ALLOC flag has not been 22362306a36Sopenharmony_ci * specified 22462306a36Sopenharmony_ci */ 22562306a36Sopenharmony_ciunsigned long airq_iv_alloc(struct airq_iv *iv, unsigned long num) 22662306a36Sopenharmony_ci{ 22762306a36Sopenharmony_ci unsigned long bit, i, flags; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (!iv->avail || num == 0) 23062306a36Sopenharmony_ci return -1UL; 23162306a36Sopenharmony_ci spin_lock_irqsave(&iv->lock, flags); 23262306a36Sopenharmony_ci bit = find_first_bit_inv(iv->avail, iv->bits); 23362306a36Sopenharmony_ci while (bit + num <= iv->bits) { 23462306a36Sopenharmony_ci for (i = 1; i < num; i++) 23562306a36Sopenharmony_ci if (!test_bit_inv(bit + i, iv->avail)) 23662306a36Sopenharmony_ci break; 23762306a36Sopenharmony_ci if (i >= num) { 23862306a36Sopenharmony_ci /* Found a suitable block of irqs */ 23962306a36Sopenharmony_ci for (i = 0; i < num; i++) 24062306a36Sopenharmony_ci clear_bit_inv(bit + i, iv->avail); 24162306a36Sopenharmony_ci if (bit + num >= iv->end) 24262306a36Sopenharmony_ci iv->end = bit + num + 1; 24362306a36Sopenharmony_ci break; 24462306a36Sopenharmony_ci } 24562306a36Sopenharmony_ci bit = find_next_bit_inv(iv->avail, iv->bits, bit + i + 1); 24662306a36Sopenharmony_ci } 24762306a36Sopenharmony_ci if (bit + num > iv->bits) 24862306a36Sopenharmony_ci bit = -1UL; 24962306a36Sopenharmony_ci spin_unlock_irqrestore(&iv->lock, flags); 25062306a36Sopenharmony_ci return bit; 25162306a36Sopenharmony_ci} 25262306a36Sopenharmony_ciEXPORT_SYMBOL(airq_iv_alloc); 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci/** 25562306a36Sopenharmony_ci * airq_iv_free - free irq bits of an interrupt vector 25662306a36Sopenharmony_ci * @iv: pointer to interrupt vector structure 25762306a36Sopenharmony_ci * @bit: number of the first irq bit to free 25862306a36Sopenharmony_ci * @num: number of consecutive irq bits to free 25962306a36Sopenharmony_ci */ 26062306a36Sopenharmony_civoid airq_iv_free(struct airq_iv *iv, unsigned long bit, unsigned long num) 26162306a36Sopenharmony_ci{ 26262306a36Sopenharmony_ci unsigned long i, flags; 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci if (!iv->avail || num == 0) 26562306a36Sopenharmony_ci return; 26662306a36Sopenharmony_ci spin_lock_irqsave(&iv->lock, flags); 26762306a36Sopenharmony_ci for (i = 0; i < num; i++) { 26862306a36Sopenharmony_ci /* Clear (possibly left over) interrupt bit */ 26962306a36Sopenharmony_ci clear_bit_inv(bit + i, iv->vector); 27062306a36Sopenharmony_ci /* Make the bit positions available again */ 27162306a36Sopenharmony_ci set_bit_inv(bit + i, iv->avail); 27262306a36Sopenharmony_ci } 27362306a36Sopenharmony_ci if (bit + num >= iv->end) { 27462306a36Sopenharmony_ci /* Find new end of bit-field */ 27562306a36Sopenharmony_ci while (iv->end > 0 && !test_bit_inv(iv->end - 1, iv->avail)) 27662306a36Sopenharmony_ci iv->end--; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci spin_unlock_irqrestore(&iv->lock, flags); 27962306a36Sopenharmony_ci} 28062306a36Sopenharmony_ciEXPORT_SYMBOL(airq_iv_free); 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci/** 28362306a36Sopenharmony_ci * airq_iv_scan - scan interrupt vector for non-zero bits 28462306a36Sopenharmony_ci * @iv: pointer to interrupt vector structure 28562306a36Sopenharmony_ci * @start: bit number to start the search 28662306a36Sopenharmony_ci * @end: bit number to end the search 28762306a36Sopenharmony_ci * 28862306a36Sopenharmony_ci * Returns the bit number of the next non-zero interrupt bit, or 28962306a36Sopenharmony_ci * -1UL if the scan completed without finding any more any non-zero bits. 29062306a36Sopenharmony_ci */ 29162306a36Sopenharmony_ciunsigned long airq_iv_scan(struct airq_iv *iv, unsigned long start, 29262306a36Sopenharmony_ci unsigned long end) 29362306a36Sopenharmony_ci{ 29462306a36Sopenharmony_ci unsigned long bit; 29562306a36Sopenharmony_ci 29662306a36Sopenharmony_ci /* Find non-zero bit starting from 'ivs->next'. */ 29762306a36Sopenharmony_ci bit = find_next_bit_inv(iv->vector, end, start); 29862306a36Sopenharmony_ci if (bit >= end) 29962306a36Sopenharmony_ci return -1UL; 30062306a36Sopenharmony_ci clear_bit_inv(bit, iv->vector); 30162306a36Sopenharmony_ci return bit; 30262306a36Sopenharmony_ci} 30362306a36Sopenharmony_ciEXPORT_SYMBOL(airq_iv_scan); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ciint __init airq_init(void) 30662306a36Sopenharmony_ci{ 30762306a36Sopenharmony_ci airq_iv_cache = dma_pool_create("airq_iv_cache", cio_get_dma_css_dev(), 30862306a36Sopenharmony_ci cache_line_size(), 30962306a36Sopenharmony_ci cache_line_size(), PAGE_SIZE); 31062306a36Sopenharmony_ci if (!airq_iv_cache) 31162306a36Sopenharmony_ci return -ENOMEM; 31262306a36Sopenharmony_ci return 0; 31362306a36Sopenharmony_ci} 314