1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0 2779e6e1cSJan Glauber /* 3a53c8fabSHeiko Carstens * Copyright IBM Corp. 2000, 2009 4779e6e1cSJan Glauber * Author(s): Utz Bacher <utz.bacher@de.ibm.com> 5779e6e1cSJan Glauber * Cornelia Huck <cornelia.huck@de.ibm.com> 6779e6e1cSJan Glauber * Jan Glauber <jang@linux.vnet.ibm.com> 7779e6e1cSJan Glauber */ 8779e6e1cSJan Glauber #include <linux/io.h> 95a0e3ad6STejun Heo #include <linux/slab.h> 1030d77c3eSJan Glauber #include <linux/kernel_stat.h> 1160063497SArun Sharma #include <linux/atomic.h> 12b2d09103SIngo Molnar #include <linux/rculist.h> 13b2d09103SIngo Molnar 14779e6e1cSJan Glauber #include <asm/debug.h> 15779e6e1cSJan Glauber #include <asm/qdio.h> 16779e6e1cSJan Glauber #include <asm/airq.h> 17779e6e1cSJan Glauber #include <asm/isc.h> 18779e6e1cSJan Glauber 19779e6e1cSJan Glauber #include "cio.h" 20779e6e1cSJan Glauber #include "ioasm.h" 21779e6e1cSJan Glauber #include "qdio.h" 22779e6e1cSJan Glauber #include "qdio_debug.h" 23779e6e1cSJan Glauber 24779e6e1cSJan Glauber /* 25779e6e1cSJan Glauber * Restriction: only 63 iqdio subchannels would have its own indicator, 26779e6e1cSJan Glauber * after that, subsequent subchannels share one indicator 27779e6e1cSJan Glauber */ 28779e6e1cSJan Glauber #define TIQDIO_NR_NONSHARED_IND 63 29779e6e1cSJan Glauber #define TIQDIO_NR_INDICATORS (TIQDIO_NR_NONSHARED_IND + 1) 305f4026f8SJan Glauber #define TIQDIO_SHARED_IND 63 315f4026f8SJan Glauber 325f4026f8SJan Glauber /* device state change indicators */ 335f4026f8SJan Glauber struct indicator_t { 345f4026f8SJan Glauber u32 ind; /* u32 because of compare-and-swap performance */ 355f4026f8SJan Glauber atomic_t count; /* use count, 0 or 1 for non-shared indicators */ 365f4026f8SJan Glauber }; 37779e6e1cSJan Glauber 38779e6e1cSJan Glauber /* list of thin interrupt input queues */ 39779e6e1cSJan Glauber static LIST_HEAD(tiq_list); 40c4736d96SMartin Schwidefsky static DEFINE_MUTEX(tiq_list_lock); 41779e6e1cSJan Glauber 425f4026f8SJan Glauber static struct indicator_t *q_indicators; 43779e6e1cSJan Glauber 44a2b86019SJan Glauber u64 last_ai_time; 45d36deae7SJan Glauber 46779e6e1cSJan Glauber /* returns addr for the device state change indicator */ 47779e6e1cSJan Glauber static u32 *get_indicator(void) 48779e6e1cSJan Glauber { 49779e6e1cSJan Glauber int i; 50779e6e1cSJan Glauber 51779e6e1cSJan Glauber for (i = 0; i < TIQDIO_NR_NONSHARED_IND; i++) 52648a6f44SSebastian Ott if (!atomic_cmpxchg(&q_indicators[i].count, 0, 1)) 53779e6e1cSJan Glauber return &q_indicators[i].ind; 54779e6e1cSJan Glauber 55779e6e1cSJan Glauber /* use the shared indicator */ 56779e6e1cSJan Glauber atomic_inc(&q_indicators[TIQDIO_SHARED_IND].count); 57779e6e1cSJan Glauber return &q_indicators[TIQDIO_SHARED_IND].ind; 58779e6e1cSJan Glauber } 59779e6e1cSJan Glauber 60779e6e1cSJan Glauber static void put_indicator(u32 *addr) 61779e6e1cSJan Glauber { 6230e8eb86SSebastian Ott struct indicator_t *ind = container_of(addr, struct indicator_t, ind); 63779e6e1cSJan Glauber 64779e6e1cSJan Glauber if (!addr) 65779e6e1cSJan Glauber return; 6630e8eb86SSebastian Ott atomic_dec(&ind->count); 67779e6e1cSJan Glauber } 68779e6e1cSJan Glauber 6994c43bdaSJulian Wiedmann void tiqdio_add_device(struct qdio_irq *irq_ptr) 70779e6e1cSJan Glauber { 71b4547402SJan Glauber mutex_lock(&tiq_list_lock); 7294c43bdaSJulian Wiedmann list_add_rcu(&irq_ptr->entry, &tiq_list); 73b4547402SJan Glauber mutex_unlock(&tiq_list_lock); 74779e6e1cSJan Glauber } 75779e6e1cSJan Glauber 7694c43bdaSJulian Wiedmann void tiqdio_remove_device(struct qdio_irq *irq_ptr) 77779e6e1cSJan Glauber { 78b4547402SJan Glauber mutex_lock(&tiq_list_lock); 7994c43bdaSJulian Wiedmann list_del_rcu(&irq_ptr->entry); 80b4547402SJan Glauber mutex_unlock(&tiq_list_lock); 81779e6e1cSJan Glauber synchronize_rcu(); 8294c43bdaSJulian Wiedmann INIT_LIST_HEAD(&irq_ptr->entry); 83779e6e1cSJan Glauber } 84779e6e1cSJan Glauber 855f4026f8SJan Glauber static inline int has_multiple_inq_on_dsci(struct qdio_irq *irq_ptr) 865f4026f8SJan Glauber { 875f4026f8SJan Glauber return irq_ptr->nr_input_qs > 1; 885f4026f8SJan Glauber } 895f4026f8SJan Glauber 905f4026f8SJan Glauber static inline int references_shared_dsci(struct qdio_irq *irq_ptr) 915f4026f8SJan Glauber { 925f4026f8SJan Glauber return irq_ptr->dsci == &q_indicators[TIQDIO_SHARED_IND].ind; 935f4026f8SJan Glauber } 945f4026f8SJan Glauber 955f4026f8SJan Glauber static inline int shared_ind(struct qdio_irq *irq_ptr) 965f4026f8SJan Glauber { 975f4026f8SJan Glauber return references_shared_dsci(irq_ptr) || 985f4026f8SJan Glauber has_multiple_inq_on_dsci(irq_ptr); 995f4026f8SJan Glauber } 1005f4026f8SJan Glauber 1015f4026f8SJan Glauber void clear_nonshared_ind(struct qdio_irq *irq_ptr) 1025f4026f8SJan Glauber { 1035f4026f8SJan Glauber if (!is_thinint_irq(irq_ptr)) 1045f4026f8SJan Glauber return; 1055f4026f8SJan Glauber if (shared_ind(irq_ptr)) 1065f4026f8SJan Glauber return; 1075f4026f8SJan Glauber xchg(irq_ptr->dsci, 0); 1085f4026f8SJan Glauber } 1095f4026f8SJan Glauber 1105f4026f8SJan Glauber int test_nonshared_ind(struct qdio_irq *irq_ptr) 1115f4026f8SJan Glauber { 1125f4026f8SJan Glauber if (!is_thinint_irq(irq_ptr)) 1135f4026f8SJan Glauber return 0; 1145f4026f8SJan Glauber if (shared_ind(irq_ptr)) 1155f4026f8SJan Glauber return 0; 1165f4026f8SJan Glauber if (*irq_ptr->dsci) 1175f4026f8SJan Glauber return 1; 1185f4026f8SJan Glauber else 1195f4026f8SJan Glauber return 0; 1205f4026f8SJan Glauber } 1215f4026f8SJan Glauber 122b02f0c2eSJan Glauber static inline u32 clear_shared_ind(void) 123779e6e1cSJan Glauber { 124b02f0c2eSJan Glauber if (!atomic_read(&q_indicators[TIQDIO_SHARED_IND].count)) 125b02f0c2eSJan Glauber return 0; 126b02f0c2eSJan Glauber return xchg(&q_indicators[TIQDIO_SHARED_IND].ind, 0); 127779e6e1cSJan Glauber } 128779e6e1cSJan Glauber 129104ea556Sfrank.blaschka@de.ibm.com static inline void tiqdio_call_inq_handlers(struct qdio_irq *irq) 130104ea556Sfrank.blaschka@de.ibm.com { 131104ea556Sfrank.blaschka@de.ibm.com struct qdio_q *q; 132104ea556Sfrank.blaschka@de.ibm.com int i; 133104ea556Sfrank.blaschka@de.ibm.com 134104ea556Sfrank.blaschka@de.ibm.com if (!references_shared_dsci(irq) && 135104ea556Sfrank.blaschka@de.ibm.com has_multiple_inq_on_dsci(irq)) 1361e4a382fSJulian Wiedmann xchg(irq->dsci, 0); 137104ea556Sfrank.blaschka@de.ibm.com 138*0a6e6345SJulian Wiedmann if (irq->irq_poll) { 139*0a6e6345SJulian Wiedmann if (!test_and_set_bit(QDIO_IRQ_DISABLED, &irq->poll_state)) 140*0a6e6345SJulian Wiedmann irq->irq_poll(irq->cdev, irq->int_parm); 141*0a6e6345SJulian Wiedmann else 14246112810SJulian Wiedmann QDIO_PERF_STAT_INC(irq, int_discarded); 143*0a6e6345SJulian Wiedmann 144*0a6e6345SJulian Wiedmann return; 145104ea556Sfrank.blaschka@de.ibm.com } 146104ea556Sfrank.blaschka@de.ibm.com 147*0a6e6345SJulian Wiedmann for_each_input_queue(irq, q, i) { 14867bb323bSJulian Wiedmann if (!shared_ind(irq)) 14967bb323bSJulian Wiedmann xchg(irq->dsci, 0); 150104ea556Sfrank.blaschka@de.ibm.com 151104ea556Sfrank.blaschka@de.ibm.com /* 152104ea556Sfrank.blaschka@de.ibm.com * Call inbound processing but not directly 153104ea556Sfrank.blaschka@de.ibm.com * since that could starve other thinint queues. 154104ea556Sfrank.blaschka@de.ibm.com */ 155104ea556Sfrank.blaschka@de.ibm.com tasklet_schedule(&q->tasklet); 156104ea556Sfrank.blaschka@de.ibm.com } 157104ea556Sfrank.blaschka@de.ibm.com } 158104ea556Sfrank.blaschka@de.ibm.com 159cf9a031cSJan Glauber /** 160cf9a031cSJan Glauber * tiqdio_thinint_handler - thin interrupt handler for qdio 16172a01d0bSJulian Wiedmann * @airq: pointer to adapter interrupt descriptor 16234c636a0SSebastian Ott * @floating: flag to recognize floating vs. directed interrupts (unused) 163cf9a031cSJan Glauber */ 16430e63ef2SSebastian Ott static void tiqdio_thinint_handler(struct airq_struct *airq, bool floating) 165779e6e1cSJan Glauber { 166b02f0c2eSJan Glauber u32 si_used = clear_shared_ind(); 16794c43bdaSJulian Wiedmann struct qdio_irq *irq; 168779e6e1cSJan Glauber 169d36deae7SJan Glauber last_ai_time = S390_lowcore.int_clock; 170420f42ecSHeiko Carstens inc_irq_stat(IRQIO_QAI); 171d36deae7SJan Glauber 172779e6e1cSJan Glauber /* protect tiq_list entries, only changed in activate or shutdown */ 173779e6e1cSJan Glauber rcu_read_lock(); 174779e6e1cSJan Glauber 17594c43bdaSJulian Wiedmann list_for_each_entry_rcu(irq, &tiq_list, entry) { 176d36deae7SJan Glauber /* only process queues from changed sets */ 177104ea556Sfrank.blaschka@de.ibm.com if (unlikely(references_shared_dsci(irq))) { 1784f325184SJan Glauber if (!si_used) 1794f325184SJan Glauber continue; 180104ea556Sfrank.blaschka@de.ibm.com } else if (!*irq->dsci) 181d36deae7SJan Glauber continue; 182d36deae7SJan Glauber 183104ea556Sfrank.blaschka@de.ibm.com tiqdio_call_inq_handlers(irq); 184d36deae7SJan Glauber 18546112810SJulian Wiedmann QDIO_PERF_STAT_INC(irq, adapter_int); 186d36deae7SJan Glauber } 187779e6e1cSJan Glauber rcu_read_unlock(); 188779e6e1cSJan Glauber } 189779e6e1cSJan Glauber 190d86f71fdSJulian Wiedmann static struct airq_struct tiqdio_airq = { 191d86f71fdSJulian Wiedmann .handler = tiqdio_thinint_handler, 192d86f71fdSJulian Wiedmann .isc = QDIO_AIRQ_ISC, 193d86f71fdSJulian Wiedmann }; 194d86f71fdSJulian Wiedmann 195779e6e1cSJan Glauber static int set_subchannel_ind(struct qdio_irq *irq_ptr, int reset) 196779e6e1cSJan Glauber { 197ca4ba153SSebastian Ott struct chsc_scssc_area *scssc = (void *)irq_ptr->chsc_page; 198ca4ba153SSebastian Ott u64 summary_indicator_addr, subchannel_indicator_addr; 199779e6e1cSJan Glauber int rc; 200779e6e1cSJan Glauber 201779e6e1cSJan Glauber if (reset) { 202ca4ba153SSebastian Ott summary_indicator_addr = 0; 203ca4ba153SSebastian Ott subchannel_indicator_addr = 0; 204779e6e1cSJan Glauber } else { 205f4eae94fSMartin Schwidefsky summary_indicator_addr = virt_to_phys(tiqdio_airq.lsi_ptr); 206ca4ba153SSebastian Ott subchannel_indicator_addr = virt_to_phys(irq_ptr->dsci); 207779e6e1cSJan Glauber } 208779e6e1cSJan Glauber 209ca4ba153SSebastian Ott rc = chsc_sadc(irq_ptr->schid, scssc, summary_indicator_addr, 210ca4ba153SSebastian Ott subchannel_indicator_addr); 211779e6e1cSJan Glauber if (rc) { 21222f99347SJan Glauber DBF_ERROR("%4x SSI r:%4x", irq_ptr->schid.sch_no, 213ca4ba153SSebastian Ott scssc->response.code); 214ca4ba153SSebastian Ott goto out; 215779e6e1cSJan Glauber } 216779e6e1cSJan Glauber 21722f99347SJan Glauber DBF_EVENT("setscind"); 218ca4ba153SSebastian Ott DBF_HEX(&summary_indicator_addr, sizeof(summary_indicator_addr)); 219ca4ba153SSebastian Ott DBF_HEX(&subchannel_indicator_addr, sizeof(subchannel_indicator_addr)); 220ca4ba153SSebastian Ott out: 221ca4ba153SSebastian Ott return rc; 222779e6e1cSJan Glauber } 223779e6e1cSJan Glauber 224779e6e1cSJan Glauber /* allocate non-shared indicators and shared indicator */ 225779e6e1cSJan Glauber int __init tiqdio_allocate_memory(void) 226779e6e1cSJan Glauber { 2276396bb22SKees Cook q_indicators = kcalloc(TIQDIO_NR_INDICATORS, 2286396bb22SKees Cook sizeof(struct indicator_t), 229779e6e1cSJan Glauber GFP_KERNEL); 230779e6e1cSJan Glauber if (!q_indicators) 231779e6e1cSJan Glauber return -ENOMEM; 232779e6e1cSJan Glauber return 0; 233779e6e1cSJan Glauber } 234779e6e1cSJan Glauber 235779e6e1cSJan Glauber void tiqdio_free_memory(void) 236779e6e1cSJan Glauber { 237779e6e1cSJan Glauber kfree(q_indicators); 238779e6e1cSJan Glauber } 239779e6e1cSJan Glauber 240779e6e1cSJan Glauber int __init tiqdio_register_thinints(void) 241779e6e1cSJan Glauber { 242f4eae94fSMartin Schwidefsky int rc; 243f4eae94fSMartin Schwidefsky 244f4eae94fSMartin Schwidefsky rc = register_adapter_interrupt(&tiqdio_airq); 245f4eae94fSMartin Schwidefsky if (rc) { 246f4eae94fSMartin Schwidefsky DBF_EVENT("RTI:%x", rc); 247f4eae94fSMartin Schwidefsky return rc; 248779e6e1cSJan Glauber } 249779e6e1cSJan Glauber return 0; 250779e6e1cSJan Glauber } 251779e6e1cSJan Glauber 252779e6e1cSJan Glauber int qdio_establish_thinint(struct qdio_irq *irq_ptr) 253779e6e1cSJan Glauber { 254779e6e1cSJan Glauber if (!is_thinint_irq(irq_ptr)) 255779e6e1cSJan Glauber return 0; 256779e6e1cSJan Glauber return set_subchannel_ind(irq_ptr, 0); 257779e6e1cSJan Glauber } 258779e6e1cSJan Glauber 259779e6e1cSJan Glauber void qdio_setup_thinint(struct qdio_irq *irq_ptr) 260779e6e1cSJan Glauber { 261779e6e1cSJan Glauber if (!is_thinint_irq(irq_ptr)) 262779e6e1cSJan Glauber return; 263779e6e1cSJan Glauber irq_ptr->dsci = get_indicator(); 26422f99347SJan Glauber DBF_HEX(&irq_ptr->dsci, sizeof(void *)); 265779e6e1cSJan Glauber } 266779e6e1cSJan Glauber 267779e6e1cSJan Glauber void qdio_shutdown_thinint(struct qdio_irq *irq_ptr) 268779e6e1cSJan Glauber { 269779e6e1cSJan Glauber if (!is_thinint_irq(irq_ptr)) 270779e6e1cSJan Glauber return; 271779e6e1cSJan Glauber 272779e6e1cSJan Glauber /* reset adapter interrupt indicators */ 273779e6e1cSJan Glauber set_subchannel_ind(irq_ptr, 1); 2744814a2b3SJan Glauber put_indicator(irq_ptr->dsci); 275779e6e1cSJan Glauber } 276779e6e1cSJan Glauber 277779e6e1cSJan Glauber void __exit tiqdio_unregister_thinints(void) 278779e6e1cSJan Glauber { 2799e890ad8SJan Glauber WARN_ON(!list_empty(&tiq_list)); 280f4eae94fSMartin Schwidefsky unregister_adapter_interrupt(&tiqdio_airq); 281779e6e1cSJan Glauber } 282