cons.c revision 7656:2621e50fdf4a
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
72static int cnopen(dev_t *, int, int, struct cred *);
73static int cnclose(dev_t, int, int, struct cred *);
74static int cnread(dev_t, struct uio *, struct cred *);
75static int cnwrite(dev_t, struct uio *, struct cred *);
76static int cnioctl(dev_t, int, intptr_t, int, struct cred *, int *);
77static int cnpoll(dev_t, short, int, short *, struct pollhead **);
78static int cn_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
79static int cn_attach(dev_info_t *, ddi_attach_cmd_t);
80static int cn_detach(dev_info_t *, ddi_detach_cmd_t);
81
82static dev_info_t *cn_dip;		/* private copy of devinfo pointer */
83
84static 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
104static 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
129extern dev_t	rconsdev;	/* "hardware" console */
130extern 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 */
135extern 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 */
151static 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
162extern int nodev(), nulldev();
163extern int dseekneg_flag;
164extern struct mod_ops mod_driverops;
165extern struct dev_ops cn_ops;
166
167/*
168 * Module linkage information for the kernel.
169 */
170
171static 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
177static struct modlinkage modlinkage = {
178	MODREV_1,
179	&modldrv,
180	NULL
181};
182
183int
184_init(void)
185{
186	return (mod_install(&modlinkage));
187}
188
189int
190_fini(void)
191{
192	return (EBUSY);
193}
194
195int
196_info(struct modinfo *modinfop)
197{
198	return (mod_info(&modlinkage, modinfop));
199}
200
201/*
202 * DDI glue routines
203 */
204static int
205cn_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
228static int
229cn_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 */
239static int
240cn_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 */
274static int
275cnopen(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 */
339static int
340cnclose(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 */
368static int
369cnread(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 */
396static int
397cnwrite(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 */
411static int
412cnprivateioc(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 */
436static int
437cnioctl(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 */
453static int
454cnpoll(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