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