1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21 /*
22 * Copyright 2010 Sun Microsystems, Inc. All rights reserved.
23 * Use is subject to license terms.
24 */
25
26 /*
27 * px_msi.c
28 */
29
30 #include <sys/types.h>
31 #include <sys/kmem.h>
32 #include <sys/conf.h>
33 #include <sys/ddi.h>
34 #include <sys/sunddi.h>
35 #include <sys/sunndi.h>
36 #include <sys/modctl.h>
37 #include <sys/disp.h>
38 #include <sys/stat.h>
39 #include <sys/ddi_impldefs.h>
40 #include <sys/pci_impl.h>
41 #include "px_obj.h"
42
43 static int px_msi_get_props(px_t *px_p);
44
45 /*
46 * msi_attach()
47 */
48 int
px_msi_attach(px_t * px_p)49 px_msi_attach(px_t *px_p)
50 {
51 dev_info_t *dip = px_p->px_dip;
52 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state;
53 ddi_irm_pool_t *irm_pool_p = NULL;
54 ddi_irm_params_t irm_params;
55 msinum_t msi_num;
56 int i, ret;
57
58 DBG(DBG_MSIQ, dip, "px_msi_attach\n");
59
60 mutex_init(&msi_state_p->msi_mutex, NULL, MUTEX_DRIVER, NULL);
61
62 /*
63 * Check for all MSI related properties and
64 * save all information.
65 */
66 if (px_msi_get_props(px_p) != DDI_SUCCESS) {
67 px_msi_detach(px_p);
68 return (DDI_FAILURE);
69 }
70
71 px_p->px_supp_intr_types |= (DDI_INTR_TYPE_MSI | DDI_INTR_TYPE_MSIX);
72
73 msi_state_p->msi_p = kmem_zalloc(msi_state_p->msi_cnt *
74 sizeof (px_msi_t), KM_SLEEP);
75
76 for (i = 0, msi_num = msi_state_p->msi_1st_msinum;
77 i < msi_state_p->msi_cnt; i++, msi_num++) {
78 msi_state_p->msi_p[i].msi_msinum = msi_num;
79 msi_state_p->msi_p[i].msi_state = MSI_STATE_FREE;
80 }
81
82 /*
83 * Create IRM pool to manage interrupt allocations.
84 */
85 bzero(&irm_params, sizeof (ddi_irm_params_t));
86 irm_params.iparams_types = msi_state_p->msi_type;
87 irm_params.iparams_total = msi_state_p->msi_cnt;
88 if (ndi_irm_create(dip, &irm_params, &irm_pool_p) == DDI_SUCCESS) {
89 msi_state_p->msi_pool_p = irm_pool_p;
90 } else {
91 DBG(DBG_MSIQ, dip, "ndi_irm_create() failed\n");
92 }
93
94 if ((ret = px_lib_msi_init(dip)) != DDI_SUCCESS)
95 px_msi_detach(px_p);
96
97 return (ret);
98 }
99
100
101 /*
102 * msi_detach()
103 */
104 void
px_msi_detach(px_t * px_p)105 px_msi_detach(px_t *px_p)
106 {
107 dev_info_t *dip = px_p->px_dip;
108 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state;
109
110 DBG(DBG_MSIQ, dip, "px_msi_detach\n");
111
112 if (msi_state_p->msi_pool_p)
113 (void) ndi_irm_destroy(msi_state_p->msi_pool_p);
114
115 if (msi_state_p->msi_p) {
116 kmem_free(msi_state_p->msi_p,
117 msi_state_p->msi_cnt * sizeof (px_msi_t));
118 }
119
120 mutex_destroy(&msi_state_p->msi_mutex);
121 bzero(&px_p->px_ib_p->ib_msi_state, sizeof (px_msi_state_t));
122 }
123
124
125 /*
126 * msi_alloc()
127 */
128 /* ARGSUSED */
129 int
px_msi_alloc(px_t * px_p,dev_info_t * rdip,int type,int inum,int msi_count,int flag,int * actual_msi_count_p)130 px_msi_alloc(px_t *px_p, dev_info_t *rdip, int type, int inum, int msi_count,
131 int flag, int *actual_msi_count_p)
132 {
133 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state;
134 int first, count, i, n;
135
136 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d "
137 "type 0x%x inum 0x%x msi_count 0x%x\n", ddi_driver_name(rdip),
138 ddi_get_instance(rdip), type, inum, msi_count);
139
140 mutex_enter(&msi_state_p->msi_mutex);
141
142 *actual_msi_count_p = 0;
143
144 /*
145 * MSI interrupts are allocated as contiguous ranges at
146 * power of 2 boundaries from the start of the MSI array.
147 */
148 if (type == DDI_INTR_TYPE_MSI) {
149
150 /* Search for a range of available interrupts */
151 for (count = msi_count; count; count >>= 1) {
152 for (first = 0; (first + count) < msi_state_p->msi_cnt;
153 first += count) {
154 for (i = first; i < (first + count); i++) {
155 if (msi_state_p->msi_p[i].msi_state
156 != MSI_STATE_FREE) {
157 break;
158 }
159 }
160 if (i == (first + count)) {
161 goto found_msi;
162 }
163 }
164 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: failed\n");
165 if (count > 1) {
166 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: "
167 "Retry MSI allocation with new msi_count "
168 "0x%x\n", count >> 1);
169 }
170 }
171
172 found_msi:
173 /* Set number of available interrupts */
174 *actual_msi_count_p = count;
175
176 /* Check if successful, and enforce strict behavior */
177 if ((count == 0) ||
178 ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) {
179 mutex_exit(&msi_state_p->msi_mutex);
180 return (DDI_EAGAIN);
181 }
182
183 /* Allocate the interrupts */
184 for (i = first; i < (first + count); i++, inum++) {
185 msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE;
186 msi_state_p->msi_p[i].msi_dip = rdip;
187 msi_state_p->msi_p[i].msi_inum = inum;
188 }
189 }
190
191 /*
192 * MSI-X interrupts are allocated from the end of the MSI
193 * array. There are no concerns about power of 2 boundaries
194 * and the allocated interrupts do not have to be contiguous.
195 */
196 if (type == DDI_INTR_TYPE_MSIX) {
197
198 /* Count available interrupts, up to count requested */
199 for (count = 0, i = (msi_state_p->msi_cnt - 1); i >= 0; i--) {
200 if (msi_state_p->msi_p[i].msi_state == MSI_STATE_FREE) {
201 if (count == 0)
202 first = i;
203 count++;
204 if (count == msi_count)
205 break;
206 }
207 }
208
209 /* Set number of available interrupts */
210 *actual_msi_count_p = count;
211
212 /* Check if successful, and enforce strict behavior */
213 if ((count == 0) ||
214 ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) {
215 mutex_exit(&msi_state_p->msi_mutex);
216 return (DDI_EAGAIN);
217 }
218
219 /* Allocate the interrupts */
220 for (n = 0, i = first; n < count; i--) {
221 if (msi_state_p->msi_p[i].msi_state != MSI_STATE_FREE)
222 continue;
223 msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE;
224 msi_state_p->msi_p[i].msi_dip = rdip;
225 msi_state_p->msi_p[i].msi_inum = inum;
226 inum++;
227 n++;
228 }
229 }
230
231 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d "
232 "msi_num 0x%x count 0x%x\n", ddi_driver_name(rdip),
233 ddi_get_instance(rdip), first, count);
234
235 mutex_exit(&msi_state_p->msi_mutex);
236
237 return (DDI_SUCCESS);
238 }
239
240
241 /*
242 * msi_free()
243 */
244 int
px_msi_free(px_t * px_p,dev_info_t * rdip,int inum,int msi_count)245 px_msi_free(px_t *px_p, dev_info_t *rdip, int inum, int msi_count)
246 {
247 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state;
248 int i, n;
249
250 DBG(DBG_R_MSIX, px_p->px_dip, "px_msi_free: rdip 0x%p "
251 "inum 0x%x msi_count 0x%x\n", rdip, inum, msi_count);
252
253 mutex_enter(&msi_state_p->msi_mutex);
254
255 /*
256 * Find and release the specified MSI/X numbers.
257 *
258 * Because the allocations are not always contiguous, perform
259 * a full linear search of the MSI/X table looking for MSI/X
260 * vectors owned by the device with inum values in the range
261 * [inum .. (inum + msi_count - 1)].
262 */
263 for (i = 0, n = 0; (i < msi_state_p->msi_cnt) && (n < msi_count); i++) {
264 if ((msi_state_p->msi_p[i].msi_dip == rdip) &&
265 (msi_state_p->msi_p[i].msi_inum >= inum) &&
266 (msi_state_p->msi_p[i].msi_inum < (inum + msi_count))) {
267 msi_state_p->msi_p[i].msi_dip = NULL;
268 msi_state_p->msi_p[i].msi_inum = 0;
269 msi_state_p->msi_p[i].msi_msiq_id = 0;
270 msi_state_p->msi_p[i].msi_state = MSI_STATE_FREE;
271 n++;
272 }
273 }
274
275 mutex_exit(&msi_state_p->msi_mutex);
276
277 /* Fail if the MSI/X numbers were not found */
278 if (n < msi_count)
279 return (DDI_FAILURE);
280
281 return (DDI_SUCCESS);
282 }
283
284 /*
285 * msi_get_msinum()
286 */
287 int
px_msi_get_msinum(px_t * px_p,dev_info_t * rdip,int inum,msinum_t * msi_num_p)288 px_msi_get_msinum(px_t *px_p, dev_info_t *rdip, int inum, msinum_t *msi_num_p)
289 {
290 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state;
291 int i;
292
293 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: "
294 "rdip 0x%p inum 0x%x\n", rdip, inum);
295
296 mutex_enter(&msi_state_p->msi_mutex);
297
298 for (i = 0; i < msi_state_p->msi_cnt; i++) {
299 if ((msi_state_p->msi_p[i].msi_inum == inum) &&
300 (msi_state_p->msi_p[i].msi_dip == rdip)) {
301
302 *msi_num_p = msi_state_p->msi_p[i].msi_msinum;
303
304 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: "
305 "inum 0x%x msi 0x%x\n", inum, *msi_num_p);
306
307 mutex_exit(&msi_state_p->msi_mutex);
308 return (DDI_SUCCESS);
309 }
310 }
311
312 if (i >= msi_state_p->msi_cnt)
313 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: "
314 "no msi for inum 0x%x\n", inum);
315
316 mutex_exit(&msi_state_p->msi_mutex);
317 return (DDI_FAILURE);
318 }
319
320 /*
321 * px_msi_get_props()
322 */
323 static int
px_msi_get_props(px_t * px_p)324 px_msi_get_props(px_t *px_p)
325 {
326 dev_info_t *dip = px_p->px_dip;
327 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state;
328 int length = sizeof (int);
329 int *valuep = NULL;
330 uint64_t msi_addr_hi, msi_addr_lo;
331
332 DBG(DBG_MSIQ, dip, "px_msi_get_props\n");
333
334 /* #msi */
335 msi_state_p->msi_cnt = ddi_getprop(DDI_DEV_T_ANY, dip,
336 DDI_PROP_DONTPASS, "#msi", 0);
337
338 DBG(DBG_MSIQ, dip, "#msi=%d\n", msi_state_p->msi_cnt);
339 if (msi_state_p->msi_cnt == 0)
340 return (DDI_FAILURE);
341
342 /* msi-ranges: msi# field */
343 if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC,
344 DDI_PROP_DONTPASS, "msi-ranges", (caddr_t)&valuep, &length)
345 != DDI_PROP_SUCCESS)
346 return (DDI_FAILURE);
347
348 msi_state_p->msi_1st_msinum = ((px_msi_ranges_t *)valuep)->msi_no;
349 kmem_free(valuep, (size_t)length);
350
351 DBG(DBG_MSIQ, dip, "msi_1st_msinum=%d\n", msi_state_p->msi_1st_msinum);
352
353 /* msi-data-mask */
354 msi_state_p->msi_data_mask = ddi_getprop(DDI_DEV_T_ANY, dip,
355 DDI_PROP_DONTPASS, "msi-data-mask", 0);
356
357 DBG(DBG_MSIQ, dip, "msi-data-mask=0x%x\n",
358 msi_state_p->msi_data_mask);
359
360 /* msi-data-width */
361 msi_state_p->msi_data_width = ddi_getprop(DDI_DEV_T_ANY, dip,
362 DDI_PROP_DONTPASS, "msix-data-width", 0);
363
364 DBG(DBG_MSIQ, dip, "msix-data-width=%d\n",
365 msi_state_p->msi_data_width);
366
367 /*
368 * Assume MSI is always supported, but also check if MSIX is supported
369 */
370 if (msi_state_p->msi_data_width) {
371 msi_state_p->msi_type = DDI_INTR_TYPE_MSI;
372 if (msi_state_p->msi_data_width == PX_MSIX_WIDTH)
373 msi_state_p->msi_type |= DDI_INTR_TYPE_MSIX;
374 } else {
375 return (DDI_FAILURE);
376 }
377
378 /* msi-address-ranges */
379 if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC,
380 DDI_PROP_DONTPASS, "msi-address-ranges", (caddr_t)&valuep, &length)
381 != DDI_PROP_SUCCESS)
382 return (DDI_FAILURE);
383
384 msi_addr_hi = ((px_msi_address_ranges_t *)valuep)->msi_addr32_hi;
385 msi_addr_lo = ((px_msi_address_ranges_t *)valuep)->msi_addr32_lo;
386 msi_state_p->msi_addr32 = (msi_addr_hi << 32) | msi_addr_lo;
387 msi_state_p->msi_addr32_len =
388 ((px_msi_address_ranges_t *)valuep)->msi_addr32_len;
389
390 msi_addr_hi = ((px_msi_address_ranges_t *)valuep)->msi_addr64_hi;
391 msi_addr_lo = ((px_msi_address_ranges_t *)valuep)->msi_addr64_lo;
392 msi_state_p->msi_addr64 = (msi_addr_hi << 32) | msi_addr_lo;
393 msi_state_p->msi_addr64_len =
394 ((px_msi_address_ranges_t *)valuep)->msi_addr64_len;
395
396 DBG(DBG_MSIQ, dip, "msi_addr32=0x%llx\n", msi_state_p->msi_addr32);
397 DBG(DBG_MSIQ, dip, "msi_addr64=0x%llx\n", msi_state_p->msi_addr64);
398
399 kmem_free(valuep, (size_t)length);
400 return (DDI_SUCCESS);
401 }
402