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 | #include <asm/tpi.h> |
19 | |
20 | #include "cio.h" |
21 | #include "ioasm.h" |
22 | #include "qdio.h" |
23 | #include "qdio_debug.h" |
24 | |
25 | /* |
26 | * Restriction: only 63 iqdio subchannels would have its own indicator, |
27 | * after that, subsequent subchannels share one indicator |
28 | */ |
29 | #define TIQDIO_NR_NONSHARED_IND 63 |
30 | #define TIQDIO_NR_INDICATORS (TIQDIO_NR_NONSHARED_IND + 1) |
31 | #define TIQDIO_SHARED_IND 63 |
32 | |
33 | /* device state change indicators */ |
34 | struct indicator_t { |
35 | u32 ind; /* u32 because of compare-and-swap performance */ |
36 | atomic_t count; /* use count, 0 or 1 for non-shared indicators */ |
37 | }; |
38 | |
39 | /* list of thin interrupt input queues */ |
40 | static LIST_HEAD(tiq_list); |
41 | static DEFINE_MUTEX(tiq_list_lock); |
42 | |
43 | static struct indicator_t *q_indicators; |
44 | |
45 | u64 last_ai_time; |
46 | |
47 | /* returns addr for the device state change indicator */ |
48 | static u32 *get_indicator(void) |
49 | { |
50 | int i; |
51 | |
52 | for (i = 0; i < TIQDIO_NR_NONSHARED_IND; i++) |
53 | if (!atomic_cmpxchg(v: &q_indicators[i].count, old: 0, new: 1)) |
54 | return &q_indicators[i].ind; |
55 | |
56 | /* use the shared indicator */ |
57 | atomic_inc(v: &q_indicators[TIQDIO_SHARED_IND].count); |
58 | return &q_indicators[TIQDIO_SHARED_IND].ind; |
59 | } |
60 | |
61 | static void put_indicator(u32 *addr) |
62 | { |
63 | struct indicator_t *ind = container_of(addr, struct indicator_t, ind); |
64 | |
65 | if (!addr) |
66 | return; |
67 | atomic_dec(v: &ind->count); |
68 | } |
69 | |
70 | static inline int references_shared_dsci(struct qdio_irq *irq_ptr) |
71 | { |
72 | return irq_ptr->dsci == &q_indicators[TIQDIO_SHARED_IND].ind; |
73 | } |
74 | |
75 | int test_nonshared_ind(struct qdio_irq *irq_ptr) |
76 | { |
77 | if (!is_thinint_irq(irq_ptr)) |
78 | return 0; |
79 | if (references_shared_dsci(irq_ptr)) |
80 | return 0; |
81 | if (*irq_ptr->dsci) |
82 | return 1; |
83 | else |
84 | return 0; |
85 | } |
86 | |
87 | static inline u32 clear_shared_ind(void) |
88 | { |
89 | if (!atomic_read(v: &q_indicators[TIQDIO_SHARED_IND].count)) |
90 | return 0; |
91 | return xchg(&q_indicators[TIQDIO_SHARED_IND].ind, 0); |
92 | } |
93 | |
94 | /** |
95 | * tiqdio_thinint_handler - thin interrupt handler for qdio |
96 | * @airq: pointer to adapter interrupt descriptor |
97 | * @tpi_info: interrupt information (e.g. floating vs directed -- unused) |
98 | */ |
99 | static void tiqdio_thinint_handler(struct airq_struct *airq, |
100 | struct tpi_info *tpi_info) |
101 | { |
102 | u64 irq_time = S390_lowcore.int_clock; |
103 | u32 si_used = clear_shared_ind(); |
104 | struct qdio_irq *irq; |
105 | |
106 | last_ai_time = irq_time; |
107 | inc_irq_stat(IRQIO_QAI); |
108 | |
109 | /* protect tiq_list entries, only changed in activate or shutdown */ |
110 | rcu_read_lock(); |
111 | |
112 | list_for_each_entry_rcu(irq, &tiq_list, entry) { |
113 | /* only process queues from changed sets */ |
114 | if (unlikely(references_shared_dsci(irq))) { |
115 | if (!si_used) |
116 | continue; |
117 | } else { |
118 | if (!*irq->dsci) |
119 | continue; |
120 | |
121 | xchg(irq->dsci, 0); |
122 | } |
123 | |
124 | qdio_deliver_irq(irq); |
125 | irq->last_data_irq_time = irq_time; |
126 | |
127 | QDIO_PERF_STAT_INC(irq, adapter_int); |
128 | } |
129 | rcu_read_unlock(); |
130 | } |
131 | |
132 | static struct airq_struct tiqdio_airq = { |
133 | .handler = tiqdio_thinint_handler, |
134 | .isc = QDIO_AIRQ_ISC, |
135 | }; |
136 | |
137 | static int set_subchannel_ind(struct qdio_irq *irq_ptr, int reset) |
138 | { |
139 | struct chsc_scssc_area *scssc = (void *)irq_ptr->chsc_page; |
140 | u64 summary_indicator_addr, subchannel_indicator_addr; |
141 | int rc; |
142 | |
143 | if (reset) { |
144 | summary_indicator_addr = 0; |
145 | subchannel_indicator_addr = 0; |
146 | } else { |
147 | summary_indicator_addr = virt_to_phys(address: tiqdio_airq.lsi_ptr); |
148 | subchannel_indicator_addr = virt_to_phys(address: irq_ptr->dsci); |
149 | } |
150 | |
151 | rc = chsc_sadc(schid: irq_ptr->schid, scssc, summary_indicator_addr, |
152 | subchannel_indicator_addr, isc: tiqdio_airq.isc); |
153 | if (rc) { |
154 | DBF_ERROR("%4x SSI r:%4x" , irq_ptr->schid.sch_no, |
155 | scssc->response.code); |
156 | goto out; |
157 | } |
158 | |
159 | DBF_EVENT("setscind" ); |
160 | DBF_HEX(addr: &summary_indicator_addr, len: sizeof(summary_indicator_addr)); |
161 | DBF_HEX(addr: &subchannel_indicator_addr, len: sizeof(subchannel_indicator_addr)); |
162 | out: |
163 | return rc; |
164 | } |
165 | |
166 | int qdio_establish_thinint(struct qdio_irq *irq_ptr) |
167 | { |
168 | int rc; |
169 | |
170 | if (!is_thinint_irq(irq_ptr)) |
171 | return 0; |
172 | |
173 | irq_ptr->dsci = get_indicator(); |
174 | DBF_HEX(addr: &irq_ptr->dsci, len: sizeof(void *)); |
175 | |
176 | rc = set_subchannel_ind(irq_ptr, reset: 0); |
177 | if (rc) { |
178 | put_indicator(addr: irq_ptr->dsci); |
179 | return rc; |
180 | } |
181 | |
182 | mutex_lock(&tiq_list_lock); |
183 | list_add_rcu(new: &irq_ptr->entry, head: &tiq_list); |
184 | mutex_unlock(lock: &tiq_list_lock); |
185 | return 0; |
186 | } |
187 | |
188 | void qdio_shutdown_thinint(struct qdio_irq *irq_ptr) |
189 | { |
190 | if (!is_thinint_irq(irq_ptr)) |
191 | return; |
192 | |
193 | mutex_lock(&tiq_list_lock); |
194 | list_del_rcu(entry: &irq_ptr->entry); |
195 | mutex_unlock(lock: &tiq_list_lock); |
196 | synchronize_rcu(); |
197 | |
198 | /* reset adapter interrupt indicators */ |
199 | set_subchannel_ind(irq_ptr, reset: 1); |
200 | put_indicator(addr: irq_ptr->dsci); |
201 | } |
202 | |
203 | int __init qdio_thinint_init(void) |
204 | { |
205 | int rc; |
206 | |
207 | q_indicators = kcalloc(TIQDIO_NR_INDICATORS, size: sizeof(struct indicator_t), |
208 | GFP_KERNEL); |
209 | if (!q_indicators) |
210 | return -ENOMEM; |
211 | |
212 | rc = register_adapter_interrupt(&tiqdio_airq); |
213 | if (rc) { |
214 | DBF_EVENT("RTI:%x" , rc); |
215 | kfree(objp: q_indicators); |
216 | return rc; |
217 | } |
218 | return 0; |
219 | } |
220 | |
221 | void __exit qdio_thinint_exit(void) |
222 | { |
223 | WARN_ON(!list_empty(&tiq_list)); |
224 | unregister_adapter_interrupt(&tiqdio_airq); |
225 | kfree(objp: q_indicators); |
226 | } |
227 | |