usb_ah.c revision d29f5a711240f866521445b1656d114da090335e
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* USB audio hid streams module - processes hid data
* from HID driver and converts to a format that SADA
* understands. The stack looks like this :
* hid --> usb_ah --> usb_ac --> audio framework
* usb_ac just acts as a passthrough layer for the converted data.
*
* During open, usb_ah gets the parser handle from hid and gets
* the hardware information passed as report descriptor. Then
* it finds out the relevant usages and stores the bitmap and other
* information in internal data structure. When a button is pressed
* hid sends that data up through the streams. usb_ah, upon getting
* this information and with the prior knowledge about the bitmap
* for each button, calculates the value and sends up to SADA
* through usb_ac. SADA in turn sends a command down to speaker to
* increase the volume of the speaker that is managed by usb_ac.
*/
#include <sys/audiovar.h>
/* debugging information */
static usb_log_handle_t usb_ah_log_handle;
/*
* Internal Function Prototypes
*/
static void usb_ah_timeout(void *);
struct iocblk, char *, int);
static void usb_ah_cancel_timeout(usb_ah_state_t *);
static int usb_ah_get_cooked_rd(usb_ah_state_t *);
/* stream qinit functions defined here */
/*
* Global Variables
*/
int usb_ah_rpt_tick;
static struct streamtab usb_ah_info;
"usb_ah",
};
/*
* Module linkage information for the kernel.
*/
extern struct mod_ops mod_strmodops;
static struct modlstrmod modlstrmod = {
"USB audio hid streams",
&fsw
};
static struct modlinkage modlinkage = {
(void *)&modlstrmod,
};
/*
* Warlock is not aware of the automatic locking mechanisms for
* streams modules.
* Since warlock is not aware of the streams perimeters, these notes
* have been added.
*/
/*
* Module qinit functions
*/
static struct module_info usb_ah_minfo = {
0, /* module id number */
"usb_ah", /* module name */
0, /* min packet size accepted */
INFPSZ, /* max packet size accepted */
2048, /* hi-water mark */
128 /* lo-water mark */
};
/* read side for key data and ioctl replies */
static struct qinit usb_ah_rinit = {
(int (*)())usb_ah_rput,
(int (*)())NULL, /* service not used */
(int (*)())NULL,
};
/* write side for ioctls */
static struct qinit usb_ah_winit = {
(int (*)())usb_ah_wput,
(int (*)())NULL,
(int (*)())NULL,
};
static struct streamtab usb_ah_info = {
NULL, /* for muxes */
NULL, /* for muxes */
};
int
_init()
{
if (rval == 0) {
}
return (rval);
}
int
_fini()
{
if (rval == 0) {
}
return (rval);
}
int
{
}
/*
* usb_ah_open :
* Open a usb audio hid device
*/
/* ARGSUSED */
static int
{
if (q->q_ptr) {
"usb_ah_open already opened");
return (0); /* already opened */
}
switch (sflag) {
case MODOPEN:
break;
case CLONEOPEN:
"usb_ah_open: Clone open not supported");
/* FALLTHRU */
default:
return (EINVAL);
}
"usb_ah_state= 0x%p", (void *)usb_ahd);
/*
* Set up private data.
*/
usb_ahd->usb_ah_readq = q;
/*
* Set up queue pointers, so that the "put" procedure will accept
* the reply to the "ioctl" message we send down.
*/
qprocson(q);
/* request hid report descriptor from HID */
/* failure to allocate M_CTL message */
qprocsoff(q);
return (ENOMEM);
}
/*
* Now that signal has been sent, wait for report descriptor.
* Cleanup if user signals in the mean time
*/
if (qwait_sig(q) == 0) {
usb_ahd->usb_ah_flags = 0;
qprocsoff(q);
return (EINTR);
}
}
/* round up to the nearest byte */
} else {
/* add more more byte for report id */
}
qprocsoff(q);
return (EIO);
}
} else {
"usb_ah: Invalid Report Descriptor Tree.");
qprocsoff(q);
return (EIO);
}
"usb_ah_open exiting");
return (0);
}
/*
* usb_ah_close :
* Close a audio hid device
*/
/* ARGSUSED1 */
static int
{
/*
* Since we're about to destroy our private data, turn off
* our open flag first, so we don't accept any more input
* and try to use that data.
*/
usb_ahd->usb_ah_flags = 0;
qprocsoff(q);
"usb_ah_close exiting");
return (0);
}
/*
* usb_ah_wput :
* usb_ah module output queue put procedure: handles M_IOCTL
* messages.
*/
static void
{
"usb_ah_wput entering");
case M_FLUSH:
}
}
break;
default:
break;
}
"usb_ah_wput exiting:3");
}
/*
* usb_ah_rput :
* Put procedure for input from driver end of stream (read queue).
*/
static void
{
"usb_ah_rput: Begin, usb_ah state=0x%p, q=0x%p, mp=0x%p",
if (usb_ahd == 0) {
return;
}
case M_FLUSH:
return;
case M_DATA:
return;
/*
* Process this report if the device doesn't have
* multiple reports, or this is the one we support
*/
if ((usb_ahd->usb_ah_report_id ==
/* we now have a complete packet */
} else {
"usb_ah_rput: skipping report with "
/* skip the reports we don't support */
}
} else {
/* filter out spurious packets */
}
break;
case M_CTL:
usb_ah_mctl_receive(q, mp);
return;
case M_IOCACK:
case M_IOCNAK:
return;
default:
return;
}
}
/*
* usb_ah_mctl_receive :
* Handle M_CTL messages from hid. If we don't understand
* the command, send it up.
*/
static void
{
case HID_GET_PARSER_HANDLE:
"usb_ah_mctl_receive HID_GET_PARSER_HANDL mctl");
*(hidparser_handle_t *)data;
} else {
}
break;
case HID_DISCONNECT_EVENT :
case HID_POWER_OFF:
"usb_ah_mctl_receive HID_DISCONNECT_EVENT/HID_POWER_OFF");
/* Cancel any auto repeat keys */
break;
case HID_CONNECT_EVENT:
case HID_FULL_POWER:
"usb_ah_mctl_receive HID_CONNECT_EVENT/HID_FULL_POWER");
break;
default:
}
}
/*
* usb_ah_repeat_send
* This function sends a M_CTL message to usb_ac repeatedly
*/
static void
{
}
}
}
/*
* usb_ah_timeout:
* Timeout routine to handle autorepeat of buttons
*/
static void
usb_ah_timeout(void *addr)
{
/*
* If a release event still hasn't reached, tid will be non-zero
* Send another press event up
*/
if (usb_ahd->usb_ah_tid) {
}
}
}
}
/*
* usb_ah_cancel_timeout:
* Cancels the timeout for autorepeat sequence
*/
static void
{
if (usb_ahd->usb_ah_tid) {
usb_ahd->usb_ah_tid = 0;
}
}
/*
* usb_ah_cp_mblk
* Create an identical 2-mblk as the one passed through argument
*/
static mblk_t *
{
int len;
"usb_ah_cp_mblk: 1st allocb failed");
return (NULL);
}
sizeof (struct iocblk));
"usb_ah_cp_mblk: 2nd allocb failed");
return (NULL);
}
}
return (bp1);
}
/*
* usb_ah_get_cooked_rd:
* Cook the report descriptor by making hidparser calls and
* put them in a library
*/
static int
{
hid_rpt) == HIDPARSER_FAILURE) {
usb_ah_log_handle, "getting usage list in order failed");
return (USB_FAILURE);
}
for (i = 0; i < hid_rpt->no_of_usages; i++) {
usb_ah_log_handle, "collection=0x%x, usage=0x%x/0x%x",
/* Initialize the variables */
hid_rpt->main_item_value = 0;
/* get input items for each usages */
while (offset >= 8) {
location++;
offset -= 8;
}
}
return (USB_SUCCESS);
}
/*
* usb_ah_check_usage_send_data:
* Check if a button is pressed, if so, send the appropriate
* message up
*/
static void
{
int i, mask;
char val;
for (i = 0; i < hid_rpt->no_of_usages; i++) {
usb_ah_log_handle, "usb_ah_check_usage_send_data:"
"uses_report_id=%d, location=%d, offset=%d, "
/* XXX workaround */
usb_ah_log_handle, "usb_ah_check_usage_send_data:"
continue;
}
usb_ah_log_handle, "usb_ah_check_usage_send_data:"
"usage=0x%x, "
/*
* skip item in unknown collections, for now.
* this includes the volume and mute controls
* in the microphone collection on plantronics
* dsp-300 device with 3.xx firmware.
*/
continue;
}
case HID_CONSUMER_VOL: /* LC */
if (val != 0) {
if (hid_rpt->main_item_value &
/* Relative volume */
mctl_ptr);
}
} else {
usb_ah_log_handle, "usb_ah_rput:"
"Absolute volume change "
"not supported");
}
}
break;
case HID_CONSUMER_VOL_DECR: /* RTC */
if (val != 0) {
}
/* FALLTHRU */
case HID_CONSUMER_VOL_INCR: /* RTC */
if (val != 0) {
/*
* If another autorepeating button has been
* pressed, cancel that one first
*/
} else {
/* Do not steal other's release event */
}
}
break;
case HID_CONSUMER_MUTE: /* OOC */
if (val) {
mctl_ptr);
}
}
break;
case HID_CONSUMER_BASS:
case HID_CONSUMER_TREBLE:
default:
break;
}
}
}