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