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 */
15
16/*
17 * This module covers enumerating properties of physical NICs. At this time, as
18 * various devices are discovered that may relate to various networking gear, we
19 * will attempt to enumerate ports and transceivers under them, if requested.
20 */
21
22#include <strings.h>
23#include <libdevinfo.h>
24#include <libdladm.h>
25#include <libdllink.h>
26#include <libsff.h>
27#include <unistd.h>
28#include <sys/dld_ioc.h>
29#include <sys/dld.h>
30
31#include <sys/fm/protocol.h>
32#include <fm/topo_mod.h>
33#include <fm/topo_list.h>
34#include <fm/topo_method.h>
35
36#include <topo_port.h>
37#include <topo_transceiver.h>
38
39#include "topo_nic.h"
40
41/*
42 * Create an instance of a transceiver with the specified id. We must create
43 * both its port and the transceiver node.
44 */
45static int
46nic_create_transceiver(topo_mod_t *mod, tnode_t *pnode, dladm_handle_t handle,
47    datalink_id_t linkid, uint_t tranid)
48{
49	int ret;
50	tnode_t *port;
51	dld_ioc_gettran_t dgt;
52	dld_ioc_tranio_t dti;
53	uint8_t buf[256];
54	char ouibuf[16];
55	char *vendor = NULL, *part = NULL, *rev = NULL, *serial = NULL;
56	nvlist_t *nvl = NULL;
57
58	if ((ret = port_create_sff(mod, pnode, tranid, &port)) != 0)
59		return (ret);
60
61	bzero(&dgt, sizeof (dgt));
62	dgt.dgt_linkid = linkid;
63	dgt.dgt_tran_id = tranid;
64
65	if (ioctl(dladm_dld_fd(handle), DLDIOC_GETTRAN, &dgt) != 0) {
66		if (errno == ENOTSUP)
67			return (0);
68		return (-1);
69	}
70
71	if (dgt.dgt_present == 0)
72		return (0);
73
74	bzero(&dti, sizeof (dti));
75	dti.dti_linkid = linkid;
76	dti.dti_tran_id = tranid;
77	dti.dti_page = 0xa0;
78	dti.dti_nbytes = sizeof (buf);
79	dti.dti_buf = (uintptr_t)buf;
80
81	if (ioctl(dladm_dld_fd(handle), DLDIOC_READTRAN, &dti) == 0) {
82		uchar_t *oui;
83		uint_t nbyte;
84
85		if (libsff_parse(buf, dti.dti_nbytes, dti.dti_page,
86		    &nvl) == 0) {
87			if ((ret = nvlist_lookup_string(nvl, LIBSFF_KEY_VENDOR,
88			    &vendor)) != 0 && nvlist_lookup_byte_array(nvl,
89			    LIBSFF_KEY_OUI, &oui, &nbyte) == 0 && nbyte == 3) {
90				if (snprintf(ouibuf, sizeof (ouibuf),
91				    "%02x:%02x:%02x", oui[0], oui[1], oui[2]) <
92				    sizeof (ouibuf)) {
93					vendor = ouibuf;
94				}
95			} else if (ret != 0) {
96				vendor = NULL;
97			}
98
99			if (nvlist_lookup_string(nvl, LIBSFF_KEY_PART,
100			    &part) != 0) {
101				part = NULL;
102			}
103
104			if (nvlist_lookup_string(nvl, LIBSFF_KEY_REVISION,
105			    &rev) != 0) {
106				rev = NULL;
107			}
108
109			if (nvlist_lookup_string(nvl, LIBSFF_KEY_SERIAL,
110			    &serial) != 0) {
111				serial = NULL;
112			}
113		}
114	}
115
116	if (transceiver_range_create(mod, port, 0, 0) != 0) {
117		nvlist_free(nvl);
118		return (-1);
119	}
120
121	if (transceiver_create_sff(mod, port, 0, dgt.dgt_usable, vendor, part,
122	    rev, serial, NULL) != 0) {
123		nvlist_free(nvl);
124		return (-1);
125	}
126
127	nvlist_free(nvl);
128	return (0);
129}
130
131/* ARGSUSED */
132static int
133nic_enum(topo_mod_t *mod, tnode_t *pnode, const char *name,
134    topo_instance_t min, topo_instance_t max, void *modarg, void *data)
135{
136	di_node_t din = data;
137	datalink_id_t linkid;
138	dladm_handle_t handle;
139	dld_ioc_gettran_t dgt;
140	uint_t ntrans, i;
141	char dname[MAXNAMELEN];
142
143	if (strcmp(name, NIC) != 0) {
144		topo_mod_dprintf(mod, "nic_enum: asked to enumerate unknown "
145		    "component: %s\n", name);
146		return (-1);
147	}
148
149	if (din == NULL) {
150		topo_mod_dprintf(mod, "nic_enum: missing data argument\n");
151		return (-1);
152	}
153
154	if ((handle = topo_mod_getspecific(mod)) == NULL) {
155		topo_mod_dprintf(mod, "nic_enum: failed to get nic module "
156		    "specific data\n");
157		return (-1);
158	}
159
160	if (snprintf(dname, sizeof (dname), "%s%d", di_driver_name(din),
161	    di_instance(din)) >= sizeof (dname)) {
162		topo_mod_dprintf(mod, "nic_enum: device name overflowed "
163		    "internal buffer\n");
164		return (-1);
165	}
166
167	if (dladm_dev2linkid(handle, dname, &linkid) != DLADM_STATUS_OK)
168		return (-1);
169
170	bzero(&dgt, sizeof (dgt));
171	dgt.dgt_linkid = linkid;
172	dgt.dgt_tran_id = DLDIOC_GETTRAN_GETNTRAN;
173
174	if (ioctl(dladm_dld_fd(handle), DLDIOC_GETTRAN, &dgt) != 0) {
175		if (errno == ENOTSUP)
176			return (0);
177		return (-1);
178	}
179
180	ntrans = dgt.dgt_tran_id;
181	if (ntrans == 0)
182		return (0);
183
184	if (port_range_create(mod, pnode, 0, ntrans - 1) != 0)
185		return (-1);
186
187	for (i = 0; i < ntrans; i++) {
188		if (nic_create_transceiver(mod, pnode, handle, linkid, i) != 0)
189			return (-1);
190	}
191
192	return (0);
193}
194
195static const topo_modops_t nic_ops = {
196	nic_enum, NULL
197};
198
199static topo_modinfo_t nic_mod = {
200	NIC, FM_FMRI_SCHEME_HC, NIC_VERSION, &nic_ops
201};
202
203int
204_topo_init(topo_mod_t *mod, topo_version_t version)
205{
206	dladm_handle_t handle;
207
208	if (getenv("TOPONICDEBUG") != NULL)
209		topo_mod_setdebug(mod);
210
211	topo_mod_dprintf(mod, "_mod_init: "
212	    "initializing %s enumerator\n", NIC);
213
214	if (version != NIC_VERSION) {
215		return (-1);
216	}
217
218	if (dladm_open(&handle) != 0)
219		return (-1);
220
221	if (topo_mod_register(mod, &nic_mod, TOPO_VERSION) != 0) {
222		dladm_close(handle);
223		return (-1);
224	}
225
226	topo_mod_setspecific(mod, handle);
227
228	return (0);
229}
230
231void
232_topo_fini(topo_mod_t *mod)
233{
234	dladm_handle_t handle;
235
236	if ((handle = topo_mod_getspecific(mod)) == NULL)
237		return;
238
239	dladm_close(handle);
240	topo_mod_setspecific(mod, NULL);
241}
242