usb_ah.c revision 9484:fbd5ddc28e96
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 2009 Sun Microsystems, Inc.  All rights reserved.
23 * Use is subject to license terms.
24 */
25
26
27/*
28 * USB audio hid streams module - processes hid data
29 * from HID driver and converts to a format that usb_ac
30 * understands. The stack looks like this :
31 *	hid --> usb_ah --> usb_ac --> audio framework
32 * usb_ac just acts as a passthrough layer for the converted data.
33 *
34 * During open, usb_ah gets the parser handle from hid and gets the
35 * hardware information passed as report descriptor. Then it finds out
36 * the relevant usages and stores the bitmap and other information in
37 * internal data structure. When a button is pressed to. say,
38 * increase/decrease the volume, a report is generated and hid sends
39 * that data up through the streams. usb_ah, upon getting this
40 * information and with the prior knowledge about the bitmap for each
41 * button, calculates the value and sends up to usb_ac.  usb_ac in
42 * turn sends a command down to speaker to increase the volume of the
43 * speaker that is managed by usb_ac.
44 */
45#include <sys/usb/usba.h>
46#include <sys/usb/clients/hid/hid.h>
47#include <sys/usb/clients/hidparser/hidparser.h>
48#include <sys/stropts.h>
49#include <sys/strsun.h>
50
51#include <sys/audio.h>
52#include <sys/audio/audio_support.h>
53#include <sys/mixer.h>
54#include <sys/audio/audio_mixer.h>
55
56#include <sys/usb/clients/audio/usb_audio.h>
57#include <sys/usb/clients/audio/usb_mixer.h>
58#include <sys/usb/clients/audio/usb_ah/usb_ah.h>
59
60/* debugging information */
61uint_t			usb_ah_errmask = (uint_t)PRINT_MASK_ALL;
62uint_t			usb_ah_errlevel = USB_LOG_L4;
63static usb_log_handle_t	usb_ah_log_handle;
64
65/*
66 * Internal Function Prototypes
67 */
68static void	usb_ah_mctl_receive(queue_t *, mblk_t *);
69static mblk_t	*usb_ah_cp_mblk(mblk_t *);
70static void	usb_ah_timeout(void *);
71static void	usb_ah_repeat_send(usb_ah_state_t *, usb_ah_button_descr_t *,
72			struct iocblk, char *, int);
73static void	usb_ah_cancel_timeout(usb_ah_state_t *);
74static void	usb_ah_check_usage_send_data(usb_ah_state_t *, mblk_t *);
75static int	usb_ah_get_cooked_rd(usb_ah_state_t *);
76static mblk_t	*usb_ah_mk_mctl(struct iocblk, void *, size_t);
77
78/* stream qinit functions defined here */
79static int	usb_ah_open(queue_t *, dev_t *, int, int, cred_t *);
80static int	usb_ah_close(queue_t *, int, cred_t *);
81static int	usb_ah_rput(queue_t *, mblk_t *);
82
83/*
84 * Global Variables
85 */
86int usb_ah_rpt_tick;
87
88static struct streamtab usb_ah_info;
89static struct fmodsw fsw = {
90	"usb_ah",
91	&usb_ah_info,
92	D_NEW | D_MP | D_MTPERMOD
93};
94
95/*
96 * Module linkage information for the kernel.
97 */
98extern struct mod_ops mod_strmodops;
99
100static struct modlstrmod modlstrmod = {
101	&mod_strmodops,
102	"USB audio hid streams",
103	&fsw
104};
105
106static struct modlinkage modlinkage = {
107	MODREV_1,
108	(void *)&modlstrmod,
109	NULL
110};
111
112/*
113 * Warlock is not aware of the automatic locking mechanisms for
114 * streams modules.
115 * Since warlock is not aware of the streams perimeters, these notes
116 * have been added.
117 */
118_NOTE(SCHEME_PROTECTS_DATA("unique per call", iocblk))
119_NOTE(SCHEME_PROTECTS_DATA("unique per call", datab))
120_NOTE(SCHEME_PROTECTS_DATA("unique per call", msgb))
121_NOTE(SCHEME_PROTECTS_DATA("unique per call", queue))
122
123/*
124 * Module qinit functions
125 */
126static struct module_info usb_ah_minfo = {
127	0,		/* module id number */
128	"usb_ah",	/* module name */
129	0,		/* min packet size accepted */
130	INFPSZ,		/* max packet size accepted */
131	2048,		/* hi-water mark */
132	128		/* lo-water mark */
133	};
134
135/* read side for key data and ioctl replies */
136static struct qinit usb_ah_rinit = {
137	usb_ah_rput,
138	NULL,		/* service not used */
139	usb_ah_open,
140	usb_ah_close,
141	NULL,
142	&usb_ah_minfo
143	};
144
145/* write side -- just pass everything down */
146static struct qinit usb_ah_winit = {
147	(int (*)(queue_t *, mblk_t *))putnext,
148	NULL,
149	usb_ah_open,
150	usb_ah_close,
151	NULL,
152	&usb_ah_minfo
153	};
154
155static struct streamtab usb_ah_info = {
156	&usb_ah_rinit,
157	&usb_ah_winit,
158	NULL,		/* for muxes */
159	NULL,		/* for muxes */
160};
161
162
163int
164_init()
165{
166	int rval = mod_install(&modlinkage);
167
168	if (rval == 0) {
169		usb_ah_rpt_tick = drv_usectohz(USB_AH_TIMEOUT);
170		usb_ah_log_handle = usb_alloc_log_hdl(NULL, "usb_ah",
171		    &usb_ah_errlevel, &usb_ah_errmask, NULL, 0);
172	}
173
174	return (rval);
175}
176
177
178int
179_fini()
180{
181	int rval = mod_remove(&modlinkage);
182
183	if (rval == 0) {
184		usb_free_log_hdl(usb_ah_log_handle);
185	}
186
187	return (rval);
188}
189
190
191int
192_info(struct modinfo *modinfop)
193{
194	return (mod_info(&modlinkage, modinfop));
195}
196
197
198/*
199 * usb_ah_open :
200 *	Open a usb audio hid device
201 */
202/* ARGSUSED */
203static int
204usb_ah_open(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *crp)
205{
206	usb_ah_state_t	*usb_ahd;
207	hidparser_packet_info_t hpack;
208	struct iocblk	mctlmsg;
209	mblk_t		*mctl_ptr;
210
211	if (q->q_ptr) {
212		USB_DPRINTF_L3(PRINT_MASK_OPEN, usb_ah_log_handle,
213		    "usb_ah_open already opened");
214
215		return (0); /* already opened */
216	}
217
218	if (sflag != MODOPEN) {
219		/* Only module open supported */
220		return (EINVAL);
221	}
222
223	usb_ahd = kmem_zalloc(sizeof (usb_ah_state_t), KM_SLEEP);
224
225	USB_DPRINTF_L3(PRINT_MASK_OPEN, usb_ah_log_handle,
226	    "usb_ah_state= 0x%p", (void *)usb_ahd);
227
228	mutex_init(&usb_ahd->usb_ah_mutex, NULL, MUTEX_DRIVER, NULL);
229
230	/*
231	 * Set up private data.
232	 */
233	usb_ahd->usb_ah_readq = q;
234	usb_ahd->usb_ah_writeq = WR(q);
235
236	/*
237	 * Set up queue pointers, so that the "put" procedure will accept
238	 * the reply to the "ioctl" message we send down.
239	 */
240	q->q_ptr = (caddr_t)usb_ahd;
241	WR(q)->q_ptr = (caddr_t)usb_ahd;
242
243	qprocson(q);
244
245	/* request hid report descriptor from HID */
246	mctlmsg.ioc_cmd = HID_GET_PARSER_HANDLE;
247	mctlmsg.ioc_count = 0;
248	mctl_ptr = usba_mk_mctl(mctlmsg, NULL, 0);
249	if (mctl_ptr == NULL) {
250		/* failure to allocate M_CTL message */
251		qprocsoff(q);
252		mutex_destroy(&usb_ahd->usb_ah_mutex);
253		kmem_free(usb_ahd, sizeof (*usb_ahd));
254
255		return (ENOMEM);
256	}
257
258	putnext(usb_ahd->usb_ah_writeq, mctl_ptr);
259
260	/*
261	 * Now that signal has been sent, wait for report descriptor.
262	 * Cleanup  if user signals in the mean time
263	 */
264	usb_ahd->usb_ah_flags |= USB_AH_QWAIT;
265	while (usb_ahd->usb_ah_flags & USB_AH_QWAIT) {
266
267		if (qwait_sig(q) == 0) {
268			usb_ahd->usb_ah_flags = 0;
269			qprocsoff(q);
270			mutex_destroy(&usb_ahd->usb_ah_mutex);
271			kmem_free(usb_ahd, sizeof (*usb_ahd));
272
273			return (EINTR);
274		}
275	}
276
277	if (usb_ahd->usb_ah_report_descr != NULL) {
278		hidparser_find_max_packet_size_from_report_descriptor(
279		    usb_ahd->usb_ah_report_descr, &hpack);
280
281		/* round up to the nearest byte */
282		usb_ahd->usb_ah_packet_size = (hpack.max_packet_size + 7) / 8;
283
284		if (hpack.report_id == HID_REPORT_ID_UNDEFINED) {
285			usb_ahd->usb_ah_uses_report_ids = 0;
286			usb_ahd->usb_ah_report_id = HID_REPORT_ID_UNDEFINED;
287		} else {
288			usb_ahd->usb_ah_uses_report_ids = 1;
289			usb_ahd->usb_ah_report_id = hpack.report_id;
290			/* add more more byte for report id */
291			usb_ahd->usb_ah_packet_size++;
292		}
293
294		if (usb_ah_get_cooked_rd(usb_ahd) != USB_SUCCESS) {
295			qprocsoff(q);
296			mutex_destroy(&usb_ahd->usb_ah_mutex);
297			kmem_free(usb_ahd, sizeof (*usb_ahd));
298
299			return (EIO);
300		}
301	} else {
302		USB_DPRINTF_L2(PRINT_MASK_OPEN, usb_ah_log_handle,
303		    "usb_ah: Invalid Report Descriptor Tree.");
304
305		qprocsoff(q);
306		mutex_destroy(&usb_ahd->usb_ah_mutex);
307		kmem_free(usb_ahd, sizeof (*usb_ahd));
308
309		return (EIO);
310	}
311
312	usb_ahd->usb_ah_flags |= USB_AH_OPEN;
313
314	return (0);
315}
316
317
318/*
319 * usb_ah_close :
320 *	Close a audio hid device
321 */
322/* ARGSUSED1 */
323static int
324usb_ah_close(register queue_t *q, int flag, cred_t *crp)
325{
326	usb_ah_state_t *usb_ahd = (usb_ah_state_t *)q->q_ptr;
327
328	mutex_enter(&usb_ahd->usb_ah_mutex);
329
330	/*
331	 * Since we're about to destroy our private data, turn off
332	 * our open flag first, so we don't accept any more input
333	 * and try to use that data.
334	 */
335	usb_ahd->usb_ah_flags = 0;
336	usb_ah_cancel_timeout(usb_ahd);
337
338	flushq(q, FLUSHALL);
339	flushq(WR(q), FLUSHALL);
340
341	mutex_exit(&usb_ahd->usb_ah_mutex);
342
343	qprocsoff(q);
344	q->q_ptr = NULL;
345	WR(q)->q_ptr = NULL;
346
347	mutex_destroy(&usb_ahd->usb_ah_mutex);
348	kmem_free(usb_ahd, sizeof (usb_ah_state_t));
349
350	return (0);
351}
352
353
354/*
355 * usb_ah_rput :
356 *	Put procedure for input from driver end of stream (read queue).
357 */
358static int
359usb_ah_rput(register queue_t *q, register mblk_t *mp)
360{
361	usb_ah_state_t		*usb_ahd;
362
363	usb_ahd = (usb_ah_state_t *)q->q_ptr;
364
365	if (usb_ahd == 0) {
366		freemsg(mp);	/* nobody's listening */
367
368		return (0);
369	}
370
371	switch (mp->b_datap->db_type) {
372
373	case M_DATA:
374		if (!(usb_ahd->usb_ah_flags & USB_AH_OPEN)) {
375			freemsg(mp);	/* not ready to listen */
376
377		} else if (MBLKL(mp) == usb_ahd->usb_ah_packet_size) {
378
379			/*
380			 * Process this report if the device doesn't have
381			 * multiple reports, or this is the one we support
382			 */
383			if ((usb_ahd->usb_ah_report_id ==
384			    HID_REPORT_ID_UNDEFINED) ||
385			    (usb_ahd->usb_ah_report_id == (int)*mp->b_rptr)) {
386				/* we now have a complete packet */
387				usb_ah_check_usage_send_data(usb_ahd, mp);
388			} else {
389				USB_DPRINTF_L2(PRINT_MASK_ALL,
390				    usb_ah_log_handle,
391				    "usb_ah_rput: skipping report with "
392				    "id= %d", *mp->b_rptr);
393
394				/* skip the reports we don't support */
395				freemsg(mp);
396			}
397		} else {
398			/* filter out spurious packets */
399			freemsg(mp);
400		}
401
402		break;
403
404	case M_CTL:
405		usb_ah_mctl_receive(q, mp);
406		break;
407
408	case M_FLUSH:
409	case M_IOCACK:
410	case M_IOCNAK:
411		putnext(q, mp);
412		break;
413
414	default:
415		putnext(q, mp);
416		break;
417	}
418
419	return (0);
420}
421
422
423/*
424 * usb_ah_mctl_receive :
425 *	Handle M_CTL messages from hid. If we don't understand
426 *	the command, send it up.
427 */
428static void
429usb_ah_mctl_receive(register queue_t *q, register mblk_t *mp)
430{
431	register usb_ah_state_t *usb_ahd = (usb_ah_state_t *)q->q_ptr;
432	register struct iocblk *iocp;
433	caddr_t  data;
434
435	iocp = (struct iocblk *)mp->b_rptr;
436	if (mp->b_cont != NULL)
437		data = (caddr_t)mp->b_cont->b_rptr;
438
439	switch (iocp->ioc_cmd) {
440	case HID_GET_PARSER_HANDLE:
441		USB_DPRINTF_L3(PRINT_MASK_ALL, usb_ah_log_handle,
442		    "usb_ah_mctl_receive HID_GET_PARSER_HANDL mctl");
443		if ((data != NULL) &&
444		    (iocp->ioc_count == sizeof (hidparser_handle_t)) &&
445		    (MBLKL(mp->b_cont) == iocp->ioc_count)) {
446			usb_ahd->usb_ah_report_descr =
447			    *(hidparser_handle_t *)data;
448		} else {
449			usb_ahd->usb_ah_report_descr = NULL;
450		}
451		freemsg(mp);
452		usb_ahd->usb_ah_flags &= ~USB_AH_QWAIT;
453
454		break;
455	case HID_DISCONNECT_EVENT :
456	case HID_POWER_OFF:
457		USB_DPRINTF_L3(PRINT_MASK_ALL, usb_ah_log_handle,
458		    "usb_ah_mctl_receive HID_DISCONNECT_EVENT/HID_POWER_OFF");
459
460		/* Cancel any auto repeat keys */
461		usb_ah_cancel_timeout(usb_ahd);
462
463		freemsg(mp);
464
465		break;
466	case HID_CONNECT_EVENT:
467	case HID_FULL_POWER:
468		USB_DPRINTF_L3(PRINT_MASK_ALL, usb_ah_log_handle,
469		    "usb_ah_mctl_receive HID_CONNECT_EVENT/HID_FULL_POWER");
470		freemsg(mp);
471
472		break;
473	default:
474		putnext(q, mp);
475	}
476}
477
478
479/*
480 * usb_ah_repeat_send
481 *	This function sends a M_CTL message to usb_ac repeatedly
482 */
483static void
484usb_ah_repeat_send(usb_ah_state_t *usb_ahd, usb_ah_button_descr_t *bd,
485		struct iocblk mctlmsg, char *buf, int len)
486{
487	mblk_t	*dup_mp;
488
489	bd->mblk = usb_ah_mk_mctl(mctlmsg, buf, len);
490
491	if (bd->mblk != NULL) {
492		dup_mp = usb_ah_cp_mblk(bd->mblk);
493
494		if (dup_mp != NULL) {
495			mutex_exit(&usb_ahd->usb_ah_mutex);
496			putnext(usb_ahd->usb_ah_readq, dup_mp);
497			mutex_enter(&usb_ahd->usb_ah_mutex);
498		}
499
500		usb_ahd->usb_ah_cur_bd = bd;
501		usb_ahd->usb_ah_tid = qtimeout(usb_ahd->usb_ah_readq,
502		    usb_ah_timeout, bd, usb_ah_rpt_tick);
503	}
504}
505
506
507/*
508 * usb_ah_timeout:
509 *	Timeout routine to handle autorepeat of buttons
510 */
511static void
512usb_ah_timeout(void *addr)
513{
514	usb_ah_button_descr_t *bd;
515	usb_ah_state_t	*usb_ahd;
516	mblk_t		*dup_mp;
517
518	bd = (usb_ah_button_descr_t *)addr;
519	usb_ahd = (usb_ah_state_t *)bd->uahp;
520
521	mutex_enter(&usb_ahd->usb_ah_mutex);
522
523	/*
524	 * If a release event still hasn't reached, tid will be non-zero
525	 * Send another press event up
526	 */
527	if (usb_ahd->usb_ah_tid) {
528		dup_mp = usb_ah_cp_mblk(bd->mblk);
529		if (dup_mp != NULL) {
530			mutex_exit(&usb_ahd->usb_ah_mutex);
531			putnext(usb_ahd->usb_ah_readq, dup_mp);
532			mutex_enter(&usb_ahd->usb_ah_mutex);
533		}
534		if (bd->mblk != NULL) {
535			usb_ahd->usb_ah_cur_bd = bd;
536			usb_ahd->usb_ah_tid = qtimeout(usb_ahd->usb_ah_readq,
537			    usb_ah_timeout, bd, usb_ah_rpt_tick);
538		}
539	}
540	mutex_exit(&usb_ahd->usb_ah_mutex);
541}
542
543
544/*
545 * usb_ah_cancel_timeout:
546 *	Cancels the timeout for autorepeat sequence
547 */
548static void
549usb_ah_cancel_timeout(usb_ah_state_t *usb_ahd)
550{
551	queue_t	*rq = usb_ahd->usb_ah_readq;
552
553	if (usb_ahd->usb_ah_tid) {
554		(void) quntimeout(rq, usb_ahd->usb_ah_tid);
555		usb_ahd->usb_ah_tid = 0;
556		usb_ahd->usb_ah_cur_bd->pressed = 0;
557		freemsg(usb_ahd->usb_ah_cur_bd->mblk);
558		usb_ahd->usb_ah_cur_bd = NULL;
559	}
560}
561
562
563/*
564 * usb_ah_cp_mblk
565 *	Create an identical 2-mblk as the one passed through argument
566 */
567static mblk_t *
568usb_ah_cp_mblk(mblk_t *mp)
569{
570	mblk_t *bp1, *bp2;
571	int len;
572	struct iocblk	*iocp;
573
574	if ((bp1 = allocb((int)sizeof (struct iocblk), BPRI_HI)) == NULL) {
575		USB_DPRINTF_L4(PRINT_MASK_ALL, usb_ah_log_handle,
576		    "usb_ah_cp_mblk: 1st allocb failed");
577
578		return (NULL);
579	}
580
581	iocp = (struct iocblk *)mp->b_rptr;
582	bcopy(iocp, (struct iocblk *)bp1->b_datap->db_base,
583	    sizeof (struct iocblk));
584
585	bp1->b_datap->db_type = M_CTL;
586	bp1->b_wptr += sizeof (struct iocblk);
587
588	ASSERT(mp->b_cont != NULL);
589	len = MBLKL(mp->b_cont);
590
591	if (mp->b_cont->b_datap->db_base) {
592		if ((bp2 = allocb(len, BPRI_HI)) == NULL) {
593			USB_DPRINTF_L4(PRINT_MASK_ALL, usb_ah_log_handle,
594			    "usb_ah_cp_mblk: 2nd allocb failed");
595			freemsg(bp1);
596
597			return (NULL);
598		}
599		bp1->b_cont = bp2;
600		bcopy(mp->b_cont->b_datap->db_base, bp2->b_datap->db_base, len);
601		bp2->b_wptr += len;
602	}
603
604	return (bp1);
605}
606
607
608/*
609 * usb_ah_get_cooked_rd:
610 *	Cook the report descriptor by making hidparser calls and
611 *	put them in a library
612 */
613static int
614usb_ah_get_cooked_rd(usb_ah_state_t *usb_ahd)
615{
616	uint_t		location;
617	uint_t		offset, i;
618	usb_ah_button_descr_t	*bd;
619	hidparser_usage_info_t	*ud;
620	usb_ah_rpt_t	*rpt;
621	hidparser_rpt_t	*hid_rpt;
622
623	rpt = &(usb_ahd->usb_ah_report[USB_AH_INPUT_RPT]);
624	hid_rpt = &(usb_ahd->usb_ah_report[USB_AH_INPUT_RPT].hid_rpt);
625
626	if (hidparser_get_usage_list_in_order(
627	    usb_ahd->usb_ah_report_descr,
628	    usb_ahd->usb_ah_report_id,
629	    HIDPARSER_ITEM_INPUT,
630	    hid_rpt) == HIDPARSER_FAILURE) {
631		USB_DPRINTF_L3(PRINT_MASK_OPEN,
632		    usb_ah_log_handle, "getting usage list in order failed");
633
634		return (USB_FAILURE);
635	}
636
637	USB_DPRINTF_L4(PRINT_MASK_OPEN, usb_ah_log_handle,
638	    "usb_ah_open:no. of usages=%d", hid_rpt->no_of_usages);
639
640	location = offset = 0;
641	for (i = 0; i < hid_rpt->no_of_usages; i++) {
642		USB_DPRINTF_L4(PRINT_MASK_OPEN,
643		    usb_ah_log_handle, "collection=0x%x, usage=0x%x/0x%x",
644		    hid_rpt->usage_descr[i].collection_usage,
645		    hid_rpt->usage_descr[i].usage_page,
646		    hid_rpt->usage_descr[i].usage_id);
647		ud = &(hid_rpt->usage_descr[i]);
648		bd = &(rpt->button_descr[i]);
649
650		/* Initialize the variables */
651		hid_rpt->main_item_value = 0;
652
653		/* get input items for each usages */
654		(void) hidparser_get_main_item_data_descr(
655		    usb_ahd->usb_ah_report_descr,
656		    usb_ahd->usb_ah_report_id,
657		    HIDPARSER_ITEM_INPUT,
658		    hid_rpt->usage_descr[i].usage_page,
659		    hid_rpt->usage_descr[i].usage_id,
660		    &hid_rpt->main_item_value);
661
662		bd->location = location;
663		bd->offset = offset;
664		bd->no_of_bits = ud->rptsz;
665
666		USB_DPRINTF_L4(PRINT_MASK_ALL, usb_ah_log_handle,
667		    "byte location %d, bit offset %d", bd->location,
668		    bd->offset);
669		offset += ud->rptsz;
670		while (offset >= 8) {
671			location++;
672			offset -= 8;
673		}
674
675	}
676
677	return (USB_SUCCESS);
678}
679
680
681/*
682 * usb_ah_check_usage_send_data:
683 *	Check if a button is pressed, if so, send the appropriate
684 *	message	up
685 */
686static void
687usb_ah_check_usage_send_data(usb_ah_state_t *usb_ahd, mblk_t *mp)
688{
689	int			i, mask;
690	char			val;
691	hidparser_rpt_t		*hid_rpt;
692	usb_ah_button_descr_t	*bd;
693	usb_ah_rpt_t		*rpt;
694	uchar_t			*ptr;
695	struct iocblk		mctlmsg;
696	mblk_t			*mctl_ptr;
697
698	mutex_enter(&usb_ahd->usb_ah_mutex);
699	rpt = &(usb_ahd->usb_ah_report[USB_AH_INPUT_RPT]);
700	hid_rpt = &(usb_ahd->usb_ah_report[USB_AH_INPUT_RPT].hid_rpt);
701
702	for (i = 0; i < hid_rpt->no_of_usages; i++) {
703
704		bd = &(rpt->button_descr[i]);
705		bd->uahp = (void *)usb_ahd;
706
707		USB_DPRINTF_L4(PRINT_MASK_ALL,
708		    usb_ah_log_handle, "usb_ah_check_usage_send_data:"
709		    "uses_report_id=%d, location=%d, offset=%d, "
710		    "no_of_bits=%d", usb_ahd->usb_ah_uses_report_ids,
711		    bd->location, bd->offset, bd->no_of_bits);
712
713		ptr = mp->b_rptr + bd->location;
714
715		/* XXX workaround */
716		if (ptr > mp->b_wptr) {
717			USB_DPRINTF_L2(PRINT_MASK_ALL,
718			    usb_ah_log_handle, "usb_ah_check_usage_send_data:"
719			    "bad report: location=%d", bd->location);
720
721			continue;
722		}
723
724		ASSERT(ptr <= mp->b_wptr);
725
726		mask = ((1 << bd->no_of_bits) - 1);
727		val = (char)((*ptr >> bd->offset) & mask);
728
729		USB_DPRINTF_L4(PRINT_MASK_ALL,
730		    usb_ah_log_handle, "usb_ah_check_usage_send_data:"
731		    "usage=0x%x, "
732		    "mask=0x%x, val=0x%x", hid_rpt->usage_descr[i].usage_id,
733		    mask, val);
734
735		if (hid_rpt->usage_descr[i].collection_usage !=
736		    HID_CONSUMER_CONTROL) {
737			/*
738			 * skip item in unknown collections, for now.
739			 * this includes the volume and mute controls
740			 * in the microphone collection on plantronics
741			 * dsp-300 device with 3.xx firmware.
742			 */
743			continue;
744		}
745
746		switch (hid_rpt->usage_descr[i].usage_id) {
747		case HID_CONSUMER_VOL:	/* LC */
748			if (val != 0) {
749				if (hid_rpt->main_item_value &
750				    HID_MAIN_ITEM_RELATIVE) {
751					/* Relative volume */
752					mctlmsg.ioc_cmd = USB_AUDIO_VOL_CHANGE;
753					mctlmsg.ioc_count = sizeof (uint_t);
754					mctl_ptr = usb_ah_mk_mctl(mctlmsg,
755					    &val, mctlmsg.ioc_count);
756					if (mctl_ptr != NULL) {
757						mutex_exit(&usb_ahd->
758						    usb_ah_mutex);
759						putnext(usb_ahd->usb_ah_readq,
760						    mctl_ptr);
761						mutex_enter(&usb_ahd->
762						    usb_ah_mutex);
763					}
764				} else {
765					USB_DPRINTF_L2(PRINT_MASK_ALL,
766					    usb_ah_log_handle, "usb_ah_rput:"
767					    "Absolute volume change "
768					    "not supported");
769				}
770			}
771
772			break;
773		case HID_CONSUMER_VOL_DECR: /* RTC */
774			if (val != 0) {
775				val = -val;
776			}
777			/* FALLTHRU */
778		case HID_CONSUMER_VOL_INCR:  /* RTC */
779			if (val != 0) {
780
781				/*
782				 * If another autorepeating button has been
783				 * pressed, cancel that one first
784				 */
785				usb_ah_cancel_timeout(usb_ahd);
786				mctlmsg.ioc_cmd = USB_AUDIO_VOL_CHANGE;
787				mctlmsg.ioc_count = sizeof (uint_t);
788				bd->pressed = 1;
789				usb_ah_repeat_send(usb_ahd, bd,
790				    mctlmsg, (char *)&val, mctlmsg.ioc_count);
791			} else {
792				/* Do not steal other's release event */
793				if (bd->pressed) {
794					usb_ah_cancel_timeout(usb_ahd);
795				}
796			}
797
798			break;
799		case HID_CONSUMER_MUTE:	/* OOC */
800			if (val) {
801				mctlmsg.ioc_cmd = USB_AUDIO_MUTE;
802				mctlmsg.ioc_count = sizeof (uint_t);
803				mctl_ptr = usb_ah_mk_mctl(mctlmsg,
804				    &val, mctlmsg.ioc_count);
805				if (mctl_ptr != NULL) {
806					mutex_exit(&usb_ahd->usb_ah_mutex);
807					putnext(usb_ahd->usb_ah_readq,
808					    mctl_ptr);
809					mutex_enter(&usb_ahd->usb_ah_mutex);
810				}
811
812			}
813
814			break;
815		case HID_CONSUMER_BASS:
816		case HID_CONSUMER_TREBLE:
817		default:
818
819			break;
820		}
821	}
822	mutex_exit(&usb_ahd->usb_ah_mutex);
823	freemsg(mp);
824}
825
826
827/*
828 * since usb_ac now uses LDI to access HID streams, we must change the msg
829 * type from M_CTL to M_PROTO since the streamhead will not pass M_CTLs up
830 */
831static mblk_t *
832usb_ah_mk_mctl(struct iocblk mctlmsg, void *buf, size_t len)
833{
834	mblk_t *mp;
835
836	mp = usba_mk_mctl(mctlmsg, buf, len);
837	if (mp == NULL)
838		return (NULL);
839
840	mp->b_datap->db_type = M_PROTO;
841	return (mp);
842}
843