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  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  *
25  * Copyright 2015 Garrett D'Amore <garrett@damore.org>
26  */
27 
28 #include <stdlib.h>
29 #include <string.h>
30 #include <strings.h>
31 #include <sys/types.h>
32 #include <libdladm_impl.h>
33 #include <libdllink.h>
34 #include <libdlstat.h>
35 #include <libdlether.h>
36 
37 /*
38  * Ethernet administration library.
39  */
40 
41 /*
42  * kstat names for extracting attributes.
43  */
44 typedef struct ether_spdx_s {
45 	dladm_ether_spdx_t eth_spdx;
46 	char *eth_spdx_stat_name;
47 } ether_spdx_t;
48 
49 static ether_spdx_t cap_spdx[] = {
50 	{{400000, LINK_DUPLEX_FULL}, "cap_400gfdx"},
51 	{{200000, LINK_DUPLEX_FULL}, "cap_200gfdx"},
52 	{{100000, LINK_DUPLEX_FULL}, "cap_100gfdx"},
53 	{{50000, LINK_DUPLEX_FULL}, "cap_50gfdx"},
54 	{{40000, LINK_DUPLEX_FULL}, "cap_40gfdx"},
55 	{{25000, LINK_DUPLEX_FULL}, "cap_25gfdx"},
56 	{{10000, LINK_DUPLEX_FULL}, "cap_10gfdx"},
57 	{{5000, LINK_DUPLEX_FULL}, "cap_5000fdx"},
58 	{{2500, LINK_DUPLEX_FULL}, "cap_2500fdx"},
59 	{{1000, LINK_DUPLEX_FULL}, "cap_1000fdx"},
60 	{{1000, LINK_DUPLEX_HALF}, "cap_1000hdx"},
61 	{{100, LINK_DUPLEX_FULL}, "cap_100fdx"},
62 	{{100, LINK_DUPLEX_HALF}, "cap_100hdx"},
63 	{{10, LINK_DUPLEX_FULL}, "cap_10fdx"},
64 	{{10, LINK_DUPLEX_HALF}, "cap_10hdx"},
65 	{{0, LINK_DUPLEX_UNKNOWN}, NULL}
66 };
67 
68 static ether_spdx_t adv_cap_spdx[] = {
69 	{{400000, LINK_DUPLEX_FULL}, "adv_cap_400gfdx"},
70 	{{200000, LINK_DUPLEX_FULL}, "adv_cap_200gfdx"},
71 	{{100000, LINK_DUPLEX_FULL}, "adv_cap_100gfdx"},
72 	{{50000, LINK_DUPLEX_FULL}, "adv_cap_50gfdx"},
73 	{{40000, LINK_DUPLEX_FULL}, "adv_cap_40gfdx"},
74 	{{25000, LINK_DUPLEX_FULL}, "adv_cap_25gfdx"},
75 	{{10000, LINK_DUPLEX_FULL}, "adv_cap_10gfdx"},
76 	{{5000, LINK_DUPLEX_FULL}, "adv_cap_5000fdx"},
77 	{{2500, LINK_DUPLEX_FULL}, "adv_cap_2500fdx"},
78 	{{1000, LINK_DUPLEX_FULL}, "adv_cap_1000fdx"},
79 	{{1000, LINK_DUPLEX_HALF}, "adv_cap_1000hdx"},
80 	{{100, LINK_DUPLEX_FULL}, "adv_cap_100fdx"},
81 	{{100, LINK_DUPLEX_HALF}, "adv_cap_100hdx"},
82 	{{10, LINK_DUPLEX_FULL}, "adv_cap_10fdx"},
83 	{{10, LINK_DUPLEX_HALF}, "adv_cap_10hdx"},
84 	{{0, LINK_DUPLEX_UNKNOWN}, NULL}
85 };
86 
87 static ether_spdx_t lp_cap_spdx[] = {
88 	{{400000, LINK_DUPLEX_FULL}, "lp_cap_400gfdx"},
89 	{{200000, LINK_DUPLEX_FULL}, "lp_cap_200gfdx"},
90 	{{100000, LINK_DUPLEX_FULL}, "lp_cap_100gfdx"},
91 	{{50000, LINK_DUPLEX_FULL}, "lp_cap_50gfdx"},
92 	{{40000, LINK_DUPLEX_FULL}, "lp_cap_40gfdx"},
93 	{{25000, LINK_DUPLEX_FULL}, "lp_cap_25gfdx"},
94 	{{10000, LINK_DUPLEX_FULL}, "lp_cap_10gfdx"},
95 	{{5000, LINK_DUPLEX_FULL}, "lp_cap_5000fdx"},
96 	{{2500, LINK_DUPLEX_FULL}, "lp_cap_2500fdx"},
97 	{{1000, LINK_DUPLEX_FULL}, "lp_cap_1000fdx"},
98 	{{1000, LINK_DUPLEX_HALF}, "lp_cap_1000hdx"},
99 	{{100, LINK_DUPLEX_FULL}, "lp_cap_100fdx"},
100 	{{100, LINK_DUPLEX_HALF}, "lp_cap_100hdx"},
101 	{{10, LINK_DUPLEX_FULL}, "lp_cap_10fdx"},
102 	{{10, LINK_DUPLEX_HALF}, "lp_cap_10hdx"},
103 	{{0, LINK_DUPLEX_UNKNOWN}, NULL}
104 };
105 
106 typedef struct attr_kstat_s {
107 	char *autoneg_stat;
108 	char *pause_stat;
109 	char *asmpause_stat;
110 	char *fault_stat;
111 	ether_spdx_t *spdx_stat;
112 } attr_kstat_t;
113 
114 static attr_kstat_t attrstat[] =  {
115 	{"link_autoneg",	/* current */
116 	    "link_pause",	"link_asmpause",	NULL,
117 	    NULL},
118 
119 	{"cap_autoneg",		/* capable */
120 	    "cap_pause",	"cap_asmpause",		"cap_rem_fault",
121 	    cap_spdx},
122 
123 	{"adv_cap_autoneg",	/* advertised */
124 	    "adv_cap_pause",	"adv_cap_asmpause",	"adv_rem_fault",
125 	    adv_cap_spdx},
126 
127 	{"lp_cap_autoneg",	/* peer advertised */
128 	    "lp_cap_pause",	"lp_cap_asmpause",	"lp_rem_fault",
129 	    lp_cap_spdx}
130 };
131 
132 /*
133  * Get the speed-duplex stats specified in the ether_spdx_t table passed in
134  * by querying the appropriate kstat for each entry in the table.
135  */
136 static dladm_status_t
i_dladm_get_spdx(dladm_handle_t handle,datalink_id_t linkid,dladm_ether_attr_t * eattr,ether_spdx_t * spdx_stat)137 i_dladm_get_spdx(dladm_handle_t handle, datalink_id_t linkid,
138     dladm_ether_attr_t *eattr, ether_spdx_t *spdx_stat)
139 {
140 	int		i, nspdx = 0;
141 	uint32_t	speed;
142 	dladm_status_t	status;
143 	void		*ptr;
144 
145 	eattr->le_spdx = NULL;
146 	for (i = 0; spdx_stat[i].eth_spdx_stat_name != NULL; i++) {
147 		if ((status = dladm_get_single_mac_stat(handle, linkid,
148 		    spdx_stat[i].eth_spdx_stat_name,
149 		    KSTAT_DATA_UINT32, &speed)) != DLADM_STATUS_OK) {
150 
151 			if (status == DLADM_STATUS_NOTFOUND) {
152 				/*
153 				 * Missing statistic.
154 				 * Skip this one and try the rest.
155 				 */
156 				continue;
157 			} else {
158 				free(eattr->le_spdx);
159 				eattr->le_num_spdx = 0;
160 				return (status);
161 			}
162 		}
163 		if (speed == 0)
164 			continue;
165 		nspdx++;
166 		ptr = realloc(eattr->le_spdx,
167 		    nspdx * sizeof (dladm_ether_spdx_t));
168 		if (ptr != NULL) {
169 			eattr->le_spdx = ptr;
170 		} else {
171 			free(eattr->le_spdx);
172 			eattr->le_num_spdx = 0;
173 			return (DLADM_STATUS_NOMEM);
174 		}
175 		eattr->le_spdx[nspdx - 1] = spdx_stat[i].eth_spdx;
176 	}
177 	eattr->le_num_spdx = nspdx;
178 	return (DLADM_STATUS_OK);
179 }
180 
181 /*
182  * Returns "yes" or "no" based on the autonegotion capabilities
183  * for the parameter type indicated by ptype. The permissible
184  * values for ptype are CURRENT, CAPABLE, ADV, PEERADV.
185  */
186 char *
dladm_ether_autoneg2str(char * buf,size_t buflen,dladm_ether_info_t * eattr,int ptype)187 dladm_ether_autoneg2str(char *buf, size_t buflen, dladm_ether_info_t *eattr,
188     int ptype)
189 {
190 	boolean_t autoneg = eattr->lei_attr[ptype].le_autoneg;
191 
192 	(void) strlcpy(buf, (autoneg ? "yes" : "no"), buflen);
193 	return (buf);
194 }
195 
196 /*
197  * Returns {"bi", "tx", "none"} based on the flow-control capabilities
198  * for the parameter type indicated by ptype. The permissible
199  * values for ptype are CURRENT, CAPABLE, ADV, PEERADV.
200  */
201 char *
dladm_ether_pause2str(char * buf,size_t buflen,dladm_ether_info_t * eattr,int ptype)202 dladm_ether_pause2str(char *buf, size_t buflen, dladm_ether_info_t *eattr,
203     int ptype)
204 {
205 	boolean_t pause = eattr->lei_attr[ptype].le_pause;
206 	boolean_t asmpause = eattr->lei_attr[ptype].le_asmpause;
207 
208 	if (pause)
209 		(void) strlcpy(buf, "bi", buflen);
210 	else if (asmpause)
211 		(void) strlcpy(buf, "tx", buflen);
212 	else
213 		(void) strlcpy(buf, "none", buflen);
214 	return (buf);
215 }
216 
217 /*
218  * For a given param type, parse the list of speed-duplex pairs in
219  * the dladm_ether_info_t and return a  comma-separated string formatted
220  * as <speed><speed-unit-char>-<duplex-chars> where <speed> is the value of
221  * speed, in units specifid by the <speed-unit-char> which is one
222  * of 'M' (Mbits/sec) or 'G' (Gigabits/sec).  The permissible values of
223  * <duplex-chars> are 'u' (indicating duplex is "unknown") or one/both of
224  * 'f', 'h' (indicating full-duplex and half-duplex respectively)
225  */
226 extern char *
dladm_ether_spdx2str(char * buf,size_t buflen,dladm_ether_info_t * eattr,int ptype)227 dladm_ether_spdx2str(char *buf, size_t buflen, dladm_ether_info_t *eattr,
228     int ptype)
229 {
230 	uint_t		i, j;
231 	boolean_t	is_full, is_half;
232 	int		speed;
233 	char		speed_unit;
234 	char		tmpbuf[DLADM_STRSIZE];
235 	dladm_ether_spdx_t *spdx;
236 	uint32_t	nspdx;
237 
238 	spdx = eattr->lei_attr[ptype].le_spdx;
239 	nspdx = eattr->lei_attr[ptype].le_num_spdx;
240 	for (i = 0; i < nspdx; i++) {
241 
242 		speed = spdx[i].lesd_speed;
243 
244 		/*
245 		 * if we have already covered this speed for
246 		 * the <other>-duplex case before this, skip it
247 		 */
248 		for (j = 0; j < i; j++) {
249 			if (speed == spdx[j].lesd_speed)
250 				break;
251 		}
252 		if (j < i)
253 			continue;
254 
255 		if ((speed % 1000) == 0) {
256 			speed = speed/1000;
257 			speed_unit = 'G';
258 		} else {
259 			speed_unit = 'M';
260 		}
261 		(void) snprintf(tmpbuf, DLADM_STRSIZE, "%d%c",
262 		    speed, speed_unit);
263 		if (i > 0)
264 			(void) strncat(buf, ",", buflen);
265 		(void) strncat(buf, tmpbuf, buflen);
266 
267 		is_full = is_half = B_FALSE;
268 		/*
269 		 * Find all the supported duplex values for this speed.
270 		 */
271 		for (j = 0; j < nspdx; j++) {
272 			if (spdx[j].lesd_speed != spdx[i].lesd_speed)
273 				continue;
274 			if (spdx[j].lesd_duplex == LINK_DUPLEX_FULL)
275 				is_full = B_TRUE;
276 			if (spdx[j].lesd_duplex == LINK_DUPLEX_HALF)
277 				is_half = B_TRUE;
278 		}
279 		if (is_full && is_half)
280 			(void) strncat(buf, "-fh", buflen);
281 		else if (is_full)
282 			(void) strncat(buf, "-f", buflen);
283 		else if (is_half)
284 			(void) strncat(buf, "-h", buflen);
285 	}
286 	return (buf);
287 }
288 
289 /*
290  * Extract Ethernet attributes of the link specified by linkid.
291  * Information for the CURRENT, CAPABLE, ADV and PEERADV parameter
292  * types is extracted into the lei_attr[] entries in the dladm_ether_info_t.
293  * On succesful return, the memory allocated in this function should be
294  * freed by calling dladm_ether_info_done().
295  */
296 extern dladm_status_t
dladm_ether_info(dladm_handle_t handle,datalink_id_t linkid,dladm_ether_info_t * eattr)297 dladm_ether_info(dladm_handle_t handle, datalink_id_t linkid,
298     dladm_ether_info_t *eattr)
299 {
300 	uint32_t	autoneg, pause, asmpause, fault;
301 	uint64_t	sp64;
302 	dladm_status_t	status;
303 	int		i;
304 	link_duplex_t	link_duplex;
305 
306 	bzero(eattr, sizeof (*eattr));
307 	status = dladm_datalink_id2info(handle, linkid, NULL, NULL, NULL,
308 	    eattr->lei_linkname, sizeof (eattr->lei_linkname));
309 	if (status != DLADM_STATUS_OK)
310 		goto bail;
311 
312 	/* get current values of speed, duplex, state of link */
313 	eattr->lei_attr[CURRENT].le_num_spdx = 1;
314 	eattr->lei_attr[CURRENT].le_spdx = malloc(sizeof (dladm_ether_spdx_t));
315 	if (eattr->lei_attr[CURRENT].le_spdx == NULL) {
316 		status = DLADM_STATUS_NOMEM;
317 		goto bail;
318 	}
319 
320 	if ((status = dladm_get_single_mac_stat(handle, linkid, "ifspeed",
321 	    KSTAT_DATA_UINT64, &sp64)) != DLADM_STATUS_OK)
322 		goto bail;
323 
324 	if ((status = dladm_get_single_mac_stat(handle, linkid, "link_duplex",
325 	    KSTAT_DATA_UINT32, &link_duplex)) != DLADM_STATUS_OK)
326 		goto bail;
327 
328 	eattr->lei_attr[CURRENT].le_spdx->lesd_speed = (int)(sp64/1000000ull);
329 	eattr->lei_attr[CURRENT].le_spdx->lesd_duplex = link_duplex;
330 
331 	status = dladm_get_state(handle, linkid, &eattr->lei_state);
332 	if (status != DLADM_STATUS_OK)
333 		goto bail;
334 
335 	/* get the auto, pause, asmpause, fault values */
336 	for (i = CURRENT; i <= PEERADV; i++)  {
337 
338 		status = dladm_get_single_mac_stat(handle, linkid,
339 		    attrstat[i].autoneg_stat, KSTAT_DATA_UINT32, &autoneg);
340 		if (status != DLADM_STATUS_OK)
341 			goto bail;
342 
343 		status = dladm_get_single_mac_stat(handle, linkid,
344 		    attrstat[i].pause_stat, KSTAT_DATA_UINT32, &pause);
345 		if (status != DLADM_STATUS_OK)
346 			goto bail;
347 
348 		status = dladm_get_single_mac_stat(handle, linkid,
349 		    attrstat[i].asmpause_stat, KSTAT_DATA_UINT32, &asmpause);
350 		if (status != DLADM_STATUS_OK)
351 			goto bail;
352 
353 		eattr->lei_attr[i].le_autoneg = (autoneg != 0);
354 		eattr->lei_attr[i].le_pause = (pause != 0);
355 		eattr->lei_attr[i].le_asmpause = (asmpause != 0);
356 
357 		if (i == CURRENT)
358 			continue;
359 		status = dladm_get_single_mac_stat(handle, linkid,
360 		    attrstat[i].fault_stat, KSTAT_DATA_UINT32, &fault);
361 		if (status != DLADM_STATUS_OK)
362 			goto bail;
363 		eattr->lei_attr[i].le_fault = (pause != 0);
364 
365 		/* get all the supported speed/duplex values */
366 		status = i_dladm_get_spdx(handle, linkid, &eattr->lei_attr[i],
367 		    attrstat[i].spdx_stat);
368 		if (status != DLADM_STATUS_OK)
369 			goto bail;
370 	}
371 	eattr->lei_attr[CURRENT].le_fault =
372 	    eattr->lei_attr[ADV].le_fault || eattr->lei_attr[PEERADV].le_fault;
373 bail:
374 	if (status != DLADM_STATUS_OK)
375 		dladm_ether_info_done(eattr);
376 	return (status);
377 }
378 
379 extern void
dladm_ether_info_done(dladm_ether_info_t * eattr)380 dladm_ether_info_done(dladm_ether_info_t *eattr)
381 {
382 	int i;
383 
384 	for (i = CURRENT; i <= PEERADV; i++) {
385 		free(eattr->lei_attr[i].le_spdx);
386 		eattr->lei_attr[i].le_spdx = NULL;
387 	}
388 }
389