usb_ac_dacf.c revision d291d9f21e8c0417aec99de243dd48bc400002d0
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* This is a dacf module for usb audio plumbing/unplumbing based
* upon the Extensions to Device Autoconfiguration project. See
* PSARC/1998/212 for more details.
*
* This module provides the dacf functions to be called after a
* driver has attached and before it detaches.
* Particularly, the usb_ac_mux_plumbing() is called after
* usb_ac_attach() is called and usb_ac_mux_unplumbing() is called
* before usb_ac_detach() is going to be called. This facilitates
* the configuration of usb_as and hid under usb_ac and also
* provides * usb_ac and usb_as hotplugging.
*
* This dacf module only supports usb_as and hid plumbing under
* usb_ac.
*
* Note: This module shares some common data structures with usb_ac
* module. So it would require dependency on usb_ac for the module
* loading. But the side effect for usb_ac_dacf to depend on usb_ac
* is a deadlock situation created when doing "modunload -i
* <usb_ac_dacf_mod_id>" before "modunload -i <usb_ac_mod_id".
* To avoid the dependency, space_store() and space_fetch() are
* used to share the usb_ac data structure with usb_ac_dacf.
*/
#include <sys/audiovar.h>
/* for getting the minor node info from hid */
/*
* Dacf entry points
*/
/*
* External functions
*/
#define INIT_PROCESS_CNT 3
extern uintptr_t space_fetch(char *);
/*
* Internal functions
*/
static void usb_ac_print_reg_data(usb_ac_state_t *,
static int usb_ac_setup_plumbed(usb_ac_state_t *, int, int, int);
static int usb_ac_mixer_registration(usb_ac_state_t *,
static void usb_ac_hold_siblings(usb_ac_state_t *);
static int usb_ac_online_siblings(usb_ac_state_t *);
static void usb_ac_rele_siblings(usb_ac_state_t *);
static am_ad_entry_t *usb_ac_entry;
/* just generic, USB Audio, 1.0 spec-compliant */
static audio_device_t usb_dev_info =
{ {"USB Audio"}, {"1.0"}, {"external"} };
static dacf_op_t usb_ac_plumb_op[] = {
{ DACF_OPID_END, NULL },
};
static dacf_opset_t opsets[] = {
{ "usb_audio_config", usb_ac_plumb_op },
};
struct dacfsw usb_audio_dacfsw = {
};
struct modldacf usb_audio_dacf = {
&mod_dacfops, /* Type of module */
"USB_AC_DACF %I%",
};
struct modlinkage usb_audio_modlinkage = {
};
int
_init(void)
{
return (mod_install(&usb_audio_modlinkage));
}
int
_fini()
{
return (mod_remove(&usb_audio_modlinkage));
}
int
{
}
/*
* This routine is called at post attach time for the usb_ac module
* by the DACF framework.
*/
/*ARGSUSED*/
static int
{
int instance;
int error;
/* get the usb_ac dip */
/* Retrieve the soft state information */
NULL) {
return (DACF_FAILURE);
}
return (DACF_FAILURE);
}
/* Access to the global variables is synchronized */
"usb_ac_mux_plumbing callback, state=%d",
/*
* by design, dacf will try to plumb again if an unplumb failed.
* therefore, just return success
*/
"audio streams driver already plumbed");
return (DACF_SUCCESS);
}
/* usb_as and hid should be attached but double check */
"no audio streams driver plumbed");
return (DACF_FAILURE);
}
"major 0x%x, minor %d usb_ac_mux_plumbing: driver name "
/* bring the device to full power */
/* avoid dips disappearing while we are plumbing */
/* opening usb_ac for plumbing underneath */
if (error == 0) {
}
if (error) {
"open failed, error=%d", error);
return (DACF_FAILURE);
}
"mux_lh=0x%p", mux_lh);
/*
* walk all siblings and create the usb_ac<->usb_as and
* usb_ac<->hid streams. return of 0 indicates no or
*/
/* pretend that we are plumbed so we can unplumb */
"no audio streams driver plumbed");
return (DACF_FAILURE);
}
/* restore state if we have already registered with the mixer */
if (uacp->usb_ac_registered_with_mixer) {
"mixer registration failed");
return (DACF_FAILURE);
}
return (DACF_SUCCESS);
}
/*
* This is the pre-detach routine invoked for usb_ac module by DACF framework
*/
/*ARGSUSED*/
static int
{
int i, error;
int maxlinked = 0;
/* this is the usb_ac dip */
/* Retrieve the soft state information */
NULL) {
return (DACF_FAILURE);
}
return (DACF_FAILURE);
}
"usb_ac_mux_unplumbing callback, state=%d",
"already unplumbed!");
return (DACF_SUCCESS);
}
/* usb_ac might not have anything plumbed yet */
"nothing plumbed!");
goto close_mux;
}
/* do not allow detach if still busy */
if (uacp->usb_ac_busy_count) {
return (DACF_FAILURE);
}
/* wait till tasks have drained using save state */
"usb_ac_mux_unplumbing mux_lh 0x%p", mux_lh);
/* unlink and close ac-as and ac-hid streams */
"usb_ac_mux_unplumbing maxlinked %d", maxlinked);
for (i = 0; i < maxlinked; i++) {
"usb_ac_mux_unplumbing linkid %d ", linkid);
if (child_dip) {
int rval;
"%s%d: unplink, error=%d",
}
}
if (mux_lh) {
"closed mux");
}
"usb_ac_mux_unplumbing: done");
return (DACF_SUCCESS);
}
/*
* walk all siblings and create the ac<->as and ac<->hid streams
*/
static int
{
int drv_instance;
int linkid;
int error;
int count = 0;
/* ignore own dip */
continue;
}
/* ignore other dip other than usb_as and hid */
} else {
continue;
}
continue;
}
if (DEVI_IS_DEVICE_REMOVED(child_dip)) {
continue;
}
if (error == 0) {
}
if (error) {
"ldi_open_by_dev failed on major = %d minor = %d, "
return (0);
}
/* get registration data */
USB_SUCCESS) {
"get_reg_data failed on major = %d "
"minor = %d, name = %s", drv_major,
return (0);
}
int rval;
/* push usb_ah module on top of hid */
if (error) {
"ldi_ioctl failed for usb_ah, "
"major:%d minor:%d name:%s",
/* skip plumbing the hid driver */
continue;
}
} else {
/* should not be here */
"usb_ac_mux_plumbing: unknown module");
count --;
/* skip plumbing an unknown module */
continue;
}
/* usb_ac_plumb_ioctl sets lwq, this entry should be free */
/* plumbing one at a time */
if (error) {
"plink failed for major:%d minor:%d"
return (0);
}
/* lwq should be set by usb_ac_plumb_ioctl by now */
}
"%d drivers plumbed under usb_ac mux", count);
return (count);
}
/*
* usb_ac_find_default_port:
*/
static int
{
int i;
for (i = 0; i < 32; i++) {
if (port & (1 << i)) {
return (1 << i);
}
}
return (0);
}
/*
* Register with mixer only after first plumbing.
* Also do not register if earlier reg data
* couldn't be received from at least one
* streaming interface
*/
static int
{
"usb_ac_mixer_registration: infp=0x%p, dflts=0x%p",
if (uacp->usb_ac_registered_with_mixer) {
return (USB_SUCCESS);
}
for (n = 0; n < USB_AC_MAX_AS_PLUMBED; n++) {
break;
}
}
/* Haven't found a streaming interface; fail mixer registration */
if (n > USB_AC_MAX_AS_PLUMBED) {
"no streaming interface");
return (USB_FAILURE);
}
"usb_ac_mixer_registration: mixer enabled =%d",
info->ad_add_mode = 0;
dflts->monitor_gain = 0;
dflts->hw_features = 0;
/*
* Fill out streaming interface specific stuff
* Note that we handle only one playing and one recording
* streaming interface at the most
*/
for (n = 0; n < USB_AC_MAX_AS_PLUMBED; n++) {
continue;
}
continue;
}
/* set first format so get_featureID can be succeed */
/* check if any channel supports vol. control for this fmt */
USB_AUDIO_VOLUME_CONTROL)) != -1) {
"dir=%d featureID=%d",
break;
}
}
"mode=%d chs=%d default_gain=%d id=%d",
nplay++;
/* no support for mixer unit */
} else {
nrec++;
}
}
/*
* we pretend to always support AUDIO_HWFEATURE_IN2OUT
* since there is no simple way to find out at this
* point
*/
}
/* the rest */
info->ad_diag_flags = 0;
info->ad_assist_flags = 0;
info->ad_translate_flags = 0;
"am_attach: failed");
return (USB_FAILURE);
}
"am_attach succeeded");
return (USB_SUCCESS);
}
/*
* get registration data from usb_as driver unless we already
* have 2 registrations
*/
static int
{
/* if already registered, just setup data structures again */
if (uacp->usb_ac_registered_with_mixer) {
}
for (n = 0; n < USB_AC_MAX_AS_PLUMBED; n ++) {
/*
* We haven't received registration data
* from n-th streaming interface in the array
*/
break;
}
}
if (n >= USB_AC_MAX_AS_PLUMBED) {
"More than 2 streaming interfaces (play "
return (USB_FAILURE);
}
/* take the stream reg struct with the same index */
"regdata from usb_as: streams_reg=0x%p, n=%d",
streams_reg, n);
"ldi_ioctl fails for mixer registration, error=%d", error);
return (USB_FAILURE);
} else {
"usb_ac_streams: index=%d, received_reg_data=%d type=%s",
"play" : "record"));
return (rval);
}
}
/*
* setup plumbed and stream info structure, either initially or
* after replumbing
* On replumbing, str_idx and reg_idx are -1
*/
static int
int reg_idx)
{
int i;
if (str_idx == -1) {
/* find a free streams info structure */
for (i = 0; i < USB_AC_MAX_AS_PLUMBED; i++) {
break;
}
}
ASSERT(i < USB_AC_MAX_AS_PLUMBED);
str_idx = i;
}
if (reg_idx == -1) {
/*
* find the corresponding registration structure, match
* on interface number and not on dip since dip may have
* changed
*/
for (i = 0; i < USB_AC_MAX_AS_PLUMBED; i++) {
break;
}
}
if (i == USB_AC_MAX_AS_PLUMBED) {
"no corresponding registration structure");
return (USB_FAILURE);
}
reg_idx = i;
}
"usb_ac_setup_plumbed: plb_idx=%d str_idx=%d reg_idx=%d",
return (USB_SUCCESS);
}
/*
* function to dump registration data
*/
static void
{
int n;
"usb_ac_print_reg_data: Begin valid=%d, play=%d, "
"n_formats=%d, mixer srs ptr=0x%p, compat srs ptr=0x%p",
for (n = 0; n < reg->reg_n_formats; n++) {
"format%d: alt=%d chns=%d prec=%d enc=%d", n,
}
"combinations: %d %d %d %d %d %d %d %d",
for (n = 0; n < USB_AS_N_FORMATS; n++) {
}
for (n = 0; n < USB_AS_N_CHANNELS; n++) {
}
for (n = 0; n < USB_AS_N_COMBINATIONS; n++) {
"reg_combinations[%d] ptr=0x%p", n,
®->reg_combinations[n]);
}
"usb_ac_print_reg_data: End");
}
static int
{
int rval = USB_SUCCESS;
"usb_ac_onlining siblings");
/* Online the child_dip of usb_as and hid, if not already */
NDI_SUCCESS) {
"failure to online driver %s%d",
/* only onlining usb_as is fatal */
"usb_as") == 0) {
rval = USB_FAILURE;
break;
}
}
}
}
return (rval);
}
/*
* hold all audio children before or after plumbing
* online usb_as and hid, if not already
*/
static void
{
int circ;
"usb_ac_hold_siblings:");
/* hold all siblings and ourselves */
/* hold the children */
"usb_ac_hold_siblings: %s%d ref=%d",
}
}
/*
* release all audio children before or after plumbing
*/
static void
{
int circ;
"usb_ac_rele_siblings:");
/* release all siblings and ourselves */
"usb_ac_rele_siblings: %s%d ref=%d",
}
}