xref: /illumos-gate/usr/src/lib/fm/topo/modules/common/pciebus/topo_pcie_cfgspace.c (revision 84ceaea936ebcf122d4f0756d298adf307fd491d)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright 2023 Oxide Computer Company
14  */
15 
16 #include <fcntl.h>
17 #include <stdbool.h>
18 #include <string.h>
19 #include <strings.h>
20 #include <stropts.h>
21 #include <unistd.h>
22 #include <sys/debug.h>
23 #include <sys/types.h>
24 #include <sys/pci.h>
25 #include <sys/pcie.h>
26 #include <sys/stat.h>
27 #include <sys/pci_tools.h>
28 
29 #include "topo_pcie_impl.h"
30 
31 static bool
read_cfgspace(topo_mod_t * mod,pcie_node_t * node,int fd,uint32_t off,uint8_t len,void * buf)32 read_cfgspace(topo_mod_t *mod, pcie_node_t *node,
33     int fd, uint32_t off, uint8_t len, void *buf)
34 {
35 	pcitool_reg_t pci_reg;
36 	bool ret = true;
37 
38 	bzero(&pci_reg, sizeof (pci_reg));
39 	pci_reg.user_version = PCITOOL_VERSION;
40 	pci_reg.bus_no = node->pn_bus;
41 	pci_reg.dev_no = node->pn_dev;
42 	pci_reg.func_no = node->pn_func;
43 	pci_reg.barnum = 0;
44 	pci_reg.offset = off;
45 	pci_reg.acc_attr = PCITOOL_ACC_ATTR_ENDN_LTL;
46 
47 	switch (len) {
48 	case 1:
49 		pci_reg.acc_attr += PCITOOL_ACC_ATTR_SIZE_1;
50 		break;
51 	case 2:
52 		pci_reg.acc_attr += PCITOOL_ACC_ATTR_SIZE_2;
53 		break;
54 	case 4:
55 		pci_reg.acc_attr += PCITOOL_ACC_ATTR_SIZE_4;
56 		break;
57 	case 8:
58 		pci_reg.acc_attr += PCITOOL_ACC_ATTR_SIZE_8;
59 		break;
60 	default:
61 		abort();
62 	}
63 
64 	if (ioctl(fd, PCITOOL_DEVICE_GET_REG, &pci_reg) != 0) {
65 		topo_mod_dprintf(mod, "ioctl(GET_REG) failed: %s",
66 		    strerror(errno));
67 		return (false);
68 	}
69 
70 	switch (len) {
71 	case 1:
72 		*(uint8_t *)buf = (uint8_t)pci_reg.data;
73 		ret = (uint8_t)pci_reg.data != PCI_EINVAL8;
74 		break;
75 	case 2:
76 		*(uint16_t *)buf = (uint16_t)pci_reg.data;
77 		ret = (uint16_t)pci_reg.data != PCI_EINVAL16;
78 		break;
79 	case 4:
80 		*(uint32_t *)buf = (uint32_t)pci_reg.data;
81 		ret = (uint32_t)pci_reg.data != PCI_EINVAL32;
82 		break;
83 	case 8:
84 		*(uint64_t *)buf = (uint64_t)pci_reg.data;
85 		ret = (uint64_t)pci_reg.data != PCI_EINVAL64;
86 		break;
87 	}
88 
89 	return (ret);
90 }
91 
92 static int
open_nexus(topo_mod_t * mod,pcie_node_t * node)93 open_nexus(topo_mod_t *mod, pcie_node_t *node)
94 {
95 	char nexus_reg[PATH_MAX];
96 	pcie_node_t *nexus;
97 	int fd;
98 
99 	for (nexus = node; nexus->pn_type != PCIE_NODE_ROOTNEXUS; nexus =
100 	    nexus->pn_parent)
101 		;
102 
103 	VERIFY3P(nexus, !=, NULL);
104 
105 	if (snprintf(nexus_reg, sizeof (nexus_reg), "/devices%s:reg",
106 	    nexus->pn_path) >= sizeof (nexus_reg)) {
107 		topo_mod_dprintf(mod,
108 		    "failed to construct nexus reg path; overflow");
109 		return (-1);
110 	}
111 
112 	if ((fd = open(nexus_reg, O_RDONLY)) < 0) {
113 		topo_mod_dprintf(mod, "failed to open %s: %s", nexus_reg,
114 		    strerror(errno));
115 		return (-1);
116 	}
117 
118 	return (fd);
119 }
120 
121 topo_pcie_link_status_t
topo_pcie_link_status(topo_mod_t * mod,pcie_node_t * node)122 topo_pcie_link_status(topo_mod_t *mod, pcie_node_t *node)
123 {
124 	int fd;
125 	uint8_t hdr, off;
126 	uint16_t status;
127 	uint32_t cap;
128 	uint_t ncaps;
129 	topo_pcie_link_status_t ret = PCI_LINK_UNKNOWN;
130 
131 	if ((fd = open_nexus(mod, node)) == -1)
132 		return (ret);
133 
134 	if (!read_cfgspace(mod, node, fd, PCI_CONF_STAT, 2, &status)) {
135 		topo_mod_dprintf(mod, "failed to read status register");
136 		goto out;
137 	}
138 
139 	if ((status & PCI_STAT_CAP) == 0) {
140 		topo_mod_dprintf(mod, "capabilities not supported");
141 		goto out;
142 	}
143 
144 	if (!read_cfgspace(mod, node, fd, PCI_CONF_HEADER, 1, &hdr)) {
145 		topo_mod_dprintf(mod, "failed to read header type");
146 		goto out;
147 	}
148 
149 	switch (hdr & PCI_HEADER_TYPE_M) {
150 	case PCI_HEADER_ZERO:
151 		cap = PCI_CONF_CAP_PTR;
152 		break;
153 	case PCI_HEADER_PPB:
154 		cap = PCI_BCNF_CAP_PTR;
155 		break;
156 	default:
157 		topo_mod_dprintf(mod, "unhandled PCI header type %x", hdr);
158 		goto out;
159 	}
160 
161 	if (!read_cfgspace(mod, node, fd, cap, 1, &off)) {
162 		topo_mod_dprintf(mod, "failed to read capabilities pointer");
163 		goto out;
164 	}
165 
166 	ncaps = 0;
167 	while (off != 0 && off != PCI_EINVAL8) {
168 		uint8_t id, nxt;
169 
170 		off &= PCI_CAP_PTR_MASK;
171 		if (!read_cfgspace(mod, node, fd,
172 		    off + PCI_CAP_ID, 1, &id)) {
173 			topo_mod_dprintf(mod, "failed to read capability ID");
174 			break;
175 		}
176 
177 		if (id == PCI_CAP_ID_PCI_E) {
178 			uint16_t pciecap, status, pciever;
179 			uint32_t linkcap;
180 
181 			topo_mod_dprintf(mod, "Found PCIe capability at %x",
182 			    off);
183 
184 			if (!read_cfgspace(mod, node, fd,
185 			    off + PCIE_PCIECAP, 2, &pciecap)) {
186 				topo_mod_dprintf(mod, "failed to read PCIe "
187 				    "capabilities register");
188 				break;
189 			}
190 
191 			pciever = pciecap & PCIE_PCIECAP_VER_MASK;
192 			if (pciever != PCIE_PCIECAP_VER_1_0 &&
193 			    pciever != PCIE_PCIECAP_VER_2_0) {
194 				topo_mod_dprintf(mod, "unsupported version "
195 				    "in PCIe capabilities register: 0x%x",
196 				    pciever);
197 				break;
198 			}
199 
200 			/*
201 			 * In version 1 of the PCIe capability, devices were
202 			 * not required to implement the entire capability.
203 			 * Whilst most devices implemented the link status
204 			 * register, the v1 capability for an RC IEP does not
205 			 * include this, and stops short of the link status
206 			 * offset.
207 			 */
208 			if (pciever == PCIE_PCIECAP_VER_1_0 &&
209 			    (pciecap & PCIE_PCIECAP_DEV_TYPE_MASK) ==
210 			    PCIE_PCIECAP_DEV_TYPE_RC_IEP) {
211 				topo_mod_dprintf(mod, "RC IEP does not have "
212 				    "a link status register");
213 				break;
214 			}
215 
216 			if (!read_cfgspace(mod, node, fd,
217 			    off + PCIE_LINKCAP, 4, &linkcap)) {
218 				topo_mod_dprintf(mod, "failed to read link "
219 				    "capabilities register");
220 				break;
221 			}
222 
223 			if ((linkcap & PCIE_LINKCAP_DLL_ACTIVE_REP_CAPABLE) ==
224 			    0) {
225 				break;
226 			}
227 
228 			if (!read_cfgspace(mod, node, fd,
229 			    off + PCIE_LINKSTS, 2, &status)) {
230 				topo_mod_dprintf(mod,
231 				    "failed to read link status register");
232 				break;
233 			}
234 
235 			if ((status & PCIE_LINKSTS_DLL_LINK_ACTIVE) != 0)
236 				ret = PCI_LINK_UP;
237 			else
238 				ret = PCI_LINK_DOWN;
239 			break;
240 		}
241 
242 		if (!read_cfgspace(mod, node, fd,
243 		    off + PCI_CAP_NEXT_PTR, 1, &nxt)) {
244 			topo_mod_dprintf(mod,
245 			    "failed to read next capability pointer");
246 			break;
247 		}
248 
249 		off = nxt;
250 		ncaps++;
251 		if (ncaps >= PCI_CAP_MAX_PTR) {
252 			topo_mod_dprintf(mod, "encountered more PCI "
253 			    "capabilities than fit in configuration space");
254 			break;
255 		}
256 	}
257 
258 out:
259 	topo_mod_dprintf(mod, "Reporting link as %s", ret ? "UP" : "DOWN");
260 	VERIFY0(close(fd));
261 	return (ret);
262 }
263