linux/drivers/s390/cio/qdio_thinint.c
<<
>>
Prefs
   1/*
   2 * linux/drivers/s390/cio/thinint_qdio.c
   3 *
   4 * Copyright 2000,2009 IBM Corp.
   5 * Author(s): Utz Bacher <utz.bacher@de.ibm.com>
   6 *            Cornelia Huck <cornelia.huck@de.ibm.com>
   7 *            Jan Glauber <jang@linux.vnet.ibm.com>
   8 */
   9#include <linux/io.h>
  10#include <linux/slab.h>
  11#include <linux/kernel_stat.h>
  12#include <asm/atomic.h>
  13#include <asm/debug.h>
  14#include <asm/qdio.h>
  15#include <asm/airq.h>
  16#include <asm/isc.h>
  17
  18#include "cio.h"
  19#include "ioasm.h"
  20#include "qdio.h"
  21#include "qdio_debug.h"
  22
  23/*
  24 * Restriction: only 63 iqdio subchannels would have its own indicator,
  25 * after that, subsequent subchannels share one indicator
  26 */
  27#define TIQDIO_NR_NONSHARED_IND         63
  28#define TIQDIO_NR_INDICATORS            (TIQDIO_NR_NONSHARED_IND + 1)
  29
  30/* list of thin interrupt input queues */
  31static LIST_HEAD(tiq_list);
  32DEFINE_MUTEX(tiq_list_lock);
  33
  34/* adapter local summary indicator */
  35static u8 *tiqdio_alsi;
  36
  37struct indicator_t *q_indicators;
  38
  39static u64 last_ai_time;
  40
  41/* returns addr for the device state change indicator */
  42static u32 *get_indicator(void)
  43{
  44        int i;
  45
  46        for (i = 0; i < TIQDIO_NR_NONSHARED_IND; i++)
  47                if (!atomic_read(&q_indicators[i].count)) {
  48                        atomic_set(&q_indicators[i].count, 1);
  49                        return &q_indicators[i].ind;
  50                }
  51
  52        /* use the shared indicator */
  53        atomic_inc(&q_indicators[TIQDIO_SHARED_IND].count);
  54        return &q_indicators[TIQDIO_SHARED_IND].ind;
  55}
  56
  57static void put_indicator(u32 *addr)
  58{
  59        int i;
  60
  61        if (!addr)
  62                return;
  63        i = ((unsigned long)addr - (unsigned long)q_indicators) /
  64                sizeof(struct indicator_t);
  65        atomic_dec(&q_indicators[i].count);
  66}
  67
  68void tiqdio_add_input_queues(struct qdio_irq *irq_ptr)
  69{
  70        struct qdio_q *q;
  71        int i;
  72
  73        mutex_lock(&tiq_list_lock);
  74        for_each_input_queue(irq_ptr, q, i)
  75                list_add_rcu(&q->entry, &tiq_list);
  76        mutex_unlock(&tiq_list_lock);
  77        xchg(irq_ptr->dsci, 1 << 7);
  78}
  79
  80void tiqdio_remove_input_queues(struct qdio_irq *irq_ptr)
  81{
  82        struct qdio_q *q;
  83        int i;
  84
  85        for (i = 0; i < irq_ptr->nr_input_qs; i++) {
  86                q = irq_ptr->input_qs[i];
  87                /* if establish triggered an error */
  88                if (!q || !q->entry.prev || !q->entry.next)
  89                        continue;
  90
  91                mutex_lock(&tiq_list_lock);
  92                list_del_rcu(&q->entry);
  93                mutex_unlock(&tiq_list_lock);
  94                synchronize_rcu();
  95        }
  96}
  97
  98static inline u32 shared_ind_set(void)
  99{
 100        return q_indicators[TIQDIO_SHARED_IND].ind;
 101}
 102
 103/**
 104 * tiqdio_thinint_handler - thin interrupt handler for qdio
 105 * @alsi: pointer to adapter local summary indicator
 106 * @data: NULL
 107 */
 108static void tiqdio_thinint_handler(void *alsi, void *data)
 109{
 110        u32 si_used = shared_ind_set();
 111        struct qdio_q *q;
 112
 113        last_ai_time = S390_lowcore.int_clock;
 114        kstat_cpu(smp_processor_id()).irqs[IOINT_QAI]++;
 115
 116        /* protect tiq_list entries, only changed in activate or shutdown */
 117        rcu_read_lock();
 118
 119        /* check for work on all inbound thinint queues */
 120        list_for_each_entry_rcu(q, &tiq_list, entry) {
 121
 122                /* only process queues from changed sets */
 123                if (unlikely(shared_ind(q->irq_ptr->dsci))) {
 124                        if (!si_used)
 125                                continue;
 126                } else if (!*q->irq_ptr->dsci)
 127                        continue;
 128
 129                if (q->u.in.queue_start_poll) {
 130                        /* skip if polling is enabled or already in work */
 131                        if (test_and_set_bit(QDIO_QUEUE_IRQS_DISABLED,
 132                                             &q->u.in.queue_irq_state)) {
 133                                qperf_inc(q, int_discarded);
 134                                continue;
 135                        }
 136
 137                        /* avoid dsci clear here, done after processing */
 138                        q->u.in.queue_start_poll(q->irq_ptr->cdev, q->nr,
 139                                                 q->irq_ptr->int_parm);
 140                } else {
 141                        /* only clear it if the indicator is non-shared */
 142                        if (!shared_ind(q->irq_ptr->dsci))
 143                                xchg(q->irq_ptr->dsci, 0);
 144                        /*
 145                         * Call inbound processing but not directly
 146                         * since that could starve other thinint queues.
 147                         */
 148                        tasklet_schedule(&q->tasklet);
 149                }
 150                qperf_inc(q, adapter_int);
 151        }
 152        rcu_read_unlock();
 153
 154        /*
 155         * If the shared indicator was used clear it now after all queues
 156         * were processed.
 157         */
 158        if (si_used && shared_ind_set())
 159                xchg(&q_indicators[TIQDIO_SHARED_IND].ind, 0);
 160}
 161
 162static int set_subchannel_ind(struct qdio_irq *irq_ptr, int reset)
 163{
 164        struct scssc_area *scssc_area;
 165        int rc;
 166
 167        scssc_area = (struct scssc_area *)irq_ptr->chsc_page;
 168        memset(scssc_area, 0, PAGE_SIZE);
 169
 170        if (reset) {
 171                scssc_area->summary_indicator_addr = 0;
 172                scssc_area->subchannel_indicator_addr = 0;
 173        } else {
 174                scssc_area->summary_indicator_addr = virt_to_phys(tiqdio_alsi);
 175                scssc_area->subchannel_indicator_addr =
 176                        virt_to_phys(irq_ptr->dsci);
 177        }
 178
 179        scssc_area->request = (struct chsc_header) {
 180                .length = 0x0fe0,
 181                .code   = 0x0021,
 182        };
 183        scssc_area->operation_code = 0;
 184        scssc_area->ks = PAGE_DEFAULT_KEY >> 4;
 185        scssc_area->kc = PAGE_DEFAULT_KEY >> 4;
 186        scssc_area->isc = QDIO_AIRQ_ISC;
 187        scssc_area->schid = irq_ptr->schid;
 188
 189        /* enable the time delay disablement facility */
 190        if (css_general_characteristics.aif_tdd)
 191                scssc_area->word_with_d_bit = 0x10000000;
 192
 193        rc = chsc(scssc_area);
 194        if (rc)
 195                return -EIO;
 196
 197        rc = chsc_error_from_response(scssc_area->response.code);
 198        if (rc) {
 199                DBF_ERROR("%4x SSI r:%4x", irq_ptr->schid.sch_no,
 200                          scssc_area->response.code);
 201                DBF_ERROR_HEX(&scssc_area->response, sizeof(void *));
 202                return rc;
 203        }
 204
 205        DBF_EVENT("setscind");
 206        DBF_HEX(&scssc_area->summary_indicator_addr, sizeof(unsigned long));
 207        DBF_HEX(&scssc_area->subchannel_indicator_addr, sizeof(unsigned long));
 208        return 0;
 209}
 210
 211/* allocate non-shared indicators and shared indicator */
 212int __init tiqdio_allocate_memory(void)
 213{
 214        q_indicators = kzalloc(sizeof(struct indicator_t) * TIQDIO_NR_INDICATORS,
 215                             GFP_KERNEL);
 216        if (!q_indicators)
 217                return -ENOMEM;
 218        return 0;
 219}
 220
 221void tiqdio_free_memory(void)
 222{
 223        kfree(q_indicators);
 224}
 225
 226int __init tiqdio_register_thinints(void)
 227{
 228        isc_register(QDIO_AIRQ_ISC);
 229        tiqdio_alsi = s390_register_adapter_interrupt(&tiqdio_thinint_handler,
 230                                                      NULL, QDIO_AIRQ_ISC);
 231        if (IS_ERR(tiqdio_alsi)) {
 232                DBF_EVENT("RTI:%lx", PTR_ERR(tiqdio_alsi));
 233                tiqdio_alsi = NULL;
 234                isc_unregister(QDIO_AIRQ_ISC);
 235                return -ENOMEM;
 236        }
 237        return 0;
 238}
 239
 240int qdio_establish_thinint(struct qdio_irq *irq_ptr)
 241{
 242        if (!is_thinint_irq(irq_ptr))
 243                return 0;
 244        return set_subchannel_ind(irq_ptr, 0);
 245}
 246
 247void qdio_setup_thinint(struct qdio_irq *irq_ptr)
 248{
 249        if (!is_thinint_irq(irq_ptr))
 250                return;
 251        irq_ptr->dsci = get_indicator();
 252        DBF_HEX(&irq_ptr->dsci, sizeof(void *));
 253}
 254
 255void qdio_shutdown_thinint(struct qdio_irq *irq_ptr)
 256{
 257        if (!is_thinint_irq(irq_ptr))
 258                return;
 259
 260        /* reset adapter interrupt indicators */
 261        set_subchannel_ind(irq_ptr, 1);
 262        put_indicator(irq_ptr->dsci);
 263}
 264
 265void __exit tiqdio_unregister_thinints(void)
 266{
 267        WARN_ON(!list_empty(&tiq_list));
 268
 269        if (tiqdio_alsi) {
 270                s390_unregister_adapter_interrupt(tiqdio_alsi, QDIO_AIRQ_ISC);
 271                isc_unregister(QDIO_AIRQ_ISC);
 272        }
 273}
 274