kern_cons.c revision 179246
1/*-
2 * Copyright (c) 1988 University of Utah.
3 * Copyright (c) 1991 The Regents of the University of California.
4 * All rights reserved.
5 *
6 * This code is derived from software contributed to Berkeley by
7 * the Systems Programming Group of the University of Utah Computer
8 * Science Department.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 *    notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 *    notice, this list of conditions and the following disclaimer in the
17 *    documentation and/or other materials provided with the distribution.
18 * 4. Neither the name of the University nor the names of its contributors
19 *    may be used to endorse or promote products derived from this software
20 *    without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 * SUCH DAMAGE.
33 *
34 *	from: @(#)cons.c	7.2 (Berkeley) 5/9/91
35 */
36
37#include <sys/cdefs.h>
38__FBSDID("$FreeBSD: head/sys/kern/tty_cons.c 179246 2008-05-23 16:06:35Z ed $");
39
40#include "opt_ddb.h"
41
42#include <sys/param.h>
43#include <sys/systm.h>
44#include <sys/lock.h>
45#include <sys/mutex.h>
46#include <sys/conf.h>
47#include <sys/cons.h>
48#include <sys/fcntl.h>
49#include <sys/kdb.h>
50#include <sys/kernel.h>
51#include <sys/malloc.h>
52#include <sys/msgbuf.h>
53#include <sys/namei.h>
54#include <sys/priv.h>
55#include <sys/proc.h>
56#include <sys/queue.h>
57#include <sys/reboot.h>
58#include <sys/sysctl.h>
59#include <sys/sbuf.h>
60#include <sys/tty.h>
61#include <sys/uio.h>
62#include <sys/vnode.h>
63
64#include <ddb/ddb.h>
65
66#include <machine/cpu.h>
67#include <machine/clock.h>
68
69static MALLOC_DEFINE(M_TTYCONS, "tty console", "tty console handling");
70
71static	d_open_t	cnopen;
72static	d_close_t	cnclose;
73static	d_read_t	cnread;
74static	d_write_t	cnwrite;
75static	d_ioctl_t	cnioctl;
76static	d_poll_t	cnpoll;
77static	d_kqfilter_t	cnkqfilter;
78
79static struct cdevsw cn_cdevsw = {
80	.d_version =	D_VERSION,
81	.d_open =	cnopen,
82	.d_close =	cnclose,
83	.d_read =	cnread,
84	.d_write =	cnwrite,
85	.d_ioctl =	cnioctl,
86	.d_poll =	cnpoll,
87	.d_name =	"console",
88	.d_flags =	D_TTY | D_NEEDGIANT,
89	.d_kqfilter =	cnkqfilter,
90};
91
92struct cn_device {
93	STAILQ_ENTRY(cn_device) cnd_next;
94	struct		vnode *cnd_vp;
95	struct		consdev *cnd_cn;
96};
97
98#define CNDEVPATHMAX	32
99#define CNDEVTAB_SIZE	4
100static struct cn_device cn_devtab[CNDEVTAB_SIZE];
101static STAILQ_HEAD(, cn_device) cn_devlist =
102    STAILQ_HEAD_INITIALIZER(cn_devlist);
103
104#define CND_INVALID(cnd, td) 						\
105	(cnd == NULL || cnd->cnd_vp == NULL ||				\
106	    (cnd->cnd_vp->v_type == VBAD && !cn_devopen(cnd, td, 1)))
107
108static dev_t	cn_udev_t;
109SYSCTL_OPAQUE(_machdep, OID_AUTO, consdev, CTLFLAG_RD,
110	&cn_udev_t, sizeof cn_udev_t, "T,struct cdev *", "");
111
112int	cons_avail_mask = 0;	/* Bit mask. Each registered low level console
113				 * which is currently unavailable for inpit
114				 * (i.e., if it is in graphics mode) will have
115				 * this bit cleared.
116				 */
117static int cn_mute;
118static int openflag;			/* how /dev/console was opened */
119static int cn_is_open;
120static char *consbuf;			/* buffer used by `consmsgbuf' */
121static struct callout conscallout;	/* callout for outputting to constty */
122struct msgbuf consmsgbuf;		/* message buffer for console tty */
123static u_char console_pausing;		/* pause after each line during probe */
124static char *console_pausestr=
125"<pause; press any key to proceed to next line or '.' to end pause mode>";
126struct tty *constty;			/* pointer to console "window" tty */
127static struct mtx cnputs_mtx;		/* Mutex for cnputs(). */
128static int use_cnputs_mtx = 0;		/* != 0 if cnputs_mtx locking reqd. */
129
130static void constty_timeout(void *arg);
131
132static struct consdev cons_consdev;
133DATA_SET(cons_set, cons_consdev);
134SET_DECLARE(cons_set, struct consdev);
135
136void
137cninit(void)
138{
139	struct consdev *best_cn, *cn, **list;
140
141	/*
142	 * Check if we should mute the console (for security reasons perhaps)
143	 * It can be changes dynamically using sysctl kern.consmute
144	 * once we are up and going.
145	 *
146	 */
147        cn_mute = ((boothowto & (RB_MUTE
148			|RB_SINGLE
149			|RB_VERBOSE
150			|RB_ASKNAME)) == RB_MUTE);
151
152	/*
153	 * Find the first console with the highest priority.
154	 */
155	best_cn = NULL;
156	SET_FOREACH(list, cons_set) {
157		cn = *list;
158		cnremove(cn);
159		if (cn->cn_probe == NULL)
160			continue;
161		cn->cn_probe(cn);
162		if (cn->cn_pri == CN_DEAD)
163			continue;
164		if (best_cn == NULL || cn->cn_pri > best_cn->cn_pri)
165			best_cn = cn;
166		if (boothowto & RB_MULTIPLE) {
167			/*
168			 * Initialize console, and attach to it.
169			 */
170			cn->cn_init(cn);
171			cnadd(cn);
172		}
173	}
174	if (best_cn == NULL)
175		return;
176	if ((boothowto & RB_MULTIPLE) == 0) {
177		best_cn->cn_init(best_cn);
178		cnadd(best_cn);
179	}
180	if (boothowto & RB_PAUSE)
181		console_pausing = 1;
182	/*
183	 * Make the best console the preferred console.
184	 */
185	cnselect(best_cn);
186}
187
188void
189cninit_finish()
190{
191	console_pausing = 0;
192}
193
194/* add a new physical console to back the virtual console */
195int
196cnadd(struct consdev *cn)
197{
198	struct cn_device *cnd;
199	int i;
200
201	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
202		if (cnd->cnd_cn == cn)
203			return (0);
204	for (i = 0; i < CNDEVTAB_SIZE; i++) {
205		cnd = &cn_devtab[i];
206		if (cnd->cnd_cn == NULL)
207			break;
208	}
209	if (cnd->cnd_cn != NULL)
210		return (ENOMEM);
211	cnd->cnd_cn = cn;
212	if (cn->cn_name[0] == '\0') {
213		/* XXX: it is unclear if/where this print might output */
214		printf("WARNING: console at %p has no name\n", cn);
215	}
216	STAILQ_INSERT_TAIL(&cn_devlist, cnd, cnd_next);
217
218	/* Add device to the active mask. */
219	cnavailable(cn, (cn->cn_flags & CN_FLAG_NOAVAIL) == 0);
220
221	return (0);
222}
223
224void
225cnremove(struct consdev *cn)
226{
227	struct cn_device *cnd;
228	int i;
229
230	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
231		if (cnd->cnd_cn != cn)
232			continue;
233		STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next);
234		if (cnd->cnd_vp != NULL)
235			vn_close(cnd->cnd_vp, openflag, NOCRED, NULL);
236		cnd->cnd_vp = NULL;
237		cnd->cnd_cn = NULL;
238
239		/* Remove this device from available mask. */
240		for (i = 0; i < CNDEVTAB_SIZE; i++)
241			if (cnd == &cn_devtab[i]) {
242				cons_avail_mask &= ~(1 << i);
243				break;
244			}
245#if 0
246		/*
247		 * XXX
248		 * syscons gets really confused if console resources are
249		 * freed after the system has initialized.
250		 */
251		if (cn->cn_term != NULL)
252			cn->cn_term(cn);
253#endif
254		return;
255	}
256}
257
258void
259cnselect(struct consdev *cn)
260{
261	struct cn_device *cnd;
262
263	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
264		if (cnd->cnd_cn != cn)
265			continue;
266		if (cnd == STAILQ_FIRST(&cn_devlist))
267			return;
268		STAILQ_REMOVE(&cn_devlist, cnd, cn_device, cnd_next);
269		STAILQ_INSERT_HEAD(&cn_devlist, cnd, cnd_next);
270		return;
271	}
272}
273
274void
275cnavailable(struct consdev *cn, int available)
276{
277	int i;
278
279	for (i = 0; i < CNDEVTAB_SIZE; i++) {
280		if (cn_devtab[i].cnd_cn == cn)
281			break;
282	}
283	if (available) {
284		if (i < CNDEVTAB_SIZE)
285			cons_avail_mask |= (1 << i);
286		cn->cn_flags &= ~CN_FLAG_NOAVAIL;
287	} else {
288		if (i < CNDEVTAB_SIZE)
289			cons_avail_mask &= ~(1 << i);
290		cn->cn_flags |= CN_FLAG_NOAVAIL;
291	}
292}
293
294int
295cnunavailable(void)
296{
297
298	return (cons_avail_mask == 0);
299}
300
301/*
302 * sysctl_kern_console() provides output parseable in conscontrol(1).
303 */
304static int
305sysctl_kern_console(SYSCTL_HANDLER_ARGS)
306{
307	struct cn_device *cnd;
308	struct consdev *cp, **list;
309	char *p;
310	int delete, error;
311	struct sbuf *sb;
312
313	sb = sbuf_new(NULL, NULL, CNDEVPATHMAX * 2, SBUF_AUTOEXTEND);
314	if (sb == NULL)
315		return (ENOMEM);
316	sbuf_clear(sb);
317	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
318		sbuf_printf(sb, "%s,", cnd->cnd_cn->cn_name);
319	sbuf_printf(sb, "/");
320	SET_FOREACH(list, cons_set) {
321		cp = *list;
322		if (cp->cn_name[0] != '\0')
323			sbuf_printf(sb, "%s,", cp->cn_name);
324	}
325	sbuf_finish(sb);
326	error = sysctl_handle_string(oidp, sbuf_data(sb), sbuf_len(sb), req);
327	if (error == 0 && req->newptr != NULL) {
328		p = sbuf_data(sb);
329		error = ENXIO;
330		delete = 0;
331		if (*p == '-') {
332			delete = 1;
333			p++;
334		}
335		SET_FOREACH(list, cons_set) {
336			cp = *list;
337			if (strcmp(p, cp->cn_name) != 0)
338				continue;
339			if (delete) {
340				cnremove(cp);
341				error = 0;
342			} else {
343				error = cnadd(cp);
344				if (error == 0)
345					cnselect(cp);
346			}
347			break;
348		}
349	}
350	sbuf_delete(sb);
351	return (error);
352}
353
354SYSCTL_PROC(_kern, OID_AUTO, console, CTLTYPE_STRING|CTLFLAG_RW,
355	0, 0, sysctl_kern_console, "A", "Console device control");
356
357/*
358 * User has changed the state of the console muting.
359 * This may require us to open or close the device in question.
360 */
361static int
362sysctl_kern_consmute(SYSCTL_HANDLER_ARGS)
363{
364	int error;
365	int ocn_mute;
366
367	ocn_mute = cn_mute;
368	error = sysctl_handle_int(oidp, &cn_mute, 0, req);
369	if (error != 0 || req->newptr == NULL)
370		return (error);
371	if (ocn_mute && !cn_mute && cn_is_open)
372		error = cnopen(NULL, openflag, 0, curthread);
373	else if (!ocn_mute && cn_mute && cn_is_open) {
374		error = cnclose(NULL, openflag, 0, curthread);
375		cn_is_open = 1;		/* XXX hack */
376	}
377	return (error);
378}
379
380SYSCTL_PROC(_kern, OID_AUTO, consmute, CTLTYPE_INT|CTLFLAG_RW,
381	0, sizeof(cn_mute), sysctl_kern_consmute, "I", "");
382
383static int
384cn_devopen(struct cn_device *cnd, struct thread *td, int forceopen)
385{
386	char path[CNDEVPATHMAX];
387	struct nameidata nd;
388	struct vnode *vp;
389	struct cdev *dev;
390	struct cdevsw *csw;
391	int error;
392
393	if ((vp = cnd->cnd_vp) != NULL) {
394		if (!forceopen && vp->v_type != VBAD) {
395			dev = vp->v_rdev;
396			csw = dev_refthread(dev);
397			if (csw == NULL)
398				return (ENXIO);
399			error = (*csw->d_open)(dev, openflag, 0, td);
400			dev_relthread(dev);
401			return (error);
402		}
403		cnd->cnd_vp = NULL;
404		vn_close(vp, openflag, td->td_ucred, td);
405	}
406	snprintf(path, sizeof(path), "/dev/%s", cnd->cnd_cn->cn_name);
407	NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, path, td);
408	error = vn_open(&nd, &openflag, 0, NULL);
409	if (error == 0) {
410		NDFREE(&nd, NDF_ONLY_PNBUF);
411		VOP_UNLOCK(nd.ni_vp, 0);
412		if (nd.ni_vp->v_type == VCHR)
413			cnd->cnd_vp = nd.ni_vp;
414		else
415			vn_close(nd.ni_vp, openflag, td->td_ucred, td);
416	}
417	return (cnd->cnd_vp != NULL);
418}
419
420static int
421cnopen(struct cdev *dev, int flag, int mode, struct thread *td)
422{
423	struct cn_device *cnd;
424
425	openflag = flag | FWRITE;	/* XXX */
426	cn_is_open = 1;			/* console is logically open */
427	if (cn_mute)
428		return (0);
429	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next)
430		cn_devopen(cnd, td, 0);
431	return (0);
432}
433
434static int
435cnclose(struct cdev *dev, int flag, int mode, struct thread *td)
436{
437	struct cn_device *cnd;
438	struct vnode *vp;
439
440	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
441		if ((vp = cnd->cnd_vp) == NULL)
442			continue;
443		cnd->cnd_vp = NULL;
444		vn_close(vp, openflag, td->td_ucred, td);
445	}
446	cn_is_open = 0;
447	return (0);
448}
449
450static int
451cnread(struct cdev *dev, struct uio *uio, int flag)
452{
453	struct cn_device *cnd;
454	struct cdevsw *csw;
455	int error;
456
457	cnd = STAILQ_FIRST(&cn_devlist);
458	if (cn_mute || CND_INVALID(cnd, curthread))
459		return (0);
460	dev = cnd->cnd_vp->v_rdev;
461	csw = dev_refthread(dev);
462	if (csw == NULL)
463		return (ENXIO);
464	error = (csw->d_read)(dev, uio, flag);
465	dev_relthread(dev);
466	return (error);
467}
468
469static int
470cnwrite(struct cdev *dev, struct uio *uio, int flag)
471{
472	struct cn_device *cnd;
473	struct cdevsw *csw;
474	int error;
475
476	cnd = STAILQ_FIRST(&cn_devlist);
477	if (cn_mute || CND_INVALID(cnd, curthread))
478		goto done;
479	if (constty)
480		dev = constty->t_dev;
481	else
482		dev = cnd->cnd_vp->v_rdev;
483	if (dev != NULL) {
484		log_console(uio);
485		csw = dev_refthread(dev);
486		if (csw == NULL)
487			return (ENXIO);
488		error = (csw->d_write)(dev, uio, flag);
489		dev_relthread(dev);
490		return (error);
491	}
492done:
493	uio->uio_resid = 0; /* dump the data */
494	return (0);
495}
496
497static int
498cnioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td)
499{
500	struct cn_device *cnd;
501	struct cdevsw *csw;
502	int error;
503
504	cnd = STAILQ_FIRST(&cn_devlist);
505	if (cn_mute || CND_INVALID(cnd, td))
506		return (0);
507	/*
508	 * Superuser can always use this to wrest control of console
509	 * output from the "virtual" console.
510	 */
511	if (cmd == TIOCCONS && constty) {
512		error = priv_check(td, PRIV_TTY_CONSOLE);
513		if (error)
514			return (error);
515		constty = NULL;
516		return (0);
517	}
518	dev = cnd->cnd_vp->v_rdev;
519	if (dev == NULL)
520		return (0);	/* XXX : ENOTTY ? */
521	csw = dev_refthread(dev);
522	if (csw == NULL)
523		return (ENXIO);
524	error = (csw->d_ioctl)(dev, cmd, data, flag, td);
525	dev_relthread(dev);
526	return (error);
527}
528
529/*
530 * XXX
531 * poll/kqfilter do not appear to be correct
532 */
533static int
534cnpoll(struct cdev *dev, int events, struct thread *td)
535{
536	struct cn_device *cnd;
537	struct cdevsw *csw;
538	int error;
539
540	cnd = STAILQ_FIRST(&cn_devlist);
541	if (cn_mute || CND_INVALID(cnd, td))
542		return (0);
543	dev = cnd->cnd_vp->v_rdev;
544	if (dev == NULL)
545		return (0);
546	csw = dev_refthread(dev);
547	if (csw == NULL)
548		return (ENXIO);
549	error = (csw->d_poll)(dev, events, td);
550	dev_relthread(dev);
551	return (error);
552}
553
554static int
555cnkqfilter(struct cdev *dev, struct knote *kn)
556{
557	struct cn_device *cnd;
558	struct cdevsw *csw;
559	int error;
560
561	cnd = STAILQ_FIRST(&cn_devlist);
562	if (cn_mute || CND_INVALID(cnd, curthread))
563		return (EINVAL);
564	dev = cnd->cnd_vp->v_rdev;
565	if (dev == NULL)
566		return (ENXIO);
567	csw = dev_refthread(dev);
568	if (csw == NULL)
569		return (ENXIO);
570	error = (csw->d_kqfilter)(dev, kn);
571	dev_relthread(dev);
572	return (error);
573}
574
575/*
576 * Low level console routines.
577 */
578int
579cngetc(void)
580{
581	int c;
582
583	if (cn_mute)
584		return (-1);
585	while ((c = cncheckc()) == -1)
586		;
587	if (c == '\r')
588		c = '\n';		/* console input is always ICRNL */
589	return (c);
590}
591
592int
593cncheckc(void)
594{
595	struct cn_device *cnd;
596	struct consdev *cn;
597	int c;
598
599	if (cn_mute)
600		return (-1);
601	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
602		cn = cnd->cnd_cn;
603		if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) {
604			if (cn->cn_checkc != NULL)
605				c = cn->cn_checkc(cn);
606			else
607				c = cn->cn_getc(cn);
608			if (c != -1) {
609				return (c);
610			}
611		}
612	}
613	return (-1);
614}
615
616void
617cnputc(int c)
618{
619	struct cn_device *cnd;
620	struct consdev *cn;
621	char *cp;
622
623	if (cn_mute || c == '\0')
624		return;
625	STAILQ_FOREACH(cnd, &cn_devlist, cnd_next) {
626		cn = cnd->cnd_cn;
627		if (!kdb_active || !(cn->cn_flags & CN_FLAG_NODEBUG)) {
628			if (c == '\n')
629				cn->cn_putc(cn, '\r');
630			cn->cn_putc(cn, c);
631		}
632	}
633	if (console_pausing && c == '\n' && !kdb_active) {
634		for (cp = console_pausestr; *cp != '\0'; cp++)
635			cnputc(*cp);
636		if (cngetc() == '.')
637			console_pausing = 0;
638		cnputc('\r');
639		for (cp = console_pausestr; *cp != '\0'; cp++)
640			cnputc(' ');
641		cnputc('\r');
642	}
643}
644
645void
646cnputs(char *p)
647{
648	int c;
649	int unlock_reqd = 0;
650
651	if (use_cnputs_mtx) {
652		mtx_lock_spin(&cnputs_mtx);
653		unlock_reqd = 1;
654	}
655
656	while ((c = *p++) != '\0')
657		cnputc(c);
658
659	if (unlock_reqd)
660		mtx_unlock_spin(&cnputs_mtx);
661}
662
663static int consmsgbuf_size = 8192;
664SYSCTL_INT(_kern, OID_AUTO, consmsgbuf_size, CTLFLAG_RW, &consmsgbuf_size, 0,
665    "");
666
667/*
668 * Redirect console output to a tty.
669 */
670void
671constty_set(struct tty *tp)
672{
673	int size;
674
675	KASSERT(tp != NULL, ("constty_set: NULL tp"));
676	if (consbuf == NULL) {
677		size = consmsgbuf_size;
678		consbuf = malloc(size, M_TTYCONS, M_WAITOK);
679		msgbuf_init(&consmsgbuf, consbuf, size);
680		callout_init(&conscallout, 0);
681	}
682	constty = tp;
683	constty_timeout(NULL);
684}
685
686/*
687 * Disable console redirection to a tty.
688 */
689void
690constty_clear(void)
691{
692	int c;
693
694	constty = NULL;
695	if (consbuf == NULL)
696		return;
697	callout_stop(&conscallout);
698	while ((c = msgbuf_getchar(&consmsgbuf)) != -1)
699		cnputc(c);
700	free(consbuf, M_TTYCONS);
701	consbuf = NULL;
702}
703
704/* Times per second to check for pending console tty messages. */
705static int constty_wakeups_per_second = 5;
706SYSCTL_INT(_kern, OID_AUTO, constty_wakeups_per_second, CTLFLAG_RW,
707    &constty_wakeups_per_second, 0, "");
708
709static void
710constty_timeout(void *arg)
711{
712	int c;
713
714	while (constty != NULL && (c = msgbuf_getchar(&consmsgbuf)) != -1) {
715		if (tputchar(c, constty) < 0)
716			constty = NULL;
717	}
718	if (constty != NULL) {
719		callout_reset(&conscallout, hz / constty_wakeups_per_second,
720		    constty_timeout, NULL);
721	} else {
722		/* Deallocate the constty buffer memory. */
723		constty_clear();
724	}
725}
726
727static void
728cn_drvinit(void *unused)
729{
730
731	make_dev(&cn_cdevsw, 0, UID_ROOT, GID_WHEEL, 0600, "console");
732
733	mtx_init(&cnputs_mtx, "cnputs_mtx", NULL, MTX_SPIN | MTX_NOWITNESS);
734	use_cnputs_mtx = 1;
735}
736
737SYSINIT(cndev, SI_SUB_DRIVERS, SI_ORDER_MIDDLE, cn_drvinit, NULL);
738
739/*
740 * Sysbeep(), if we have hardware for it
741 */
742
743#ifdef HAS_TIMER_SPKR
744
745static int beeping;
746
747static void
748sysbeepstop(void *chan)
749{
750
751	timer_spkr_release();
752	beeping = 0;
753}
754
755int
756sysbeep(int pitch, int period)
757{
758
759	if (timer_spkr_acquire()) {
760		if (!beeping) {
761			/* Something else owns it. */
762			return (EBUSY);
763		}
764	}
765	timer_spkr_setfreq(pitch);
766	if (!beeping) {
767		beeping = period;
768		timeout(sysbeepstop, (void *)NULL, period);
769	}
770	return (0);
771}
772
773#else
774
775/*
776 * No hardware, no sound
777 */
778
779int
780sysbeep(int pitch __unused, int period __unused)
781{
782
783	return (ENODEV);
784}
785
786#endif
787
788