linux/drivers/s390/cio/qdio_thinint.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Copyright IBM Corp. 2000, 2009
   4 * Author(s): Utz Bacher <utz.bacher@de.ibm.com>
   5 *            Cornelia Huck <cornelia.huck@de.ibm.com>
   6 *            Jan Glauber <jang@linux.vnet.ibm.com>
   7 */
   8#include <linux/io.h>
   9#include <linux/slab.h>
  10#include <linux/kernel_stat.h>
  11#include <linux/atomic.h>
  12#include <linux/rculist.h>
  13
  14#include <asm/debug.h>
  15#include <asm/qdio.h>
  16#include <asm/airq.h>
  17#include <asm/isc.h>
  18
  19#include "cio.h"
  20#include "ioasm.h"
  21#include "qdio.h"
  22#include "qdio_debug.h"
  23
  24/*
  25 * Restriction: only 63 iqdio subchannels would have its own indicator,
  26 * after that, subsequent subchannels share one indicator
  27 */
  28#define TIQDIO_NR_NONSHARED_IND         63
  29#define TIQDIO_NR_INDICATORS            (TIQDIO_NR_NONSHARED_IND + 1)
  30#define TIQDIO_SHARED_IND               63
  31
  32/* device state change indicators */
  33struct indicator_t {
  34        u32 ind;        /* u32 because of compare-and-swap performance */
  35        atomic_t count; /* use count, 0 or 1 for non-shared indicators */
  36};
  37
  38/* list of thin interrupt input queues */
  39static LIST_HEAD(tiq_list);
  40static DEFINE_MUTEX(tiq_list_lock);
  41
  42static struct indicator_t *q_indicators;
  43
  44u64 last_ai_time;
  45
  46/* returns addr for the device state change indicator */
  47static u32 *get_indicator(void)
  48{
  49        int i;
  50
  51        for (i = 0; i < TIQDIO_NR_NONSHARED_IND; i++)
  52                if (!atomic_cmpxchg(&q_indicators[i].count, 0, 1))
  53                        return &q_indicators[i].ind;
  54
  55        /* use the shared indicator */
  56        atomic_inc(&q_indicators[TIQDIO_SHARED_IND].count);
  57        return &q_indicators[TIQDIO_SHARED_IND].ind;
  58}
  59
  60static void put_indicator(u32 *addr)
  61{
  62        struct indicator_t *ind = container_of(addr, struct indicator_t, ind);
  63
  64        if (!addr)
  65                return;
  66        atomic_dec(&ind->count);
  67}
  68
  69void tiqdio_add_device(struct qdio_irq *irq_ptr)
  70{
  71        mutex_lock(&tiq_list_lock);
  72        list_add_rcu(&irq_ptr->entry, &tiq_list);
  73        mutex_unlock(&tiq_list_lock);
  74}
  75
  76void tiqdio_remove_device(struct qdio_irq *irq_ptr)
  77{
  78        mutex_lock(&tiq_list_lock);
  79        list_del_rcu(&irq_ptr->entry);
  80        mutex_unlock(&tiq_list_lock);
  81        synchronize_rcu();
  82        INIT_LIST_HEAD(&irq_ptr->entry);
  83}
  84
  85static inline int references_shared_dsci(struct qdio_irq *irq_ptr)
  86{
  87        return irq_ptr->dsci == &q_indicators[TIQDIO_SHARED_IND].ind;
  88}
  89
  90int test_nonshared_ind(struct qdio_irq *irq_ptr)
  91{
  92        if (!is_thinint_irq(irq_ptr))
  93                return 0;
  94        if (references_shared_dsci(irq_ptr))
  95                return 0;
  96        if (*irq_ptr->dsci)
  97                return 1;
  98        else
  99                return 0;
 100}
 101
 102static inline u32 clear_shared_ind(void)
 103{
 104        if (!atomic_read(&q_indicators[TIQDIO_SHARED_IND].count))
 105                return 0;
 106        return xchg(&q_indicators[TIQDIO_SHARED_IND].ind, 0);
 107}
 108
 109static inline void tiqdio_call_inq_handlers(struct qdio_irq *irq)
 110{
 111        struct qdio_q *q;
 112        int i;
 113
 114        if (!references_shared_dsci(irq))
 115                xchg(irq->dsci, 0);
 116
 117        if (irq->irq_poll) {
 118                if (!test_and_set_bit(QDIO_IRQ_DISABLED, &irq->poll_state))
 119                        irq->irq_poll(irq->cdev, irq->int_parm);
 120                else
 121                        QDIO_PERF_STAT_INC(irq, int_discarded);
 122
 123                return;
 124        }
 125
 126        for_each_input_queue(irq, q, i) {
 127                /*
 128                 * Call inbound processing but not directly
 129                 * since that could starve other thinint queues.
 130                 */
 131                tasklet_schedule(&q->tasklet);
 132        }
 133}
 134
 135/**
 136 * tiqdio_thinint_handler - thin interrupt handler for qdio
 137 * @airq: pointer to adapter interrupt descriptor
 138 * @floating: flag to recognize floating vs. directed interrupts (unused)
 139 */
 140static void tiqdio_thinint_handler(struct airq_struct *airq, bool floating)
 141{
 142        u32 si_used = clear_shared_ind();
 143        struct qdio_irq *irq;
 144
 145        last_ai_time = S390_lowcore.int_clock;
 146        inc_irq_stat(IRQIO_QAI);
 147
 148        /* protect tiq_list entries, only changed in activate or shutdown */
 149        rcu_read_lock();
 150
 151        list_for_each_entry_rcu(irq, &tiq_list, entry) {
 152                /* only process queues from changed sets */
 153                if (unlikely(references_shared_dsci(irq))) {
 154                        if (!si_used)
 155                                continue;
 156                } else if (!*irq->dsci)
 157                        continue;
 158
 159                tiqdio_call_inq_handlers(irq);
 160
 161                QDIO_PERF_STAT_INC(irq, adapter_int);
 162        }
 163        rcu_read_unlock();
 164}
 165
 166static struct airq_struct tiqdio_airq = {
 167        .handler = tiqdio_thinint_handler,
 168        .isc = QDIO_AIRQ_ISC,
 169};
 170
 171static int set_subchannel_ind(struct qdio_irq *irq_ptr, int reset)
 172{
 173        struct chsc_scssc_area *scssc = (void *)irq_ptr->chsc_page;
 174        u64 summary_indicator_addr, subchannel_indicator_addr;
 175        int rc;
 176
 177        if (reset) {
 178                summary_indicator_addr = 0;
 179                subchannel_indicator_addr = 0;
 180        } else {
 181                summary_indicator_addr = virt_to_phys(tiqdio_airq.lsi_ptr);
 182                subchannel_indicator_addr = virt_to_phys(irq_ptr->dsci);
 183        }
 184
 185        rc = chsc_sadc(irq_ptr->schid, scssc, summary_indicator_addr,
 186                       subchannel_indicator_addr, tiqdio_airq.isc);
 187        if (rc) {
 188                DBF_ERROR("%4x SSI r:%4x", irq_ptr->schid.sch_no,
 189                          scssc->response.code);
 190                goto out;
 191        }
 192
 193        DBF_EVENT("setscind");
 194        DBF_HEX(&summary_indicator_addr, sizeof(summary_indicator_addr));
 195        DBF_HEX(&subchannel_indicator_addr, sizeof(subchannel_indicator_addr));
 196out:
 197        return rc;
 198}
 199
 200int qdio_establish_thinint(struct qdio_irq *irq_ptr)
 201{
 202        int rc;
 203
 204        if (!is_thinint_irq(irq_ptr))
 205                return 0;
 206
 207        irq_ptr->dsci = get_indicator();
 208        DBF_HEX(&irq_ptr->dsci, sizeof(void *));
 209
 210        rc = set_subchannel_ind(irq_ptr, 0);
 211        if (rc)
 212                put_indicator(irq_ptr->dsci);
 213
 214        return rc;
 215}
 216
 217void qdio_shutdown_thinint(struct qdio_irq *irq_ptr)
 218{
 219        if (!is_thinint_irq(irq_ptr))
 220                return;
 221
 222        /* reset adapter interrupt indicators */
 223        set_subchannel_ind(irq_ptr, 1);
 224        put_indicator(irq_ptr->dsci);
 225}
 226
 227int __init qdio_thinint_init(void)
 228{
 229        int rc;
 230
 231        q_indicators = kcalloc(TIQDIO_NR_INDICATORS, sizeof(struct indicator_t),
 232                               GFP_KERNEL);
 233        if (!q_indicators)
 234                return -ENOMEM;
 235
 236        rc = register_adapter_interrupt(&tiqdio_airq);
 237        if (rc) {
 238                DBF_EVENT("RTI:%x", rc);
 239                kfree(q_indicators);
 240                return rc;
 241        }
 242        return 0;
 243}
 244
 245void __exit qdio_thinint_exit(void)
 246{
 247        WARN_ON(!list_empty(&tiq_list));
 248        unregister_adapter_interrupt(&tiqdio_airq);
 249        kfree(q_indicators);
 250}
 251