xref: /illumos-gate/usr/src/lib/fm/topo/modules/common/nic/topo_nic.c (revision c94be9439c4f0773ef60e2cec21d548359cfea20)
1 /*
2  * This file and its contents are supplied under the terms of the
3  * Common Development and Distribution License ("CDDL"), version 1.0.
4  * You may only use this file in accordance with the terms of version
5  * 1.0 of the CDDL.
6  *
7  * A full copy of the text of the CDDL should have accompanied this
8  * source.  A copy of the CDDL is also available via the Internet at
9  * http://www.illumos.org/license/CDDL.
10  */
11 
12 /*
13  * Copyright (c) 2017, Joyent, Inc.
14  * Copyright 2020 Robert Mustacchi
15  */
16 
17 /*
18  * This module covers enumerating properties of physical NICs. At this time, as
19  * various devices are discovered that may relate to various networking gear, we
20  * will attempt to enumerate ports and transceivers under them, if requested.
21  */
22 
23 #include <strings.h>
24 #include <libdevinfo.h>
25 #include <libdladm.h>
26 #include <libdllink.h>
27 #include <libdlstat.h>
28 #include <libsff.h>
29 #include <unistd.h>
30 #include <sys/dld_ioc.h>
31 #include <sys/dld.h>
32 #include <sys/mac.h>
33 
34 #include <sys/fm/protocol.h>
35 #include <fm/topo_mod.h>
36 #include <fm/topo_list.h>
37 #include <fm/topo_method.h>
38 
39 #include <topo_port.h>
40 #include <topo_transceiver.h>
41 
42 #include "topo_nic.h"
43 
44 typedef enum {
45 	NIC_PORT_UNKNOWN,
46 	NIC_PORT_SFF
47 } nic_port_type_t;
48 
49 static const topo_pgroup_info_t datalink_pgroup = {
50 	TOPO_PGROUP_DATALINK,
51 	TOPO_STABILITY_PRIVATE,
52 	TOPO_STABILITY_PRIVATE,
53 	1
54 };
55 
56 typedef struct nic_port_mac {
57 	char npm_mac[ETHERADDRSTRL];
58 	boolean_t npm_valid;
59 	topo_mod_t *npm_mod;
60 } nic_port_mac_t;
61 
62 /*
63  * The first MAC address is always the primary MAC address, so we only worry
64  * about the first. Thus this function always returns B_FALSE, to terminate
65  * iteration.
66  */
67 static boolean_t
68 nic_port_datalink_mac_cb(void *arg, dladm_macaddr_attr_t *attr)
69 {
70 	nic_port_mac_t *mac = arg;
71 
72 	if (attr->ma_addrlen != ETHERADDRL) {
73 		topo_mod_dprintf(mac->npm_mod,
74 		    "found address with bad length: %u\n", attr->ma_addrlen);
75 		return (B_FALSE);
76 	}
77 
78 	(void) snprintf(mac->npm_mac, sizeof (mac->npm_mac),
79 	    "%02x:%02x:%02x:%02x:%02x:%02x",
80 	    attr->ma_addr[0], attr->ma_addr[1], attr->ma_addr[2],
81 	    attr->ma_addr[3], attr->ma_addr[4], attr->ma_addr[5]);
82 	mac->npm_valid = B_TRUE;
83 	return (B_FALSE);
84 }
85 
86 static int
87 nic_port_datalink_props(topo_mod_t *mod, tnode_t *port, dladm_handle_t handle,
88     datalink_id_t linkid)
89 {
90 	int err;
91 	dladm_status_t status;
92 	uint64_t ifspeed;
93 	link_duplex_t duplex;
94 	link_state_t state;
95 	const char *duplex_str, *state_str;
96 	datalink_class_t dlclass;
97 	uint32_t media;
98 	char dlname[MAXLINKNAMELEN * 2];
99 	char dlerr[DLADM_STRSIZE];
100 	nic_port_mac_t mac;
101 
102 	status = dladm_datalink_id2info(handle, linkid, NULL, &dlclass, &media,
103 	    dlname, sizeof (dlname));
104 	if (status != DLADM_STATUS_OK) {
105 		topo_mod_dprintf(mod, "failed to get link info: %s\n",
106 		    dladm_status2str(status, dlerr));
107 		return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
108 	}
109 
110 	if (dlclass != DATALINK_CLASS_PHYS) {
111 		return (0);
112 	}
113 
114 	status = dladm_get_single_mac_stat(handle, linkid, "ifspeed",
115 	    KSTAT_DATA_UINT64, &ifspeed);
116 	if (status != DLADM_STATUS_OK) {
117 		topo_mod_dprintf(mod, "failed to get ifspeed: %s\n",
118 		    dladm_status2str(status, dlerr));
119 		return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
120 	}
121 
122 	status = dladm_get_single_mac_stat(handle, linkid, "link_duplex",
123 	    KSTAT_DATA_UINT32, &duplex);
124 	if (status != DLADM_STATUS_OK) {
125 		topo_mod_dprintf(mod, "failed to get link_duplex: %s\n",
126 		    dladm_status2str(status, dlerr));
127 		return (topo_mod_seterrno(mod, EMOD_UKNOWN_ENUM));
128 	}
129 
130 	switch (duplex) {
131 	case LINK_DUPLEX_HALF:
132 		duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_HALF;
133 		break;
134 	case LINK_DUPLEX_FULL:
135 		duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_FULL;
136 		break;
137 	default:
138 		duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_UNKNOWN;
139 		break;
140 	}
141 
142 	status = dladm_get_single_mac_stat(handle, linkid, "link_state",
143 	    KSTAT_DATA_UINT32, &state);
144 	if (status != DLADM_STATUS_OK) {
145 		topo_mod_dprintf(mod, "failed to get link_duplex: %s\n",
146 		    dladm_status2str(status, dlerr));
147 		return (topo_mod_seterrno(mod, status));
148 	}
149 
150 	switch (state) {
151 	case LINK_STATE_UP:
152 		state_str = TOPO_PGROUP_DATALINK_LINK_STATUS_UP;
153 		break;
154 	case LINK_STATE_DOWN:
155 		state_str = TOPO_PGROUP_DATALINK_LINK_STATUS_DOWN;
156 		break;
157 	default:
158 		state_str = TOPO_PGROUP_DATALINK_LINK_STATUS_UNKNOWN;
159 		break;
160 	}
161 
162 	/*
163 	 * Override the duplex if the link is down. Some devices will leave it
164 	 * set at half as opposed to unknown.
165 	 */
166 	if (state == LINK_STATE_DOWN || state == LINK_STATE_UNKNOWN) {
167 		duplex_str = TOPO_PGROUP_DATALINK_LINK_DUPLEX_UNKNOWN;
168 	}
169 
170 	mac.npm_mac[0] = '\0';
171 	mac.npm_valid = B_FALSE;
172 	mac.npm_mod = mod;
173 	if (media == DL_ETHER) {
174 		(void) dladm_walk_macaddr(handle, linkid, &mac,
175 		    nic_port_datalink_mac_cb);
176 	}
177 
178 	if (topo_pgroup_create(port, &datalink_pgroup, &err) != 0) {
179 		topo_mod_dprintf(mod, "falied to create property group %s: "
180 		    "%s\n", TOPO_PGROUP_DATALINK, topo_strerror(err));
181 		return (topo_mod_seterrno(mod, err));
182 	}
183 
184 	if (topo_prop_set_uint64(port, TOPO_PGROUP_DATALINK,
185 	    TOPO_PGROUP_DATALINK_LINK_SPEED, TOPO_PROP_IMMUTABLE, ifspeed,
186 	    &err) != 0) {
187 		topo_mod_dprintf(mod, "failed to set %s property: %s\n",
188 		    TOPO_PGROUP_DATALINK_LINK_SPEED, topo_strerror(err));
189 		return (topo_mod_seterrno(mod, err));
190 	}
191 
192 	if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK,
193 	    TOPO_PGROUP_DATALINK_LINK_DUPLEX, TOPO_PROP_IMMUTABLE, duplex_str,
194 	    &err) != 0) {
195 		topo_mod_dprintf(mod, "failed to set %s property: %s\n",
196 		    TOPO_PGROUP_DATALINK_LINK_DUPLEX, topo_strerror(err));
197 		return (topo_mod_seterrno(mod, err));
198 	}
199 
200 	if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK,
201 	    TOPO_PGROUP_DATALINK_LINK_STATUS, TOPO_PROP_IMMUTABLE, state_str,
202 	    &err) != 0) {
203 		topo_mod_dprintf(mod, "failed to set %s property: %s\n",
204 		    TOPO_PGROUP_DATALINK_LINK_STATUS, topo_strerror(err));
205 		return (topo_mod_seterrno(mod, err));
206 	}
207 
208 	if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK,
209 	    TOPO_PGROUP_DATALINK_LINK_NAME, TOPO_PROP_IMMUTABLE, dlname,
210 	    &err) != 0) {
211 		topo_mod_dprintf(mod, "failed to set %s propery: %s\n",
212 		    TOPO_PGROUP_DATALINK_LINK_NAME, topo_strerror(err));
213 		return (topo_mod_seterrno(mod, err));
214 	}
215 
216 	if (mac.npm_valid) {
217 		if (topo_prop_set_string(port, TOPO_PGROUP_DATALINK,
218 		    TOPO_PGROUP_DATALINK_PMAC, TOPO_PROP_IMMUTABLE,
219 		    mac.npm_mac, &err) != 0) {
220 			topo_mod_dprintf(mod, "failed to set %s propery: %s\n",
221 			    TOPO_PGROUP_DATALINK_PMAC, topo_strerror(err));
222 			return (topo_mod_seterrno(mod, err));
223 		}
224 	}
225 
226 
227 	return (0);
228 }
229 
230 /*
231  * Create an instance of a transceiver with the specified id. We must create
232  * both its port and the transceiver node.
233  */
234 static int
235 nic_create_transceiver(topo_mod_t *mod, tnode_t *pnode, dladm_handle_t handle,
236     datalink_id_t linkid, uint_t tranid, nic_port_type_t port_type)
237 {
238 	int ret;
239 	tnode_t *port;
240 	dld_ioc_gettran_t dgt;
241 	dld_ioc_tranio_t dti;
242 	uint8_t buf[256];
243 	char ouibuf[16];
244 	char *vendor = NULL, *part = NULL, *rev = NULL, *serial = NULL;
245 	nvlist_t *nvl = NULL;
246 
247 	switch (port_type) {
248 	case NIC_PORT_UNKNOWN:
249 		ret = port_create_unknown(mod, pnode, tranid, &port);
250 		break;
251 	case NIC_PORT_SFF:
252 		ret = port_create_sff(mod, pnode, tranid, &port);
253 		break;
254 	}
255 
256 	if ((ret = nic_port_datalink_props(mod, port, handle, linkid)) != 0)
257 		return (ret);
258 
259 	if (port_type != NIC_PORT_SFF)
260 		return (0);
261 
262 	bzero(&dgt, sizeof (dgt));
263 	dgt.dgt_linkid = linkid;
264 	dgt.dgt_tran_id = tranid;
265 
266 	if (ioctl(dladm_dld_fd(handle), DLDIOC_GETTRAN, &dgt) != 0) {
267 		if (errno == ENOTSUP)
268 			return (0);
269 		return (-1);
270 	}
271 
272 	if (dgt.dgt_present == 0)
273 		return (0);
274 
275 	bzero(&dti, sizeof (dti));
276 	dti.dti_linkid = linkid;
277 	dti.dti_tran_id = tranid;
278 	dti.dti_page = 0xa0;
279 	dti.dti_nbytes = sizeof (buf);
280 	dti.dti_buf = (uintptr_t)buf;
281 
282 	if (ioctl(dladm_dld_fd(handle), DLDIOC_READTRAN, &dti) == 0) {
283 		uchar_t *oui;
284 		uint_t nbyte;
285 
286 		if (libsff_parse(buf, dti.dti_nbytes, dti.dti_page,
287 		    &nvl) == 0) {
288 			if ((ret = nvlist_lookup_string(nvl, LIBSFF_KEY_VENDOR,
289 			    &vendor)) != 0 && nvlist_lookup_byte_array(nvl,
290 			    LIBSFF_KEY_OUI, &oui, &nbyte) == 0 && nbyte == 3) {
291 				if (snprintf(ouibuf, sizeof (ouibuf),
292 				    "%02x:%02x:%02x", oui[0], oui[1], oui[2]) <
293 				    sizeof (ouibuf)) {
294 					vendor = ouibuf;
295 				}
296 			} else if (ret != 0) {
297 				vendor = NULL;
298 			}
299 
300 			if (nvlist_lookup_string(nvl, LIBSFF_KEY_PART,
301 			    &part) != 0) {
302 				part = NULL;
303 			}
304 
305 			if (nvlist_lookup_string(nvl, LIBSFF_KEY_REVISION,
306 			    &rev) != 0) {
307 				rev = NULL;
308 			}
309 
310 			if (nvlist_lookup_string(nvl, LIBSFF_KEY_SERIAL,
311 			    &serial) != 0) {
312 				serial = NULL;
313 			}
314 		}
315 	}
316 
317 	if (transceiver_range_create(mod, port, 0, 0) != 0) {
318 		nvlist_free(nvl);
319 		return (-1);
320 	}
321 
322 	if (transceiver_create_sff(mod, port, 0, dgt.dgt_usable, vendor, part,
323 	    rev, serial, NULL) != 0) {
324 		nvlist_free(nvl);
325 		return (-1);
326 	}
327 
328 	nvlist_free(nvl);
329 	return (0);
330 }
331 
332 /* ARGSUSED */
333 static int
334 nic_enum(topo_mod_t *mod, tnode_t *pnode, const char *name,
335     topo_instance_t min, topo_instance_t max, void *modarg, void *data)
336 {
337 	di_node_t din = data;
338 	datalink_id_t linkid;
339 	dladm_handle_t handle;
340 	dld_ioc_gettran_t dgt;
341 	uint_t ntrans, i;
342 	char dname[MAXNAMELEN];
343 	nic_port_type_t pt;
344 
345 	if (strcmp(name, NIC) != 0) {
346 		topo_mod_dprintf(mod, "nic_enum: asked to enumerate unknown "
347 		    "component: %s\n", name);
348 		return (-1);
349 	}
350 
351 	if (din == NULL) {
352 		topo_mod_dprintf(mod, "nic_enum: missing data argument\n");
353 		return (-1);
354 	}
355 
356 	if ((handle = topo_mod_getspecific(mod)) == NULL) {
357 		topo_mod_dprintf(mod, "nic_enum: failed to get nic module "
358 		    "specific data\n");
359 		return (-1);
360 	}
361 
362 	if (snprintf(dname, sizeof (dname), "%s%d", di_driver_name(din),
363 	    di_instance(din)) >= sizeof (dname)) {
364 		topo_mod_dprintf(mod, "nic_enum: device name overflowed "
365 		    "internal buffer\n");
366 		return (-1);
367 	}
368 
369 	if (dladm_dev2linkid(handle, dname, &linkid) != DLADM_STATUS_OK)
370 		return (-1);
371 
372 	bzero(&dgt, sizeof (dgt));
373 	dgt.dgt_linkid = linkid;
374 	dgt.dgt_tran_id = DLDIOC_GETTRAN_GETNTRAN;
375 
376 	if (ioctl(dladm_dld_fd(handle), DLDIOC_GETTRAN, &dgt) != 0) {
377 		if (errno != ENOTSUP) {
378 			return (-1);
379 		}
380 		pt = NIC_PORT_UNKNOWN;
381 		dgt.dgt_tran_id = 1;
382 	} else {
383 		pt = NIC_PORT_SFF;
384 	}
385 
386 	ntrans = dgt.dgt_tran_id;
387 	if (ntrans == 0)
388 		return (0);
389 
390 	if (port_range_create(mod, pnode, 0, ntrans - 1) != 0)
391 		return (-1);
392 
393 	for (i = 0; i < ntrans; i++) {
394 		if (nic_create_transceiver(mod, pnode, handle, linkid, i,
395 		    pt) != 0) {
396 			return (-1);
397 		}
398 	}
399 
400 	return (0);
401 }
402 
403 static const topo_modops_t nic_ops = {
404 	nic_enum, NULL
405 };
406 
407 static topo_modinfo_t nic_mod = {
408 	NIC, FM_FMRI_SCHEME_HC, NIC_VERSION, &nic_ops
409 };
410 
411 int
412 _topo_init(topo_mod_t *mod, topo_version_t version)
413 {
414 	dladm_handle_t handle;
415 
416 	if (getenv("TOPONICDEBUG") != NULL)
417 		topo_mod_setdebug(mod);
418 
419 	topo_mod_dprintf(mod, "_mod_init: "
420 	    "initializing %s enumerator\n", NIC);
421 
422 	if (version != NIC_VERSION) {
423 		return (-1);
424 	}
425 
426 	if (dladm_open(&handle) != 0)
427 		return (-1);
428 
429 	if (topo_mod_register(mod, &nic_mod, TOPO_VERSION) != 0) {
430 		dladm_close(handle);
431 		return (-1);
432 	}
433 
434 	topo_mod_setspecific(mod, handle);
435 
436 	return (0);
437 }
438 
439 void
440 _topo_fini(topo_mod_t *mod)
441 {
442 	dladm_handle_t handle;
443 
444 	if ((handle = topo_mod_getspecific(mod)) == NULL)
445 		return;
446 
447 	dladm_close(handle);
448 	topo_mod_setspecific(mod, NULL);
449 }
450