xref: /freebsd/sys/dev/mii/mii.c (revision 7ef62cebc2f965b0f640263e179276928885e33d)
1 /*	$NetBSD: mii.c,v 1.12 1999/08/03 19:41:49 drochner Exp $	*/
2 
3 /*-
4  * SPDX-License-Identifier: BSD-2-Clause
5  *
6  * Copyright (c) 1998 The NetBSD Foundation, Inc.
7  * All rights reserved.
8  *
9  * This code is derived from software contributed to The NetBSD Foundation
10  * by Jason R. Thorpe of the Numerical Aerospace Simulation Facility,
11  * NASA Ames Research Center.
12  *
13  * Redistribution and use in source and binary forms, with or without
14  * modification, are permitted provided that the following conditions
15  * are met:
16  * 1. Redistributions of source code must retain the above copyright
17  *    notice, this list of conditions and the following disclaimer.
18  * 2. Redistributions in binary form must reproduce the above copyright
19  *    notice, this list of conditions and the following disclaimer in the
20  *    documentation and/or other materials provided with the distribution.
21  *
22  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
23  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
24  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
25  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
26  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32  * POSSIBILITY OF SUCH DAMAGE.
33  */
34 
35 #include <sys/cdefs.h>
36 __FBSDID("$FreeBSD$");
37 
38 /*
39  * MII bus layer, glues MII-capable network interface drivers to sharable
40  * PHY drivers.  This exports an interface compatible with BSD/OS 3.0's,
41  * plus some NetBSD extensions.
42  */
43 
44 #include <sys/param.h>
45 #include <sys/systm.h>
46 #include <sys/socket.h>
47 #include <sys/malloc.h>
48 #include <sys/module.h>
49 #include <sys/bus.h>
50 #include <sys/sbuf.h>
51 
52 #include <net/if.h>
53 #include <net/if_var.h>
54 #include <net/if_media.h>
55 
56 #include <dev/mii/mii.h>
57 #include <dev/mii/miivar.h>
58 
59 MODULE_VERSION(miibus, 1);
60 
61 #include "miibus_if.h"
62 
63 static bus_child_detached_t miibus_child_detached;
64 static bus_child_location_t miibus_child_location;
65 static bus_child_pnpinfo_t miibus_child_pnpinfo;
66 static device_detach_t miibus_detach;
67 static bus_hinted_child_t miibus_hinted_child;
68 static bus_print_child_t miibus_print_child;
69 static device_probe_t miibus_probe;
70 static bus_read_ivar_t miibus_read_ivar;
71 static miibus_readreg_t miibus_readreg;
72 static miibus_statchg_t miibus_statchg;
73 static miibus_writereg_t miibus_writereg;
74 static miibus_linkchg_t miibus_linkchg;
75 static miibus_mediainit_t miibus_mediainit;
76 
77 static unsigned char mii_bitreverse(unsigned char x);
78 
79 static device_method_t miibus_methods[] = {
80 	/* device interface */
81 	DEVMETHOD(device_probe,		miibus_probe),
82 	DEVMETHOD(device_attach,	miibus_attach),
83 	DEVMETHOD(device_detach,	miibus_detach),
84 	DEVMETHOD(device_shutdown,	bus_generic_shutdown),
85 
86 	/* bus interface */
87 	DEVMETHOD(bus_print_child,	miibus_print_child),
88 	DEVMETHOD(bus_read_ivar,	miibus_read_ivar),
89 	DEVMETHOD(bus_child_detached,	miibus_child_detached),
90 	DEVMETHOD(bus_child_pnpinfo,	miibus_child_pnpinfo),
91 	DEVMETHOD(bus_child_location,	miibus_child_location),
92 	DEVMETHOD(bus_hinted_child,	miibus_hinted_child),
93 
94 	/* MII interface */
95 	DEVMETHOD(miibus_readreg,	miibus_readreg),
96 	DEVMETHOD(miibus_writereg,	miibus_writereg),
97 	DEVMETHOD(miibus_statchg,	miibus_statchg),
98 	DEVMETHOD(miibus_linkchg,	miibus_linkchg),
99 	DEVMETHOD(miibus_mediainit,	miibus_mediainit),
100 
101 	DEVMETHOD_END
102 };
103 
104 DEFINE_CLASS_0(miibus, miibus_driver, miibus_methods, sizeof(struct mii_data));
105 
106 struct miibus_ivars {
107 	if_t		ifp;
108 	ifm_change_cb_t	ifmedia_upd;
109 	ifm_stat_cb_t	ifmedia_sts;
110 	u_int		mii_flags;
111 	u_int		mii_offset;
112 };
113 
114 static int
115 miibus_probe(device_t dev)
116 {
117 
118 	device_set_desc(dev, "MII bus");
119 
120 	return (BUS_PROBE_SPECIFIC);
121 }
122 
123 int
124 miibus_attach(device_t dev)
125 {
126 	struct miibus_ivars	*ivars;
127 	struct mii_attach_args	*ma;
128 	struct mii_data		*mii;
129 	device_t		*children;
130 	int			i, nchildren;
131 
132 	mii = device_get_softc(dev);
133 	if (device_get_children(dev, &children, &nchildren) == 0) {
134 		for (i = 0; i < nchildren; i++) {
135 			ma = device_get_ivars(children[i]);
136 			ma->mii_data = mii;
137 		}
138 		free(children, M_TEMP);
139 	}
140 	if (nchildren == 0) {
141 		device_printf(dev, "cannot get children\n");
142 		return (ENXIO);
143 	}
144 	ivars = device_get_ivars(dev);
145 	ifmedia_init(&mii->mii_media, IFM_IMASK, ivars->ifmedia_upd,
146 	    ivars->ifmedia_sts);
147 	mii->mii_ifp = ivars->ifp;
148 	if_setcapabilitiesbit(mii->mii_ifp, IFCAP_LINKSTATE, 0);
149 	if_setcapenablebit(mii->mii_ifp, IFCAP_LINKSTATE, 0);
150 	LIST_INIT(&mii->mii_phys);
151 
152 	return (bus_generic_attach(dev));
153 }
154 
155 static int
156 miibus_detach(device_t dev)
157 {
158 	struct mii_data		*mii;
159 	struct miibus_ivars	*ivars;
160 
161 	ivars = device_get_ivars(dev);
162 	bus_generic_detach(dev);
163 	mii = device_get_softc(dev);
164 	ifmedia_removeall(&mii->mii_media);
165 	free(ivars, M_DEVBUF);
166 	mii->mii_ifp = NULL;
167 
168 	return (0);
169 }
170 
171 static void
172 miibus_child_detached(device_t dev, device_t child)
173 {
174 	struct mii_attach_args *args;
175 
176 	args = device_get_ivars(child);
177 	free(args, M_DEVBUF);
178 }
179 
180 static int
181 miibus_print_child(device_t dev, device_t child)
182 {
183 	struct mii_attach_args *ma;
184 	int retval;
185 
186 	ma = device_get_ivars(child);
187 	retval = bus_print_child_header(dev, child);
188 	retval += printf(" PHY %d", ma->mii_phyno);
189 	retval += bus_print_child_footer(dev, child);
190 
191 	return (retval);
192 }
193 
194 static int
195 miibus_read_ivar(device_t dev, device_t child __unused, int which,
196     uintptr_t *result)
197 {
198 	struct miibus_ivars *ivars;
199 
200 	/*
201 	 * NB: this uses the instance variables of the miibus rather than
202 	 * its PHY children.
203 	 */
204 	ivars = device_get_ivars(dev);
205 	switch (which) {
206 	case MIIBUS_IVAR_FLAGS:
207 		*result = ivars->mii_flags;
208 		break;
209 	default:
210 		return (ENOENT);
211 	}
212 	return (0);
213 }
214 
215 static int
216 miibus_child_pnpinfo(device_t dev __unused, device_t child, struct sbuf *sb)
217 {
218 	struct mii_attach_args *ma;
219 
220 	ma = device_get_ivars(child);
221 	sbuf_printf(sb, "oui=0x%x model=0x%x rev=0x%x",
222 	    MII_OUI(ma->mii_id1, ma->mii_id2),
223 	    MII_MODEL(ma->mii_id2), MII_REV(ma->mii_id2));
224 	return (0);
225 }
226 
227 static int
228 miibus_child_location(device_t dev __unused, device_t child, struct sbuf *sb)
229 {
230 	struct mii_attach_args *ma;
231 
232 	ma = device_get_ivars(child);
233 	sbuf_printf(sb, "phyno=%d", ma->mii_phyno);
234 	return (0);
235 }
236 
237 static void
238 miibus_hinted_child(device_t dev, const char *name, int unit)
239 {
240 	struct miibus_ivars *ivars;
241 	struct mii_attach_args *args, *ma;
242 	device_t *children, phy;
243 	int i, nchildren;
244 	u_int val;
245 
246 	if (resource_int_value(name, unit, "phyno", &val) != 0)
247 		return;
248 	if (device_get_children(dev, &children, &nchildren) != 0)
249 		return;
250 	ma = NULL;
251 	for (i = 0; i < nchildren; i++) {
252 		args = device_get_ivars(children[i]);
253 		if (args->mii_phyno == val) {
254 			ma = args;
255 			break;
256 		}
257 	}
258 	free(children, M_TEMP);
259 
260 	/*
261 	 * Don't add a PHY that was automatically identified by having media
262 	 * in its BMSR twice, only allow to alter its attach arguments.
263 	 */
264 	if (ma == NULL) {
265 		ma = malloc(sizeof(struct mii_attach_args), M_DEVBUF,
266 		    M_NOWAIT);
267 		if (ma == NULL)
268 			return;
269 		phy = device_add_child(dev, name, unit);
270 		if (phy == NULL) {
271 			free(ma, M_DEVBUF);
272 			return;
273 		}
274 		ivars = device_get_ivars(dev);
275 		ma->mii_phyno = val;
276 		ma->mii_offset = ivars->mii_offset++;
277 		ma->mii_id1 = 0;
278 		ma->mii_id2 = 0;
279 		ma->mii_capmask = BMSR_DEFCAPMASK;
280 		device_set_ivars(phy, ma);
281 	}
282 
283 	if (resource_int_value(name, unit, "id1", &val) == 0)
284 		ma->mii_id1 = val;
285 	if (resource_int_value(name, unit, "id2", &val) == 0)
286 		ma->mii_id2 = val;
287 	if (resource_int_value(name, unit, "capmask", &val) == 0)
288 		ma->mii_capmask = val;
289 }
290 
291 static int
292 miibus_readreg(device_t dev, int phy, int reg)
293 {
294 	device_t		parent;
295 
296 	parent = device_get_parent(dev);
297 	return (MIIBUS_READREG(parent, phy, reg));
298 }
299 
300 static int
301 miibus_writereg(device_t dev, int phy, int reg, int data)
302 {
303 	device_t		parent;
304 
305 	parent = device_get_parent(dev);
306 	return (MIIBUS_WRITEREG(parent, phy, reg, data));
307 }
308 
309 static void
310 miibus_statchg(device_t dev)
311 {
312 	device_t		parent;
313 	struct mii_data		*mii;
314 
315 	parent = device_get_parent(dev);
316 	MIIBUS_STATCHG(parent);
317 
318 	mii = device_get_softc(dev);
319 	if_setbaudrate(mii->mii_ifp, ifmedia_baudrate(mii->mii_media_active));
320 }
321 
322 static void
323 miibus_linkchg(device_t dev)
324 {
325 	struct mii_data		*mii;
326 	device_t		parent;
327 	int			link_state;
328 
329 	parent = device_get_parent(dev);
330 	MIIBUS_LINKCHG(parent);
331 
332 	mii = device_get_softc(dev);
333 
334 	if (mii->mii_media_status & IFM_AVALID) {
335 		if (mii->mii_media_status & IFM_ACTIVE)
336 			link_state = LINK_STATE_UP;
337 		else
338 			link_state = LINK_STATE_DOWN;
339 	} else
340 		link_state = LINK_STATE_UNKNOWN;
341 	if_link_state_change(mii->mii_ifp, link_state);
342 }
343 
344 static void
345 miibus_mediainit(device_t dev)
346 {
347 	struct mii_data		*mii;
348 	struct ifmedia_entry	*m;
349 	int			media = 0;
350 
351 	/* Poke the parent in case it has any media of its own to add. */
352 	MIIBUS_MEDIAINIT(device_get_parent(dev));
353 
354 	mii = device_get_softc(dev);
355 	LIST_FOREACH(m, &mii->mii_media.ifm_list, ifm_list) {
356 		media = m->ifm_media;
357 		if (media == (IFM_ETHER | IFM_AUTO))
358 			break;
359 	}
360 
361 	ifmedia_set(&mii->mii_media, media);
362 }
363 
364 /*
365  * Helper function used by network interface drivers, attaches the miibus and
366  * the PHYs to the network interface driver parent.
367  */
368 int
369 mii_attach(device_t dev, device_t *miibus, if_t ifp,
370     ifm_change_cb_t ifmedia_upd, ifm_stat_cb_t ifmedia_sts, int capmask,
371     int phyloc, int offloc, int flags)
372 {
373 	struct miibus_ivars *ivars;
374 	struct mii_attach_args *args, ma;
375 	device_t *children, phy;
376 	int bmsr, first, i, nchildren, phymax, phymin, rv;
377 	uint32_t phymask;
378 
379 	bus_topo_assert();
380 
381 	if (phyloc != MII_PHY_ANY && offloc != MII_OFFSET_ANY) {
382 		printf("%s: phyloc and offloc specified\n", __func__);
383 		return (EINVAL);
384 	}
385 
386 	if (offloc != MII_OFFSET_ANY && (offloc < 0 || offloc >= MII_NPHY)) {
387 		printf("%s: invalid offloc %d\n", __func__, offloc);
388 		return (EINVAL);
389 	}
390 
391 	if (phyloc == MII_PHY_ANY) {
392 		phymin = 0;
393 		phymax = MII_NPHY - 1;
394 	} else {
395 		if (phyloc < 0 || phyloc >= MII_NPHY) {
396 			printf("%s: invalid phyloc %d\n", __func__, phyloc);
397 			return (EINVAL);
398 		}
399 		phymin = phymax = phyloc;
400 	}
401 
402 	first = 0;
403 	if (*miibus == NULL) {
404 		first = 1;
405 		ivars = malloc(sizeof(*ivars), M_DEVBUF, M_NOWAIT);
406 		if (ivars == NULL)
407 			return (ENOMEM);
408 		ivars->ifp = ifp;
409 		ivars->ifmedia_upd = ifmedia_upd;
410 		ivars->ifmedia_sts = ifmedia_sts;
411 		ivars->mii_flags = flags;
412 		*miibus = device_add_child(dev, "miibus", -1);
413 		if (*miibus == NULL) {
414 			rv = ENXIO;
415 			goto fail;
416 		}
417 		device_set_ivars(*miibus, ivars);
418 	} else {
419 		ivars = device_get_ivars(*miibus);
420 		if (ivars->ifp != ifp || ivars->ifmedia_upd != ifmedia_upd ||
421 		    ivars->ifmedia_sts != ifmedia_sts ||
422 		    ivars->mii_flags != flags) {
423 			printf("%s: non-matching invariant\n", __func__);
424 			return (EINVAL);
425 		}
426 		/*
427 		 * Assignment of the attach arguments mii_data for the first
428 		 * pass is done in miibus_attach(), i.e. once the miibus softc
429 		 * has been allocated.
430 		 */
431 		ma.mii_data = device_get_softc(*miibus);
432 	}
433 
434 	ma.mii_capmask = capmask;
435 
436 	if (resource_int_value(device_get_name(*miibus),
437 	    device_get_unit(*miibus), "phymask", &phymask) != 0)
438 		phymask = 0xffffffff;
439 
440 	if (device_get_children(*miibus, &children, &nchildren) != 0) {
441 		children = NULL;
442 		nchildren = 0;
443 	}
444 	ivars->mii_offset = 0;
445 	for (ma.mii_phyno = phymin; ma.mii_phyno <= phymax; ma.mii_phyno++) {
446 		/*
447 		 * Make sure we haven't already configured a PHY at this
448 		 * address.  This allows mii_attach() to be called
449 		 * multiple times.
450 		 */
451 		for (i = 0; i < nchildren; i++) {
452 			args = device_get_ivars(children[i]);
453 			if (args->mii_phyno == ma.mii_phyno) {
454 				/*
455 				 * Yes, there is already something
456 				 * configured at this address.
457 				 */
458 				goto skip;
459 			}
460 		}
461 
462 		/*
463 		 * Check to see if there is a PHY at this address.  Note,
464 		 * many braindead PHYs report 0/0 in their ID registers,
465 		 * so we test for media in the BMSR.
466 		 */
467 		bmsr = MIIBUS_READREG(dev, ma.mii_phyno, MII_BMSR);
468 		if (bmsr == 0 || bmsr == 0xffff ||
469 		    (bmsr & (BMSR_EXTSTAT | BMSR_MEDIAMASK)) == 0) {
470 			/* Assume no PHY at this address. */
471 			continue;
472 		}
473 
474 		/*
475 		 * There is a PHY at this address.  If we were given an
476 		 * `offset' locator, skip this PHY if it doesn't match.
477 		 */
478 		if (offloc != MII_OFFSET_ANY && offloc != ivars->mii_offset)
479 			goto skip;
480 
481 		/*
482 		 * Skip this PHY if it's not included in the phymask hint.
483 		 */
484 		if ((phymask & (1 << ma.mii_phyno)) == 0)
485 			goto skip;
486 
487 		/*
488 		 * Extract the IDs.  Braindead PHYs will be handled by
489 		 * the `ukphy' driver, as we have no ID information to
490 		 * match on.
491 		 */
492 		ma.mii_id1 = MIIBUS_READREG(dev, ma.mii_phyno, MII_PHYIDR1);
493 		ma.mii_id2 = MIIBUS_READREG(dev, ma.mii_phyno, MII_PHYIDR2);
494 
495 		ma.mii_offset = ivars->mii_offset;
496 		args = malloc(sizeof(struct mii_attach_args), M_DEVBUF,
497 		    M_NOWAIT);
498 		if (args == NULL)
499 			goto skip;
500 		bcopy((char *)&ma, (char *)args, sizeof(ma));
501 		phy = device_add_child(*miibus, NULL, -1);
502 		if (phy == NULL) {
503 			free(args, M_DEVBUF);
504 			goto skip;
505 		}
506 		device_set_ivars(phy, args);
507  skip:
508 		ivars->mii_offset++;
509 	}
510 	free(children, M_TEMP);
511 
512 	if (first != 0) {
513 		rv = device_set_driver(*miibus, &miibus_driver);
514 		if (rv != 0)
515 			goto fail;
516 		bus_enumerate_hinted_children(*miibus);
517 		rv = device_get_children(*miibus, &children, &nchildren);
518 		if (rv != 0)
519 			goto fail;
520 		free(children, M_TEMP);
521 		if (nchildren == 0) {
522 			rv = ENXIO;
523 			goto fail;
524 		}
525 		rv = bus_generic_attach(dev);
526 		if (rv != 0)
527 			goto fail;
528 
529 		/* Attaching of the PHY drivers is done in miibus_attach(). */
530 		return (0);
531 	}
532 	rv = bus_generic_attach(*miibus);
533 	if (rv != 0)
534 		goto fail;
535 
536 	return (0);
537 
538  fail:
539 	if (*miibus != NULL)
540 		device_delete_child(dev, *miibus);
541 	free(ivars, M_DEVBUF);
542 	if (first != 0)
543 		*miibus = NULL;
544 	return (rv);
545 }
546 
547 /*
548  * Media changed; notify all PHYs.
549  */
550 int
551 mii_mediachg(struct mii_data *mii)
552 {
553 	struct mii_softc *child;
554 	struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
555 	int rv;
556 
557 	mii->mii_media_status = 0;
558 	mii->mii_media_active = IFM_NONE;
559 
560 	LIST_FOREACH(child, &mii->mii_phys, mii_list) {
561 		/*
562 		 * If the media indicates a different PHY instance,
563 		 * isolate this one.
564 		 */
565 		if (IFM_INST(ife->ifm_media) != child->mii_inst) {
566 			if ((child->mii_flags & MIIF_NOISOLATE) != 0) {
567 				device_printf(child->mii_dev, "%s: "
568 				    "can't handle non-zero PHY instance %d\n",
569 				    __func__, child->mii_inst);
570 				continue;
571 			}
572 			PHY_WRITE(child, MII_BMCR, PHY_READ(child, MII_BMCR) |
573 			    BMCR_ISO);
574 			continue;
575 		}
576 		rv = PHY_SERVICE(child, mii, MII_MEDIACHG);
577 		if (rv)
578 			return (rv);
579 	}
580 	return (0);
581 }
582 
583 /*
584  * Call the PHY tick routines, used during autonegotiation.
585  */
586 void
587 mii_tick(struct mii_data *mii)
588 {
589 	struct mii_softc *child;
590 	struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
591 
592 	LIST_FOREACH(child, &mii->mii_phys, mii_list) {
593 		/*
594 		 * If this PHY instance isn't currently selected, just skip
595 		 * it.
596 		 */
597 		if (IFM_INST(ife->ifm_media) != child->mii_inst)
598 			continue;
599 		(void)PHY_SERVICE(child, mii, MII_TICK);
600 	}
601 }
602 
603 /*
604  * Get media status from PHYs.
605  */
606 void
607 mii_pollstat(struct mii_data *mii)
608 {
609 	struct mii_softc *child;
610 	struct ifmedia_entry *ife = mii->mii_media.ifm_cur;
611 
612 	mii->mii_media_status = 0;
613 	mii->mii_media_active = IFM_NONE;
614 
615 	LIST_FOREACH(child, &mii->mii_phys, mii_list) {
616 		/*
617 		 * If we're not polling this PHY instance, just skip it.
618 		 */
619 		if (IFM_INST(ife->ifm_media) != child->mii_inst)
620 			continue;
621 		(void)PHY_SERVICE(child, mii, MII_POLLSTAT);
622 	}
623 }
624 
625 static unsigned char
626 mii_bitreverse(unsigned char x)
627 {
628 	static unsigned const char nibbletab[16] = {
629 		0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15
630 	};
631 
632 	return ((nibbletab[x & 15] << 4) | nibbletab[x >> 4]);
633 }
634 
635 u_int
636 mii_oui(u_int id1, u_int id2)
637 {
638 	u_int h;
639 
640 	h = (id1 << 6) | (id2 >> 10);
641 
642 	return ((mii_bitreverse(h >> 16) << 16) |
643 	    (mii_bitreverse((h >> 8) & 0xff) << 8) |
644 	    mii_bitreverse(h & 0xff));
645 }
646 
647 int
648 mii_phy_mac_match(struct mii_softc *mii, const char *name)
649 {
650 
651 	return (strcmp(device_get_name(device_get_parent(mii->mii_dev)),
652 	    name) == 0);
653 }
654 
655 int
656 mii_dev_mac_match(device_t parent, const char *name)
657 {
658 
659 	return (strcmp(device_get_name(device_get_parent(
660 	    device_get_parent(parent))), name) == 0);
661 }
662 
663 void *
664 mii_phy_mac_softc(struct mii_softc *mii)
665 {
666 
667 	return (device_get_softc(device_get_parent(mii->mii_dev)));
668 }
669 
670 void *
671 mii_dev_mac_softc(device_t parent)
672 {
673 
674 	return (device_get_softc(device_get_parent(device_get_parent(parent))));
675 }
676