xref: /illumos-gate/usr/src/uts/common/io/bge/bge_ndd.c (revision ddf7fe95b8ad67aa16deb427a0b78f4dd4ff22b1)
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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #pragma ident	"%Z%%M%	%I%	%E% SMI"
28 
29 #include "bge_impl.h"
30 
31 
32 #define	BGE_DBG		BGE_DBG_NDD	/* debug flag for this code	*/
33 /*
34  * Property names
35  */
36 static char transfer_speed_propname[] = "transfer-speed";
37 static char speed_propname[] = "speed";
38 static char duplex_propname[] = "full-duplex";
39 static char supported_net[] = "supported-network-types";
40 
41 /*
42  * Notes:
43  *	The first character of the <name> field encodes the read/write
44  *	status of the parameter:
45  *		'=' => read-only,
46  *		'-' => read-only and forced to 0 on serdes
47  *		'+' => read/write,
48  *		'?' => read/write on copper, read-only and 0 on serdes
49  *		'!' => invisible!
50  *
51  *	For writable parameters, we check for a driver property with the
52  *	same name; if found, and its value is in range, we initialise
53  *	the parameter from the property, overriding the default in the
54  *	table below.
55  *
56  *	A NULL in the <name> field terminates the array.
57  *
58  *	The <info> field is used here to provide the index of the
59  *	parameter to be initialised; thus it doesn't matter whether
60  *	this table is kept ordered or not.
61  *
62  *	The <info> field in the per-instance copy, on the other hand,
63  *	is used to count assignments so that we can tell when a magic
64  *	parameter has been set via ndd (see bge_param_set()).
65  */
66 static const nd_param_t nd_template[] = {
67 /*	info		min	max	init	r/w+name		*/
68 
69 /* Our hardware capabilities */
70 { PARAM_AUTONEG_CAP,	    0,	  1,	1,	"=autoneg_cap"		},
71 { PARAM_PAUSE_CAP,	    0,	  1,	1,	"=pause_cap"		},
72 { PARAM_ASYM_PAUSE_CAP,	    0,	  1,	1,	"=asym_pause_cap"	},
73 { PARAM_1000FDX_CAP,	    0,	  1,	1,	"=1000fdx_cap"		},
74 { PARAM_1000HDX_CAP,	    0,	  1,	1,	"=1000hdx_cap"		},
75 { PARAM_100T4_CAP,	    0,	  1,	0,	"=100T4_cap"		},
76 { PARAM_100FDX_CAP,	    0,	  1,	1,	"-100fdx_cap"		},
77 { PARAM_100HDX_CAP,	    0,	  1,	1,	"-100hdx_cap"		},
78 { PARAM_10FDX_CAP,	    0,	  1,	1,	"-10fdx_cap"		},
79 { PARAM_10HDX_CAP,	    0,	  1,	1,	"-10hdx_cap"		},
80 
81 /* Our advertised capabilities */
82 { PARAM_ADV_AUTONEG_CAP,    0,	  1,	1,	"+adv_autoneg_cap"	},
83 { PARAM_ADV_PAUSE_CAP,	    0,	  1,	1,	"+adv_pause_cap"	},
84 { PARAM_ADV_ASYM_PAUSE_CAP, 0,	  1,	1,	"+adv_asym_pause_cap"	},
85 { PARAM_ADV_1000FDX_CAP,    0,	  1,	1,	"+adv_1000fdx_cap"	},
86 { PARAM_ADV_1000HDX_CAP,    0,	  1,	1,	"+adv_1000hdx_cap"	},
87 { PARAM_ADV_100T4_CAP,	    0,	  1,	0,	"=adv_100T4_cap"	},
88 { PARAM_ADV_100FDX_CAP,	    0,	  1,	1,	"?adv_100fdx_cap"	},
89 { PARAM_ADV_100HDX_CAP,	    0,	  1,	1,	"?adv_100hdx_cap"	},
90 { PARAM_ADV_10FDX_CAP,	    0,	  1,	1,	"?adv_10fdx_cap"	},
91 { PARAM_ADV_10HDX_CAP,	    0,	  1,	1,	"?adv_10hdx_cap"	},
92 
93 /* Partner's advertised capabilities */
94 { PARAM_LP_AUTONEG_CAP,	    0,	  1,	0,	"-lp_autoneg_cap"	},
95 { PARAM_LP_PAUSE_CAP,	    0,	  1,	0,	"-lp_pause_cap"		},
96 { PARAM_LP_ASYM_PAUSE_CAP,  0,	  1,	0,	"-lp_asym_pause_cap"	},
97 { PARAM_LP_1000FDX_CAP,	    0,	  1,	0,	"-lp_1000fdx_cap"	},
98 { PARAM_LP_1000HDX_CAP,	    0,	  1,	0,	"-lp_1000hdx_cap"	},
99 { PARAM_LP_100T4_CAP,	    0,	  1,	0,	"-lp_100T4_cap"		},
100 { PARAM_LP_100FDX_CAP,	    0,	  1,	0,	"-lp_100fdx_cap"	},
101 { PARAM_LP_100HDX_CAP,	    0,	  1,	0,	"-lp_100hdx_cap"	},
102 { PARAM_LP_10FDX_CAP,	    0,	  1,	0,	"-lp_10fdx_cap"		},
103 { PARAM_LP_10HDX_CAP,	    0,	  1,	0,	"-lp_10hdx_cap"		},
104 
105 /* Current operating modes */
106 { PARAM_LINK_STATUS,	    0,	  1,	0,	"-link_status"		},
107 { PARAM_LINK_SPEED,	    0,    1000,	0,	"-link_speed"		},
108 { PARAM_LINK_DUPLEX,	    0,	  2,	0,	"-link_duplex"		},
109 
110 { PARAM_LINK_AUTONEG,	    0,	  1,	0,	"-link_autoneg"		},
111 { PARAM_LINK_RX_PAUSE,	    0,	  1,	0,	"-link_rx_pause"	},
112 { PARAM_LINK_TX_PAUSE,	    0,	  1,	0,	"-link_tx_pause"	},
113 
114 /* Terminator */
115 { PARAM_COUNT,		    0,	  0,	0,	NULL			}
116 };
117 
118 
119 /*  ============== NDD Support Functions ===============  */
120 
121 /*
122  * Extracts the value from the bge parameter array and prints
123  * the parameter value. cp points to the required parameter.
124  */
125 static int
126 bge_param_get(queue_t *q, mblk_t *mp, caddr_t cp, cred_t *credp)
127 {
128 	nd_param_t *ndp;
129 
130 	_NOTE(ARGUNUSED(q, credp))
131 
132 	ndp = (nd_param_t *)cp;
133 	(void) mi_mpprintf(mp, "%d", ndp->ndp_val);
134 
135 	return (0);
136 }
137 
138 /*
139  * synchronize the  adv* and en* parameters.
140  *
141  * See comments in <sys/dld.h> for details of the *_en_*
142  * parameters.  The usage of ndd for setting adv parameters will
143  * synchronize all the en parameters with the bge parameters,
144  * implicitly disabling any settings made via dladm.
145  */
146 static void
147 bge_param_sync(bge_t *bgep)
148 {
149 	bgep->param_en_pause = bgep->param_adv_pause;
150 	bgep->param_en_asym_pause = bgep->param_adv_asym_pause;
151 	bgep->param_en_1000fdx = bgep->param_adv_1000fdx;
152 	bgep->param_en_1000hdx = bgep->param_adv_1000hdx;
153 	bgep->param_en_100fdx = bgep->param_adv_100fdx;
154 	bgep->param_en_100hdx = bgep->param_adv_100hdx;
155 	bgep->param_en_10fdx = bgep->param_adv_10fdx;
156 	bgep->param_en_10hdx = bgep->param_adv_10hdx;
157 }
158 
159 /*
160  * Validates the request to set a BGE parameter to a specific value.
161  * If the request is OK, the parameter is set.  Also the <info> field
162  * is incremented to show that the parameter was touched, even though
163  * it may have been set to the same value it already had.
164  */
165 static int
166 bge_param_set(queue_t *q, mblk_t *mp, char *value, caddr_t cp, cred_t *credp)
167 {
168 	nd_param_t *ndp;
169 	long new_value;
170 	char *end;
171 
172 	_NOTE(ARGUNUSED(q, mp, credp))
173 
174 	ndp = (nd_param_t *)cp;
175 	new_value = mi_strtol(value, &end, 10);
176 	if (end == value)
177 		return (EINVAL);
178 	if (new_value < ndp->ndp_min || new_value > ndp->ndp_max)
179 		return (EINVAL);
180 
181 	ndp->ndp_val = new_value;
182 	ndp->ndp_info += 1;
183 	return (0);
184 }
185 
186 /*
187  * Initialise the per-instance parameter array from the global prototype,
188  * and register each element with the named dispatch handler using nd_load()
189  */
190 static int
191 bge_param_register(bge_t *bgep)
192 {
193 	const nd_param_t *tmplp;
194 	dev_info_t *dip;
195 	nd_param_t *ndp;
196 	caddr_t *nddpp;
197 	pfi_t setfn;
198 	char *nm;
199 	int pval;
200 
201 	BGE_TRACE(("bge_param_register($%p)", (void *)bgep));
202 
203 	dip = bgep->devinfo;
204 	nddpp = &bgep->nd_data_p;
205 	ASSERT(*nddpp == NULL);
206 
207 	for (tmplp = nd_template; tmplp->ndp_name != NULL; ++tmplp) {
208 		/*
209 		 * Copy the template from nd_template[] into the
210 		 * proper slot in the per-instance parameters,
211 		 * then register the parameter with nd_load()
212 		 */
213 		ndp = &bgep->nd_params[tmplp->ndp_info];
214 		*ndp = *tmplp;
215 		nm = &ndp->ndp_name[0];
216 		setfn = bge_param_set;
217 		if (bgep->chipid.flags & CHIP_FLAG_SERDES)
218 			switch (*nm) {
219 			default:
220 				break;
221 
222 			case '-':
223 			case '?':
224 				ndp->ndp_val = 0;
225 				setfn = NULL;
226 				break;
227 			}
228 
229 		switch (*nm) {
230 		default:
231 		case '!':
232 			continue;
233 
234 		case '+':
235 		case '?':
236 			break;
237 
238 		case '=':
239 		case '-':
240 			setfn = NULL;
241 			break;
242 		}
243 
244 		if (!nd_load(nddpp, ++nm, bge_param_get, setfn, (caddr_t)ndp))
245 			goto nd_fail;
246 
247 		/*
248 		 * If the parameter is writable, and there's a property
249 		 * with the same name, and its value is in range, we use
250 		 * it to initialise the parameter.  If it exists but is
251 		 * out of range, it's ignored.
252 		 */
253 		if (setfn && BGE_PROP_EXISTS(dip, nm)) {
254 			pval = BGE_PROP_GET_INT(dip, nm);
255 			if (pval >= ndp->ndp_min && pval <= ndp->ndp_max)
256 				ndp->ndp_val = pval;
257 		}
258 	}
259 
260 	BGE_DEBUG(("bge_param_register: OK"));
261 	return (DDI_SUCCESS);
262 
263 nd_fail:
264 	BGE_DEBUG(("bge_param_register: FAILED at index %d [info %d]",
265 	    tmplp-nd_template, tmplp->ndp_info));
266 	nd_free(nddpp);
267 	return (DDI_FAILURE);
268 }
269 
270 int
271 bge_nd_init(bge_t *bgep)
272 {
273 	dev_info_t *dip;
274 	int duplex;
275 	int speed;
276 	char **options, *prop;
277 	uint_t  noptions;
278 
279 	BGE_TRACE(("bge_nd_init($%p)", (void *)bgep));
280 
281 	/*
282 	 * Register all the per-instance properties, initialising
283 	 * them from the table above or from driver properties set
284 	 * in the .conf file
285 	 */
286 	if (bge_param_register(bgep) != DDI_SUCCESS)
287 		return (-1);
288 
289 	/*
290 	 * check the OBP property "supported-network-types"
291 	 */
292 	if (BGE_PROP_EXISTS(bgep->devinfo, supported_net)) {
293 		if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, bgep->devinfo,
294 		    DDI_PROP_DONTPASS, supported_net,
295 		    &options, &noptions) == DDI_PROP_SUCCESS) {
296 
297 			bgep->param_adv_autoneg = 0;
298 			bgep->param_adv_1000fdx = 0;
299 			bgep->param_adv_1000hdx = 0;
300 			bgep->param_adv_100fdx = 0;
301 			bgep->param_adv_100hdx = 0;
302 			bgep->param_adv_10fdx = 0;
303 			bgep->param_adv_10hdx = 0;
304 
305 			for (; noptions > 0; noptions--) {
306 				prop = options[noptions-1];
307 				if (strstr(prop, "ethernet") == NULL)
308 					continue;
309 				if (strstr(prop, "1000")) {
310 					if (strstr(prop, "auto")) {
311 						bgep->param_adv_1000fdx = 1;
312 						bgep->param_adv_1000hdx = 1;
313 						bgep->param_adv_autoneg = 1;
314 					} else if (strstr(prop, "full"))
315 						bgep->param_adv_1000fdx = 1;
316 					else if (strstr(prop, "half"))
317 						bgep->param_adv_1000hdx = 1;
318 				} else if (strstr(prop, "100")) {
319 					if (strstr(prop, "auto")) {
320 						bgep->param_adv_100fdx = 1;
321 						bgep->param_adv_100hdx = 1;
322 						bgep->param_adv_autoneg = 1;
323 					} else if (strstr(prop, "full"))
324 						bgep->param_adv_100fdx = 1;
325 					else if (strstr(prop, "half"))
326 						bgep->param_adv_100hdx = 1;
327 				} else if (strstr(prop, "10")) {
328 					if (strstr(prop, "auto")) {
329 						bgep->param_adv_10fdx = 1;
330 						bgep->param_adv_10hdx = 1;
331 						bgep->param_adv_autoneg = 1;
332 					} else if (strstr(prop, "full"))
333 						bgep->param_adv_10fdx = 1;
334 					else if (strstr(prop, "half"))
335 						bgep->param_adv_10hdx = 1;
336 				}
337 			}
338 
339 			ddi_prop_free(options);
340 		}
341 	}
342 
343 	/*
344 	 * The link speed may be forced to 10, 100 or 1000 Mbps using
345 	 * the property "transfer-speed". This may be done in OBP by
346 	 * using the command "apply transfer-speed=<speed> <device>".
347 	 * The speed may be 10, 100 or 1000 - any other value will be
348 	 * ignored.  Note that this does *enables* autonegotiation, but
349 	 * restricts it to the speed specified by the property.
350 	 */
351 	dip = bgep->devinfo;
352 	if (BGE_PROP_EXISTS(dip, transfer_speed_propname)) {
353 
354 		speed = BGE_PROP_GET_INT(dip, transfer_speed_propname);
355 		bge_log(bgep, "%s property is %d",
356 		    transfer_speed_propname, speed);
357 
358 		switch (speed) {
359 		case 1000:
360 			bgep->param_adv_autoneg = 1;
361 			bgep->param_adv_1000fdx = 1;
362 			bgep->param_adv_1000hdx = 1;
363 			bgep->param_adv_100fdx = 0;
364 			bgep->param_adv_100hdx = 0;
365 			bgep->param_adv_10fdx = 0;
366 			bgep->param_adv_10hdx = 0;
367 			break;
368 
369 		case 100:
370 			bgep->param_adv_autoneg = 1;
371 			bgep->param_adv_1000fdx = 0;
372 			bgep->param_adv_1000hdx = 0;
373 			bgep->param_adv_100fdx = 1;
374 			bgep->param_adv_100hdx = 1;
375 			bgep->param_adv_10fdx = 0;
376 			bgep->param_adv_10hdx = 0;
377 			break;
378 
379 		case 10:
380 			bgep->param_adv_autoneg = 1;
381 			bgep->param_adv_1000fdx = 0;
382 			bgep->param_adv_1000hdx = 0;
383 			bgep->param_adv_100fdx = 0;
384 			bgep->param_adv_100hdx = 0;
385 			bgep->param_adv_10fdx = 1;
386 			bgep->param_adv_10hdx = 1;
387 			break;
388 
389 		default:
390 			break;
391 		}
392 	}
393 
394 	/*
395 	 * Also check the "speed" and "full-duplex" properties.  Setting
396 	 * these properties will override all other settings and *disable*
397 	 * autonegotiation, so both should be specified if either one is.
398 	 * Otherwise, the unspecified parameter will be set to a default
399 	 * value (1000Mb/s, full-duplex).
400 	 */
401 	if (BGE_PROP_EXISTS(dip, speed_propname) ||
402 	    BGE_PROP_EXISTS(dip, duplex_propname)) {
403 
404 		bgep->param_adv_autoneg = 0;
405 		bgep->param_adv_1000fdx = 1;
406 		bgep->param_adv_1000hdx = 1;
407 		bgep->param_adv_100fdx = 1;
408 		bgep->param_adv_100hdx = 1;
409 		bgep->param_adv_10fdx = 1;
410 		bgep->param_adv_10hdx = 1;
411 
412 		speed = BGE_PROP_GET_INT(dip, speed_propname);
413 		duplex = BGE_PROP_GET_INT(dip, duplex_propname);
414 		bge_log(bgep, "%s property is %d",
415 		    speed_propname, speed);
416 		bge_log(bgep, "%s property is %d",
417 		    duplex_propname, duplex);
418 
419 		switch (speed) {
420 		case 1000:
421 		default:
422 			bgep->param_adv_100fdx = 0;
423 			bgep->param_adv_100hdx = 0;
424 			bgep->param_adv_10fdx = 0;
425 			bgep->param_adv_10hdx = 0;
426 			break;
427 
428 		case 100:
429 			bgep->param_adv_1000fdx = 0;
430 			bgep->param_adv_1000hdx = 0;
431 			bgep->param_adv_10fdx = 0;
432 			bgep->param_adv_10hdx = 0;
433 			break;
434 
435 		case 10:
436 			bgep->param_adv_1000fdx = 0;
437 			bgep->param_adv_1000hdx = 0;
438 			bgep->param_adv_100fdx = 0;
439 			bgep->param_adv_100hdx = 0;
440 			break;
441 		}
442 
443 		switch (duplex) {
444 		default:
445 		case 1:
446 			bgep->param_adv_1000hdx = 0;
447 			bgep->param_adv_100hdx = 0;
448 			bgep->param_adv_10hdx = 0;
449 			break;
450 
451 		case 0:
452 			bgep->param_adv_1000fdx = 0;
453 			bgep->param_adv_100fdx = 0;
454 			bgep->param_adv_10fdx = 0;
455 			break;
456 		}
457 	}
458 
459 	BGE_DEBUG(("bge_nd_init: autoneg %d"
460 	    "pause %d asym_pause %d "
461 	    "1000fdx %d 1000hdx %d "
462 	    "100fdx %d 100hdx %d "
463 	    "10fdx %d 10hdx %d ",
464 	    bgep->param_adv_autoneg,
465 	    bgep->param_adv_pause, bgep->param_adv_asym_pause,
466 	    bgep->param_adv_1000fdx, bgep->param_adv_1000hdx,
467 	    bgep->param_adv_100fdx, bgep->param_adv_100hdx,
468 	    bgep->param_adv_10fdx, bgep->param_adv_10hdx));
469 
470 	bge_param_sync(bgep);
471 
472 	return (0);
473 }
474 
475 enum ioc_reply
476 bge_nd_ioctl(bge_t *bgep, queue_t *wq, mblk_t *mp, struct iocblk *iocp)
477 {
478 	nd_param_t *ndp;
479 	boolean_t ok;
480 	int info;
481 	int cmd;
482 
483 	BGE_TRACE(("bge_nd_ioctl($%p, $%p, $%p, $%p)",
484 	    (void *)bgep, (void *)wq, (void *)mp, (void *)iocp));
485 
486 	ASSERT(mutex_owned(bgep->genlock));
487 
488 	cmd = iocp->ioc_cmd;
489 	switch (cmd) {
490 	default:
491 		/* NOTREACHED */
492 		bge_error(bgep, "bge_nd_ioctl: invalid cmd 0x%x", cmd);
493 		return (IOC_INVAL);
494 
495 	case ND_GET:
496 		/*
497 		 * If nd_getset() returns B_FALSE, the command was
498 		 * not valid (e.g. unknown name), so we just tell the
499 		 * top-level ioctl code to send a NAK (with code EINVAL).
500 		 *
501 		 * Otherwise, nd_getset() will have built the reply to
502 		 * be sent (but not actually sent it), so we tell the
503 		 * caller to send the prepared reply.
504 		 */
505 		ok = nd_getset(wq, bgep->nd_data_p, mp);
506 		BGE_DEBUG(("bge_nd_ioctl: get %s", ok ? "OK" : "FAIL"));
507 		return (ok ? IOC_REPLY : IOC_INVAL);
508 
509 	case ND_SET:
510 		/*
511 		 * All adv_* parameters are locked (read-only) while
512 		 * the device is in any sort of loopback mode ...
513 		 */
514 		if (bgep->param_loop_mode != BGE_LOOP_NONE) {
515 			iocp->ioc_error = EBUSY;
516 			return (IOC_INVAL);
517 		}
518 
519 		/*
520 		 * Before calling nd_getset(), we save the <info> field
521 		 * of the 'autonegotiation' parameter so that we can tell
522 		 * whether it was assigned (even if its value doesn't
523 		 * actually change).
524 		 */
525 		ndp = &bgep->nd_params[PARAM_ADV_AUTONEG_CAP];
526 		info = ndp->ndp_info;
527 		ok = nd_getset(wq, bgep->nd_data_p, mp);
528 
529 		bge_param_sync(bgep);
530 
531 		/*
532 		 * If nd_getset() returns B_FALSE, the command was
533 		 * not valid (e.g. unknown name), so we just tell
534 		 * the top-level ioctl code to send a NAK (with code
535 		 * EINVAL by default).
536 		 *
537 		 * Otherwise, nd_getset() will have built the reply to
538 		 * be sent - but that doesn't imply success!  In some
539 		 * cases, the reply it's built will have a non-zero
540 		 * error code in it (e.g. EPERM if not superuser).
541 		 * So, we also drop out in that case ...
542 		 */
543 		BGE_DEBUG(("bge_nd_ioctl: set %s err %d autoneg %d info %d/%d",
544 		    ok ? "OK" : "FAIL", iocp->ioc_error,
545 		    ndp->ndp_val, info, ndp->ndp_info));
546 		if (!ok)
547 			return (IOC_INVAL);
548 		if (iocp->ioc_error)
549 			return (IOC_REPLY);
550 
551 		return (IOC_RESTART_REPLY);
552 	}
553 }
554 
555 /* Free the Named Dispatch Table by calling nd_free */
556 void
557 bge_nd_cleanup(bge_t *bgep)
558 {
559 	BGE_TRACE(("bge_nd_cleanup($%p)", (void *)bgep));
560 
561 	nd_free(&bgep->nd_data_p);
562 }
563