xref: /illumos-gate/usr/src/uts/common/io/bge/bge_ndd.c (revision f808c858fa61e7769218966759510a8b1190dfcf)
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 "sys/bge_impl2.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 /* Terminator */
122 { PARAM_COUNT,		    0,	  0,	0,	NULL			}
123 };
124 
125 
126 /*  ============== NDD Support Functions ===============  */
127 
128 /*
129  * Extracts the value from the bge parameter array and prints
130  * the parameter value. cp points to the required parameter.
131  */
132 static int
133 bge_param_get(queue_t *q, mblk_t *mp, caddr_t cp, cred_t *credp)
134 {
135 	nd_param_t *ndp;
136 
137 	_NOTE(ARGUNUSED(q, credp))
138 
139 	ndp = (nd_param_t *)cp;
140 	(void) mi_mpprintf(mp, "%d", ndp->ndp_val);
141 
142 	return (0);
143 }
144 
145 /*
146  * Validates the request to set a BGE parameter to a specific value.
147  * If the request is OK, the parameter is set.  Also the <info> field
148  * is incremented to show that the parameter was touched, even though
149  * it may have been set to the same value it already had.
150  */
151 static int
152 bge_param_set(queue_t *q, mblk_t *mp, char *value, caddr_t cp, cred_t *credp)
153 {
154 	nd_param_t *ndp;
155 	long new_value;
156 	char *end;
157 
158 	_NOTE(ARGUNUSED(q, mp, credp))
159 
160 	ndp = (nd_param_t *)cp;
161 	new_value = mi_strtol(value, &end, 10);
162 	if (end == value)
163 		return (EINVAL);
164 	if (new_value < ndp->ndp_min || new_value > ndp->ndp_max)
165 		return (EINVAL);
166 
167 	ndp->ndp_val = new_value;
168 	ndp->ndp_info += 1;
169 	return (0);
170 }
171 
172 /*
173  * Initialise the per-instance parameter array from the global prototype,
174  * and register each element with the named dispatch handler using nd_load()
175  */
176 static int
177 bge_param_register(bge_t *bgep)
178 {
179 	const nd_param_t *tmplp;
180 	dev_info_t *dip;
181 	nd_param_t *ndp;
182 	caddr_t *nddpp;
183 	pfi_t setfn;
184 	char *nm;
185 	int pval;
186 
187 	BGE_TRACE(("bge_param_register($%p)", (void *)bgep));
188 
189 	dip = bgep->devinfo;
190 	nddpp = &bgep->nd_data_p;
191 	ASSERT(*nddpp == NULL);
192 
193 	for (tmplp = nd_template; tmplp->ndp_name != NULL; ++tmplp) {
194 		/*
195 		 * Copy the template from nd_template[] into the
196 		 * proper slot in the per-instance parameters,
197 		 * then register the parameter with nd_load()
198 		 */
199 		ndp = &bgep->nd_params[tmplp->ndp_info];
200 		*ndp = *tmplp;
201 		nm = &ndp->ndp_name[0];
202 		setfn = bge_param_set;
203 		if (bgep->chipid.flags & CHIP_FLAG_SERDES)
204 			switch (*nm) {
205 			default:
206 				break;
207 
208 			case '-':
209 			case '?':
210 				ndp->ndp_val = 0;
211 				setfn = NULL;
212 				break;
213 			}
214 
215 		switch (*nm) {
216 		default:
217 		case '!':
218 			continue;
219 
220 		case '+':
221 		case '?':
222 			break;
223 
224 		case '=':
225 		case '-':
226 			setfn = NULL;
227 			break;
228 		}
229 
230 		if (!nd_load(nddpp, ++nm, bge_param_get, setfn, (caddr_t)ndp))
231 			goto nd_fail;
232 
233 		/*
234 		 * If the parameter is writable, and there's a property
235 		 * with the same name, and its value is in range, we use
236 		 * it to initialise the parameter.  If it exists but is
237 		 * out of range, it's ignored.
238 		 */
239 		if (setfn && BGE_PROP_EXISTS(dip, nm)) {
240 			pval = BGE_PROP_GET_INT(dip, nm);
241 			if (pval >= ndp->ndp_min && pval <= ndp->ndp_max)
242 				ndp->ndp_val = pval;
243 		}
244 	}
245 
246 	BGE_DEBUG(("bge_param_register: OK"));
247 	return (DDI_SUCCESS);
248 
249 nd_fail:
250 	BGE_DEBUG(("bge_param_register: FAILED at index %d [info %d]",
251 		tmplp-nd_template, tmplp->ndp_info));
252 	nd_free(nddpp);
253 	return (DDI_FAILURE);
254 }
255 
256 int
257 bge_nd_init(bge_t *bgep)
258 {
259 	dev_info_t *dip;
260 	int duplex;
261 	int speed;
262 	char **options, *prop;
263 	uint_t  noptions;
264 
265 	BGE_TRACE(("bge_nd_init($%p)", (void *)bgep));
266 
267 	/*
268 	 * Register all the per-instance properties, initialising
269 	 * them from the table above or from driver properties set
270 	 * in the .conf file
271 	 */
272 	if (bge_param_register(bgep) != DDI_SUCCESS)
273 		return (-1);
274 
275 	/*
276 	 * check the OBP property "supported-network-types"
277 	 */
278 	if (BGE_PROP_EXISTS(bgep->devinfo, supported_net)) {
279 		if (ddi_prop_lookup_string_array(DDI_DEV_T_ANY, bgep->devinfo,
280 			DDI_PROP_DONTPASS, supported_net,
281 			&options, &noptions) == DDI_PROP_SUCCESS) {
282 
283 			bgep->param_adv_autoneg = 0;
284 			bgep->param_adv_1000fdx = 0;
285 			bgep->param_adv_1000hdx = 0;
286 			bgep->param_adv_100fdx = 0;
287 			bgep->param_adv_100hdx = 0;
288 			bgep->param_adv_10fdx = 0;
289 			bgep->param_adv_10hdx = 0;
290 
291 			for (; noptions > 0; noptions--) {
292 				prop = options[noptions-1];
293 				if (strstr(prop, "ethernet") == NULL)
294 					continue;
295 				if (strstr(prop, "1000")) {
296 					if (strstr(prop, "auto")) {
297 						bgep->param_adv_1000fdx = 1;
298 						bgep->param_adv_1000hdx = 1;
299 						bgep->param_adv_autoneg = 1;
300 					} else if (strstr(prop, "full"))
301 						bgep->param_adv_1000fdx = 1;
302 					else if (strstr(prop, "half"))
303 						bgep->param_adv_1000hdx = 1;
304 				} else if (strstr(prop, "100")) {
305 					if (strstr(prop, "auto")) {
306 						bgep->param_adv_100fdx = 1;
307 						bgep->param_adv_100hdx = 1;
308 						bgep->param_adv_autoneg = 1;
309 					} else if (strstr(prop, "full"))
310 						bgep->param_adv_100fdx = 1;
311 					else if (strstr(prop, "half"))
312 						bgep->param_adv_100hdx = 1;
313 				} else if (strstr(prop, "10")) {
314 					if (strstr(prop, "auto")) {
315 						bgep->param_adv_10fdx = 1;
316 						bgep->param_adv_10hdx = 1;
317 						bgep->param_adv_autoneg = 1;
318 					} else if (strstr(prop, "full"))
319 						bgep->param_adv_10fdx = 1;
320 					else if (strstr(prop, "half"))
321 						bgep->param_adv_10hdx = 1;
322 				}
323 			}
324 
325 			ddi_prop_free(options);
326 		}
327 	}
328 
329 	/*
330 	 * The link speed may be forced to 10, 100 or 1000 Mbps using
331 	 * the property "transfer-speed". This may be done in OBP by
332 	 * using the command "apply transfer-speed=<speed> <device>".
333 	 * The speed may be 10, 100 or 1000 - any other value will be
334 	 * ignored.  Note that this does *enables* autonegotiation, but
335 	 * restricts it to the speed specified by the property.
336 	 */
337 	dip = bgep->devinfo;
338 	if (BGE_PROP_EXISTS(dip, transfer_speed_propname)) {
339 
340 		speed = BGE_PROP_GET_INT(dip, transfer_speed_propname);
341 		bge_log(bgep, "%s property is %d",
342 			transfer_speed_propname, speed);
343 
344 		switch (speed) {
345 		case 1000:
346 			bgep->param_adv_autoneg = 1;
347 			bgep->param_adv_1000fdx = 1;
348 			bgep->param_adv_1000hdx = 1;
349 			bgep->param_adv_100fdx = 0;
350 			bgep->param_adv_100hdx = 0;
351 			bgep->param_adv_10fdx = 0;
352 			bgep->param_adv_10hdx = 0;
353 			break;
354 
355 		case 100:
356 			bgep->param_adv_autoneg = 1;
357 			bgep->param_adv_1000fdx = 0;
358 			bgep->param_adv_1000hdx = 0;
359 			bgep->param_adv_100fdx = 1;
360 			bgep->param_adv_100hdx = 1;
361 			bgep->param_adv_10fdx = 0;
362 			bgep->param_adv_10hdx = 0;
363 			break;
364 
365 		case 10:
366 			bgep->param_adv_autoneg = 1;
367 			bgep->param_adv_1000fdx = 0;
368 			bgep->param_adv_1000hdx = 0;
369 			bgep->param_adv_100fdx = 0;
370 			bgep->param_adv_100hdx = 0;
371 			bgep->param_adv_10fdx = 1;
372 			bgep->param_adv_10hdx = 1;
373 			break;
374 
375 		default:
376 			break;
377 		}
378 	}
379 
380 	/*
381 	 * Also check the "speed" and "full-duplex" properties.  Setting
382 	 * these properties will override all other settings and *disable*
383 	 * autonegotiation, so both should be specified if either one is.
384 	 * Otherwise, the unspecified parameter will be set to a default
385 	 * value (1000Mb/s, full-duplex).
386 	 */
387 	if (BGE_PROP_EXISTS(dip, speed_propname) ||
388 	    BGE_PROP_EXISTS(dip, duplex_propname)) {
389 
390 		bgep->param_adv_autoneg = 0;
391 		bgep->param_adv_1000fdx = 1;
392 		bgep->param_adv_1000hdx = 1;
393 		bgep->param_adv_100fdx = 1;
394 		bgep->param_adv_100hdx = 1;
395 		bgep->param_adv_10fdx = 1;
396 		bgep->param_adv_10hdx = 1;
397 
398 		speed = BGE_PROP_GET_INT(dip, speed_propname);
399 		duplex = BGE_PROP_GET_INT(dip, duplex_propname);
400 		bge_log(bgep, "%s property is %d",
401 			speed_propname, speed);
402 		bge_log(bgep, "%s property is %d",
403 			duplex_propname, duplex);
404 
405 		switch (speed) {
406 		case 1000:
407 		default:
408 			bgep->param_adv_100fdx = 0;
409 			bgep->param_adv_100hdx = 0;
410 			bgep->param_adv_10fdx = 0;
411 			bgep->param_adv_10hdx = 0;
412 			break;
413 
414 		case 100:
415 			bgep->param_adv_1000fdx = 0;
416 			bgep->param_adv_1000hdx = 0;
417 			bgep->param_adv_10fdx = 0;
418 			bgep->param_adv_10hdx = 0;
419 			break;
420 
421 		case 10:
422 			bgep->param_adv_1000fdx = 0;
423 			bgep->param_adv_1000hdx = 0;
424 			bgep->param_adv_100fdx = 0;
425 			bgep->param_adv_100hdx = 0;
426 			break;
427 		}
428 
429 		switch (duplex) {
430 		default:
431 		case 1:
432 			bgep->param_adv_1000hdx = 0;
433 			bgep->param_adv_100hdx = 0;
434 			bgep->param_adv_10hdx = 0;
435 			break;
436 
437 		case 0:
438 			bgep->param_adv_1000fdx = 0;
439 			bgep->param_adv_100fdx = 0;
440 			bgep->param_adv_10fdx = 0;
441 			break;
442 		}
443 	}
444 
445 	BGE_DEBUG(("bge_nd_init: autoneg %d"
446 			"pause %d asym_pause %d "
447 			"1000fdx %d 1000hdx %d "
448 			"100fdx %d 100hdx %d "
449 			"10fdx %d 10hdx %d ",
450 		bgep->param_adv_autoneg,
451 		bgep->param_adv_pause, bgep->param_adv_asym_pause,
452 		bgep->param_adv_1000fdx, bgep->param_adv_1000hdx,
453 		bgep->param_adv_100fdx, bgep->param_adv_100hdx,
454 		bgep->param_adv_10fdx, bgep->param_adv_10hdx));
455 
456 	return (0);
457 }
458 
459 enum ioc_reply
460 bge_nd_ioctl(bge_t *bgep, queue_t *wq, mblk_t *mp, struct iocblk *iocp)
461 {
462 	nd_param_t *ndp;
463 	boolean_t ok;
464 	int info;
465 	int cmd;
466 
467 	BGE_TRACE(("bge_nd_ioctl($%p, $%p, $%p, $%p)",
468 		(void *)bgep, (void *)wq, (void *)mp, (void *)iocp));
469 
470 	ASSERT(mutex_owned(bgep->genlock));
471 
472 	cmd = iocp->ioc_cmd;
473 	switch (cmd) {
474 	default:
475 		/* NOTREACHED */
476 		bge_error(bgep, "bge_nd_ioctl: invalid cmd 0x%x", cmd);
477 		return (IOC_INVAL);
478 
479 	case ND_GET:
480 		/*
481 		 * If nd_getset() returns B_FALSE, the command was
482 		 * not valid (e.g. unknown name), so we just tell the
483 		 * top-level ioctl code to send a NAK (with code EINVAL).
484 		 *
485 		 * Otherwise, nd_getset() will have built the reply to
486 		 * be sent (but not actually sent it), so we tell the
487 		 * caller to send the prepared reply.
488 		 */
489 		ok = nd_getset(wq, bgep->nd_data_p, mp);
490 		BGE_DEBUG(("bge_nd_ioctl: get %s", ok ? "OK" : "FAIL"));
491 		return (ok ? IOC_REPLY : IOC_INVAL);
492 
493 	case ND_SET:
494 		/*
495 		 * All adv_* parameters are locked (read-only) while
496 		 * the device is in any sort of loopback mode ...
497 		 */
498 		if (bgep->param_loop_mode != BGE_LOOP_NONE) {
499 			iocp->ioc_error = EBUSY;
500 			return (IOC_INVAL);
501 		}
502 
503 		/*
504 		 * Before calling nd_getset(), we save the <info> field
505 		 * of the 'autonegotiation' parameter so that we can tell
506 		 * whether it was assigned (even if its value doesn't
507 		 * actually change).
508 		 */
509 		ndp = &bgep->nd_params[PARAM_ADV_AUTONEG_CAP];
510 		info = ndp->ndp_info;
511 		ok = nd_getset(wq, bgep->nd_data_p, mp);
512 
513 		/*
514 		 * If nd_getset() returns B_FALSE, the command was
515 		 * not valid (e.g. unknown name), so we just tell
516 		 * the top-level ioctl code to send a NAK (with code
517 		 * EINVAL by default).
518 		 *
519 		 * Otherwise, nd_getset() will have built the reply to
520 		 * be sent - but that doesn't imply success!  In some
521 		 * cases, the reply it's built will have a non-zero
522 		 * error code in it (e.g. EPERM if not superuser).
523 		 * So, we also drop out in that case ...
524 		 */
525 		BGE_DEBUG(("bge_nd_ioctl: set %s err %d autoneg %d info %d/%d",
526 			ok ? "OK" : "FAIL", iocp->ioc_error,
527 			ndp->ndp_val, info, ndp->ndp_info));
528 		if (!ok)
529 			return (IOC_INVAL);
530 		if (iocp->ioc_error)
531 			return (IOC_REPLY);
532 
533 		/*
534 		 * OK, a successful 'set'.  Prepare the messages explaining
535 		 * the link down/up cycle that will probably follow, then
536 		 * return IOC_RESTART_REPLY, telling the top-level ioctl
537 		 * code to update the PHY and restart the chip before
538 		 * sending our prepared reply
539 		 */
540 		if (ndp->ndp_val) {
541 			bgep->link_down_msg = " (autonegotiation enabled)";
542 			bgep->link_up_msg = " (autonegotiated)";
543 		} else {
544 			bgep->link_down_msg = " (autonegotiation disabled)";
545 			bgep->link_up_msg = " (forced)";
546 		}
547 		if (ndp->ndp_info == info)
548 			bgep->link_down_msg = " (advertised capabilities "
549 						"changed)";
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