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