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) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
24 */
25
26 /*
27 * Subroutines used by the i86pc Generic Topology Enumerator
28 */
29
30 #include <sys/types.h>
31 #include <strings.h>
32 #include <deflt.h>
33 #include <fcntl.h>
34 #include <unistd.h>
35 #include <fm/topo_mod.h>
36 #include <fm/topo_hc.h>
37 #include <sys/devfm.h>
38 #include <sys/pci.h>
39 #include <sys/systeminfo.h>
40 #include <sys/fm/protocol.h>
41 #include <sys/utsname.h>
42 #include <sys/smbios.h>
43 #include <sys/smbios_impl.h>
44 #include <x86pi_impl.h>
45
46
47 static const topo_pgroup_info_t sys_pgroup = {
48 TOPO_PGROUP_SYSTEM,
49 TOPO_STABILITY_PRIVATE,
50 TOPO_STABILITY_PRIVATE,
51 1
52 };
53
54 static const topo_pgroup_info_t auth_pgroup = {
55 FM_FMRI_AUTHORITY,
56 TOPO_STABILITY_PRIVATE,
57 TOPO_STABILITY_PRIVATE,
58 1
59 };
60
61
62 /*
63 * Free hcfmri strings.
64 */
65 void
x86pi_hcfmri_info_fini(topo_mod_t * mod,x86pi_hcfmri_t * hc)66 x86pi_hcfmri_info_fini(topo_mod_t *mod, x86pi_hcfmri_t *hc)
67 {
68 if (hc->hc_name != NULL)
69 topo_mod_strfree(mod, (char *)hc->hc_name);
70 if (hc->manufacturer != NULL)
71 topo_mod_strfree(mod, (char *)hc->manufacturer);
72 if (hc->product != NULL)
73 topo_mod_strfree(mod, (char *)hc->product);
74 if (hc->version != NULL)
75 topo_mod_strfree(mod, (char *)hc->version);
76 if (hc->serial_number != NULL)
77 topo_mod_strfree(mod, (char *)hc->serial_number);
78 if (hc->asset_tag != NULL)
79 topo_mod_strfree(mod, (char *)hc->asset_tag);
80 if (hc->location != NULL)
81 topo_mod_strfree(mod, (char *)hc->location);
82 if (hc->part_number != NULL)
83 topo_mod_strfree(mod, (char *)hc->part_number);
84 }
85
86
87 /*
88 * Get the server hostname (the ID as far as the topo authority is
89 * concerned) from sysinfo and return a copy to the caller.
90 *
91 * The string must be freed with topo_mod_strfree()
92 */
93 char *
x86pi_get_serverid(topo_mod_t * mod)94 x86pi_get_serverid(topo_mod_t *mod)
95 {
96 int result;
97 char hostname[MAXNAMELEN];
98
99 topo_mod_dprintf(mod, "x86pi_get_serverid\n");
100
101 result = sysinfo(SI_HOSTNAME, hostname, sizeof (hostname));
102 /* Everything is freed up and it's time to return the platform-id */
103 if (result == -1) {
104 return (NULL);
105 }
106 topo_mod_dprintf(mod, "x86pi_get_serverid: hostname = %s\n", hostname);
107
108 return (topo_mod_strdup(mod, hostname));
109 }
110
111
112 /*
113 * Get copy of SMBIOS.
114 */
115 smbios_hdl_t *
x86pi_smb_open(topo_mod_t * mod)116 x86pi_smb_open(topo_mod_t *mod)
117 {
118 smbios_hdl_t *smb_hdl;
119 char *f = "x86pi_smb_open";
120
121 topo_mod_dprintf(mod, "%s\n", f);
122
123 smb_hdl = topo_mod_smbios(mod);
124 if (smb_hdl == NULL) {
125 topo_mod_dprintf(mod, "%s: failed to load SMBIOS\n", f);
126 return (NULL);
127 }
128
129 return (smb_hdl);
130 }
131
132
133 /*
134 * Go through the smbios structures looking for a type. Fill in
135 * the structure count as well as the id(s) of the struct types.
136 */
137 void
x86pi_smb_strcnt(topo_mod_t * mod,smbs_cnt_t * stype)138 x86pi_smb_strcnt(topo_mod_t *mod, smbs_cnt_t *stype)
139 {
140 const smb_struct_t *sp;
141 int nstructs;
142 int i, cnt;
143 smbios_hdl_t *shp;
144
145 shp = topo_mod_smbios(mod);
146 if (shp == NULL) {
147 stype->count = 0;
148 return;
149 }
150
151 nstructs = shp->sh_nstructs;
152 sp = shp->sh_structs;
153
154 for (i = 0, cnt = 0; i < nstructs; i++, sp++) {
155 if (sp->smbst_hdr->smbh_type == stype->type) {
156 stype->ids[cnt].node = NULL;
157 stype->ids[cnt].id = sp->smbst_hdr->smbh_hdl;
158 cnt++;
159 }
160 }
161
162 stype->count = cnt;
163 }
164
165
166 /*
167 * Calculate the authority information for a node. Inherit the data if
168 * possible, but always create an appropriate property group.
169 */
170 int
x86pi_set_auth(topo_mod_t * mod,x86pi_hcfmri_t * hcfmri,tnode_t * t_parent,tnode_t * t_node)171 x86pi_set_auth(topo_mod_t *mod, x86pi_hcfmri_t *hcfmri, tnode_t *t_parent,
172 tnode_t *t_node)
173 {
174 int result;
175 int err;
176 int is_chassis = 0;
177 int chassis_instance = 0;
178 nvlist_t *auth;
179 char *val = NULL;
180 char *prod = NULL;
181 char *psn = NULL;
182 char *csn = NULL;
183 char *server = NULL;
184 char *f = "x86pi_set_auth";
185
186 if (mod == NULL || t_parent == NULL || t_node == NULL) {
187 return (-1);
188 }
189
190 result = topo_pgroup_create(t_node, &auth_pgroup, &err);
191 if (result != 0 && err != ETOPO_PROP_DEFD) {
192 /*
193 * We failed to create the property group and it was not
194 * already defined. Set the err code and return failure.
195 */
196 (void) topo_mod_seterrno(mod, err);
197 return (-1);
198 }
199
200 /* Get the authority information already available from the parent */
201 auth = topo_mod_auth(mod, t_parent);
202
203 /* Determnine if this is a chassis node and set it's instance */
204 if ((strlen(hcfmri->hc_name) == strlen(CHASSIS)) &&
205 strncmp(hcfmri->hc_name, CHASSIS, strlen(CHASSIS)) == 0) {
206 is_chassis = 1;
207 chassis_instance = hcfmri->instance;
208 }
209
210 /*
211 * Set the authority data, inheriting it if possible, but creating it
212 * if necessary.
213 */
214
215 /* product-id */
216 result = topo_prop_inherit(t_node, FM_FMRI_AUTHORITY,
217 FM_FMRI_AUTH_PRODUCT, &err);
218 if (result != 0 && err != ETOPO_PROP_DEFD) {
219 result = nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT,
220 &prod);
221 if (result != 0 || prod == NULL) {
222 /*
223 * No product information in the parent node or auth
224 * list. Use the product information in the hcfrmi
225 * struct.
226 */
227 prod = (char *)hcfmri->product;
228 if (prod == NULL) {
229 topo_mod_dprintf(mod, "%s: product name not "
230 "found for %s node\n", f, hcfmri->hc_name);
231 }
232 }
233
234 /*
235 * We continue even if the product information is not available
236 * to enumerate as much as possible.
237 */
238 if (prod != NULL) {
239 result = topo_prop_set_string(t_node, FM_FMRI_AUTHORITY,
240 FM_FMRI_AUTH_PRODUCT, TOPO_PROP_IMMUTABLE, prod,
241 &err);
242 if (result != 0) {
243 /* Preserve the error and continue */
244 (void) topo_mod_seterrno(mod, err);
245 topo_mod_dprintf(mod, "%s: failed to set "
246 "property %s (%d) : %s\n", f,
247 FM_FMRI_AUTH_PRODUCT, err,
248 topo_strerror(err));
249 }
250 }
251 }
252
253 /* product-sn */
254 result = topo_prop_inherit(t_node, FM_FMRI_AUTHORITY,
255 FM_FMRI_AUTH_PRODUCT_SN, &err);
256 if (result != 0 && err != ETOPO_PROP_DEFD) {
257 result = nvlist_lookup_string(auth, FM_FMRI_AUTH_PRODUCT_SN,
258 &psn);
259 if (result != 0 || psn == NULL) {
260 /*
261 * No product-sn information in the parent node or auth
262 * list.
263 */
264 topo_mod_dprintf(mod, "%s: psn not found\n", f);
265 } else {
266 /*
267 * We continue even if the product-sn information is
268 * not available to enumerate as much as possible.
269 */
270 result = topo_prop_set_string(t_node, FM_FMRI_AUTHORITY,
271 FM_FMRI_AUTH_PRODUCT_SN, TOPO_PROP_IMMUTABLE, psn,
272 &err);
273 if (result != 0) {
274 /* Preserve the error and continue */
275 (void) topo_mod_seterrno(mod, err);
276 topo_mod_dprintf(mod, "%s: failed to "
277 "set property %s (%d) : %s\n", f,
278 FM_FMRI_AUTH_PRODUCT_SN, err,
279 topo_strerror(err));
280 }
281 }
282 }
283
284 /* chassis-id */
285 if (is_chassis == 0 || (is_chassis == 1 && chassis_instance == 0)) {
286 /* either not a chassis node, or chassis #0 */
287 result = topo_prop_inherit(t_node, FM_FMRI_AUTHORITY,
288 FM_FMRI_AUTH_CHASSIS, &err);
289 } else {
290 /* chassis 'n' in a >1 chassis system */
291 result = err = -1;
292 }
293 if (result != 0 && err != ETOPO_PROP_DEFD) {
294 if (is_chassis == 0) {
295 result = nvlist_lookup_string(auth,
296 FM_FMRI_AUTH_CHASSIS, &csn);
297 if (result != 0 || csn == NULL) {
298 /*
299 * No chassis information in the parent
300 * node or auth list.
301 */
302 topo_mod_dprintf(mod,
303 "%s: csn name not found\n", f);
304 }
305 } else {
306 /*
307 * So as not to blindly set the chassis-id to
308 * chassis #0's serial number.
309 */
310 csn = val = topo_mod_strdup(mod, hcfmri->serial_number);
311 }
312
313 /*
314 * We continue even if the chassis information is not available
315 * to enumerate as much as possible.
316 */
317 if (csn != NULL) {
318 if (is_chassis == 1)
319 result = topo_prop_set_string(t_node,
320 FM_FMRI_AUTHORITY, FM_FMRI_AUTH_CHASSIS,
321 TOPO_PROP_MUTABLE, csn, &err);
322 else
323 result = topo_prop_set_string(t_node,
324 FM_FMRI_AUTHORITY, FM_FMRI_AUTH_CHASSIS,
325 TOPO_PROP_IMMUTABLE, csn, &err);
326
327 if (result != 0) {
328 /* Preserve the error and continue */
329 (void) topo_mod_seterrno(mod, err);
330 topo_mod_dprintf(mod, "%s: failed to "
331 "set property %s (%d) : %s\n", f,
332 FM_FMRI_AUTH_CHASSIS, err,
333 topo_strerror(err));
334 }
335 }
336
337 if (val != NULL) {
338 topo_mod_strfree(mod, val);
339 val = NULL;
340 }
341 }
342
343 /* server-id */
344 result = topo_prop_inherit(t_node, FM_FMRI_AUTHORITY,
345 FM_FMRI_AUTH_SERVER, &err);
346 if (result != 0 && err != ETOPO_PROP_DEFD) {
347 result = nvlist_lookup_string(auth, FM_FMRI_AUTH_SERVER,
348 &server);
349 if (result != 0 || server == NULL) {
350 /*
351 * No server information in the parent node or auth
352 * list. Find the server information in hostname.
353 */
354 server = val = x86pi_get_serverid(mod);
355 if (server == NULL) {
356 topo_mod_dprintf(mod, "%s: server "
357 "name not found for %s node\n", f,
358 hcfmri->hc_name);
359 }
360 }
361
362 /*
363 * We continue even if the server information is not available
364 * to enumerate as much as possible.
365 */
366 if (server != NULL) {
367 result = topo_prop_set_string(t_node, FM_FMRI_AUTHORITY,
368 FM_FMRI_AUTH_SERVER, TOPO_PROP_IMMUTABLE, server,
369 &err);
370 if (result != 0) {
371 /* Preserve the error and continue */
372 (void) topo_mod_seterrno(mod, err);
373 topo_mod_dprintf(mod, "%s: failed to "
374 "set property %s (%d) : %s\n", f,
375 FM_FMRI_AUTH_SERVER, err,
376 topo_strerror(err));
377 }
378 }
379
380 if (val != NULL)
381 topo_mod_strfree(mod, val);
382 }
383
384 nvlist_free(auth);
385
386 return (0);
387 }
388
389
390 /*
391 * Calculate a generic FRU for the given node. If the node is not a FRU,
392 * then inherit the FRU data from the nodes parent.
393 */
394 int
x86pi_set_frufmri(topo_mod_t * mod,x86pi_hcfmri_t * hcfmri,tnode_t * t_parent,tnode_t * t_node,int flag)395 x86pi_set_frufmri(topo_mod_t *mod, x86pi_hcfmri_t *hcfmri, tnode_t *t_parent,
396 tnode_t *t_node, int flag)
397 {
398 int result;
399 int err;
400
401 nvlist_t *auth = NULL;
402 nvlist_t *frufmri = NULL;
403
404 if (t_node == NULL || mod == NULL) {
405 return (-1);
406 }
407
408 /*
409 * Determine if this node is a FRU
410 */
411 if (!(flag & X86PI_ENUM_FRU)) {
412 /* This node is not a FRU. Inherit from parent and return */
413 (void) topo_node_fru_set(t_node, NULL, 0, &result);
414 return (0);
415 }
416
417 /*
418 * This node is a FRU. Create an FMRI.
419 */
420 auth = topo_mod_auth(mod, t_parent);
421 frufmri = topo_mod_hcfmri(mod, t_parent, FM_HC_SCHEME_VERSION,
422 hcfmri->hc_name, hcfmri->instance, NULL, auth,
423 hcfmri->part_number, hcfmri->version, hcfmri->serial_number);
424 if (frufmri == NULL) {
425 topo_mod_dprintf(mod, "failed to create FRU: %s\n",
426 topo_strerror(topo_mod_errno(mod)));
427 }
428 nvlist_free(auth);
429
430 /* Set the FRU, whether NULL or not */
431 result = topo_node_fru_set(t_node, frufmri, 0, &err);
432 if (result != 0) {
433 (void) topo_mod_seterrno(mod, err);
434 }
435 nvlist_free(frufmri);
436
437 return (result);
438 }
439
440
441 /*
442 * Set the label for a topo node.
443 */
444 int
x86pi_set_label(topo_mod_t * mod,const char * label,const char * name,tnode_t * t_node)445 x86pi_set_label(topo_mod_t *mod, const char *label, const char *name,
446 tnode_t *t_node)
447 {
448 int result;
449 int err;
450
451 if (mod == NULL) {
452 return (-1);
453 }
454
455 /*
456 * Set the label for this topology node.
457 * Note that a NULL label will inherit the label from topology
458 * node's parent.
459 */
460 result = topo_node_label_set(t_node, (char *)label, &err);
461 if (result != 0) {
462 (void) topo_mod_seterrno(mod, err);
463 topo_mod_dprintf(mod, "x86pi_set_label: failed with label %s "
464 "on %s node: %s\n", (label == NULL ? "NULL" : label),
465 name, topo_strerror(err));
466 }
467
468 return (result);
469 }
470
471
472 /*
473 * Calculate the system information for a node. Inherit the data if
474 * possible, but always create an appropriate property group.
475 */
476 int
x86pi_set_system(topo_mod_t * mod,tnode_t * t_node)477 x86pi_set_system(topo_mod_t *mod, tnode_t *t_node)
478 {
479 int result;
480 int err;
481 struct utsname uts;
482 char isa[MAXNAMELEN];
483
484 if (mod == NULL || t_node == NULL) {
485 return (-1);
486 }
487
488 result = topo_pgroup_create(t_node, &sys_pgroup, &err);
489 if (result != 0 && err != ETOPO_PROP_DEFD) {
490 /*
491 * We failed to create the property group and it was not
492 * already defined. Set the err code and return failure.
493 */
494 (void) topo_mod_seterrno(mod, err);
495 return (-1);
496 }
497
498 result = topo_prop_inherit(t_node, TOPO_PGROUP_SYSTEM, TOPO_PROP_ISA,
499 &err);
500 if (result != 0 && err != ETOPO_PROP_DEFD) {
501 isa[0] = '\0';
502 result = sysinfo(SI_ARCHITECTURE, isa, sizeof (isa));
503 if (result == -1) {
504 /* Preserve the error and continue */
505 topo_mod_dprintf(mod, "x86pi_set_system: failed to "
506 "read SI_ARCHITECTURE: %d\n", errno);
507 }
508 if (strnlen(isa, MAXNAMELEN) > 0) {
509 result = topo_prop_set_string(t_node,
510 TOPO_PGROUP_SYSTEM, TOPO_PROP_ISA,
511 TOPO_PROP_IMMUTABLE, isa, &err);
512 if (result != 0) {
513 /* Preserve the error and continue */
514 (void) topo_mod_seterrno(mod, err);
515 topo_mod_dprintf(mod,
516 "x86pi_set_auth: failed to "
517 "set property %s (%d) : %s\n",
518 TOPO_PROP_ISA, err, topo_strerror(err));
519 }
520 }
521 }
522
523 result = topo_prop_inherit(t_node, TOPO_PGROUP_SYSTEM,
524 TOPO_PROP_MACHINE, &err);
525 if (result != 0 && err != ETOPO_PROP_DEFD) {
526 result = uname(&uts);
527 if (result == -1) {
528 /* Preserve the error and continue */
529 (void) topo_mod_seterrno(mod, errno);
530 topo_mod_dprintf(mod, "x86pi_set_system: failed to "
531 "read uname: %d\n", errno);
532 }
533 if (strnlen(uts.machine, sizeof (uts.machine)) > 0) {
534 result = topo_prop_set_string(t_node,
535 TOPO_PGROUP_SYSTEM, TOPO_PROP_MACHINE,
536 TOPO_PROP_IMMUTABLE, uts.machine, &err);
537 if (result != 0) {
538 /* Preserve the error and continue */
539 (void) topo_mod_seterrno(mod, err);
540 topo_mod_dprintf(mod,
541 "x86pi_set_auth: failed to "
542 "set property %s (%d) : %s\n",
543 TOPO_PROP_MACHINE, err, topo_strerror(err));
544 }
545 }
546 }
547
548 return (0);
549 }
550
551 /*
552 * All the checks for compatibility are done within the kernel where the
553 * ereport generators are. They'll determine first if there's a problem
554 * and the topo enum will follow suit. The /dev/fm ioclt returns the value
555 * of the x86gentopo_legacy kernel variable which determines if this platform
556 * will provide an x86 generic topo or legacy topo enumeration.
557 */
558 /* ARGSUSED */
559 int
x86pi_check_comp(topo_mod_t * mod)560 x86pi_check_comp(topo_mod_t *mod)
561 {
562 int rv;
563 int fd;
564 int32_t legacy;
565 nvlist_t *nvl = NULL;
566 fm_ioc_data_t fid;
567 char *ibuf = NULL, *obuf = NULL;
568 size_t insz = 0, outsz = 0;
569 char *f = "x86pi_check_comp";
570 smbios_hdl_t *shp;
571
572 shp = topo_mod_smbios(mod);
573 if (shp == NULL)
574 return (X86PI_NONE);
575
576 /* open /dev/fm */
577 fd = open("/dev/fm", O_RDONLY);
578 if (fd < 0) {
579 topo_mod_dprintf(mod, "%s: failed to open /dev/fm.\n", f);
580 return (X86PI_NONE);
581 }
582
583 /* set up buffers and ioctl data structure */
584 outsz = FM_IOC_MAXBUFSZ;
585 obuf = topo_mod_alloc(mod, outsz);
586 if (obuf == NULL) {
587 perror("umem_alloc");
588 return (X86PI_NONE);
589 }
590
591 fid.fid_version = 1;
592 fid.fid_insz = insz;
593 fid.fid_inbuf = ibuf;
594 fid.fid_outsz = outsz;
595 fid.fid_outbuf = obuf;
596
597 /* send the ioctl to /dev/fm to retrieve legacy variable */
598 rv = ioctl(fd, FM_IOC_GENTOPO_LEGACY, &fid);
599 if (rv < 0) {
600 topo_mod_dprintf(mod, "%s: ioctl to /dev/fm failed", f);
601 perror("fm_ioctl");
602 (void) close(fd);
603 return (X86PI_NONE);
604 }
605 (void) close(fd);
606
607 (void) nvlist_unpack(fid.fid_outbuf, fid.fid_outsz, &nvl, 0);
608 (void) nvlist_lookup_int32(nvl, FM_GENTOPO_LEGACY, &legacy);
609
610 nvlist_free(nvl);
611 topo_mod_free(mod, obuf, outsz);
612
613 if (legacy == 1) {
614 /* legacy kernel variable set; will do the same */
615 return (X86PI_NONE);
616 }
617
618 /* legacy kernel variable not set; generic topo enum */
619 return (X86PI_FULL);
620 }
621
622 const char *
x86pi_cleanup_smbios_str(topo_mod_t * mod,const char * begin,int str_type)623 x86pi_cleanup_smbios_str(topo_mod_t *mod, const char *begin, int str_type)
624 {
625 char buf[MAXNAMELEN];
626 const char *end, *cp;
627 char *pp;
628 char c;
629 int i;
630
631 end = begin + strlen(begin);
632
633 while (begin < end && isspace(*begin))
634 begin++;
635 while (begin < end && isspace(*(end - 1)))
636 end--;
637
638 if (begin >= end)
639 return (NULL);
640
641 cp = begin;
642 for (i = 0; i < MAXNAMELEN - 1; i++) {
643 if (cp >= end)
644 break;
645 c = *cp;
646 if (str_type == LABEL) {
647 if (!isprint(c))
648 buf[i] = '-';
649 else
650 buf[i] = c;
651 } else {
652 if (c == ':' || c == '=' || c == '/' ||
653 isspace(c) || !isprint(c))
654 buf[i] = '-';
655 else
656 buf[i] = c;
657 }
658 cp++;
659 }
660 buf[i] = 0;
661
662 pp = topo_mod_strdup(mod, buf);
663
664 if (str_type == LABEL)
665 topo_mod_strfree(mod, (char *)begin);
666
667 return (pp);
668 }
669
670 /*
671 * Return Bus/Dev/Func from "reg" devinfo property.
672 */
673 uint16_t
x86pi_bdf(topo_mod_t * mod,di_node_t node)674 x86pi_bdf(topo_mod_t *mod, di_node_t node)
675 {
676 int *val;
677
678 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "reg", &val) < 0) {
679 topo_mod_dprintf(mod, "couldn't get \"reg\" prop: %s.\n",
680 strerror(errno));
681 return ((uint16_t)-1);
682 }
683
684 return (uint16_t)((*val & PCI_REG_BDFR_M) >> PCI_REG_FUNC_SHIFT);
685 }
686
687 /*
688 * Return PHY from "sata-phy" devinfo proporty.
689 */
690 int
x86pi_phy(topo_mod_t * mod,di_node_t node)691 x86pi_phy(topo_mod_t *mod, di_node_t node)
692 {
693 int *phy;
694
695 if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "sata-phy", &phy) < 0) {
696 topo_mod_dprintf(mod, "couldn't get \"sata-phy\" prop: %s.\n",
697 strerror(errno));
698 return (-1);
699 }
700
701 return (*phy);
702 }
703