xref: /illumos-gate/usr/src/uts/common/io/cons.c (revision 186f7fbf5e07d046b50e4e15c32b21f109b76c80)
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 
28 /*
29  * Indirect console driver for Sun.
30  *
31  * Redirects all I/O to the device designated as the underlying "hardware"
32  * console, as given by the value of rconsvp.  The implementation assumes that
33  * rconsvp denotes a STREAMS device; the assumption is justified since
34  * consoles must be capable of effecting tty semantics.
35  *
36  * rconsvp is set in autoconf.c:consconfig(), based on information obtained
37  * from the EEPROM.
38  *
39  * XXX:	The driver still needs to be converted to use ANSI C consistently
40  *	throughout.
41  */
42 
43 #include <sys/types.h>
44 #include <sys/open.h>
45 #include <sys/param.h>
46 #include <sys/systm.h>
47 #include <sys/signal.h>
48 #include <sys/cred.h>
49 #include <sys/user.h>
50 #include <sys/proc.h>
51 #include <sys/disp.h>
52 #include <sys/file.h>
53 #include <sys/taskq.h>
54 #include <sys/log.h>
55 #include <sys/vnode.h>
56 #include <sys/uio.h>
57 #include <sys/stat.h>
58 
59 #include <sys/console.h>
60 #include <sys/consdev.h>
61 
62 #include <sys/stream.h>
63 #include <sys/strsubr.h>
64 #include <sys/poll.h>
65 
66 #include <sys/debug.h>
67 
68 #include <sys/conf.h>
69 #include <sys/ddi.h>
70 #include <sys/sunddi.h>
71 
72 static int cnopen(dev_t *, int, int, struct cred *);
73 static int cnclose(dev_t, int, int, struct cred *);
74 static int cnread(dev_t, struct uio *, struct cred *);
75 static int cnwrite(dev_t, struct uio *, struct cred *);
76 static int cnioctl(dev_t, int, intptr_t, int, struct cred *, int *);
77 static int cnpoll(dev_t, short, int, short *, struct pollhead **);
78 static int cn_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
79 static int cn_attach(dev_info_t *, ddi_attach_cmd_t);
80 static int cn_detach(dev_info_t *, ddi_detach_cmd_t);
81 
82 static dev_info_t *cn_dip;		/* private copy of devinfo pointer */
83 
84 static struct cb_ops cn_cb_ops = {
85 
86 	cnopen,			/* open */
87 	cnclose,		/* close */
88 	nodev,			/* strategy */
89 	nodev,			/* print */
90 	nodev,			/* dump */
91 	cnread,			/* read */
92 	cnwrite,		/* write */
93 	cnioctl,		/* ioctl */
94 	nodev,			/* devmap */
95 	nodev,			/* mmap */
96 	nodev, 			/* segmap */
97 	cnpoll,			/* poll */
98 	ddi_prop_op,		/* cb_prop_op */
99 	0,			/* streamtab  */
100 	D_NEW | D_MP		/* Driver compatibility flag */
101 
102 };
103 
104 static struct dev_ops cn_ops = {
105 
106 	DEVO_REV,		/* devo_rev, */
107 	0,			/* refcnt  */
108 	cn_info,		/* info */
109 	nulldev,		/* identify */
110 	nulldev,		/* probe */
111 	cn_attach,		/* attach */
112 	cn_detach,		/* detach */
113 	nodev,			/* reset */
114 	&cn_cb_ops,		/* driver operations */
115 	(struct bus_ops *)0,	/* bus operations */
116 	NULL,			/* power */
117 	ddi_quiesce_not_needed,		/* quiesce */
118 
119 };
120 
121 /*
122  * Global variables associated with the console device:
123  *
124  * XXX:	There are too many of these!
125  * moved to space.c to become resident in the kernel so that cons
126  * can be loadable.
127  */
128 
129 extern dev_t	rconsdev;	/* "hardware" console */
130 extern vnode_t	*rconsvp;	/* pointer to vnode for that device */
131 
132 /*
133  * XXX: consulted in prsubr.c, for /proc entry point for obtaining ps info.
134  */
135 extern dev_t	uconsdev;	/* What the user thinks is the console device */
136 
137 /*
138  * Private driver state:
139  */
140 
141 /*
142  * The underlying console device potentially can be opened through (at least)
143  * two paths: through this driver and through the underlying device's driver.
144  * To ensure that reference counts are meaningful and therefore that close
145  * routines are called at the right time, it's important to make sure that
146  * rconsvp's s_count field (i.e., the count on the underlying device) never
147  * has a contribution of more than one through this driver, regardless of how
148  * many times this driver's been opened.  rconsopen keeps track of the
149  * necessary information to ensure this property.
150  */
151 static uint_t	rconsopen;
152 
153 
154 #include <sys/types.h>
155 #include <sys/conf.h>
156 #include <sys/param.h>
157 #include <sys/systm.h>
158 #include <sys/errno.h>
159 #include <sys/modctl.h>
160 
161 
162 extern int nodev(), nulldev();
163 extern int dseekneg_flag;
164 extern struct mod_ops mod_driverops;
165 extern struct dev_ops cn_ops;
166 
167 /*
168  * Module linkage information for the kernel.
169  */
170 
171 static struct modldrv modldrv = {
172 	&mod_driverops, /* Type of module.  This one is a pseudo driver */
173 	"Console redirection driver",
174 	&cn_ops,	/* driver ops */
175 };
176 
177 static struct modlinkage modlinkage = {
178 	MODREV_1,
179 	&modldrv,
180 	NULL
181 };
182 
183 int
184 _init(void)
185 {
186 	return (mod_install(&modlinkage));
187 }
188 
189 int
190 _fini(void)
191 {
192 	return (EBUSY);
193 }
194 
195 int
196 _info(struct modinfo *modinfop)
197 {
198 	return (mod_info(&modlinkage, modinfop));
199 }
200 
201 /*
202  * DDI glue routines
203  */
204 static int
205 cn_attach(dev_info_t *devi, ddi_attach_cmd_t cmd)
206 {
207 	if (cmd != DDI_ATTACH)
208 		return (DDI_FAILURE);
209 
210 	if (ddi_create_minor_node(devi, "syscon", S_IFCHR,
211 	    0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
212 		return (DDI_FAILURE);
213 	}
214 	if (ddi_create_minor_node(devi, "systty", S_IFCHR,
215 	    0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
216 		ddi_remove_minor_node(devi, NULL);
217 		return (DDI_FAILURE);
218 	}
219 	if (ddi_create_minor_node(devi, "console", S_IFCHR,
220 	    0, DDI_PSEUDO, NULL) == DDI_FAILURE) {
221 		ddi_remove_minor_node(devi, NULL);
222 		return (DDI_FAILURE);
223 	}
224 	cn_dip = devi;
225 	return (DDI_SUCCESS);
226 }
227 
228 static int
229 cn_detach(dev_info_t *devi, ddi_detach_cmd_t cmd)
230 {
231 	if (cmd != DDI_DETACH)
232 		return (DDI_FAILURE);
233 	ddi_remove_minor_node(devi, NULL);
234 	uconsdev = NODEV;
235 	return (DDI_SUCCESS);
236 }
237 
238 /* ARGSUSED */
239 static int
240 cn_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
241 {
242 	int error = DDI_FAILURE;
243 
244 	switch (infocmd) {
245 	case DDI_INFO_DEVT2DEVINFO:
246 		if (getminor((dev_t)arg) == 0 && cn_dip != NULL) {
247 			*result = (void *) cn_dip;
248 			error = DDI_SUCCESS;
249 		}
250 		break;
251 
252 	case DDI_INFO_DEVT2INSTANCE:
253 		if (getminor((dev_t)arg) == 0) {
254 			*result = (void *)0;
255 			error = DDI_SUCCESS;
256 		}
257 		break;
258 
259 	default:
260 		break;
261 	}
262 
263 	return (error);
264 }
265 
266 /*
267  * XXX	Caution: before allowing more than 256 minor devices on the
268  *	console, make sure you understand the 'compatibility' hack
269  *	in ufs_iget() that translates old dev_t's to new dev_t's.
270  *	See bugid 1098104 for the sordid details.
271  */
272 
273 /* ARGSUSED */
274 static int
275 cnopen(dev_t *dev, int flag, int state, struct cred *cred)
276 {
277 	int	err;
278 	static int	been_here;
279 	vnode_t	*vp = rconsvp;
280 
281 	ASSERT(cred != NULL);
282 
283 	if (rconsvp == NULL)
284 		return (0);
285 
286 
287 	/*
288 	 * XXX: Clean up inactive PIDs from previous opens if any.
289 	 * These would have been created as a result of an I_SETSIG
290 	 * issued against console.  This is a workaround, and
291 	 * console driver must be correctly redesigned not to need
292 	 * this hook.
293 	 */
294 	if (vp->v_stream) {
295 		str_cn_clean(vp);
296 	}
297 
298 	/*
299 	 * XXX:	Set hook to tell /proc about underlying console.  (There's
300 	 *	gotta be a better way...)
301 	 */
302 	if (state != OTYP_CHR || getminor(*dev) != 0)
303 		return (ENXIO);
304 	if (been_here == 0) {
305 		uconsdev = *dev;
306 		been_here = 1;
307 		if (vn_open("/dev/console", UIO_SYSSPACE, FWRITE | FNOCTTY,
308 		    0, &console_vnode, 0, 0) == 0)
309 			console_taskq = taskq_create("console_taskq",
310 			    1, maxclsyspri - 1, LOG_LOWAT / LOG_MSGSIZE,
311 			    LOG_HIWAT / LOG_MSGSIZE, TASKQ_PREPOPULATE);
312 	}
313 
314 	if ((err = VOP_OPEN(&vp, flag, cred, NULL)) != 0)
315 		return (err);
316 
317 	/*
318 	 * The underlying driver is not allowed to have cloned itself
319 	 * for this open.
320 	 */
321 	if (vp != rconsvp) {
322 		/*
323 		 * It might happen that someone set rconsvp to NULL
324 		 * whilst we were in the middle of the open.
325 		 */
326 		if (rconsvp == NULL) {
327 			(void) VOP_CLOSE(vp, flag, 1, (offset_t)0, cred, NULL);
328 			return (0);
329 		}
330 		cmn_err(CE_PANIC, "cnopen: cloned open");
331 	}
332 
333 	rconsopen++;
334 
335 	return (0);
336 }
337 
338 /* ARGSUSED */
339 static int
340 cnclose(dev_t dev, int flag, int state, struct cred *cred)
341 {
342 	int	err = 0;
343 	vnode_t	*vp;
344 
345 	/*
346 	 * Since this is the _last_ close, it's our last chance to close the
347 	 * underlying device.  (Note that if someone else has the underlying
348 	 * hardware console device open, we won't get here, since spec_close
349 	 * will see s_count > 1.)
350 	 */
351 	if (state != OTYP_CHR)
352 		return (ENXIO);
353 
354 	if (rconsvp == NULL)
355 		return (0);
356 
357 	while ((rconsopen != 0) && ((vp = rconsvp) != NULL)) {
358 		err = VOP_CLOSE(vp, flag, 1, (offset_t)0, cred, NULL);
359 		if (!err) {
360 			vp->v_stream = NULL;
361 			rconsopen--;
362 		}
363 	}
364 	return (err);
365 }
366 
367 /* ARGSUSED */
368 static int
369 cnread(dev_t dev, struct uio *uio, struct cred *cred)
370 {
371 	kcondvar_t	sleep_forever;
372 	kmutex_t	sleep_forever_mutex;
373 
374 	if (rconsvp == NULL) {
375 		/*
376 		 * Go to sleep forever.  This seems like the least
377 		 * harmful thing to do if there's no console.
378 		 * EOF might be better if we're ending up single-user
379 		 * mode.
380 		 */
381 		cv_init(&sleep_forever, NULL, CV_DRIVER, NULL);
382 		mutex_init(&sleep_forever_mutex, NULL, MUTEX_DRIVER, NULL);
383 		mutex_enter(&sleep_forever_mutex);
384 		(void) cv_wait_sig(&sleep_forever, &sleep_forever_mutex);
385 		mutex_exit(&sleep_forever_mutex);
386 		return (EIO);
387 	}
388 
389 	if (rconsvp->v_stream != NULL)
390 		return (strread(rconsvp, uio, cred));
391 	else
392 		return (cdev_read(rconsdev, uio, cred));
393 }
394 
395 /* ARGSUSED */
396 static int
397 cnwrite(dev_t dev, struct uio *uio, struct cred *cred)
398 {
399 	if (rconsvp == NULL) {
400 		uio->uio_resid = 0;
401 		return (0);
402 	}
403 
404 	if (rconsvp->v_stream != NULL)
405 		return (strwrite(rconsvp, uio, cred));
406 	else
407 		return (cdev_write(rconsdev, uio, cred));
408 }
409 
410 /* ARGSUSED */
411 static int
412 cnprivateioc(dev_t dev, int cmd, intptr_t arg, int flag, struct cred *cred,
413 	int *rvalp)
414 {
415 
416 	/* currently we only support one ioctl */
417 	if (cmd != CONS_GETTERM)
418 		return (EINVAL);
419 
420 	/* Confirm iwscn is immediate target of cn redirection */
421 	if (rconsvp != wsconsvp)
422 		return (ENODEV);
423 
424 	/*
425 	 * If the redirection client is not wc, it should return
426 	 * error upon receiving the CONS_GETTERM ioctl.
427 	 *
428 	 * if it is wc, we know that the target supports the CONS_GETTERM
429 	 * ioctl, which very conviently has the exact same data
430 	 * format as this ioctl...  so let's just pass it on.
431 	 */
432 	return (cdev_ioctl(rconsdev, CONS_GETTERM, arg, flag, cred, rvalp));
433 }
434 
435 /* ARGSUSED */
436 static int
437 cnioctl(dev_t dev, int cmd, intptr_t arg, int flag, struct cred *cred,
438 	int *rvalp)
439 {
440 	if (rconsvp == NULL)
441 		return (0);
442 
443 	if ((cmd & _CNIOC_MASK) == _CNIOC)
444 		return (cnprivateioc(dev, cmd, arg, flag, cred, rvalp));
445 	else if (rconsvp->v_stream != NULL)
446 		return (strioctl(rconsvp, cmd, arg, flag, U_TO_K, cred,
447 		    rvalp));
448 	else
449 		return (cdev_ioctl(rconsdev, cmd, arg, flag, cred, rvalp));
450 }
451 
452 /* ARGSUSED */
453 static int
454 cnpoll(dev_t dev, short events, int anyyet, short *reventsp,
455 	struct pollhead **phpp)
456 {
457 	if (rconsvp == NULL)
458 		return (nochpoll(dev, events, anyyet, reventsp, phpp));
459 
460 	if (rconsvp->v_stream != NULL)
461 		return (strpoll(rconsvp->v_stream, events, anyyet, reventsp,
462 		    phpp));
463 	else
464 		return (cdev_poll(rconsdev, events, anyyet, reventsp, phpp));
465 }
466