audio_ddi.c revision 11936:54dc8a89ba0d
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 * Copyright (C) 4Front Technologies 1996-2008.
23 *
24 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
25 * Use is subject to license terms.
26 */
27
28#include <sys/types.h>
29#include <sys/sysmacros.h>
30#include <sys/stropts.h>
31#include <sys/strsun.h>
32#include <sys/list.h>
33#include <sys/mkdev.h>
34#include <sys/conf.h>
35#include <sys/note.h>
36#include <sys/atomic.h>
37#include <sys/ddi.h>
38#include <sys/sunddi.h>
39
40#include "audio_impl.h"
41
42/*
43 * Audio DDI glue implementation.
44 */
45
46/*
47 * The audio module is itself a pseudo driver, as it contains the
48 * logic to support un-associated nodes.  (Think generic /dev/mixer
49 * and /dev/sndstat used by OSS.)
50 */
51static int
52audio_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
53{
54	audio_dev_t	*adev;
55
56	/* pseudo devices don't need S/R support */
57	if ((cmd != DDI_ATTACH) || (dip == NULL)) {
58		return (DDI_FAILURE);
59	}
60
61	if (ddi_get_instance(dip) != 0) {
62		return (DDI_FAILURE);
63	}
64
65	/* this can't fail */
66	adev = audio_dev_alloc(dip, 0);
67	adev->d_flags = DEV_SNDSTAT_CAP;
68	audio_dev_set_description(adev, "Audio Common Code");
69	audio_dev_set_version(adev, "pseudo");
70	ddi_set_driver_private(dip, adev);
71
72	/* look up our properties! */
73
74	if (audio_dev_register(adev) != NULL) {
75		audio_dev_free(adev);
76		return (DDI_FAILURE);
77	}
78
79	ddi_report_dev(dip);
80
81	return (DDI_SUCCESS);
82}
83
84static int
85audio_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
86{
87	audio_dev_t	*adev;
88
89	/* pseudo devices don't need S/R support */
90	if (cmd != DDI_DETACH) {
91		return (DDI_FAILURE);
92	}
93
94	if (dip == NULL) {
95		return (DDI_FAILURE);
96	}
97
98	if ((adev = ddi_get_driver_private(dip)) == NULL) {
99		return (DDI_FAILURE);
100	}
101
102	if (audio_dev_unregister(adev) != DDI_SUCCESS) {
103		return (DDI_FAILURE);
104	}
105
106	audio_dev_free(adev);
107
108	return (DDI_SUCCESS);
109}
110
111static int
112audio_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **resp)
113{
114	dip = NULL;
115
116	if (getminor((dev_t)arg) & AUDIO_MN_CLONE_MASK) {
117		audio_client_t *c;
118		c = auclnt_hold_by_devt((dev_t)arg);
119		if (c != NULL) {
120			dip = c->c_dev->d_dip;
121			auclnt_release(c);
122		}
123	} else {
124		audio_dev_t	*adev;
125		if ((adev = auimpl_dev_hold_by_devt((dev_t)arg)) != NULL) {
126			dip = adev->d_dip;
127			auimpl_dev_release(adev);
128		}
129	}
130
131	if (dip == NULL) {
132		return (DDI_FAILURE);
133	}
134
135	switch (cmd) {
136	case DDI_INFO_DEVT2DEVINFO:
137		*resp = dip;
138		break;
139	case DDI_INFO_DEVT2INSTANCE:
140		*resp = (void *)(uintptr_t)ddi_get_instance(dip);
141		break;
142	default:
143		*resp = NULL;
144		return (DDI_FAILURE);
145	}
146	return (DDI_SUCCESS);
147}
148
149static int
150audio_open(dev_t *devp, int oflag, int otyp, cred_t *credp)
151{
152	int			rv;
153	audio_client_t		*c;
154
155	if (otyp == OTYP_BLK) {
156		return (ENXIO);
157	}
158
159	if ((c = auimpl_client_create(*devp)) == NULL) {
160		audio_dev_warn(NULL, "client create failed");
161		return (ENXIO);
162	}
163
164	c->c_omode = oflag;
165	c->c_pid = ddi_get_pid();
166	c->c_cred = credp;
167
168	/*
169	 * Call client/personality specific open handler.  Note that
170	 * we "insist" that there is an open.  The personality layer
171	 * will initialize/allocate any engines required.
172	 *
173	 * Hmm... do we need to pass in the cred?
174	 */
175	if ((rv = c->c_open(c, oflag)) != 0) {
176		audio_dev_warn(c->c_dev, "open failed (rv %d)", rv);
177		auimpl_client_destroy(c);
178		return (rv);
179	}
180
181	/* we do device cloning! */
182	*devp = makedevice(c->c_major, c->c_minor);
183
184	/* now we can receive upcalls */
185	auimpl_client_activate(c);
186
187	atomic_inc_uint(&c->c_dev->d_serial);
188
189	return (0);
190}
191
192static int
193audio_stropen(queue_t *rq, dev_t *devp, int oflag, int sflag, cred_t *credp)
194{
195	int			rv;
196	audio_client_t		*c;
197
198	if (sflag != 0) {
199		/* no direct clone or module opens */
200		return (ENXIO);
201	}
202
203	/*
204	 * Make sure its a STREAMS personality - only legacy Sun API uses
205	 * STREAMS.
206	 */
207	switch (AUDIO_MN_TYPE_MASK & getminor(*devp)) {
208	case AUDIO_MINOR_DEVAUDIO:
209	case AUDIO_MINOR_DEVAUDIOCTL:
210		break;
211	default:
212		return (ENOSTR);
213	}
214
215	if ((c = auimpl_client_create(*devp)) == NULL) {
216		audio_dev_warn(NULL, "client create failed");
217		return (ENXIO);
218	}
219
220	rq->q_ptr = WR(rq)->q_ptr = c;
221	c->c_omode = oflag;
222	c->c_pid = ddi_get_pid();
223	c->c_cred = credp;
224	c->c_rq = rq;
225	c->c_wq = WR(rq);
226
227	/*
228	 * Call client/personality specific open handler.  Note that
229	 * we "insist" that there is an open.  The personality layer
230	 * will initialize/allocate any engines required.
231	 *
232	 * Hmm... do we need to pass in the cred?
233	 */
234	if ((rv = c->c_open(c, oflag)) != 0) {
235		audio_dev_warn(c->c_dev, "open failed (rv %d)", rv);
236		auimpl_client_destroy(c);
237		return (rv);
238	}
239
240	/* we do device cloning! */
241	*devp = makedevice(c->c_major, c->c_minor);
242
243	qprocson(rq);
244
245	/* now we can receive upcalls */
246	auimpl_client_activate(c);
247
248	atomic_inc_uint(&c->c_dev->d_serial);
249
250	return (0);
251}
252
253static int
254audio_strclose(queue_t *rq, int flag, cred_t *credp)
255{
256	audio_client_t	*c;
257	audio_dev_t	*d;
258	int		rv;
259
260	_NOTE(ARGUNUSED(flag));
261	_NOTE(ARGUNUSED(credp));
262
263	if ((c = rq->q_ptr) == NULL) {
264		return (ENXIO);
265	}
266	if (ddi_can_receive_sig() || (ddi_get_pid() == 0)) {
267		rv = auclnt_drain(c);
268	}
269
270	/* make sure we won't get any upcalls */
271	auimpl_client_deactivate(c);
272
273	/*
274	 * Pick up any data sitting around in input buffers.  This
275	 * avoids leaving record data stuck in queues.
276	 */
277	if (c->c_istream.s_engine != NULL)
278		auimpl_input_callback(c->c_istream.s_engine);
279
280	/* get a local hold on the device */
281	d = c->c_dev;
282	auimpl_dev_hold(c->c_dev);
283
284	/* Turn off queue processing... */
285	qprocsoff(rq);
286
287	/* Call personality specific close handler */
288	c->c_close(c);
289
290	auimpl_client_destroy(c);
291
292	/* notify peers that a change has occurred */
293	atomic_inc_uint(&d->d_serial);
294
295	/* now we can drop the release we had on the device */
296	auimpl_dev_release(d);
297
298	return (rv);
299}
300
301static int
302audio_close(dev_t dev, int flag, int otyp, cred_t *credp)
303{
304	audio_client_t	*c;
305	audio_dev_t	*d;
306
307	_NOTE(ARGUNUSED(flag));
308	_NOTE(ARGUNUSED(credp));
309	_NOTE(ARGUNUSED(otyp));
310
311	if ((c = auclnt_hold_by_devt(dev)) == NULL) {
312		audio_dev_warn(NULL, "close on bogus devt %x,%x",
313		    getmajor(dev), getminor(dev));
314		return (ENXIO);
315	}
316
317	/* we don't want any upcalls anymore */
318	auimpl_client_deactivate(c);
319
320	/*
321	 * Pick up any data sitting around in input buffers.  This
322	 * avoids leaving record data stuck in queues.
323	 */
324	if (c->c_istream.s_engine != NULL)
325		auimpl_input_callback(c->c_istream.s_engine);
326
327	/* get a local hold on the device */
328	d = c->c_dev;
329	auimpl_dev_hold(c->c_dev);
330
331	/*
332	 * NB: This must be done before c->c_close, since it calls
333	 * auclnt_close which will block waiting for the refence count
334	 * to drop to zero.
335	 */
336	auclnt_release(c);
337
338	/* Call personality specific close handler */
339	c->c_close(c);
340
341	auimpl_client_destroy(c);
342
343	/* notify peers that a change has occurred */
344	atomic_inc_uint(&d->d_serial);
345
346	/* now we can drop the release we had on the device */
347	auimpl_dev_release(d);
348
349	return (0);
350}
351
352static int
353audio_write(dev_t dev, struct uio *uio, cred_t *credp)
354{
355	audio_client_t *c;
356	int rv;
357
358	if ((c = auclnt_hold_by_devt(dev)) == NULL) {
359		return (ENXIO);
360	}
361	if ((rv = auclnt_serialize(c)) == 0) {
362		rv = (c->c_write == NULL) ? ENXIO : c->c_write(c, uio, credp);
363		auclnt_unserialize(c);
364	}
365	auclnt_release(c);
366
367	return (rv);
368}
369
370static int
371audio_read(dev_t dev, struct uio *uio, cred_t *credp)
372{
373	audio_client_t *c;
374	int rv;
375
376	if ((c = auclnt_hold_by_devt(dev)) == NULL) {
377		return (ENXIO);
378	}
379	if ((rv = auclnt_serialize(c)) == 0) {
380		rv = (c->c_read == NULL) ? ENXIO : c->c_read(c, uio, credp);
381		auclnt_unserialize(c);
382	}
383	auclnt_release(c);
384
385	return (rv);
386}
387
388static int
389audio_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp,
390    int *rvalp)
391{
392	audio_client_t *c;
393	int rv;
394
395	if ((c = auclnt_hold_by_devt(dev)) == NULL) {
396		return (ENXIO);
397	}
398	rv = (c->c_ioctl == NULL) ? ENXIO : c->c_ioctl(c, cmd, arg, mode,
399	    credp, rvalp);
400	auclnt_release(c);
401
402	return (rv);
403}
404
405static int
406audio_chpoll(dev_t dev, short events, int anyyet, short *reventsp,
407    struct pollhead **phpp)
408{
409	audio_client_t *c;
410	int rv;
411
412	if ((c = auclnt_hold_by_devt(dev)) == NULL) {
413		return (ENXIO);
414	}
415	rv = (c->c_chpoll == NULL) ?
416	    ENXIO :
417	    c->c_chpoll(c, events, anyyet, reventsp, phpp);
418	auclnt_release(c);
419
420	return (rv);
421}
422
423static int
424audio_wput(queue_t *wq, mblk_t *mp)
425{
426	audio_client_t	*c;
427
428	c = wq->q_ptr;
429	if (c->c_wput) {
430		c->c_wput(c, mp);
431	} else {
432		freemsg(mp);
433	}
434	return (0);
435}
436
437static int
438audio_wsrv(queue_t *wq)
439{
440	audio_client_t	*c;
441
442	c = wq->q_ptr;
443	if (c->c_wsrv) {
444		c->c_wsrv(c);
445	} else {
446		flushq(wq, FLUSHALL);
447	}
448	return (0);
449}
450
451static int
452audio_rsrv(queue_t *rq)
453{
454	audio_client_t	*c;
455
456	c = rq->q_ptr;
457	if (c->c_rsrv) {
458		c->c_rsrv(c);
459	} else {
460		flushq(rq, FLUSHALL);
461	}
462	return (0);
463}
464
465
466static struct dev_ops audio_dev_ops = {
467	DEVO_REV,		/* rev */
468	0,			/* refcnt */
469	NULL,			/* getinfo */
470	nulldev,		/* identify */
471	nulldev,		/* probe */
472	audio_attach,		/* attach */
473	audio_detach,		/* detach */
474	nodev,			/* reset */
475	NULL,			/* cb_ops */
476	NULL,			/* bus_ops */
477	NULL,			/* power */
478};
479
480static struct modldrv modldrv = {
481	&mod_driverops,
482	"Audio Framework",
483	&audio_dev_ops,
484};
485
486static struct modlinkage modlinkage = {
487	MODREV_1,			/* MODREV_1 indicated by manual */
488	&modldrv,
489	NULL
490};
491
492struct audio_ops_helper {
493	struct cb_ops		cbops;	/* NB: must be first */
494	struct streamtab	strtab;
495	struct qinit		rqinit;
496	struct qinit		wqinit;
497	struct module_info	minfo;
498	char			name[MODMAXNAMELEN+1];
499};
500
501void
502audio_init_ops(struct dev_ops *devops, const char *name)
503{
504	struct audio_ops_helper	*helper;
505
506	helper = kmem_zalloc(sizeof (*helper), KM_SLEEP);
507
508	(void) strlcpy(helper->name, name, sizeof (helper->name));
509
510	helper->minfo.mi_idnum = 0;	/* only for strlog(1M) */
511	helper->minfo.mi_idname = helper->name;
512	helper->minfo.mi_minpsz = 0;
513	helper->minfo.mi_maxpsz = 8192;
514	helper->minfo.mi_hiwat = 65536;
515	helper->minfo.mi_lowat = 32768;
516
517	helper->wqinit.qi_putp = audio_wput;
518	helper->wqinit.qi_srvp = audio_wsrv;
519	helper->wqinit.qi_qopen = NULL;
520	helper->wqinit.qi_qclose = NULL;
521	helper->wqinit.qi_qadmin = NULL;
522	helper->wqinit.qi_minfo = &helper->minfo;
523	helper->wqinit.qi_mstat = NULL;
524
525	helper->rqinit.qi_putp = putq;
526	helper->rqinit.qi_srvp = audio_rsrv;
527	helper->rqinit.qi_qopen = audio_stropen;
528	helper->rqinit.qi_qclose = audio_strclose;
529	helper->rqinit.qi_qadmin = NULL;
530	helper->rqinit.qi_minfo = &helper->minfo;
531	helper->rqinit.qi_mstat = NULL;
532
533	helper->strtab.st_rdinit = &helper->rqinit;
534	helper->strtab.st_wrinit = &helper->wqinit;
535	helper->strtab.st_muxrinit = NULL;
536	helper->strtab.st_muxwinit = NULL;
537
538	helper->cbops.cb_open = audio_open;
539	helper->cbops.cb_close = audio_close;
540	helper->cbops.cb_strategy = nodev;
541	helper->cbops.cb_print = nodev;
542	helper->cbops.cb_dump = nodev;
543	helper->cbops.cb_read = audio_read;
544	helper->cbops.cb_write = audio_write;
545	helper->cbops.cb_ioctl = audio_ioctl;
546	helper->cbops.cb_devmap = nodev;
547	helper->cbops.cb_mmap = nodev;
548	helper->cbops.cb_segmap = nodev;
549	helper->cbops.cb_chpoll = audio_chpoll;
550	helper->cbops.cb_prop_op = ddi_prop_op;
551	helper->cbops.cb_str = &helper->strtab;
552	helper->cbops.cb_flag = D_MP | D_64BIT;
553	helper->cbops.cb_rev = CB_REV;
554	helper->cbops.cb_aread = nodev;
555	helper->cbops.cb_awrite = nodev;
556
557	devops->devo_cb_ops = &helper->cbops;
558	devops->devo_getinfo = audio_getinfo;
559}
560
561void
562audio_fini_ops(struct dev_ops *devops)
563{
564	kmem_free(devops->devo_cb_ops, sizeof (struct audio_ops_helper));
565	devops->devo_cb_ops = NULL;
566	devops->devo_getinfo = NULL;
567}
568
569void
570auimpl_dev_vwarn(audio_dev_t *dev, const char *fmt, va_list va)
571{
572	char	buf[256];
573
574	if (dev != NULL) {
575		(void) snprintf(buf, sizeof (buf), "%s#%d: %s",
576		    ddi_driver_name(dev->d_dip), ddi_get_instance(dev->d_dip),
577		    fmt);
578	} else {
579		(void) snprintf(buf, sizeof (buf), "audio: %s", fmt);
580	}
581
582	vcmn_err(CE_WARN, buf, va);
583}
584
585
586void
587audio_dev_warn(audio_dev_t *dev, const char *fmt, ...)
588{
589	va_list	va;
590
591	va_start(va, fmt);
592	auimpl_dev_vwarn(dev, fmt, va);
593	va_end(va);
594}
595
596/*
597 * _init, _info, and _fini DDI glue.
598 */
599int
600_init(void)
601{
602	int	rv;
603
604	auimpl_client_init();
605	auimpl_dev_init();
606	auimpl_sun_init();
607	auimpl_oss_init();
608
609	audio_init_ops(&audio_dev_ops, "audio");
610
611	if ((rv = mod_install(&modlinkage)) != 0) {
612		audio_fini_ops(&audio_dev_ops);
613		auimpl_dev_fini();
614		auimpl_client_fini();
615	}
616	return (rv);
617}
618
619int
620_info(struct modinfo *modinfop)
621{
622	return (mod_info(&modlinkage, modinfop));
623}
624
625int
626_fini(void)
627{
628	int rv;
629
630	if ((rv = mod_remove(&modlinkage)) != 0)
631		return (rv);
632
633	auimpl_dev_fini();
634	auimpl_client_fini();
635
636	return (rv);
637}
638