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