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
44typedef enum {
45	NIC_PORT_UNKNOWN,
46	NIC_PORT_SFF
47} nic_port_type_t;
48
49static const topo_pgroup_info_t datalink_pgroup = {
50	TOPO_PGROUP_DATALINK,
51	TOPO_STABILITY_PRIVATE,
52	TOPO_STABILITY_PRIVATE,
53	1
54};
55
56typedef 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 */
67static boolean_t
68nic_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
86static int
87nic_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 */
234static int
235nic_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 */
333static int
334nic_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
403static const topo_modops_t nic_ops = {
404	nic_enum, NULL
405};
406
407static topo_modinfo_t nic_mod = {
408	NIC, FM_FMRI_SCHEME_HC, NIC_VERSION, &nic_ops
409};
410
411int
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
439void
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