xref: /illumos-gate/usr/src/uts/common/io/pci_cap.c (revision c1bbf920)
127255037Spjha /*
227255037Spjha  * CDDL HEADER START
327255037Spjha  *
427255037Spjha  * The contents of this file are subject to the terms of the
527255037Spjha  * Common Development and Distribution License (the "License").
627255037Spjha  * You may not use this file except in compliance with the License.
727255037Spjha  *
827255037Spjha  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
927255037Spjha  * or http://www.opensolaris.org/os/licensing.
1027255037Spjha  * See the License for the specific language governing permissions
1127255037Spjha  * and limitations under the License.
1227255037Spjha  *
1327255037Spjha  * When distributing Covered Code, include this CDDL HEADER in each
1427255037Spjha  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1527255037Spjha  * If applicable, add the following below this CDDL HEADER, with the
1627255037Spjha  * fields enclosed by brackets "[]" replaced with your own identifying
1727255037Spjha  * information: Portions Copyright [yyyy] [name of copyright owner]
1827255037Spjha  *
1927255037Spjha  * CDDL HEADER END
2027255037Spjha  */
2127255037Spjha 
2227255037Spjha #include <sys/note.h>
2327255037Spjha #include <sys/conf.h>
2427255037Spjha #include <sys/debug.h>
2527255037Spjha #include <sys/sunddi.h>
2627255037Spjha #include <sys/pci.h>
2727255037Spjha #include <sys/pcie.h>
2827255037Spjha #include <sys/bitmap.h>
2927255037Spjha #include <sys/autoconf.h>
3027255037Spjha #include <sys/sysmacros.h>
3127255037Spjha #include <sys/pci_cap.h>
3227255037Spjha 
3327255037Spjha /*
34fb66942fSCasper H.S. Dik  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
3527255037Spjha  * Use is subject to license terms.
36*c1bbf920SRobert Mustacchi  * Copyright 2022 Oxide Computer Company
3727255037Spjha  */
3827255037Spjha 
3927255037Spjha /*
4027255037Spjha  * Generic PCI Capabilites Interface for all pci platforms
4127255037Spjha  */
4227255037Spjha 
4327255037Spjha #ifdef DEBUG
4427255037Spjha uint_t  pci_cap_debug = 0;
4527255037Spjha #endif
4627255037Spjha 
4727255037Spjha /* Cap Base Macro */
4827255037Spjha #define	PCI_CAP_BASE(h, id, base_p) (*base_p ? DDI_SUCCESS : \
4927255037Spjha 	(id ? PCI_CAP_LOCATE(h, id, base_p) : DDI_FAILURE))
5027255037Spjha 
5127255037Spjha /*
5227255037Spjha  * pci_cap_probe: returns the capid and base based upon a given index
5327255037Spjha  */
5427255037Spjha int
pci_cap_probe(ddi_acc_handle_t h,uint16_t index,uint32_t * id_p,uint16_t * base_p)55*c1bbf920SRobert Mustacchi pci_cap_probe(ddi_acc_handle_t h, uint16_t index, uint32_t *id_p,
56*c1bbf920SRobert Mustacchi     uint16_t *base_p)
5727255037Spjha {
5827255037Spjha 	int i, search_ext = 0;
5927255037Spjha 	uint16_t base, pcix_cmd, status;
6027255037Spjha 	uint32_t id, xcaps_hdr; /* Extended Caps Header Word */
6127255037Spjha 
6227255037Spjha 	status = pci_config_get16(h, PCI_CONF_STAT);
6327255037Spjha 
64dc5d169bSpjha 	if (status == PCI_CAP_EINVAL16 || !(status & PCI_STAT_CAP))
6527255037Spjha 		return (DDI_FAILURE);
6627255037Spjha 
6727255037Spjha 	/* PCIE and PCIX Version 2 contain Extended Config Space */
6827255037Spjha 	for (i = 0, base = pci_config_get8(h, PCI_CONF_CAP_PTR);
69cb7ea99dSJimmy Vetayases 	    base && i < index; base = pci_config_get8(h, base
70cb7ea99dSJimmy Vetayases 	    + PCI_CAP_NEXT_PTR), i++) {
7127255037Spjha 
7227255037Spjha 		if ((id = pci_config_get8(h, base)) == 0xff)
7327255037Spjha 			break;
7427255037Spjha 
7527255037Spjha 		if (id == PCI_CAP_ID_PCI_E)
7627255037Spjha 			search_ext = 1;
7727255037Spjha 		else if (id == PCI_CAP_ID_PCIX) {
7827255037Spjha 			if ((pcix_cmd = pci_config_get16(h, base +
79cb7ea99dSJimmy Vetayases 			    PCI_PCIX_COMMAND)) != PCI_CAP_EINVAL16)
8027255037Spjha 				continue;
8127255037Spjha 			if ((pcix_cmd & PCI_PCIX_VER_MASK) == PCI_PCIX_VER_2)
8227255037Spjha 				search_ext = 1;
8327255037Spjha 		}
8427255037Spjha 	}
8527255037Spjha 
8627255037Spjha 	if (base && i == index) {
8727255037Spjha 		if ((id = pci_config_get8(h, base)) != 0xff)
8827255037Spjha 			goto found;
8927255037Spjha 	}
9027255037Spjha 
9127255037Spjha 	if (!search_ext)
9227255037Spjha 		return (DDI_FAILURE);
9327255037Spjha 
9427255037Spjha 	for (base = PCIE_EXT_CAP; base && i < index; i++) {
95dc5d169bSpjha 		if ((xcaps_hdr = pci_config_get32(h, base)) == PCI_CAP_EINVAL32)
9627255037Spjha 			break;
9727255037Spjha 
9827255037Spjha 		id = (xcaps_hdr >> PCIE_EXT_CAP_ID_SHIFT)
99cb7ea99dSJimmy Vetayases 		    & PCIE_EXT_CAP_ID_MASK;
10027255037Spjha 		base = (xcaps_hdr >> PCIE_EXT_CAP_NEXT_PTR_SHIFT)
101cb7ea99dSJimmy Vetayases 		    & PCIE_EXT_CAP_NEXT_PTR_MASK;
10227255037Spjha 	}
10327255037Spjha 
10427255037Spjha 	if (!base || i < index)
10527255037Spjha 		return (DDI_FAILURE);
10627255037Spjha 
107dc5d169bSpjha 	if ((xcaps_hdr = pci_config_get32(h, base)) == PCI_CAP_EINVAL32)
10827255037Spjha 		return (DDI_FAILURE);
10927255037Spjha 
11027255037Spjha 	id = ((xcaps_hdr >> PCIE_EXT_CAP_ID_SHIFT) & PCIE_EXT_CAP_ID_MASK) |
111cb7ea99dSJimmy Vetayases 	    PCI_CAP_XCFG_FLAG;
11227255037Spjha found:
11327255037Spjha 	PCI_CAP_DBG("pci_cap_probe: index=%x, id=%x, base=%x\n",
114cb7ea99dSJimmy Vetayases 	    index, id, base);
11527255037Spjha 
11627255037Spjha 	*id_p = id;
11727255037Spjha 	*base_p = base;
11827255037Spjha 	return (DDI_SUCCESS);
11927255037Spjha 
12027255037Spjha }
12127255037Spjha 
12227255037Spjha /*
12327255037Spjha  * pci_lcap_locate: Helper function locates a base in conventional config space.
12427255037Spjha  */
12527255037Spjha int
pci_lcap_locate(ddi_acc_handle_t h,uint8_t id,uint16_t * base_p)12627255037Spjha pci_lcap_locate(ddi_acc_handle_t h, uint8_t id, uint16_t *base_p)
12727255037Spjha {
128fb66942fSCasper H.S. Dik 	uint8_t header;
129*c1bbf920SRobert Mustacchi 	uint16_t status, base, ncaps;
13027255037Spjha 
13127255037Spjha 	status = pci_config_get16(h, PCI_CONF_STAT);
13227255037Spjha 
133dc5d169bSpjha 	if (status == PCI_CAP_EINVAL16 || !(status & PCI_STAT_CAP))
13427255037Spjha 		return (DDI_FAILURE);
13527255037Spjha 
136fb66942fSCasper H.S. Dik 	header = pci_config_get8(h, PCI_CONF_HEADER);
137fb66942fSCasper H.S. Dik 	switch (header & PCI_HEADER_TYPE_M) {
138fb66942fSCasper H.S. Dik 	case PCI_HEADER_ZERO:
139fb66942fSCasper H.S. Dik 		base = PCI_CONF_CAP_PTR;
140fb66942fSCasper H.S. Dik 		break;
141fb66942fSCasper H.S. Dik 	case PCI_HEADER_PPB:
142fb66942fSCasper H.S. Dik 		base = PCI_BCNF_CAP_PTR;
143fb66942fSCasper H.S. Dik 		break;
144fb66942fSCasper H.S. Dik 	case PCI_HEADER_CARDBUS:
145fb66942fSCasper H.S. Dik 		base = PCI_CBUS_CAP_PTR;
146fb66942fSCasper H.S. Dik 		break;
147fb66942fSCasper H.S. Dik 	default:
148fb66942fSCasper H.S. Dik 		cmn_err(CE_WARN, "%s: unexpected pci header type:%x",
149cb7ea99dSJimmy Vetayases 		    __func__, header);
150fb66942fSCasper H.S. Dik 		return (DDI_FAILURE);
151fb66942fSCasper H.S. Dik 	}
152fb66942fSCasper H.S. Dik 
153*c1bbf920SRobert Mustacchi 	ncaps = 0;
154fb66942fSCasper H.S. Dik 	for (base = pci_config_get8(h, base); base;
155cb7ea99dSJimmy Vetayases 	    base = pci_config_get8(h, base + PCI_CAP_NEXT_PTR)) {
15627255037Spjha 		if (pci_config_get8(h, base) == id) {
15727255037Spjha 			*base_p = base;
15827255037Spjha 			return (DDI_SUCCESS);
15927255037Spjha 		}
160*c1bbf920SRobert Mustacchi 
161*c1bbf920SRobert Mustacchi 		ncaps++;
162*c1bbf920SRobert Mustacchi 		if (ncaps >= PCI_CAP_MAX_PTR)
163*c1bbf920SRobert Mustacchi 			break;
16427255037Spjha 	}
16527255037Spjha 
16627255037Spjha 	*base_p = PCI_CAP_NEXT_PTR_NULL;
16727255037Spjha 	return (DDI_FAILURE);
16827255037Spjha }
16927255037Spjha 
17027255037Spjha /*
17127255037Spjha  * pci_xcap_locate: Helper function locates a base in extended config space.
17227255037Spjha  */
17327255037Spjha int
pci_xcap_locate(ddi_acc_handle_t h,uint16_t id,uint16_t * base_p)17427255037Spjha pci_xcap_locate(ddi_acc_handle_t h, uint16_t id, uint16_t *base_p)
17527255037Spjha {
17627255037Spjha 	uint16_t status, base;
177*c1bbf920SRobert Mustacchi 	uint32_t xcaps_hdr, ncaps;
17827255037Spjha 
17927255037Spjha 	status = pci_config_get16(h, PCI_CONF_STAT);
18027255037Spjha 
181dc5d169bSpjha 	if (status == PCI_CAP_EINVAL16 || !(status & PCI_STAT_CAP))
18227255037Spjha 		return (DDI_FAILURE);
18327255037Spjha 
184*c1bbf920SRobert Mustacchi 	ncaps = 0;
18527255037Spjha 	for (base = PCIE_EXT_CAP; base; base = (xcaps_hdr >>
186cb7ea99dSJimmy Vetayases 	    PCIE_EXT_CAP_NEXT_PTR_SHIFT) & PCIE_EXT_CAP_NEXT_PTR_MASK) {
18727255037Spjha 
188dc5d169bSpjha 		if ((xcaps_hdr = pci_config_get32(h, base)) == PCI_CAP_EINVAL32)
18927255037Spjha 			break;
19027255037Spjha 
19127255037Spjha 		if (((xcaps_hdr >> PCIE_EXT_CAP_ID_SHIFT) &
192cb7ea99dSJimmy Vetayases 		    PCIE_EXT_CAP_ID_MASK) == id) {
193cb7ea99dSJimmy Vetayases 			*base_p = base;
194cb7ea99dSJimmy Vetayases 			return (DDI_SUCCESS);
195cb7ea99dSJimmy Vetayases 		}
196*c1bbf920SRobert Mustacchi 
197*c1bbf920SRobert Mustacchi 		ncaps++;
198*c1bbf920SRobert Mustacchi 		if (ncaps >= PCIE_EXT_CAP_MAX_PTR)
199*c1bbf920SRobert Mustacchi 			break;
200cb7ea99dSJimmy Vetayases 	}
201cb7ea99dSJimmy Vetayases 
202cb7ea99dSJimmy Vetayases 	*base_p = PCI_CAP_NEXT_PTR_NULL;
203cb7ea99dSJimmy Vetayases 	return (DDI_FAILURE);
204cb7ea99dSJimmy Vetayases }
205cb7ea99dSJimmy Vetayases 
206cb7ea99dSJimmy Vetayases /*
207cb7ea99dSJimmy Vetayases  * There can be multiple pci caps with a Hypertransport technology cap ID
208cb7ea99dSJimmy Vetayases  * Each is distiguished by a type register in the upper half of the cap
209cb7ea99dSJimmy Vetayases  * header (the "command" register part).
210cb7ea99dSJimmy Vetayases  *
211cb7ea99dSJimmy Vetayases  * This returns the location of a hypertransport capability whose upper
212cb7ea99dSJimmy Vetayases  * 16-bits of the cap header matches <reg_val> after masking the value
213cb7ea99dSJimmy Vetayases  * with <reg_mask>; if both <reg_mask> and <reg_val> are 0, it will return
214cb7ea99dSJimmy Vetayases  * the first HT cap found
215cb7ea99dSJimmy Vetayases  */
216cb7ea99dSJimmy Vetayases int
pci_htcap_locate(ddi_acc_handle_t h,uint16_t reg_mask,uint16_t reg_val,uint16_t * base_p)217cb7ea99dSJimmy Vetayases pci_htcap_locate(ddi_acc_handle_t h, uint16_t reg_mask, uint16_t reg_val,
218cb7ea99dSJimmy Vetayases     uint16_t *base_p)
219cb7ea99dSJimmy Vetayases {
220cb7ea99dSJimmy Vetayases 	uint8_t header;
221cb7ea99dSJimmy Vetayases 	uint16_t status, base;
222cb7ea99dSJimmy Vetayases 
223cb7ea99dSJimmy Vetayases 	status = pci_config_get16(h, PCI_CONF_STAT);
224cb7ea99dSJimmy Vetayases 
225cb7ea99dSJimmy Vetayases 	if (status == PCI_CAP_EINVAL16 || !(status & PCI_STAT_CAP))
226cb7ea99dSJimmy Vetayases 		return (DDI_FAILURE);
227cb7ea99dSJimmy Vetayases 
228cb7ea99dSJimmy Vetayases 	header = pci_config_get8(h, PCI_CONF_HEADER);
229cb7ea99dSJimmy Vetayases 	switch (header & PCI_HEADER_TYPE_M) {
230cb7ea99dSJimmy Vetayases 	case PCI_HEADER_ZERO:
231cb7ea99dSJimmy Vetayases 		base = PCI_CONF_CAP_PTR;
232cb7ea99dSJimmy Vetayases 		break;
233cb7ea99dSJimmy Vetayases 	case PCI_HEADER_PPB:
234cb7ea99dSJimmy Vetayases 		base = PCI_BCNF_CAP_PTR;
235cb7ea99dSJimmy Vetayases 		break;
236cb7ea99dSJimmy Vetayases 	default:
237cb7ea99dSJimmy Vetayases 		cmn_err(CE_WARN, "%s: unexpected pci header type:%x",
238cb7ea99dSJimmy Vetayases 		    __func__, header);
239cb7ea99dSJimmy Vetayases 		return (DDI_FAILURE);
240cb7ea99dSJimmy Vetayases 	}
241cb7ea99dSJimmy Vetayases 
242cb7ea99dSJimmy Vetayases 	for (base = pci_config_get8(h, base); base;
243cb7ea99dSJimmy Vetayases 	    base = pci_config_get8(h, base + PCI_CAP_NEXT_PTR)) {
244cb7ea99dSJimmy Vetayases 		if (pci_config_get8(h, base) == PCI_CAP_ID_HT &&
245cb7ea99dSJimmy Vetayases 		    (pci_config_get16(h, base + PCI_CAP_ID_REGS_OFF) &
246cb7ea99dSJimmy Vetayases 		    reg_mask) == reg_val) {
24727255037Spjha 			*base_p = base;
24827255037Spjha 			return (DDI_SUCCESS);
24927255037Spjha 		}
25027255037Spjha 	}
25127255037Spjha 
25227255037Spjha 	*base_p = PCI_CAP_NEXT_PTR_NULL;
25327255037Spjha 	return (DDI_FAILURE);
25427255037Spjha }
25527255037Spjha 
25627255037Spjha /*
25727255037Spjha  * pci_cap_get: This function uses the base or capid to get a byte, word,
25827255037Spjha  * or dword. If access by capid is requested, the function uses the capid to
25927255037Spjha  * locate the base. Access by a base results in better performance
26027255037Spjha  * because no cap list traversal is required.
26127255037Spjha  */
26227255037Spjha uint32_t
pci_cap_get(ddi_acc_handle_t h,pci_cap_config_size_t size,uint32_t id,uint16_t base,uint16_t offset)263*c1bbf920SRobert Mustacchi pci_cap_get(ddi_acc_handle_t h, pci_cap_config_size_t size, uint32_t id,
264*c1bbf920SRobert Mustacchi     uint16_t base, uint16_t offset)
26527255037Spjha {
26627255037Spjha 	uint32_t data;
26727255037Spjha 
26827255037Spjha 	if (PCI_CAP_BASE(h, id, &base) != DDI_SUCCESS)
269dc5d169bSpjha 		return (PCI_CAP_EINVAL32);
27027255037Spjha 
27127255037Spjha 	/*
272911fc2e5Spjha 	 * Each access to a PCI Configuration Space should be checked
273911fc2e5Spjha 	 * by the calling function. A returned value of the 2's complement
274911fc2e5Spjha 	 * of -1 indicates that either the device is offlined or it does not
275911fc2e5Spjha 	 * exist.
27627255037Spjha 	 */
27727255037Spjha 	offset += base;
27827255037Spjha 
27927255037Spjha 	switch (size) {
28027255037Spjha 	case PCI_CAP_CFGSZ_8:
281911fc2e5Spjha 		data = pci_config_get8(h, offset);
28227255037Spjha 		break;
28327255037Spjha 	case PCI_CAP_CFGSZ_16:
284911fc2e5Spjha 		data = pci_config_get16(h, offset);
28527255037Spjha 		break;
28627255037Spjha 	case PCI_CAP_CFGSZ_32:
287911fc2e5Spjha 		data = pci_config_get32(h, offset);
28827255037Spjha 		break;
28927255037Spjha 	default:
290dc5d169bSpjha 		data = PCI_CAP_EINVAL32;
29127255037Spjha 	}
29227255037Spjha 
29327255037Spjha 	PCI_CAP_DBG("pci_cap_get: %p[x%x]=x%x\n", (void *)h, offset, data);
29427255037Spjha 	return (data);
29527255037Spjha }
29627255037Spjha 
29727255037Spjha /*
29827255037Spjha  * pci_cap_put: This function uses the caps ptr or capid to put a byte, word,
29927255037Spjha  * or dword. If access by capid is requested, the function uses the capid to
30027255037Spjha  * locate the base. Access by base results in better performance
30127255037Spjha  * because no cap list traversal is required.
30227255037Spjha  */
30327255037Spjha int
pci_cap_put(ddi_acc_handle_t h,pci_cap_config_size_t size,uint32_t id,uint16_t base,uint16_t offset,uint32_t data)3043c4226f9Spjha pci_cap_put(ddi_acc_handle_t h, pci_cap_config_size_t size,
305*c1bbf920SRobert Mustacchi     uint32_t id, uint16_t base, uint16_t offset, uint32_t data)
30627255037Spjha {
30727255037Spjha 
30827255037Spjha 	/*
30927255037Spjha 	 * use the pci_config_size_t to switch for the appropriate read
31027255037Spjha 	 */
31127255037Spjha 	if (PCI_CAP_BASE(h, id, &base) != DDI_SUCCESS)
31227255037Spjha 		return (DDI_FAILURE);
31327255037Spjha 
31427255037Spjha 	offset += base;
31527255037Spjha 
31627255037Spjha 	switch (size) {
31727255037Spjha 	case PCI_CAP_CFGSZ_8:
31827255037Spjha 		pci_config_put8(h, offset, data);
31927255037Spjha 		break;
32027255037Spjha 	case PCI_CAP_CFGSZ_16:
32127255037Spjha 		pci_config_put16(h, offset, data);
32227255037Spjha 		break;
32327255037Spjha 	case PCI_CAP_CFGSZ_32:
32427255037Spjha 		pci_config_put32(h, offset, data);
32527255037Spjha 		break;
32627255037Spjha 	default:
32727255037Spjha 		return (DDI_FAILURE);
32827255037Spjha 	}
32927255037Spjha 
33027255037Spjha 	PCI_CAP_DBG("pci_cap_put: data=%x\n", data);
33127255037Spjha 	return (DDI_SUCCESS);
33227255037Spjha }
33327255037Spjha 
33427255037Spjha /*
33527255037Spjha  * Cache the entire Cap Structure.  The caller is required to allocate and free
33627255037Spjha  * buffer.
33727255037Spjha  */
33827255037Spjha int
pci_cap_read(ddi_acc_handle_t h,uint32_t id,uint16_t base,uint32_t * buf_p,uint32_t nwords)33927255037Spjha pci_cap_read(ddi_acc_handle_t h, uint32_t id, uint16_t base,
340*c1bbf920SRobert Mustacchi     uint32_t *buf_p, uint32_t nwords)
34127255037Spjha {
34227255037Spjha 
34327255037Spjha 	int i;
34427255037Spjha 	uint32_t *ptr;
34527255037Spjha 
34627255037Spjha 	ASSERT(nwords < 1024);
34727255037Spjha 
34827255037Spjha 	if (PCI_CAP_BASE(h, id, &base) != DDI_SUCCESS)
34927255037Spjha 		return (DDI_FAILURE);
35027255037Spjha 
35127255037Spjha 	for (ptr = buf_p, i = 0; i < nwords; i++, base += 4) {
352dc5d169bSpjha 		if ((*ptr++ = pci_config_get32(h, base)) == PCI_CAP_EINVAL32)
35327255037Spjha 			return (DDI_FAILURE);
35427255037Spjha 	}
35527255037Spjha 
35627255037Spjha 	return (DDI_SUCCESS);
35727255037Spjha }
358