qdio_thinint.c 7.0 KB
Newer Older
J
Jan Glauber 已提交
1 2 3
/*
 * linux/drivers/s390/cio/thinint_qdio.c
 *
4
 * Copyright 2000,2009 IBM Corp.
J
Jan Glauber 已提交
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
 * Author(s): Utz Bacher <utz.bacher@de.ibm.com>
 *	      Cornelia Huck <cornelia.huck@de.ibm.com>
 *	      Jan Glauber <jang@linux.vnet.ibm.com>
 */
#include <linux/io.h>
#include <asm/atomic.h>
#include <asm/debug.h>
#include <asm/qdio.h>
#include <asm/airq.h>
#include <asm/isc.h>

#include "cio.h"
#include "ioasm.h"
#include "qdio.h"
#include "qdio_debug.h"

/*
 * Restriction: only 63 iqdio subchannels would have its own indicator,
 * after that, subsequent subchannels share one indicator
 */
#define TIQDIO_NR_NONSHARED_IND		63
#define TIQDIO_NR_INDICATORS		(TIQDIO_NR_NONSHARED_IND + 1)
#define TIQDIO_SHARED_IND		63

/* list of thin interrupt input queues */
static LIST_HEAD(tiq_list);
31
DEFINE_MUTEX(tiq_list_lock);
J
Jan Glauber 已提交
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92

/* adapter local summary indicator */
static unsigned char *tiqdio_alsi;

/* device state change indicators */
struct indicator_t {
	u32 ind;	/* u32 because of compare-and-swap performance */
	atomic_t count; /* use count, 0 or 1 for non-shared indicators */
};
static struct indicator_t *q_indicators;

static int css_qdio_omit_svs;

static inline unsigned long do_clear_global_summary(void)
{
	register unsigned long __fn asm("1") = 3;
	register unsigned long __tmp asm("2");
	register unsigned long __time asm("3");

	asm volatile(
		"	.insn	rre,0xb2650000,2,0"
		: "+d" (__fn), "=d" (__tmp), "=d" (__time));
	return __time;
}

/* returns addr for the device state change indicator */
static u32 *get_indicator(void)
{
	int i;

	for (i = 0; i < TIQDIO_NR_NONSHARED_IND; i++)
		if (!atomic_read(&q_indicators[i].count)) {
			atomic_set(&q_indicators[i].count, 1);
			return &q_indicators[i].ind;
		}

	/* use the shared indicator */
	atomic_inc(&q_indicators[TIQDIO_SHARED_IND].count);
	return &q_indicators[TIQDIO_SHARED_IND].ind;
}

static void put_indicator(u32 *addr)
{
	int i;

	if (!addr)
		return;
	i = ((unsigned long)addr - (unsigned long)q_indicators) /
		sizeof(struct indicator_t);
	atomic_dec(&q_indicators[i].count);
}

void tiqdio_add_input_queues(struct qdio_irq *irq_ptr)
{
	struct qdio_q *q;
	int i;

	/* No TDD facility? If we must use SIGA-s we can also omit SVS. */
	if (!css_qdio_omit_svs && irq_ptr->siga_flag.sync)
		css_qdio_omit_svs = 1;

93 94
	mutex_lock(&tiq_list_lock);
	for_each_input_queue(irq_ptr, q, i)
J
Jan Glauber 已提交
95
		list_add_rcu(&q->entry, &tiq_list);
96
	mutex_unlock(&tiq_list_lock);
J
Jan Glauber 已提交
97 98 99 100 101 102 103 104
	xchg(irq_ptr->dsci, 1);
}

void tiqdio_remove_input_queues(struct qdio_irq *irq_ptr)
{
	struct qdio_q *q;
	int i;

105 106 107 108 109
	for (i = 0; i < irq_ptr->nr_input_qs; i++) {
		q = irq_ptr->input_qs[i];
		/* if establish triggered an error */
		if (!q || !q->entry.prev || !q->entry.next)
			continue;
110 111

		mutex_lock(&tiq_list_lock);
J
Jan Glauber 已提交
112
		list_del_rcu(&q->entry);
113
		mutex_unlock(&tiq_list_lock);
J
Jan Glauber 已提交
114 115 116 117 118 119 120 121 122
		synchronize_rcu();
	}
}

static inline int shared_ind(struct qdio_irq *irq_ptr)
{
	return irq_ptr->dsci == &q_indicators[TIQDIO_SHARED_IND].ind;
}

123 124 125 126 127 128
/**
 * tiqdio_thinint_handler - thin interrupt handler for qdio
 * @ind: pointer to adapter local summary indicator
 * @drv_data: NULL
 */
static void tiqdio_thinint_handler(void *ind, void *drv_data)
J
Jan Glauber 已提交
129 130 131
{
	struct qdio_q *q;

132 133 134 135 136 137 138 139 140 141 142 143
	/*
	 * SVS only when needed: issue SVS to benefit from iqdio interrupt
	 * avoidance (SVS clears adapter interrupt suppression overwrite)
	 */
	if (!css_qdio_omit_svs)
		do_clear_global_summary();

	/*
	 * reset local summary indicator (tiqdio_alsi) to stop adapter
	 * interrupts for now
	 */
	xchg((u8 *)ind, 0);
J
Jan Glauber 已提交
144 145 146 147

	/* protect tiq_list entries, only changed in activate or shutdown */
	rcu_read_lock();

148
	/* check for work on all inbound thinint queues */
J
Jan Glauber 已提交
149 150 151
	list_for_each_entry_rcu(q, &tiq_list, entry)
		/* only process queues from changed sets */
		if (*q->irq_ptr->dsci) {
152
			qperf_inc(q, adapter_int);
J
Jan Glauber 已提交
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200

			/* only clear it if the indicator is non-shared */
			if (!shared_ind(q->irq_ptr))
				xchg(q->irq_ptr->dsci, 0);
			/*
			 * don't call inbound processing directly since
			 * that could starve other thinint queues
			 */
			tasklet_schedule(&q->tasklet);
		}

	rcu_read_unlock();

	/*
	 * if we used the shared indicator clear it now after all queues
	 * were processed
	 */
	if (atomic_read(&q_indicators[TIQDIO_SHARED_IND].count)) {
		xchg(&q_indicators[TIQDIO_SHARED_IND].ind, 0);

		/* prevent racing */
		if (*tiqdio_alsi)
			xchg(&q_indicators[TIQDIO_SHARED_IND].ind, 1);
	}
}

static int set_subchannel_ind(struct qdio_irq *irq_ptr, int reset)
{
	struct scssc_area *scssc_area;
	int rc;

	scssc_area = (struct scssc_area *)irq_ptr->chsc_page;
	memset(scssc_area, 0, PAGE_SIZE);

	if (reset) {
		scssc_area->summary_indicator_addr = 0;
		scssc_area->subchannel_indicator_addr = 0;
	} else {
		scssc_area->summary_indicator_addr = virt_to_phys(tiqdio_alsi);
		scssc_area->subchannel_indicator_addr =
			virt_to_phys(irq_ptr->dsci);
	}

	scssc_area->request = (struct chsc_header) {
		.length = 0x0fe0,
		.code	= 0x0021,
	};
	scssc_area->operation_code = 0;
201 202
	scssc_area->ks = PAGE_DEFAULT_KEY >> 4;
	scssc_area->kc = PAGE_DEFAULT_KEY >> 4;
J
Jan Glauber 已提交
203 204 205 206 207 208 209 210 211 212 213 214 215
	scssc_area->isc = QDIO_AIRQ_ISC;
	scssc_area->schid = irq_ptr->schid;

	/* enable the time delay disablement facility */
	if (css_general_characteristics.aif_tdd)
		scssc_area->word_with_d_bit = 0x10000000;

	rc = chsc(scssc_area);
	if (rc)
		return -EIO;

	rc = chsc_error_from_response(scssc_area->response.code);
	if (rc) {
216 217 218
		DBF_ERROR("%4x SSI r:%4x", irq_ptr->schid.sch_no,
			  scssc_area->response.code);
		DBF_ERROR_HEX(&scssc_area->response, sizeof(void *));
J
Jan Glauber 已提交
219 220 221
		return rc;
	}

222 223 224
	DBF_EVENT("setscind");
	DBF_HEX(&scssc_area->summary_indicator_addr, sizeof(unsigned long));
	DBF_HEX(&scssc_area->subchannel_indicator_addr,	sizeof(unsigned long));
J
Jan Glauber 已提交
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
	return 0;
}

/* allocate non-shared indicators and shared indicator */
int __init tiqdio_allocate_memory(void)
{
	q_indicators = kzalloc(sizeof(struct indicator_t) * TIQDIO_NR_INDICATORS,
			     GFP_KERNEL);
	if (!q_indicators)
		return -ENOMEM;
	return 0;
}

void tiqdio_free_memory(void)
{
	kfree(q_indicators);
}

int __init tiqdio_register_thinints(void)
{
	isc_register(QDIO_AIRQ_ISC);
	tiqdio_alsi = s390_register_adapter_interrupt(&tiqdio_thinint_handler,
						      NULL, QDIO_AIRQ_ISC);
	if (IS_ERR(tiqdio_alsi)) {
249
		DBF_EVENT("RTI:%lx", PTR_ERR(tiqdio_alsi));
J
Jan Glauber 已提交
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274
		tiqdio_alsi = NULL;
		isc_unregister(QDIO_AIRQ_ISC);
		return -ENOMEM;
	}
	return 0;
}

int qdio_establish_thinint(struct qdio_irq *irq_ptr)
{
	if (!is_thinint_irq(irq_ptr))
		return 0;

	/* Check for aif time delay disablement. If installed,
	 * omit SVS even under LPAR
	 */
	if (css_general_characteristics.aif_tdd)
		css_qdio_omit_svs = 1;
	return set_subchannel_ind(irq_ptr, 0);
}

void qdio_setup_thinint(struct qdio_irq *irq_ptr)
{
	if (!is_thinint_irq(irq_ptr))
		return;
	irq_ptr->dsci = get_indicator();
275
	DBF_HEX(&irq_ptr->dsci, sizeof(void *));
J
Jan Glauber 已提交
276 277 278 279 280 281 282 283 284 285 286 287 288 289
}

void qdio_shutdown_thinint(struct qdio_irq *irq_ptr)
{
	if (!is_thinint_irq(irq_ptr))
		return;

	/* reset adapter interrupt indicators */
	put_indicator(irq_ptr->dsci);
	set_subchannel_ind(irq_ptr, 1);
}

void __exit tiqdio_unregister_thinints(void)
{
290
	WARN_ON(!list_empty(&tiq_list));
J
Jan Glauber 已提交
291 292 293 294 295 296

	if (tiqdio_alsi) {
		s390_unregister_adapter_interrupt(tiqdio_alsi, QDIO_AIRQ_ISC);
		isc_unregister(QDIO_AIRQ_ISC);
	}
}