1779e6e1cSJan Glauber /* 2779e6e1cSJan Glauber * linux/drivers/s390/cio/thinint_qdio.c 3779e6e1cSJan Glauber * 46486cda6SJan Glauber * Copyright 2000,2009 IBM Corp. 5779e6e1cSJan Glauber * Author(s): Utz Bacher <utz.bacher@de.ibm.com> 6779e6e1cSJan Glauber * Cornelia Huck <cornelia.huck@de.ibm.com> 7779e6e1cSJan Glauber * Jan Glauber <jang@linux.vnet.ibm.com> 8779e6e1cSJan Glauber */ 9779e6e1cSJan Glauber #include <linux/io.h> 105a0e3ad6STejun Heo #include <linux/slab.h> 1130d77c3eSJan Glauber #include <linux/kernel_stat.h> 1260063497SArun Sharma #include <linux/atomic.h> 13779e6e1cSJan Glauber #include <asm/debug.h> 14779e6e1cSJan Glauber #include <asm/qdio.h> 15779e6e1cSJan Glauber #include <asm/airq.h> 16779e6e1cSJan Glauber #include <asm/isc.h> 17779e6e1cSJan Glauber 18779e6e1cSJan Glauber #include "cio.h" 19779e6e1cSJan Glauber #include "ioasm.h" 20779e6e1cSJan Glauber #include "qdio.h" 21779e6e1cSJan Glauber #include "qdio_debug.h" 22779e6e1cSJan Glauber 23779e6e1cSJan Glauber /* 24779e6e1cSJan Glauber * Restriction: only 63 iqdio subchannels would have its own indicator, 25779e6e1cSJan Glauber * after that, subsequent subchannels share one indicator 26779e6e1cSJan Glauber */ 27779e6e1cSJan Glauber #define TIQDIO_NR_NONSHARED_IND 63 28779e6e1cSJan Glauber #define TIQDIO_NR_INDICATORS (TIQDIO_NR_NONSHARED_IND + 1) 29779e6e1cSJan Glauber 30779e6e1cSJan Glauber /* list of thin interrupt input queues */ 31779e6e1cSJan Glauber static LIST_HEAD(tiq_list); 32b4547402SJan Glauber DEFINE_MUTEX(tiq_list_lock); 33779e6e1cSJan Glauber 34779e6e1cSJan Glauber /* adapter local summary indicator */ 35d36deae7SJan Glauber static u8 *tiqdio_alsi; 36779e6e1cSJan Glauber 37d36deae7SJan Glauber struct indicator_t *q_indicators; 38779e6e1cSJan Glauber 39*a2b86019SJan Glauber u64 last_ai_time; 40d36deae7SJan Glauber 41779e6e1cSJan Glauber /* returns addr for the device state change indicator */ 42779e6e1cSJan Glauber static u32 *get_indicator(void) 43779e6e1cSJan Glauber { 44779e6e1cSJan Glauber int i; 45779e6e1cSJan Glauber 46779e6e1cSJan Glauber for (i = 0; i < TIQDIO_NR_NONSHARED_IND; i++) 47779e6e1cSJan Glauber if (!atomic_read(&q_indicators[i].count)) { 48779e6e1cSJan Glauber atomic_set(&q_indicators[i].count, 1); 49779e6e1cSJan Glauber return &q_indicators[i].ind; 50779e6e1cSJan Glauber } 51779e6e1cSJan Glauber 52779e6e1cSJan Glauber /* use the shared indicator */ 53779e6e1cSJan Glauber atomic_inc(&q_indicators[TIQDIO_SHARED_IND].count); 54779e6e1cSJan Glauber return &q_indicators[TIQDIO_SHARED_IND].ind; 55779e6e1cSJan Glauber } 56779e6e1cSJan Glauber 57779e6e1cSJan Glauber static void put_indicator(u32 *addr) 58779e6e1cSJan Glauber { 59779e6e1cSJan Glauber int i; 60779e6e1cSJan Glauber 61779e6e1cSJan Glauber if (!addr) 62779e6e1cSJan Glauber return; 63779e6e1cSJan Glauber i = ((unsigned long)addr - (unsigned long)q_indicators) / 64779e6e1cSJan Glauber sizeof(struct indicator_t); 65779e6e1cSJan Glauber atomic_dec(&q_indicators[i].count); 66779e6e1cSJan Glauber } 67779e6e1cSJan Glauber 68779e6e1cSJan Glauber void tiqdio_add_input_queues(struct qdio_irq *irq_ptr) 69779e6e1cSJan Glauber { 70b4547402SJan Glauber mutex_lock(&tiq_list_lock); 71104ea556Sfrank.blaschka@de.ibm.com BUG_ON(irq_ptr->nr_input_qs < 1); 72104ea556Sfrank.blaschka@de.ibm.com list_add_rcu(&irq_ptr->input_qs[0]->entry, &tiq_list); 73b4547402SJan Glauber mutex_unlock(&tiq_list_lock); 74d0c9d4a8SJan Glauber xchg(irq_ptr->dsci, 1 << 7); 75779e6e1cSJan Glauber } 76779e6e1cSJan Glauber 77779e6e1cSJan Glauber void tiqdio_remove_input_queues(struct qdio_irq *irq_ptr) 78779e6e1cSJan Glauber { 79779e6e1cSJan Glauber struct qdio_q *q; 80779e6e1cSJan Glauber 81104ea556Sfrank.blaschka@de.ibm.com BUG_ON(irq_ptr->nr_input_qs < 1); 82104ea556Sfrank.blaschka@de.ibm.com q = irq_ptr->input_qs[0]; 8353b41ba7SJan Glauber /* if establish triggered an error */ 8453b41ba7SJan Glauber if (!q || !q->entry.prev || !q->entry.next) 85104ea556Sfrank.blaschka@de.ibm.com return; 86b4547402SJan Glauber 87b4547402SJan Glauber mutex_lock(&tiq_list_lock); 88779e6e1cSJan Glauber list_del_rcu(&q->entry); 89b4547402SJan Glauber mutex_unlock(&tiq_list_lock); 90779e6e1cSJan Glauber synchronize_rcu(); 91779e6e1cSJan Glauber } 92779e6e1cSJan Glauber 93b02f0c2eSJan Glauber static inline u32 clear_shared_ind(void) 94779e6e1cSJan Glauber { 95b02f0c2eSJan Glauber if (!atomic_read(&q_indicators[TIQDIO_SHARED_IND].count)) 96b02f0c2eSJan Glauber return 0; 97b02f0c2eSJan Glauber return xchg(&q_indicators[TIQDIO_SHARED_IND].ind, 0); 98779e6e1cSJan Glauber } 99779e6e1cSJan Glauber 100104ea556Sfrank.blaschka@de.ibm.com static inline void tiqdio_call_inq_handlers(struct qdio_irq *irq) 101104ea556Sfrank.blaschka@de.ibm.com { 102104ea556Sfrank.blaschka@de.ibm.com struct qdio_q *q; 103104ea556Sfrank.blaschka@de.ibm.com int i; 104104ea556Sfrank.blaschka@de.ibm.com 105104ea556Sfrank.blaschka@de.ibm.com for_each_input_queue(irq, q, i) { 106104ea556Sfrank.blaschka@de.ibm.com if (!references_shared_dsci(irq) && 107104ea556Sfrank.blaschka@de.ibm.com has_multiple_inq_on_dsci(irq)) 108104ea556Sfrank.blaschka@de.ibm.com xchg(q->irq_ptr->dsci, 0); 109104ea556Sfrank.blaschka@de.ibm.com 110104ea556Sfrank.blaschka@de.ibm.com if (q->u.in.queue_start_poll) { 111104ea556Sfrank.blaschka@de.ibm.com /* skip if polling is enabled or already in work */ 112104ea556Sfrank.blaschka@de.ibm.com if (test_and_set_bit(QDIO_QUEUE_IRQS_DISABLED, 113104ea556Sfrank.blaschka@de.ibm.com &q->u.in.queue_irq_state)) { 114104ea556Sfrank.blaschka@de.ibm.com qperf_inc(q, int_discarded); 115104ea556Sfrank.blaschka@de.ibm.com continue; 116104ea556Sfrank.blaschka@de.ibm.com } 117104ea556Sfrank.blaschka@de.ibm.com 118104ea556Sfrank.blaschka@de.ibm.com /* avoid dsci clear here, done after processing */ 119104ea556Sfrank.blaschka@de.ibm.com q->u.in.queue_start_poll(q->irq_ptr->cdev, q->nr, 120104ea556Sfrank.blaschka@de.ibm.com q->irq_ptr->int_parm); 121104ea556Sfrank.blaschka@de.ibm.com } else { 122104ea556Sfrank.blaschka@de.ibm.com if (!shared_ind(q)) 123104ea556Sfrank.blaschka@de.ibm.com xchg(q->irq_ptr->dsci, 0); 124104ea556Sfrank.blaschka@de.ibm.com 125104ea556Sfrank.blaschka@de.ibm.com /* 126104ea556Sfrank.blaschka@de.ibm.com * Call inbound processing but not directly 127104ea556Sfrank.blaschka@de.ibm.com * since that could starve other thinint queues. 128104ea556Sfrank.blaschka@de.ibm.com */ 129104ea556Sfrank.blaschka@de.ibm.com tasklet_schedule(&q->tasklet); 130104ea556Sfrank.blaschka@de.ibm.com } 131104ea556Sfrank.blaschka@de.ibm.com } 132104ea556Sfrank.blaschka@de.ibm.com } 133104ea556Sfrank.blaschka@de.ibm.com 134cf9a031cSJan Glauber /** 135cf9a031cSJan Glauber * tiqdio_thinint_handler - thin interrupt handler for qdio 136d36deae7SJan Glauber * @alsi: pointer to adapter local summary indicator 137d36deae7SJan Glauber * @data: NULL 138cf9a031cSJan Glauber */ 139d36deae7SJan Glauber static void tiqdio_thinint_handler(void *alsi, void *data) 140779e6e1cSJan Glauber { 141b02f0c2eSJan Glauber u32 si_used = clear_shared_ind(); 142779e6e1cSJan Glauber struct qdio_q *q; 143779e6e1cSJan Glauber 144d36deae7SJan Glauber last_ai_time = S390_lowcore.int_clock; 14530d77c3eSJan Glauber kstat_cpu(smp_processor_id()).irqs[IOINT_QAI]++; 146d36deae7SJan Glauber 147779e6e1cSJan Glauber /* protect tiq_list entries, only changed in activate or shutdown */ 148779e6e1cSJan Glauber rcu_read_lock(); 149779e6e1cSJan Glauber 150cf9a031cSJan Glauber /* check for work on all inbound thinint queues */ 151d36deae7SJan Glauber list_for_each_entry_rcu(q, &tiq_list, entry) { 152104ea556Sfrank.blaschka@de.ibm.com struct qdio_irq *irq; 153779e6e1cSJan Glauber 154d36deae7SJan Glauber /* only process queues from changed sets */ 155104ea556Sfrank.blaschka@de.ibm.com irq = q->irq_ptr; 156104ea556Sfrank.blaschka@de.ibm.com if (unlikely(references_shared_dsci(irq))) { 1574f325184SJan Glauber if (!si_used) 1584f325184SJan Glauber continue; 159104ea556Sfrank.blaschka@de.ibm.com } else if (!*irq->dsci) 160d36deae7SJan Glauber continue; 161d36deae7SJan Glauber 162104ea556Sfrank.blaschka@de.ibm.com tiqdio_call_inq_handlers(irq); 163d36deae7SJan Glauber 164d36deae7SJan Glauber qperf_inc(q, adapter_int); 165d36deae7SJan Glauber } 166779e6e1cSJan Glauber rcu_read_unlock(); 167779e6e1cSJan Glauber } 168779e6e1cSJan Glauber 169779e6e1cSJan Glauber static int set_subchannel_ind(struct qdio_irq *irq_ptr, int reset) 170779e6e1cSJan Glauber { 171779e6e1cSJan Glauber struct scssc_area *scssc_area; 172779e6e1cSJan Glauber int rc; 173779e6e1cSJan Glauber 174779e6e1cSJan Glauber scssc_area = (struct scssc_area *)irq_ptr->chsc_page; 175779e6e1cSJan Glauber memset(scssc_area, 0, PAGE_SIZE); 176779e6e1cSJan Glauber 177779e6e1cSJan Glauber if (reset) { 178779e6e1cSJan Glauber scssc_area->summary_indicator_addr = 0; 179779e6e1cSJan Glauber scssc_area->subchannel_indicator_addr = 0; 180779e6e1cSJan Glauber } else { 181779e6e1cSJan Glauber scssc_area->summary_indicator_addr = virt_to_phys(tiqdio_alsi); 182779e6e1cSJan Glauber scssc_area->subchannel_indicator_addr = 183779e6e1cSJan Glauber virt_to_phys(irq_ptr->dsci); 184779e6e1cSJan Glauber } 185779e6e1cSJan Glauber 186779e6e1cSJan Glauber scssc_area->request = (struct chsc_header) { 187779e6e1cSJan Glauber .length = 0x0fe0, 188779e6e1cSJan Glauber .code = 0x0021, 189779e6e1cSJan Glauber }; 190779e6e1cSJan Glauber scssc_area->operation_code = 0; 191d1bf8590SHeiko Carstens scssc_area->ks = PAGE_DEFAULT_KEY >> 4; 192d1bf8590SHeiko Carstens scssc_area->kc = PAGE_DEFAULT_KEY >> 4; 193779e6e1cSJan Glauber scssc_area->isc = QDIO_AIRQ_ISC; 194779e6e1cSJan Glauber scssc_area->schid = irq_ptr->schid; 195779e6e1cSJan Glauber 196779e6e1cSJan Glauber /* enable the time delay disablement facility */ 197779e6e1cSJan Glauber if (css_general_characteristics.aif_tdd) 198779e6e1cSJan Glauber scssc_area->word_with_d_bit = 0x10000000; 199779e6e1cSJan Glauber 200779e6e1cSJan Glauber rc = chsc(scssc_area); 201779e6e1cSJan Glauber if (rc) 202779e6e1cSJan Glauber return -EIO; 203779e6e1cSJan Glauber 204779e6e1cSJan Glauber rc = chsc_error_from_response(scssc_area->response.code); 205779e6e1cSJan Glauber if (rc) { 20622f99347SJan Glauber DBF_ERROR("%4x SSI r:%4x", irq_ptr->schid.sch_no, 20722f99347SJan Glauber scssc_area->response.code); 20822f99347SJan Glauber DBF_ERROR_HEX(&scssc_area->response, sizeof(void *)); 209779e6e1cSJan Glauber return rc; 210779e6e1cSJan Glauber } 211779e6e1cSJan Glauber 21222f99347SJan Glauber DBF_EVENT("setscind"); 21322f99347SJan Glauber DBF_HEX(&scssc_area->summary_indicator_addr, sizeof(unsigned long)); 21422f99347SJan Glauber DBF_HEX(&scssc_area->subchannel_indicator_addr, sizeof(unsigned long)); 215779e6e1cSJan Glauber return 0; 216779e6e1cSJan Glauber } 217779e6e1cSJan Glauber 218779e6e1cSJan Glauber /* allocate non-shared indicators and shared indicator */ 219779e6e1cSJan Glauber int __init tiqdio_allocate_memory(void) 220779e6e1cSJan Glauber { 221779e6e1cSJan Glauber q_indicators = kzalloc(sizeof(struct indicator_t) * TIQDIO_NR_INDICATORS, 222779e6e1cSJan Glauber GFP_KERNEL); 223779e6e1cSJan Glauber if (!q_indicators) 224779e6e1cSJan Glauber return -ENOMEM; 225779e6e1cSJan Glauber return 0; 226779e6e1cSJan Glauber } 227779e6e1cSJan Glauber 228779e6e1cSJan Glauber void tiqdio_free_memory(void) 229779e6e1cSJan Glauber { 230779e6e1cSJan Glauber kfree(q_indicators); 231779e6e1cSJan Glauber } 232779e6e1cSJan Glauber 233779e6e1cSJan Glauber int __init tiqdio_register_thinints(void) 234779e6e1cSJan Glauber { 235779e6e1cSJan Glauber isc_register(QDIO_AIRQ_ISC); 236779e6e1cSJan Glauber tiqdio_alsi = s390_register_adapter_interrupt(&tiqdio_thinint_handler, 237779e6e1cSJan Glauber NULL, QDIO_AIRQ_ISC); 238779e6e1cSJan Glauber if (IS_ERR(tiqdio_alsi)) { 23922f99347SJan Glauber DBF_EVENT("RTI:%lx", PTR_ERR(tiqdio_alsi)); 240779e6e1cSJan Glauber tiqdio_alsi = NULL; 241779e6e1cSJan Glauber isc_unregister(QDIO_AIRQ_ISC); 242779e6e1cSJan Glauber return -ENOMEM; 243779e6e1cSJan Glauber } 244779e6e1cSJan Glauber return 0; 245779e6e1cSJan Glauber } 246779e6e1cSJan Glauber 247779e6e1cSJan Glauber int qdio_establish_thinint(struct qdio_irq *irq_ptr) 248779e6e1cSJan Glauber { 249779e6e1cSJan Glauber if (!is_thinint_irq(irq_ptr)) 250779e6e1cSJan Glauber return 0; 251779e6e1cSJan Glauber return set_subchannel_ind(irq_ptr, 0); 252779e6e1cSJan Glauber } 253779e6e1cSJan Glauber 254779e6e1cSJan Glauber void qdio_setup_thinint(struct qdio_irq *irq_ptr) 255779e6e1cSJan Glauber { 256779e6e1cSJan Glauber if (!is_thinint_irq(irq_ptr)) 257779e6e1cSJan Glauber return; 258779e6e1cSJan Glauber irq_ptr->dsci = get_indicator(); 25922f99347SJan Glauber DBF_HEX(&irq_ptr->dsci, sizeof(void *)); 260779e6e1cSJan Glauber } 261779e6e1cSJan Glauber 262779e6e1cSJan Glauber void qdio_shutdown_thinint(struct qdio_irq *irq_ptr) 263779e6e1cSJan Glauber { 264779e6e1cSJan Glauber if (!is_thinint_irq(irq_ptr)) 265779e6e1cSJan Glauber return; 266779e6e1cSJan Glauber 267779e6e1cSJan Glauber /* reset adapter interrupt indicators */ 268779e6e1cSJan Glauber set_subchannel_ind(irq_ptr, 1); 2694814a2b3SJan Glauber put_indicator(irq_ptr->dsci); 270779e6e1cSJan Glauber } 271779e6e1cSJan Glauber 272779e6e1cSJan Glauber void __exit tiqdio_unregister_thinints(void) 273779e6e1cSJan Glauber { 2749e890ad8SJan Glauber WARN_ON(!list_empty(&tiq_list)); 275779e6e1cSJan Glauber 276779e6e1cSJan Glauber if (tiqdio_alsi) { 277779e6e1cSJan Glauber s390_unregister_adapter_interrupt(tiqdio_alsi, QDIO_AIRQ_ISC); 278779e6e1cSJan Glauber isc_unregister(QDIO_AIRQ_ISC); 279779e6e1cSJan Glauber } 280779e6e1cSJan Glauber } 281