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 /*
23  * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
24  * Copyright 2019 Joyent, Inc.
25  */
26 
27 #include <alloca.h>
28 #include <assert.h>
29 #include <fm/topo_mod.h>
30 #include <libnvpair.h>
31 #include <string.h>
32 #include <sys/fm/protocol.h>
33 
34 #include <did.h>
35 #include <pcibus.h>
36 #include <pcibus_labels.h>
37 
38 extern slotnm_rewrite_t *Slot_Rewrites;
39 extern physlot_names_t *Physlot_Names;
40 extern missing_names_t *Missing_Names;
41 
42 /*
43  * Do a platform specific label lookup based on physical slot number.
44  */
45 static const char *
46 pci_label_physlot_lookup(topo_mod_t *mod, char *platform, did_t *dp)
47 {
48 	const char *rlabel = NULL;
49 	int n, p, i;
50 
51 	topo_mod_dprintf(mod, "%s: doing a lookup for platform=%s\n",
52 	    __func__, platform);
53 
54 	if ((n = did_physlot(dp)) < 0 || Physlot_Names == NULL ||
55 	    platform == NULL)
56 		return (NULL);
57 
58 	topo_mod_dprintf(mod, "%s: doing a lookup for physlot=%d\n",
59 	    __func__, n);
60 
61 	for (p = 0; p < Physlot_Names->psn_nplats; p++) {
62 		topo_mod_dprintf(mod, "%s: comparing against platform=%s\n",
63 		    __func__, Physlot_Names->psn_names[p].pnm_platform);
64 		if (strcasecmp(Physlot_Names->psn_names[p].pnm_platform,
65 		    platform) != 0)
66 			continue;
67 		topo_mod_dprintf(mod, "%s: found lookup table for this "
68 		    "platform\n", __func__);
69 		for (i = 0; i < Physlot_Names->psn_names[p].pnm_nnames; i++) {
70 			physnm_t ps;
71 			ps = Physlot_Names->psn_names[p].pnm_names[i];
72 			if (ps.ps_num == n) {
73 				topo_mod_dprintf(mod, "%s: matched entry=%d, "
74 				    "label=%s\n", __func__, i, ps.ps_label);
75 				rlabel = ps.ps_label;
76 				break;
77 			}
78 		}
79 		break;
80 	}
81 	if (rlabel != NULL) {
82 		topo_mod_dprintf(mod, "%s: returning label=%s\n",
83 		    __func__, rlabel);
84 	}
85 	return (rlabel);
86 }
87 
88 /*
89  * Do a platform specific label lookup based on slot name.
90  */
91 static const char *
92 pci_label_slotname_lookup(topo_mod_t *mod, char *platform,
93     const char *label, did_t *dp)
94 {
95 	const char *rlabel = label;
96 	int s, i, ret;
97 
98 	if (Slot_Rewrites == NULL || platform == NULL)
99 		return (rlabel);
100 
101 	topo_mod_dprintf(mod, "%s: doing a lookup for platform=%s\n",
102 	    __func__, platform);
103 
104 	for (s = 0; s < Slot_Rewrites->srw_nplats; s++) {
105 		topo_mod_dprintf(mod, "%s: comparing against platform=%s\n",
106 		    __func__, Slot_Rewrites->srw_platrewrites[s].prw_platform);
107 		if (strcasecmp(Slot_Rewrites->srw_platrewrites[s].prw_platform,
108 		    platform) != 0)
109 			continue;
110 		topo_mod_dprintf(mod, "%s: found lookup table for this "
111 		    "platform\n", __func__);
112 		for (i = 0;
113 		    i < Slot_Rewrites->srw_platrewrites[s].prw_nrewrites;
114 		    i++) {
115 			slot_rwd_t rw;
116 			rw = Slot_Rewrites->srw_platrewrites[s].prw_rewrites[i];
117 			if (strcmp(rw.srw_obp, label) == 0) {
118 				topo_mod_dprintf(mod, "%s: matched entry=%d, "
119 				    "old_label=%s, new_label=%s\n",
120 				    __func__, i, rw.srw_obp,
121 				    rw.srw_new ? rw.srw_new : NULL);
122 				/*
123 				 * If a test function is specified then call
124 				 * it to do an additional check.
125 				 */
126 				if (rw.srw_test != NULL) {
127 					topo_mod_dprintf(mod,
128 					    "%s: calling test function=%p\n",
129 					    __func__, rw.srw_test);
130 					if ((ret = rw.srw_test(mod, dp)) != 0)
131 						rlabel = rw.srw_new;
132 					topo_mod_dprintf(mod,
133 					    "%s: test function return=%d\n",
134 					    __func__, ret);
135 				} else {
136 					rlabel = rw.srw_new;
137 				}
138 				break;
139 			}
140 		}
141 		break;
142 	}
143 	topo_mod_dprintf(mod, "%s: returning label=%s\n", __func__,
144 	    rlabel ? rlabel : "NULL");
145 	return (rlabel);
146 }
147 
148 /*
149  * Do a platform specific label lookup based on bus, dev, etc.
150  */
151 static const char *
152 pci_label_missing_lookup(topo_mod_t *mod, char *platform, did_t *dp)
153 {
154 	const char *rlabel = NULL;
155 	int board, bridge, rc, bus, dev;
156 	int p, i, ret;
157 
158 	if (Missing_Names == NULL || platform == NULL)
159 		return (NULL);
160 
161 	bridge = did_bridge(dp);
162 	board = did_board(dp);
163 	rc = did_rc(dp);
164 	did_BDF(dp, &bus, &dev, NULL);
165 
166 	topo_mod_dprintf(mod, "%s: doing a lookup for platform=%s, "
167 	    "board=%d, bridge=%d, rc=%d, bus=%d, dev=%d\n",
168 	    __func__, platform, board, bridge, rc, bus, dev);
169 
170 	for (p = 0; p < Missing_Names->mn_nplats; p++) {
171 		topo_mod_dprintf(mod, "%s: comparing against platform=%s\n",
172 		    __func__, Missing_Names->mn_names[p].pdl_platform);
173 		if (strcasecmp(Missing_Names->mn_names[p].pdl_platform,
174 		    platform) != 0)
175 			continue;
176 		topo_mod_dprintf(mod, "%s: found lookup table for this "
177 		    "platform\n", __func__);
178 		for (i = 0; i < Missing_Names->mn_names[p].pdl_nnames; i++) {
179 			devlab_t m;
180 			m = Missing_Names->mn_names[p].pdl_names[i];
181 			if (m.dl_board == board && m.dl_bridge == bridge &&
182 			    m.dl_rc == rc &&
183 			    (m.dl_bus == -1 || m.dl_bus == bus) &&
184 			    (m.dl_dev == -1 || m.dl_dev == dev)) {
185 				topo_mod_dprintf(mod, "%s: matched entry=%d, "
186 				    "label=%s\n", __func__, i, m.dl_label);
187 				/*
188 				 * If a test function is specified then call
189 				 * it to do an additional test.
190 				 */
191 				if (m.dl_test != NULL) {
192 					topo_mod_dprintf(mod,
193 					    "%s: calling test function=%p\n",
194 					    __func__, m.dl_test);
195 					if ((ret = m.dl_test(mod, dp)) != 0)
196 						rlabel = m.dl_label;
197 					topo_mod_dprintf(mod,
198 					    "%s: test function return=%d\n",
199 					    __func__, ret);
200 					if (ret)
201 						break;
202 				} else {
203 					rlabel = m.dl_label;
204 					break;
205 				}
206 			}
207 		}
208 		break;
209 	}
210 	if (rlabel != NULL) {
211 		topo_mod_dprintf(mod, "%s: match found, label=%s\n",
212 		    __func__, rlabel);
213 	}
214 	return (rlabel);
215 }
216 
217 /*
218  * Do an overall slot label lookup for the device node.
219  */
220 char *
221 pci_slot_label_lookup(topo_mod_t *mod, tnode_t *node, did_t *dp, did_t *pdp)
222 {
223 	tnode_t *anode, *apnode;
224 	did_t *adp, *apdp;
225 	char *plat, *pp, *l = NULL, *ancestor_l = NULL, *new_l = NULL;
226 	int err, b, d, f, done = 0;
227 	size_t len;
228 
229 	did_BDF(dp, &b, &d, &f);
230 
231 	topo_mod_dprintf(mod, "%s: entry: node=%p, node_name=%s, "
232 	    "node_inst=%d, dp=%p, dp_bdf=%d/%d/%d, pdp=%p\n",
233 	    __func__, node, topo_node_name(node), topo_node_instance(node),
234 	    dp, b, d, f, pdp);
235 
236 	/*
237 	 * If this device has a physical slot number then check if
238 	 * an ancestor also has a slot label.
239 	 *
240 	 * If an ancestor has a slot label, then this node's label
241 	 * is generated by concatenating a default label onto the
242 	 * ancestor's label.
243 	 *
244 	 * We grab pairs of ancestors (parent and child) as we go up
245 	 * the tree because the parent is checked for the presence
246 	 * of a slot while the child contains the label.
247 	 *
248 	 * Note that this algorithm only applies to nodes which have
249 	 * a physical slot number. (i.e. PCIE devices or PCI/PCIX
250 	 * devices off of a PCIE to PCIX switch)
251 	 */
252 	if (did_physlot(pdp) >= 0) {
253 
254 		topo_mod_dprintf(mod, "%s: node=%p: node has a physical "
255 		    "slot=%d, checking ancestors for slots\n",
256 		    __func__, node, did_physlot(pdp));
257 
258 		/*
259 		 * Get this device's physical slot name.
260 		 */
261 		l = (char *)did_physlot_name(pdp, d);
262 		anode = topo_node_parent(node);
263 
264 		/*
265 		 * Check ancestors for a slot label until we
266 		 * either find one or hit a non-pci device.
267 		 */
268 		while (!done) {
269 
270 			/*
271 			 * Get next ancestor node and data pointers.
272 			 */
273 			anode = topo_node_parent(anode);
274 			if (anode != NULL) {
275 				adp = did_find(mod,
276 				    topo_node_getspecific(anode));
277 				apnode = topo_node_parent(anode);
278 				if (apnode != NULL)
279 					apdp = did_find(mod,
280 					    topo_node_getspecific(apnode));
281 				else
282 					apdp = NULL;
283 			} else {
284 				apnode = NULL;
285 				apdp = adp = NULL;
286 			}
287 
288 			topo_mod_dprintf(mod, "%s: node=%p: checking next "
289 			    "two ancestors: anode=%p, adp=%p "
290 			    "apnode=%p, apdp=%p\n",
291 			    __func__, node, anode, adp, apnode, apdp);
292 			if ((anode != NULL) && (adp != NULL)) {
293 				did_BDF(adp, &b, &d, &f);
294 				topo_mod_dprintf(mod, "%s: node=%p: "
295 				    "anode_name=%s[%d], anode_bdf=%d/%d/%d\n",
296 				    __func__, node, topo_node_name(anode),
297 				    topo_node_instance(anode), b, d, f);
298 			}
299 			if ((apnode != NULL) && (apdp != NULL)) {
300 				did_BDF(apdp, &b, &d, &f);
301 				topo_mod_dprintf(mod, "%s: node=%p: "
302 				    "apnode_name=%s[%d], "
303 				    "apnode_bdf=%d/%d/%d\n",
304 				    __func__, node, topo_node_name(apnode),
305 				    topo_node_instance(apnode), b, d, f);
306 			}
307 
308 			/*
309 			 * If the ancestors do not exist or are not pci
310 			 * devices then we're done searching.
311 			 *
312 			 * Otherwise, if the ancestor has a physical slot,
313 			 * and it is a different slot than the one we
314 			 * started with then lookup the ancestor label,
315 			 * and we're done.
316 			 */
317 			if ((anode == NULL) || (adp == NULL) ||
318 			    (apnode == NULL) || (apdp == NULL)) {
319 				done++;
320 			} else if (did_physlot_exists(apdp) &&
321 			    (apdp != pdp)) {
322 				if (topo_node_label(anode, &ancestor_l,
323 				    &err) != 0) {
324 					topo_mod_dprintf(mod,
325 					    "%s: node=%p: topo_node_label() "
326 					    "FAILED!", __func__, node);
327 					(void) topo_mod_seterrno(mod, err);
328 					return (NULL);
329 				}
330 				done++;
331 				topo_mod_dprintf(mod, "%s: node=%p: found "
332 				    "ancestor with a slot, label=%s ",
333 				    __func__, node, ancestor_l);
334 			}
335 		}
336 		if (ancestor_l == NULL) {
337 			topo_mod_dprintf(mod, "%s: node=%p: no ancestor "
338 			    "slot found\n", __func__, node);
339 		}
340 	}
341 
342 	/*
343 	 * If we found an ancestor with a slot label, and this node has
344 	 * a physical slot number label then concatenate the two to form
345 	 * this node's label. Otherwise, do a full slot label lookup.
346 	 */
347 	if (ancestor_l && l) {
348 		topo_mod_dprintf(mod, "%s: node=%p: concatenating "
349 		    "ancestor_l=%s and l=%s\n",
350 		    __func__, node, ancestor_l, l);
351 		len = strlen(ancestor_l) + strlen(l) + 2;
352 		new_l = alloca(len);
353 		(void) snprintf(new_l, len, "%s/%s", ancestor_l, l);
354 		l = new_l;
355 	} else {
356 		/*
357 		 * Get platform name used for lookups.
358 		 */
359 		if (topo_prop_get_string(node, FM_FMRI_AUTHORITY,
360 		    FM_FMRI_AUTH_PRODUCT, &plat, &err) < 0) {
361 			(void) topo_mod_seterrno(mod, err);
362 			return (NULL);
363 		}
364 		/*
365 		 * Trim SUNW, from the platform name
366 		 */
367 		pp = strchr(plat, ',');
368 		if (pp == NULL)
369 			pp = plat;
370 		else
371 			++pp;
372 		/*
373 		 * Get device number used for lookup.
374 		 */
375 		did_BDF(dp, NULL, &d, NULL);
376 
377 		/*
378 		 * The slot label is determined in the following order:
379 		 * - Platform specific lookup based on physical slot #.
380 		 * - Platform specific lookup based on default label string.
381 		 * - Platform specific lookup based on device number.
382 		 * - Default label.
383 		 *   The default label is based on the slot names property
384 		 *   if it exists, else it is a generic name derived from
385 		 *   the slot #.
386 		 */
387 		if ((l = (char *)pci_label_physlot_lookup(mod, pp, pdp))
388 		    == NULL) {
389 			if ((l = (char *)did_physlot_name(dp, d)) != NULL) {
390 				l = (char *)
391 				    pci_label_slotname_lookup(mod, pp, l, dp);
392 			}
393 			if (l == NULL) {
394 				l = (char *)
395 				    pci_label_missing_lookup(mod, pp, dp);
396 			}
397 		}
398 		topo_mod_strfree(mod, plat);
399 	}
400 
401 	/*
402 	 * If we calculated a slot label,  then save it in the
403 	 * node's data structure so we can free it later.
404 	 */
405 	if (l) {
406 		if (did_slot_label_get(dp) != NULL)
407 			topo_mod_strfree(mod, did_slot_label_get(dp));
408 		l = topo_mod_strdup(mod, l);
409 		did_slot_label_set(dp, l);
410 	}
411 
412 	topo_mod_dprintf(mod, "%s: exit: node=%p: label=%s\n",
413 	    __func__, node, (l ? l : "NULL"));
414 
415 	return (l);
416 }
417 
418 int
419 pci_label_cmn(topo_mod_t *mod, tnode_t *node, nvlist_t *in, nvlist_t **out)
420 {
421 	uint64_t ptr;
422 	char *l;
423 	did_t *dp, *pdp;
424 	tnode_t *pnode;
425 	char *nm;
426 	int err;
427 
428 	/*
429 	 * If it's not a device or a PCI-express bus (which could potentially
430 	 * represent a slot, and therefore we might need to capture its slot
431 	 * name information), just inherit any label from our parent
432 	 */
433 	*out = NULL;
434 	nm = topo_node_name(node);
435 	if (strcmp(nm, PCI_DEVICE) != 0 && strcmp(nm, PCIEX_DEVICE) != 0 &&
436 	    strcmp(nm, PCIEX_BUS) != 0) {
437 		if (topo_node_label_set(node, NULL, &err) < 0)
438 			if (err != ETOPO_PROP_NOENT)
439 				return (topo_mod_seterrno(mod, err));
440 		return (0);
441 	}
442 
443 	if (nvlist_lookup_uint64(in, TOPO_METH_LABEL_ARG_NVL, &ptr) != 0) {
444 		topo_mod_dprintf(mod,
445 		    "%s: label method argument not found.\n", __func__);
446 		return (-1);
447 	}
448 	dp = (did_t *)(uintptr_t)ptr;
449 	pnode = did_gettnode(dp);
450 	pdp = did_find(mod, topo_node_getspecific(pnode));
451 
452 	/*
453 	 * Is there a slot label associated with the device?
454 	 */
455 	if ((l = pci_slot_label_lookup(mod, node, dp, pdp)) != NULL) {
456 		nvlist_t *rnvl;
457 
458 		if (topo_mod_nvalloc(mod, &rnvl, NV_UNIQUE_NAME) != 0 ||
459 		    nvlist_add_string(rnvl, TOPO_METH_LABEL_RET_STR, l) != 0)
460 			return (topo_mod_seterrno(mod, EMOD_FMRI_NVL));
461 		*out = rnvl;
462 		return (0);
463 	} else {
464 		if (topo_node_label_set(node, NULL, &err) < 0)
465 			if (err != ETOPO_PROP_NOENT)
466 				return (topo_mod_seterrno(mod, err));
467 		return (0);
468 	}
469 }
470 
471 int
472 pci_fru_cmn(topo_mod_t *mod, tnode_t *node, nvlist_t *in, nvlist_t **out)
473 {
474 	int err = 0;
475 	uint64_t ptr;
476 	did_t *dp, *pdp;
477 	tnode_t *pnode;
478 	char *nm;
479 
480 	*out = NULL;
481 	nm = topo_node_name(node);
482 	if (strcmp(nm, PCI_DEVICE) != 0 && strcmp(nm, PCIEX_DEVICE) != 0 &&
483 	    strcmp(nm, PCIEX_BUS) != 0)
484 		return (0);
485 
486 	if (nvlist_lookup_uint64(in, "nv1", &ptr) != 0) {
487 		topo_mod_dprintf(mod,
488 		    "%s: label method argument not found.\n", __func__);
489 		return (-1);
490 	}
491 	dp = (did_t *)(uintptr_t)ptr;
492 	pnode = did_gettnode(dp);
493 	pdp = did_find(mod, topo_node_getspecific(pnode));
494 
495 	/*
496 	 * Is there a slot label associated with the device?
497 	 */
498 	if (pci_slot_label_lookup(mod, pnode, dp, pdp) != NULL) {
499 		nvlist_t *rnvl;
500 
501 		if (topo_node_resource(node, &rnvl, &err) < 0 || rnvl == NULL) {
502 			topo_mod_dprintf(mod, "%s: error: %s\n",
503 			    __func__, topo_strerror(topo_mod_errno(mod)));
504 			return (topo_mod_seterrno(mod, err));
505 		}
506 		*out = rnvl;
507 	}
508 	return (0);
509 }
510