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
audio_attach(dev_info_t * dip,ddi_attach_cmd_t cmd)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) != NULL) {
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
audio_detach(dev_info_t * dip,ddi_detach_cmd_t cmd)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
audio_getinfo(dev_info_t * dip,ddi_info_cmd_t cmd,void * arg,void ** resp)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
audio_open(dev_t * devp,int oflag,int otyp,cred_t * credp)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
audio_stropen(queue_t * rq,dev_t * devp,int oflag,int sflag,cred_t * credp)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
audio_strclose(queue_t * rq,int flag,cred_t * credp)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
audio_close(dev_t dev,int flag,int otyp,cred_t * credp)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
audio_write(dev_t dev,struct uio * uio,cred_t * credp)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
audio_read(dev_t dev,struct uio * uio,cred_t * credp)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
audio_ioctl(dev_t dev,int cmd,intptr_t arg,int mode,cred_t * credp,int * rvalp)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
audio_chpoll(dev_t dev,short events,int anyyet,short * reventsp,struct pollhead ** phpp)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
audio_wput(queue_t * wq,mblk_t * mp)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
audio_wsrv(queue_t * wq)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
audio_rsrv(queue_t * rq)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
audio_init_ops(struct dev_ops * devops,const char * name)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
audio_fini_ops(struct dev_ops * devops)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
auimpl_dev_vwarn(audio_dev_t * dev,const char * fmt,va_list va)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
audio_dev_warn(audio_dev_t * dev,const char * fmt,...)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
_init(void)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
_info(struct modinfo * modinfop)620 _info(struct modinfo *modinfop)
621 {
622 return (mod_info(&modlinkage, modinfop));
623 }
624
625 int
_fini(void)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