162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright IBM Corp. 2000, 2009 462306a36Sopenharmony_ci * Author(s): Utz Bacher <utz.bacher@de.ibm.com> 562306a36Sopenharmony_ci * Cornelia Huck <cornelia.huck@de.ibm.com> 662306a36Sopenharmony_ci * Jan Glauber <jang@linux.vnet.ibm.com> 762306a36Sopenharmony_ci */ 862306a36Sopenharmony_ci#include <linux/io.h> 962306a36Sopenharmony_ci#include <linux/slab.h> 1062306a36Sopenharmony_ci#include <linux/kernel_stat.h> 1162306a36Sopenharmony_ci#include <linux/atomic.h> 1262306a36Sopenharmony_ci#include <linux/rculist.h> 1362306a36Sopenharmony_ci 1462306a36Sopenharmony_ci#include <asm/debug.h> 1562306a36Sopenharmony_ci#include <asm/qdio.h> 1662306a36Sopenharmony_ci#include <asm/airq.h> 1762306a36Sopenharmony_ci#include <asm/isc.h> 1862306a36Sopenharmony_ci#include <asm/tpi.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include "cio.h" 2162306a36Sopenharmony_ci#include "ioasm.h" 2262306a36Sopenharmony_ci#include "qdio.h" 2362306a36Sopenharmony_ci#include "qdio_debug.h" 2462306a36Sopenharmony_ci 2562306a36Sopenharmony_ci/* 2662306a36Sopenharmony_ci * Restriction: only 63 iqdio subchannels would have its own indicator, 2762306a36Sopenharmony_ci * after that, subsequent subchannels share one indicator 2862306a36Sopenharmony_ci */ 2962306a36Sopenharmony_ci#define TIQDIO_NR_NONSHARED_IND 63 3062306a36Sopenharmony_ci#define TIQDIO_NR_INDICATORS (TIQDIO_NR_NONSHARED_IND + 1) 3162306a36Sopenharmony_ci#define TIQDIO_SHARED_IND 63 3262306a36Sopenharmony_ci 3362306a36Sopenharmony_ci/* device state change indicators */ 3462306a36Sopenharmony_cistruct indicator_t { 3562306a36Sopenharmony_ci u32 ind; /* u32 because of compare-and-swap performance */ 3662306a36Sopenharmony_ci atomic_t count; /* use count, 0 or 1 for non-shared indicators */ 3762306a36Sopenharmony_ci}; 3862306a36Sopenharmony_ci 3962306a36Sopenharmony_ci/* list of thin interrupt input queues */ 4062306a36Sopenharmony_cistatic LIST_HEAD(tiq_list); 4162306a36Sopenharmony_cistatic DEFINE_MUTEX(tiq_list_lock); 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_cistatic struct indicator_t *q_indicators; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ciu64 last_ai_time; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci/* returns addr for the device state change indicator */ 4862306a36Sopenharmony_cistatic u32 *get_indicator(void) 4962306a36Sopenharmony_ci{ 5062306a36Sopenharmony_ci int i; 5162306a36Sopenharmony_ci 5262306a36Sopenharmony_ci for (i = 0; i < TIQDIO_NR_NONSHARED_IND; i++) 5362306a36Sopenharmony_ci if (!atomic_cmpxchg(&q_indicators[i].count, 0, 1)) 5462306a36Sopenharmony_ci return &q_indicators[i].ind; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci /* use the shared indicator */ 5762306a36Sopenharmony_ci atomic_inc(&q_indicators[TIQDIO_SHARED_IND].count); 5862306a36Sopenharmony_ci return &q_indicators[TIQDIO_SHARED_IND].ind; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_cistatic void put_indicator(u32 *addr) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci struct indicator_t *ind = container_of(addr, struct indicator_t, ind); 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci if (!addr) 6662306a36Sopenharmony_ci return; 6762306a36Sopenharmony_ci atomic_dec(&ind->count); 6862306a36Sopenharmony_ci} 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_cistatic inline int references_shared_dsci(struct qdio_irq *irq_ptr) 7162306a36Sopenharmony_ci{ 7262306a36Sopenharmony_ci return irq_ptr->dsci == &q_indicators[TIQDIO_SHARED_IND].ind; 7362306a36Sopenharmony_ci} 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ciint test_nonshared_ind(struct qdio_irq *irq_ptr) 7662306a36Sopenharmony_ci{ 7762306a36Sopenharmony_ci if (!is_thinint_irq(irq_ptr)) 7862306a36Sopenharmony_ci return 0; 7962306a36Sopenharmony_ci if (references_shared_dsci(irq_ptr)) 8062306a36Sopenharmony_ci return 0; 8162306a36Sopenharmony_ci if (*irq_ptr->dsci) 8262306a36Sopenharmony_ci return 1; 8362306a36Sopenharmony_ci else 8462306a36Sopenharmony_ci return 0; 8562306a36Sopenharmony_ci} 8662306a36Sopenharmony_ci 8762306a36Sopenharmony_cistatic inline u32 clear_shared_ind(void) 8862306a36Sopenharmony_ci{ 8962306a36Sopenharmony_ci if (!atomic_read(&q_indicators[TIQDIO_SHARED_IND].count)) 9062306a36Sopenharmony_ci return 0; 9162306a36Sopenharmony_ci return xchg(&q_indicators[TIQDIO_SHARED_IND].ind, 0); 9262306a36Sopenharmony_ci} 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci/** 9562306a36Sopenharmony_ci * tiqdio_thinint_handler - thin interrupt handler for qdio 9662306a36Sopenharmony_ci * @airq: pointer to adapter interrupt descriptor 9762306a36Sopenharmony_ci * @tpi_info: interrupt information (e.g. floating vs directed -- unused) 9862306a36Sopenharmony_ci */ 9962306a36Sopenharmony_cistatic void tiqdio_thinint_handler(struct airq_struct *airq, 10062306a36Sopenharmony_ci struct tpi_info *tpi_info) 10162306a36Sopenharmony_ci{ 10262306a36Sopenharmony_ci u64 irq_time = S390_lowcore.int_clock; 10362306a36Sopenharmony_ci u32 si_used = clear_shared_ind(); 10462306a36Sopenharmony_ci struct qdio_irq *irq; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_ci last_ai_time = irq_time; 10762306a36Sopenharmony_ci inc_irq_stat(IRQIO_QAI); 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci /* protect tiq_list entries, only changed in activate or shutdown */ 11062306a36Sopenharmony_ci rcu_read_lock(); 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci list_for_each_entry_rcu(irq, &tiq_list, entry) { 11362306a36Sopenharmony_ci /* only process queues from changed sets */ 11462306a36Sopenharmony_ci if (unlikely(references_shared_dsci(irq))) { 11562306a36Sopenharmony_ci if (!si_used) 11662306a36Sopenharmony_ci continue; 11762306a36Sopenharmony_ci } else { 11862306a36Sopenharmony_ci if (!*irq->dsci) 11962306a36Sopenharmony_ci continue; 12062306a36Sopenharmony_ci 12162306a36Sopenharmony_ci xchg(irq->dsci, 0); 12262306a36Sopenharmony_ci } 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci qdio_deliver_irq(irq); 12562306a36Sopenharmony_ci irq->last_data_irq_time = irq_time; 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci QDIO_PERF_STAT_INC(irq, adapter_int); 12862306a36Sopenharmony_ci } 12962306a36Sopenharmony_ci rcu_read_unlock(); 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_cistatic struct airq_struct tiqdio_airq = { 13362306a36Sopenharmony_ci .handler = tiqdio_thinint_handler, 13462306a36Sopenharmony_ci .isc = QDIO_AIRQ_ISC, 13562306a36Sopenharmony_ci}; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_cistatic int set_subchannel_ind(struct qdio_irq *irq_ptr, int reset) 13862306a36Sopenharmony_ci{ 13962306a36Sopenharmony_ci struct chsc_scssc_area *scssc = (void *)irq_ptr->chsc_page; 14062306a36Sopenharmony_ci u64 summary_indicator_addr, subchannel_indicator_addr; 14162306a36Sopenharmony_ci int rc; 14262306a36Sopenharmony_ci 14362306a36Sopenharmony_ci if (reset) { 14462306a36Sopenharmony_ci summary_indicator_addr = 0; 14562306a36Sopenharmony_ci subchannel_indicator_addr = 0; 14662306a36Sopenharmony_ci } else { 14762306a36Sopenharmony_ci summary_indicator_addr = virt_to_phys(tiqdio_airq.lsi_ptr); 14862306a36Sopenharmony_ci subchannel_indicator_addr = virt_to_phys(irq_ptr->dsci); 14962306a36Sopenharmony_ci } 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci rc = chsc_sadc(irq_ptr->schid, scssc, summary_indicator_addr, 15262306a36Sopenharmony_ci subchannel_indicator_addr, tiqdio_airq.isc); 15362306a36Sopenharmony_ci if (rc) { 15462306a36Sopenharmony_ci DBF_ERROR("%4x SSI r:%4x", irq_ptr->schid.sch_no, 15562306a36Sopenharmony_ci scssc->response.code); 15662306a36Sopenharmony_ci goto out; 15762306a36Sopenharmony_ci } 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci DBF_EVENT("setscind"); 16062306a36Sopenharmony_ci DBF_HEX(&summary_indicator_addr, sizeof(summary_indicator_addr)); 16162306a36Sopenharmony_ci DBF_HEX(&subchannel_indicator_addr, sizeof(subchannel_indicator_addr)); 16262306a36Sopenharmony_ciout: 16362306a36Sopenharmony_ci return rc; 16462306a36Sopenharmony_ci} 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ciint qdio_establish_thinint(struct qdio_irq *irq_ptr) 16762306a36Sopenharmony_ci{ 16862306a36Sopenharmony_ci int rc; 16962306a36Sopenharmony_ci 17062306a36Sopenharmony_ci if (!is_thinint_irq(irq_ptr)) 17162306a36Sopenharmony_ci return 0; 17262306a36Sopenharmony_ci 17362306a36Sopenharmony_ci irq_ptr->dsci = get_indicator(); 17462306a36Sopenharmony_ci DBF_HEX(&irq_ptr->dsci, sizeof(void *)); 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci rc = set_subchannel_ind(irq_ptr, 0); 17762306a36Sopenharmony_ci if (rc) { 17862306a36Sopenharmony_ci put_indicator(irq_ptr->dsci); 17962306a36Sopenharmony_ci return rc; 18062306a36Sopenharmony_ci } 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci mutex_lock(&tiq_list_lock); 18362306a36Sopenharmony_ci list_add_rcu(&irq_ptr->entry, &tiq_list); 18462306a36Sopenharmony_ci mutex_unlock(&tiq_list_lock); 18562306a36Sopenharmony_ci return 0; 18662306a36Sopenharmony_ci} 18762306a36Sopenharmony_ci 18862306a36Sopenharmony_civoid qdio_shutdown_thinint(struct qdio_irq *irq_ptr) 18962306a36Sopenharmony_ci{ 19062306a36Sopenharmony_ci if (!is_thinint_irq(irq_ptr)) 19162306a36Sopenharmony_ci return; 19262306a36Sopenharmony_ci 19362306a36Sopenharmony_ci mutex_lock(&tiq_list_lock); 19462306a36Sopenharmony_ci list_del_rcu(&irq_ptr->entry); 19562306a36Sopenharmony_ci mutex_unlock(&tiq_list_lock); 19662306a36Sopenharmony_ci synchronize_rcu(); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci /* reset adapter interrupt indicators */ 19962306a36Sopenharmony_ci set_subchannel_ind(irq_ptr, 1); 20062306a36Sopenharmony_ci put_indicator(irq_ptr->dsci); 20162306a36Sopenharmony_ci} 20262306a36Sopenharmony_ci 20362306a36Sopenharmony_ciint __init qdio_thinint_init(void) 20462306a36Sopenharmony_ci{ 20562306a36Sopenharmony_ci int rc; 20662306a36Sopenharmony_ci 20762306a36Sopenharmony_ci q_indicators = kcalloc(TIQDIO_NR_INDICATORS, sizeof(struct indicator_t), 20862306a36Sopenharmony_ci GFP_KERNEL); 20962306a36Sopenharmony_ci if (!q_indicators) 21062306a36Sopenharmony_ci return -ENOMEM; 21162306a36Sopenharmony_ci 21262306a36Sopenharmony_ci rc = register_adapter_interrupt(&tiqdio_airq); 21362306a36Sopenharmony_ci if (rc) { 21462306a36Sopenharmony_ci DBF_EVENT("RTI:%x", rc); 21562306a36Sopenharmony_ci kfree(q_indicators); 21662306a36Sopenharmony_ci return rc; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci return 0; 21962306a36Sopenharmony_ci} 22062306a36Sopenharmony_ci 22162306a36Sopenharmony_civoid __exit qdio_thinint_exit(void) 22262306a36Sopenharmony_ci{ 22362306a36Sopenharmony_ci WARN_ON(!list_empty(&tiq_list)); 22462306a36Sopenharmony_ci unregister_adapter_interrupt(&tiqdio_airq); 22562306a36Sopenharmony_ci kfree(q_indicators); 22662306a36Sopenharmony_ci} 227