xref: /titanic_44/usr/src/lib/libprtdiag/common/pdevinfo_funcs.c (revision e824d57f8160a27ac5e650005c7a4f037109c2be)
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, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <fcntl.h>
33 #include <stdarg.h>
34 #include <errno.h>
35 #include <unistd.h>
36 #include <sys/utsname.h>
37 #include <sys/openpromio.h>
38 #include <libintl.h>
39 #include "pdevinfo.h"
40 #include "display.h"
41 #include "pdevinfo_sun4u.h"
42 
43 /*
44  * For machines that support the openprom, fetch and print the list
45  * of devices that the kernel has fetched from the prom or conjured up.
46  *
47  */
48 
49 
50 static int prom_fd;
51 extern char *progname;
52 extern char *promdev;
53 extern void getppdata();
54 extern void printppdata();
55 
56 /*
57  * Define DPRINT for run-time debugging printf's...
58  * #define DPRINT	1
59  */
60 
61 #ifdef	DPRINT
62 static	char    vdebug_flag = 1;
63 #define	dprintf	if (vdebug_flag) printf
64 static void dprint_dev_info(caddr_t, dev_info_t *);
65 #endif	/* DPRINT */
66 
67 #if !defined(TEXT_DOMAIN)
68 #define	TEXT_DOMAIN	"SYS_TEST"
69 #endif
70 
71 /*VARARGS1*/
72 int
73 _error(char *fmt, ...)
74 {
75 	int saved_errno;
76 	va_list ap;
77 	extern int errno;
78 	saved_errno = errno;
79 
80 	if (progname)
81 		(void) fprintf(stderr, "%s: ", progname);
82 
83 	va_start(ap, fmt);
84 
85 	(void) vfprintf(stderr, fmt, ap);
86 
87 	va_end(ap);
88 
89 	(void) fprintf(stderr, ": ");
90 	errno = saved_errno;
91 	perror("");
92 
93 	return (2);
94 }
95 
96 int
97 is_openprom(void)
98 {
99 	Oppbuf	oppbuf;
100 	register struct openpromio *opp = &(oppbuf.opp);
101 	register unsigned int i;
102 
103 	opp->oprom_size = MAXVALSIZE;
104 	if (ioctl(prom_fd, OPROMGETCONS, opp) < 0)
105 		exit(_error("OPROMGETCONS"));
106 
107 	i = (unsigned int)((unsigned char)opp->oprom_array[0]);
108 	return ((i & OPROMCONS_OPENPROM) == OPROMCONS_OPENPROM);
109 }
110 
111 /*
112  * Read all properties and values from nodes.
113  * Copy the properties read into the prom_node passsed in.
114  */
115 void
116 dump_node(Prom_node *node)
117 {
118 	Oppbuf oppbuf;
119 	register struct openpromio *opp = &oppbuf.opp;
120 	Prop *prop = NULL;	/* tail of properties list */
121 	StaticProp *temp;
122 
123 	/* clear out pointers in pnode */
124 	node->props = NULL;
125 
126 	/* get first prop by asking for null string */
127 	(void) memset((void *) oppbuf.buf, 0, BUFSIZE);
128 
129 	/* allocate space for the property */
130 	if ((temp = malloc(sizeof (StaticProp))) == NULL) {
131 		perror("malloc");
132 		exit(1);
133 	}
134 
135 	opp->oprom_size = MAXPROPSIZE;
136 	while (opp->oprom_size != 0) {
137 		Prop *new;
138 		int i;
139 		char *tempp, *newp;
140 
141 		/*
142 		 * get property
143 		 */
144 		opp->oprom_size = MAXPROPSIZE;
145 
146 		if (ioctl(prom_fd, OPROMNXTPROP, opp) < 0)
147 			exit(_error("OPROMNXTPROP"));
148 
149 		if (opp->oprom_size != 0) {
150 			temp->name.opp.oprom_size = opp->oprom_size;
151 			(void) strcpy(temp->name.opp.oprom_array,
152 				opp->oprom_array);
153 
154 			(void) strcpy(temp->value.opp.oprom_array,
155 				temp->name.opp.oprom_array);
156 			getpropval(&temp->value.opp);
157 			temp->size = temp->value.opp.oprom_size;
158 
159 			/* Now copy over temp's data to new. */
160 			if ((new = malloc(sizeof (Prop))) == NULL) {
161 				perror("malloc");
162 				exit(1);
163 			}
164 
165 			/*
166 			 * First copy over temp->name's data. The
167 			 * temp->name.opp.opio_u union always contains char[]
168 			 * (as opposed to an int or int []).
169 			 */
170 			new->name.opp.oprom_size = temp->name.opp.oprom_size;
171 
172 			if ((new->name.opp.oprom_array =
173 			    malloc(new->name.opp.oprom_size)) == NULL) {
174 				perror("malloc");
175 				exit(1);
176 			}
177 			(void) strcpy(new->name.opp.oprom_array,
178 			    temp->name.opp.oprom_array);
179 
180 			new->name.opp.holds_array = 1;
181 
182 			/*
183 			 * Then copy over temp->value's data.
184 			 * temp->value.opp.opio_u could contain char[], int or
185 			 * int []. If *(temp->value.opp.oprom_array) is '\0',
186 			 * this indicates int or int []. int is the norm, but
187 			 * to be safe we assume int [] and copy over
188 			 * OPROM_NODE_SIZE int elements.
189 			 */
190 			new->value.opp.oprom_size = temp->value.opp.oprom_size;
191 
192 			if (*(temp->value.opp.oprom_array) == '\0') {
193 				for (i = 0; i < OPROM_NODE_SIZE; i++)
194 					new->value.opp.oprom_node[i] =
195 					    *(&temp->value.opp.oprom_node+i);
196 
197 				new->value.opp.holds_array = 0;
198 			} else {
199 				if ((new->value.opp.oprom_array =
200 				    malloc(new->value.opp.oprom_size))
201 				    == NULL) {
202 					perror("malloc");
203 					exit(1);
204 				}
205 
206 				/*
207 				 * temp->value.opp.oprom_array can contain one
208 				 * or more embedded NULLs. These trip-up the
209 				 * standard string copying functions, so we do
210 				 * the copy by hand. temp->value.opp.oprom_array
211 				 * will be NULL-terminated. oprom_size includes
212 				 * this terminating NULL.
213 				 */
214 				newp = new->value.opp.oprom_array;
215 				tempp = temp->value.opp.oprom_array;
216 				for (i = new->value.opp.oprom_size; i > 0; i--)
217 					*newp++ = *tempp++;
218 
219 				new->value.opp.holds_array = 1;
220 			}
221 
222 			new->size = temp->size;
223 
224 			/* everything worked so link the property list */
225 			if (node->props == NULL)
226 				node->props = new;
227 			else if (prop != NULL)
228 				prop->next = new;
229 			prop = new;
230 			prop->next = NULL;
231 		}
232 	}
233 	free(temp);
234 }
235 
236 int
237 promopen(int oflag)
238 {
239 	/*CONSTCOND*/
240 	while (1)  {
241 		if ((prom_fd = open(promdev, oflag)) < 0)  {
242 			if (errno == EAGAIN)   {
243 				(void) sleep(5);
244 				continue;
245 			}
246 			if (errno == ENXIO)
247 				return (-1);
248 			exit(_error(dgettext(TEXT_DOMAIN, "cannot open %s"),
249 				promdev));
250 		} else
251 			return (0);
252 	}
253 	/*NOTREACHED*/
254 }
255 
256 void
257 promclose(void)
258 {
259 	if (close(prom_fd) < 0)
260 		exit(_error(dgettext(TEXT_DOMAIN, "close error on %s"),
261 			promdev));
262 }
263 
264 /*
265  * Read the value of the property from the PROM device tree
266  */
267 void
268 getpropval(struct openpromio *opp)
269 {
270 	opp->oprom_size = MAXVALSIZE;
271 
272 	if (ioctl(prom_fd, OPROMGETPROP, opp) < 0)
273 		exit(_error("OPROMGETPROP"));
274 }
275 
276 int
277 next(int id)
278 {
279 	Oppbuf	oppbuf;
280 	register struct openpromio *opp = &(oppbuf.opp);
281 	/* LINTED */
282 	int *ip = (int *)(opp->oprom_array);
283 
284 	(void) memset((void *) oppbuf.buf, 0, BUFSIZE);
285 
286 	opp->oprom_size = MAXVALSIZE;
287 	*ip = id;
288 	if (ioctl(prom_fd, OPROMNEXT, opp) < 0)
289 		return (_error("OPROMNEXT"));
290 	/* LINTED */
291 	return (*(int *)opp->oprom_array);
292 }
293 
294 int
295 child(int id)
296 {
297 	Oppbuf	oppbuf;
298 	register struct openpromio *opp = &(oppbuf.opp);
299 	/* LINTED */
300 	int *ip = (int *)(opp->oprom_array);
301 
302 	(void) memset((void *) oppbuf.buf, 0, BUFSIZE);
303 	opp->oprom_size = MAXVALSIZE;
304 	*ip = id;
305 	if (ioctl(prom_fd, OPROMCHILD, opp) < 0)
306 		return (_error("OPROMCHILD"));
307 	/* LINTED */
308 	return (*(int *)opp->oprom_array);
309 }
310 
311 /*
312  * Check if the Prom node passed in contains a property called
313  * "board#".
314  */
315 int
316 has_board_num(Prom_node *node)
317 {
318 	Prop *prop = node->props;
319 
320 	/*
321 	 * walk thru all properties in this PROM node and look for
322 	 * board# prop
323 	 */
324 	while (prop != NULL) {
325 		if (strcmp(prop->name.opp.oprom_array, "board#") == 0)
326 		    return (1);
327 
328 		prop = prop->next;
329 	}
330 
331 	return (0);
332 }	/* end of has_board_num() */
333 
334 /*
335  * Retrieve the value of the board number property from this Prom
336  * node. It has the type of int.
337  */
338 int
339 get_board_num(Prom_node *node)
340 {
341 	Prop *prop = node->props;
342 
343 	/*
344 	 * walk thru all properties in this PROM node and look for
345 	 * board# prop
346 	 */
347 	while (prop != NULL) {
348 		if (strcmp(prop->name.opp.oprom_array, "board#") == 0)
349 			return (prop->value.opp.oprom_node[0]);
350 
351 		prop = prop->next;
352 	}
353 
354 	return (-1);
355 }	/* end of get_board_num() */
356 
357 /*
358  * Find the requested board struct in the system device tree.
359  */
360 Board_node *
361 find_board(Sys_tree *root, int board)
362 {
363 	Board_node *bnode = root->bd_list;
364 
365 	while ((bnode != NULL) && (board != bnode->board_num))
366 		bnode = bnode->next;
367 
368 	return (bnode);
369 }	/* end of find_board() */
370 
371 /*
372  * Add a board to the system list in order. Initialize all pointer
373  * fields to NULL.
374  */
375 Board_node *
376 insert_board(Sys_tree *root, int board)
377 {
378 	Board_node *bnode;
379 	Board_node *temp = root->bd_list;
380 
381 	if ((bnode = (Board_node *) malloc(sizeof (Board_node))) == NULL) {
382 		perror("malloc");
383 		exit(1);
384 	}
385 	bnode->nodes = NULL;
386 	bnode->next = NULL;
387 	bnode->board_num = board;
388 
389 	if (temp == NULL)
390 		root->bd_list = bnode;
391 	else if (temp->board_num > board) {
392 		bnode->next = temp;
393 		root->bd_list = bnode;
394 	} else {
395 		while ((temp->next != NULL) && (board > temp->next->board_num))
396 			temp = temp->next;
397 		bnode->next = temp->next;
398 		temp->next = bnode;
399 	}
400 	root->board_cnt++;
401 
402 	return (bnode);
403 }	/* end of insert_board() */
404 
405 /*
406  * This function searches through the properties of the node passed in
407  * and returns a pointer to the value of the name property.
408  */
409 char *
410 get_node_name(Prom_node *pnode)
411 {
412 	Prop *prop;
413 
414 	if (pnode == NULL) {
415 		return (NULL);
416 	}
417 
418 	prop = pnode->props;
419 	while (prop != NULL) {
420 		if (strcmp("name", prop->name.opp.oprom_array) == 0)
421 			return (prop->value.opp.oprom_array);
422 		prop = prop->next;
423 	}
424 	return (NULL);
425 }	/* end of get_node_name() */
426 
427 /*
428  * This function searches through the properties of the node passed in
429  * and returns a pointer to the value of the name property.
430  */
431 char *
432 get_node_type(Prom_node *pnode)
433 {
434 	Prop *prop;
435 
436 	if (pnode == NULL) {
437 		return (NULL);
438 	}
439 
440 	prop = pnode->props;
441 	while (prop != NULL) {
442 		if (strcmp("device_type", prop->name.opp.oprom_array) == 0)
443 			return (prop->value.opp.oprom_array);
444 		prop = prop->next;
445 	}
446 	return (NULL);
447 }	/* end of get_node_type() */
448 
449 /*
450  * Do a depth-first walk of a device tree and
451  * return the first node with the name matching.
452  */
453 
454 Prom_node *
455 dev_find_node(Prom_node *root, char *name)
456 {
457 	Prom_node *node;
458 
459 	node = dev_find_node_by_type(root, "name", name);
460 
461 	return (node);
462 }
463 
464 Prom_node *
465 dev_next_node(Prom_node *root, char *name)
466 {
467 	Prom_node *node;
468 
469 	node = dev_next_node_by_type(root, "name", name);
470 
471 	return (node);
472 }
473 
474 /*
475  * Search for and return a node of the required type. If no node is found,
476  * then return NULL.
477  */
478 Prom_node *
479 dev_find_type(Prom_node *root, char *type)
480 {
481 	Prom_node *node;
482 
483 	node = dev_find_node_by_type(root, "device_type", type);
484 
485 	return (node);  /* not found */
486 }
487 
488 /*
489  * Start from the current node and return the next node besides the
490  * current one which has the requested type property.
491  */
492 Prom_node *
493 dev_next_type(Prom_node *root, char *type)
494 {
495 	Prom_node *node;
496 
497 	node = dev_next_node_by_type(root, "device_type", type);
498 
499 	return (node);  /* not found */
500 }
501 
502 /*
503  * Search a device tree and return the first failed node that is found.
504  * (has a 'status' property)
505  */
506 Prom_node *
507 find_failed_node(Prom_node * root)
508 {
509 	Prom_node *pnode;
510 
511 	if (root == NULL)
512 		return (NULL);
513 
514 	if (node_failed(root)) {
515 		return (root);
516 	}
517 
518 	/* search the child */
519 	if ((pnode = find_failed_node(root->child)) != NULL)
520 		return (pnode);
521 
522 	/* search the siblings */
523 	if ((pnode = find_failed_node(root->sibling)) != NULL)
524 		return (pnode);
525 
526 	return (NULL);
527 }	/* end of find_failed_node() */
528 
529 /*
530  * Start from the current node and return the next node besides
531  * the current one which is failed. (has a 'status' property)
532  */
533 Prom_node *
534 next_failed_node(Prom_node * root)
535 {
536 	Prom_node *pnode;
537 	Prom_node *parent;
538 
539 	if (root == NULL)
540 		return (NULL);
541 
542 	/* search the child */
543 	if ((pnode = find_failed_node(root->child)) != NULL) {
544 		return (pnode);
545 	}
546 
547 	/* search the siblings */
548 	if ((pnode = find_failed_node(root->sibling)) != NULL) {
549 		return (pnode);
550 	}
551 
552 	/* backtracking the search up through parents' siblings */
553 	parent = root->parent;
554 	while (parent != NULL) {
555 		if ((pnode = find_failed_node(parent->sibling)) != NULL)
556 			return (pnode);
557 		else
558 			parent = parent->parent;
559 	}
560 
561 	return (NULL);
562 }	/* end of find_failed_node() */
563 
564 /*
565  * node_failed
566  *
567  * This function determines if the current Prom node is failed. This
568  * is defined by having a status property containing the token 'fail'.
569  */
570 int
571 node_failed(Prom_node *node)
572 {
573 	return (node_status(node, "fail"));
574 }
575 
576 int
577 node_status(Prom_node *node, char *status)
578 {
579 	void *value;
580 
581 	if (status == NULL)
582 		return (0);
583 
584 	/* search the local node */
585 	if ((value = get_prop_val(find_prop(node, "status"))) != NULL) {
586 		if ((value != NULL) && strstr((char *)value, status))
587 			return (1);
588 	}
589 	return (0);
590 }
591 
592 /*
593  * Get a property's value. Must be void * since the property can
594  * be any data type. Caller must know the *PROPER* way to use this
595  * data.
596  */
597 void *
598 get_prop_val(Prop *prop)
599 {
600 	if (prop == NULL)
601 		return (NULL);
602 
603 	if (prop->value.opp.holds_array)
604 		return ((void *)(prop->value.opp.oprom_array));
605 	else
606 		return ((void *)(&prop->value.opp.oprom_node[0]));
607 }	/* end of get_prop_val() */
608 
609 /*
610  * Search a Prom node and retrieve the property with the correct
611  * name.
612  */
613 Prop *
614 find_prop(Prom_node *pnode, char *name)
615 {
616 	Prop *prop;
617 
618 	if (pnode  == NULL) {
619 		return (NULL);
620 	}
621 
622 	if (pnode->props == NULL) {
623 		(void) printf("%s", dgettext(TEXT_DOMAIN, "Prom node has "
624 			"no properties\n"));
625 		return (NULL);
626 	}
627 
628 	prop = pnode->props;
629 	while ((prop != NULL) && (strcmp(prop->name.opp.oprom_array, name)))
630 		prop = prop->next;
631 
632 	return (prop);
633 }
634 
635 /*
636  * This function adds a board node to the board structure where that
637  * that node's physical component lives.
638  */
639 void
640 add_node(Sys_tree *root, Prom_node *pnode)
641 {
642 	int board;
643 	Board_node *bnode;
644 	Prom_node *p;
645 
646 	/* add this node to the Board list of the appropriate board */
647 	if ((board = get_board_num(pnode)) == -1) {
648 		/* board is 0 if not on Sunfire */
649 		board = 0;
650 	}
651 
652 	/* find the node with the same board number */
653 	if ((bnode = find_board(root, board)) == NULL) {
654 		bnode = insert_board(root, board);
655 		bnode->board_type = UNKNOWN_BOARD;
656 	}
657 
658 	/* now attach this prom node to the board list */
659 	/* Insert this node at the end of the list */
660 	pnode->sibling = NULL;
661 	if (bnode->nodes == NULL)
662 		bnode->nodes = pnode;
663 	else {
664 		p = bnode->nodes;
665 		while (p->sibling != NULL)
666 			p = p->sibling;
667 		p->sibling = pnode;
668 	}
669 
670 }
671 
672 /*
673  * Find the device on the current board with the requested device ID
674  * and name. If this rountine is passed a NULL pointer, it simply returns
675  * NULL.
676  */
677 Prom_node *
678 find_device(Board_node *board, int id, char *name)
679 {
680 	Prom_node *pnode;
681 	int mask;
682 
683 	/* find the first cpu node */
684 	pnode = dev_find_node(board->nodes, name);
685 
686 	mask = 0x1F;
687 	while (pnode != NULL) {
688 		if ((get_id(pnode) & mask) == id)
689 			return (pnode);
690 
691 		pnode = dev_next_node(pnode, name);
692 	}
693 	return (NULL);
694 }
695 
696 Prom_node *
697 dev_find_node_by_type(Prom_node *root, char *type, char *property)
698 {
699 	Prom_node *node;
700 	char *type_prop;
701 
702 	if (root == NULL || property == NULL)
703 		return (NULL);
704 
705 	type_prop = (char *)get_prop_val(find_prop(root, type));
706 
707 	if (type_prop != NULL) {
708 		if (strcmp(type_prop, property) == 0) {
709 			return (root);
710 		}
711 	}
712 
713 	/* look at your children first */
714 	if ((node = dev_find_node_by_type(root->child, type,
715 	    property)) != NULL)
716 		return (node);
717 
718 	/* now look at your siblings */
719 	if ((node = dev_find_node_by_type(root->sibling, type,
720 	    property)) != NULL)
721 		return (node);
722 
723 	return (NULL);	/* not found */
724 }
725 
726 Prom_node *
727 dev_next_node_by_type(Prom_node *root, char *type, char *property)
728 {
729 	Prom_node *node;
730 
731 	if (root == NULL || property == NULL)
732 		return (NULL);
733 
734 	/* look at your children first */
735 	if ((node = dev_find_node_by_type(root->child, type,
736 	    property)) != NULL)
737 		return (node);
738 
739 	/* now look at your siblings */
740 	if ((node = dev_find_node_by_type(root->sibling, type,
741 	    property)) != NULL)
742 		return (node);
743 
744 	/* now look at papa's siblings */
745 	if ((node = dev_find_node_by_type(root->parent->sibling,
746 	    type, property)) != NULL)
747 		return (node);
748 
749 	return (NULL);  /* not found */
750 }
751 
752 /*
753  * Do a depth-first walk of a device tree and
754  * return the first node with the matching compatible.
755  */
756 Prom_node *
757 dev_find_node_by_compatible(Prom_node *root, char *compatible)
758 {
759 	Prom_node *node;
760 	Prop	*prop;
761 	char	*compatible_array;
762 	int	size, nbytes;
763 
764 	if (root == NULL || compatible == NULL)
765 		return (NULL);
766 
767 	if ((prop = find_prop(root, "compatible")) != NULL &&
768 	    (compatible_array = (char *)get_prop_val(prop)) != NULL) {
769 		/*
770 		 * The Prop structure returned by find_prop() is supposed
771 		 * to contain an indication of how big the value of the
772 		 * compatible property is.  Since it is an array of strings
773 		 * this is our only means of determining just how many
774 		 * strings might be in this property.  However, this size
775 		 * is often left as zero even though there is at least one
776 		 * string present.  When this is the case, all we can do
777 		 * is examine the first string in the compatible property.
778 		 */
779 
780 		for (size = prop->size; size >= 0; size -= nbytes) {
781 			if (strcmp(compatible_array, compatible) == 0)
782 				return (root);		/* found a match */
783 
784 			nbytes = strlen(compatible_array) + 1;
785 			compatible_array += nbytes;
786 		}
787 	}
788 
789 	node = dev_find_node_by_compatible(root->child, compatible);
790 	if (node != NULL)
791 		return (node);
792 
793 	/*
794 	 * Note the very deliberate use of tail recursion here.	 A good
795 	 * compiler (such as Sun's) will recognize this and generate code
796 	 * that does not allocate another stack frame.	Instead, it will
797 	 * overlay the existing stack frame with the new one, the only change
798 	 * having been to replace the original root with its sibling.
799 	 * This has the potential to create some confusion for anyone
800 	 * trying to debug this code from a core dump, since the stack
801 	 * trace will not reveal recursion on siblings, only on children.
802 	 */
803 
804 	return (dev_find_node_by_compatible(root->sibling, compatible));
805 }
806 
807 /*
808  * Start from the current node and return the next node besides
809  * the current one which has the requested compatible property.
810  */
811 Prom_node *
812 dev_next_node_by_compatible(Prom_node *root, char *compatible)
813 {
814 	Prom_node *node;
815 
816 	if (root == NULL || compatible == NULL)
817 		return (NULL);
818 
819 	node = dev_find_node_by_compatible(root->child, compatible);
820 	if (node != NULL)
821 		return (node);
822 
823 	/*
824 	 * More tail recursion.	 Even though it is a different function,
825 	 * this will overlay the current stack frame.  Caveat exterminator.
826 	 */
827 
828 	node = dev_find_node_by_compatible(root->sibling, compatible);
829 	if (node != NULL)
830 		return (node);
831 
832 	return (dev_find_node_by_compatible(root->parent->sibling, compatible));
833 }
834