xref: /illumos-gate/usr/src/uts/common/io/audio/impl/audio_ddi.c (revision c83793e8beca1f638b19f9fba9570ee705f7a494)
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  */
51 static int
52 audio_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) != DDI_SUCCESS) {
75 		audio_dev_free(adev);
76 		return (DDI_FAILURE);
77 	}
78 
79 	ddi_report_dev(dip);
80 
81 	return (DDI_SUCCESS);
82 }
83 
84 static int
85 audio_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 
111 static int
112 audio_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 
149 static int
150 audio_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 
192 static int
193 audio_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 
253 static int
254 audio_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 
301 static int
302 audio_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 
352 static int
353 audio_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 
370 static int
371 audio_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 
388 static int
389 audio_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 
405 static int
406 audio_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 
423 static int
424 audio_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 
437 static int
438 audio_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 
451 static int
452 audio_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 
466 static 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 
480 static struct modldrv modldrv = {
481 	&mod_driverops,
482 	"Audio Framework",
483 	&audio_dev_ops,
484 };
485 
486 static struct modlinkage modlinkage = {
487 	MODREV_1,			/* MODREV_1 indicated by manual */
488 	&modldrv,
489 	NULL
490 };
491 
492 struct 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 
501 void
502 audio_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 
561 void
562 audio_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 
569 void
570 auimpl_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 
586 void
587 audio_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  */
599 int
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 
619 int
620 _info(struct modinfo *modinfop)
621 {
622 	return (mod_info(&modlinkage, modinfop));
623 }
624 
625 int
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