xref: /illumos-gate/usr/src/cmd/oplhpd/scf_notify.c (revision 52244c0958bdf281ca42932b449f644b4decfdc2)
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  * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <fcntl.h>
28 #include <config_admin.h>
29 #include <strings.h>
30 #include <syslog.h>
31 #include <libsysevent.h>
32 #include <libdevinfo.h>
33 #include <libnvpair.h>
34 #include <assert.h>
35 #include <errno.h>
36 #include <unistd.h>
37 #include <stropts.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <sys/sysevent/dr.h>
41 #include <sys/scfd/opcioif.h>
42 
43 
44 /* Macros */
45 #define	SCF_DEV_DIR  "/devices"	/* device base dir */
46 
47 
48 
49 /*
50  * Connection for SCF driver
51  */
52 
53 /* Check the availability of SCF driver */
54 static int	scfdrv_enable = 0;
55 
56 
57 /* Device for SCF Driver */
58 #define	SCFIOCDEV	"/devices/pseudo/scfd@200:rasctl"
59 #define	SCFRETRY	10
60 #define	SCFIOCWAIT	3
61 #define	SCFDATA_DEV_INFO	32
62 #define	SCFDATA_APID    1054
63 
64 /*
65  * Data for XSCF
66  * Note the size of the ap_id must be SCFDATA_APID for proper data alignment
67  * for the ioctl. The SCF has a corresponding data structure which is matched
68  * here.
69  */
70 typedef struct {
71 	char		ap_id[SCFDATA_APID];
72 	uint8_t		ioua;
73 	uint8_t		vflag;
74 	uint32_t	r_state;
75 	uint32_t	o_state;
76 	uint64_t	tstamp;
77 	char		dev_name[SCFDATA_DEV_INFO];
78 	char		dev_model[SCFDATA_DEV_INFO];
79 } scf_slotinfo_t;
80 
81 /*
82  * Data for scf notification of state changes.
83  * pci_name is an ap_id phys path for the hot pluggable pci device.
84  * r_state is the recepticle state.
85  * o_state is the occupant state.
86  * cache_fmri_str is a string representation of an fmri in the rsrc cache.
87  * fmri_asru_str is the asru for an fmri which is found in the topology.
88  * found is a boolean indicating whether the device was found in the topology.
89  */
90 typedef struct {
91 	char		pci_name[MAXPATHLEN];
92 	uint32_t	r_state;
93 	uint32_t	o_state;
94 } pci_notify_t;
95 
96 /*
97  * Function Prototypes
98  */
99 void scf_get_slotinfo(char *ap_id, cfga_stat_t *o_state,
100 		cfga_stat_t *r_state);
101 static int scf_get_pci_name(const char *ap_phys_id, char *pci_name);
102 static int scf_get_devinfo(char *dev_name, char *dev_model,
103 		const char *pci_name);
104 void notify_scf_of_hotplug(sysevent_t *ev);
105 
106 
107 /*
108  * Error report utility for libcfgadm functions
109  */
110 void
111 config_error(cfga_err_t err, const char *func_name, const char *errstr,
112     const char *ap_id)
113 {
114 	const char *ep;
115 
116 	ep = config_strerror(err);
117 	if (ep == NULL) {
118 		ep = "configuration administration unknown error";
119 	}
120 
121 	if (errstr != NULL && *errstr != '\0') {
122 		syslog(LOG_DEBUG, "%s: %s (%s), ap_id = %s\n",
123 		    func_name, ep, errstr, ap_id);
124 	} else {
125 		syslog(LOG_DEBUG, "%s: %s , ap_id = %s\n",
126 		    func_name, ep, ap_id);
127 	}
128 
129 }
130 
131 /*
132  * Get the slot status.
133  */
134 void
135 scf_get_slotinfo(char *ap_pid, cfga_stat_t *r_state, cfga_stat_t *o_state)
136 {
137 	cfga_err_t		rv;		/* return value */
138 	cfga_list_data_t	*stat = NULL;	/* slot info. */
139 	int			nlist;		/* number of slot */
140 	char			*errstr = NULL;	/* error code */
141 
142 	/*
143 	 * Get the attachment point information.
144 	 */
145 	rv = config_list_ext(1, (char *const *)&ap_pid, &stat, &nlist, NULL,
146 	    NULL, &errstr, 0);
147 
148 	if (rv != CFGA_OK) {
149 		config_error(rv, "config_list_ext", errstr, ap_pid);
150 		goto out;
151 	}
152 	assert(nlist == 1);
153 
154 	syslog(LOG_DEBUG, "\n"
155 	    "ap_log_id       = %.*s\n"
156 	    "ap_phys_id      = %.*s\n"
157 	    "ap_r_state      = %d\n"
158 	    "ap_o_state      = %d\n"
159 	    "ap_cond         = %d\n"
160 	    "ap_busy         = %6d\n"
161 	    "ap_status_time  = %s"
162 	    "ap_info         = %.*s\n"
163 	    "ap_type         = %.*s\n",
164 	    sizeof (stat->ap_log_id), stat->ap_log_id,
165 	    sizeof (stat->ap_phys_id), stat->ap_phys_id,
166 	    stat->ap_r_state,
167 	    stat->ap_o_state,
168 	    stat->ap_cond,
169 	    stat->ap_busy,
170 	    asctime(localtime(&stat->ap_status_time)),
171 	    sizeof (stat->ap_info), stat->ap_info,
172 	    sizeof (stat->ap_type), stat->ap_type);
173 
174 	/* Copy the slot status. */
175 	*r_state = stat->ap_r_state;
176 	*o_state = stat->ap_o_state;
177 
178 out:
179 	if (stat) {
180 		free(stat);
181 	}
182 
183 	if (errstr) {
184 		free(errstr);
185 	}
186 }
187 
188 
189 /*
190  * Get the pci_name
191  */
192 static int
193 scf_get_pci_name(const char *ap_phys_id, char *pci_name)
194 {
195 	char		*pci_name_ptr;  /* pci node name pointer */
196 	char		*ap_lid_ptr;    /* logical ap_id pointer */
197 
198 	int		devices_len;	/* "/device" length */
199 	int		pci_name_len;	/* pci node name length */
200 	int		ap_lid_len;	/* logical ap_id pointer */
201 
202 
203 	/*
204 	 * Pick pci node name up from physical ap_id string.
205 	 * "/devices/pci@XX,YYYYYY:PCI#ZZ"
206 	 */
207 
208 	/* Check the length of physical ap_id string */
209 	if (strlen(ap_phys_id) >= MAXPATHLEN) {
210 		return (-1); /* changed */
211 	}
212 
213 	/* Check the pci node name start, which is after "/devices". */
214 	if (strncmp(SCF_DEV_DIR, ap_phys_id, strlen(SCF_DEV_DIR)) == 0) {
215 		devices_len = strlen(SCF_DEV_DIR);
216 	} else {
217 		devices_len = 0;
218 	}
219 	/* Check the pci node name end, which is before ":". */
220 	if ((ap_lid_ptr = strchr(ap_phys_id, ':')) == NULL) {
221 		ap_lid_len = 0;
222 	} else {
223 		ap_lid_len = strlen(ap_lid_ptr);
224 	}
225 
226 	/*
227 	 * Get the head of pci node name string.
228 	 * Get the length of pci node name string.
229 	 */
230 	pci_name_ptr = (char *)ap_phys_id + devices_len;
231 	pci_name_len = strlen(ap_phys_id) - devices_len - ap_lid_len;
232 
233 	/* Copy the pci node name. */
234 	(void) strncpy(pci_name, pci_name_ptr, pci_name_len);
235 	pci_name[pci_name_len] = '\0';
236 
237 	syslog(LOG_DEBUG, "pci device path = %s\n", pci_name);
238 
239 	return (0);
240 
241 }
242 
243 
244 /*
245  * Get the property of name and model.
246  */
247 static int
248 scf_get_devinfo(char *dev_name, char *dev_model, const char *pci_name)
249 {
250 	char		*tmp;		/* tmp */
251 	unsigned int    devid, funcid;  /* bus addr */
252 	unsigned int    sdevid, sfuncid; /* sibling bus addr */
253 
254 	di_node_t	pci_node;	/* pci device node */
255 	di_node_t	child_node;	/* child level node */
256 	di_node_t	ap_node;	/* hotplugged node */
257 
258 	pci_node = ap_node = DI_NODE_NIL;
259 
260 
261 	/*
262 	 * Take the snap shot of device node configuration,
263 	 * to get the names of node and model.
264 	 */
265 	if ((pci_node = di_init(pci_name, DINFOCPYALL)) == DI_NODE_NIL) {
266 		syslog(LOG_NOTICE,
267 		    "Could not get dev info snapshot. errno=%d\n",
268 		    errno);
269 		return (-1); /* changed */
270 	}
271 
272 	/*
273 	 * The new child under pci node should be added. Then the
274 	 * device and model names should be passed, which is in the
275 	 * node with the minimum bus address.
276 	 *
277 	 * - Move to the child node level.
278 	 * - Search the node with the minimum bus addrress in the
279 	 *   sibling list.
280 	 */
281 	if ((child_node = di_child_node(pci_node)) == DI_NODE_NIL) {
282 		syslog(LOG_NOTICE, "No slot device in snapshot\n");
283 		goto out;
284 	}
285 
286 	ap_node = child_node;
287 	if ((tmp = di_bus_addr(child_node)) != NULL) {
288 		if (sscanf(tmp, "%x,%x", &devid, &funcid) != 2) {
289 			funcid = 0;
290 			if (sscanf(tmp, "%x", &devid) != 1) {
291 				devid = 0;
292 				syslog(LOG_DEBUG,
293 				    "no bus addrress on device\n");
294 				goto one_child;
295 			}
296 		}
297 	}
298 
299 	while ((child_node = di_sibling_node(child_node)) != NULL) {
300 		if ((tmp = di_bus_addr(child_node)) == NULL) {
301 			ap_node = child_node;
302 			break;
303 		}
304 
305 		if (sscanf(tmp, "%x,%x", &sdevid, &sfuncid) == 2) {
306 			/*
307 			 * We do need to update the child node
308 			 *   Case 1. devid > sdevid
309 			 *   Case 2. devid == sdevid && funcid > sfuncid
310 			 */
311 			if ((devid > sdevid) || ((devid == sdevid) &&
312 			    (funcid > sfuncid))) {
313 				ap_node = child_node;
314 				devid   = sdevid;
315 				funcid  = sfuncid;
316 			}
317 
318 		} else if (sscanf(tmp, "%x", &sdevid) == 1) {
319 			/*
320 			 * We do need to update the child node
321 			 *   Case 1. devid >= sdevid
322 			 */
323 			if (devid >= sdevid) {
324 				ap_node = child_node;
325 				devid   = sdevid;
326 				funcid  = 0;
327 			}
328 
329 		} else {
330 			ap_node = child_node;
331 			break;
332 		}
333 	}
334 
335 one_child:
336 	/*
337 	 * Get the name and model properties.
338 	 */
339 	tmp = di_node_name(ap_node);
340 	if (tmp != NULL) {
341 		(void) strlcpy((char *)dev_name, tmp, SCFDATA_DEV_INFO);
342 	}
343 
344 	tmp = NULL;
345 	if (di_prop_lookup_strings(DDI_DEV_T_ANY, ap_node, "model", &tmp) > 0) {
346 		if (tmp != NULL) {
347 			(void) strlcpy((char *)dev_model, tmp,
348 			    SCFDATA_DEV_INFO);
349 		}
350 	}
351 
352 	syslog(LOG_DEBUG, "device: %s@%x,%x [model: %s]\n",
353 	    dev_name, devid, funcid, dev_model);
354 
355 out:
356 	di_fini(pci_node);
357 	return (0); /* added */
358 }
359 
360 
361 void
362 notify_scf_of_hotplug(sysevent_t *ev)
363 {
364 	int		rc;			/* return code */
365 
366 	/* For libsysevent */
367 	char		*vendor = NULL;		/* event vendor */
368 	char		*publisher = NULL;	/* event publisher */
369 	nvlist_t	*ev_attr_list = NULL;	/* attribute */
370 
371 	/* For libcfgadm */
372 	char		*ap_id = NULL;		/* attachment point */
373 	cfga_stat_t	r_state, o_state;	/* slot status */
374 
375 	/* For libdevinfo */
376 	char		dev_name[SCFDATA_DEV_INFO];	/* name property */
377 	char		dev_model[SCFDATA_DEV_INFO];	/* model property */
378 
379 	/* Data for SCF */
380 	pci_notify_t pci_notify_dev_info;
381 	scfsetphpinfo_t scfdata;
382 	scf_slotinfo_t  sdata;
383 	time_t sec;			/* hotplug event current time */
384 	int		fd, retry = 0;
385 
386 
387 	/*
388 	 * Initialization
389 	 */
390 	r_state = o_state = 0;
391 	dev_name[0] = dev_model[0] = '\0';
392 	(void) memset((void *)&pci_notify_dev_info, 0, sizeof (pci_notify_t));
393 
394 	/* Get the current time when event picked up. */
395 	sec = time(NULL);
396 
397 	/*
398 	 * Check the vendor and publisher name of event.
399 	 */
400 	vendor = sysevent_get_vendor_name(ev);
401 	publisher = sysevent_get_pub_name(ev);
402 	/* Check the vendor is "SUNW" */
403 	if (strncmp("SUNW", vendor, strlen("SUNW")) != 0) {
404 		/* Just return when not from SUNW */
405 		syslog(LOG_DEBUG, "Event is not a SUNW vendor event\n");
406 		goto out;
407 	}
408 
409 	/* Enough to check "px" and "pcieb" at the beginning of string */
410 	if (strncmp("px", publisher, strlen("px")) != 0 &&
411 	    strncmp("pcieb", publisher, strlen("pcieb")) != 0) {
412 		/* Just return when not px event */
413 		syslog(LOG_DEBUG, "Event is not a px publisher event\n");
414 		goto out;
415 	}
416 
417 	/*
418 	 * Get attribute values of attachment point.
419 	 */
420 	if (sysevent_get_attr_list(ev, &ev_attr_list) != 0) {
421 		/* could not get attribute list */
422 		syslog(LOG_DEBUG, "Could not get attribute list\n");
423 		goto out;
424 	}
425 	if (nvlist_lookup_string(ev_attr_list, DR_AP_ID, &ap_id) != 0) {
426 		/* could not find the attribute from the list */
427 		syslog(LOG_DEBUG, "Could not get ap_id in attribute list\n");
428 		goto out;
429 	}
430 
431 	if (ap_id == NULL || strlen(ap_id) == 0) {
432 		syslog(LOG_DEBUG, "ap_id is NULL\n");
433 		goto out;
434 	} else {
435 		/*
436 		 * Get the slot status.
437 		 */
438 		syslog(LOG_DEBUG, "ap_id = %s\n", ap_id);
439 		scf_get_slotinfo(ap_id, &r_state, &o_state);
440 	}
441 
442 	syslog(LOG_DEBUG, "r_state = %d\n", r_state);
443 	syslog(LOG_DEBUG, "o_state = %d\n", o_state);
444 
445 	/*
446 	 * Get the pci name which is needed for both the configure and
447 	 * unconfigure.
448 	 */
449 	rc = scf_get_pci_name(ap_id, (char *)pci_notify_dev_info.pci_name);
450 	if (rc != 0) {
451 		goto out;
452 	}
453 
454 	/*
455 	 * Event for configure case only,
456 	 * Get the name and model property
457 	 */
458 	if (o_state == CFGA_STAT_CONFIGURED) {
459 		rc = scf_get_devinfo(dev_name, dev_model,
460 		    (char *)pci_notify_dev_info.pci_name);
461 		if (rc != 0) {
462 			goto out;
463 		}
464 	}
465 	/*
466 	 * Copy the data for SCF.
467 	 * Initialize Data passed to SCF Driver.
468 	 */
469 	(void) memset(scfdata.buf, 0, sizeof (scfdata.buf));
470 
471 	/*
472 	 * Set Data passed to SCF Driver.
473 	 */
474 	scfdata.size = sizeof (scf_slotinfo_t);
475 	(void) strlcpy(sdata.ap_id, ap_id, sizeof (sdata.ap_id));
476 
477 	sdata.vflag = (uint8_t)0x80;
478 	sdata.r_state  = (uint32_t)r_state;
479 	sdata.o_state  = (uint32_t)o_state;
480 	sdata.tstamp   = (uint64_t)sec;
481 	(void) strlcpy(sdata.dev_name, dev_name, sizeof (dev_name));
482 	(void) strlcpy(sdata.dev_model, dev_model, sizeof (sdata.dev_model));
483 
484 	(void) memcpy((void *)&(scfdata.buf), (void *)&sdata,
485 	    sizeof (scf_slotinfo_t));
486 
487 	pci_notify_dev_info.r_state	= (uint32_t)r_state;
488 	pci_notify_dev_info.o_state	= (uint32_t)o_state;
489 
490 	if (!scfdrv_enable) {
491 		scfdrv_enable = 1;
492 
493 		/*
494 		 * Pass data to SCF driver by ioctl.
495 		 */
496 		if ((fd = open(SCFIOCDEV, O_WRONLY)) < 0) {
497 			syslog(LOG_ERR, "open %s fail", SCFIOCDEV);
498 			scfdrv_enable = 0;
499 			goto out;
500 		}
501 
502 		while (ioctl(fd, SCFIOCSETPHPINFO, scfdata) < 0) {
503 			/* retry a few times for EBUSY and EIO */
504 			if ((++retry <= SCFRETRY) &&
505 			    ((errno == EBUSY) || (errno == EIO))) {
506 				(void) sleep(SCFIOCWAIT);
507 				continue;
508 			}
509 
510 			syslog(LOG_ERR, "SCFIOCSETPHPINFO failed: %s.",
511 			    strerror(errno));
512 			break;
513 		}
514 
515 		(void) close(fd);
516 		scfdrv_enable = 0;
517 	}
518 
519 out:
520 	if (vendor != NULL) {
521 		free(vendor);
522 	}
523 	if (publisher != NULL) {
524 		free(publisher);
525 	}
526 
527 	if (ev_attr_list != NULL) {
528 		nvlist_free(ev_attr_list);
529 	}
530 
531 }
532