xref: /illumos-gate/usr/src/uts/common/io/pci_cap.c (revision 533affcbc7fc4d0c8132976ea454aaa715fe2307)
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 #include <sys/note.h>
23 #include <sys/conf.h>
24 #include <sys/debug.h>
25 #include <sys/sunddi.h>
26 #include <sys/pci.h>
27 #include <sys/pcie.h>
28 #include <sys/bitmap.h>
29 #include <sys/autoconf.h>
30 #include <sys/sysmacros.h>
31 #include <sys/pci_cap.h>
32 
33 /*
34  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
35  * Use is subject to license terms.
36  * Copyright 2022 Oxide Computer Company
37  */
38 
39 /*
40  * Generic PCI Capabilites Interface for all pci platforms
41  */
42 
43 #ifdef DEBUG
44 uint_t  pci_cap_debug = 0;
45 #endif
46 
47 /* Cap Base Macro */
48 #define	PCI_CAP_BASE(h, id, base_p) (*base_p ? DDI_SUCCESS : \
49 	(id ? PCI_CAP_LOCATE(h, id, base_p) : DDI_FAILURE))
50 
51 /*
52  * pci_cap_probe: returns the capid and base based upon a given index
53  */
54 int
55 pci_cap_probe(ddi_acc_handle_t h, uint16_t index, uint32_t *id_p,
56     uint16_t *base_p)
57 {
58 	int i, search_ext = 0;
59 	uint16_t base, pcix_cmd, status;
60 	uint32_t id, xcaps_hdr; /* Extended Caps Header Word */
61 
62 	status = pci_config_get16(h, PCI_CONF_STAT);
63 
64 	if (status == PCI_CAP_EINVAL16 || !(status & PCI_STAT_CAP))
65 		return (DDI_FAILURE);
66 
67 	/* PCIE and PCIX Version 2 contain Extended Config Space */
68 	for (i = 0, base = pci_config_get8(h, PCI_CONF_CAP_PTR);
69 	    base && i < index; base = pci_config_get8(h, base
70 	    + PCI_CAP_NEXT_PTR), i++) {
71 
72 		if ((id = pci_config_get8(h, base)) == 0xff)
73 			break;
74 
75 		if (id == PCI_CAP_ID_PCI_E)
76 			search_ext = 1;
77 		else if (id == PCI_CAP_ID_PCIX) {
78 			if ((pcix_cmd = pci_config_get16(h, base +
79 			    PCI_PCIX_COMMAND)) != PCI_CAP_EINVAL16)
80 				continue;
81 			if ((pcix_cmd & PCI_PCIX_VER_MASK) == PCI_PCIX_VER_2)
82 				search_ext = 1;
83 		}
84 	}
85 
86 	if (base && i == index) {
87 		if ((id = pci_config_get8(h, base)) != 0xff)
88 			goto found;
89 	}
90 
91 	if (!search_ext)
92 		return (DDI_FAILURE);
93 
94 	for (base = PCIE_EXT_CAP; base && i < index; i++) {
95 		if ((xcaps_hdr = pci_config_get32(h, base)) == PCI_CAP_EINVAL32)
96 			break;
97 
98 		id = (xcaps_hdr >> PCIE_EXT_CAP_ID_SHIFT)
99 		    & PCIE_EXT_CAP_ID_MASK;
100 		base = (xcaps_hdr >> PCIE_EXT_CAP_NEXT_PTR_SHIFT)
101 		    & PCIE_EXT_CAP_NEXT_PTR_MASK;
102 	}
103 
104 	if (!base || i < index)
105 		return (DDI_FAILURE);
106 
107 	if ((xcaps_hdr = pci_config_get32(h, base)) == PCI_CAP_EINVAL32)
108 		return (DDI_FAILURE);
109 
110 	id = ((xcaps_hdr >> PCIE_EXT_CAP_ID_SHIFT) & PCIE_EXT_CAP_ID_MASK) |
111 	    PCI_CAP_XCFG_FLAG;
112 found:
113 	PCI_CAP_DBG("pci_cap_probe: index=%x, id=%x, base=%x\n",
114 	    index, id, base);
115 
116 	*id_p = id;
117 	*base_p = base;
118 	return (DDI_SUCCESS);
119 
120 }
121 
122 /*
123  * pci_lcap_locate: Helper function locates a base in conventional config space.
124  */
125 int
126 pci_lcap_locate(ddi_acc_handle_t h, uint8_t id, uint16_t *base_p)
127 {
128 	uint8_t header;
129 	uint16_t status, base, ncaps;
130 
131 	status = pci_config_get16(h, PCI_CONF_STAT);
132 
133 	if (status == PCI_CAP_EINVAL16 || !(status & PCI_STAT_CAP))
134 		return (DDI_FAILURE);
135 
136 	header = pci_config_get8(h, PCI_CONF_HEADER);
137 	switch (header & PCI_HEADER_TYPE_M) {
138 	case PCI_HEADER_ZERO:
139 		base = PCI_CONF_CAP_PTR;
140 		break;
141 	case PCI_HEADER_PPB:
142 		base = PCI_BCNF_CAP_PTR;
143 		break;
144 	case PCI_HEADER_CARDBUS:
145 		base = PCI_CBUS_CAP_PTR;
146 		break;
147 	default:
148 		cmn_err(CE_WARN, "%s: unexpected pci header type:%x",
149 		    __func__, header);
150 		return (DDI_FAILURE);
151 	}
152 
153 	ncaps = 0;
154 	for (base = pci_config_get8(h, base); base;
155 	    base = pci_config_get8(h, base + PCI_CAP_NEXT_PTR)) {
156 		if (pci_config_get8(h, base) == id) {
157 			*base_p = base;
158 			return (DDI_SUCCESS);
159 		}
160 
161 		ncaps++;
162 		if (ncaps >= PCI_CAP_MAX_PTR)
163 			break;
164 	}
165 
166 	*base_p = PCI_CAP_NEXT_PTR_NULL;
167 	return (DDI_FAILURE);
168 }
169 
170 /*
171  * pci_xcap_locate: Helper function locates a base in extended config space.
172  */
173 int
174 pci_xcap_locate(ddi_acc_handle_t h, uint16_t id, uint16_t *base_p)
175 {
176 	uint16_t status, base;
177 	uint32_t xcaps_hdr, ncaps;
178 
179 	status = pci_config_get16(h, PCI_CONF_STAT);
180 
181 	if (status == PCI_CAP_EINVAL16 || !(status & PCI_STAT_CAP))
182 		return (DDI_FAILURE);
183 
184 	ncaps = 0;
185 	for (base = PCIE_EXT_CAP; base; base = (xcaps_hdr >>
186 	    PCIE_EXT_CAP_NEXT_PTR_SHIFT) & PCIE_EXT_CAP_NEXT_PTR_MASK) {
187 
188 		if ((xcaps_hdr = pci_config_get32(h, base)) == PCI_CAP_EINVAL32)
189 			break;
190 
191 		if (((xcaps_hdr >> PCIE_EXT_CAP_ID_SHIFT) &
192 		    PCIE_EXT_CAP_ID_MASK) == id) {
193 			*base_p = base;
194 			return (DDI_SUCCESS);
195 		}
196 
197 		ncaps++;
198 		if (ncaps >= PCIE_EXT_CAP_MAX_PTR)
199 			break;
200 	}
201 
202 	*base_p = PCI_CAP_NEXT_PTR_NULL;
203 	return (DDI_FAILURE);
204 }
205 
206 /*
207  * There can be multiple pci caps with a Hypertransport technology cap ID
208  * Each is distiguished by a type register in the upper half of the cap
209  * header (the "command" register part).
210  *
211  * This returns the location of a hypertransport capability whose upper
212  * 16-bits of the cap header matches <reg_val> after masking the value
213  * with <reg_mask>; if both <reg_mask> and <reg_val> are 0, it will return
214  * the first HT cap found
215  */
216 int
217 pci_htcap_locate(ddi_acc_handle_t h, uint16_t reg_mask, uint16_t reg_val,
218     uint16_t *base_p)
219 {
220 	uint8_t header;
221 	uint16_t status, base;
222 
223 	status = pci_config_get16(h, PCI_CONF_STAT);
224 
225 	if (status == PCI_CAP_EINVAL16 || !(status & PCI_STAT_CAP))
226 		return (DDI_FAILURE);
227 
228 	header = pci_config_get8(h, PCI_CONF_HEADER);
229 	switch (header & PCI_HEADER_TYPE_M) {
230 	case PCI_HEADER_ZERO:
231 		base = PCI_CONF_CAP_PTR;
232 		break;
233 	case PCI_HEADER_PPB:
234 		base = PCI_BCNF_CAP_PTR;
235 		break;
236 	default:
237 		cmn_err(CE_WARN, "%s: unexpected pci header type:%x",
238 		    __func__, header);
239 		return (DDI_FAILURE);
240 	}
241 
242 	for (base = pci_config_get8(h, base); base;
243 	    base = pci_config_get8(h, base + PCI_CAP_NEXT_PTR)) {
244 		if (pci_config_get8(h, base) == PCI_CAP_ID_HT &&
245 		    (pci_config_get16(h, base + PCI_CAP_ID_REGS_OFF) &
246 		    reg_mask) == reg_val) {
247 			*base_p = base;
248 			return (DDI_SUCCESS);
249 		}
250 	}
251 
252 	*base_p = PCI_CAP_NEXT_PTR_NULL;
253 	return (DDI_FAILURE);
254 }
255 
256 /*
257  * pci_cap_get: This function uses the base or capid to get a byte, word,
258  * or dword. If access by capid is requested, the function uses the capid to
259  * locate the base. Access by a base results in better performance
260  * because no cap list traversal is required.
261  */
262 uint32_t
263 pci_cap_get(ddi_acc_handle_t h, pci_cap_config_size_t size, uint32_t id,
264     uint16_t base, uint16_t offset)
265 {
266 	uint32_t data;
267 
268 	if (PCI_CAP_BASE(h, id, &base) != DDI_SUCCESS)
269 		return (PCI_CAP_EINVAL32);
270 
271 	/*
272 	 * Each access to a PCI Configuration Space should be checked
273 	 * by the calling function. A returned value of the 2's complement
274 	 * of -1 indicates that either the device is offlined or it does not
275 	 * exist.
276 	 */
277 	offset += base;
278 
279 	switch (size) {
280 	case PCI_CAP_CFGSZ_8:
281 		data = pci_config_get8(h, offset);
282 		break;
283 	case PCI_CAP_CFGSZ_16:
284 		data = pci_config_get16(h, offset);
285 		break;
286 	case PCI_CAP_CFGSZ_32:
287 		data = pci_config_get32(h, offset);
288 		break;
289 	default:
290 		data = PCI_CAP_EINVAL32;
291 	}
292 
293 	PCI_CAP_DBG("pci_cap_get: %p[x%x]=x%x\n", (void *)h, offset, data);
294 	return (data);
295 }
296 
297 /*
298  * pci_cap_put: This function uses the caps ptr or capid to put a byte, word,
299  * or dword. If access by capid is requested, the function uses the capid to
300  * locate the base. Access by base results in better performance
301  * because no cap list traversal is required.
302  */
303 int
304 pci_cap_put(ddi_acc_handle_t h, pci_cap_config_size_t size,
305     uint32_t id, uint16_t base, uint16_t offset, uint32_t data)
306 {
307 
308 	/*
309 	 * use the pci_config_size_t to switch for the appropriate read
310 	 */
311 	if (PCI_CAP_BASE(h, id, &base) != DDI_SUCCESS)
312 		return (DDI_FAILURE);
313 
314 	offset += base;
315 
316 	switch (size) {
317 	case PCI_CAP_CFGSZ_8:
318 		pci_config_put8(h, offset, data);
319 		break;
320 	case PCI_CAP_CFGSZ_16:
321 		pci_config_put16(h, offset, data);
322 		break;
323 	case PCI_CAP_CFGSZ_32:
324 		pci_config_put32(h, offset, data);
325 		break;
326 	default:
327 		return (DDI_FAILURE);
328 	}
329 
330 	PCI_CAP_DBG("pci_cap_put: data=%x\n", data);
331 	return (DDI_SUCCESS);
332 }
333 
334 /*
335  * Cache the entire Cap Structure.  The caller is required to allocate and free
336  * buffer.
337  */
338 int
339 pci_cap_read(ddi_acc_handle_t h, uint32_t id, uint16_t base,
340     uint32_t *buf_p, uint32_t nwords)
341 {
342 
343 	int i;
344 	uint32_t *ptr;
345 
346 	ASSERT(nwords < 1024);
347 
348 	if (PCI_CAP_BASE(h, id, &base) != DDI_SUCCESS)
349 		return (DDI_FAILURE);
350 
351 	for (ptr = buf_p, i = 0; i < nwords; i++, base += 4) {
352 		if ((*ptr++ = pci_config_get32(h, base)) == PCI_CAP_EINVAL32)
353 			return (DDI_FAILURE);
354 	}
355 
356 	return (DDI_SUCCESS);
357 }
358